molframe_core/structure/
chain_labels.rs1use super::{CoordinateStore, Structure, StructureData};
11use crate::index::ChainIndex;
12use crate::symbol::SymbolId;
13use std::collections::{HashMap, HashSet};
14use std::sync::Arc;
15
16impl Structure {
17 #[must_use]
27 pub fn with_distinct_labels(&self) -> Option<Self> {
28 let mut data = self.0.as_ref().clone();
29 let mut changed = distinct_in(&mut data);
30 changed |= distinct_residues(&mut data);
31 if let CoordinateStore::Ragged { models } = &mut data.coords {
32 for model in models {
33 if let Some(distinct) = model.with_distinct_labels() {
34 *model = distinct;
35 changed = true;
36 }
37 }
38 }
39 changed.then(|| Self(Arc::new(data)))
40 }
41}
42
43fn distinct_in(data: &mut StructureData) -> bool {
44 let chains: Vec<(ChainIndex, SymbolId)> = data
45 .topology
46 .chains
47 .iter()
48 .filter_map(|chain| Some((chain, data.topology.chains.label_asym_id(chain)?)))
49 .collect();
50 let mut taken: HashSet<SymbolId> = chains.iter().map(|&(_, label)| label).collect();
51 let mut seen = HashSet::new();
52 let mut changed = false;
53 for (chain, label) in chains {
54 if seen.insert(label) {
55 continue;
56 }
57 let base = data
58 .dictionary
59 .resolve(label)
60 .map_or_else(String::new, str::to_owned);
61 let fresh = (2u32..100_000).find_map(|number| {
62 let symbol = data.dictionary.intern(&format!("{base}{number}")).ok()?;
63 taken.insert(symbol).then_some(symbol)
64 });
65 let Some(fresh) = fresh else { continue };
66 if data.topology.chains.relabel(chain, fresh).is_ok() {
67 seen.insert(fresh);
68 changed = true;
69 }
70 }
71 changed
72}
73
74fn distinct_residues(data: &mut StructureData) -> bool {
75 let mut renumbered = Vec::new();
76 for chain in data.chains() {
77 let numbers: Vec<_> = chain
78 .residues()
79 .filter_map(|residue| {
80 let code = residue.ins_code().map_or_else(String::new, str::to_owned);
81 Some((residue.index(), residue.label_seq_id()?, code))
82 })
83 .collect();
84 let mut first = HashMap::new();
87 let collides = numbers
88 .iter()
89 .any(|(_, number, code)| *first.entry(*number).or_insert(code) != code);
90 if !collides {
91 continue;
92 }
93 let mut shift = 0;
94 let mut previous: Option<(i32, &str)> = None;
95 for (residue, number, code) in &numbers {
96 if previous.is_some_and(|(n, c)| n == *number && c != code) {
97 shift += 1;
98 }
99 previous = Some((*number, code));
100 if shift > 0 {
101 renumbered.push((*residue, number.saturating_add(shift)));
102 }
103 }
104 }
105 let changed = !renumbered.is_empty();
106 for (residue, number) in renumbered {
107 let _ = data.topology.residues.set_label_seq_id(residue, number);
108 }
109 changed
110}
111
112#[cfg(test)]
113#[path = "chain_labels_tests.rs"]
114mod tests;