use anyhow::Result;
use candle_core::test_utils::to_vec2_round;
use candle_core::{DType, Device, Result as CResult, Tensor};
use candle_optimisers::lbfgs::{GradConv, Lbfgs, LineSearch, ParamsLBFGS, StepConv};
use candle_optimisers::{LossOptimizer, Model, ModelOutcome};
#[derive(Debug, Clone)]
pub struct RosenbrockModel {
x_pos: candle_core::Var,
y_pos: candle_core::Var,
}
impl Model for RosenbrockModel {
fn loss(&self) -> CResult<Tensor> {
self.forward()?.squeeze(1)?.squeeze(0)
}
}
impl RosenbrockModel {
fn new() -> CResult<Self> {
let x_pos = candle_core::Var::from_tensor(
&(10. * Tensor::ones((1, 1), DType::F64, &Device::Cpu)?)?,
)?;
let y_pos = candle_core::Var::from_tensor(
&(10. * Tensor::ones((1, 1), DType::F64, &Device::Cpu)?)?,
)?;
Ok(Self { x_pos, y_pos })
}
fn vars(&self) -> Vec<candle_core::Var> {
vec![self.x_pos.clone(), self.y_pos.clone()]
}
fn forward(&self) -> CResult<Tensor> {
(1. - self.x_pos.as_tensor())?.powf(2.)?
+ 100. * (self.y_pos.as_tensor() - self.x_pos.as_tensor().powf(2.)?)?.powf(2.)?
}
}
#[test]
fn lbfgs_test() -> Result<()> {
let params = ParamsLBFGS {
lr: 1.,
..Default::default()
};
let model = RosenbrockModel::new()?;
let mut lbfgs = Lbfgs::new(model.vars(), params, &model)?;
let mut loss = model.loss()?;
for _step in 0..500 {
let res = lbfgs.backward_step(&loss)?; match res {
ModelOutcome::Converged(_, _) => break,
ModelOutcome::Stepped(new_loss, _) => loss = new_loss,
}
}
for v in model.vars() {
assert_eq!(to_vec2_round(&v.to_dtype(DType::F32)?, 4)?, &[[1.0000]]);
}
Ok(())
}
#[test]
fn lbfgs_test_strong_wolfe() -> Result<()> {
let params = ParamsLBFGS {
lr: 1.,
line_search: Some(LineSearch::StrongWolfe(1e-4, 0.9, 1e-9)),
..Default::default()
};
let model = RosenbrockModel::new()?;
let mut lbfgs = Lbfgs::new(model.vars(), params, &model)?;
let mut loss = model.loss()?;
for _step in 0..500 {
let res = lbfgs.backward_step(&loss)?; match res {
ModelOutcome::Converged(_, _) => break,
ModelOutcome::Stepped(new_loss, _) => loss = new_loss,
}
}
for v in model.vars() {
assert_eq!(to_vec2_round(&v.to_dtype(DType::F32)?, 4)?, &[[1.0000]]);
}
Ok(())
}
#[test]
fn lbfgs_rms_grad_test() -> Result<()> {
let params = ParamsLBFGS {
lr: 1.,
grad_conv: GradConv::RMSForce(1e-6),
..Default::default()
};
let model = RosenbrockModel::new()?;
let mut lbfgs = Lbfgs::new(model.vars(), params, &model)?;
let mut loss = model.loss()?;
for _step in 0..500 {
let res = lbfgs.backward_step(&loss)?; match res {
ModelOutcome::Converged(_, _) => break,
ModelOutcome::Stepped(new_loss, _) => loss = new_loss,
}
}
for v in model.vars() {
assert_eq!(to_vec2_round(&v.to_dtype(DType::F32)?, 4)?, &[[1.0000]]);
}
Ok(())
}
#[test]
fn lbfgs_rms_step_test() -> Result<()> {
let params = ParamsLBFGS {
lr: 1.,
grad_conv: GradConv::RMSForce(0.),
step_conv: StepConv::RMSStep(1e-7),
..Default::default()
};
let model = RosenbrockModel::new()?;
let mut lbfgs = Lbfgs::new(model.vars(), params, &model)?;
let mut loss = model.loss()?;
for _step in 0..500 {
let res = lbfgs.backward_step(&loss)?; match res {
ModelOutcome::Converged(_, _) => break,
ModelOutcome::Stepped(new_loss, _) => loss = new_loss,
}
}
for v in model.vars() {
assert_eq!(to_vec2_round(&v.to_dtype(DType::F32)?, 4)?, &[[1.0000]]);
}
Ok(())
}
#[test]
fn lbfgs_test_strong_wolfe_weight_decay() -> Result<()> {
let params = ParamsLBFGS {
lr: 1.,
line_search: Some(LineSearch::StrongWolfe(1e-4, 0.9, 1e-9)),
weight_decay: Some(0.1),
..Default::default()
};
let model = RosenbrockModel::new()?;
let mut lbfgs = Lbfgs::new(model.vars(), params, &model)?;
let mut loss = model.loss()?;
for _step in 0..500 {
let res = lbfgs.backward_step(&loss)?; match res {
ModelOutcome::Converged(_, _) => break,
ModelOutcome::Stepped(new_loss, _) => loss = new_loss,
}
}
let expected = [0.8861, 0.7849]; for (v, e) in model.vars().iter().zip(expected) {
assert_eq!(to_vec2_round(&v.to_dtype(DType::F32)?, 4)?, &[[e]]);
}
Ok(())
}
#[test]
fn lbfgs_test_weight_decay() -> Result<()> {
let params = ParamsLBFGS {
lr: 1.,
weight_decay: Some(0.1),
..Default::default()
};
let model = RosenbrockModel::new()?;
let mut lbfgs = Lbfgs::new(model.vars(), params, &model)?;
let mut loss = model.loss()?;
for _step in 0..500 {
let res = lbfgs.backward_step(&loss)?; match res {
ModelOutcome::Converged(_, _) => break,
ModelOutcome::Stepped(new_loss, _) => loss = new_loss,
}
}
let expected = [0.8861, 0.7849]; for (v, e) in model.vars().iter().zip(expected) {
assert_eq!(to_vec2_round(&v.to_dtype(DType::F32)?, 4)?, &[[e]]);
}
Ok(())
}