Skip to main content

molrs_core/
frame_access.rs

1//! Unified access trait for owned and borrowed frame types.
2//!
3//! [`FrameAccess`] provides a common read-only interface implemented by both
4//! [`Frame`] and [`FrameView`], enabling generic code that works with either.
5
6use std::collections::HashMap;
7
8use ndarray::ArrayViewD;
9
10use crate::block::access::BlockAccess;
11use crate::frame::Frame;
12use crate::frame_view::FrameView;
13use crate::region::simbox::SimBox;
14use crate::types::{F, I, U};
15
16/// Unified read-only access for [`Frame`] and [`FrameView`].
17///
18/// Provides direct access to typed column data across block boundaries,
19/// as well as metadata and simulation box references.
20pub trait FrameAccess {
21    /// Gets a float array view from a column inside a block.
22    fn get_float(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, F>>;
23    /// Gets an int array view from a column inside a block.
24    fn get_int(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, I>>;
25    /// Gets a bool array view from a column inside a block.
26    fn get_bool(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, bool>>;
27    /// Gets a uint array view from a column inside a block.
28    fn get_uint(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, U>>;
29    /// Gets a u8 array view from a column inside a block.
30    fn get_u8(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, u8>>;
31    /// Gets a string array view from a column inside a block.
32    fn get_string(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, String>>;
33    /// Returns a reference to the simulation box, if present.
34    fn simbox_ref(&self) -> Option<&SimBox>;
35    /// Returns a reference to the metadata map.
36    fn meta_ref(&self) -> &HashMap<String, String>;
37    /// Returns block keys as a `Vec`.
38    fn block_keys(&self) -> Vec<&str>;
39    /// Returns `true` if the frame contains the specified block key.
40    fn contains_block(&self, key: &str) -> bool;
41    /// Number of blocks.
42    fn block_count(&self) -> usize;
43    /// Returns `true` if the frame contains no blocks.
44    fn is_empty(&self) -> bool;
45    /// Visits a block by key through the [`BlockAccess`] trait, using the visitor pattern
46    /// to avoid lifetime/return-type issues. Returns `None` if the block does not exist.
47    fn visit_block<R>(&self, key: &str, f: impl FnOnce(&dyn BlockAccess) -> R) -> Option<R>;
48}
49
50impl FrameAccess for Frame {
51    fn get_float(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, F>> {
52        self.get(block_key)
53            .and_then(|b| b.get_float(col_key))
54            .map(|a| a.view())
55    }
56
57    fn get_int(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, I>> {
58        self.get(block_key)
59            .and_then(|b| b.get_int(col_key))
60            .map(|a| a.view())
61    }
62
63    fn get_bool(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, bool>> {
64        self.get(block_key)
65            .and_then(|b| b.get_bool(col_key))
66            .map(|a| a.view())
67    }
68
69    fn get_uint(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, U>> {
70        self.get(block_key)
71            .and_then(|b| b.get_uint(col_key))
72            .map(|a| a.view())
73    }
74
75    fn get_u8(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, u8>> {
76        self.get(block_key)
77            .and_then(|b| b.get_u8(col_key))
78            .map(|a| a.view())
79    }
80
81    fn get_string(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, String>> {
82        self.get(block_key)
83            .and_then(|b| b.get_string(col_key))
84            .map(|a| a.view())
85    }
86
87    fn simbox_ref(&self) -> Option<&SimBox> {
88        self.simbox.as_ref()
89    }
90
91    fn meta_ref(&self) -> &HashMap<String, String> {
92        &self.meta
93    }
94
95    fn block_keys(&self) -> Vec<&str> {
96        self.keys().collect()
97    }
98
99    fn contains_block(&self, key: &str) -> bool {
100        self.contains_key(key)
101    }
102
103    fn block_count(&self) -> usize {
104        self.len()
105    }
106
107    fn is_empty(&self) -> bool {
108        Frame::is_empty(self)
109    }
110
111    fn visit_block<R>(&self, key: &str, f: impl FnOnce(&dyn BlockAccess) -> R) -> Option<R> {
112        self.get(key).map(|block| f(block))
113    }
114}
115
116impl FrameAccess for FrameView<'_> {
117    fn get_float(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, F>> {
118        self.get(block_key).and_then(|b| b.get_float(col_key))
119    }
120
121    fn get_int(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, I>> {
122        self.get(block_key).and_then(|b| b.get_int(col_key))
123    }
124
125    fn get_bool(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, bool>> {
126        self.get(block_key).and_then(|b| b.get_bool(col_key))
127    }
128
129    fn get_uint(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, U>> {
130        self.get(block_key).and_then(|b| b.get_uint(col_key))
131    }
132
133    fn get_u8(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, u8>> {
134        self.get(block_key).and_then(|b| b.get_u8(col_key))
135    }
136
137    fn get_string(&self, block_key: &str, col_key: &str) -> Option<ArrayViewD<'_, String>> {
138        self.get(block_key).and_then(|b| b.get_string(col_key))
139    }
140
141    fn simbox_ref(&self) -> Option<&SimBox> {
142        self.simbox
143    }
144
145    fn meta_ref(&self) -> &HashMap<String, String> {
146        self.meta
147    }
148
149    fn block_keys(&self) -> Vec<&str> {
150        self.keys().copied().collect()
151    }
152
153    fn contains_block(&self, key: &str) -> bool {
154        self.contains_key(key)
155    }
156
157    fn block_count(&self) -> usize {
158        self.len()
159    }
160
161    fn is_empty(&self) -> bool {
162        FrameView::is_empty(self)
163    }
164
165    fn visit_block<R>(&self, key: &str, f: impl FnOnce(&dyn BlockAccess) -> R) -> Option<R> {
166        self.get(key).map(|block_view| f(block_view))
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173    use crate::block::Block;
174    use crate::types::{F, I};
175    use ndarray::Array1;
176
177    fn make_frame() -> Frame {
178        let mut frame = Frame::new();
179        let mut atoms = Block::new();
180        atoms
181            .insert("x", Array1::from_vec(vec![1.0 as F, 2.0, 3.0]).into_dyn())
182            .unwrap();
183        atoms
184            .insert("id", Array1::from_vec(vec![10 as I, 20, 30]).into_dyn())
185            .unwrap();
186        frame.insert("atoms", atoms);
187        frame.meta.insert("title".into(), "Test".into());
188        frame
189    }
190
191    #[test]
192    fn test_frame_access_on_frame() {
193        let frame = make_frame();
194        assert!(FrameAccess::get_float(&frame, "atoms", "x").is_some());
195        assert!(FrameAccess::get_int(&frame, "atoms", "id").is_some());
196        assert!(FrameAccess::get_float(&frame, "atoms", "missing").is_none());
197        assert!(FrameAccess::get_float(&frame, "missing", "x").is_none());
198        assert_eq!(FrameAccess::block_count(&frame), 1);
199        assert!(FrameAccess::contains_block(&frame, "atoms"));
200        assert!(!FrameAccess::is_empty(&frame));
201        assert_eq!(FrameAccess::meta_ref(&frame).get("title").unwrap(), "Test");
202        assert!(FrameAccess::simbox_ref(&frame).is_none());
203    }
204
205    #[test]
206    fn test_frame_access_on_frame_view() {
207        let frame = make_frame();
208        let view = FrameView::from(&frame);
209        assert!(FrameAccess::get_float(&view, "atoms", "x").is_some());
210        assert!(FrameAccess::get_int(&view, "atoms", "id").is_some());
211        assert!(FrameAccess::get_float(&view, "atoms", "missing").is_none());
212        assert_eq!(FrameAccess::block_count(&view), 1);
213        assert!(FrameAccess::contains_block(&view, "atoms"));
214        assert!(!FrameAccess::is_empty(&view));
215        assert_eq!(FrameAccess::meta_ref(&view).get("title").unwrap(), "Test");
216    }
217
218    #[test]
219    fn test_generic_function_with_frame_access() {
220        fn get_x_data(f: &impl FrameAccess) -> Option<Vec<F>> {
221            f.get_float("atoms", "x")
222                .map(|a| a.iter().copied().collect())
223        }
224
225        let frame = make_frame();
226        assert_eq!(get_x_data(&frame), Some(vec![1.0, 2.0, 3.0]));
227
228        let view = FrameView::from(&frame);
229        assert_eq!(get_x_data(&view), Some(vec![1.0, 2.0, 3.0]));
230    }
231}