math_audio_optimisation/
parallel_eval.rs1use ndarray::{Array1, Array2};
2use rayon::prelude::*;
3use std::sync::Arc;
4
5#[derive(Debug, Clone)]
7pub struct ParallelConfig {
8 pub enabled: bool,
10 pub num_threads: Option<usize>,
12}
13
14impl Default for ParallelConfig {
15 fn default() -> Self {
16 Self {
17 enabled: true,
18 num_threads: None, }
20 }
21}
22
23pub fn evaluate_population_parallel<F>(
33 population: &Array2<f64>,
34 eval_fn: Arc<F>,
35 config: &ParallelConfig,
36) -> Array1<f64>
37where
38 F: Fn(&Array1<f64>) -> f64 + Send + Sync,
39{
40 let npop = population.nrows();
41
42 if !config.enabled || npop < 4 {
43 let mut energies = Array1::zeros(npop);
45 for i in 0..npop {
46 let individual = population.row(i).to_owned();
47 energies[i] = eval_fn(&individual);
48 }
49 return energies;
50 }
51
52 let results = (0..npop)
54 .into_par_iter()
55 .map(|i| {
56 let individual = population.row(i).to_owned();
57 eval_fn(&individual)
58 })
59 .collect::<Vec<f64>>();
60
61 Array1::from_vec(results)
62}
63
64pub fn evaluate_trials_parallel<F>(
77 trials: &[Array1<f64>],
78 eval_fn: Arc<F>,
79 config: &ParallelConfig,
80) -> Vec<f64>
81where
82 F: Fn(&Array1<f64>) -> f64 + Send + Sync,
83{
84 if !config.enabled || trials.len() < 4 {
85 return trials.iter().map(|trial| eval_fn(trial)).collect();
87 }
88
89 trials.par_iter().map(|trial| eval_fn(trial)).collect()
91}
92
93pub struct IndexedEvaluation {
95 pub index: usize,
97 pub individual: Array1<f64>,
99 pub fitness: f64,
101}
102
103pub fn evaluate_population_indexed<F>(
105 population: &Array2<f64>,
106 eval_fn: Arc<F>,
107 config: &ParallelConfig,
108) -> Vec<IndexedEvaluation>
109where
110 F: Fn(&Array1<f64>) -> f64 + Send + Sync,
111{
112 let npop = population.nrows();
113
114 if !config.enabled || npop < 4 {
115 let mut results = Vec::with_capacity(npop);
117 for i in 0..npop {
118 let individual = population.row(i).to_owned();
119 let fitness = eval_fn(&individual);
120 results.push(IndexedEvaluation {
121 index: i,
122 individual,
123 fitness,
124 });
125 }
126 return results;
127 }
128
129 (0..npop)
131 .into_par_iter()
132 .map(|i| {
133 let individual = population.row(i).to_owned();
134 let fitness = eval_fn(&individual);
135 IndexedEvaluation {
136 index: i,
137 individual,
138 fitness,
139 }
140 })
141 .collect()
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_parallel_evaluation() {
150 let eval_fn = Arc::new(|x: &Array1<f64>| -> f64 { x.iter().map(|&xi| xi * xi).sum() });
152
153 let mut population = Array2::zeros((10, 3));
155 for i in 0..10 {
156 for j in 0..3 {
157 population[[i, j]] = (i as f64) * 0.1 + (j as f64) * 0.01;
158 }
159 }
160
161 let config = ParallelConfig {
163 enabled: true,
164 num_threads: Some(2),
165 };
166 let energies = evaluate_population_parallel(&population, eval_fn.clone(), &config);
167
168 assert_eq!(energies.len(), 10);
170 for i in 0..10 {
171 let expected = population.row(i).iter().map(|&x| x * x).sum::<f64>();
172 assert!((energies[i] - expected).abs() < 1e-10);
173 }
174
175 let config_seq = ParallelConfig {
177 enabled: false,
178 num_threads: None,
179 };
180 let energies_seq = evaluate_population_parallel(&population, eval_fn, &config_seq);
181
182 for i in 0..10 {
184 assert_eq!(energies[i], energies_seq[i]);
185 }
186 }
187
188 #[test]
189 fn test_indexed_evaluation() {
190 let eval_fn = Arc::new(|x: &Array1<f64>| -> f64 { x.iter().sum() });
191
192 let mut population = Array2::zeros((5, 2));
193 for i in 0..5 {
194 population[[i, 0]] = i as f64;
195 population[[i, 1]] = (i * 2) as f64;
196 }
197
198 let config = ParallelConfig::default();
199 let results = evaluate_population_indexed(&population, eval_fn, &config);
200
201 assert_eq!(results.len(), 5);
202 for result in results {
203 let expected = population.row(result.index).sum();
204 assert_eq!(result.fitness, expected);
205 }
206 }
207}