use molframe_core::index::{AtomIndex, ChainIndex, EntityIndex, ResidueIndex};
use molframe_core::selection::AtomSelection;
use molframe_core::structure::{ResidueRef, Structure};
use std::collections::HashSet;
use std::ops::Range;
pub(crate) fn same_residue(
structure: &Structure,
universe: &AtomSelection,
selected: &AtomSelection,
) -> AtomSelection {
if selected.is_empty() {
return AtomSelection::Empty;
}
let residues = selected_residues(structure, selected);
if residues.is_empty() {
return AtomSelection::Empty;
}
select_residues(structure, universe, &residues)
}
pub(crate) fn same_chain(
structure: &Structure,
universe: &AtomSelection,
selected: &AtomSelection,
) -> AtomSelection {
if selected.is_empty() {
return AtomSelection::Empty;
}
let chains = selected_chains(structure, selected);
if chains.is_empty() {
return AtomSelection::Empty;
}
select_chains(structure, universe, &chains)
}
pub(crate) fn same_entity(
structure: &Structure,
universe: &AtomSelection,
selected: &AtomSelection,
) -> AtomSelection {
if selected.is_empty() {
return AtomSelection::Empty;
}
let entities = selected_entities(structure, selected);
if entities.is_empty() {
return AtomSelection::Empty;
}
select_entities(structure, universe, &entities)
}
fn selected_residues(structure: &Structure, selected: &AtomSelection) -> Vec<u32> {
let mut residues: Vec<u32> = Vec::new();
for atom in selected {
let Some(residue) = structure
.atom(AtomIndex::new(atom))
.and_then(molframe_core::structure::AtomRef::residue)
else {
continue;
};
let residue = residue.index().get();
if residues.last() != Some(&residue) {
residues.push(residue);
}
}
residues.sort_unstable();
residues.dedup();
residues
}
fn selected_chains(structure: &Structure, selected: &AtomSelection) -> HashSet<ChainIndex> {
let mut chains = HashSet::new();
for atom in selected {
let Some(residue) = structure
.atom(AtomIndex::new(atom))
.and_then(molframe_core::structure::AtomRef::residue)
else {
continue;
};
let Some(chain) = structure
.data()
.topology
.chains
.containing(residue.index().get())
else {
continue;
};
chains.insert(chain);
}
chains
}
fn selected_entities(structure: &Structure, selected: &AtomSelection) -> HashSet<EntityIndex> {
let mut entities = HashSet::new();
for atom in selected {
let Some(residue) = structure
.atom(AtomIndex::new(atom))
.and_then(molframe_core::structure::AtomRef::residue)
else {
continue;
};
let Some(chain_index) = structure
.data()
.topology
.chains
.containing(residue.index().get())
else {
continue;
};
let Some(chain) = structure.chain(chain_index) else {
continue;
};
let Some(entity) = chain.entity() else {
continue;
};
entities.insert(entity);
}
entities
}
fn select_residues(
structure: &Structure,
universe: &AtomSelection,
residues: &[u32],
) -> AtomSelection {
let mut positions = Vec::new();
let mut cached: Option<(Range<u32>, bool)> = None;
for &index in residues {
let inside = cached
.as_ref()
.is_some_and(|(span, _)| span.contains(&index));
if !inside {
let chain = structure.data().topology.chains.containing(index);
cached = chain.and_then(|chain| {
let span = structure.data().topology.chains.residues(chain)?;
let entity = structure.chain(chain)?.entity().is_some();
Some((span, entity))
});
}
let Some((_, true)) = cached else {
continue;
};
if let Some(residue) = structure.residue(ResidueIndex::new(index)) {
append_residue_atoms(residue, universe, &mut positions);
}
}
AtomSelection::from_sorted(positions)
}
fn select_chains(
structure: &Structure,
universe: &AtomSelection,
chains: &HashSet<ChainIndex>,
) -> AtomSelection {
let mut positions = Vec::new();
for chain in structure.data().chains() {
if chain.entity().is_none() || !chains.contains(&chain.index()) {
continue;
}
for residue in chain.residues() {
append_residue_atoms(residue, universe, &mut positions);
}
}
AtomSelection::from_sorted(positions)
}
fn select_entities(
structure: &Structure,
universe: &AtomSelection,
entities: &HashSet<EntityIndex>,
) -> AtomSelection {
let mut positions = Vec::new();
for chain in structure.data().chains() {
let Some(entity) = chain.entity() else {
continue;
};
if !entities.contains(&entity) {
continue;
}
for residue in chain.residues() {
append_residue_atoms(residue, universe, &mut positions);
}
}
AtomSelection::from_sorted(positions)
}
#[inline]
fn append_residue_atoms(
residue: ResidueRef<'_>,
universe: &AtomSelection,
positions: &mut Vec<u32>,
) {
positions.extend(
residue
.atoms()
.map(molframe_core::structure::AtomRef::index)
.map(molframe_core::index::AtomIndex::get)
.filter(|atom| universe.contains(*atom)),
);
}
#[cfg(test)]
#[path = "expand_tests.rs"]
mod tests;