use super::{CoordinateStore, Structure, StructureData};
use crate::index::ChainIndex;
use crate::symbol::SymbolId;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
impl Structure {
#[must_use]
pub fn with_distinct_labels(&self) -> Option<Self> {
let mut data = self.0.as_ref().clone();
let mut changed = distinct_in(&mut data);
changed |= distinct_residues(&mut data);
if let CoordinateStore::Ragged { models } = &mut data.coords {
for model in models {
if let Some(distinct) = model.with_distinct_labels() {
*model = distinct;
changed = true;
}
}
}
changed.then(|| Self(Arc::new(data)))
}
}
fn distinct_in(data: &mut StructureData) -> bool {
let chains: Vec<(ChainIndex, SymbolId)> = data
.topology
.chains
.iter()
.filter_map(|chain| Some((chain, data.topology.chains.label_asym_id(chain)?)))
.collect();
let mut taken: HashSet<SymbolId> = chains.iter().map(|&(_, label)| label).collect();
let mut seen = HashSet::new();
let mut changed = false;
for (chain, label) in chains {
if seen.insert(label) {
continue;
}
let base = data
.dictionary
.resolve(label)
.map_or_else(String::new, str::to_owned);
let fresh = (2u32..100_000).find_map(|number| {
let symbol = data.dictionary.intern(&format!("{base}{number}")).ok()?;
taken.insert(symbol).then_some(symbol)
});
let Some(fresh) = fresh else { continue };
if data.topology.chains.relabel(chain, fresh).is_ok() {
seen.insert(fresh);
changed = true;
}
}
changed
}
fn distinct_residues(data: &mut StructureData) -> bool {
let mut renumbered = Vec::new();
for chain in data.chains() {
let numbers: Vec<_> = chain
.residues()
.filter_map(|residue| {
let code = residue.ins_code().map_or_else(String::new, str::to_owned);
Some((residue.index(), residue.label_seq_id()?, code))
})
.collect();
let mut first = HashMap::new();
let collides = numbers
.iter()
.any(|(_, number, code)| *first.entry(*number).or_insert(code) != code);
if !collides {
continue;
}
let mut shift = 0;
let mut previous: Option<(i32, &str)> = None;
for (residue, number, code) in &numbers {
if previous.is_some_and(|(n, c)| n == *number && c != code) {
shift += 1;
}
previous = Some((*number, code));
if shift > 0 {
renumbered.push((*residue, number.saturating_add(shift)));
}
}
}
let changed = !renumbered.is_empty();
for (residue, number) in renumbered {
let _ = data.topology.residues.set_label_seq_id(residue, number);
}
changed
}
#[cfg(test)]
#[path = "chain_labels_tests.rs"]
mod tests;