henad_core/explore/search/
random.rs1use crate::explore::design_rng::DesignRng;
4use crate::explore::search::evaluation_log::EvaluationLog;
5use crate::explore::search::genome::SearchSpace;
6use crate::explore::search::{
7 Candidate, CandidateOrigin, CandidateTracker, Evaluation, Objective, Proposal, RankingEntry, SearchReport, Searcher,
8};
9
10#[derive(Debug, Clone)]
12pub struct RandomSearch {
13 space: SearchSpace,
14 rng: DesignRng,
15 tracker: CandidateTracker,
16 log: EvaluationLog,
17}
18
19impl RandomSearch {
20 pub fn new(space: SearchSpace, objective: Objective, max_evaluations: u64, seed: u64) -> Self {
22 Self {
23 space,
24 rng: DesignRng::new(seed),
25 tracker: CandidateTracker::new(max_evaluations),
26 log: EvaluationLog::new(objective),
27 }
28 }
29}
30
31impl Searcher for RandomSearch {
32 fn ask(&mut self, max: usize) -> Vec<Candidate> {
33 let proposals = (0..self.tracker.capacity(max))
34 .map(|_| Proposal::first_evaluation(self.space.random_genome(&mut self.rng), CandidateOrigin::Random))
35 .collect();
36 self.tracker.issue(proposals)
37 }
38
39 fn tell(&mut self, evaluations: &[Evaluation]) {
40 for evaluation in evaluations {
41 if let Some((candidate, batch)) = self.tracker.settle(evaluation.candidate_id) {
42 self.log.record(&candidate, batch, evaluation);
43 }
44 }
45 }
46
47 fn is_done(&self) -> bool {
48 self.tracker.is_done()
49 }
50
51 fn report(&self) -> SearchReport {
52 SearchReport {
53 ranking: self.log.ranking(),
54 ..SearchReport::default()
55 }
56 }
57
58 fn best(&self) -> Option<RankingEntry> {
59 self.log.best()
60 }
61
62 fn ranking_entry(&self, candidate_id: u64) -> Option<RankingEntry> {
63 self.log.ranking_entry(candidate_id)
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::RandomSearch;
70 use crate::explore::search::tests::support::{drive, unit_space};
71 use crate::explore::search::{Aggregate, Candidate, Goal, Objective, Searcher as _};
72
73 fn search(seed: u64) -> RandomSearch {
74 let objective = Objective {
75 column: "Infected:max".to_owned(),
76 goal: Goal::Maximize,
77 aggregate: Aggregate::Mean,
78 };
79 RandomSearch::new(unit_space(3), objective, 50, seed)
80 }
81
82 #[test]
83 fn random_search_is_reproducible_from_its_seed() {
84 let sum = |candidate: &Candidate, _| vec![Some(candidate.genome.genes().iter().sum())];
85 let (mut first, mut again, mut other) = (search(7), search(7), search(8));
86 let first_asked = drive(&mut first, 6, 2, sum);
87 assert_eq!(first_asked.len(), 50);
88 assert_eq!(drive(&mut again, 6, 2, sum), first_asked);
89 assert_eq!(first.report(), again.report());
90 assert_ne!(drive(&mut other, 6, 2, sum), first_asked);
91
92 let report = first.report();
93 let best = report.best().expect("the search scored candidates");
94 let best_sum: f64 = first_asked[best.candidate_id as usize].genome.genes().iter().sum();
95 let top = first_asked
96 .iter()
97 .map(|candidate| candidate.genome.genes().iter().sum::<f64>())
98 .fold(f64::NEG_INFINITY, f64::max);
99 assert_eq!(best_sum, top, "the best candidate has the largest sum");
100 assert_eq!((best.replicate_count, best.evaluations), (2, 1));
101 }
102}