1use crate::{
2 Metric, MetricUpdate,
3 stats::{Tag, TagKind, defaults::try_add_tag_from_str, fmt},
4};
5use radiate_utils::intern;
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8use std::{
9 collections::HashMap,
10 fmt::{Debug, Display},
11};
12
13pub(super) const METRIC_SET: &str = "metric_set";
14
15#[derive(PartialEq)]
16pub struct MetricSetSummary {
17 pub metrics: usize,
18 pub updates: f32,
19}
20
21#[derive(Clone, Default, PartialEq)]
22pub struct MetricSet {
23 metrics: HashMap<&'static str, Metric>,
24 set_stats: Metric,
25}
26
27impl MetricSet {
28 pub fn new() -> Self {
29 MetricSet {
30 metrics: HashMap::new(),
31 set_stats: Metric::new(METRIC_SET),
32 }
33 }
34
35 #[inline(always)]
36 pub fn keys(&self) -> Vec<&'static str> {
37 self.metrics.keys().cloned().collect()
38 }
39
40 #[inline(always)]
41 pub fn flush_all_into(&mut self, target: &mut MetricSet) {
42 for (key, mut m) in self.metrics.drain() {
43 if let Some(target_metric) = target.metrics.get_mut(key) {
44 target_metric.update_from(m);
45 } else {
46 try_add_tag_from_str(&mut m);
47 target.metrics.insert(key, m);
48 }
49 }
50
51 target.set_stats.update_from(self.set_stats.clone());
52 self.clear();
53 }
54
55 #[inline(always)]
56 pub fn upsert<'a>(&mut self, metric: impl Into<MetricSetUpdate<'a>>) {
57 let update = metric.into();
58 match update {
59 MetricSetUpdate::Many(metrics) => {
60 for metric in metrics {
61 self.add_or_update_internal(metric);
62 }
63 }
64 MetricSetUpdate::Single(metric) => {
65 self.add_or_update_internal(metric);
66 }
67 MetricSetUpdate::NamedSingle(name, metric_update) => {
68 self.set_stats.apply_update(1);
69 if let Some(m) = self.metrics.get_mut(name) {
70 m.apply_update(metric_update);
71 return;
72 }
73
74 let new_name = radiate_utils::intern_name_as_snake_case(name);
75 if let Some(m) = self.metrics.get_mut(&new_name) {
76 m.apply_update(metric_update);
77 } else {
78 let mut metric = Metric::new(new_name);
79 try_add_tag_from_str(&mut metric);
80 metric.apply_update(metric_update);
81 self.add(metric);
82 }
83 }
84 }
85 }
86
87 pub fn iter_tagged<'a>(
88 &'a self,
89 tag: TagKind,
90 ) -> impl Iterator<Item = (&'static str, &'a Metric)> {
91 self.metrics.iter().filter_map(move |(k, m)| {
92 if m.tags().has(tag) {
93 Some((*k, m))
94 } else {
95 None
96 }
97 })
98 }
99
100 pub fn iter_stats(&self) -> impl Iterator<Item = &Metric> {
101 self.metrics.values().filter(|m| m.statistic().is_some())
102 }
103
104 pub fn iter_times(&self) -> impl Iterator<Item = &Metric> {
105 self.metrics
106 .values()
107 .filter(|m| m.time_statistic().is_some())
108 }
109
110 pub fn tags(&self) -> impl Iterator<Item = TagKind> {
111 self.metrics
112 .values()
113 .fold(Tag::empty(), |acc, m| acc.union(m.tags()))
114 .into_iter()
115 }
116
117 #[inline(always)]
118 pub fn iter(&self) -> impl Iterator<Item = (&'static str, &Metric)> {
119 self.metrics.iter().map(|(name, metric)| (*name, metric))
120 }
121
122 #[inline(always)]
123 pub fn add(&mut self, metric: Metric) {
124 self.metrics.insert(intern!(metric.name()), metric);
125 }
126
127 #[inline(always)]
128 pub fn get(&self, name: &str) -> Option<&Metric> {
129 self.metrics.get(name)
130 }
131
132 #[inline(always)]
133 pub fn get_from_string(&self, name: String) -> Option<&Metric> {
134 self.metrics.get(name.as_str())
135 }
136
137 #[inline(always)]
138 pub fn clear(&mut self) {
139 for (_, m) in self.metrics.iter_mut() {
140 m.clear_values();
141 }
142
143 self.set_stats.clear_values();
144 }
145
146 #[inline(always)]
147 pub fn contains_key(&self, name: &str) -> bool {
148 self.metrics.contains_key(intern!(name))
149 }
150
151 #[inline(always)]
152 pub fn len(&self) -> usize {
153 self.metrics.len()
154 }
155
156 #[inline(always)]
157 pub fn summary(&self) -> MetricSetSummary {
158 MetricSetSummary {
159 metrics: self.metrics.len(),
160 updates: self.set_stats.statistic().map(|s| s.sum()).unwrap_or(0.0),
161 }
162 }
163
164 pub fn dashboard(&self) -> String {
165 fmt::render_full(self).unwrap_or_default()
166 }
167
168 pub fn time(&self) -> Option<&Metric> {
170 self.get(super::metric_names::TIME)
171 }
172
173 pub fn score(&self) -> Option<&Metric> {
174 self.get(super::metric_names::SCORES)
175 }
176
177 pub fn improvements(&self) -> Option<&Metric> {
178 self.get(super::metric_names::BEST_SCORE_IMPROVEMENT)
179 }
180
181 pub fn age(&self) -> Option<&Metric> {
182 self.get(super::metric_names::AGE)
183 }
184
185 pub fn replace_age(&self) -> Option<&Metric> {
186 self.get(super::metric_names::REPLACE_AGE)
187 }
188
189 pub fn replace_invalid(&self) -> Option<&Metric> {
190 self.get(super::metric_names::REPLACE_INVALID)
191 }
192
193 pub fn genome_size(&self) -> Option<&Metric> {
194 self.get(super::metric_names::GENOME_SIZE)
195 }
196
197 pub fn front_size(&self) -> Option<&Metric> {
198 self.get(super::metric_names::FRONT_SIZE)
199 }
200
201 pub fn front_comparisons(&self) -> Option<&Metric> {
202 self.get(super::metric_names::FRONT_COMPARISONS)
203 }
204
205 pub fn front_removals(&self) -> Option<&Metric> {
206 self.get(super::metric_names::FRONT_REMOVALS)
207 }
208
209 pub fn front_additions(&self) -> Option<&Metric> {
210 self.get(super::metric_names::FRONT_ADDITIONS)
211 }
212
213 pub fn front_entropy(&self) -> Option<&Metric> {
214 self.get(super::metric_names::FRONT_ENTROPY)
215 }
216
217 pub fn unique_members(&self) -> Option<&Metric> {
218 self.get(super::metric_names::UNIQUE_MEMBERS)
219 }
220
221 pub fn unique_scores(&self) -> Option<&Metric> {
222 self.get(super::metric_names::UNIQUE_SCORES)
223 }
224
225 pub fn new_children(&self) -> Option<&Metric> {
226 self.get(super::metric_names::NEW_CHILDREN)
227 }
228
229 pub fn survivor_count(&self) -> Option<&Metric> {
230 self.get(super::metric_names::SURVIVOR_COUNT)
231 }
232
233 pub fn carryover_rate(&self) -> Option<&Metric> {
234 self.get(super::metric_names::CARRYOVER_RATE)
235 }
236
237 pub fn evaluation_count(&self) -> Option<&Metric> {
238 self.get(super::metric_names::EVALUATION_COUNT)
239 }
240
241 pub fn diversity_ratio(&self) -> Option<&Metric> {
242 self.get(super::metric_names::DIVERSITY_RATIO)
243 }
244
245 pub fn score_volatility(&self) -> Option<&Metric> {
246 self.get(super::metric_names::SCORE_VOLATILITY)
247 }
248
249 pub fn species_count(&self) -> Option<&Metric> {
250 self.get(super::metric_names::SPECIES_COUNT)
251 }
252
253 pub fn species_age_fail(&self) -> Option<&Metric> {
254 self.get(super::metric_names::SPECIES_AGE_FAIL)
255 }
256
257 pub fn species_distance_dist(&self) -> Option<&Metric> {
258 self.get(super::metric_names::SPECIES_DISTANCE_DIST)
259 }
260
261 pub fn species_created(&self) -> Option<&Metric> {
262 self.get(super::metric_names::SPECIES_CREATED)
263 }
264
265 pub fn species_died(&self) -> Option<&Metric> {
266 self.get(super::metric_names::SPECIES_DIED)
267 }
268
269 pub fn species_age(&self) -> Option<&Metric> {
270 self.get(super::metric_names::SPECIES_AGE)
271 }
272
273 pub fn species_size(&self) -> Option<&Metric> {
274 self.get(super::metric_names::SPECIES_SIZE)
275 }
276
277 pub fn species_evenness(&self) -> Option<&Metric> {
278 self.get(super::metric_names::SPECIES_EVENNESS)
279 }
280
281 pub fn largest_species_share(&self) -> Option<&Metric> {
282 self.get(super::metric_names::LARGEST_SPECIES_SHARE)
283 }
284
285 fn add_or_update_internal(&mut self, mut metric: Metric) {
286 self.set_stats.apply_update(1);
287 if let Some(existing) = self.metrics.get_mut(metric.name()) {
288 existing.update_from(metric);
289 } else {
290 try_add_tag_from_str(&mut metric);
291 self.metrics.insert(intern!(metric.name()), metric);
292 }
293 }
294}
295
296impl Display for MetricSet {
297 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298 let summary = self.summary();
299 let out = format!(
300 "[{} metrics, {:.0} updates]",
301 summary.metrics, summary.updates
302 );
303 write!(f, "{out}\n{}", fmt::render_full(self).unwrap_or_default())?;
304 Ok(())
305 }
306}
307
308impl Debug for MetricSet {
309 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
310 write!(f, "MetricSet {{\n")?;
311 write!(f, "{}\n", fmt::render_dashboard(&self).unwrap_or_default())?;
312 write!(f, "}}")
313 }
314}
315
316#[cfg(feature = "serde")]
317impl Serialize for MetricSet {
318 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
319 where
320 S: serde::Serializer,
321 {
322 let metrics = self
323 .metrics
324 .iter()
325 .map(|(_, metric)| metric.clone())
326 .collect::<Vec<Metric>>();
327 metrics.serialize(serializer)
328 }
329}
330
331#[cfg(feature = "serde")]
332impl<'de> Deserialize<'de> for MetricSet {
333 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
334 where
335 D: serde::Deserializer<'de>,
336 {
337 let metrics = Vec::<Metric>::deserialize(deserializer)?;
338
339 let mut metric_set = MetricSet::new();
340 for metric in metrics {
341 metric_set.add(metric);
342 }
343
344 Ok(metric_set)
345 }
346}
347
348pub enum MetricSetUpdate<'a> {
349 Many(Vec<Metric>),
350 Single(Metric),
351 NamedSingle(&'static str, MetricUpdate<'a>),
352}
353
354impl From<Vec<Metric>> for MetricSetUpdate<'_> {
355 fn from(metrics: Vec<Metric>) -> Self {
356 MetricSetUpdate::Many(metrics)
357 }
358}
359
360impl From<Metric> for MetricSetUpdate<'_> {
361 fn from(metric: Metric) -> Self {
362 MetricSetUpdate::Single(metric)
363 }
364}
365
366impl<'a, U> From<(&'static str, U)> for MetricSetUpdate<'a>
367where
368 U: Into<MetricUpdate<'a>>,
369{
370 fn from((name, update): (&'static str, U)) -> Self {
371 MetricSetUpdate::NamedSingle(name, update.into())
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378
379 const EPSILON: f32 = 1e-5;
380
381 fn approx_eq(a: f32, b: f32, eps: f32) -> bool {
382 (a - b).abs() <= eps
383 }
384
385 fn assert_stat_eq(m: &Metric, count: i32, mean: f32, var: f32, min: f32, max: f32) {
386 assert_eq!(m.count(), count);
387 assert!(approx_eq(m.value_mean().unwrap(), mean, EPSILON), "mean");
388 assert!(approx_eq(m.value_variance().unwrap(), var, EPSILON), "var");
389 assert!(approx_eq(m.value_min().unwrap(), min, EPSILON), "min");
390 assert!(approx_eq(m.value_max().unwrap(), max, EPSILON), "max");
391 }
392
393 fn stats_of(values: &[f32]) -> (i32, f32, f32, f32, f32) {
394 let n = values.len() as i32;
396 if n == 0 {
397 return (0, 0.0, f32::NAN, f32::INFINITY, f32::NEG_INFINITY);
398 }
399 let mean = values.iter().sum::<f32>() / values.len() as f32;
400
401 let mut m2 = 0.0_f32;
402 for &v in values {
403 let d = v - mean;
404 m2 += d * d;
405 }
406
407 let var = if n == 1 { 0.0 } else { m2 / (n as f32 - 1.0) };
408
409 let min = values.iter().cloned().fold(f32::INFINITY, f32::min);
410 let max = values.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
411
412 (n, mean, var, min, max)
413 }
414
415 #[test]
416 fn metric_set_flush_all_into_merges_metrics() {
417 let mut a = MetricSet::new();
418 let mut b = MetricSet::new();
419
420 a.upsert(("scores", &[1.0, 2.0, 3.0][..]));
421 b.upsert(("scores", &[10.0, 20.0][..]));
422
423 a.flush_all_into(&mut b);
425
426 let m = b.get("scores").unwrap();
427 let combined = [1.0, 2.0, 3.0, 10.0, 20.0];
428 let (n, mean, var, min, max) = stats_of(&combined);
429 assert_stat_eq(m, n, mean, var, min, max);
430 }
431}