border_core/record/
base.rs

1//! Base implementation of records for logging.
2//!
3//! This module provides a flexible record system for storing and retrieving
4//! various types of data, including scalars, arrays, and strings. It is designed
5//! to be used for logging training metrics, storing experiment results, and
6//! managing configuration data.
7
8use crate::error::LrrError;
9use chrono::prelude::{DateTime, Local};
10use std::{
11    collections::{
12        hash_map::{IntoIter, Iter, Keys},
13        HashMap,
14    },
15    convert::Into,
16    iter::IntoIterator,
17};
18
19/// Represents possible types of values that can be stored in a [`Record`].
20///
21/// This enum provides a type-safe way to store different kinds of data in a record,
22/// including numerical values, arrays, strings, and timestamps.
23///
24/// # Variants
25///
26/// * `Scalar(f32)` - A single floating-point value, typically used for metrics
27/// * `DateTime(DateTime<Local>)` - A timestamp with local timezone
28/// * `Array1(Vec<f32>)` - A 1-dimensional array of floating-point values
29/// * `Array2(Vec<f32>, [usize; 2])` - A 2-dimensional array with shape information
30/// * `Array3(Vec<f32>, [usize; 3])` - A 3-dimensional array with shape information
31/// * `String(String)` - A text value
32#[derive(Debug, Clone)]
33pub enum RecordValue {
34    /// A single floating-point value, typically used for metrics like loss or accuracy.
35    Scalar(f32),
36
37    /// A timestamp with local timezone, useful for logging events.
38    DateTime(DateTime<Local>),
39
40    /// A 1-dimensional array of floating-point values.
41    Array1(Vec<f32>),
42
43    /// A 2-dimensional array with shape information.
44    Array2(Vec<f32>, [usize; 2]),
45
46    /// A 3-dimensional array with shape information.
47    Array3(Vec<f32>, [usize; 3]),
48
49    /// A text value, useful for storing labels or descriptions.
50    String(String),
51}
52
53/// A container for storing key-value pairs of various data types.
54///
55/// This structure provides a flexible way to store and retrieve different types
56/// of data using string keys. It supports merging records and provides type-safe
57/// access to stored values.
58#[derive(Debug)]
59pub struct Record(HashMap<String, RecordValue>);
60
61impl Record {
62    /// Creates an empty record.
63    ///
64    /// # Returns
65    ///
66    /// A new empty record
67    pub fn empty() -> Self {
68        Self { 0: HashMap::new() }
69    }
70
71    /// Creates a record containing a single scalar value.
72    ///
73    /// # Arguments
74    ///
75    /// * `name` - The key for the scalar value
76    /// * `value` - The scalar value to store
77    ///
78    /// # Returns
79    ///
80    /// A new record containing the scalar value
81    pub fn from_scalar(name: impl Into<String>, value: f32) -> Self {
82        Self {
83            0: HashMap::from([(name.into(), RecordValue::Scalar(value))]),
84        }
85    }
86
87    /// Creates a record from a slice of key-value pairs.
88    ///
89    /// # Arguments
90    ///
91    /// * `s` - A slice of tuples containing keys and values
92    ///
93    /// # Returns
94    ///
95    /// A new record containing all the key-value pairs
96    pub fn from_slice<K: Into<String> + Clone>(s: &[(K, RecordValue)]) -> Self {
97        Self(
98            s.iter()
99                .map(|(k, v)| (k.clone().into(), v.clone()))
100                .collect(),
101        )
102    }
103
104    /// Returns an iterator over the keys in the record.
105    ///
106    /// # Returns
107    ///
108    /// An iterator over the record's keys
109    pub fn keys(&self) -> Keys<String, RecordValue> {
110        self.0.keys()
111    }
112
113    /// Inserts a key-value pair into the record.
114    ///
115    /// # Arguments
116    ///
117    /// * `k` - The key to insert
118    /// * `v` - The value to insert
119    pub fn insert(&mut self, k: impl Into<String>, v: RecordValue) {
120        self.0.insert(k.into(), v);
121    }
122
123    /// Returns an iterator over the key-value pairs in the record.
124    ///
125    /// # Returns
126    ///
127    /// An iterator over the record's key-value pairs
128    pub fn iter(&self) -> Iter<'_, String, RecordValue> {
129        self.0.iter()
130    }
131
132    /// Returns an iterator that consumes the record.
133    ///
134    /// # Returns
135    ///
136    /// An iterator that takes ownership of the record
137    pub fn into_iter_in_record(self) -> IntoIter<String, RecordValue> {
138        self.0.into_iter()
139    }
140
141    /// Gets a reference to the value associated with the given key.
142    ///
143    /// # Arguments
144    ///
145    /// * `k` - The key to look up
146    ///
147    /// # Returns
148    ///
149    /// A reference to the value if the key exists, `None` otherwise
150    pub fn get(&self, k: &str) -> Option<&RecordValue> {
151        self.0.get(k)
152    }
153
154    /// Merges two records, consuming both.
155    ///
156    /// If both records contain the same key, the value from the second record
157    /// will overwrite the value from the first record.
158    ///
159    /// # Arguments
160    ///
161    /// * `record` - The record to merge with
162    ///
163    /// # Returns
164    ///
165    /// A new record containing all key-value pairs from both records
166    pub fn merge(self, record: Record) -> Self {
167        Record(self.0.into_iter().chain(record.0).collect())
168    }
169
170    /// Merges another record into this one in place.
171    ///
172    /// If both records contain the same key, the value from the second record
173    /// will overwrite the value from this record.
174    ///
175    /// # Arguments
176    ///
177    /// * `record` - The record to merge with
178    pub fn merge_inplace(&mut self, record: Record) {
179        for (k, v) in record.iter() {
180            self.0.insert(k.clone(), v.clone());
181        }
182    }
183
184    /// Gets a scalar value from the record.
185    ///
186    /// # Arguments
187    ///
188    /// * `k` - The key of the scalar value
189    ///
190    /// # Returns
191    ///
192    /// The scalar value if it exists and is of the correct type
193    ///
194    /// # Errors
195    ///
196    /// Returns an error if:
197    /// - The key does not exist
198    /// - The value is not a scalar
199    pub fn get_scalar(&self, k: &str) -> Result<f32, LrrError> {
200        if let Some(v) = self.0.get(k) {
201            match v {
202                RecordValue::Scalar(v) => Ok(*v as _),
203                _ => Err(LrrError::RecordValueTypeError("Scalar".to_string())),
204            }
205        } else {
206            Err(LrrError::RecordKeyError(k.to_string()))
207        }
208    }
209
210    /// Gets a 1-dimensional array from the record.
211    ///
212    /// # Arguments
213    ///
214    /// * `k` - The key of the array
215    ///
216    /// # Returns
217    ///
218    /// The array if it exists and is of the correct type
219    ///
220    /// # Errors
221    ///
222    /// Returns an error if:
223    /// - The key does not exist
224    /// - The value is not a 1-dimensional array
225    pub fn get_array1(&self, k: &str) -> Result<Vec<f32>, LrrError> {
226        if let Some(v) = self.0.get(k) {
227            match v {
228                RecordValue::Array1(v) => Ok(v.clone()),
229                _ => Err(LrrError::RecordValueTypeError("Array1".to_string())),
230            }
231        } else {
232            Err(LrrError::RecordKeyError(k.to_string()))
233        }
234    }
235
236    /// Gets a 2-dimensional array from the record.
237    ///
238    /// # Arguments
239    ///
240    /// * `k` - The key of the array
241    ///
242    /// # Returns
243    ///
244    /// A tuple containing the array data and its shape
245    ///
246    /// # Errors
247    ///
248    /// Returns an error if:
249    /// - The key does not exist
250    /// - The value is not a 2-dimensional array
251    pub fn get_array2(&self, k: &str) -> Result<(Vec<f32>, [usize; 2]), LrrError> {
252        if let Some(v) = self.0.get(k) {
253            match v {
254                RecordValue::Array2(v, s) => Ok((v.clone(), s.clone())),
255                _ => Err(LrrError::RecordValueTypeError("Array2".to_string())),
256            }
257        } else {
258            Err(LrrError::RecordKeyError(k.to_string()))
259        }
260    }
261
262    /// Gets a 3-dimensional array from the record.
263    ///
264    /// # Arguments
265    ///
266    /// * `k` - The key of the array
267    ///
268    /// # Returns
269    ///
270    /// A tuple containing the array data and its shape
271    ///
272    /// # Errors
273    ///
274    /// Returns an error if:
275    /// - The key does not exist
276    /// - The value is not a 3-dimensional array
277    pub fn get_array3(&self, k: &str) -> Result<(Vec<f32>, [usize; 3]), LrrError> {
278        if let Some(v) = self.0.get(k) {
279            match v {
280                RecordValue::Array3(v, s) => Ok((v.clone(), s.clone())),
281                _ => Err(LrrError::RecordValueTypeError("Array3".to_string())),
282            }
283        } else {
284            Err(LrrError::RecordKeyError(k.to_string()))
285        }
286    }
287
288    /// Gets a string value from the record.
289    ///
290    /// # Arguments
291    ///
292    /// * `k` - The key of the string
293    ///
294    /// # Returns
295    ///
296    /// The string if it exists and is of the correct type
297    ///
298    /// # Errors
299    ///
300    /// Returns an error if:
301    /// - The key does not exist
302    /// - The value is not a string
303    pub fn get_string(&self, k: &str) -> Result<String, LrrError> {
304        if let Some(v) = self.0.get(k) {
305            match v {
306                RecordValue::String(s) => Ok(s.clone()),
307                _ => Err(LrrError::RecordValueTypeError("String".to_string())),
308            }
309        } else {
310            Err(LrrError::RecordKeyError(k.to_string()))
311        }
312    }
313
314    /// Checks if the record is empty.
315    ///
316    /// # Returns
317    ///
318    /// `true` if the record contains no key-value pairs
319    pub fn is_empty(&self) -> bool {
320        self.0.len() == 0
321    }
322
323    /// Gets a scalar value from the record without specifying a key.
324    ///
325    /// This method is useful when the record contains only one scalar value.
326    ///
327    /// # Returns
328    ///
329    /// The scalar value if it exists and is the only value in the record
330    pub fn get_scalar_without_key(&self) -> Option<f32> {
331        if self.0.len() != 1 {
332            return None;
333        } else {
334            let key = self.0.keys().next().unwrap();
335            match self.0.get(key) {
336                Some(RecordValue::Scalar(value)) => Some(*value),
337                _ => None,
338            }
339        }
340    }
341}