Skip to main content

expman_core/
models.rs

1//! Data models for expman-rs.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::path::PathBuf;
7
8/// Configuration for a single experiment run.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ExperimentConfig {
11    /// Name of the experiment (e.g. "resnet_cifar10")
12    pub name: String,
13    /// Name of this specific run (auto-generated if None)
14    pub run_name: String,
15    /// Root directory for all experiments
16    pub base_dir: PathBuf,
17    /// Flush metrics to disk every N rows (default: 50)
18    pub flush_interval_rows: usize,
19    /// Flush metrics to disk every N milliseconds (default: 500)
20    pub flush_interval_ms: u64,
21    /// Language used for the run (e.g. "rust", "python")
22    pub language: String,
23    /// Environment path or executable (e.g. python executable path)
24    pub env_path: Option<String>,
25}
26
27impl ExperimentConfig {
28    pub fn new(name: impl Into<String>, base_dir: impl Into<PathBuf>) -> Self {
29        let now = chrono::Local::now();
30        Self {
31            name: name.into(),
32            run_name: now.format("%Y%m%d_%H%M%S").to_string(),
33            base_dir: base_dir.into(),
34            flush_interval_rows: 50,
35            flush_interval_ms: 500,
36            language: "rust".to_string(),
37            env_path: None,
38        }
39    }
40
41    pub fn with_run_name(mut self, run_name: impl Into<String>) -> Self {
42        self.run_name = run_name.into();
43        self
44    }
45
46    pub fn run_dir(&self) -> PathBuf {
47        self.base_dir.join(&self.name).join(&self.run_name)
48    }
49
50    pub fn experiment_dir(&self) -> PathBuf {
51        self.base_dir.join(&self.name)
52    }
53}
54
55/// A single metric value — supports float, int, or string.
56#[derive(Debug, Clone, Serialize, Deserialize)]
57#[serde(untagged)]
58pub enum MetricValue {
59    Float(f64),
60    Int(i64),
61    Bool(bool),
62    Text(String),
63}
64
65impl From<f64> for MetricValue {
66    fn from(v: f64) -> Self {
67        MetricValue::Float(v)
68    }
69}
70impl From<f32> for MetricValue {
71    fn from(v: f32) -> Self {
72        MetricValue::Float(v as f64)
73    }
74}
75impl From<i64> for MetricValue {
76    fn from(v: i64) -> Self {
77        MetricValue::Int(v)
78    }
79}
80impl From<i32> for MetricValue {
81    fn from(v: i32) -> Self {
82        MetricValue::Int(v as i64)
83    }
84}
85impl From<usize> for MetricValue {
86    fn from(v: usize) -> Self {
87        MetricValue::Int(v as i64)
88    }
89}
90impl From<bool> for MetricValue {
91    fn from(v: bool) -> Self {
92        MetricValue::Bool(v)
93    }
94}
95impl From<String> for MetricValue {
96    fn from(v: String) -> Self {
97        MetricValue::Text(v)
98    }
99}
100impl From<&str> for MetricValue {
101    fn from(v: &str) -> Self {
102        MetricValue::Text(v.to_string())
103    }
104}
105
106/// A row of metrics logged at a specific step/time.
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct MetricRow {
109    pub step: Option<u64>,
110    pub timestamp: DateTime<Utc>,
111    pub values: HashMap<String, MetricValue>,
112}
113
114impl MetricRow {
115    pub fn new(values: HashMap<String, MetricValue>, step: Option<u64>) -> Self {
116        Self {
117            step,
118            timestamp: Utc::now(),
119            values,
120        }
121    }
122}
123
124/// Status of a run.
125#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
126#[serde(rename_all = "UPPERCASE")]
127pub enum RunStatus {
128    Running,
129    Finished,
130    Failed,
131    Crashed,
132}
133
134impl std::fmt::Display for RunStatus {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        match self {
137            RunStatus::Running => write!(f, "RUNNING"),
138            RunStatus::Finished => write!(f, "FINISHED"),
139            RunStatus::Failed => write!(f, "FAILED"),
140            RunStatus::Crashed => write!(f, "CRASHED"),
141        }
142    }
143}
144
145/// Metadata stored alongside a run.
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct RunMetadata {
148    pub name: String,
149    pub experiment: String,
150    pub status: RunStatus,
151    pub started_at: DateTime<Utc>,
152    pub finished_at: Option<DateTime<Utc>>,
153    pub duration_secs: Option<f64>,
154    pub description: Option<String>,
155    /// Latest scalar metrics (numeric only) from the last logged row.
156    #[serde(default)]
157    pub metrics: Option<HashMap<String, f64>>,
158    /// Language of the run
159    #[serde(default)]
160    pub language: Option<String>,
161    /// Environment path or executable used
162    #[serde(default)]
163    pub env_path: Option<String>,
164}
165
166impl Default for RunMetadata {
167    fn default() -> Self {
168        Self {
169            name: String::new(),
170            experiment: String::new(),
171            status: RunStatus::Crashed,
172            started_at: Utc::now(),
173            finished_at: None,
174            duration_secs: None,
175            description: None,
176            metrics: None,
177            language: None,
178            env_path: None,
179        }
180    }
181}
182
183/// Metadata stored for an experiment.
184#[derive(Debug, Clone, Serialize, Deserialize, Default)]
185pub struct ExperimentMetadata {
186    pub display_name: Option<String>,
187    pub description: Option<String>,
188    pub tags: Vec<String>,
189}