1use super::types::{Element, Point};
9use smol_str::SmolStr;
10use std::fmt;
11
12#[derive(Debug, Clone, PartialEq)]
18pub struct Atom {
19 pub name: SmolStr,
21 pub element: Element,
23 pub pos: Point,
25}
26
27impl Atom {
28 pub fn new(name: &str, element: Element, pos: Point) -> Self {
43 Self {
44 name: SmolStr::new(name),
45 element,
46 pos,
47 }
48 }
49
50 pub fn distance_squared(&self, other: &Atom) -> f64 {
63 nalgebra::distance_squared(&self.pos, &other.pos)
64 }
65
66 pub fn distance(&self, other: &Atom) -> f64 {
79 nalgebra::distance(&self.pos, &other.pos)
80 }
81
82 pub fn translate_by(&mut self, vector: &nalgebra::Vector3<f64>) {
91 self.pos += vector;
92 }
93}
94
95impl fmt::Display for Atom {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 write!(
98 f,
99 "Atom {{ name: \"{}\", element: {}, pos: [{:.3}, {:.3}, {:.3}] }}",
100 self.name, self.element, self.pos.x, self.pos.y, self.pos.z
101 )
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn atom_new_creates_correct_atom() {
111 let pos = Point::new(1.0, 2.0, 3.0);
112 let atom = Atom::new("C1", Element::C, pos);
113
114 assert_eq!(atom.name, "C1");
115 assert_eq!(atom.element, Element::C);
116 assert_eq!(atom.pos, pos);
117 }
118
119 #[test]
120 fn atom_distance_squared_calculates_correctly() {
121 let atom1 = Atom::new("A", Element::H, Point::new(0.0, 0.0, 0.0));
122 let atom2 = Atom::new("B", Element::H, Point::new(3.0, 4.0, 0.0));
123
124 let dist_sq = atom1.distance_squared(&atom2);
125 assert!((dist_sq - 25.0).abs() < 1e-10);
126 }
127
128 #[test]
129 fn atom_distance_calculates_correctly() {
130 let atom1 = Atom::new("A", Element::H, Point::new(0.0, 0.0, 0.0));
131 let atom2 = Atom::new("B", Element::H, Point::new(3.0, 4.0, 0.0));
132
133 let dist = atom1.distance(&atom2);
134 assert!((dist - 5.0).abs() < 1e-10);
135 }
136
137 #[test]
138 fn atom_distance_squared_zero_for_same_position() {
139 let pos = Point::new(1.5, -2.3, 4.7);
140 let atom1 = Atom::new("A", Element::O, pos);
141 let atom2 = Atom::new("B", Element::O, pos);
142
143 let dist_sq = atom1.distance_squared(&atom2);
144 assert!((dist_sq - 0.0).abs() < 1e-10);
145 }
146
147 #[test]
148 fn atom_distance_zero_for_same_position() {
149 let pos = Point::new(1.5, -2.3, 4.7);
150 let atom1 = Atom::new("A", Element::O, pos);
151 let atom2 = Atom::new("B", Element::O, pos);
152
153 let dist = atom1.distance(&atom2);
154 assert!((dist - 0.0).abs() < 1e-10);
155 }
156
157 #[test]
158 fn atom_translate_by_updates_position_correctly() {
159 let mut atom = Atom::new("Test", Element::N, Point::new(1.0, 2.0, 3.0));
160 let vector = nalgebra::Vector3::new(0.5, -1.0, 2.5);
161
162 atom.translate_by(&vector);
163
164 assert!((atom.pos.x - 1.5).abs() < 1e-10);
165 assert!((atom.pos.y - 1.0).abs() < 1e-10);
166 assert!((atom.pos.z - 5.5).abs() < 1e-10);
167 }
168
169 #[test]
170 fn atom_translate_by_with_zero_vector_no_change() {
171 let mut atom = Atom::new("Test", Element::N, Point::new(1.0, 2.0, 3.0));
172 let original_pos = atom.pos;
173 let vector = nalgebra::Vector3::new(0.0, 0.0, 0.0);
174
175 atom.translate_by(&vector);
176
177 assert_eq!(atom.pos, original_pos);
178 }
179
180 #[test]
181 fn atom_display_formats_correctly() {
182 let atom = Atom::new("CA", Element::C, Point::new(1.234, -5.678, 9.012));
183
184 let display = format!("{}", atom);
185 let expected = "Atom { name: \"CA\", element: C, pos: [1.234, -5.678, 9.012] }";
186
187 assert_eq!(display, expected);
188 }
189
190 #[test]
191 fn atom_display_with_unknown_element() {
192 let atom = Atom::new("UNK", Element::Unknown, Point::new(0.0, 0.0, 0.0));
193
194 let display = format!("{}", atom);
195 let expected = "Atom { name: \"UNK\", element: Unknown, pos: [0.000, 0.000, 0.000] }";
196
197 assert_eq!(display, expected);
198 }
199
200 #[test]
201 fn atom_clone_creates_identical_copy() {
202 let atom = Atom::new("CloneTest", Element::Fe, Point::new(7.89, -1.23, 4.56));
203 let cloned = atom.clone();
204
205 assert_eq!(atom, cloned);
206 assert_eq!(atom.name, cloned.name);
207 assert_eq!(atom.element, cloned.element);
208 assert_eq!(atom.pos, cloned.pos);
209 }
210
211 #[test]
212 fn atom_partial_eq_compares_correctly() {
213 let atom1 = Atom::new("Test", Element::O, Point::new(1.0, 2.0, 3.0));
214 let atom2 = Atom::new("Test", Element::O, Point::new(1.0, 2.0, 3.0));
215 let atom3 = Atom::new("Different", Element::O, Point::new(1.0, 2.0, 3.0));
216 let atom4 = Atom::new("Test", Element::N, Point::new(1.0, 2.0, 3.0));
217 let atom5 = Atom::new("Test", Element::O, Point::new(1.1, 2.0, 3.0));
218
219 assert_eq!(atom1, atom2);
220 assert_ne!(atom1, atom3);
221 assert_ne!(atom1, atom4);
222 assert_ne!(atom1, atom5);
223 }
224
225 #[test]
226 fn atom_distance_with_negative_coordinates() {
227 let atom1 = Atom::new("A", Element::H, Point::new(-1.0, -2.0, -3.0));
228 let atom2 = Atom::new("B", Element::H, Point::new(1.0, 2.0, 3.0));
229
230 let dist_sq = atom1.distance_squared(&atom2);
231 assert!((dist_sq - 56.0).abs() < 1e-10);
232
233 let dist = atom1.distance(&atom2);
234 assert!((dist - (56.0_f64).sqrt()).abs() < 1e-10);
235 }
236
237 #[test]
238 fn atom_translate_by_multiple_times_accumulates() {
239 let mut atom = Atom::new("Test", Element::C, Point::new(0.0, 0.0, 0.0));
240
241 atom.translate_by(&nalgebra::Vector3::new(1.0, 0.0, 0.0));
242 atom.translate_by(&nalgebra::Vector3::new(0.0, 2.0, 0.0));
243 atom.translate_by(&nalgebra::Vector3::new(0.0, 0.0, 3.0));
244
245 assert!((atom.pos.x - 1.0).abs() < 1e-10);
246 assert!((atom.pos.y - 2.0).abs() < 1e-10);
247 assert!((atom.pos.z - 3.0).abs() < 1e-10);
248 }
249}