1use serde::{Deserialize, Serialize};
7use std::fmt;
8use std::hash::{Hash, Hasher};
9
10use super::uncertainty::FitnessEstimate;
11use crate::genome::traits::EvolutionaryGenome;
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
19pub struct CandidateId(pub usize);
20
21impl fmt::Display for CandidateId {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 write!(f, "Candidate({})", self.0)
24 }
25}
26
27impl From<usize> for CandidateId {
28 fn from(id: usize) -> Self {
29 Self(id)
30 }
31}
32
33impl From<CandidateId> for usize {
34 fn from(id: CandidateId) -> Self {
35 id.0
36 }
37}
38
39#[derive(Clone, Debug, Serialize, Deserialize)]
44#[serde(bound = "G: Serialize + for<'a> Deserialize<'a>")]
45pub struct Candidate<G>
46where
47 G: EvolutionaryGenome,
48{
49 pub id: CandidateId,
51 pub genome: G,
53 pub fitness_estimate: Option<f64>,
55 #[serde(default)]
57 pub fitness_with_uncertainty: Option<FitnessEstimate>,
58 pub birth_generation: usize,
60 pub evaluation_count: usize,
62}
63
64impl<G> Candidate<G>
65where
66 G: EvolutionaryGenome,
67{
68 pub fn new(id: CandidateId, genome: G) -> Self {
70 Self {
71 id,
72 genome,
73 fitness_estimate: None,
74 fitness_with_uncertainty: None,
75 birth_generation: 0,
76 evaluation_count: 0,
77 }
78 }
79
80 pub fn with_generation(id: CandidateId, genome: G, generation: usize) -> Self {
82 Self {
83 id,
84 genome,
85 fitness_estimate: None,
86 fitness_with_uncertainty: None,
87 birth_generation: generation,
88 evaluation_count: 0,
89 }
90 }
91
92 pub fn set_fitness(&mut self, fitness: f64) {
94 self.fitness_estimate = Some(fitness);
95 }
96
97 pub fn set_fitness_with_uncertainty(&mut self, estimate: FitnessEstimate) {
99 self.fitness_estimate = Some(estimate.mean);
100 self.fitness_with_uncertainty = Some(estimate);
101 }
102
103 pub fn fitness(&self) -> Option<f64> {
105 self.fitness_estimate
106 }
107
108 pub fn fitness_uncertainty(&self) -> Option<&FitnessEstimate> {
110 self.fitness_with_uncertainty.as_ref()
111 }
112
113 pub fn fitness_variance(&self) -> Option<f64> {
115 self.fitness_with_uncertainty.as_ref().map(|e| e.variance)
116 }
117
118 pub fn is_evaluated(&self) -> bool {
120 self.evaluation_count > 0
121 }
122
123 pub fn record_evaluation(&mut self) {
125 self.evaluation_count += 1;
126 }
127
128 pub fn age(&self, current_generation: usize) -> usize {
130 current_generation.saturating_sub(self.birth_generation)
131 }
132
133 pub fn is_uncertain(&self, min_observations: usize) -> bool {
137 self.fitness_with_uncertainty
138 .as_ref()
139 .map(|e| e.is_uncertain(min_observations))
140 .unwrap_or(true) }
142}
143
144impl<G> PartialEq for Candidate<G>
145where
146 G: EvolutionaryGenome,
147{
148 fn eq(&self, other: &Self) -> bool {
149 self.id == other.id
150 }
151}
152
153impl<G> Eq for Candidate<G> where G: EvolutionaryGenome {}
154
155impl<G> Hash for Candidate<G>
156where
157 G: EvolutionaryGenome,
158{
159 fn hash<H: Hasher>(&self, state: &mut H) {
160 self.id.hash(state);
161 }
162}
163
164#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
168pub struct RatingScale {
169 pub min: f64,
171 pub max: f64,
173 pub allow_ties: bool,
175 pub step: Option<f64>,
177}
178
179impl RatingScale {
180 pub fn new(min: f64, max: f64) -> Self {
182 Self {
183 min,
184 max,
185 allow_ties: true,
186 step: None,
187 }
188 }
189
190 pub fn one_to_ten() -> Self {
192 Self {
193 min: 1.0,
194 max: 10.0,
195 allow_ties: true,
196 step: Some(1.0),
197 }
198 }
199
200 pub fn one_to_five() -> Self {
202 Self {
203 min: 1.0,
204 max: 5.0,
205 allow_ties: true,
206 step: Some(1.0),
207 }
208 }
209
210 pub fn binary() -> Self {
212 Self {
213 min: 0.0,
214 max: 1.0,
215 allow_ties: false,
216 step: Some(1.0),
217 }
218 }
219
220 pub fn with_ties(mut self, allow: bool) -> Self {
222 self.allow_ties = allow;
223 self
224 }
225
226 pub fn with_step(mut self, step: f64) -> Self {
228 self.step = Some(step);
229 self
230 }
231
232 pub fn validate(&self, rating: f64) -> bool {
234 if rating < self.min || rating > self.max {
235 return false;
236 }
237 if let Some(step) = self.step {
238 let steps_from_min = (rating - self.min) / step;
240 (steps_from_min - steps_from_min.round()).abs() < 1e-9
241 } else {
242 true
243 }
244 }
245
246 pub fn clamp(&self, rating: f64) -> f64 {
248 rating.clamp(self.min, self.max)
249 }
250
251 pub fn normalize(&self, rating: f64) -> f64 {
253 (rating - self.min) / (self.max - self.min)
254 }
255
256 pub fn denormalize(&self, normalized: f64) -> f64 {
258 normalized * (self.max - self.min) + self.min
259 }
260}
261
262impl Default for RatingScale {
263 fn default() -> Self {
264 Self::one_to_ten()
265 }
266}
267
268#[derive(Clone, Debug, Serialize, Deserialize)]
273#[serde(bound = "G: Serialize + for<'a> Deserialize<'a>")]
274pub enum EvaluationRequest<G>
275where
276 G: EvolutionaryGenome,
277{
278 RateCandidates {
283 candidates: Vec<Candidate<G>>,
285 scale: RatingScale,
287 },
288
289 PairwiseComparison {
294 candidate_a: Candidate<G>,
296 candidate_b: Candidate<G>,
298 allow_tie: bool,
300 },
301
302 BatchSelection {
307 candidates: Vec<Candidate<G>>,
309 select_count: usize,
311 min_select: usize,
313 },
314}
315
316impl<G> EvaluationRequest<G>
317where
318 G: EvolutionaryGenome,
319{
320 pub fn rate(candidates: Vec<Candidate<G>>) -> Self {
322 Self::RateCandidates {
323 candidates,
324 scale: RatingScale::default(),
325 }
326 }
327
328 pub fn rate_with_scale(candidates: Vec<Candidate<G>>, scale: RatingScale) -> Self {
330 Self::RateCandidates { candidates, scale }
331 }
332
333 pub fn compare(a: Candidate<G>, b: Candidate<G>) -> Self {
335 Self::PairwiseComparison {
336 candidate_a: a,
337 candidate_b: b,
338 allow_tie: true,
339 }
340 }
341
342 pub fn select_from_batch(candidates: Vec<Candidate<G>>, select_count: usize) -> Self {
344 Self::BatchSelection {
345 candidates,
346 select_count,
347 min_select: 1,
348 }
349 }
350
351 pub fn candidate_count(&self) -> usize {
353 match self {
354 Self::RateCandidates { candidates, .. } => candidates.len(),
355 Self::PairwiseComparison { .. } => 2,
356 Self::BatchSelection { candidates, .. } => candidates.len(),
357 }
358 }
359
360 pub fn candidate_ids(&self) -> Vec<CandidateId> {
362 match self {
363 Self::RateCandidates { candidates, .. } => candidates.iter().map(|c| c.id).collect(),
364 Self::PairwiseComparison {
365 candidate_a,
366 candidate_b,
367 ..
368 } => vec![candidate_a.id, candidate_b.id],
369 Self::BatchSelection { candidates, .. } => candidates.iter().map(|c| c.id).collect(),
370 }
371 }
372}
373
374#[derive(Clone, Debug, Serialize, Deserialize)]
378pub enum EvaluationResponse {
379 Ratings(Vec<(CandidateId, f64)>),
383
384 PairwiseWinner(Option<CandidateId>),
388
389 BatchSelected(Vec<CandidateId>),
393
394 Skip,
398}
399
400impl EvaluationResponse {
401 pub fn ratings(ratings: Vec<(CandidateId, f64)>) -> Self {
403 Self::Ratings(ratings)
404 }
405
406 pub fn winner(id: CandidateId) -> Self {
408 Self::PairwiseWinner(Some(id))
409 }
410
411 pub fn tie() -> Self {
413 Self::PairwiseWinner(None)
414 }
415
416 pub fn selected(ids: Vec<CandidateId>) -> Self {
418 Self::BatchSelected(ids)
419 }
420
421 pub fn skip() -> Self {
423 Self::Skip
424 }
425
426 pub fn is_skip(&self) -> bool {
428 matches!(self, Self::Skip)
429 }
430
431 pub fn mentioned_ids(&self) -> Vec<CandidateId> {
433 match self {
434 Self::Ratings(ratings) => ratings.iter().map(|(id, _)| *id).collect(),
435 Self::PairwiseWinner(Some(id)) => vec![*id],
436 Self::PairwiseWinner(None) => vec![],
437 Self::BatchSelected(ids) => ids.clone(),
438 Self::Skip => vec![],
439 }
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446 use crate::genome::real_vector::RealVector;
447
448 #[test]
449 fn test_candidate_id() {
450 let id1 = CandidateId(0);
451 let id2 = CandidateId(1);
452 let id3 = CandidateId(0);
453
454 assert_eq!(id1, id3);
455 assert_ne!(id1, id2);
456 assert_eq!(format!("{}", id1), "Candidate(0)");
457 }
458
459 #[test]
460 fn test_candidate_creation() {
461 let genome = RealVector::new(vec![1.0, 2.0, 3.0]);
462 let candidate: Candidate<RealVector> = Candidate::new(CandidateId(0), genome);
463
464 assert_eq!(candidate.id, CandidateId(0));
465 assert!(candidate.fitness_estimate.is_none());
466 assert!(!candidate.is_evaluated());
467 assert_eq!(candidate.evaluation_count, 0);
468 }
469
470 #[test]
471 fn test_candidate_evaluation() {
472 let genome = RealVector::new(vec![1.0, 2.0, 3.0]);
473 let mut candidate: Candidate<RealVector> = Candidate::new(CandidateId(0), genome);
474
475 candidate.set_fitness(7.5);
476 candidate.record_evaluation();
477
478 assert_eq!(candidate.fitness(), Some(7.5));
479 assert!(candidate.is_evaluated());
480 assert_eq!(candidate.evaluation_count, 1);
481 }
482
483 #[test]
484 fn test_rating_scale_validation() {
485 let scale = RatingScale::one_to_ten();
486
487 assert!(scale.validate(1.0));
488 assert!(scale.validate(5.0));
489 assert!(scale.validate(10.0));
490 assert!(!scale.validate(0.0));
491 assert!(!scale.validate(11.0));
492 assert!(!scale.validate(5.5)); }
494
495 #[test]
496 fn test_rating_scale_normalization() {
497 let scale = RatingScale::one_to_ten();
498
499 assert!((scale.normalize(1.0) - 0.0).abs() < 1e-9);
500 assert!((scale.normalize(5.5) - 0.5).abs() < 1e-9);
501 assert!((scale.normalize(10.0) - 1.0).abs() < 1e-9);
502
503 assert!((scale.denormalize(0.0) - 1.0).abs() < 1e-9);
504 assert!((scale.denormalize(0.5) - 5.5).abs() < 1e-9);
505 assert!((scale.denormalize(1.0) - 10.0).abs() < 1e-9);
506 }
507
508 #[test]
509 fn test_evaluation_request_rate() {
510 let c1: Candidate<RealVector> = Candidate::new(CandidateId(0), RealVector::new(vec![1.0]));
511 let c2: Candidate<RealVector> = Candidate::new(CandidateId(1), RealVector::new(vec![2.0]));
512
513 let request = EvaluationRequest::rate(vec![c1, c2]);
514 assert_eq!(request.candidate_count(), 2);
515 assert_eq!(
516 request.candidate_ids(),
517 vec![CandidateId(0), CandidateId(1)]
518 );
519 }
520
521 #[test]
522 fn test_evaluation_request_compare() {
523 let c1: Candidate<RealVector> = Candidate::new(CandidateId(0), RealVector::new(vec![1.0]));
524 let c2: Candidate<RealVector> = Candidate::new(CandidateId(1), RealVector::new(vec![2.0]));
525
526 let request = EvaluationRequest::compare(c1, c2);
527 assert_eq!(request.candidate_count(), 2);
528 }
529
530 #[test]
531 fn test_evaluation_response() {
532 let response = EvaluationResponse::winner(CandidateId(0));
533 assert_eq!(response.mentioned_ids(), vec![CandidateId(0)]);
534
535 let response = EvaluationResponse::tie();
536 assert!(response.mentioned_ids().is_empty());
537
538 let response = EvaluationResponse::skip();
539 assert!(response.is_skip());
540 }
541}