diff --git a/Cargo.toml b/Cargo.toml index a5fbca0..d2889e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ categories = ["algorithms"] repository = "https://github.com/lucidfrontier45/localsearch" license-file = "LICENSE" readme = "README.md" -version = "0.14.0" +version = "0.15.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/README.md b/README.md index e541ee0..8a8f382 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Rust library for local search optimization All of the algorithms are parallelized with Rayon. 1. Hill Climbing. -2. Tabu Search. +2. Tabu Search. To use this optimizer you also need to implement your problem specific tabu list. 3. Simulated Annealing 4. Epsilon Greedy Search, a variant of Hill Climbing which accepts the trial solution with a constant probabilith even if the score of the trial solution is worse than the previous one. 5. Relative Annealing, a variant of Simulated Annealing which uses relative score diff to calculate transition probability. @@ -110,7 +110,7 @@ fn main() { pb.set_position(op.iter as u64); }; - let res = opt.run(&model, None, n_iter, time_limit, Some(&callback), ()); + let res = opt.run(&model, None, n_iter, time_limit, Some(&callback)); pb.finish(); dbg!(res.unwrap()); } diff --git a/examples/quadratic_model.rs b/examples/quadratic_model.rs index 3779a03..9b75d76 100644 --- a/examples/quadratic_model.rs +++ b/examples/quadratic_model.rs @@ -91,7 +91,7 @@ fn main() { pb.set_position(op.iter as u64); }; - let res = opt.run(&model, None, n_iter, time_limit, Some(&callback), ()); + let res = opt.run(&model, None, n_iter, time_limit, Some(&callback)); pb.finish(); dbg!(res.unwrap()); } diff --git a/examples/tsp_model.rs b/examples/tsp_model.rs index 2a38cf8..7976698 100644 --- a/examples/tsp_model.rs +++ b/examples/tsp_model.rs @@ -179,17 +179,21 @@ impl DequeTabuList { } } -impl TabuList for DequeTabuList { - type Item = (SolutionType, TransitionType); +impl Default for DequeTabuList { + fn default() -> Self { + Self::new(10) + } +} - fn contains(&self, item: &Self::Item) -> bool { +impl TabuList for DequeTabuList { + fn contains(&self, item: &(SolutionType, TransitionType)) -> bool { let (_, (_, inserted_edges)) = item; inserted_edges .iter() .any(|edge| self.buff.iter().any(|e| *e == *edge)) } - fn append(&mut self, item: Self::Item) { + fn append(&mut self, item: (SolutionType, TransitionType)) { let (_, (removed_edges, _)) = item; for edge in removed_edges { if self.buff.iter().all(|e| *e != edge) { @@ -197,6 +201,10 @@ impl TabuList for DequeTabuList { } } } + + fn set_size(&mut self, n: usize) { + self.buff = RingBuffer::new(n); + } } // The output is wrapped in a Result to allow matching on errors @@ -261,14 +269,13 @@ fn main() { println!("run hill climbing"); let optimizer = HillClimbingOptimizer::new(1000, 200); - let (final_solution, final_score, _) = optimizer + let (final_solution, final_score) = optimizer .run( &tsp_model, initial_solution.clone(), n_iter, time_limit, Some(&callback), - (), ) .unwrap(); println!( @@ -280,16 +287,14 @@ fn main() { pb.reset(); println!("run tabu search"); - let tabu_list = DequeTabuList::new(20); - let optimizer = TabuSearchOptimizer::new(patience, 200, 10); - let (final_solution, final_score, _) = optimizer + let optimizer = TabuSearchOptimizer::::new(patience, 200, 10, 20); + let (final_solution, final_score) = optimizer .run( &tsp_model, initial_solution.clone(), n_iter, time_limit, Some(&callback), - tabu_list, ) .unwrap(); println!( @@ -301,15 +306,14 @@ fn main() { pb.reset(); println!("run annealing"); - let optimizer = SimulatedAnnealingOptimizer::new(patience, 200); - let (final_solution, final_score, _) = optimizer + let optimizer = SimulatedAnnealingOptimizer::new(patience, 200, 200.0, 50.0); + let (final_solution, final_score) = optimizer .run( &tsp_model, initial_solution.clone(), n_iter, time_limit, Some(&callback), - (200.0, 50.0), ) .unwrap(); println!( @@ -322,14 +326,13 @@ fn main() { println!("run epsilon greedy"); let optimizer = EpsilonGreedyOptimizer::new(patience, 200, 10, 0.3); - let (final_solution, final_score, _) = optimizer + let (final_solution, final_score) = optimizer .run( &tsp_model, initial_solution.clone(), n_iter, time_limit, Some(&callback), - (), ) .unwrap(); println!( @@ -342,14 +345,13 @@ fn main() { println!("run relative annealing"); let optimizer = RelativeAnnealingOptimizer::new(patience, 200, 10, 1e1); - let (final_solution, final_score, _) = optimizer + let (final_solution, final_score) = optimizer .run( &tsp_model, initial_solution, n_iter, time_limit, Some(&callback), - (), ) .unwrap(); println!( diff --git a/src/optim/base.rs b/src/optim/base.rs index df32a97..0c4f559 100644 --- a/src/optim/base.rs +++ b/src/optim/base.rs @@ -6,11 +6,6 @@ use crate::{callback::OptCallbackFn, Duration, OptModel}; /// Optimizer that implements local search algorithm #[auto_impl(&, Box, Rc, Arc)] pub trait LocalSearchOptimizer { - /// Extra input type - type ExtraIn; - /// Extra output type - type ExtraOut; - /// Start optimization fn optimize( &self, @@ -20,8 +15,7 @@ pub trait LocalSearchOptimizer { n_iter: usize, time_limit: Duration, callback: Option<&F>, - extra_in: Self::ExtraIn, - ) -> (M::SolutionType, M::ScoreType, Self::ExtraOut) + ) -> (M::SolutionType, M::ScoreType) where M: OptModel, F: OptCallbackFn; @@ -34,8 +28,7 @@ pub trait LocalSearchOptimizer { n_iter: usize, time_limit: Duration, callback: Option<&F>, - extra_in: Self::ExtraIn, - ) -> AnyResult<(M::SolutionType, M::ScoreType, Self::ExtraOut)> + ) -> AnyResult<(M::SolutionType, M::ScoreType)> where M: OptModel, F: OptCallbackFn, @@ -51,18 +44,17 @@ pub trait LocalSearchOptimizer { let (initial_solution, initial_score) = model.preprocess_solution(initial_solution, initial_score)?; - let (solution, score, extra) = self.optimize( + let (solution, score) = self.optimize( model, initial_solution, initial_score, n_iter, time_limit, callback, - extra_in, ); let (solution, score) = model.postprocess_solution(solution, score); - Ok((solution, score, extra)) + Ok((solution, score)) } } diff --git a/src/optim/epsilon_greedy.rs b/src/optim/epsilon_greedy.rs index 2541fca..568709f 100644 --- a/src/optim/epsilon_greedy.rs +++ b/src/optim/epsilon_greedy.rs @@ -39,8 +39,6 @@ impl EpsilonGreedyOptimizer { } impl LocalSearchOptimizer for EpsilonGreedyOptimizer { - type ExtraIn = (); - type ExtraOut = (); /// Start optimization /// /// - `model` : the model to optimize @@ -49,7 +47,6 @@ impl LocalSearchOptimizer for EpsilonGreedyOptimizer { /// - `n_iter`: maximum iterations /// - `time_limit`: maximum iteration time /// - `callback` : callback function that will be invoked at the end of each iteration - /// - `_extra_in` : not used fn optimize( &self, model: &M, @@ -58,8 +55,7 @@ impl LocalSearchOptimizer for EpsilonGreedyOptimizer { n_iter: usize, time_limit: Duration, callback: Option<&F>, - _extra_in: Self::ExtraIn, - ) -> (M::SolutionType, M::ScoreType, Self::ExtraOut) + ) -> (M::SolutionType, M::ScoreType) where M: OptModel + Sync + Send, F: OptCallbackFn, @@ -77,7 +73,6 @@ impl LocalSearchOptimizer for EpsilonGreedyOptimizer { n_iter, time_limit, callback, - _extra_in, ) } } diff --git a/src/optim/generic.rs b/src/optim/generic.rs index ec2b8d8..2d94173 100644 --- a/src/optim/generic.rs +++ b/src/optim/generic.rs @@ -55,8 +55,6 @@ where FT: TransitionProbabilityFn, M: OptModel, { - type ExtraIn = (); - type ExtraOut = (); /// Start optimization /// /// - `model` : the model to optimize @@ -65,7 +63,6 @@ where /// - `n_iter`: maximum iterations /// - `time_limit`: maximum iteration time /// - `callback` : callback function that will be invoked at the end of each iteration - /// - `_extra_in` : not used fn optimize( &self, model: &M, @@ -74,8 +71,7 @@ where n_iter: usize, time_limit: Duration, callback: Option<&F>, - _extra_in: Self::ExtraIn, - ) -> (M::SolutionType, M::ScoreType, Self::ExtraOut) + ) -> (M::SolutionType, M::ScoreType) where F: OptCallbackFn, { @@ -141,6 +137,6 @@ where } let best_solution = (*best_solution.borrow()).clone(); - (best_solution, best_score, ()) + (best_solution, best_score) } } diff --git a/src/optim/hill_climbing.rs b/src/optim/hill_climbing.rs index ea1dd33..6414196 100644 --- a/src/optim/hill_climbing.rs +++ b/src/optim/hill_climbing.rs @@ -19,9 +19,6 @@ impl HillClimbingOptimizer { } impl LocalSearchOptimizer for HillClimbingOptimizer { - type ExtraIn = (); - type ExtraOut = (); - /// Start optimization /// /// - `model` : the model to optimize @@ -39,8 +36,7 @@ impl LocalSearchOptimizer for HillClimbingOptimizer { n_iter: usize, time_limit: Duration, callback: Option<&F>, - _extra_in: Self::ExtraIn, - ) -> (M::SolutionType, M::ScoreType, Self::ExtraOut) + ) -> (M::SolutionType, M::ScoreType) where F: OptCallbackFn, { @@ -52,7 +48,6 @@ impl LocalSearchOptimizer for HillClimbingOptimizer { n_iter, time_limit, callback, - _extra_in, ) } } diff --git a/src/optim/logistic_annealing.rs b/src/optim/logistic_annealing.rs index 52f374b..aa45e56 100644 --- a/src/optim/logistic_annealing.rs +++ b/src/optim/logistic_annealing.rs @@ -44,8 +44,6 @@ impl LogisticAnnealingOptimizer { } impl>> LocalSearchOptimizer for LogisticAnnealingOptimizer { - type ExtraIn = (); - type ExtraOut = (); /// Start optimization /// /// - `model` : the model to optimize @@ -54,7 +52,6 @@ impl>> LocalSearchOptimizer for LogisticA /// - `n_iter`: maximum iterations /// - `time_limit`: maximum iteration time /// - `callback` : callback function that will be invoked at the end of each iteration - /// - `_extra_in` : not used fn optimize( &self, model: &M, @@ -63,8 +60,7 @@ impl>> LocalSearchOptimizer for LogisticA n_iter: usize, time_limit: Duration, callback: Option<&F>, - _extra_in: Self::ExtraIn, - ) -> (M::SolutionType, M::ScoreType, Self::ExtraOut) + ) -> (M::SolutionType, M::ScoreType) where F: OptCallbackFn, { @@ -82,7 +78,6 @@ impl>> LocalSearchOptimizer for LogisticA n_iter, time_limit, callback, - _extra_in, ) } } diff --git a/src/optim/relative_annealing.rs b/src/optim/relative_annealing.rs index 19b346b..ead189b 100644 --- a/src/optim/relative_annealing.rs +++ b/src/optim/relative_annealing.rs @@ -44,9 +44,6 @@ impl RelativeAnnealingOptimizer { } impl>> LocalSearchOptimizer for RelativeAnnealingOptimizer { - type ExtraIn = (); - type ExtraOut = (); - /// Start optimization /// /// - `model` : the model to optimize @@ -64,8 +61,7 @@ impl>> LocalSearchOptimizer for RelativeA n_iter: usize, time_limit: Duration, callback: Option<&F>, - _extra_in: Self::ExtraIn, - ) -> (M::SolutionType, M::ScoreType, Self::ExtraOut) + ) -> (M::SolutionType, M::ScoreType) where F: OptCallbackFn, { @@ -83,7 +79,6 @@ impl>> LocalSearchOptimizer for RelativeA n_iter, time_limit, callback, - _extra_in, ) } } diff --git a/src/optim/simulated_annealing.rs b/src/optim/simulated_annealing.rs index 044845d..3191ab4 100644 --- a/src/optim/simulated_annealing.rs +++ b/src/optim/simulated_annealing.rs @@ -16,6 +16,8 @@ use super::LocalSearchOptimizer; pub struct SimulatedAnnealingOptimizer { patience: usize, n_trials: usize, + max_temperature: f64, + min_temperature: f64, } impl SimulatedAnnealingOptimizer { @@ -24,17 +26,25 @@ impl SimulatedAnnealingOptimizer { /// - `patience` : the optimizer will give up /// if there is no improvement of the score after this number of iterations /// - `n_trials` : number of trial solutions to generate and evaluate at each iteration - pub fn new(patience: usize, n_trials: usize) -> Self { - Self { patience, n_trials } + /// - `max_temperature` : maximum temperature + /// - `min_temperature` : minimum temperature + pub fn new( + patience: usize, + n_trials: usize, + max_temperature: f64, + min_temperature: f64, + ) -> Self { + Self { + patience, + n_trials, + max_temperature, + min_temperature, + } } } -impl>> LocalSearchOptimizer for SimulatedAnnealingOptimizer { - /// max temperature, min temperature - type ExtraIn = (f64, f64); - type ExtraOut = (); - - /// Start optimization +impl SimulatedAnnealingOptimizer { + /// Start optimization with given temperature range /// /// - `model` : the model to optimize /// - `initial_solution` : the initial solution to start optimization. If None, a random solution will be generated. @@ -42,8 +52,9 @@ impl>> LocalSearchOptimizer for Simulated /// - `n_iter`: maximum iterations /// - `time_limit`: maximum iteration time /// - `callback` : callback function that will be invoked at the end of each iteration - /// - `max_min_temperatures` : (max_temperature, min_temperature) - fn optimize( + /// - `max_temperature` : maximum temperature + /// - `min_temperature` : minimum temperature + fn optimize_with_temperature( &self, model: &M, initial_solution: M::SolutionType, @@ -51,13 +62,14 @@ impl>> LocalSearchOptimizer for Simulated n_iter: usize, time_limit: Duration, callback: Option<&F>, - max_min_temperatures: Self::ExtraIn, - ) -> (M::SolutionType, M::ScoreType, Self::ExtraOut) + max_temperature: f64, + min_temperature: f64, + ) -> (M::SolutionType, M::ScoreType) where + M: OptModel>, F: OptCallbackFn, { let start_time = Instant::now(); - let (max_temperature, min_temperature) = max_min_temperatures; let mut rng = rand::thread_rng(); let mut current_solution = initial_solution; let mut current_score = initial_score; @@ -118,6 +130,40 @@ impl>> LocalSearchOptimizer for Simulated } let best_solution = (*best_solution.borrow()).clone(); - (best_solution, best_score, ()) + (best_solution, best_score) + } +} + +impl>> LocalSearchOptimizer for SimulatedAnnealingOptimizer { + /// Start optimization + /// + /// - `model` : the model to optimize + /// - `initial_solution` : the initial solution to start optimization. If None, a random solution will be generated. + /// - `initial_score` : the initial score of the initial solution + /// - `n_iter`: maximum iterations + /// - `time_limit`: maximum iteration time + /// - `callback` : callback function that will be invoked at the end of each iteration + fn optimize( + &self, + model: &M, + initial_solution: M::SolutionType, + initial_score: M::ScoreType, + n_iter: usize, + time_limit: Duration, + callback: Option<&F>, + ) -> (M::SolutionType, M::ScoreType) + where + F: OptCallbackFn, + { + self.optimize_with_temperature( + model, + initial_solution, + initial_score, + n_iter, + time_limit, + callback, + self.max_temperature, + self.min_temperature, + ) } } diff --git a/src/optim/tabu_search.rs b/src/optim/tabu_search.rs index 4c26c3d..0423903 100644 --- a/src/optim/tabu_search.rs +++ b/src/optim/tabu_search.rs @@ -1,6 +1,5 @@ use std::{cell::RefCell, marker::PhantomData, rc::Rc}; -use auto_impl::auto_impl; use rayon::prelude::*; use crate::{ @@ -11,33 +10,34 @@ use crate::{ use super::LocalSearchOptimizer; /// Trait that a tabu list must satisfies -#[auto_impl(&mut, Box)] -pub trait TabuList { - /// Item type of the likst - type Item; +pub trait TabuList: Default { + /// Set the length of the tabu list + fn set_size(&mut self, n: usize); /// Check if the item is a Tabu - fn contains(&self, item: &Self::Item) -> bool; + fn contains(&self, item: &(M::SolutionType, M::TransitionType)) -> bool; /// Append the item to the list - fn append(&mut self, item: Self::Item); + fn append(&mut self, item: (M::SolutionType, M::TransitionType)); } /// Optimizer that implements the tabu search algorithm -pub struct TabuSearchOptimizer { +pub struct TabuSearchOptimizer> { patience: usize, n_trials: usize, return_iter: usize, - phantom: PhantomData, + default_tabu_size: usize, + phantom: PhantomData<(M, T)>, } -fn find_accepted_solution( - samples: Vec<(S, T, O)>, +fn find_accepted_solution( + samples: Vec<(M::SolutionType, M::TransitionType, O)>, tabu_list: &L, best_score: O, -) -> Option<(S, T, O)> +) -> Option<(M::SolutionType, M::TransitionType, O)> where - L: TabuList, + M: OptModel, + L: TabuList, O: Ord, { for (solution, transition, score) in samples.into_iter() { @@ -56,29 +56,34 @@ where None } -impl TabuSearchOptimizer { +impl> TabuSearchOptimizer { /// Constructor of TabuSearchOptimizer /// /// - `patience` : the optimizer will give up /// if there is no improvement of the score after this number of iterations /// - `n_trials` : number of trial solutions to generate and evaluate at each iteration /// - `return_iter` : returns to the current best solution if there is no improvement after this number of iterations. - pub fn new(patience: usize, n_trials: usize, return_iter: usize) -> Self { + pub fn new( + patience: usize, + n_trials: usize, + return_iter: usize, + default_tabu_size: usize, + ) -> Self { Self { patience, n_trials, return_iter, + default_tabu_size, phantom: PhantomData, } } } -impl> LocalSearchOptimizer - for TabuSearchOptimizer +impl TabuSearchOptimizer +where + M: OptModel, + T: TabuList, { - type ExtraIn = T; - type ExtraOut = T; - /// Start optimization /// /// - `model` : the model to optimize @@ -88,7 +93,7 @@ impl> Loca /// - `time_limit`: maximum iteration time /// - `callback` : callback function that will be invoked at the end of each iteration /// - `tabu_list` : initial tabu list - fn optimize( + fn optimize_with_tabu_list( &self, model: &M, initial_solution: M::SolutionType, @@ -96,8 +101,8 @@ impl> Loca n_iter: usize, time_limit: Duration, callback: Option<&F>, - mut tabu_list: Self::ExtraIn, - ) -> (M::SolutionType, M::ScoreType, Self::ExtraOut) + mut tabu_list: T, + ) -> (M::SolutionType, M::ScoreType, T) where F: OptCallbackFn, { @@ -167,3 +172,33 @@ impl> Loca (best_solution, best_score, tabu_list) } } + +impl> LocalSearchOptimizer for TabuSearchOptimizer { + #[doc = " Start optimization"] + fn optimize( + &self, + model: &M, + initial_solution: M::SolutionType, + initial_score: M::ScoreType, + n_iter: usize, + time_limit: Duration, + callback: Option<&F>, + ) -> (M::SolutionType, M::ScoreType) + where + M: OptModel, + F: OptCallbackFn, + { + let mut tabu_list = T::default(); + tabu_list.set_size(self.default_tabu_size); + let (solution, score, _) = self.optimize_with_tabu_list( + model, + initial_solution, + initial_score, + n_iter, + time_limit, + callback, + tabu_list, + ); + (solution, score) + } +} diff --git a/src/tests/test_epsilon_greedy.rs b/src/tests/test_epsilon_greedy.rs index 35fc146..ae57317 100644 --- a/src/tests/test_epsilon_greedy.rs +++ b/src/tests/test_epsilon_greedy.rs @@ -11,15 +11,8 @@ fn test() { let model = QuadraticModel::new(3, vec![2.0, 0.0, -3.5], (-10.0, 10.0)); let opt = EpsilonGreedyOptimizer::new(1000, 10, 200, 0.1); let null_closure = None::<&fn(_)>; - let (final_solution, final_score, _) = opt - .run( - &model, - None, - 10000, - Duration::from_secs(10), - null_closure, - (), - ) + let (final_solution, final_score) = opt + .run(&model, None, 10000, Duration::from_secs(10), null_closure) .unwrap(); assert_abs_diff_eq!(2.0, final_solution[0], epsilon = 0.05); assert_abs_diff_eq!(0.0, final_solution[1], epsilon = 0.05); diff --git a/src/tests/test_hill_climbing.rs b/src/tests/test_hill_climbing.rs index a1b1b37..2a86cc2 100644 --- a/src/tests/test_hill_climbing.rs +++ b/src/tests/test_hill_climbing.rs @@ -11,15 +11,8 @@ fn test() { let model = QuadraticModel::new(3, vec![2.0, 0.0, -3.5], (-10.0, 10.0)); let opt = HillClimbingOptimizer::new(1000, 10); let null_closure = None::<&fn(_)>; - let (final_solution, final_score, _) = opt - .run( - &model, - None, - 10000, - Duration::from_secs(10), - null_closure, - (), - ) + let (final_solution, final_score) = opt + .run(&model, None, 10000, Duration::from_secs(10), null_closure) .unwrap(); assert_abs_diff_eq!(2.0, final_solution[0], epsilon = 0.05); assert_abs_diff_eq!(0.0, final_solution[1], epsilon = 0.05); diff --git a/src/tests/test_logistic_annealing.rs b/src/tests/test_logistic_annealing.rs index 8df0ea7..b7108a3 100644 --- a/src/tests/test_logistic_annealing.rs +++ b/src/tests/test_logistic_annealing.rs @@ -11,15 +11,8 @@ fn test() { let model = QuadraticModel::new(3, vec![2.0, 0.0, -3.5], (-10.0, 10.0)); let opt = LogisticAnnealingOptimizer::new(5000, 10, 200, 1e1); let null_closure = None::<&fn(_)>; - let (final_solution, final_score, _) = opt - .run( - &model, - None, - 10000, - Duration::from_secs(10), - null_closure, - (), - ) + let (final_solution, final_score) = opt + .run(&model, None, 10000, Duration::from_secs(10), null_closure) .unwrap(); assert_abs_diff_eq!(2.0, final_solution[0], epsilon = 0.05); assert_abs_diff_eq!(0.0, final_solution[1], epsilon = 0.05); diff --git a/src/tests/test_relative_annealing.rs b/src/tests/test_relative_annealing.rs index 683fd9b..329a801 100644 --- a/src/tests/test_relative_annealing.rs +++ b/src/tests/test_relative_annealing.rs @@ -11,15 +11,8 @@ fn test() { let model = QuadraticModel::new(3, vec![2.0, 0.0, -3.5], (-10.0, 10.0)); let opt = RelativeAnnealingOptimizer::new(5000, 10, 200, 1e1); let null_closure = None::<&fn(_)>; - let (final_solution, final_score, _) = opt - .run( - &model, - None, - 10000, - Duration::from_secs(10), - null_closure, - (), - ) + let (final_solution, final_score) = opt + .run(&model, None, 10000, Duration::from_secs(10), null_closure) .unwrap(); assert_abs_diff_eq!(2.0, final_solution[0], epsilon = 0.05); assert_abs_diff_eq!(0.0, final_solution[1], epsilon = 0.05); diff --git a/src/tests/test_simulated_annealing.rs b/src/tests/test_simulated_annealing.rs index e629361..35f7253 100644 --- a/src/tests/test_simulated_annealing.rs +++ b/src/tests/test_simulated_annealing.rs @@ -9,17 +9,10 @@ use super::QuadraticModel; #[test] fn test() { let model = QuadraticModel::new(3, vec![2.0, 0.0, -3.5], (-10.0, 10.0)); - let opt = SimulatedAnnealingOptimizer::new(10000, 10); + let opt = SimulatedAnnealingOptimizer::new(10000, 10, 1.0, 0.1); let null_closure = None::<&fn(_)>; - let (final_solution, final_score, _) = opt - .run( - &model, - None, - 5000, - Duration::from_secs(10), - null_closure, - (1.0, 0.1), - ) + let (final_solution, final_score) = opt + .run(&model, None, 5000, Duration::from_secs(10), null_closure) .unwrap(); assert_abs_diff_eq!(2.0, final_solution[0], epsilon = 0.05); assert_abs_diff_eq!(0.0, final_solution[1], epsilon = 0.05); diff --git a/src/tests/test_tabu_search.rs b/src/tests/test_tabu_search.rs index ef8d918..baf1d7b 100644 --- a/src/tests/test_tabu_search.rs +++ b/src/tests/test_tabu_search.rs @@ -7,7 +7,7 @@ use crate::{ utils::RingBuffer, }; -use super::{QuadraticModel, SolutionType, TransitionType}; +use super::{QuadraticModel, TransitionType}; #[derive(Debug)] struct MyTabuList { @@ -21,17 +21,37 @@ impl MyTabuList { } } -impl TabuList for MyTabuList { - type Item = (SolutionType, TransitionType); +impl Default for MyTabuList { + fn default() -> Self { + Self::new(10) + } +} + +impl TabuList for MyTabuList { + fn set_size(&mut self, n: usize) { + self.buff = RingBuffer::new(n); + } - fn contains(&self, item: &Self::Item) -> bool { + fn contains( + &self, + item: &( + ::SolutionType, + ::TransitionType, + ), + ) -> bool { let (k1, _, x) = item.1; self.buff .iter() .any(|&(k2, y, _)| (k1 == k2) && (x - y).abs() < 0.005) } - fn append(&mut self, item: Self::Item) { + fn append( + &mut self, + item: ( + ::SolutionType, + ::TransitionType, + ), + ) { self.buff.append(item.1); } } @@ -39,18 +59,10 @@ impl TabuList for MyTabuList { #[test] fn test() { let model = QuadraticModel::new(3, vec![2.0, 0.0, -3.5], (-10.0, 10.0)); - let opt = TabuSearchOptimizer::new(1000, 25, 5); - let tabu_list = MyTabuList::new(10); + let opt = TabuSearchOptimizer::::new(1000, 25, 5, 10); let null_closure = None::<&fn(_)>; - let (final_solution, final_score, _) = opt - .run( - &model, - None, - 10000, - Duration::from_secs(10), - null_closure, - tabu_list, - ) + let (final_solution, final_score) = opt + .run(&model, None, 10000, Duration::from_secs(10), null_closure) .unwrap(); assert_abs_diff_eq!(2.0, final_solution[0], epsilon = 0.1); assert_abs_diff_eq!(0.0, final_solution[1], epsilon = 0.1);