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}