use std::marker::PhantomData;
use crate::{
caching::{CacheKey, CachedChallenge, ThreadLocalCachedChallenge},
evolution::{CacheType, Challenge},
phenotype::Phenotype,
};
pub trait CachingChallenge<P: Phenotype>: Challenge<P> + Sized + Clone {
fn with_global_cache(&self) -> CachedChallenge<P, Self>
where
P: CacheKey;
fn with_thread_local_cache(&self) -> ThreadLocalCachedChallenge<P, Self>
where
P: CacheKey;
fn with_cache(&self, cache_type: CacheType) -> Box<dyn Challenge<P>>
where
P: CacheKey + 'static,
Self: 'static,
{
match cache_type {
CacheType::Global => Box::new(self.with_global_cache()),
CacheType::ThreadLocal => Box::new(self.with_thread_local_cache()),
}
}
}
impl<P, C> CachingChallenge<P> for C
where
P: Phenotype,
C: Challenge<P> + Clone,
{
fn with_global_cache(&self) -> CachedChallenge<P, Self>
where
P: CacheKey,
{
CachedChallenge::new(self.clone())
}
fn with_thread_local_cache(&self) -> ThreadLocalCachedChallenge<P, Self>
where
P: CacheKey,
{
ThreadLocalCachedChallenge::new(self.clone())
}
}
#[derive(Debug, Clone)]
pub struct CachingChallengeSwitcher<P, C>
where
P: Phenotype,
C: Challenge<P>,
{
inner: C,
cache_type: Option<CacheType>,
_phantom: PhantomData<P>,
}
impl<P, C> CachingChallengeSwitcher<P, C>
where
P: Phenotype,
C: Challenge<P>,
{
pub fn new(challenge: C) -> Self {
Self {
inner: challenge,
cache_type: None,
_phantom: PhantomData,
}
}
pub fn with_cache(mut self, cache_type: CacheType) -> Self {
self.cache_type = Some(cache_type);
self
}
pub fn inner(&self) -> &C {
&self.inner
}
pub fn unwrap(self) -> C {
self.inner
}
}
impl<P, C> Challenge<P> for CachingChallengeSwitcher<P, C>
where
P: Phenotype,
C: Challenge<P>,
{
fn score(&self, phenotype: &P) -> f64 {
self.inner.score(phenotype)
}
}
#[cfg(test)]
mod tests {
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use super::*;
use crate::rng::RandomNumberGenerator;
#[derive(Clone, Debug)]
struct TestPhenotype {
value: i32,
}
impl Phenotype for TestPhenotype {
fn crossover(&mut self, other: &Self) {
self.value = (self.value + other.value) / 2;
}
fn mutate(&mut self, _rng: &mut RandomNumberGenerator) {
self.value += 1;
}
}
impl CacheKey for TestPhenotype {
type Key = i32;
fn cache_key(&self) -> Self::Key {
self.value
}
}
#[derive(Clone)]
struct TestChallenge {
evaluations: Arc<AtomicUsize>,
}
impl TestChallenge {
fn new() -> Self {
Self {
evaluations: Arc::new(AtomicUsize::new(0)),
}
}
fn get_evaluations(&self) -> usize {
self.evaluations.load(Ordering::SeqCst)
}
}
impl Challenge<TestPhenotype> for TestChallenge {
fn score(&self, phenotype: &TestPhenotype) -> f64 {
self.evaluations.fetch_add(1, Ordering::SeqCst);
-(phenotype.value as f64)
}
}
#[test]
fn test_with_global_cache() {
let challenge = TestChallenge::new();
let cached_challenge = challenge.with_global_cache();
let phenotype1 = TestPhenotype { value: 5 };
let phenotype2 = TestPhenotype { value: 10 };
let score1 = cached_challenge.score(&phenotype1);
assert_eq!(score1, -5.0);
assert_eq!(challenge.get_evaluations(), 1);
let score1_again = cached_challenge.score(&phenotype1);
assert_eq!(score1_again, -5.0);
assert_eq!(challenge.get_evaluations(), 1);
let score2 = cached_challenge.score(&phenotype2);
assert_eq!(score2, -10.0);
assert_eq!(challenge.get_evaluations(), 2);
}
#[test]
fn test_with_thread_local_cache() {
let challenge = TestChallenge::new();
let cached_challenge = challenge.with_thread_local_cache();
let phenotype1 = TestPhenotype { value: 5 };
let phenotype2 = TestPhenotype { value: 10 };
let score1 = cached_challenge.score(&phenotype1);
assert_eq!(score1, -5.0);
assert_eq!(challenge.get_evaluations(), 1);
let score1_again = cached_challenge.score(&phenotype1);
assert_eq!(score1_again, -5.0);
assert_eq!(challenge.get_evaluations(), 1);
let score2 = cached_challenge.score(&phenotype2);
assert_eq!(score2, -10.0);
assert_eq!(challenge.get_evaluations(), 2);
}
#[test]
fn test_with_cache() {
let challenge = TestChallenge::new();
let cached_challenge = challenge.clone().with_cache(CacheType::Global);
let phenotype = TestPhenotype { value: 5 };
let score1 = cached_challenge.score(&phenotype);
assert_eq!(score1, -5.0);
let score2 = cached_challenge.score(&phenotype);
assert_eq!(score2, -5.0);
let cached_challenge = challenge.clone().with_cache(CacheType::ThreadLocal);
let score1 = cached_challenge.score(&phenotype);
assert_eq!(score1, -5.0);
let score2 = cached_challenge.score(&phenotype);
assert_eq!(score2, -5.0);
}
#[test]
fn test_caching_challenge_switcher() {
let challenge = TestChallenge::new();
let switcher = CachingChallengeSwitcher::new(challenge.clone());
let phenotype = TestPhenotype { value: 5 };
let score1 = switcher.score(&phenotype);
assert_eq!(score1, -5.0);
assert_eq!(challenge.get_evaluations(), 1);
let score2 = switcher.score(&phenotype);
assert_eq!(score2, -5.0);
assert_eq!(challenge.get_evaluations(), 2);
let challenge = TestChallenge::new();
let switcher =
CachingChallengeSwitcher::new(challenge.clone()).with_cache(CacheType::Global);
let score1 = switcher.score(&phenotype);
assert_eq!(score1, -5.0);
assert_eq!(challenge.get_evaluations(), 1);
let score2 = switcher.score(&phenotype);
assert_eq!(score2, -5.0);
assert_eq!(challenge.get_evaluations(), 2); }
}