use anyhow::anyhow;
use std::fmt;
use tch::{IndexOp, Tensor};
#[cfg(feature = "warnings")]
use tracing::warn;
#[cfg(not(feature = "warnings"))]
macro_rules! warn {
($($arg:tt)*) => {};
}
pub trait Optimizer: Send + Sync + fmt::Display {
fn optimize(&self, function: &dyn Fn(&Tensor) -> Tensor, x0: &Tensor)
-> anyhow::Result<Tensor>;
}
fn validate_optimizer_output(tensor: &Tensor, optimizer_name: &str) -> anyhow::Result<()> {
if tensor.isfinite().f_all()?.f_int64_value(&[])? == 0 {
anyhow::bail!(
"Optimizer {} produced non-finite result (NaN/Inf)",
optimizer_name
);
}
Ok(())
}
pub struct Newton {
max_steps: usize,
gtol: Option<f64>,
ftol: Option<f64>,
}
pub struct BFGS {
max_steps: usize,
gtol: Option<f64>,
ftol: Option<f64>,
}
pub struct Halley {
max_steps: usize,
gtol: Option<f64>,
ftol: Option<f64>,
}
pub struct CG {
max_steps: usize,
gtol: Option<f64>,
ftol: Option<f64>,
}
pub(crate) fn differentiate(
function: &dyn Fn(&Tensor) -> Tensor,
x: &Tensor,
) -> anyhow::Result<Tensor> {
let x_with_grad = x.f_detach()?.copy().set_requires_grad(true);
let y = function(&x_with_grad);
if y.size() != [] as [i64; 0] {
return Err(anyhow!(
"Bad shape of `y`. Expected [], but got {:?}",
y.size()
));
}
if !y.requires_grad() {
return Ok(tch::Tensor::f_zeros(x.size(), (x.kind(), x.device()))?);
}
let gradient = tch::Tensor::f_run_backward(&[y], &[x_with_grad], false, false)?[0].copy();
Ok(gradient)
}
pub(crate) fn gradient_and_hessian(
function: &dyn Fn(&Tensor) -> Tensor,
x: &Tensor,
) -> anyhow::Result<(Tensor, Tensor)> {
let x_with_grad = x.f_detach()?.copy().set_requires_grad(true);
let y = function(&x_with_grad);
if y.size() != [] as [i64; 0] {
return Err(anyhow!(
"Bad shape of `y`. Expected [], but got {:?}",
y.size()
));
}
if !y.requires_grad() {
return Ok((
tch::Tensor::f_zeros(x.size(), (x.kind(), x.device()))?,
tch::Tensor::f_zeros([x.size()[0], x.size()[0]], (x.kind(), x.device()))?,
));
}
let grad = Tensor::f_run_backward(&[y], &[&x_with_grad], true, true)?[0].copy();
let grad_len = grad.size()[0];
let grad_kind = grad.kind();
let grad_device = grad.device();
if !grad.requires_grad() {
return Ok((
grad,
Tensor::f_zeros([grad_len, grad_len], (grad_kind, grad_device))?,
));
}
let mut vectors = Vec::<Tensor>::with_capacity(grad_len as usize);
for i in 0..grad_len {
vectors.append(&mut Tensor::f_run_backward(
&[grad.i(i)],
&[&x_with_grad],
true,
false,
)?);
}
let grad = grad.f_detach()?;
let hessian = Tensor::f_stack(&vectors, 0)?.f_detach()?;
Ok((grad, hessian))
}
pub(crate) fn derivative_tensors_123(
function: &dyn Fn(&Tensor) -> Tensor,
x: &Tensor,
) -> anyhow::Result<(Tensor, Tensor, Tensor)> {
let x_with_grad = x.f_detach()?.copy().set_requires_grad(true);
let y = function(&x_with_grad);
if y.size() != [] as [i64; 0] {
return Err(anyhow!(
"Bad shape of `y`. Expected [], but got {:?}",
y.size()
));
}
if !y.requires_grad() {
return Ok((
tch::Tensor::f_zeros(x.size(), (x.kind(), x.device()))?,
tch::Tensor::f_zeros([x.size()[0], x.size()[0]], (x.kind(), x.device()))?,
tch::Tensor::f_zeros(
[x.size()[0], x.size()[0], x.size()[0]],
(x.kind(), x.device()),
)?,
));
}
let grad = Tensor::f_run_backward(&[y], &[&x_with_grad], true, true)?[0].copy();
let grad_len = grad.size()[0];
let grad_kind = grad.kind();
let grad_device = grad.device();
if !grad.requires_grad() {
return Ok((
grad,
Tensor::f_zeros([grad_len, grad_len], (grad_kind, grad_device))?,
Tensor::f_zeros([grad_len, grad_len, grad_len], (grad_kind, grad_device))?,
));
}
let mut vectors = Vec::<Tensor>::with_capacity(grad_len as usize);
for i in 0..grad_len {
vectors.append(&mut Tensor::f_run_backward(
&[grad.i(i)],
&[&x_with_grad],
true,
true,
)?);
}
let hessian = Tensor::f_stack(&vectors, 0)?;
if !hessian.requires_grad() {
return Ok((
grad,
hessian,
Tensor::f_zeros([grad_len, grad_len, grad_len], (grad_kind, grad_device))?,
));
}
let mut vectors2 = Vec::<Tensor>::with_capacity(grad_len as usize);
for i in 0..grad_len {
let mut vectors1 = Vec::<Tensor>::with_capacity(grad_len as usize);
for j in 0..grad_len {
vectors1.append(&mut Tensor::f_run_backward(
&[hessian.i((i, j))],
&[&x_with_grad],
true,
false,
)?);
}
vectors2.push(Tensor::f_stack(&vectors1, 0)?);
}
let grad = grad.f_detach()?;
let hessian = hessian.f_detach()?;
let d3_tensor = Tensor::f_stack(&vectors2, 0)?.f_detach()?;
Ok((grad, hessian, d3_tensor))
}
const P0: f64 = 0.0000000001f64;
const PHI2: f64 = 2.618033988749894848207f64;
const RPHI: f64 = 0.618033988749894848207f64;
fn choose_step_golden_section(
x0: &Tensor,
direction: &Tensor,
function: &dyn Fn(&Tensor) -> Tensor,
atol: f64,
) -> anyhow::Result<Tensor> {
let (mut x1, mut x2, mut x3, mut x4): (f64, f64, f64, f64);
let (fx1, mut fx3, mut fx4): (f64, f64, f64);
fx1 = function(&x0).f_double_value(&[])?;
x1 = 0.;
let fx_guess = function(&(x0 + direction * atol * 15.)).f_double_value(&[])?;
x2 = if !fx_guess.is_finite() || fx_guess > fx1 {
P0
} else {
atol * 15.
};
let mut fx = function(&(x0 + direction * x2)).f_double_value(&[])?;
let mut forward_iters: u32 = 0;
while fx <= fx1 {
let new_x2 = x1 + (x2 - x1) * PHI2;
fx = function(&(x0 + direction * new_x2)).f_double_value(&[])?;
if !fx.is_finite() {
break;
}
x2 = new_x2;
forward_iters += 1;
}
x3 = x2 - (x2 - x1) * RPHI;
x4 = x1 + (x2 - x1) * RPHI;
fx3 = function(&(x0 + direction * x3)).f_double_value(&[])?;
fx4 = function(&(x0 + direction * x4)).f_double_value(&[])?;
let mut refine_iters: u32 = 0;
while x2 - x1 > atol && refine_iters < 500 {
if fx3 < fx4 {
x2 = x4;
fx4 = fx3;
x3 = x2 - (x2 - x1) * RPHI;
x4 = x1 + (x2 - x1) * RPHI;
fx3 = function(&(x0 + direction * x3)).f_double_value(&[])?;
} else {
x1 = x3;
fx3 = fx4;
x3 = x2 - (x2 - x1) * RPHI;
x4 = x1 + (x2 - x1) * RPHI;
fx4 = function(&(x0 + direction * x4)).f_double_value(&[])?;
}
refine_iters += 1;
}
if forward_iters >= 100 {
warn!(
"golden section line search: forward search took {} iterations without bracketing a minimum; direction may be poor",
forward_iters
);
}
if refine_iters >= 200 {
warn!(
"golden section line search: refinement took {} iterations; atol may be too small or objective flat",
refine_iters
);
}
Ok(direction * ((x1 + x2) / 2.))
}
fn choose_step_backtracking(
x0: &Tensor,
direction: &Tensor,
function: &dyn Fn(&Tensor) -> Tensor,
grad: &Tensor,
alpha: f64,
beta: f64,
) -> anyhow::Result<Tensor> {
let fx0 = function(&x0).f_double_value(&[])?;
let mut t = 1f64;
let mut backtrack_iters: u32 = 0;
while {
let fx = function(&(x0 + direction * t)).f_double_value(&[])?;
if !fx.is_finite() {
true
} else {
fx > fx0
+ grad
.f_reshape([-1])?
.f_dot(&direction.f_reshape([-1])?)?
.f_double_value(&[])?
* alpha
* t
}
} {
t *= beta;
backtrack_iters += 1;
if t < 1e-30 {
break;
}
}
if backtrack_iters >= 100 {
warn!(
"backtracking line search: {} iterations without satisfying Armijo condition; direction may not be a descent direction",
backtrack_iters
);
}
if t < 1e-30 {
warn!(
"backtracking line search: step size collapsed to {:.3e}; optimizer may be stuck",
t
);
}
Ok(direction.copy() * t)
}
impl CG {
pub fn new(max_steps: usize, gtol: Option<f64>, ftol: Option<f64>) -> Self {
Self {
max_steps,
gtol,
ftol,
}
}
}
impl Optimizer for CG {
fn optimize(
&self,
function: &dyn Fn(&Tensor) -> Tensor,
x0: &Tensor,
) -> anyhow::Result<Tensor> {
if x0.size().len() != 1 {
return Err(anyhow!("`x0` must have rank 1"));
}
let mut prev3_step_norm = 0f64;
let mut prev2_step_norm = 0f64;
let mut prev_step_norm = 0f64;
let mut prev_grad = Tensor::f_zeros_like(&x0)?;
let mut prev_direction = Tensor::f_zeros_like(&x0)?;
let mut prev_y: Option<Tensor> = None;
let mut x = x0.copy();
let mut warned_nonfinite_grad = false;
let mut warned_beta_clamp = false;
let mut warned_nonfinite_iter = false;
for step_num in 0..self.max_steps {
let grad = match differentiate(function, &x) {
Ok(grad) => grad,
Err(e) => {
return Err(anyhow!(
"Runtime error: Differentiation failed in CG optimizer: {}",
e
));
}
};
if !warned_nonfinite_grad && grad.isfinite().f_all()?.f_int64_value(&[])? == 0 {
warn!("CG: non-finite gradient detected; function may be ill-defined");
warned_nonfinite_grad = true;
}
if let Some(gtol) = self.gtol {
if grad.norm().f_double_value(&[])? < gtol {
validate_optimizer_output(&x, "CG")?;
return Ok(x);
}
} else {
if grad.norm().f_double_value(&[])? == 0. {
validate_optimizer_output(&x, "CG")?;
return Ok(x);
}
}
let direction = match step_num {
0 => -&grad,
_ => {
let orthogonality_measure = grad
.f_reshape([-1])?
.f_dot(&prev_grad.f_reshape([-1])?)?
.f_abs()?
/ grad.f_reshape([-1])?.f_dot(&grad.f_reshape([-1])?)?;
if orthogonality_measure.f_double_value(&[])? > 0.2 {
-&grad
} else {
let beta = grad
.f_reshape([-1])?
.f_dot(&(&grad - &prev_grad).f_reshape([-1])?)?
/ prev_grad
.f_reshape([-1])?
.f_dot(&prev_grad.f_reshape([-1])?)?;
let beta = if beta.f_double_value(&[])? > 0. {
beta
} else {
tch::Tensor::f_zeros_like(&beta)?
};
let beta = if beta.f_double_value(&[])? > 1e12 {
if !warned_beta_clamp {
warn!("CG: beta clamped to 1e12; optimizer may be diverging");
warned_beta_clamp = true;
}
tch::Tensor::f_ones_like(&beta)? * 1e12
} else {
beta
};
-&grad + beta * &prev_direction
}
}
};
let linesearch_atol =
P0.max(prev_step_norm.min(prev2_step_norm).min(prev3_step_norm) / 1000.);
let step = choose_step_golden_section(&x, &direction, &function, linesearch_atol)?;
prev3_step_norm = prev2_step_norm;
prev2_step_norm = prev_step_norm;
prev_step_norm = step.f_norm()?.f_double_value(&[])?;
x = x + step;
if !warned_nonfinite_iter && x.isfinite().f_all()?.f_int64_value(&[])? == 0 {
warn!("CG: non-finite iterate detected; step size may be too large");
warned_nonfinite_iter = true;
}
let y = function(&x);
if let (Some(prev_y), Some(ftol)) = (prev_y, self.ftol) {
if (&prev_y - &y).f_double_value(&[])? < ftol {
validate_optimizer_output(&x, "CG")?;
return Ok(x);
}
}
prev_y = Some(y);
prev_grad = grad;
prev_direction = direction;
}
validate_optimizer_output(&x, "CG")?;
Ok(x)
}
}
impl fmt::Display for CG {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut string = String::from("CG(");
string = string + "max_steps=" + self.max_steps.to_string().as_str();
if let Some(gtol) = self.gtol {
string = string + ", gtol=" + gtol.to_string().as_str();
}
if let Some(ftol) = self.ftol {
string = string + ", ftol=" + ftol.to_string().as_str();
}
string = string + ")";
write!(f, "{}", string)
}
}
impl BFGS {
pub fn new(max_steps: usize, gtol: Option<f64>, ftol: Option<f64>) -> Self {
Self {
max_steps,
gtol,
ftol,
}
}
}
impl Optimizer for BFGS {
fn optimize(
&self,
function: &dyn Fn(&Tensor) -> Tensor,
x0: &Tensor,
) -> anyhow::Result<Tensor> {
if x0.size().len() != 1 {
return Err(anyhow!("`x0` must have rank 1"));
}
let kind = x0.kind();
let device = x0.device();
let mut prev3_step_norm = 0f64;
let mut prev2_step_norm = 0f64;
let mut prev_step_norm = 0f64;
let x0_length = x0.size()[0];
let identity = match Tensor::f_eye(x0_length, (kind, device)) {
Ok(matrix) => matrix,
Err(tch::TchError::Torch(_)) => {
return Err(anyhow!(
"Could not allocate {}x{} matrix. Maybe try less resourcefull algorithm.",
x0_length,
x0_length
));
}
e => e.unwrap(),
};
let mut x = x0.copy();
let mut appr_inv_h = identity.copy();
let mut curr_grad = match differentiate(function, &x) {
Ok(grad) => grad,
Err(e) => {
return Err(anyhow!(
"Runtime error: Differentiation failed in BFGS optimizer: {}",
e
));
}
};
let mut curr_y = function(&x);
if curr_y.size() != Vec::<i64>::new() {
return Err(anyhow!("Output of function `function` must be scalar"));
}
let mut warned_inv_hess_large = false;
for _ in 0..self.max_steps {
if let Some(gtol) = self.gtol {
if curr_grad.f_norm()?.f_double_value(&[])? < gtol {
validate_optimizer_output(&x, "BFGS")?;
return Ok(x);
}
} else {
if curr_grad.f_norm()?.f_double_value(&[])? == 0. {
validate_optimizer_output(&x, "BFGS")?;
return Ok(x);
}
}
let direction = (-appr_inv_h.f_mm(&curr_grad.f_reshape([-1, 1])?)?).f_reshape([-1])?;
let linesearch_atol =
P0.max(prev_step_norm.min(prev2_step_norm).min(prev3_step_norm) / 100.);
let step = choose_step_golden_section(&x, &direction, function, linesearch_atol)?;
prev3_step_norm = prev2_step_norm;
prev2_step_norm = prev_step_norm;
prev_step_norm = step.f_norm()?.f_double_value(&[])?;
x = x + &step;
let y = function(&x);
if let Some(ftol) = self.ftol {
if (curr_y.f_double_value(&[])? - y.f_double_value(&[])?) < ftol {
validate_optimizer_output(&x, "BFGS")?;
return Ok(x);
}
}
curr_y = y;
let grad = match differentiate(function, &x) {
Ok(grad) => grad,
Err(e) => {
return Err(anyhow!(
"Runtime error: Differentiation failed in BFGS optimizer: {}",
e
));
}
};
let gdiff = &grad - &curr_grad;
let gamma = {
let delta = 0.0001;
let sty = step.f_dot(&gdiff)?.f_double_value(&[])?;
let step_norm_sq = step.f_dot(&step)?.f_double_value(&[])?;
let theta = if sty >= delta * step_norm_sq {
1.
} else {
let numerator = (1. - delta) * step_norm_sq;
let denominator = step_norm_sq - sty;
if denominator.abs() < 1e-10 {
1.
} else {
(numerator / denominator).min(1.)
}
};
let projection_factor = if step_norm_sq < 1e-10 {
0.
} else {
sty / step_norm_sq
};
let gdiff_prime = &gdiff * theta + &step * ((1. - theta) * projection_factor);
let sty_prime = step.f_dot(&gdiff_prime)?.f_double_value(&[])?;
if sty_prime.abs() < 1e-10 {
1. / (delta * step_norm_sq + 1e-10)
} else {
1. / sty_prime
}
};
appr_inv_h = (&identity
- gamma * step.f_reshape([-1, 1])?.f_mm(&gdiff.f_reshape([1, -1])?)?)
.f_mm(&appr_inv_h)?
.f_mm(
&(&identity - gamma * gdiff.f_reshape([-1, 1])?.f_mm(&step.f_reshape([1, -1])?)?),
)? + gamma * step.f_reshape([-1, 1])?.f_mm(&step.f_reshape([1, -1])?)?;
let inv_h_norm = appr_inv_h.f_norm()?.f_double_value(&[])?;
if !warned_inv_hess_large && inv_h_norm > 1e10 {
warn!(
"BFGS: inverse Hessian approximation norm reached {:.3e}; problem may be ill-conditioned",
inv_h_norm
);
warned_inv_hess_large = true;
}
curr_grad = grad;
}
validate_optimizer_output(&x, "BFGS")?;
Ok(x)
}
}
impl fmt::Display for BFGS {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut string = String::from("BFGS(");
string = string + "max_steps=" + self.max_steps.to_string().as_str();
if let Some(gtol) = self.gtol {
string = string + ", gtol=" + gtol.to_string().as_str();
}
if let Some(ftol) = self.ftol {
string = string + ", ftol=" + ftol.to_string().as_str();
}
string = string + ")";
write!(f, "{}", string)
}
}
impl Newton {
pub fn new(max_steps: usize, gtol: Option<f64>, ftol: Option<f64>) -> Self {
Self {
max_steps,
gtol,
ftol,
}
}
}
impl Optimizer for Newton {
fn optimize(
&self,
function: &dyn Fn(&Tensor) -> Tensor,
x0: &Tensor,
) -> anyhow::Result<Tensor> {
if x0.size().len() != 1 {
return Err(anyhow!("`x0` must have rank 1"));
}
let kind = x0.kind();
let device = x0.device();
let x0_length = x0.size()[0];
let _ = match Tensor::f_eye(x0_length, (kind, device)) {
Ok(matrix) => matrix,
Err(tch::TchError::Torch(_)) => {
return Err(anyhow!(
"Could not allocate {}x{} matrix. Maybe try less resourcefull algorithm.",
x0_length,
x0_length
));
}
e => e.unwrap(),
};
let mut x = x0.copy();
let mut curr_y = function(&x);
if curr_y.size() != Vec::<i64>::new() {
return Err(anyhow!("Output of function `function` must be scalar"));
}
let mut warned_damping_moderate = false;
let mut warned_damping_severe = false;
for _ in 0..self.max_steps {
let (curr_grad, curr_hessian) = match gradient_and_hessian(function, &x) {
Ok(gh) => gh,
Err(e) => {
return Err(anyhow!(
"Runtime error: Differentiation failed in Newton optimizer: {}",
e
));
}
};
if let Some(gtol) = self.gtol {
if curr_grad.f_norm()?.f_double_value(&[])? < gtol {
validate_optimizer_output(&x, "Newton")?;
return Ok(x);
}
} else {
if curr_grad.f_norm()?.f_double_value(&[])? == 0. {
validate_optimizer_output(&x, "Newton")?;
return Ok(x);
}
}
let negative_grad = -curr_grad.f_reshape([-1, 1])?; let mut lambda = (negative_grad.f_norm()?.f_double_value(&[])? * 1e-3).max(1e-8); let direction = loop {
let damped_hessian =
&curr_hessian + Tensor::f_eye(x0_length, (kind, device))? * lambda;
match damped_hessian.f_linalg_cholesky(false) {
Ok(lower_triangular) => {
let y = lower_triangular.f_linalg_solve_triangular(
&negative_grad,
false,
true,
false,
)?;
break lower_triangular
.f_transpose(0, 1)?
.f_linalg_solve_triangular(&y, true, true, false)?
.reshape([-1]);
}
Err(_) => {
lambda *= 10.;
if !warned_damping_moderate && lambda >= 1e3 && lambda < 1e7 {
warn!(
"Newton: Hessian required damping factor {:.3e}; problem may be ill-conditioned",
lambda
);
warned_damping_moderate = true;
}
if !warned_damping_severe && lambda >= 1e10 {
warn!(
"Newton: Hessian damping factor reached {:.3e}; falling back to pseudoinverse (Hessian is severely ill-conditioned)",
lambda
);
warned_damping_severe = true;
}
if lambda > 1e10 {
break curr_hessian
.f_linalg_pinv(1e-14, false)?
.f_mm(&negative_grad)?
.f_reshape([-1])?;
}
}
}
};
let step = choose_step_backtracking(&x, &direction, function, &curr_grad, 0.1, 0.9)?;
x = x + &step;
let y = function(&x);
if let Some(ftol) = self.ftol {
if (curr_y.f_double_value(&[])? - y.f_double_value(&[])?) < ftol {
validate_optimizer_output(&x, "Newton")?;
return Ok(x);
}
}
curr_y = y;
}
validate_optimizer_output(&x, "Newton")?;
Ok(x)
}
}
impl fmt::Display for Newton {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut string = String::from("Newton(");
string = string + "max_steps=" + self.max_steps.to_string().as_str();
if let Some(gtol) = self.gtol {
string = string + ", gtol=" + gtol.to_string().as_str();
}
if let Some(ftol) = self.ftol {
string = string + ", ftol=" + ftol.to_string().as_str();
}
string = string + ")";
write!(f, "{}", string)
}
}
impl Halley {
pub fn new(max_steps: usize, gtol: Option<f64>, ftol: Option<f64>) -> Self {
Self {
max_steps,
gtol,
ftol,
}
}
}
impl Optimizer for Halley {
fn optimize(
&self,
function: &dyn Fn(&Tensor) -> Tensor,
x0: &Tensor,
) -> anyhow::Result<Tensor> {
if x0.size().len() != 1 {
return Err(anyhow!("`x0` must have rank 1"));
}
let kind = x0.kind();
let device = x0.device();
let x0_length = x0.size()[0];
let _ = match Tensor::f_zeros([x0_length, x0_length, x0_length], (kind, device)) {
Ok(matrix) => matrix,
Err(tch::TchError::Torch(_)) => {
return Err(anyhow!(
"Could not allocate {}x{}x{} tensor. Maybe try less resourcefull algorithm.",
x0_length,
x0_length,
x0_length
));
}
e => e.unwrap(),
};
let mut x = x0.copy();
let mut curr_y = function(&x);
if curr_y.size() != Vec::<i64>::new() {
return Err(anyhow!("Output of function `function` must be scalar"));
}
let mut warned_pinv_large = false;
for _ in 0..self.max_steps {
let (curr_grad, curr_hessian, curr_d3_tensor) =
match derivative_tensors_123(function, &x) {
Ok(ghd3) => ghd3,
Err(e) => {
return Err(anyhow!(
"Runtime error: Differentiation failed in Halley optimizer: {}",
e
));
}
};
if let Some(gtol) = self.gtol {
if curr_grad.f_norm()?.f_double_value(&[])? < gtol {
validate_optimizer_output(&x, "Halley")?;
return Ok(x);
}
} else {
if curr_grad.f_norm()?.f_double_value(&[])? == 0. {
validate_optimizer_output(&x, "Halley")?;
return Ok(x);
}
}
let hessian_pinv = curr_hessian.f_linalg_pinv(1e-14, false)?;
let pinv_norm = hessian_pinv.f_norm()?.f_double_value(&[])?;
if !warned_pinv_large && pinv_norm > 1e8 {
warn!(
"Halley: Hessian pseudoinverse norm is {:.3e}; Hessian may be ill-conditioned",
pinv_norm
);
warned_pinv_large = true;
}
let neg_newton_dir = hessian_pinv.f_mm(&curr_grad.f_reshape([-1, 1])?)?;
let direction = -hessian_pinv
.f_mm(
&(curr_grad.f_reshape([-1, 1])?
+ curr_d3_tensor
.f_matmul(&neg_newton_dir)?
.f_reshape([x0_length, x0_length])?
.f_mm(&neg_newton_dir)?
* 0.5),
)?
.f_reshape([-1])?;
let step = choose_step_backtracking(&x, &direction, function, &curr_grad, 0.1, 0.9)?;
x = x + &step;
let y = function(&x);
if let Some(ftol) = self.ftol {
if (curr_y.f_double_value(&[])? - y.f_double_value(&[])?) < ftol {
validate_optimizer_output(&x, "Halley")?;
return Ok(x);
}
}
curr_y = y;
}
validate_optimizer_output(&x, "Halley")?;
Ok(x)
}
}
impl fmt::Display for Halley {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut string = String::from("Halley(");
string = string + "max_steps=" + self.max_steps.to_string().as_str();
if let Some(gtol) = self.gtol {
string = string + ", gtol=" + gtol.to_string().as_str();
}
if let Some(ftol) = self.ftol {
string = string + ", ftol=" + ftol.to_string().as_str();
}
string = string + ")";
write!(f, "{}", string)
}
}