1use crate::data::OrderedSet;
4use crate::model::Model;
5
6#[derive(Clone)]
12pub struct Unit<'a> {
13 model: &'a Model,
14 atoms: OrderedSet,
15}
16
17impl<'a> Unit<'a> {
18 pub fn from_mask(model: &'a Model, mask: &[bool]) -> Self {
26 assert_eq!(
27 mask.len(),
28 model.n_atoms(),
29 "mask length must equal number of atoms"
30 );
31 let indices: Vec<u32> = mask
32 .iter()
33 .enumerate()
34 .filter_map(|(i, &selected)| if selected { Some(i as u32) } else { None })
35 .collect();
36
37 let atoms = if indices.is_empty() {
38 OrderedSet::interval(0, 0)
39 } else {
40 OrderedSet::from_sorted(indices)
41 };
42
43 Self { model, atoms }
44 }
45
46 pub fn all(model: &'a Model) -> Self {
48 let n = model.n_atoms() as u32;
49 Self {
50 model,
51 atoms: OrderedSet::interval(0, n),
52 }
53 }
54
55 pub fn from_indices(model: &'a Model, indices: OrderedSet) -> Self {
57 Self {
58 model,
59 atoms: indices,
60 }
61 }
62
63 pub fn len(&self) -> usize {
65 self.atoms.len()
66 }
67
68 pub fn is_empty(&self) -> bool {
70 self.atoms.is_empty()
71 }
72
73 pub fn model(&self) -> &Model {
75 self.model
76 }
77
78 pub fn atom_indices(&self) -> impl Iterator<Item = u32> + '_ {
80 self.atoms.iter()
81 }
82
83 pub fn coords(&self) -> impl Iterator<Item = [f32; 3]> + '_ {
85 self.atoms.iter().map(|i| self.model.coord(i as usize))
86 }
87
88 pub fn union(&self, other: &Unit<'a>) -> Self {
93 assert!(
94 std::ptr::eq(self.model, other.model),
95 "cannot union Units from different Models"
96 );
97 Self {
98 model: self.model,
99 atoms: self.atoms.union(&other.atoms),
100 }
101 }
102
103 pub fn intersection(&self, other: &Unit<'a>) -> Self {
108 assert!(
109 std::ptr::eq(self.model, other.model),
110 "cannot intersect Units from different Models"
111 );
112 Self {
113 model: self.model,
114 atoms: self.atoms.intersection(&other.atoms),
115 }
116 }
117
118 pub fn difference(&self, other: &Unit<'a>) -> Self {
123 assert!(
124 std::ptr::eq(self.model, other.model),
125 "cannot difference Units from different Models"
126 );
127 Self {
128 model: self.model,
129 atoms: self.atoms.difference(&other.atoms),
130 }
131 }
132
133 pub fn chain(model: &'a Model, chain_id: &str) -> Option<Self> {
137 let chain_idx = model
138 .hierarchy
139 .chains
140 .label_asym_id
141 .iter()
142 .position(|c| c == chain_id)?;
143
144 let res_range = model.hierarchy.residue_to_chain.segment(chain_idx);
145 if res_range.is_empty() {
146 return Some(Self {
147 model,
148 atoms: OrderedSet::interval(0, 0),
149 });
150 }
151 let atom_start = model.hierarchy.atom_to_residue.segment(res_range.start).start;
152 let atom_end = model.hierarchy.atom_to_residue.segment(res_range.end - 1).end;
153
154 Some(Self {
155 model,
156 atoms: OrderedSet::interval(atom_start as u32, atom_end as u32),
157 })
158 }
159
160 pub fn backbone(model: &'a Model) -> Self {
162 let backbone_names = ["N", "CA", "C", "O"];
163 let indices: Vec<u32> = model
164 .hierarchy
165 .atoms
166 .atom_name
167 .iter()
168 .enumerate()
169 .filter_map(|(i, name)| {
170 if backbone_names.contains(&name.as_str()) {
171 Some(i as u32)
172 } else {
173 None
174 }
175 })
176 .collect();
177
178 let atoms = if indices.is_empty() {
179 OrderedSet::interval(0, 0)
180 } else {
181 OrderedSet::from_sorted(indices)
182 };
183
184 Self { model, atoms }
185 }
186}
187
188impl Model {
189 pub fn filter(&self, mask: &[bool]) -> Unit<'_> {
194 Unit::from_mask(self, mask)
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201 use crate::data::Segmentation;
202 use crate::model::bonds::Bonds;
203 use crate::model::conformation::AtomicConformation;
204 use crate::model::hierarchy::AtomicHierarchy;
205 use crate::model::tables::{AtomsTable, ChainsTable, ResidueGroup, ResiduesTable};
206 use std::sync::Arc;
207
208 fn make_test_model(n_atoms: usize) -> Model {
209 let atoms = AtomsTable {
210 atom_name: (0..n_atoms).map(|i| format!("A{}", i)).collect(),
211 element: vec!["C".into(); n_atoms],
212 alt_loc: vec![None; n_atoms],
213 formal_charge: vec![None; n_atoms],
214 };
215 let residues = ResiduesTable {
216 comp_id: vec!["ALA".into()],
217 label_seq_id: vec![0],
218 auth_seq_id: vec![1],
219 ins_code: vec![None],
220 group: vec![ResidueGroup::Polymer],
221 };
222 let chains = ChainsTable {
223 label_asym_id: vec!["A".into()],
224 auth_asym_id: vec!["A".into()],
225 entity_id: vec!["1".into()],
226 };
227 let atom_to_residue = Segmentation::from_offsets(vec![0, n_atoms as u32]);
228 let residue_to_chain = Segmentation::from_offsets(vec![0, 1]);
229 let bonds = Bonds::from_unsorted(vec![], vec![], vec![], n_atoms);
230
231 let hierarchy = Arc::new(AtomicHierarchy {
232 atoms,
233 residues,
234 chains,
235 atom_to_residue,
236 residue_to_chain,
237 bonds,
238 });
239
240 let conformation = AtomicConformation {
241 x: (0..n_atoms).map(|i| i as f32).collect(),
242 y: (0..n_atoms).map(|i| i as f32 * 10.0).collect(),
243 z: (0..n_atoms).map(|_| 0.0).collect(),
244 occupancy: None,
245 b_iso: None,
246 confidence: None,
247 };
248
249 Model::new(hierarchy, conformation)
250 }
251
252 fn make_backbone_model() -> Model {
253 let atom_names = vec![
254 "N".into(),
255 "CA".into(),
256 "C".into(),
257 "O".into(),
258 "CB".into(),
259 ];
260 let n_atoms = atom_names.len();
261 let atoms = AtomsTable {
262 atom_name: atom_names,
263 element: vec!["C".into(); n_atoms],
264 alt_loc: vec![None; n_atoms],
265 formal_charge: vec![None; n_atoms],
266 };
267 let residues = ResiduesTable {
268 comp_id: vec!["ALA".into()],
269 label_seq_id: vec![0],
270 auth_seq_id: vec![1],
271 ins_code: vec![None],
272 group: vec![ResidueGroup::Polymer],
273 };
274 let chains = ChainsTable {
275 label_asym_id: vec!["A".into()],
276 auth_asym_id: vec!["A".into()],
277 entity_id: vec!["1".into()],
278 };
279 let atom_to_residue = Segmentation::from_offsets(vec![0, n_atoms as u32]);
280 let residue_to_chain = Segmentation::from_offsets(vec![0, 1]);
281 let bonds = Bonds::from_unsorted(vec![], vec![], vec![], n_atoms);
282
283 let hierarchy = Arc::new(AtomicHierarchy {
284 atoms,
285 residues,
286 chains,
287 atom_to_residue,
288 residue_to_chain,
289 bonds,
290 });
291
292 let conformation = AtomicConformation {
293 x: vec![0.0, 1.0, 2.0, 3.0, 4.0],
294 y: vec![0.0, 0.0, 0.0, 0.0, 0.0],
295 z: vec![0.0, 0.0, 0.0, 0.0, 0.0],
296 occupancy: None,
297 b_iso: None,
298 confidence: None,
299 };
300
301 Model::new(hierarchy, conformation)
302 }
303
304 #[test]
305 fn test_unit_from_mask_no_copy() {
306 let model = make_test_model(10);
307 let mask: Vec<bool> = (0..10).map(|i| i % 2 == 0).collect();
308
309 let unit = Unit::from_mask(&model, &mask);
310
311 assert_eq!(unit.len(), 5);
312 assert!(std::ptr::eq(unit.model(), &model));
313
314 let indices: Vec<u32> = unit.atom_indices().collect();
315 assert_eq!(indices, vec![0, 2, 4, 6, 8]);
316 }
317
318 #[test]
319 fn test_unit_all() {
320 let model = make_test_model(5);
321 let unit = Unit::all(&model);
322
323 assert_eq!(unit.len(), 5);
324 let indices: Vec<u32> = unit.atom_indices().collect();
325 assert_eq!(indices, vec![0, 1, 2, 3, 4]);
326 }
327
328 #[test]
329 fn test_unit_iteration_lazy() {
330 let model = make_test_model(100);
331 let mask: Vec<bool> = (0..100).map(|i| i < 3).collect();
332
333 let unit = Unit::from_mask(&model, &mask);
334
335 let coords: Vec<[f32; 3]> = unit.coords().collect();
336 assert_eq!(coords.len(), 3);
337 assert_eq!(coords[0], [0.0, 0.0, 0.0]);
338 assert_eq!(coords[1], [1.0, 10.0, 0.0]);
339 assert_eq!(coords[2], [2.0, 20.0, 0.0]);
340 }
341
342 #[test]
343 fn test_unit_set_operations() {
344 let model = make_test_model(10);
345
346 let mask_a: Vec<bool> = (0..10).map(|i| i < 5).collect();
347 let mask_b: Vec<bool> = (0..10).map(|i| i >= 3 && i < 8).collect();
348
349 let unit_a = Unit::from_mask(&model, &mask_a);
350 let unit_b = Unit::from_mask(&model, &mask_b);
351
352 let union = unit_a.union(&unit_b);
353 let union_indices: Vec<u32> = union.atom_indices().collect();
354 assert_eq!(union_indices, vec![0, 1, 2, 3, 4, 5, 6, 7]);
355
356 let intersection = unit_a.intersection(&unit_b);
357 let int_indices: Vec<u32> = intersection.atom_indices().collect();
358 assert_eq!(int_indices, vec![3, 4]);
359
360 let difference = unit_a.difference(&unit_b);
361 let diff_indices: Vec<u32> = difference.atom_indices().collect();
362 assert_eq!(diff_indices, vec![0, 1, 2]);
363 }
364
365 #[test]
366 fn test_unit_empty() {
367 let model = make_test_model(5);
368 let mask = vec![false; 5];
369
370 let unit = Unit::from_mask(&model, &mask);
371 assert!(unit.is_empty());
372 assert_eq!(unit.len(), 0);
373 }
374
375 #[test]
376 fn test_model_filter() {
377 let model = make_test_model(5);
378 let mask = vec![true, false, true, false, true];
379
380 let unit = model.filter(&mask);
381 assert_eq!(unit.len(), 3);
382 }
383
384 #[test]
385 fn test_unit_chain() {
386 let model = make_test_model(5);
387
388 let chain_a = Unit::chain(&model, "A");
389 assert!(chain_a.is_some());
390 assert_eq!(chain_a.unwrap().len(), 5);
391
392 let chain_b = Unit::chain(&model, "B");
393 assert!(chain_b.is_none());
394 }
395
396 #[test]
397 fn test_unit_backbone() {
398 let model = make_backbone_model();
399
400 let backbone = Unit::backbone(&model);
401 assert_eq!(backbone.len(), 4);
402
403 let indices: Vec<u32> = backbone.atom_indices().collect();
404 assert_eq!(indices, vec![0, 1, 2, 3]);
405 }
406
407 #[test]
408 #[should_panic(expected = "mask length must equal number of atoms")]
409 fn test_unit_from_mask_wrong_length() {
410 let model = make_test_model(5);
411 let mask = vec![true, false, true];
412 Unit::from_mask(&model, &mask);
413 }
414}