use std::fmt::Debug;
use solverforge_core::domain::PlanningSolution;
pub trait SolutionPartitioner<S: PlanningSolution>: Send + Sync + Debug {
fn partition(&self, solution: &S) -> Vec<S>;
fn merge(&self, original: &S, partitions: Vec<S>) -> S;
fn recommended_partition_count(&self) -> Option<usize> {
None
}
}
pub struct FunctionalPartitioner<S, PF, MF>
where
S: PlanningSolution,
PF: Fn(&S) -> Vec<S> + Send + Sync,
MF: Fn(&S, Vec<S>) -> S + Send + Sync,
{
partition_fn: PF,
merge_fn: MF,
recommended_count: Option<usize>,
_phantom: std::marker::PhantomData<fn() -> S>,
}
impl<S, PF, MF> FunctionalPartitioner<S, PF, MF>
where
S: PlanningSolution,
PF: Fn(&S) -> Vec<S> + Send + Sync,
MF: Fn(&S, Vec<S>) -> S + Send + Sync,
{
pub fn new(partition_fn: PF, merge_fn: MF) -> Self {
Self {
partition_fn,
merge_fn,
recommended_count: None,
_phantom: std::marker::PhantomData,
}
}
pub fn with_recommended_count(mut self, count: usize) -> Self {
self.recommended_count = Some(count);
self
}
}
impl<S, PF, MF> Debug for FunctionalPartitioner<S, PF, MF>
where
S: PlanningSolution,
PF: Fn(&S) -> Vec<S> + Send + Sync,
MF: Fn(&S, Vec<S>) -> S + Send + Sync,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FunctionalPartitioner")
.field("recommended_count", &self.recommended_count)
.finish()
}
}
impl<S, PF, MF> SolutionPartitioner<S> for FunctionalPartitioner<S, PF, MF>
where
S: PlanningSolution,
PF: Fn(&S) -> Vec<S> + Send + Sync,
MF: Fn(&S, Vec<S>) -> S + Send + Sync,
{
fn partition(&self, solution: &S) -> Vec<S> {
(self.partition_fn)(solution)
}
fn merge(&self, original: &S, partitions: Vec<S>) -> S {
(self.merge_fn)(original, partitions)
}
fn recommended_partition_count(&self) -> Option<usize> {
self.recommended_count
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ThreadCount {
#[default]
Auto,
Unlimited,
Specific(usize),
}
impl ThreadCount {
pub fn resolve(&self, partition_count: usize) -> usize {
match self {
ThreadCount::Auto => {
let cpus = std::thread::available_parallelism()
.map(|p| p.get())
.unwrap_or(1);
std::cmp::min(cpus, partition_count)
}
ThreadCount::Unlimited => std::thread::available_parallelism()
.map(|p| p.get())
.unwrap_or(1),
ThreadCount::Specific(n) => std::cmp::min(*n, partition_count),
}
}
}
impl std::fmt::Display for ThreadCount {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ThreadCount::Auto => write!(f, "Auto"),
ThreadCount::Unlimited => write!(f, "Unlimited"),
ThreadCount::Specific(n) => write!(f, "{}", n),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use solverforge_core::score::SoftScore;
#[derive(Clone, Debug)]
struct TestSolution {
values: Vec<i32>,
score: Option<SoftScore>,
}
impl PlanningSolution for TestSolution {
type Score = SoftScore;
fn score(&self) -> Option<Self::Score> {
self.score
}
fn set_score(&mut self, score: Option<Self::Score>) {
self.score = score;
}
}
#[test]
fn test_thread_count_default() {
assert_eq!(ThreadCount::default(), ThreadCount::Auto);
}
#[test]
fn test_thread_count_display() {
assert_eq!(format!("{}", ThreadCount::Auto), "Auto");
assert_eq!(format!("{}", ThreadCount::Unlimited), "Unlimited");
assert_eq!(format!("{}", ThreadCount::Specific(4)), "4");
}
#[test]
fn test_thread_count_resolve_specific() {
assert_eq!(ThreadCount::Specific(4).resolve(10), 4);
assert_eq!(ThreadCount::Specific(10).resolve(4), 4); }
#[test]
fn test_thread_count_resolve_auto() {
let count = ThreadCount::Auto.resolve(100);
assert!(count > 0);
}
#[test]
fn test_functional_partitioner() {
let partitioner = FunctionalPartitioner::new(
|s: &TestSolution| {
let mid = s.values.len() / 2;
vec![
TestSolution {
values: s.values[..mid].to_vec(),
score: None,
},
TestSolution {
values: s.values[mid..].to_vec(),
score: None,
},
]
},
|_original, partitions| {
let mut values = Vec::new();
for p in partitions {
values.extend(p.values);
}
TestSolution {
values,
score: None,
}
},
);
let solution = TestSolution {
values: vec![1, 2, 3, 4],
score: None,
};
let partitions = partitioner.partition(&solution);
assert_eq!(partitions.len(), 2);
assert_eq!(partitions[0].values, vec![1, 2]);
assert_eq!(partitions[1].values, vec![3, 4]);
let merged = partitioner.merge(&solution, partitions);
assert_eq!(merged.values, vec![1, 2, 3, 4]);
}
#[test]
fn test_partitioner_debug() {
let partitioner = FunctionalPartitioner::new(
|_: &TestSolution| Vec::new(),
|original: &TestSolution, _| original.clone(),
)
.with_recommended_count(4);
let debug = format!("{:?}", partitioner);
assert!(debug.contains("FunctionalPartitioner"));
assert!(debug.contains("recommended_count"));
}
}