Skip to main content

molrs_core/
frame.rs

1//! Frame: a dictionary mapping string keys to heterogeneous [`Block`]s.
2//!
3//! A Frame groups multiple [`Block`]s under string keys. Each `Block` may contain
4//! heterogeneous columns (different scalar dtypes like f32, f64, i64, bool), and
5//! manages its own `nrows` invariant. `Frame` itself only manages the mapping from
6//! names to blocks and does **not** enforce cross-block axis-0 consistency.
7//!
8//! # Examples
9//!
10//! ```
11//! use molrs_core::frame::Frame;
12//! use molrs_core::block::Block;
13//! use molrs_core::types::{F, I};
14//! use ndarray::Array1;
15//!
16//! let mut frame = Frame::new();
17//!
18//! // Create an atoms block
19//! let mut atoms = Block::new();
20//! atoms.insert("x", Array1::from_vec(vec![1.0 as F, 2.0 as F, 3.0 as F]).into_dyn()).unwrap();
21//! atoms.insert("y", Array1::from_vec(vec![0.0 as F, 1.0 as F, 2.0 as F]).into_dyn()).unwrap();
22//! atoms.insert("id", Array1::from_vec(vec![1 as I, 2 as I, 3 as I]).into_dyn()).unwrap();
23//!
24//! frame.insert("atoms", atoms);
25//!
26//! // Access via Index trait
27//! let atoms_ref = &frame["atoms"];
28//! assert_eq!(atoms_ref.nrows(), Some(3));
29//!
30//! // Add metadata
31//! frame.meta.insert("title".into(), "My Molecule".into());
32//! ```
33
34use std::collections::HashMap;
35use std::ops::{Index, IndexMut};
36
37use super::block::Block;
38use super::region::simbox::SimBox;
39use crate::error::MolRsError;
40use crate::grid::Grid;
41
42/// A dictionary from string keys to [`Block`]s.
43///
44/// Frame provides a simple container for organizing multiple blocks of data,
45/// typically representing different aspects of a molecular system (e.g., atoms,
46/// bonds, velocities). Each block can have different numbers of rows and different
47/// column types.
48#[derive(Default, Clone)]
49pub struct Frame {
50    map: HashMap<String, Block>,
51    grids: HashMap<String, Grid>,
52    /// Arbitrary key-value metadata associated with the frame.
53    pub meta: HashMap<String, String>,
54    /// Simulation box defining periodic boundary conditions.
55    pub simbox: Option<SimBox>,
56}
57
58/// Type alias for the result of into_inner().
59type IntoInnerResult = (
60    HashMap<String, Block>,
61    HashMap<String, Grid>,
62    HashMap<String, String>,
63    Option<SimBox>,
64);
65
66impl std::fmt::Debug for Frame {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        let mut debug_struct = f.debug_struct("Frame");
69
70        // Format blocks as a map of name -> (nrows, ncols)
71        let mut blocks_map = std::collections::BTreeMap::new();
72        for (k, b) in &self.map {
73            blocks_map.insert(k.as_str(), (b.nrows(), b.len()));
74        }
75        debug_struct.field("blocks", &blocks_map);
76
77        // Show metadata if non-empty
78        if !self.meta.is_empty() {
79            debug_struct.field("meta", &self.meta);
80        }
81        if !self.grids.is_empty() {
82            let mut grid_names: Vec<_> = self.grids.keys().cloned().collect();
83            grid_names.sort();
84            debug_struct.field("grids", &grid_names);
85        }
86
87        debug_struct.finish()
88    }
89}
90
91impl Frame {
92    /// Creates an empty Frame.
93    ///
94    /// # Examples
95    ///
96    /// ```
97    /// use molrs_core::frame::Frame;
98    ///
99    /// let frame = Frame::new();
100    /// assert!(frame.is_empty());
101    /// ```
102    pub fn new() -> Self {
103        Self {
104            map: HashMap::new(),
105            grids: HashMap::new(),
106            meta: HashMap::new(),
107            simbox: None,
108        }
109    }
110
111    /// Creates an empty Frame with the specified capacity for blocks.
112    ///
113    /// # Examples
114    ///
115    /// ```
116    /// use molrs_core::frame::Frame;
117    ///
118    /// let frame = Frame::with_capacity(10);
119    /// assert!(frame.is_empty());
120    /// ```
121    pub fn with_capacity(cap: usize) -> Self {
122        Self {
123            map: HashMap::with_capacity(cap),
124            grids: HashMap::new(),
125            meta: HashMap::new(),
126            simbox: None,
127        }
128    }
129
130    /// Creates a Frame from an existing HashMap of blocks.
131    ///
132    /// # Examples
133    ///
134    /// ```
135    /// use molrs_core::frame::Frame;
136    /// use molrs_core::block::Block;
137    /// use std::collections::HashMap;
138    ///
139    /// let mut map = HashMap::new();
140    /// map.insert("atoms".to_string(), Block::new());
141    ///
142    /// let frame = Frame::from_map(map);
143    /// assert_eq!(frame.len(), 1);
144    /// ```
145    pub fn from_map(map: HashMap<String, Block>) -> Self {
146        Self {
147            map,
148            grids: HashMap::new(),
149            meta: HashMap::new(),
150            simbox: None,
151        }
152    }
153
154    /// Consumes the Frame and returns the inner HashMap of blocks, grids, metadata, and simbox.
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// use molrs_core::frame::Frame;
160    /// use molrs_core::block::Block;
161    ///
162    /// let mut frame = Frame::new();
163    /// frame.insert("atoms", Block::new());
164    /// frame.meta.insert("title".into(), "Test".into());
165    ///
166    /// let (blocks, grids, meta, simbox) = frame.into_inner();
167    /// assert_eq!(blocks.len(), 1);
168    /// assert!(grids.is_empty());
169    /// assert_eq!(meta.get("title").unwrap(), "Test");
170    /// assert!(simbox.is_none());
171    /// ```
172    pub fn into_inner(self) -> IntoInnerResult {
173        (self.map, self.grids, self.meta, self.simbox)
174    }
175
176    /// Number of blocks (keys) in the frame.
177    ///
178    /// # Examples
179    ///
180    /// ```
181    /// use molrs_core::frame::Frame;
182    /// use molrs_core::block::Block;
183    ///
184    /// let mut frame = Frame::new();
185    /// assert_eq!(frame.len(), 0);
186    ///
187    /// frame.insert("atoms", Block::new());
188    /// assert_eq!(frame.len(), 1);
189    /// ```
190    #[inline]
191    pub fn len(&self) -> usize {
192        self.map.len()
193    }
194
195    /// Returns true if the frame contains no blocks.
196    ///
197    /// Note: This only checks blocks, not metadata.
198    #[inline]
199    pub fn is_empty(&self) -> bool {
200        self.map.is_empty()
201    }
202
203    /// Returns true if the frame contains the specified key.
204    #[inline]
205    pub fn contains_key(&self, key: &str) -> bool {
206        self.map.contains_key(key)
207    }
208
209    /// Gets an immutable reference to the block for `key` if present.
210    ///
211    /// For a panicking version, use the `Index` trait: `&frame["key"]`.
212    #[inline]
213    pub fn get(&self, key: &str) -> Option<&Block> {
214        self.map.get(key)
215    }
216
217    /// Gets a mutable reference to the block for `key` if present.
218    ///
219    /// For a panicking version, use the `IndexMut` trait: `&mut frame["key"]`.
220    #[inline]
221    pub fn get_mut(&mut self, key: &str) -> Option<&mut Block> {
222        self.map.get_mut(key)
223    }
224
225    /// Inserts a block under `key`. Returns the previous block if any.
226    ///
227    /// # Examples
228    ///
229    /// ```
230    /// use molrs_core::frame::Frame;
231    /// use molrs_core::block::Block;
232    ///
233    /// let mut frame = Frame::new();
234    /// let old = frame.insert("atoms", Block::new());
235    /// assert!(old.is_none());
236    ///
237    /// let old = frame.insert("atoms", Block::new());
238    /// assert!(old.is_some());
239    /// ```
240    pub fn insert(&mut self, key: impl Into<String>, block: Block) -> Option<Block> {
241        self.map.insert(key.into(), block)
242    }
243
244    /// Removes and returns the block for `key`, if present.
245    pub fn remove(&mut self, key: &str) -> Option<Block> {
246        self.map.remove(key)
247    }
248
249    /// Clears the frame, removing all blocks.
250    ///
251    /// **Note**: This does NOT clear metadata. Use `clear_all()` to clear both.
252    ///
253    /// # Examples
254    ///
255    /// ```
256    /// use molrs_core::frame::Frame;
257    /// use molrs_core::block::Block;
258    ///
259    /// let mut frame = Frame::new();
260    /// frame.insert("atoms", Block::new());
261    /// frame.meta.insert("title".into(), "Test".into());
262    ///
263    /// frame.clear();
264    /// assert!(frame.is_empty());
265    /// assert!(!frame.meta.is_empty()); // metadata preserved
266    /// ```
267    pub fn clear(&mut self) {
268        self.map.clear();
269    }
270
271    /// Clears both blocks and metadata.
272    ///
273    /// # Examples
274    ///
275    /// ```
276    /// use molrs_core::frame::Frame;
277    /// use molrs_core::block::Block;
278    ///
279    /// let mut frame = Frame::new();
280    /// frame.insert("atoms", Block::new());
281    /// frame.meta.insert("title".into(), "Test".into());
282    ///
283    /// frame.clear_all();
284    /// assert!(frame.is_empty());
285    /// assert!(frame.meta.is_empty());
286    /// ```
287    pub fn clear_all(&mut self) {
288        self.map.clear();
289        self.grids.clear();
290        self.meta.clear();
291        self.simbox = None;
292    }
293
294    /// Insert or replace a named grid.
295    pub fn insert_grid(&mut self, name: impl Into<String>, grid: Grid) -> Option<Grid> {
296        self.grids.insert(name.into(), grid)
297    }
298
299    /// Remove a named grid.
300    pub fn remove_grid(&mut self, name: &str) -> Option<Grid> {
301        self.grids.remove(name)
302    }
303
304    /// Borrow a grid by name.
305    pub fn get_grid(&self, name: &str) -> Option<&Grid> {
306        self.grids.get(name)
307    }
308
309    /// Borrow a grid mutably by name.
310    pub fn get_grid_mut(&mut self, name: &str) -> Option<&mut Grid> {
311        self.grids.get_mut(name)
312    }
313
314    /// Returns true if the frame contains a named grid.
315    pub fn has_grid(&self, name: &str) -> bool {
316        self.grids.contains_key(name)
317    }
318
319    /// Returns an iterator over `(name, grid)` pairs.
320    pub fn grids(&self) -> impl Iterator<Item = (&str, &Grid)> {
321        self.grids.iter().map(|(k, v)| (k.as_str(), v))
322    }
323
324    /// Returns an iterator over grid names.
325    pub fn grid_keys(&self) -> impl Iterator<Item = &str> {
326        self.grids.keys().map(|k| k.as_str())
327    }
328
329    /// Renames a column in the specified block.
330    ///
331    /// Returns `true` if the column was successfully renamed, `false` if the block doesn't exist,
332    /// the old column key doesn't exist, or the new column key already exists.
333    ///
334    /// # Examples
335    ///
336    /// ```
337    /// use molrs_core::frame::Frame;
338    /// use molrs_core::block::Block;
339    /// use molrs_core::types::F;
340    /// use ndarray::Array1;
341    ///
342    /// let mut frame = Frame::new();
343    /// let mut atoms = Block::new();
344    /// atoms.insert("x", Array1::from_vec(vec![1.0 as F, 2.0 as F]).into_dyn()).unwrap();
345    /// frame.insert("atoms", atoms);
346    ///
347    /// assert!(frame.rename_column("atoms", "x", "position_x"));
348    /// assert!(!frame["atoms"].contains_key("x"));
349    /// assert!(frame["atoms"].contains_key("position_x"));
350    /// ```
351    pub fn rename_column(&mut self, block_key: &str, old_col_key: &str, new_col_key: &str) -> bool {
352        if let Some(block) = self.map.get_mut(block_key) {
353            block.rename_column(old_col_key, new_col_key)
354        } else {
355            false
356        }
357    }
358
359    /// Renames a block in the frame.
360    ///
361    /// Returns `true` if the block was successfully renamed, `false` if the old block
362    /// doesn't exist or the new block name already exists.
363    ///
364    /// # Examples
365    ///
366    /// ```
367    /// use molrs_core::frame::Frame;
368    /// use molrs_core::block::Block;
369    /// use molrs_core::types::F;
370    /// use ndarray::Array1;
371    ///
372    /// let mut frame = Frame::new();
373    /// let mut atoms = Block::new();
374    /// atoms.insert("x", Array1::from_vec(vec![1.0 as F]).into_dyn()).unwrap();
375    /// frame.insert("atoms", atoms);
376    ///
377    /// assert!(frame.rename_block("atoms", "molecules"));
378    /// assert!(!frame.contains_key("atoms"));
379    /// assert!(frame.contains_key("molecules"));
380    /// ```
381    pub fn rename_block(&mut self, old_key: &str, new_key: &str) -> bool {
382        // Check if old_key exists and new_key doesn't exist
383        if !self.map.contains_key(old_key) || self.map.contains_key(new_key) {
384            return false;
385        }
386
387        // Remove the old key and re-insert with new key
388        if let Some(block) = self.map.remove(old_key) {
389            self.map.insert(new_key.to_string(), block);
390            true
391        } else {
392            false
393        }
394    }
395
396    /// Returns an iterator over (&str, &Block).
397    pub fn iter(&self) -> impl Iterator<Item = (&str, &Block)> {
398        self.map.iter().map(|(k, v)| (k.as_str(), v))
399    }
400
401    /// Returns a mutable iterator over (&str, &mut Block).
402    ///
403    /// # Examples
404    ///
405    /// ```
406    /// use molrs_core::frame::Frame;
407    /// use molrs_core::block::Block;
408    /// use molrs_core::types::F;
409    /// use ndarray::Array1;
410    ///
411    /// let mut frame = Frame::new();
412    /// let mut atoms = Block::new();
413    /// atoms.insert("x", Array1::from_vec(vec![1.0 as F]).into_dyn()).unwrap();
414    /// frame.insert("atoms", atoms);
415    ///
416    /// for (_name, block) in frame.iter_mut() {
417    ///     // Can mutate blocks
418    ///     if let Some(x) = block.get_float_mut("x") {
419    ///         x[[0]] = 99.0 as F;
420    ///     }
421    /// }
422    /// ```
423    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut Block)> {
424        self.map.iter_mut().map(|(k, v)| (k.as_str(), v))
425    }
426
427    /// Returns an iterator over keys.
428    pub fn keys(&self) -> impl Iterator<Item = &str> {
429        self.map.keys().map(|k| k.as_str())
430    }
431
432    /// Returns an iterator over block references.
433    pub fn values(&self) -> impl Iterator<Item = &Block> {
434        self.map.values()
435    }
436
437    /// Returns a mutable iterator over block references.
438    pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Block> {
439        self.map.values_mut()
440    }
441
442    /// Validates cross-block consistency.
443    ///
444    /// This method checks for common consistency issues:
445    /// - All blocks with "atoms" prefix should have the same nrows
446    /// - Bond indices (if present) should reference valid atoms
447    ///
448    /// # Returns
449    /// - `Ok(())` if validation passes
450    /// - `Err(MolRsError::Validation)` with details if validation fails
451    ///
452    /// # Examples
453    ///
454    /// ```
455    /// use molrs_core::frame::Frame;
456    /// use molrs_core::block::Block;
457    /// use molrs_core::types::F;
458    /// use ndarray::Array1;
459    ///
460    /// let mut frame = Frame::new();
461    /// let mut atoms = Block::new();
462    /// atoms.insert("x", Array1::from_vec(vec![1.0 as F, 2.0 as F, 3.0 as F]).into_dyn()).unwrap();
463    /// atoms.insert("y", Array1::from_vec(vec![0.0 as F, 1.0 as F, 2.0 as F]).into_dyn()).unwrap();
464    /// frame.insert("atoms", atoms);
465    ///
466    /// assert!(frame.validate().is_ok());
467    /// ```
468    pub fn validate(&self) -> Result<(), MolRsError> {
469        // Check atoms blocks have consistent nrows
470        let atoms_blocks: Vec<_> = self
471            .map
472            .iter()
473            .filter(|(k, _)| k.starts_with("atoms"))
474            .collect();
475
476        if !atoms_blocks.is_empty() {
477            let first_nrows = atoms_blocks[0].1.nrows();
478            for (key, block) in &atoms_blocks {
479                if block.nrows() != first_nrows {
480                    return Err(MolRsError::validation(format!(
481                        "Inconsistent atom block sizes: '{}' has {:?} rows but expected {:?}",
482                        key,
483                        block.nrows(),
484                        first_nrows
485                    )));
486                }
487            }
488        }
489
490        // Check bond indices if bonds block exists
491        if let Some(bonds) = self.get("bonds")
492            && let Some(atoms) = self.get("atoms")
493        {
494            let natoms = atoms.nrows().unwrap_or(0);
495
496            // Check atomi indices
497            if let Some(i_col) = bonds.get_uint("atomi") {
498                for &idx in i_col.iter() {
499                    if idx as usize >= natoms {
500                        return Err(MolRsError::validation(format!(
501                            "Bond atomi index {} out of range [0, {})",
502                            idx, natoms
503                        )));
504                    }
505                }
506            }
507
508            // Check atomj indices
509            if let Some(j_col) = bonds.get_uint("atomj") {
510                for &idx in j_col.iter() {
511                    if idx as usize >= natoms {
512                        return Err(MolRsError::validation(format!(
513                            "Bond atomj index {} out of range [0, {})",
514                            idx, natoms
515                        )));
516                    }
517                }
518            }
519        }
520
521        Ok(())
522    }
523
524    /// Checks if the frame is consistent without returning an error.
525    ///
526    /// This is a non-panicking version of `validate()` that returns a boolean.
527    ///
528    /// # Examples
529    ///
530    /// ```
531    /// use molrs_core::frame::Frame;
532    /// use molrs_core::block::Block;
533    ///
534    /// let frame = Frame::new();
535    /// assert!(frame.is_consistent());
536    /// ```
537    pub fn is_consistent(&self) -> bool {
538        self.validate().is_ok()
539    }
540}
541
542// Index trait for convenient access: frame["atoms"]
543impl Index<&str> for Frame {
544    type Output = Block;
545
546    fn index(&self, key: &str) -> &Self::Output {
547        self.get(key)
548            .unwrap_or_else(|| panic!("Frame does not contain block '{}'", key))
549    }
550}
551
552impl IndexMut<&str> for Frame {
553    fn index_mut(&mut self, key: &str) -> &mut Self::Output {
554        self.get_mut(key)
555            .unwrap_or_else(|| panic!("Frame does not contain block '{}'", key))
556    }
557}
558
559#[cfg(test)]
560mod tests {
561    use super::*;
562    use crate::types::{F, I};
563    use ndarray::Array1;
564
565    #[test]
566    fn test_frame_new() {
567        let frame = Frame::new();
568        assert!(frame.is_empty());
569        assert_eq!(frame.len(), 0);
570    }
571
572    #[test]
573    fn test_frame_insert_get() {
574        let mut frame = Frame::new();
575        let mut block = Block::new();
576        block
577            .insert("x", Array1::from_vec(vec![1.0 as F, 2.0 as F]).into_dyn())
578            .unwrap();
579
580        frame.insert("atoms", block);
581        assert_eq!(frame.len(), 1);
582        assert!(frame.contains_key("atoms"));
583
584        let atoms = frame.get("atoms").unwrap();
585        assert_eq!(atoms.nrows(), Some(2));
586    }
587
588    #[test]
589    fn test_frame_index_access() {
590        let mut frame = Frame::new();
591        let mut block = Block::new();
592        block
593            .insert("x", Array1::from_vec(vec![1.0 as F]).into_dyn())
594            .unwrap();
595        frame.insert("atoms", block);
596
597        // Immutable index
598        let atoms = &frame["atoms"];
599        assert_eq!(atoms.nrows(), Some(1));
600
601        // Mutable index
602        let atoms_mut = &mut frame["atoms"];
603        if let Some(x) = atoms_mut.get_float_mut("x") {
604            x[[0]] = 99.0;
605        }
606        assert_eq!(frame["atoms"].get_float("x").unwrap()[[0]], 99.0);
607    }
608
609    #[test]
610    #[should_panic(expected = "Frame does not contain block 'missing'")]
611    fn test_frame_index_panic() {
612        let frame = Frame::new();
613        let _ = &frame["missing"];
614    }
615
616    #[test]
617    fn test_frame_iter() {
618        let mut frame = Frame::new();
619        frame.insert("atoms", Block::new());
620        frame.insert("bonds", Block::new());
621
622        let keys: Vec<&str> = frame.keys().collect();
623        assert_eq!(keys.len(), 2);
624        assert!(keys.contains(&"atoms"));
625        assert!(keys.contains(&"bonds"));
626
627        let mut count = 0;
628        for (_name, _block) in frame.iter() {
629            count += 1;
630        }
631        assert_eq!(count, 2);
632    }
633
634    #[test]
635    fn test_frame_iter_mut() {
636        let mut frame = Frame::new();
637        let mut block = Block::new();
638        block
639            .insert("x", Array1::from_vec(vec![1.0 as F]).into_dyn())
640            .unwrap();
641        frame.insert("atoms", block);
642
643        for (_name, block) in frame.iter_mut() {
644            if let Some(x) = block.get_float_mut("x") {
645                x[[0]] = 42.0;
646            }
647        }
648
649        assert_eq!(frame["atoms"].get_float("x").unwrap()[[0]], 42.0);
650    }
651
652    #[test]
653    fn test_frame_values_mut() {
654        let mut frame = Frame::new();
655        let mut block = Block::new();
656        block
657            .insert("x", Array1::from_vec(vec![1.0 as F]).into_dyn())
658            .unwrap();
659        frame.insert("atoms", block);
660
661        for block in frame.values_mut() {
662            if let Some(x) = block.get_float_mut("x") {
663                x[[0]] = 77.0;
664            }
665        }
666
667        assert_eq!(frame["atoms"].get_float("x").unwrap()[[0]], 77.0);
668    }
669
670    #[test]
671    fn test_frame_from_map() {
672        let mut map = HashMap::new();
673        map.insert("atoms".to_string(), Block::new());
674        map.insert("bonds".to_string(), Block::new());
675
676        let frame = Frame::from_map(map);
677        assert_eq!(frame.len(), 2);
678        assert!(frame.contains_key("atoms"));
679        assert!(frame.contains_key("bonds"));
680    }
681
682    #[test]
683    fn test_frame_into_inner() {
684        let mut frame = Frame::new();
685        frame.insert("atoms", Block::new());
686        frame.meta.insert("title".into(), "Test".into());
687
688        let (blocks, grids, meta, simbox) = frame.into_inner();
689        assert_eq!(blocks.len(), 1);
690        assert!(grids.is_empty());
691        assert!(blocks.contains_key("atoms"));
692        assert_eq!(meta.get("title").unwrap(), "Test");
693        assert!(simbox.is_none());
694    }
695
696    #[test]
697    fn test_frame_clear_preserves_meta() {
698        let mut frame = Frame::new();
699        frame.insert("atoms", Block::new());
700        frame.meta.insert("title".into(), "Test".into());
701
702        frame.clear();
703        assert!(frame.is_empty());
704        assert!(!frame.meta.is_empty());
705        assert_eq!(frame.meta.get("title").unwrap(), "Test");
706    }
707
708    #[test]
709    fn test_frame_clear_all() {
710        let mut frame = Frame::new();
711        frame.insert("atoms", Block::new());
712        frame.meta.insert("title".into(), "Test".into());
713
714        frame.clear_all();
715        assert!(frame.is_empty());
716        assert!(frame.meta.is_empty());
717    }
718
719    #[test]
720    fn test_frame_debug() {
721        let mut frame = Frame::new();
722        let mut atoms = Block::new();
723        atoms
724            .insert(
725                "x",
726                Array1::from_vec(vec![1.0 as F, 2.0 as F, 3.0 as F]).into_dyn(),
727            )
728            .unwrap();
729        atoms
730            .insert(
731                "y",
732                Array1::from_vec(vec![0.0 as F, 1.0 as F, 2.0 as F]).into_dyn(),
733            )
734            .unwrap();
735        frame.insert("atoms", atoms);
736        frame.meta.insert("title".into(), "Test".into());
737
738        let debug_str = format!("{:?}", frame);
739        assert!(debug_str.contains("Frame"));
740        assert!(debug_str.contains("atoms"));
741        assert!(debug_str.contains("title"));
742    }
743
744    #[test]
745    fn test_rename_column() {
746        let mut frame = Frame::new();
747        let mut atoms = Block::new();
748        atoms
749            .insert("x", Array1::from_vec(vec![1.0 as F, 2.0 as F]).into_dyn())
750            .unwrap();
751        atoms
752            .insert("y", Array1::from_vec(vec![3.0 as F, 4.0 as F]).into_dyn())
753            .unwrap();
754        frame.insert("atoms", atoms);
755
756        // Successful rename
757        assert!(frame.rename_column("atoms", "x", "position_x"));
758        assert!(!frame["atoms"].contains_key("x"));
759        assert!(frame["atoms"].contains_key("position_x"));
760        assert_eq!(
761            frame["atoms"]
762                .get_float("position_x")
763                .unwrap()
764                .as_slice_memory_order()
765                .unwrap(),
766            &[1.0, 2.0]
767        );
768
769        // Try to rename in non-existent block
770        assert!(!frame.rename_column("nonexistent", "x", "new_x"));
771
772        // Try to rename non-existent column
773        assert!(!frame.rename_column("atoms", "nonexistent", "new_name"));
774    }
775
776    #[test]
777    fn test_rename_block() {
778        let mut frame = Frame::new();
779        let mut atoms = Block::new();
780        atoms
781            .insert("x", Array1::from_vec(vec![1.0 as F, 2.0 as F]).into_dyn())
782            .unwrap();
783        atoms
784            .insert("y", Array1::from_vec(vec![3.0 as F, 4.0 as F]).into_dyn())
785            .unwrap();
786        frame.insert("atoms", atoms);
787
788        // Successful rename
789        assert!(frame.rename_block("atoms", "molecules"));
790        assert!(!frame.contains_key("atoms"));
791        assert!(frame.contains_key("molecules"));
792        assert_eq!(
793            frame["molecules"]
794                .get_float("x")
795                .unwrap()
796                .as_slice_memory_order()
797                .unwrap(),
798            &[1.0, 2.0]
799        );
800
801        // Try to rename non-existent block
802        assert!(!frame.rename_block("nonexistent", "new_block"));
803
804        // Try to rename to existing block name
805        let mut bonds = Block::new();
806        bonds
807            .insert("type", Array1::from_vec(vec![1 as I]).into_dyn())
808            .unwrap();
809        frame.insert("bonds", bonds);
810        assert!(!frame.rename_block("molecules", "bonds"));
811    }
812}