use ndarray::{Array1, Array2};
use rayon::prelude::*;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct ParallelConfig {
pub enabled: bool,
pub num_threads: Option<usize>,
}
impl Default for ParallelConfig {
fn default() -> Self {
Self {
enabled: true,
num_threads: None, }
}
}
pub fn evaluate_population_parallel<F>(
population: &Array2<f64>,
eval_fn: Arc<F>,
config: &ParallelConfig,
) -> Array1<f64>
where
F: Fn(&Array1<f64>) -> f64 + Send + Sync,
{
let npop = population.nrows();
if !config.enabled || npop < 4 {
let mut energies = Array1::zeros(npop);
for i in 0..npop {
let individual = population.row(i).to_owned();
energies[i] = eval_fn(&individual);
}
return energies;
}
let results = (0..npop)
.into_par_iter()
.map(|i| {
let individual = population.row(i).to_owned();
eval_fn(&individual)
})
.collect::<Vec<f64>>();
Array1::from_vec(results)
}
pub fn evaluate_trials_parallel<F>(
trials: Vec<Array1<f64>>,
eval_fn: Arc<F>,
config: &ParallelConfig,
) -> Vec<f64>
where
F: Fn(&Array1<f64>) -> f64 + Send + Sync,
{
if !config.enabled || trials.len() < 4 {
return trials.iter().map(|trial| eval_fn(trial)).collect();
}
trials.par_iter().map(|trial| eval_fn(trial)).collect()
}
pub struct IndexedEvaluation {
pub index: usize,
pub individual: Array1<f64>,
pub fitness: f64,
}
pub fn evaluate_population_indexed<F>(
population: &Array2<f64>,
eval_fn: Arc<F>,
config: &ParallelConfig,
) -> Vec<IndexedEvaluation>
where
F: Fn(&Array1<f64>) -> f64 + Send + Sync,
{
let npop = population.nrows();
if !config.enabled || npop < 4 {
let mut results = Vec::with_capacity(npop);
for i in 0..npop {
let individual = population.row(i).to_owned();
let fitness = eval_fn(&individual);
results.push(IndexedEvaluation {
index: i,
individual,
fitness,
});
}
return results;
}
(0..npop)
.into_par_iter()
.map(|i| {
let individual = population.row(i).to_owned();
let fitness = eval_fn(&individual);
IndexedEvaluation {
index: i,
individual,
fitness,
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parallel_evaluation() {
let eval_fn = Arc::new(|x: &Array1<f64>| -> f64 { x.iter().map(|&xi| xi * xi).sum() });
let mut population = Array2::zeros((10, 3));
for i in 0..10 {
for j in 0..3 {
population[[i, j]] = (i as f64) * 0.1 + (j as f64) * 0.01;
}
}
let config = ParallelConfig {
enabled: true,
num_threads: Some(2),
};
let energies = evaluate_population_parallel(&population, eval_fn.clone(), &config);
assert_eq!(energies.len(), 10);
for i in 0..10 {
let expected = population.row(i).iter().map(|&x| x * x).sum::<f64>();
assert!((energies[i] - expected).abs() < 1e-10);
}
let config_seq = ParallelConfig {
enabled: false,
num_threads: None,
};
let energies_seq = evaluate_population_parallel(&population, eval_fn, &config_seq);
for i in 0..10 {
assert_eq!(energies[i], energies_seq[i]);
}
}
#[test]
fn test_indexed_evaluation() {
let eval_fn = Arc::new(|x: &Array1<f64>| -> f64 { x.iter().sum() });
let mut population = Array2::zeros((5, 2));
for i in 0..5 {
population[[i, 0]] = i as f64;
population[[i, 1]] = (i * 2) as f64;
}
let config = ParallelConfig::default();
let results = evaluate_population_indexed(&population, eval_fn, &config);
assert_eq!(results.len(), 5);
for result in results {
let expected = population.row(result.index).sum();
assert_eq!(result.fitness, expected);
}
}
}