Skip to main content

math_audio_optimisation/
external_archive.rs

1//! External Archive for L-SHADE
2//!
3//! Stores recently discarded good solutions to maintain population diversity.
4//! Used by SHADE, L-SHADE, and their variants.
5
6use ndarray::Array1;
7
8/// External archive storing discarded solutions
9#[derive(Debug, Clone)]
10pub struct ExternalArchive {
11    /// Stored solutions
12    solutions: Vec<Array1<f64>>,
13    /// Maximum archive size (typically 2.1 * initial_population or arc_rate * NP)
14    max_size: usize,
15}
16
17impl ExternalArchive {
18    /// Create a new empty archive
19    pub fn new(max_size: usize) -> Self {
20        Self {
21            solutions: Vec::with_capacity(max_size),
22            max_size,
23        }
24    }
25
26    /// Create archive with size proportional to population
27    pub fn with_population_size(np: usize, arc_rate: f64) -> Self {
28        let max_size = (arc_rate * np as f64).ceil() as usize;
29        Self::new(max_size.max(1))
30    }
31
32    /// Add a solution to the archive
33    /// If archive is full, removes a random solution
34    pub fn add(&mut self, solution: Array1<f64>) {
35        if self.solutions.len() < self.max_size {
36            self.solutions.push(solution);
37        } else if self.max_size > 0 {
38            use rand::Rng;
39            let mut rng = rand::rng();
40            let idx = rng.random_range(0..self.max_size);
41            self.solutions[idx] = solution;
42        }
43    }
44
45    /// Select a random solution from the archive
46    /// Returns None if archive is empty
47    pub fn random_select<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Option<&Array1<f64>> {
48        if self.solutions.is_empty() {
49            None
50        } else {
51            let idx = rng.random_range(0..self.solutions.len());
52            Some(&self.solutions[idx])
53        }
54    }
55
56    /// Get a random index for archive selection
57    /// Returns None if archive is empty, otherwise returns a valid index into the archive
58    pub fn random_index<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Option<usize> {
59        if self.solutions.is_empty() {
60            None
61        } else {
62            Some(rng.random_range(0..self.solutions.len()))
63        }
64    }
65
66    /// Get solution at index (used after random_index)
67    pub fn get(&self, idx: usize) -> Option<&Array1<f64>> {
68        self.solutions.get(idx)
69    }
70
71    /// Number of solutions in archive
72    pub fn len(&self) -> usize {
73        self.solutions.len()
74    }
75
76    /// Check if archive is empty
77    pub fn is_empty(&self) -> bool {
78        self.solutions.is_empty()
79    }
80
81    /// Clear the archive
82    pub fn clear(&mut self) {
83        self.solutions.clear();
84    }
85
86    /// Resize the archive (keeps existing solutions if new size is larger)
87    pub fn resize(&mut self, new_max_size: usize) {
88        self.max_size = new_max_size;
89        if self.solutions.len() > new_max_size {
90            self.solutions.truncate(new_max_size);
91        }
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use ndarray::array;
99
100    #[test]
101    fn test_archive_basic() {
102        let mut archive = ExternalArchive::new(5);
103        assert!(archive.is_empty());
104
105        archive.add(array![1.0, 2.0]);
106        archive.add(array![3.0, 4.0]);
107        assert_eq!(archive.len(), 2);
108    }
109
110    #[test]
111    fn test_archive_full() {
112        let mut archive = ExternalArchive::new(2);
113        archive.add(array![1.0]);
114        archive.add(array![2.0]);
115        archive.add(array![3.0]);
116        assert_eq!(archive.len(), 2);
117    }
118
119    #[test]
120    fn test_archive_select() {
121        let mut archive = ExternalArchive::new(5);
122        archive.add(array![1.0, 2.0]);
123        archive.add(array![3.0, 4.0]);
124
125        let mut rng = rand::rng();
126        let selected = archive.random_select(&mut rng);
127        assert!(selected.is_some());
128    }
129}