trueno_db/experiment/
store.rs1use std::collections::HashMap;
7
8use super::{ExperimentRecord, MetricRecord, RunRecord};
9
10#[derive(Debug, Default)]
22pub struct ExperimentStore {
23 experiments: HashMap<String, ExperimentRecord>,
24 runs: HashMap<String, RunRecord>,
25 metrics: Vec<MetricRecord>,
26}
27
28impl ExperimentStore {
29 #[must_use]
31 pub fn new() -> Self {
32 Self::default()
33 }
34
35 #[must_use]
37 pub fn is_empty(&self) -> bool {
38 self.experiments.is_empty() && self.runs.is_empty() && self.metrics.is_empty()
39 }
40
41 #[must_use]
43 pub fn experiment_count(&self) -> usize {
44 self.experiments.len()
45 }
46
47 #[must_use]
49 pub fn run_count(&self) -> usize {
50 self.runs.len()
51 }
52
53 #[must_use]
55 pub fn metric_count(&self) -> usize {
56 self.metrics.len()
57 }
58
59 pub fn add_experiment(&mut self, experiment: ExperimentRecord) {
61 self.experiments.insert(experiment.experiment_id().to_string(), experiment);
62 }
63
64 #[must_use]
66 pub fn get_experiment(&self, experiment_id: &str) -> Option<&ExperimentRecord> {
67 self.experiments.get(experiment_id)
68 }
69
70 pub fn add_run(&mut self, run: RunRecord) {
72 self.runs.insert(run.run_id().to_string(), run);
73 }
74
75 #[must_use]
77 pub fn get_run(&self, run_id: &str) -> Option<&RunRecord> {
78 self.runs.get(run_id)
79 }
80
81 #[must_use]
83 pub fn get_runs_for_experiment(&self, experiment_id: &str) -> Vec<&RunRecord> {
84 self.runs.values().filter(|run| run.experiment_id() == experiment_id).collect()
85 }
86
87 pub fn add_metric(&mut self, metric: MetricRecord) {
89 self.metrics.push(metric);
90 }
91
92 #[must_use]
124 pub fn get_metrics_for_run(&self, run_id: &str, key: &str) -> Vec<MetricRecord> {
125 let mut metrics: Vec<MetricRecord> = self
126 .metrics
127 .iter()
128 .filter(|m| m.run_id() == run_id && m.key() == key)
129 .cloned()
130 .collect();
131
132 metrics.sort_by_key(MetricRecord::step);
134
135 metrics
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_store_default() {
145 let store = ExperimentStore::new();
146 assert!(store.is_empty());
147 assert_eq!(store.experiment_count(), 0);
148 assert_eq!(store.run_count(), 0);
149 assert_eq!(store.metric_count(), 0);
150 }
151
152 #[test]
153 fn test_store_add_and_get() {
154 let mut store = ExperimentStore::new();
155
156 let experiment = ExperimentRecord::new("exp-1", "Test");
157 store.add_experiment(experiment);
158
159 let run = RunRecord::new("run-1", "exp-1");
160 store.add_run(run);
161
162 let metric = MetricRecord::new("run-1", "loss", 0, 0.5);
163 store.add_metric(metric);
164
165 assert!(!store.is_empty());
166 assert!(store.get_experiment("exp-1").is_some());
167 assert!(store.get_run("run-1").is_some());
168 }
169
170 #[test]
171 fn test_get_metrics_for_run_ordering() {
172 let mut store = ExperimentStore::new();
173
174 store.add_metric(MetricRecord::new("run-1", "loss", 2, 0.2));
176 store.add_metric(MetricRecord::new("run-1", "loss", 0, 0.0));
177 store.add_metric(MetricRecord::new("run-1", "loss", 1, 0.1));
178
179 let metrics = store.get_metrics_for_run("run-1", "loss");
180
181 assert_eq!(metrics.len(), 3);
182 assert_eq!(metrics[0].step(), 0);
183 assert_eq!(metrics[1].step(), 1);
184 assert_eq!(metrics[2].step(), 2);
185 }
186}