ferritin_core/model/
hierarchy.rs1use std::ops::Range;
9use crate::data::Segmentation;
10use super::tables::{AtomsTable, ResiduesTable, ChainsTable};
11use super::bonds::Bonds;
12
13#[derive(Clone, Debug)]
29pub struct AtomicHierarchy {
30 pub atoms: AtomsTable,
32 pub residues: ResiduesTable,
34 pub chains: ChainsTable,
36 pub atom_to_residue: Segmentation,
41 pub residue_to_chain: Segmentation,
46 pub bonds: Bonds,
48}
49
50impl AtomicHierarchy {
51 pub fn n_atoms(&self) -> usize {
53 self.atoms.len()
54 }
55
56 pub fn n_residues(&self) -> usize {
58 self.residues.len()
59 }
60
61 pub fn n_chains(&self) -> usize {
63 self.chains.len()
64 }
65
66 pub fn atoms_in_residue(&self, res_idx: usize) -> Range<usize> {
71 self.atom_to_residue.segment(res_idx)
72 }
73
74 pub fn residues_in_chain(&self, chain_idx: usize) -> Range<usize> {
79 self.residue_to_chain.segment(chain_idx)
80 }
81
82 pub fn residue_of_atom(&self, atom_idx: usize) -> usize {
89 self.atom_to_residue.segment_of(atom_idx)
90 }
91
92 pub fn chain_of_residue(&self, res_idx: usize) -> usize {
99 self.residue_to_chain.segment_of(res_idx)
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use crate::model::tables::ResidueGroup;
107
108 fn make_test_hierarchy() -> AtomicHierarchy {
110 let n_residues = 10;
113 let n_atoms = 30; let _n_chains = 3;
115
116 let atoms = AtomsTable {
117 atom_name: (0..n_atoms).map(|i| match i % 3 { 0 => "N", 1 => "CA", _ => "C" }.to_string()).collect(),
118 element: (0..n_atoms).map(|i| match i % 3 { 0 => "N", _ => "C" }.to_string()).collect(),
119 alt_loc: vec![None; n_atoms],
120 formal_charge: vec![None; n_atoms],
121 };
122
123 let residues = ResiduesTable {
124 comp_id: (0..n_residues).map(|i| format!("RES{}", i)).collect(),
125 label_seq_id: (0..n_residues as i32).collect(),
126 auth_seq_id: (1..=n_residues as i32).collect(),
127 ins_code: vec![None; n_residues],
128 group: vec![ResidueGroup::Polymer; n_residues],
129 };
130
131 let chains = ChainsTable {
132 label_asym_id: vec!["A".into(), "B".into(), "C".into()],
133 auth_asym_id: vec!["A".into(), "B".into(), "C".into()],
134 entity_id: vec!["1".into(), "2".into(), "3".into()],
135 };
136
137 let atom_offsets: Vec<u32> = (0..=n_residues as u32).map(|i| i * 3).collect();
139 let atom_to_residue = Segmentation::from_offsets(atom_offsets);
140
141 let residue_offsets: Vec<u32> = vec![0, 4, 7, 10];
143 let residue_to_chain = Segmentation::from_offsets(residue_offsets);
144
145 let bonds = Bonds::from_unsorted(vec![], vec![], vec![], n_atoms);
146
147 AtomicHierarchy { atoms, residues, chains, atom_to_residue, residue_to_chain, bonds }
148 }
149
150 #[test]
151 fn test_hierarchy_basic() {
152 let h = make_test_hierarchy();
153
154 assert_eq!(h.n_atoms(), 30);
155 assert_eq!(h.n_residues(), 10);
156 assert_eq!(h.n_chains(), 3);
157
158 assert_eq!(h.atoms_in_residue(0), 0..3);
160 assert_eq!(h.atoms_in_residue(4), 12..15);
162 assert_eq!(h.atoms_in_residue(9), 27..30);
164
165 assert_eq!(h.residues_in_chain(0), 0..4);
167 assert_eq!(h.residues_in_chain(1), 4..7);
169 assert_eq!(h.residues_in_chain(2), 7..10);
171 }
172
173 #[test]
174 fn test_hierarchy_residue_of_atom() {
175 let h = make_test_hierarchy();
176
177 assert_eq!(h.residue_of_atom(0), 0);
179 assert_eq!(h.residue_of_atom(1), 0);
180 assert_eq!(h.residue_of_atom(2), 0);
181 assert_eq!(h.residue_of_atom(3), 1);
183 assert_eq!(h.residue_of_atom(5), 1);
184 assert_eq!(h.residue_of_atom(29), 9);
186 }
187
188 #[test]
189 fn test_hierarchy_chain_of_residue() {
190 let h = make_test_hierarchy();
191
192 for r in 0..4 {
194 assert_eq!(h.chain_of_residue(r), 0, "residue {} should be in chain 0", r);
195 }
196 for r in 4..7 {
198 assert_eq!(h.chain_of_residue(r), 1, "residue {} should be in chain 1", r);
199 }
200 for r in 7..10 {
202 assert_eq!(h.chain_of_residue(r), 2, "residue {} should be in chain 2", r);
203 }
204 }
205}