use scirs2_core::ndarray::Array1;
use plotters::prelude::*;
use optirs_core::optimizers::{Adam, AdamW, Optimizer, RAdam, RMSprop, SGD};
type OptimizerList = Vec<(String, Box<dyn Optimizer<f64, scirs2_core::ndarray::Ix1>>)>;
type OptimizerSlice<'a> = &'a [(String, Box<dyn Optimizer<f64, scirs2_core::ndarray::Ix1>>)];
#[allow(dead_code)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let params = Array1::from_vec(vec![5.0, 5.0]);
let learning_rate = 0.1;
let num_iterations = 100;
let mut optimizers: OptimizerList = vec![
("SGD".to_string(), Box::new(SGD::new(learning_rate))),
("Adam".to_string(), Box::new(Adam::new(learning_rate))),
("AdamW".to_string(), Box::new(AdamW::new(learning_rate))),
("RAdam".to_string(), Box::new(RAdam::new(learning_rate))),
("RMSprop".to_string(), Box::new(RMSprop::new(learning_rate))),
];
let mut paths: Vec<Vec<(f64, f64)>> = Vec::new();
let mut loss_histories: Vec<Vec<f64>> = Vec::new();
for (name, optimizer) in optimizers.iter_mut() {
println!("Running {} optimizer...", name);
let mut current_params = params.clone();
let mut path = Vec::new();
let mut losses = Vec::new();
path.push((current_params[0], current_params[1]));
let current_loss = function_value(¤t_params);
losses.push(current_loss);
for i in 0..num_iterations {
let gradients = compute_gradients(¤t_params);
current_params = optimizer.step(¤t_params, &gradients)?;
path.push((current_params[0], current_params[1]));
let current_loss = function_value(¤t_params);
losses.push(current_loss);
if i % 10 == 0 {
println!(
" Iteration {}: params = {:?}, loss = {}",
i, current_params, current_loss
);
}
}
println!(" Final loss: {}", function_value(¤t_params));
println!(" Final parameters: {:?}", current_params);
paths.push(path);
loss_histories.push(losses);
}
plot_paths("optimizer_paths.png", &paths, &optimizers)?;
plot_loss_history("optimizer_loss_history.png", &loss_histories, &optimizers)?;
Ok(())
}
#[allow(dead_code)]
fn function_value(params: &Array1<f64>) -> f64 {
params[0].powi(2) + 2.0 * params[1].powi(2)
}
#[allow(dead_code)]
fn compute_gradients(params: &Array1<f64>) -> Array1<f64> {
Array1::from_vec(vec![2.0 * params[0], 4.0 * params[1]])
}
#[allow(dead_code)]
fn plot_paths(
filename: &str,
paths: &[Vec<(f64, f64)>],
optimizers: OptimizerSlice,
) -> Result<(), Box<dyn std::error::Error>> {
let mut x_min = f64::INFINITY;
let mut x_max = f64::NEG_INFINITY;
let mut y_min = f64::INFINITY;
let mut y_max = f64::NEG_INFINITY;
for path in paths {
for &(x, y) in path {
x_min = x_min.min(x);
x_max = x_max.max(x);
y_min = y_min.min(y);
y_max = y_max.max(y);
}
}
let x_padding = (x_max - x_min) * 0.1;
let y_padding = (y_max - y_min) * 0.1;
let root = BitMapBackend::new(filename, (800, 600)).into_drawing_area();
root.fill(&WHITE)?;
let mut chart = ChartBuilder::on(&root)
.caption("Optimizer Comparison", ("sans-serif", 30))
.margin(10)
.x_label_area_size(30)
.y_label_area_size(30)
.build_cartesian_2d(
(x_min - x_padding)..(x_max + x_padding),
(y_min - y_padding)..(y_max + y_padding),
)?;
chart.configure_mesh().draw()?;
let contour_density = 40;
let contour_values = [0.5, 2.0, 5.0, 10.0, 20.0, 50.0, 100.0];
for &value in &contour_values {
let points: Vec<(f64, f64)> = (0..contour_density)
.flat_map(|i| {
let theta = 2.0 * std::f64::consts::PI * (i as f64) / (contour_density as f64);
let r = (value / (f64::cos(theta).powi(2) + 2.0 * f64::sin(theta).powi(2))).sqrt();
let x = r * f64::cos(theta);
let y = r * f64::sin(theta);
Some((x, y))
})
.collect();
chart.draw_series(LineSeries::new(points, &BLACK.mix(0.3)))?;
}
let colors = [&RED, &BLUE, &GREEN, &MAGENTA, &CYAN, &YELLOW, &BLACK];
for (i, (path, (name, _optimizer))) in paths.iter().zip(optimizers).enumerate() {
let color = colors[i % colors.len()];
chart
.draw_series(LineSeries::new(path.clone(), color))?
.label(name)
.legend(move |(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], color));
chart.draw_series(PointSeries::of_element(
vec![*path.first().expect("unwrap failed")],
5,
color,
&|c, s, st| Circle::new(c, s, st.filled()),
))?;
chart.draw_series(PointSeries::of_element(
vec![*path.last().expect("unwrap failed")],
5,
color,
&|c, s, st| {
EmptyElement::at(c)
+ Circle::new((0, 0), s, st.filled())
+ Text::new(name.to_string(), (10, 0), ("sans-serif", 10).into_font())
},
))?;
}
chart
.configure_series_labels()
.background_style(WHITE.mix(0.8))
.border_style(BLACK)
.draw()?;
println!("Path plot saved to {}", filename);
Ok(())
}
#[allow(dead_code)]
fn plot_loss_history(
filename: &str,
loss_histories: &[Vec<f64>],
optimizers: OptimizerSlice,
) -> Result<(), Box<dyn std::error::Error>> {
let root = BitMapBackend::new(filename, (800, 600)).into_drawing_area();
root.fill(&WHITE)?;
let max_loss = loss_histories
.iter()
.flat_map(|losses| losses.iter().cloned())
.fold(0.0, f64::max);
let max_iter = loss_histories
.iter()
.map(|losses| losses.len())
.max()
.expect("unwrap failed");
let mut chart = ChartBuilder::on(&root)
.caption("Loss History Comparison", ("sans-serif", 30))
.margin(10)
.x_label_area_size(30)
.y_label_area_size(30)
.build_cartesian_2d(0.0..(max_iter as f64), 0.0..max_loss)?;
chart
.configure_mesh()
.x_desc("Iteration")
.y_desc("Loss")
.draw()?;
let colors = [&RED, &BLUE, &GREEN, &MAGENTA, &CYAN, &YELLOW, &BLACK];
for (i, (losses, (name, _optimizer))) in loss_histories.iter().zip(optimizers).enumerate() {
let color = colors[i % colors.len()];
let losses_with_idx: Vec<(f64, f64)> = losses
.iter()
.enumerate()
.map(|(idx, &loss)| (idx as f64, loss))
.collect();
chart
.draw_series(LineSeries::new(losses_with_idx, color))?
.label(name)
.legend(move |(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], color));
}
chart
.configure_series_labels()
.background_style(WHITE.mix(0.8))
.border_style(BLACK)
.draw()?;
println!("Loss history plot saved to {}", filename);
Ok(())
}