diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 60127c5..f569f6f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -20,7 +20,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - rust: [1.76.0, stable] + rust: [1.85.0, stable] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master diff --git a/Cargo.toml b/Cargo.toml index 74f05d2..d176f03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "rpid" -version = "0.1.0" -edition = "2021" -rust-version = "1.76" +version = "0.2.0" +edition = "2024" +rust-version = "1.85" description = "Rust Programmable Interface for Domain-Independent Dynamic Programming (RPID)" license = "MIT OR Apache-2.0" authors = ["Ryo Kuroiwa "] @@ -13,6 +13,7 @@ rustc-hash = "2.1" smallvec = "1.15" num-traits = "0.2" itertools = "0.14" +ordered-float = "5.0" [dev-dependencies] fixedbitset = "0.5" diff --git a/README.md b/README.md index 35b8dc7..35f861c 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,9 @@ struct TspState { impl Dp for Tsp { type State = TspState; type CostType = i32; + type Label = usize; - fn get_target(&self) -> TspState { + fn get_target(&self) -> Self::State { let mut unvisited = FixedBitSet::with_capacity(self.c.len()); unvisited.insert_range(1..); @@ -36,7 +37,10 @@ impl Dp for Tsp { } } - fn get_successors(&self, state: &TspState) -> impl IntoIterator { + fn get_successors( + &self, + state: &Self::State, + ) -> impl IntoIterator { state.unvisited.ones().map(|next| { let mut unvisited = state.unvisited.clone(); unvisited.remove(next); @@ -51,7 +55,7 @@ impl Dp for Tsp { }) } - fn get_base_cost(&self, state: &TspState) -> Option { + fn get_base_cost(&self, state: &Self::State) -> Option { if state.unvisited.is_clear() { Some(self.c[state.current][0]) } else { @@ -64,7 +68,7 @@ impl Dominance for Tsp { type State = TspState; type Key = (FixedBitSet, usize); - fn get_key(&self, state: &TspState) -> Self::Key { + fn get_key(&self, state: &Self::State) -> Self::Key { (state.unvisited.clone(), state.current) } } @@ -73,7 +77,7 @@ impl Bound for Tsp { type State = TspState; type CostType = i32; - fn get_dual_bound(&self, _: &TspState) -> Option { + fn get_dual_bound(&self, _: &Self::State) -> Option { Some(0) } } diff --git a/src/algorithms.rs b/src/algorithms.rs index 50acbce..6e7abd9 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -2,10 +2,10 @@ //! //! The algorithms in this module are useful for preprocessing and relaxation. -use num_traits::{One, Zero}; +use num_traits::Zero; +use ordered_float::OrderedFloat; use std::cmp::Ordering; -use std::iter::Sum; -use std::ops::{Add, Div, Rem, SubAssign}; +use std::ops::{Add, SubAssign}; /// Transpose an m x n matrix. /// @@ -834,9 +834,8 @@ where .enumerate() .map(|(i, (w, v))| (i, w, v)) .collect::>(); - sorted_weight_value_pairs.sort_by(|&a, &b| { - (f64::from(a.1) * f64::from(b.2)).total_cmp(&(f64::from(a.2) * f64::from(b.1))) - }); + sorted_weight_value_pairs + .sort_by_key(|&(_, w, v)| OrderedFloat::from(f64::from(w) / f64::from(v))); sorted_weight_value_pairs } @@ -854,6 +853,7 @@ where /// # Examples /// /// ``` +/// use approx::assert_relative_eq; /// use rpid::algorithms; /// /// let weights = [1, 2, 4]; @@ -861,13 +861,12 @@ where /// /// let sorted_items = algorithms::sort_knapsack_items_by_efficiency(&weights, &values); /// let sorted_weight_value_pairs = sorted_items.iter().map(|&(_, w, v)| (w, v)); -/// let profit = algorithms::compute_fractional_knapsack_profit(5, sorted_weight_value_pairs, 1e-6); -/// assert_eq!(profit, 7.0); +/// let profit = algorithms::compute_fractional_knapsack_profit(5, sorted_weight_value_pairs); +/// assert_relative_eq!(profit, 7.5); /// ``` pub fn compute_fractional_knapsack_profit( capacity: T, sorted_weight_value_pairs: impl Iterator, - epsilon: f64, ) -> f64 where T: PartialOrd + SubAssign + Copy, @@ -886,48 +885,13 @@ where } } - f64::floor(profit + epsilon) -} - -/// Computes the number of bins to pack weighted items, allowing fractions of items to be taken. -/// -/// This bound is sometimes called LB1. -/// -/// `epsilon` is the upper bound on the difference between two values to be considered equal. -/// If the cost of the fractional bin packing is `c`, the number of bins is `ceil(c - epsilon)`. -/// -/// # Examples -/// -/// ``` -/// use rpid::algorithms; -/// -/// let weights = [2, 2, 3, 4, 5]; -/// -/// let n_bins = algorithms::compute_fractional_bin_packing_cost(5, weights.iter().sum(), 0); -/// assert_eq!(n_bins, 4.0); -/// ``` -pub fn compute_fractional_bin_packing_cost(capacity: T, weight_sum: T, epsilon: T) -> f64 -where - T: Sum + Rem + Div + PartialOrd + Copy + One, - f64: From, -{ - if capacity <= epsilon { - return f64::INFINITY; - } - - if weight_sum % capacity <= epsilon { - f64::from(weight_sum / capacity).trunc() - } else { - f64::from(weight_sum / capacity).trunc() + 1.0 - } + profit } /// Computes the number of bins to pack items, whose weights are at least half of the capacity. /// /// This bound is sometimes called LB2. /// -/// `epsilon` is the upper bound on the difference between two values to be considered equal. -/// /// # Examples /// /// ``` @@ -935,34 +899,31 @@ where /// /// let weights = [4, 2, 3, 5, 4, 3, 3]; /// -/// let n_bins = algorithms::compute_bin_packing_lb2(6, weights.iter().copied(), 0); -/// assert_eq!(n_bins, 5.0); +/// let n_bins = algorithms::compute_bin_packing_lb2(6, weights.iter().copied()); +/// assert_eq!(n_bins, 5); /// ``` -pub fn compute_bin_packing_lb2(capacity: T, weights: impl Iterator, epsilon: T) -> f64 +pub fn compute_bin_packing_lb2(capacity: T, weights: impl Iterator) -> usize where T: PartialOrd + Add + Copy, { - if capacity <= epsilon { - return f64::INFINITY; - } - let mut n_more_than_half = 0; let mut n_half = 0; weights.for_each(|w| { let twice = w + w; - if twice > capacity + epsilon { + if twice > capacity { n_more_than_half += 1; - } else if twice + epsilon >= capacity { + } + if twice == capacity { n_half += 1; } }); if n_half % 2 == 0 { - (n_more_than_half + n_half / 2) as f64 + n_more_than_half + n_half / 2 } else { - (n_more_than_half + n_half / 2 + 1) as f64 + n_more_than_half + n_half / 2 + 1 } } @@ -970,8 +931,6 @@ where /// /// This bound is sometimes called LB3. /// -/// `epsilon` is the upper bound on the difference between two values to be considered equal. -/// /// # Examples /// /// ``` @@ -979,41 +938,37 @@ where /// /// let weights = [4, 2, 3, 5, 4, 3, 3]; /// -/// let n_bins = algorithms::compute_bin_packing_lb3(6, weights.iter().copied(), 0); -/// assert_eq!(n_bins, 5.0); +/// let n_bins = algorithms::compute_bin_packing_lb3(6, weights.iter().copied()); +/// assert_eq!(n_bins, 5); /// ``` -pub fn compute_bin_packing_lb3(capacity: T, weights: impl Iterator, epsilon: T) -> f64 +pub fn compute_bin_packing_lb3(capacity: T, weights: impl Iterator) -> usize where T: PartialOrd + Add + Copy, { - if capacity <= epsilon { - return f64::INFINITY; - } - let twice_capacity = capacity + capacity; let weight_sum = weights .map(|w| { let thrice = w + w + w; - if thrice > twice_capacity + epsilon { + if thrice > twice_capacity { 6 - } else if thrice + epsilon >= twice_capacity { + } else if thrice >= twice_capacity { 4 - } else if thrice > capacity + epsilon { + } else if thrice > capacity { 3 - } else if thrice + epsilon >= capacity { + } else if thrice >= capacity { 2 } else { 0 } }) - .sum::(); + .sum::(); if weight_sum % 6 == 0 { - (weight_sum / 6) as f64 + weight_sum / 6 } else { - (weight_sum / 6 + 1) as f64 + weight_sum / 6 + 1 } } @@ -1359,8 +1314,8 @@ mod tests { fn test_compute_fractional_knapsack_profit() { let sorted_weight_value_pairs = [(1, 2), (2, 3), (4, 5), (1, 1)]; assert_relative_eq!( - compute_fractional_knapsack_profit(5, sorted_weight_value_pairs.into_iter(), 1e-6), - 7.0 + compute_fractional_knapsack_profit(5, sorted_weight_value_pairs.into_iter()), + 7.5 ); } @@ -1368,64 +1323,32 @@ mod tests { fn test_compute_fractional_knapsack_profit_empty() { let sorted_weight_value_pairs: [(i32, i32); 0] = []; assert_relative_eq!( - compute_fractional_knapsack_profit(0, sorted_weight_value_pairs.into_iter(), 1e-6), + compute_fractional_knapsack_profit(0, sorted_weight_value_pairs.into_iter()), 0.0 ); } - #[test] - fn test_compute_fractional_bin_packing_cost() { - let weights = [2, 2, 3, 4, 5]; - assert_relative_eq!( - compute_fractional_bin_packing_cost(5, weights.iter().sum(), 0), - 4.0 - ); - } - - #[test] - fn test_compute_fractional_bin_packing_cost_empty() { - assert_relative_eq!(compute_fractional_bin_packing_cost(5, 0, 0), 0.0); - } - - #[test] - fn test_compute_fractional_bin_packing_cost_infinity() { - let weights = [2, 2, 3, 4, 5]; - assert!(compute_fractional_bin_packing_cost(0, weights.iter().sum(), 0).is_infinite()); - } - #[test] fn test_compute_bin_packing_lb2() { let weights = [4, 2, 3, 5, 4, 3, 3]; - assert_relative_eq!(compute_bin_packing_lb2(6, weights.into_iter(), 0), 5.0); + assert_eq!(compute_bin_packing_lb2(6, weights.into_iter()), 5); } #[test] fn test_compute_bin_packing_lb2_empty() { let weights: [i32; 0] = []; - assert_relative_eq!(compute_bin_packing_lb2(6, weights.into_iter(), 0), 0.0); - } - - #[test] - fn test_compute_bin_packing_lb2_infinity() { - let weights = [4, 2, 3, 5, 4, 3, 3]; - assert!(compute_bin_packing_lb2(0, weights.into_iter(), 0).is_infinite()); + assert_eq!(compute_bin_packing_lb2(6, weights.into_iter()), 0); } #[test] fn test_compute_bin_packing_lb3() { let weights = [4, 2, 3, 5, 4, 3, 3]; - assert_relative_eq!(compute_bin_packing_lb3(6, weights.into_iter(), 0), 5.0); + assert_eq!(compute_bin_packing_lb3(6, weights.into_iter()), 5); } #[test] fn test_compute_bin_packing_lb3_empty() { let weights: [i32; 0] = []; - assert_relative_eq!(compute_bin_packing_lb3(6, weights.into_iter(), 0), 0.0); - } - - #[test] - fn test_compute_bin_packing_lb3_infinity() { - let weights = [4, 2, 3, 5, 4, 3, 3]; - assert!(compute_bin_packing_lb3(0, weights.into_iter(), 0).is_infinite()); + assert_eq!(compute_bin_packing_lb3(6, weights.into_iter()), 0); } } diff --git a/src/dp.rs b/src/dp.rs index 48cf240..4b8311a 100644 --- a/src/dp.rs +++ b/src/dp.rs @@ -39,8 +39,10 @@ pub enum OptimizationMode { /// impl Dp for Tsp { /// type State = TspState; /// type CostType = i32; +/// type Label = usize; /// -/// fn get_target(&self) -> TspState { +/// +/// fn get_target(&self) -> Self::State { /// let mut unvisited = FixedBitSet::with_capacity(self.c.len()); /// unvisited.insert_range(1..); /// @@ -50,7 +52,10 @@ pub enum OptimizationMode { /// } /// } /// -/// fn get_successors(&self, state: &TspState) -> impl IntoIterator { +/// fn get_successors( +/// &self, +/// state: &Self::State, +/// ) -> impl IntoIterator { /// state.unvisited.ones().map(|next| { /// let mut unvisited = state.unvisited.clone(); /// unvisited.remove(next); @@ -65,7 +70,7 @@ pub enum OptimizationMode { /// }) /// } /// -/// fn get_base_cost(&self, state: &TspState) -> Option { +/// fn get_base_cost(&self, state: &Self::State) -> Option { /// if state.unvisited.is_clear() { /// Some(self.c[state.current][0]) /// } else { @@ -106,20 +111,22 @@ pub trait Dp { type State; /// Type of the cost. Usually, `i32` or `f64`. type CostType: PartialOrd + Add + Zero; + /// Type of the transition label. Usually, an unsigned integer type or `bool` (e.g., the knapsack problem). + type Label; /// Gets the target (initial) state. fn get_target(&self) -> Self::State; /// Gets the successors of a state. /// - /// The easiest way to implement this method is to return a vector (`Vec<(Self::State, Self::CostType, usize)>`) - /// or an array ([`(Self::State, Self::CostType, usize); N]`), where the first element of a tuple is the successor state, - /// the second element is the cost weight of the transition, and the third element is the index of the transition. + /// The easiest way to implement this method is to return a vector (`Vec<(Self::State, Self::CostType, Self::Label)>`) + /// or an array ([`(Self::State, Self::CostType, Self::Label); N]`), where the first element of a tuple is the successor state, + /// the second element is the cost weight of the transition, and the third element is the label of the transition. /// However, by returning an iterator, you can avoid allocating memory for the successors. fn get_successors( &self, state: &Self::State, - ) -> impl IntoIterator; + ) -> impl IntoIterator; /// Checks if a state is a base (goal) state and returns the base cost if it is. fn get_base_cost(&self, state: &Self::State) -> Option; @@ -182,7 +189,7 @@ pub trait Dp { /// type State = TspState; /// type Key = (FixedBitSet, usize); /// -/// fn get_key(&self, state: &TspState) -> Self::Key { +/// fn get_key(&self, state: &Self::State) -> Self::Key { /// (state.unvisited.clone(), state.current) /// } /// } @@ -248,7 +255,7 @@ pub trait Dominance { /// type State = TspState; /// type CostType = i32; /// -/// fn get_dual_bound(&self, _: &TspState) -> Option { +/// fn get_dual_bound(&self, _: &Self::State) -> Option { /// Some(0) /// } /// } @@ -297,6 +304,7 @@ mod tests { impl Dp for MockDp { type State = i32; type CostType = i32; + type Label = usize; fn get_target(&self) -> Self::State { 0 @@ -305,7 +313,7 @@ mod tests { fn get_successors( &self, _: &Self::State, - ) -> impl IntoIterator { + ) -> impl IntoIterator { vec![] } diff --git a/src/io.rs b/src/io.rs index 6317131..fa4d5f6 100644 --- a/src/io.rs +++ b/src/io.rs @@ -64,13 +64,14 @@ where /// /// The first field is the time, second is the cost, third is the bound, fourth is the transitions, /// fifth is the expanded, and sixth is the generated. -pub fn run_solver_and_dump_solution_history( +pub fn run_solver_and_dump_solution_history( solver: &mut S, filename: &str, -) -> Result, Box> +) -> Result, Box> where - S: Search, + S: Search, C: Display + Copy, + L: Display, { let mut file = OpenOptions::new() .create(true) @@ -85,19 +86,23 @@ where let transitions = solution .transitions .iter() - .map(|t| format!("{}", t)) + .map(|t| format!("{t}")) .collect::>() .join(" "); let line = if let Some(bound) = solution.best_bound { format!( - "{}, {}, {}, {}, {}, {}\n", - solution.time, cost, bound, transitions, solution.expanded, solution.generated + "{time}, {cost}, {bound}, {transitions}, {expanded}, {generated}\n", + time = solution.time, + expanded = solution.expanded, + generated = solution.generated ) } else { format!( - "{}, {}, , {}, {}, {}\n", - solution.time, cost, transitions, solution.expanded, solution.generated + "{time}, {cost}, , {transitions}, {expanded}, {generated}\n", + time = solution.time, + expanded = solution.expanded, + generated = solution.generated ) }; file.write_all(line.as_bytes())?; @@ -111,15 +116,15 @@ where } /// Print the cost, bound, and statistics of a solution. -pub fn print_solution_statistics(solution: &Solution) +pub fn print_solution_statistics(solution: &Solution) where C: Copy + Display, { if let Some(cost) = solution.cost { - println!("cost: {}", cost); + println!("cost: {cost}"); if solution.is_optimal { - println!("optimal cost: {}", cost); + println!("optimal cost: {cost}"); } } else { println!("No solution is found."); @@ -130,12 +135,12 @@ where } if let Some(bound) = solution.best_bound { - println!("best bound: {}", bound); + println!("best bound: {bound}"); } - println!("Search time: {}s", solution.time); - println!("Expanded: {}", solution.expanded); - println!("Generated: {}", solution.generated); + println!("Search time: {time}s", time = solution.time); + println!("Expanded: {expanded}", expanded = solution.expanded); + println!("Generated: {generated}", generated = solution.generated); } #[cfg(test)] diff --git a/src/solvers/astar.rs b/src/solvers/astar.rs index 2635bd3..723e45e 100644 --- a/src/solvers/astar.rs +++ b/src/solvers/astar.rs @@ -30,8 +30,9 @@ use std::hash::Hash; /// impl Dp for Tsp { /// type State = TspState; /// type CostType = i32; +/// type Label = usize; /// -/// fn get_target(&self) -> TspState { +/// fn get_target(&self) -> Self::State { /// let mut unvisited = FixedBitSet::with_capacity(self.c.len()); /// unvisited.insert_range(1..); /// @@ -41,7 +42,10 @@ use std::hash::Hash; /// } /// } /// -/// fn get_successors(&self, state: &TspState) -> impl IntoIterator { +/// fn get_successors( +/// &self, +/// state: &Self::State, +/// ) -> impl IntoIterator { /// state.unvisited.ones().map(|next| { /// let mut unvisited = state.unvisited.clone(); /// unvisited.remove(next); @@ -56,7 +60,7 @@ use std::hash::Hash; /// }) /// } /// -/// fn get_base_cost(&self, state: &TspState) -> Option { +/// fn get_base_cost(&self, state: &TspState) -> Option { /// if state.unvisited.is_clear() { /// Some(self.c[state.current][0]) /// } else { @@ -69,7 +73,7 @@ use std::hash::Hash; /// type State = TspState; /// type Key = (FixedBitSet, usize); /// -/// fn get_key(&self, state: &TspState) -> Self::Key { +/// fn get_key(&self, state: &Self::State) -> Self::Key { /// (state.unvisited.clone(), state.current) /// } /// } @@ -78,7 +82,7 @@ use std::hash::Hash; /// type State = TspState; /// type CostType = i32; /// -/// fn get_dual_bound(&self, state: &TspState) -> Option { +/// fn get_dual_bound(&self, state: &Self::State) -> Option { /// Some(0) /// } /// } @@ -96,13 +100,16 @@ use std::hash::Hash; /// assert!(!solution.is_infeasible); /// assert_eq!(solution.best_bound, Some(6)); /// ``` -pub fn create_astar( +pub fn create_astar( dp: D, mut parameters: SearchParameters, -) -> impl Search +) -> impl Search where - D: Dp + Dominance + Bound, + D: Dp + + Dominance + + Bound, C: Ord + Copy + Signed + Display, + L: Default + Copy, K: Hash + Eq, { let root_node_constructor = |dp: &D, bound| { @@ -112,12 +119,12 @@ where state, cost, transition, - parent: &DualBoundNode<_, _, _>, + parent: &DualBoundNode<_, _, _, _>, primal_bound, other: Option<&_>| { parent.create_child(dp, state, cost, transition, primal_bound, other) }; - let solution_checker = |dp: &_, node: &DualBoundNode<_, _, _>| node.check_solution(dp); + let solution_checker = |dp: &_, node: &DualBoundNode<_, _, _, _>| node.check_solution(dp); parameters.update_bounds(&dp); BestFirstSearch::new( @@ -141,6 +148,7 @@ mod tests { impl Dp for MockDp { type State = i32; type CostType = i32; + type Label = usize; fn get_target(&self) -> Self::State { self.0 @@ -149,16 +157,12 @@ mod tests { fn get_successors( &self, state: &Self::State, - ) -> impl IntoIterator { + ) -> impl IntoIterator { vec![(*state - 1, 1, 1)] } fn get_base_cost(&self, state: &Self::State) -> Option { - if *state <= 0 { - Some(0) - } else { - None - } + if *state <= 0 { Some(0) } else { None } } } @@ -186,6 +190,7 @@ mod tests { type DpData = MockDp; type State = i32; type CostType = i32; + type Label = usize; fn get_state(&self, _: &Self::DpData) -> &Self::State { &self.0 @@ -211,7 +216,7 @@ mod tests { self.2.get() } - fn get_transitions(&self, _: &Self::DpData) -> Vec { + fn get_transitions(&self, _: &Self::DpData) -> Vec { self.3.clone() } } diff --git a/src/solvers/cabs.rs b/src/solvers/cabs.rs index 6b2fef6..10e3d1e 100644 --- a/src/solvers/cabs.rs +++ b/src/solvers/cabs.rs @@ -31,8 +31,9 @@ use std::hash::Hash; /// impl Dp for Tsp { /// type State = TspState; /// type CostType = i32; +/// type Label = usize; /// -/// fn get_target(&self) -> TspState { +/// fn get_target(&self) -> Self::State { /// let mut unvisited = FixedBitSet::with_capacity(self.c.len()); /// unvisited.insert_range(1..); /// @@ -42,7 +43,10 @@ use std::hash::Hash; /// } /// } /// -/// fn get_successors(&self, state: &TspState) -> impl IntoIterator { +/// fn get_successors( +/// &self, +/// state: &Self::State, +/// ) -> impl IntoIterator { /// state.unvisited.ones().map(|next| { /// let mut unvisited = state.unvisited.clone(); /// unvisited.remove(next); @@ -57,7 +61,7 @@ use std::hash::Hash; /// }) /// } /// -/// fn get_base_cost(&self, state: &TspState) -> Option { +/// fn get_base_cost(&self, state: &Self::State) -> Option { /// if state.unvisited.is_clear() { /// Some(self.c[state.current][0]) /// } else { @@ -70,7 +74,7 @@ use std::hash::Hash; /// type State = TspState; /// type Key = (FixedBitSet, usize); /// -/// fn get_key(&self, state: &TspState) -> Self::Key { +/// fn get_key(&self, state: &Self::State) -> Self::Key { /// (state.unvisited.clone(), state.current) /// } /// } @@ -79,7 +83,7 @@ use std::hash::Hash; /// type State = TspState; /// type CostType = i32; /// -/// fn get_dual_bound(&self, state: &TspState) -> Option { +/// fn get_dual_bound(&self, state: &Self::State) -> Option { /// Some(0) /// } /// } @@ -98,24 +102,27 @@ use std::hash::Hash; /// assert!(!solution.is_infeasible); /// assert_eq!(solution.best_bound, Some(6)); /// ``` -pub fn create_cabs( +pub fn create_cabs( dp: D, mut parameters: SearchParameters, cabs_parameters: CabsParameters, -) -> impl Search +) -> impl Search where - D: Dp + Dominance + Bound, + D: Dp + + Dominance + + Bound, C: Ord + Copy + Signed + Display, + L: Default + Copy, K: Hash + Eq, { let root_node_constructor = |dp: &D, bound| { DualBoundNode::create_root(dp, dp.get_target(), dp.get_identity_weight(), bound) }; let node_constructor = - |dp: &_, state, cost, transition, parent: &DualBoundNode<_, _, _>, primal_bound| { + |dp: &_, state, cost, transition, parent: &DualBoundNode<_, _, _, _>, primal_bound| { parent.create_child(dp, state, cost, transition, primal_bound, None) }; - let solution_checker = |dp: &_, node: &DualBoundNode<_, _, _>| node.check_solution(dp); + let solution_checker = |dp: &_, node: &DualBoundNode<_, _, _, _>| node.check_solution(dp); let beam_search_closure = move |dp: &_, root_node, parameters: &_| { search_algorithms::beam_search( dp, @@ -161,6 +168,7 @@ where /// impl Dp for Tsp { /// type State = TspState; /// type CostType = i32; +/// type Label = usize; /// /// fn get_target(&self) -> TspState { /// let mut unvisited = FixedBitSet::with_capacity(self.c.len()); @@ -219,14 +227,15 @@ where /// assert!(!solution.is_infeasible); /// assert_eq!(solution.best_bound, Some(6)); /// ``` -pub fn create_blind_cabs( +pub fn create_blind_cabs( dp: D, parameters: SearchParameters, cabs_parameters: CabsParameters, -) -> impl Search +) -> impl Search where - D: Dp + Dominance, + D: Dp + Dominance, C: Ord + Copy + Signed + Display, + L: Default + Copy, K: Hash + Eq, { let root_node_constructor = |dp: &D, _| { @@ -236,10 +245,10 @@ where dp.get_identity_weight(), )) }; - let node_constructor = |dp: &_, state, cost, transition, parent: &CostNode<_, _, _>, _| { + let node_constructor = |dp: &_, state, cost, transition, parent: &CostNode<_, _, _, _>, _| { Some(parent.create_child(dp, state, cost, transition)) }; - let solution_checker = |dp: &_, node: &CostNode<_, _, _>| node.check_solution(dp); + let solution_checker = |dp: &_, node: &CostNode<_, _, _, _>| node.check_solution(dp); let beam_search_closure = move |dp: &_, root_node, parameters: &_| { search_algorithms::beam_search( dp, @@ -271,6 +280,7 @@ mod tests { impl Dp for MockDp { type State = i32; type CostType = i32; + type Label = usize; fn get_target(&self) -> Self::State { self.0 @@ -279,16 +289,12 @@ mod tests { fn get_successors( &self, state: &Self::State, - ) -> impl IntoIterator { + ) -> impl IntoIterator { vec![(*state - 1, 1, 1)] } fn get_base_cost(&self, state: &Self::State) -> Option { - if *state <= 0 { - Some(0) - } else { - None - } + if *state <= 0 { Some(0) } else { None } } } @@ -316,6 +322,7 @@ mod tests { type DpData = MockDp; type State = i32; type CostType = i32; + type Label = usize; fn get_state(&self, _: &Self::DpData) -> &Self::State { &self.0 @@ -341,7 +348,7 @@ mod tests { self.2.get() } - fn get_transitions(&self, _: &Self::DpData) -> Vec { + fn get_transitions(&self, _: &Self::DpData) -> Vec { self.3.clone() } } diff --git a/src/solvers/dijkstra.rs b/src/solvers/dijkstra.rs index 9e31256..e2c61a6 100644 --- a/src/solvers/dijkstra.rs +++ b/src/solvers/dijkstra.rs @@ -30,8 +30,9 @@ use std::hash::Hash; /// impl Dp for Tsp { /// type State = TspState; /// type CostType = i32; +/// type Label = usize; /// -/// fn get_target(&self) -> TspState { +/// fn get_target(&self) -> Self::State { /// let mut unvisited = FixedBitSet::with_capacity(self.c.len()); /// unvisited.insert_range(1..); /// @@ -41,7 +42,10 @@ use std::hash::Hash; /// } /// } /// -/// fn get_successors(&self, state: &TspState) -> impl IntoIterator { +/// fn get_successors( +/// &self, +/// state: &Self::State, +/// ) -> impl IntoIterator { /// state.unvisited.ones().map(|next| { /// let mut unvisited = state.unvisited.clone(); /// unvisited.remove(next); @@ -56,7 +60,7 @@ use std::hash::Hash; /// }) /// } /// -/// fn get_base_cost(&self, state: &TspState) -> Option { +/// fn get_base_cost(&self, state: &Self::State) -> Option { /// if state.unvisited.is_clear() { /// Some(self.c[state.current][0]) /// } else { @@ -69,7 +73,7 @@ use std::hash::Hash; /// type State = TspState; /// type Key = (FixedBitSet, usize); /// -/// fn get_key(&self, state: &TspState) -> Self::Key { +/// fn get_key(&self, state: &Self::State) -> Self::Key { /// (state.unvisited.clone(), state.current) /// } /// } @@ -87,13 +91,14 @@ use std::hash::Hash; /// assert!(!solution.is_infeasible); /// assert_eq!(solution.best_bound, Some(6)); /// ``` -pub fn create_dijkstra( +pub fn create_dijkstra( dp: D, parameters: SearchParameters, -) -> impl Search +) -> impl Search where - D: Dp + Dominance, + D: Dp + Dominance, C: Ord + Copy + Signed + Display, + L: Default + Copy, K: Hash + Eq, { let root_node_constructor = |dp: &D, _| { @@ -104,10 +109,10 @@ where )) }; let node_constructor = - |dp: &_, state, cost, transition, parent: &CostNode<_, _, _>, _, _: Option<&_>| { + |dp: &_, state, cost, transition, parent: &CostNode<_, _, _, _>, _, _: Option<&_>| { Some(parent.create_child(dp, state, cost, transition)) }; - let solution_checker = |dp: &_, node: &CostNode<_, _, _>| node.check_solution(dp); + let solution_checker = |dp: &_, node: &CostNode<_, _, _, _>| node.check_solution(dp); BestFirstSearch::new( dp, @@ -121,6 +126,7 @@ where #[cfg(test)] mod tests { use super::*; + use crate::Solution; use std::cell::Cell; use std::cmp::Ordering; @@ -130,6 +136,7 @@ mod tests { impl Dp for MockDp { type State = i32; type CostType = i32; + type Label = usize; fn get_target(&self) -> Self::State { self.0 @@ -138,16 +145,12 @@ mod tests { fn get_successors( &self, state: &Self::State, - ) -> impl IntoIterator { + ) -> impl IntoIterator { vec![(*state - 1, 1, 1)] } fn get_base_cost(&self, state: &Self::State) -> Option { - if *state <= 0 { - Some(0) - } else { - None - } + if *state <= 0 { Some(0) } else { None } } } @@ -166,6 +169,7 @@ mod tests { type DpData = MockDp; type State = i32; type CostType = i32; + type Label = usize; fn get_state(&self, _: &Self::DpData) -> &Self::State { &self.0 @@ -191,7 +195,7 @@ mod tests { self.2.get() } - fn get_transitions(&self, _: &Self::DpData) -> Vec { + fn get_transitions(&self, _: &Self::DpData) -> Vec { self.3.clone() } } @@ -225,7 +229,7 @@ mod tests { }; let mut search = create_dijkstra(dp, parameters); - let solution = search.search(); + let solution: Solution<_, usize> = search.search(); assert_eq!(solution.cost, Some(2)); assert_eq!(solution.transitions, vec![1, 1]); assert_eq!(solution.best_bound, Some(2)); @@ -245,7 +249,7 @@ mod tests { }; let mut search = create_dijkstra(dp, parameters); - let solution = search.search(); + let solution: Solution<_, usize> = search.search(); assert_eq!(solution.cost, None); assert_eq!(solution.transitions, vec![]); assert_eq!(solution.best_bound, None); diff --git a/src/solvers/search_algorithms/beam_search.rs b/src/solvers/search_algorithms/beam_search.rs index 3a01627..a0327e1 100644 --- a/src/solvers/search_algorithms/beam_search.rs +++ b/src/solvers/search_algorithms/beam_search.rs @@ -1,12 +1,12 @@ +use super::SearchParameters; use super::search::Solution; use super::search_nodes::{SearchNode, StateRegistry}; -use super::SearchParameters; +use crate::Dominance; use crate::dp::Dp; use crate::timer::Timer; -use crate::Dominance; use smallvec::SmallVec; use std::cmp::Reverse; -use std::collections::{binary_heap, BinaryHeap}; +use std::collections::{BinaryHeap, binary_heap}; use std::fmt::Display; use std::hash::Hash; use std::mem; @@ -83,7 +83,7 @@ where fn clean_garbage(&mut self) { let mut peek = self.queue.peek(); - while peek.map_or(false, |node| node.0.is_closed()) { + while peek.is_some_and(|node| node.0.is_closed()) { self.queue.pop(); peek = self.queue.peek(); } @@ -124,7 +124,7 @@ where removed: None, }; - if self.size < self.beam_width || self.queue.peek().map_or(true, |peek| node > *peek.0) { + if self.size < self.beam_width || self.queue.peek().is_none_or(|peek| node > *peek.0) { let insertion_result = registry.insert_if_not_dominated(dp, node); for d in insertion_result.dominated.iter() { @@ -194,20 +194,21 @@ where /// /// `solution_checker` is a function that checks whether the given node is a solution /// and returns the cost and transitions if it is. -pub fn beam_search( +pub fn beam_search( dp: &D, root_node: N, mut node_constructor: F, mut solution_checker: G, parameters: &BeamSearchParameters, -) -> Solution +) -> Solution where - D: Dp + Dominance, + D: Dp + Dominance, C: Ord + Copy + Display, + L: Copy, K: Hash + Eq, - N: Ord + SearchNode, - F: FnMut(&D, S, C, usize, &N, Option) -> Option, - G: FnMut(&D, &N) -> Option<(C, Vec)>, + N: Ord + SearchNode, + F: FnMut(&D, S, C, L, &N, Option) -> Option, + G: FnMut(&D, &N) -> Option<(C, Vec)>, { let timer = parameters .search_parameters @@ -259,18 +260,18 @@ where } if let Some((solution_cost, transitions)) = solution_checker(dp, &node) { - if primal_bound.map_or(true, |bound| dp.is_better_cost(solution_cost, bound)) { + if primal_bound.is_none_or(|bound| dp.is_better_cost(solution_cost, bound)) { primal_bound = Some(solution_cost); solution.cost = Some(solution_cost); solution.transitions = transitions; if !quiet { println!( - "New primal bound: {}, expanded: {}, generated: {}, elapsed time: {}s.", - solution_cost, - solution.expanded, - solution.generated, - timer.get_elapsed_time() + "New primal bound: {solution_cost}, expanded: {expanded}, generated: {generated}, elapsed time: {time}s.", + solution_cost = solution_cost, + expanded = solution.expanded, + generated = solution.generated, + time = timer.get_elapsed_time() ); } } @@ -301,24 +302,23 @@ where if let Some(bound) = successor_bound { if layer_dual_bound - .map_or(true, |layer_bound| dp.is_better_cost(bound, layer_bound)) + .is_none_or(|layer_bound| dp.is_better_cost(bound, layer_bound)) { layer_dual_bound = Some(bound); } if result.is_pruned - && removed_dual_bound.map_or(true, |removed_bound| { - dp.is_better_cost(bound, removed_bound) - }) + && removed_dual_bound + .is_none_or(|removed_bound| dp.is_better_cost(bound, removed_bound)) { removed_dual_bound = Some(bound); } } if let Some(bound) = result.removed.and_then(|removed| removed.get_bound(dp)) { - if removed_dual_bound.map_or(true, |removed_bound| { - dp.is_better_cost(bound, removed_bound) - }) { + if removed_dual_bound + .is_none_or(|removed_bound| dp.is_better_cost(bound, removed_bound)) + { removed_dual_bound = Some(bound); } } @@ -331,7 +331,7 @@ where solution.expanded += 1; - if expansion_limit.map_or(false, |limit| solution.expanded >= limit) { + if expansion_limit.is_some_and(|limit| solution.expanded >= limit) { if !quiet { println!("Expansion limit reached."); } @@ -345,16 +345,15 @@ where if !quiet { println!( - "Layer: {}, expanded: {}, generated: {}, elapsed time: {}s", - layer_index, - solution.expanded, - solution.generated, - timer.get_elapsed_time() + "Layer: {layer_index}, expanded: {expanded}, generated: {generated}, elapsed time: {time}s", + expanded = solution.expanded, + generated = solution.generated, + time = timer.get_elapsed_time() ); } if let Some(bound) = layer_dual_bound { - if primal_bound.map_or(false, |primal_bound| dp.is_better_cost(primal_bound, bound)) { + if primal_bound.is_some_and(|primal_bound| dp.is_better_cost(primal_bound, bound)) { solution.best_bound = primal_bound; solution.is_optimal = solution.cost.is_some(); solution.is_infeasible = solution.cost.is_none(); @@ -363,12 +362,12 @@ where return solution; } else if solution .best_bound - .map_or(true, |best_bound| dp.is_better_cost(best_bound, bound)) + .is_none_or(|best_bound| dp.is_better_cost(best_bound, bound)) { solution.best_bound = Some(bound); if !quiet { - println!("New dual bound: {}", bound); + println!("New dual bound: {bound}"); } } } @@ -415,6 +414,7 @@ mod tests { impl Dp for MockDp { type State = i32; type CostType = i32; + type Label = usize; fn get_target(&self) -> Self::State { self.0 @@ -423,16 +423,12 @@ mod tests { fn get_successors( &self, state: &Self::State, - ) -> impl IntoIterator { + ) -> impl IntoIterator { vec![(*state - 1, 1, 1)] } fn get_base_cost(&self, state: &Self::State) -> Option { - if *state <= 0 { - Some(0) - } else { - None - } + if *state <= 0 { Some(0) } else { None } } } @@ -451,6 +447,7 @@ mod tests { type DpData = MockDp; type State = i32; type CostType = i32; + type Label = usize; fn get_state(&self, _: &Self::DpData) -> &Self::State { &self.0 @@ -476,7 +473,7 @@ mod tests { self.2.get() } - fn get_transitions(&self, _: &Self::DpData) -> Vec { + fn get_transitions(&self, _: &Self::DpData) -> Vec { self.3.clone() } } diff --git a/src/solvers/search_algorithms/best_first_search.rs b/src/solvers/search_algorithms/best_first_search.rs index 53a15a2..c36fb96 100644 --- a/src/solvers/search_algorithms/best_first_search.rs +++ b/src/solvers/search_algorithms/best_first_search.rs @@ -8,20 +8,21 @@ use std::hash::Hash; use std::rc::Rc; /// Best-first search. -pub struct BestFirstSearch { - base: SearchBase, +pub struct BestFirstSearch { + base: SearchBase, open: BinaryHeap>, timer: Timer, } -impl BestFirstSearch +impl BestFirstSearch where - D: Dp + Dominance, + D: Dp + Dominance, C: Ord + Copy + Display, + L: Copy, K: Hash + Eq, - N: Ord + SearchNode, - F: FnMut(&D, S, C, usize, &N, Option, Option<&N>) -> Option, - G: FnMut(&D, &N) -> Option<(C, Vec)>, + N: Ord + SearchNode, + F: FnMut(&D, S, C, L, &N, Option, Option<&N>) -> Option, + G: FnMut(&D, &N) -> Option<(C, Vec)>, { /// Creates a new instance of the best-first search algorithm. /// @@ -63,18 +64,20 @@ where } } -impl Search for BestFirstSearch +impl Search for BestFirstSearch where - D: Dp + Dominance, + D: Dp + Dominance, C: Ord + Copy + Display, + L: Copy, K: Hash + Eq, - N: Ord + SearchNode, - F: FnMut(&D, S, C, usize, &N, Option, Option<&N>) -> Option, - G: FnMut(&D, &N) -> Option<(C, Vec)>, + N: Ord + SearchNode, + F: FnMut(&D, S, C, L, &N, Option, Option<&N>) -> Option, + G: FnMut(&D, &N) -> Option<(C, Vec)>, { type CostType = C; + type Label = L; - fn search_next(&mut self) -> (Solution, bool) { + fn search_next(&mut self) -> (Solution, bool) { self.timer.start(); if self.base.is_terminated() { @@ -142,6 +145,7 @@ mod tests { impl Dp for MockDp { type State = i32; type CostType = i32; + type Label = usize; fn get_target(&self) -> Self::State { self.0 @@ -150,16 +154,12 @@ mod tests { fn get_successors( &self, state: &Self::State, - ) -> impl IntoIterator { + ) -> impl IntoIterator { vec![(*state - 1, 1, 1)] } fn get_base_cost(&self, state: &Self::State) -> Option { - if *state <= 0 { - Some(0) - } else { - None - } + if *state <= 0 { Some(0) } else { None } } } @@ -178,6 +178,7 @@ mod tests { type DpData = MockDp; type State = i32; type CostType = i32; + type Label = usize; fn get_state(&self, _: &Self::DpData) -> &Self::State { &self.0 @@ -203,7 +204,7 @@ mod tests { self.2.get() } - fn get_transitions(&self, _: &Self::DpData) -> Vec { + fn get_transitions(&self, _: &Self::DpData) -> Vec { self.3.clone() } } diff --git a/src/solvers/search_algorithms/cabs.rs b/src/solvers/search_algorithms/cabs.rs index 70014d6..79e0a48 100644 --- a/src/solvers/search_algorithms/cabs.rs +++ b/src/solvers/search_algorithms/cabs.rs @@ -29,7 +29,7 @@ impl Default for CabsParameters { /// Complete anytime beam search (CABS). /// /// CABS repeats beam search while doubling the beam width until a termination condition is met. -pub struct Cabs { +pub struct Cabs { dp: D, root_node_constructor: R, beam_search: B, @@ -37,17 +37,18 @@ pub struct Cabs { cabs_parameters: CabsParameters, beam_width: usize, primal_bound: Option, - solution: Solution, + solution: Solution, is_terminated: bool, timer: Timer, } -impl Cabs +impl Cabs where - D: Dp + Dominance, + D: Dp + Dominance, + L: Clone, R: FnMut(&D, Option) -> Option, - N: SearchNode, - B: FnMut(&D, N, &BeamSearchParameters) -> Solution, + N: SearchNode, + B: FnMut(&D, N, &BeamSearchParameters) -> Solution, C: Ord + Copy + Display, K: Hash + Eq, { @@ -98,11 +99,10 @@ where { if !self.parameters.quiet { println!( - "New dual bound: {}, expanded: {}, generated: {}, elapsed time: {}s.", - dual_bound, - self.solution.expanded, - self.solution.generated, - self.timer.get_elapsed_time() + "New dual bound: {dual_bound}, expanded: {expanded}, generated: {generated}, elapsed time: {time}s.", + expanded = self.solution.expanded, + generated = self.solution.generated, + time = self.timer.get_elapsed_time() ); } @@ -114,7 +114,7 @@ where } } - fn stop_timer_and_return_solution(&mut self) -> Solution { + fn stop_timer_and_return_solution(&mut self) -> Solution { self.solution.time = self.timer.get_elapsed_time(); self.timer.stop(); @@ -122,18 +122,20 @@ where } } -impl Search for Cabs +impl Search for Cabs where - D: Dp + Dominance, + D: Dp + Dominance, + L: Clone, R: FnMut(&D, Option) -> Option, - N: SearchNode, - B: FnMut(&D, N, &BeamSearchParameters) -> Solution, + N: SearchNode, + B: FnMut(&D, N, &BeamSearchParameters) -> Solution, C: Ord + Copy + Display, K: Hash + Eq, { type CostType = C; + type Label = L; - fn search_next(&mut self) -> (Solution, bool) { + fn search_next(&mut self) -> (Solution, bool) { self.timer.start(); if self.is_terminated { @@ -191,11 +193,11 @@ where if !self.parameters.quiet { println!( - "Searched with beam width {}, expanded: {}, generated: {}, elapsed time: {}s.", - self.beam_width, - self.solution.expanded, - self.solution.generated, - self.timer.get_elapsed_time() + "Searched with beam width {beam_width}, expanded: {expanded}, generated: {generated}, elapsed time: {time}s.", + beam_width = self.beam_width, + expanded = self.solution.expanded, + generated = self.solution.generated, + time = self.timer.get_elapsed_time() ); } @@ -217,11 +219,11 @@ where if !self.parameters.quiet { println!( - "New primal bound {}, expanded: {}, generated: {}, elapsed time: {}s.", - cost, - self.solution.expanded, - self.solution.generated, - self.timer.get_elapsed_time() + "New primal bound: {solution_cost}, expanded: {expanded}, generated: {generated}, elapsed time: {time}s.", + solution_cost = cost, + expanded = self.solution.expanded, + generated = self.solution.generated, + time = self.timer.get_elapsed_time() ); } @@ -285,6 +287,7 @@ mod tests { impl Dp for MockDp { type State = i32; type CostType = i32; + type Label = usize; fn get_target(&self) -> Self::State { self.0 @@ -293,16 +296,12 @@ mod tests { fn get_successors( &self, state: &Self::State, - ) -> impl IntoIterator { + ) -> impl IntoIterator { vec![(*state - 1, 1, 1)] } fn get_base_cost(&self, state: &Self::State) -> Option { - if *state <= 0 { - Some(0) - } else { - None - } + if *state <= 0 { Some(0) } else { None } } } @@ -321,6 +320,7 @@ mod tests { type DpData = MockDp; type State = i32; type CostType = i32; + type Label = usize; fn get_state(&self, _: &Self::DpData) -> &Self::State { &self.0 @@ -346,7 +346,7 @@ mod tests { self.2.get() } - fn get_transitions(&self, _: &Self::DpData) -> Vec { + fn get_transitions(&self, _: &Self::DpData) -> Vec { self.3.clone() } } diff --git a/src/solvers/search_algorithms/search.rs b/src/solvers/search_algorithms/search.rs index c46379e..089434e 100644 --- a/src/solvers/search_algorithms/search.rs +++ b/src/solvers/search_algorithms/search.rs @@ -1,7 +1,7 @@ use super::search_nodes::{SearchNode, StateRegistry}; +use crate::Bound; use crate::dp::{Dominance, Dp}; use crate::timer::Timer; -use crate::Bound; use std::fmt::Display; use std::hash::Hash; use std::rc::Rc; @@ -54,7 +54,7 @@ impl SearchParameters { /// Solution information. #[derive(Clone, PartialEq, Debug)] -pub struct Solution { +pub struct Solution { /// Cost of the solution. pub cost: Option, /// Best dual bound found (lower/upper bound for minimization/maximization). @@ -64,7 +64,7 @@ pub struct Solution { /// Whether the model is infeasible. pub is_infeasible: bool, /// Transitions of the solution. - pub transitions: Vec, + pub transitions: Vec, /// Number of nodes expanded. pub expanded: usize, /// Number of nodes generated. @@ -77,7 +77,7 @@ pub struct Solution { pub is_expansion_limit_reached: bool, } -impl Default for Solution { +impl Default for Solution { fn default() -> Self { Self { cost: None, @@ -97,14 +97,15 @@ impl Default for Solution { /// Search trait. pub trait Search { type CostType; + type Label; /// Searches for the next solution. /// /// The second return value indicates whether the search is terminated. - fn search_next(&mut self) -> (Solution, bool); + fn search_next(&mut self) -> (Solution, bool); /// Performs search until termination. - fn search(&mut self) -> Solution { + fn search(&mut self) -> Solution { loop { let (solution, terminated) = self.search_next(); @@ -119,43 +120,45 @@ pub trait Search { /// /// - `D` is the DP model. /// - `C` is the cost type associated with the DP model. +/// - `L` is the transition label type associated with the DP model. /// - `K` is the key type associated with dominance detection. /// - `N` is the search node type. /// - `F` is the node constructor, which generates a new node. /// - `G` is the solution checker, which checks whether a node is a solution. -pub struct SearchBase { +pub struct SearchBase { dp: D, node_constructor: F, solution_checker: G, parameters: SearchParameters, registry: StateRegistry, primal_bound: Option, - solution: Solution, + solution: Solution, } /// Expansion result. #[derive(Clone, PartialEq, Debug)] -pub enum ExpansionResult { +pub enum ExpansionResult { /// Node is closed. Closed, /// Node is pruned by bound. PrunedByBound, /// Solution found. - Solution(C, Vec), + Solution(C, Vec), /// Solution found but pruned since it is not better. SolutionPruned, /// Node is expanded. Expanded, } -impl SearchBase +impl SearchBase where - D: Dp + Dominance, + D: Dp + Dominance, C: Ord + Copy + Display, + L: Copy, K: Hash + Eq, N: Ord + SearchNode, - F: FnMut(&D, S, C, usize, &N, Option, Option<&N>) -> Option, - G: FnMut(&D, &N) -> Option<(C, Vec)>, + F: FnMut(&D, S, C, L, &N, Option, Option<&N>) -> Option, + G: FnMut(&D, &N) -> Option<(C, Vec)>, { /// Creates a new search base. /// @@ -215,7 +218,7 @@ where node: &N, callback: &mut impl FnMut(Rc), timer: &Timer, - ) -> ExpansionResult { + ) -> ExpansionResult { if node.is_closed() { return ExpansionResult::Closed; } @@ -233,7 +236,7 @@ where if let Some((solution_cost, transitions)) = (self.solution_checker)(&self.dp, node) { if self .primal_bound - .map_or(true, |bound| self.dp.is_better_cost(solution_cost, bound)) + .is_none_or(|bound| self.dp.is_better_cost(solution_cost, bound)) { self.primal_bound = Some(solution_cost); self.solution.cost = Some(solution_cost); @@ -245,11 +248,10 @@ where if !self.parameters.quiet { println!( - "New primal bound: {}, expanded: {}, generated: {}, elapsed time: {}s.", - solution_cost, - self.solution.expanded, - self.solution.generated, - timer.get_elapsed_time() + "New primal bound: {solution_cost}, expanded: {expanded}, generated: {generated}, elapsed time: {time}s.", + expanded = self.solution.expanded, + generated = self.solution.generated, + time = timer.get_elapsed_time() ); } @@ -306,7 +308,7 @@ where if self .parameters .expansion_limit - .map_or(false, |limit| self.solution.expanded >= limit) + .is_some_and(|limit| self.solution.expanded >= limit) { if !self.parameters.quiet { println!("Expansion limit reached."); @@ -338,11 +340,10 @@ where { if !self.parameters.quiet { println!( - "New dual bound: {}, expanded: {}, generated: {}, elapsed time: {}s.", - dual_bound, - self.solution.expanded, - self.solution.generated, - timer.get_elapsed_time() + "New dual bound: {dual_bound}, expanded: {expanded}, generated: {generated}, elapsed time: {time}s.", + expanded = self.solution.expanded, + generated = self.solution.generated, + time = timer.get_elapsed_time() ); } @@ -402,7 +403,7 @@ where } /// Returns the solution. - pub fn get_solution(&self) -> &Solution { + pub fn get_solution(&self) -> &Solution { &self.solution } } @@ -419,6 +420,7 @@ mod tests { impl Dp for MockDp { type State = i32; type CostType = i32; + type Label = usize; fn get_target(&self) -> Self::State { self.0 @@ -432,11 +434,7 @@ mod tests { } fn get_base_cost(&self, state: &Self::State) -> Option { - if *state <= 0 { - Some(0) - } else { - None - } + if *state <= 0 { Some(0) } else { None } } } @@ -472,6 +470,7 @@ mod tests { type DpData = MockDp; type State = i32; type CostType = i32; + type Label = usize; fn get_state(&self, _: &Self::DpData) -> &Self::State { &self.0 @@ -497,7 +496,7 @@ mod tests { self.2.get() } - fn get_transitions(&self, _: &Self::DpData) -> Vec { + fn get_transitions(&self, _: &Self::DpData) -> Vec { self.3.clone() } } @@ -526,8 +525,9 @@ mod tests { impl Search for MockSearch { type CostType = i32; + type Label = usize; - fn search_next(&mut self) -> (Solution, bool) { + fn search_next(&mut self) -> (Solution, bool) { ( Solution { cost: Some(42), diff --git a/src/solvers/search_algorithms/search_nodes.rs b/src/solvers/search_algorithms/search_nodes.rs index 2c52a1e..4f58583 100644 --- a/src/solvers/search_algorithms/search_nodes.rs +++ b/src/solvers/search_algorithms/search_nodes.rs @@ -19,6 +19,8 @@ pub trait SearchNode: Sized { type State; /// Type of the cost. type CostType; + /// Type of the transition label. + type Label; /// Returns the state of the node. fn get_state(&self, dp: &Self::DpData) -> &Self::State; @@ -39,10 +41,10 @@ pub trait SearchNode: Sized { fn close(&self); /// Returns the transitions to reach the node. - fn get_transitions(&self, dp: &Self::DpData) -> Vec; + fn get_transitions(&self, dp: &Self::DpData) -> Vec; /// Checks if the node is a solution and returns the cost and transitions if it is. - fn check_solution(&self, dp: &Self::DpData) -> Option<(Self::CostType, Vec)> { + fn check_solution(&self, dp: &Self::DpData) -> Option<(Self::CostType, Vec)> { let state = self.get_state(dp); let cost = self.get_cost(dp); @@ -73,6 +75,7 @@ mod tests { impl Dp for MockDp { type State = i32; type CostType = i32; + type Label = usize; fn get_target(&self) -> Self::State { 2 @@ -81,16 +84,12 @@ mod tests { fn get_successors( &self, _: &Self::State, - ) -> impl IntoIterator { + ) -> impl IntoIterator { vec![] } fn get_base_cost(&self, state: &Self::State) -> Option { - if *state == 0 { - Some(0) - } else { - None - } + if *state == 0 { Some(0) } else { None } } fn combine_cost_weights(&self, a: Self::CostType, b: Self::CostType) -> Self::CostType { @@ -104,6 +103,7 @@ mod tests { type DpData = MockDp; type State = i32; type CostType = i32; + type Label = usize; fn get_state(&self, _: &Self::DpData) -> &Self::State { &self.0 @@ -127,7 +127,7 @@ mod tests { fn close(&self) {} - fn get_transitions(&self, _: &Self::DpData) -> Vec { + fn get_transitions(&self, _: &Self::DpData) -> Vec { vec![0, 1] } } diff --git a/src/solvers/search_algorithms/search_nodes/cost_node.rs b/src/solvers/search_algorithms/search_nodes/cost_node.rs index a763358..b88fdf8 100644 --- a/src/solvers/search_algorithms/search_nodes/cost_node.rs +++ b/src/solvers/search_algorithms/search_nodes/cost_node.rs @@ -1,5 +1,5 @@ -use super::id_tree::IdTree; use super::SearchNode; +use super::id_tree::IdTree; use crate::dp::{Dp, OptimizationMode}; use std::cell::Cell; use std::cmp::Ordering; @@ -8,18 +8,19 @@ use std::ops::Neg; use std::rc::Rc; /// Node ordered by the cost. -pub struct CostNode { +pub struct CostNode { state: S, cost: C, closed: Cell, - transition_tree: Rc, + transition_tree: Rc>, _phantom: PhantomData, } -impl CostNode +impl CostNode where D: Dp, C: Neg, + L: Default + Copy, { /// Creates a new root node given the state and the cost. pub fn create_root(dp: &D, state: S, cost: C) -> Self { @@ -36,7 +37,7 @@ where } /// Creates a new child node given the state, the cost, and the transition. - pub fn create_child(&self, dp: &D, state: S, cost: C, transition: usize) -> Self { + pub fn create_child(&self, dp: &D, state: S, cost: C, transition: L) -> Self { Self { state, cost: match dp.get_optimization_mode() { @@ -53,10 +54,11 @@ where } } -impl Clone for CostNode +impl Clone for CostNode where S: Clone, C: Clone, + L: Clone, { fn clone(&self) -> Self { Self { @@ -69,14 +71,16 @@ where } } -impl SearchNode for CostNode +impl SearchNode for CostNode where - D: Dp, + D: Dp, C: Copy + Neg, + L: Copy, { type DpData = D; type State = S; type CostType = C; + type Label = L; fn get_state(&self, _: &Self::DpData) -> &Self::State { &self.state @@ -105,12 +109,12 @@ where self.closed.set(true); } - fn get_transitions(&self, _: &D) -> Vec { + fn get_transitions(&self, _: &D) -> Vec { self.transition_tree.get_path() } } -impl PartialEq for CostNode +impl PartialEq for CostNode where C: PartialEq, { @@ -119,9 +123,9 @@ where } } -impl Eq for CostNode where C: Eq {} +impl Eq for CostNode where C: Eq {} -impl Ord for CostNode +impl Ord for CostNode where C: Eq + Ord, { @@ -130,7 +134,7 @@ where } } -impl PartialOrd for CostNode +impl PartialOrd for CostNode where C: Eq + Ord, { @@ -149,13 +153,17 @@ mod tests { impl Dp for MockDp { type State = i32; type CostType = i32; + type Label = usize; fn get_target(&self) -> i32 { 0 } #[allow(refining_impl_trait_internal)] - fn get_successors(&self, _: &Self::State) -> Vec<(i32, i32, usize)> { + fn get_successors( + &self, + _: &Self::State, + ) -> Vec<(Self::State, Self::CostType, Self::Label)> { vec![] } @@ -171,7 +179,7 @@ mod tests { #[test] fn test_create_root_minimization() { let dp = MockDp(OptimizationMode::Minimization); - let node = CostNode::<_, _, i32>::create_root(&dp, 0, 1); + let node = CostNode::<_, _, i32, usize>::create_root(&dp, 0, 1); assert_eq!(node.get_state(&dp), &0); assert_eq!(node.get_cost(&dp), 1); @@ -183,7 +191,7 @@ mod tests { #[test] fn test_create_root_maximization() { let dp = MockDp(OptimizationMode::Maximization); - let node = CostNode::<_, _, i32>::create_root(&dp, 0, 1); + let node = CostNode::<_, _, i32, usize>::create_root(&dp, 0, 1); assert_eq!(node.get_state(&dp), &0); assert_eq!(node.get_cost(&dp), 1); @@ -195,7 +203,7 @@ mod tests { #[test] fn test_create_child_minimization() { let dp = MockDp(OptimizationMode::Minimization); - let parent = CostNode::<_, _, i32>::create_root(&dp, 0, 1); + let parent = CostNode::<_, _, i32, usize>::create_root(&dp, 0, 1); let child = parent.create_child(&dp, 1, 2, 0); assert_eq!(child.get_state(&dp), &1); @@ -208,7 +216,7 @@ mod tests { #[test] fn test_create_child_maximization() { let dp = MockDp(OptimizationMode::Minimization); - let parent = CostNode::<_, _, i32>::create_root(&dp, 0, 1); + let parent = CostNode::<_, _, i32, usize>::create_root(&dp, 0, 1); let child = parent.create_child(&dp, 1, 2, 0); assert_eq!(child.get_state(&dp), &1); @@ -221,7 +229,7 @@ mod tests { #[test] fn test_clone() { let dp = MockDp(OptimizationMode::Minimization); - let node = CostNode::<_, _, i32>::create_root(&dp, 0, 1); + let node = CostNode::<_, _, i32, usize>::create_root(&dp, 0, 1); let cloned = node.clone(); assert_eq!(node.get_state(&dp), cloned.get_state(&dp)); assert_eq!(node.get_cost(&dp), cloned.get_cost(&dp)); @@ -233,7 +241,7 @@ mod tests { #[test] fn test_state_mut() { let dp = MockDp(OptimizationMode::Minimization); - let mut node = CostNode::<_, _, i32>::create_root(&dp, 0, 1); + let mut node = CostNode::<_, _, i32, usize>::create_root(&dp, 0, 1); *node.get_state_mut(&dp) = 1; assert_eq!(node.get_state(&dp), &1); @@ -242,7 +250,7 @@ mod tests { #[test] fn test_close() { let dp = MockDp(OptimizationMode::Minimization); - let node = CostNode::<_, _, i32>::create_root(&dp, 0, 1); + let node = CostNode::<_, _, i32, usize>::create_root(&dp, 0, 1); assert!(!node.is_closed()); node.close(); @@ -252,9 +260,9 @@ mod tests { #[test] fn test_ord_minimization() { let dp = MockDp(OptimizationMode::Minimization); - let node1 = CostNode::<_, _, i32>::create_root(&dp, 0, 1); - let node2 = CostNode::<_, _, i32>::create_root(&dp, 1, 1); - let node3 = CostNode::<_, _, i32>::create_root(&dp, 0, 2); + let node1 = CostNode::<_, _, i32, usize>::create_root(&dp, 0, 1); + let node2 = CostNode::<_, _, i32, usize>::create_root(&dp, 1, 1); + let node3 = CostNode::<_, _, i32, usize>::create_root(&dp, 0, 2); assert!(node1 == node1); assert!(node1 == node2); @@ -265,9 +273,9 @@ mod tests { #[test] fn test_ord_maximization() { let dp = MockDp(OptimizationMode::Maximization); - let node1 = CostNode::<_, _, i32>::create_root(&dp, 0, 1); - let node2 = CostNode::<_, _, i32>::create_root(&dp, 1, 1); - let node3 = CostNode::<_, _, i32>::create_root(&dp, 0, 2); + let node1 = CostNode::<_, _, i32, usize>::create_root(&dp, 0, 1); + let node2 = CostNode::<_, _, i32, usize>::create_root(&dp, 1, 1); + let node3 = CostNode::<_, _, i32, usize>::create_root(&dp, 0, 2); assert!(node1 == node1); assert!(node1 == node2); diff --git a/src/solvers/search_algorithms/search_nodes/dual_bound_node.rs b/src/solvers/search_algorithms/search_nodes/dual_bound_node.rs index 53237b4..c7275c6 100644 --- a/src/solvers/search_algorithms/search_nodes/dual_bound_node.rs +++ b/src/solvers/search_algorithms/search_nodes/dual_bound_node.rs @@ -1,7 +1,7 @@ -use super::id_tree::IdTree; use super::SearchNode; -use crate::dp::{Dp, OptimizationMode}; +use super::id_tree::IdTree; use crate::Bound; +use crate::dp::{Dp, OptimizationMode}; use std::cell::Cell; use std::cmp::Ordering; use std::marker::PhantomData; @@ -12,20 +12,21 @@ use std::rc::Rc; /// /// Ties are broken by the h-value. #[derive(Debug)] -pub struct DualBoundNode { +pub struct DualBoundNode { state: S, g: C, h: C, f: C, closed: Cell, - transition_tree: Rc, + transition_tree: Rc>, _phantom: PhantomData, } -impl DualBoundNode +impl DualBoundNode where D: Dp + Bound, C: Copy + Neg, + L: Default + Copy, { fn compute_h_and_f(dp: &D, g: C, h: C, primal_bound: Option) -> Option<(C, C)> { let f = dp.combine_cost_weights(g, h); @@ -69,7 +70,7 @@ where dp: &D, state: S, cost: C, - transition: usize, + transition: L, primal_bound: Option, other: Option<&Self>, ) -> Option { @@ -95,10 +96,11 @@ where } } -impl Clone for DualBoundNode +impl Clone for DualBoundNode where S: Clone, C: Clone, + L: Clone, { fn clone(&self) -> Self { Self { @@ -113,7 +115,7 @@ where } } -impl PartialEq for DualBoundNode +impl PartialEq for DualBoundNode where C: PartialEq, { @@ -122,9 +124,9 @@ where } } -impl Eq for DualBoundNode where C: Eq {} +impl Eq for DualBoundNode where C: Eq {} -impl Ord for DualBoundNode +impl Ord for DualBoundNode where C: Eq + Ord, { @@ -136,7 +138,7 @@ where } } -impl PartialOrd for DualBoundNode +impl PartialOrd for DualBoundNode where C: Eq + Ord, { @@ -145,14 +147,16 @@ where } } -impl SearchNode for DualBoundNode +impl SearchNode for DualBoundNode where D: Dp, C: Copy + Neg, + L: Copy, { type DpData = D; type State = S; type CostType = C; + type Label = L; fn get_state(&self, _: &Self::DpData) -> &Self::State { &self.state @@ -181,7 +185,7 @@ where self.closed.set(true); } - fn get_transitions(&self, _: &D) -> Vec { + fn get_transitions(&self, _: &D) -> Vec { self.transition_tree.get_path() } @@ -200,12 +204,16 @@ mod tests { impl Dp for MockDp { type State = i32; type CostType = i32; + type Label = usize; fn get_target(&self) -> i32 { 0 } - fn get_successors(&self, _: &Self::State) -> impl IntoIterator { + fn get_successors( + &self, + _: &Self::State, + ) -> impl IntoIterator { vec![] } @@ -236,7 +244,7 @@ mod tests { #[test] fn test_create_root_minimization() { let dp = MockDp(OptimizationMode::Minimization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, None); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, None); assert!(node.is_some()); let node = node.unwrap(); @@ -250,7 +258,7 @@ mod tests { #[test] fn test_create_root_none_minimization() { let dp = MockDp(OptimizationMode::Minimization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 6, 1, None); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 6, 1, None); assert!(node.is_none()); } @@ -258,7 +266,7 @@ mod tests { #[test] fn test_create_root_with_primal_bound_minimization() { let dp = MockDp(OptimizationMode::Minimization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, Some(5)); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, Some(5)); assert!(node.is_some()); let node = node.unwrap(); @@ -272,7 +280,7 @@ mod tests { #[test] fn test_create_root_with_primal_bound_none_minimization() { let dp = MockDp(OptimizationMode::Minimization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, Some(4)); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, Some(4)); assert!(node.is_none()); } @@ -280,7 +288,7 @@ mod tests { #[test] fn test_create_root_maximization() { let dp = MockDp(OptimizationMode::Maximization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, None); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, None); assert!(node.is_some()); let node = node.unwrap(); @@ -294,7 +302,7 @@ mod tests { #[test] fn test_create_root_none_maximization() { let dp = MockDp(OptimizationMode::Maximization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 6, 1, None); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 6, 1, None); assert!(node.is_none()); } @@ -302,7 +310,7 @@ mod tests { #[test] fn test_create_root_with_primal_bound_maximization() { let dp = MockDp(OptimizationMode::Maximization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, Some(3)); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, Some(3)); assert!(node.is_some()); let node = node.unwrap(); @@ -316,7 +324,7 @@ mod tests { #[test] fn test_create_root_with_primal_bound_none_maximization() { let dp = MockDp(OptimizationMode::Maximization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, Some(4)); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, Some(4)); assert!(node.is_none()); } @@ -324,7 +332,7 @@ mod tests { #[test] fn test_create_child_minimization() { let dp = MockDp(OptimizationMode::Minimization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, None); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, None); assert!(node.is_some()); let node = node.unwrap(); let child = node.create_child(&dp, 1, 2, 0, None, None); @@ -341,7 +349,7 @@ mod tests { #[test] fn test_create_child_none_minimization() { let dp = MockDp(OptimizationMode::Minimization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, None); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, None); assert!(node.is_some()); let node = node.unwrap(); let child = node.create_child(&dp, 6, 2, 0, None, None); @@ -352,7 +360,7 @@ mod tests { #[test] fn test_create_child_with_primal_bound_minimization() { let dp = MockDp(OptimizationMode::Minimization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, None); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, None); assert!(node.is_some()); let node = node.unwrap(); let child = node.create_child(&dp, 1, 2, 0, Some(5), None); @@ -369,7 +377,7 @@ mod tests { #[test] fn test_create_child_with_primal_bound_none_minimization() { let dp = MockDp(OptimizationMode::Minimization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, None); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, None); assert!(node.is_some()); let node = node.unwrap(); let child = node.create_child(&dp, 1, 2, 0, Some(4), None); @@ -380,10 +388,10 @@ mod tests { #[test] fn test_create_child_with_other_minimization() { let dp = MockDp(OptimizationMode::Minimization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, None); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, None); assert!(node.is_some()); let node = node.unwrap(); - let other = DualBoundNode::<_, _, i32>::create_root(&dp, 1, 3, None); + let other = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 1, 3, None); assert!(other.is_some()); let other = other.unwrap(); let child = node.create_child(&dp, 1, 2, 0, None, Some(&other)); @@ -400,7 +408,7 @@ mod tests { #[test] fn test_create_child_maximization() { let dp = MockDp(OptimizationMode::Maximization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, None); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, None); assert!(node.is_some()); let node = node.unwrap(); let child = node.create_child(&dp, 1, 2, 0, None, None); @@ -417,7 +425,7 @@ mod tests { #[test] fn test_create_child_none_maximization() { let dp = MockDp(OptimizationMode::Maximization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, None); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, None); assert!(node.is_some()); let node = node.unwrap(); let child = node.create_child(&dp, 6, 2, 0, None, None); @@ -428,7 +436,7 @@ mod tests { #[test] fn test_create_child_with_primal_bound_maximization() { let dp = MockDp(OptimizationMode::Maximization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, None); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, None); assert!(node.is_some()); let node = node.unwrap(); let child = node.create_child(&dp, 1, 2, 0, Some(3), None); @@ -445,7 +453,7 @@ mod tests { #[test] fn test_create_child_with_primal_bound_none_maximization() { let dp = MockDp(OptimizationMode::Maximization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, None); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, None); assert!(node.is_some()); let node = node.unwrap(); let child = node.create_child(&dp, 1, 2, 0, Some(4), None); @@ -456,10 +464,10 @@ mod tests { #[test] fn test_create_child_with_other_maximization() { let dp = MockDp(OptimizationMode::Maximization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, None); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, None); assert!(node.is_some()); let node = node.unwrap(); - let other = DualBoundNode::<_, _, i32>::create_root(&dp, 1, 3, None); + let other = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 1, 3, None); assert!(other.is_some()); let other = other.unwrap(); let child = node.create_child(&dp, 1, 2, 0, None, Some(&other)); @@ -476,7 +484,7 @@ mod tests { #[test] fn test_clone() { let dp = MockDp(OptimizationMode::Minimization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, None); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, None); assert!(node.is_some()); let node = node.unwrap(); let cloned = node.clone(); @@ -490,7 +498,7 @@ mod tests { #[test] fn test_state_mut() { let dp = MockDp(OptimizationMode::Minimization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, None); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, None); assert!(node.is_some()); let mut node = node.unwrap(); @@ -501,7 +509,7 @@ mod tests { #[test] fn test_close() { let dp = MockDp(OptimizationMode::Minimization); - let node = DualBoundNode::<_, _, i32>::create_root(&dp, 0, 1, None); + let node = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 0, 1, None); assert!(node.is_some()); let node = node.unwrap(); @@ -513,16 +521,16 @@ mod tests { #[test] fn test_ord_minimization() { let dp = MockDp(OptimizationMode::Minimization); - let node1 = DualBoundNode::<_, _, i32>::create_root(&dp, 3, 2, None); + let node1 = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 3, 2, None); assert!(node1.is_some()); let node1 = node1.unwrap(); - let node2 = DualBoundNode::<_, _, i32>::create_root(&dp, 4, 2, None); + let node2 = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 4, 2, None); assert!(node2.is_some()); let node2 = node2.unwrap(); - let node3 = DualBoundNode::<_, _, i32>::create_root(&dp, 2, 1, None); + let node3 = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 2, 1, None); assert!(node3.is_some()); let node3 = node3.unwrap(); - let node4 = DualBoundNode::<_, _, i32>::create_root(&dp, 2, 0, None); + let node4 = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 2, 0, None); assert!(node4.is_some()); let node4 = node4.unwrap(); @@ -537,16 +545,16 @@ mod tests { #[test] fn test_ord_maximization() { let dp = MockDp(OptimizationMode::Maximization); - let node1 = DualBoundNode::<_, _, i32>::create_root(&dp, 3, 2, None); + let node1 = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 3, 2, None); assert!(node1.is_some()); let node1 = node1.unwrap(); - let node2 = DualBoundNode::<_, _, i32>::create_root(&dp, 4, 2, None); + let node2 = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 4, 2, None); assert!(node2.is_some()); let node2 = node2.unwrap(); - let node3 = DualBoundNode::<_, _, i32>::create_root(&dp, 2, 1, None); + let node3 = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 2, 1, None); assert!(node3.is_some()); let node3 = node3.unwrap(); - let node4 = DualBoundNode::<_, _, i32>::create_root(&dp, 2, 0, None); + let node4 = DualBoundNode::<_, _, i32, usize>::create_root(&dp, 2, 0, None); assert!(node4.is_some()); let node4 = node4.unwrap(); diff --git a/src/solvers/search_algorithms/search_nodes/id_tree.rs b/src/solvers/search_algorithms/search_nodes/id_tree.rs index 9c93cc8..e13b7b1 100644 --- a/src/solvers/search_algorithms/search_nodes/id_tree.rs +++ b/src/solvers/search_algorithms/search_nodes/id_tree.rs @@ -2,14 +2,17 @@ use std::rc::Rc; /// Tree data structure to store a sequence of ids. #[derive(Clone, Debug, Default)] -pub struct IdTree { - id: Option, +pub struct IdTree { + id: Option, parent: Option>, } -impl IdTree { +impl IdTree +where + T: Copy, +{ /// Creates a child node. - pub fn create_child(node: Rc, id: usize) -> Self { + pub fn create_child(node: Rc, id: T) -> Self { Self { id: Some(id), parent: Some(node.clone()), @@ -17,7 +20,7 @@ impl IdTree { } /// Returns the path from the root to the current node. - pub fn get_path(&self) -> Vec { + pub fn get_path(&self) -> Vec { let mut path = Vec::new(); let mut current = self; diff --git a/src/solvers/search_algorithms/search_nodes/state_registry.rs b/src/solvers/search_algorithms/search_nodes/state_registry.rs index 8a7ec02..625098f 100644 --- a/src/solvers/search_algorithms/search_nodes/state_registry.rs +++ b/src/solvers/search_algorithms/search_nodes/state_registry.rs @@ -212,6 +212,7 @@ mod tests { impl Dp for MockDp { type State = (i32, i32, i32); type CostType = i32; + type Label = usize; fn get_target(&self) -> Self::State { (0, 0, 0) @@ -220,7 +221,7 @@ mod tests { fn get_successors( &self, _: &Self::State, - ) -> impl IntoIterator { + ) -> impl IntoIterator { vec![] }