Add Simulated annealing
This commit is contained in:
parent
35a3705763
commit
72b33f732a
3 changed files with 130 additions and 17 deletions
31
src/main.rs
31
src/main.rs
|
|
@ -3,6 +3,7 @@ extern crate nalgebra as na;
|
||||||
|
|
||||||
mod univariate_solvers;
|
mod univariate_solvers;
|
||||||
mod univariate_minimizers;
|
mod univariate_minimizers;
|
||||||
|
mod simulated_annealing;
|
||||||
mod nelder_mead;
|
mod nelder_mead;
|
||||||
mod particle_swarm_optimization;
|
mod particle_swarm_optimization;
|
||||||
mod non_linear_least_squares;
|
mod non_linear_least_squares;
|
||||||
|
|
@ -131,7 +132,7 @@ fn test_univariate_solvers(verbose: bool) {
|
||||||
let x_bisection : f64 = univariate_solvers::bisection_solve(&(fct as fn(f64) -> f64), -5.0, 1.0, tol).unwrap();
|
let x_bisection : f64 = univariate_solvers::bisection_solve(&(fct as fn(f64) -> f64), -5.0, 1.0, tol).unwrap();
|
||||||
let x_secant : f64 = univariate_solvers::secant_solve(&(fct as fn(f64) -> f64), -1.0, 1.0, tol, max_iter);
|
let x_secant : f64 = univariate_solvers::secant_solve(&(fct as fn(f64) -> f64), -1.0, 1.0, tol, max_iter);
|
||||||
let x_ridder : f64 = univariate_solvers::ridder_solve(&(fct as fn(f64) -> f64), -5.0, 1.0, tol, max_iter).unwrap();
|
let x_ridder : f64 = univariate_solvers::ridder_solve(&(fct as fn(f64) -> f64), -5.0, 1.0, tol, max_iter).unwrap();
|
||||||
let x_itp : f64 = univariate_solvers::itp_solve(&(fct as fn(f64) -> f64), -5.0, 1.0, 0.1, 2.0, 1.0, tol);
|
let x_itp : f64 = univariate_solvers::itp_solve(&(fct as fn(f64) -> f64), -5.0, 1.0, 0.1, 2.0, 1.0, tol).unwrap();
|
||||||
num_tests_passed += check_result(x_newton, x_mathematica, tol, "Newton's method", verbose); num_tests_total += 1;
|
num_tests_passed += check_result(x_newton, x_mathematica, tol, "Newton's method", verbose); num_tests_total += 1;
|
||||||
num_tests_passed += check_result(x_newton_num, x_mathematica, tol, "Newton's method (num)", verbose); num_tests_total += 1;
|
num_tests_passed += check_result(x_newton_num, x_mathematica, tol, "Newton's method (num)", verbose); num_tests_total += 1;
|
||||||
num_tests_passed += check_result(x_halley, x_mathematica_2, tol, "Halley's method", verbose); num_tests_total += 1;
|
num_tests_passed += check_result(x_halley, x_mathematica_2, tol, "Halley's method", verbose); num_tests_total += 1;
|
||||||
|
|
@ -145,6 +146,7 @@ fn test_univariate_solvers(verbose: bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_univariate_optimizers(verbose: bool) {
|
fn test_univariate_optimizers(verbose: bool) {
|
||||||
|
println!("Testing univariate optimizers.");
|
||||||
let tol : f64 = 1e-10;
|
let tol : f64 = 1e-10;
|
||||||
// let max_iter : u32 = 100;
|
// let max_iter : u32 = 100;
|
||||||
// let dx_num : f64 = 1e-6;
|
// let dx_num : f64 = 1e-6;
|
||||||
|
|
@ -159,6 +161,7 @@ fn test_univariate_optimizers(verbose: bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_multivariate_optimizers(verbose: bool) {
|
fn test_multivariate_optimizers(verbose: bool) {
|
||||||
|
println!("Testing multivariate optimizers.");
|
||||||
let tol : f64 = 1e-10;
|
let tol : f64 = 1e-10;
|
||||||
let max_iter : u32 = 1000;
|
let max_iter : u32 = 1000;
|
||||||
// let dx_num : f64 = 1e-6;
|
// let dx_num : f64 = 1e-6;
|
||||||
|
|
@ -168,24 +171,26 @@ fn test_multivariate_optimizers(verbose: bool) {
|
||||||
let tol_x: f64 = 1e-4;
|
let tol_x: f64 = 1e-4;
|
||||||
let tol_f_x: f64 = 1e-6;
|
let tol_f_x: f64 = 1e-6;
|
||||||
|
|
||||||
|
let x_init: na::DVector<f64> = na::DVector::from_vec(vec![2.0,-1.0]);
|
||||||
let x_true: na::DVector<f64> = na::DVector::from_vec(vec![1.,1.]);
|
let x_true: na::DVector<f64> = na::DVector::from_vec(vec![1.,1.]);
|
||||||
let f_x_true: f64 = rosenbrock(&x_true);
|
let f_x_true: f64 = rosenbrock(&x_true);
|
||||||
let sol_nelder_mead: (na::DVector<f64>, f64) = nelder_mead::nelder_mead_minimize(&(rosenbrock as fn(&na::DVector<f64>) -> f64), &na::DVector::from_vec(vec![2.0,-1.0]), 0.1, tol, max_iter, false);
|
let sol_nelder_mead: (na::DVector<f64>, f64) = nelder_mead::nelder_mead_minimize(&(rosenbrock as fn(&na::DVector<f64>) -> f64), &x_init, 0.1, tol, max_iter, false);
|
||||||
// num_tests_passed += check_result(x_nelder_mead, x_true, tol*1e2, "Nelder-Mead", verbose);
|
let sol_simulated_annealing: (na::DVector<f64>, f64) = simulated_annealing::simulated_annealing(&rosenbrock, &x_init, 0.1, 100.0, 0.95, max_iter, 123);
|
||||||
num_tests_passed += check_result_optim(&sol_nelder_mead.0, sol_nelder_mead.1, &x_true, f_x_true, tol_x, tol_f_x, "Nelder-Mead", verbose); num_tests_total += 1;
|
num_tests_passed += check_result_optim(&sol_nelder_mead.0, sol_nelder_mead.1, &x_true, f_x_true, tol_x, tol_f_x, "Nelder-Mead", verbose); num_tests_total += 1;
|
||||||
|
num_tests_passed += check_result_optim(&sol_simulated_annealing.0, sol_simulated_annealing.1, &x_true, f_x_true, 0.1, 0.01, "Simulated Annealing", verbose); num_tests_total += 1;
|
||||||
print_test_results(num_tests_passed, num_tests_total);
|
print_test_results(num_tests_passed, num_tests_total);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_particle_swarm_debug() {
|
// fn test_particle_swarm_debug() {
|
||||||
let n_particles: u32 = 10;
|
// let n_particles: u32 = 10;
|
||||||
let lb: na::DVector<f64> = na::DVector::from_vec(vec![-5.0,-5.0]);
|
// let lb: na::DVector<f64> = na::DVector::from_vec(vec![-5.0,-5.0]);
|
||||||
let ub: na::DVector<f64> = na::DVector::from_vec(vec![5.0,5.0]);
|
// let ub: na::DVector<f64> = na::DVector::from_vec(vec![5.0,5.0]);
|
||||||
let tol: f64 = 1e-6;
|
// let tol: f64 = 1e-6;
|
||||||
let n_iter_max: u32 = 1000;
|
// let n_iter_max: u32 = 1000;
|
||||||
let rng_seed: u32 = 69;
|
// let rng_seed: u32 = 69;
|
||||||
let x: f64 = particle_swarm_optimization::particle_swarm_minimize(&rosenbrock, n_particles, &lb, &ub, tol, n_iter_max, rng_seed);
|
// let x: f64 = particle_swarm_optimization::particle_swarm_minimize(&rosenbrock, n_particles, &lb, &ub, tol, n_iter_max, rng_seed);
|
||||||
println!("x = {}", x);
|
// println!("x = {}", x);
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn test_non_linear_lsqr_solvers(verbose: bool) {
|
fn test_non_linear_lsqr_solvers(verbose: bool) {
|
||||||
let mut num_tests_passed : u32 = 0;
|
let mut num_tests_passed : u32 = 0;
|
||||||
|
|
|
||||||
95
src/simulated_annealing.rs
Normal file
95
src/simulated_annealing.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
extern crate nalgebra as na;
|
||||||
|
|
||||||
|
#[path = "./xorwow.rs"]
|
||||||
|
mod xorwow;
|
||||||
|
|
||||||
|
use xorwow::Xorwow;
|
||||||
|
use na::DVector;
|
||||||
|
|
||||||
|
/// Performs simulated annealing optimization on a given objective function.
|
||||||
|
///
|
||||||
|
/// Simulated annealing is a probabilistic technique for approximating the global optimum of a given function.
|
||||||
|
/// It is inspired by the annealing process in metallurgy, where a material is heated and then slowly cooled
|
||||||
|
/// to increase the size of its crystals and reduce defects.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `f` - A closure that takes a vector of `f64` values and returns a single `f64` value representing the objective function to be optimized.
|
||||||
|
/// * `initial_x` - The initial guess or starting point for the optimization process, represented as a `DVector<f64>`.
|
||||||
|
/// * `search_scale` - A scaling factor that determines the magnitude of the random perturbations applied during the optimization process.
|
||||||
|
/// * `temperature_0` - The initial temperature for the simulated annealing process.
|
||||||
|
/// * `temperature_decay` - The rate at which the temperature decreases during the optimization process.
|
||||||
|
/// * `num_iterations` - The maximum number of iterations to perform during the optimization process.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The function returns a `DVector<f64>` representing the approximate global optimum of the objective function.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// extern crate nalgebra as na;
|
||||||
|
///
|
||||||
|
/// let rosenbrock = |x: &na::DVector<f64>| -> f64 {
|
||||||
|
/// return (1.0-x[0]).powi(2) + 100.0*(x[1] - x[0].powi(2)).powi(2);
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// // Initial guess
|
||||||
|
/// let initial_x: na::DVector<f64> = na::DVector::from_vec(vec![0.0, 0.0]);
|
||||||
|
///
|
||||||
|
/// // Simulated annealing parameters
|
||||||
|
/// let search_scale = 0.1;
|
||||||
|
/// let temperature_0 = 100.0;
|
||||||
|
/// let temperature_decay = 0.99;
|
||||||
|
/// let num_iterations = 1000;
|
||||||
|
/// let seed = 123;
|
||||||
|
///
|
||||||
|
/// // Perform simulated annealing optimization
|
||||||
|
/// let result = simulated_annealing::simulated_annealing(&rosenbrock, &initial_x, search_scale, temperature_0, temperature_decay, num_iterations, seed);
|
||||||
|
///
|
||||||
|
/// println!("Approximate global optimum: {:?}", result);
|
||||||
|
/// ```
|
||||||
|
pub fn simulated_annealing(
|
||||||
|
f: &dyn Fn(&DVector<f64>) -> f64,
|
||||||
|
initial_x: &DVector<f64>,
|
||||||
|
search_scale: f64,
|
||||||
|
temperature_0: f64,
|
||||||
|
temperature_decay: f64,
|
||||||
|
num_iterations: u32,
|
||||||
|
seed: u32
|
||||||
|
) -> (na::DVector<f64>, f64) {
|
||||||
|
let mut temperature = temperature_0;
|
||||||
|
let mut rng = Xorwow::new(seed);
|
||||||
|
let mut current_x = initial_x.clone();
|
||||||
|
let mut best_x = current_x.clone();
|
||||||
|
let mut current_energy = f(¤t_x);
|
||||||
|
let mut best_energy = current_energy;
|
||||||
|
|
||||||
|
for _ in 0..num_iterations {
|
||||||
|
// Generate a random vector
|
||||||
|
let mut next_x = current_x.clone();
|
||||||
|
for i in 0..current_x.len() {
|
||||||
|
next_x[i] += search_scale * (2.0 * rng.next_f64() - 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the energy of the new vector
|
||||||
|
let next_energy = f(&next_x);
|
||||||
|
|
||||||
|
if next_energy < best_energy {
|
||||||
|
best_energy = next_energy;
|
||||||
|
best_x = next_x.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decide whether to accept the new vector based on Metropolis-Hastings criterion
|
||||||
|
if next_energy < current_energy
|
||||||
|
|| f64::exp((current_energy - next_energy) / temperature) > rng.next_f64()
|
||||||
|
{
|
||||||
|
current_x = next_x.clone();
|
||||||
|
current_energy = next_energy;
|
||||||
|
}
|
||||||
|
|
||||||
|
temperature *= temperature_decay;
|
||||||
|
}
|
||||||
|
|
||||||
|
(best_x, best_energy)
|
||||||
|
}
|
||||||
|
|
@ -233,6 +233,15 @@ fn swap(a: &mut f64, b: &mut f64) {
|
||||||
*b = temp;
|
*b = temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief Solves the equation f(x) = 0 for x in [a, b]. The root must be bracketed by [a, b], meaning that f(a) * f(b) < 0.
|
||||||
|
/// @param `f` A reference to a function that takes a `f64` as an argument and returns a `f64`.
|
||||||
|
/// @param `a` The left endpoint of the interval [a, b].
|
||||||
|
/// @param `b` The right endpoint of the interval [a, b].
|
||||||
|
/// @param `k1` Hyperparameter in [0, inf]
|
||||||
|
/// @param `k2` Hyperparameter in [1, 1 + phi], with phi = (1 + sqrt(5))/2
|
||||||
|
/// @param `n0` Hyperparameter in [0, inf]
|
||||||
|
/// @param `epsilon` Tolerance on x.
|
||||||
|
/// @return Solution if f(a) * f(b) < 0, an error string otherwise.
|
||||||
pub fn itp_solve(
|
pub fn itp_solve(
|
||||||
f: &dyn Fn(f64) -> f64,
|
f: &dyn Fn(f64) -> f64,
|
||||||
a: f64,
|
a: f64,
|
||||||
|
|
@ -241,7 +250,7 @@ pub fn itp_solve(
|
||||||
k2: f64,
|
k2: f64,
|
||||||
n0: f64,
|
n0: f64,
|
||||||
epsilon: f64
|
epsilon: f64
|
||||||
) -> f64 {
|
) -> Result<f64, &'static str> {
|
||||||
let mut a: f64 = a;
|
let mut a: f64 = a;
|
||||||
let mut b: f64 = b;
|
let mut b: f64 = b;
|
||||||
|
|
||||||
|
|
@ -254,6 +263,10 @@ pub fn itp_solve(
|
||||||
swap(&mut ya, &mut yb);
|
swap(&mut ya, &mut yb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ya * yb > 0.0 {
|
||||||
|
return Err("Root is not bracketed")
|
||||||
|
}
|
||||||
|
|
||||||
// Preprocessing
|
// Preprocessing
|
||||||
let nhalf: f64 = f64::log2((b - a)/(2.0 * epsilon));
|
let nhalf: f64 = f64::log2((b - a)/(2.0 * epsilon));
|
||||||
let nmax: i32 = (nhalf + n0) as i32;
|
let nmax: i32 = (nhalf + n0) as i32;
|
||||||
|
|
@ -301,5 +314,5 @@ pub fn itp_solve(
|
||||||
j += 1;
|
j += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
(a + b) / 2.0
|
Ok((a + b) / 2.0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue