ferritin_core/model/
conformation.rs1use super::ModelError;
8
9#[derive(Clone, Debug)]
21pub struct AtomicConformation {
22 pub x: Vec<f32>,
24 pub y: Vec<f32>,
26 pub z: Vec<f32>,
28 pub occupancy: Option<Vec<f32>>,
30 pub b_iso: Option<Vec<f32>>,
33 pub confidence: Option<Vec<f32>>,
38}
39
40impl AtomicConformation {
41 pub fn validate(&self, n_atoms: usize) -> Result<(), ModelError> {
43 for (name, len) in [
44 ("x", self.x.len()),
45 ("y", self.y.len()),
46 ("z", self.z.len()),
47 ] {
48 if len != n_atoms {
49 return Err(ModelError::new(format!(
50 "conformation.{name} has length {len}, expected {n_atoms}"
51 )));
52 }
53 }
54 for (name, values) in [
55 ("occupancy", self.occupancy.as_ref()),
56 ("b_iso", self.b_iso.as_ref()),
57 ("confidence", self.confidence.as_ref()),
58 ] {
59 if let Some(values) = values
60 && values.len() != n_atoms
61 {
62 return Err(ModelError::new(format!(
63 "conformation.{name} has length {}, expected {n_atoms}",
64 values.len()
65 )));
66 }
67 }
68 Ok(())
69 }
70 pub fn n_atoms(&self) -> usize {
72 self.x.len()
73 }
74
75 pub fn coord(&self, i: usize) -> [f32; 3] {
80 [self.x[i], self.y[i], self.z[i]]
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn test_conformation_coord() {
90 let conf = AtomicConformation {
91 x: vec![1.0, 4.0, 7.0],
92 y: vec![2.0, 5.0, 8.0],
93 z: vec![3.0, 6.0, 9.0],
94 occupancy: Some(vec![1.0, 0.5, 1.0]),
95 b_iso: None,
96 confidence: Some(vec![90.0, 85.0, 92.0]),
97 };
98
99 assert_eq!(conf.n_atoms(), 3);
100 assert_eq!(conf.coord(0), [1.0, 2.0, 3.0]);
101 assert_eq!(conf.coord(1), [4.0, 5.0, 6.0]);
102 assert_eq!(conf.coord(2), [7.0, 8.0, 9.0]);
103 }
104
105 #[test]
106 fn test_conformation_optional_fields() {
107 let conf = AtomicConformation {
108 x: vec![0.0],
109 y: vec![0.0],
110 z: vec![0.0],
111 occupancy: None,
112 b_iso: None,
113 confidence: None,
114 };
115 assert_eq!(conf.n_atoms(), 1);
116 assert_eq!(conf.coord(0), [0.0, 0.0, 0.0]);
117 assert!(conf.occupancy.is_none());
118 assert!(conf.b_iso.is_none());
119 assert!(conf.confidence.is_none());
120 }
121
122 #[test]
123 fn test_conformation_validation_rejects_mismatched_columns() {
124 let conf = AtomicConformation {
125 x: vec![0.0],
126 y: vec![],
127 z: vec![0.0],
128 occupancy: None,
129 b_iso: None,
130 confidence: None,
131 };
132 assert_eq!(
133 conf.validate(1).unwrap_err().to_string(),
134 "conformation.y has length 0, expected 1"
135 );
136 }
137}