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}