use super::AtomContext;
use molframe_core::chunk::AtomChunk;
use molframe_core::column::{Presence, ValidityMask};
use molframe_core::selection::AtomSelection;
use molframe_core::structure::{ChainRef, ResidueRef, Structure};
pub(super) struct SelectionCursor<I> {
iter: I,
next: Option<u32>,
}
impl<I> SelectionCursor<I>
where
I: Iterator<Item = u32>,
{
pub(super) fn new(mut iter: I) -> Self {
let next = iter.next();
Self { iter, next }
}
pub(super) fn is_exhausted(&self) -> bool {
self.next.is_none()
}
pub(super) fn skip_before(&mut self, minimum: u32) {
while self.next.is_some_and(|atom| atom < minimum) {
self.next = self.iter.next();
}
}
pub(super) fn matches(&mut self, atom: u32) -> bool {
self.skip_before(atom);
if self.next != Some(atom) {
return false;
}
self.next = self.iter.next();
true
}
pub(super) fn next_before(&mut self, end: u32) -> Option<u32> {
let atom = self.next?;
if atom >= end {
return None;
}
self.next = self.iter.next();
Some(atom)
}
}
pub(crate) fn scan(
structure: &Structure,
universe: &AtomSelection,
mut accepts: impl FnMut(AtomContext<'_>) -> bool,
) -> AtomSelection {
if universe.is_empty() {
return AtomSelection::Empty;
}
let mut selected = match usize::try_from(universe.len()) {
Ok(ceiling) => Vec::with_capacity(ceiling),
Err(_) => Vec::new(),
};
visit(structure, universe, |context| {
if accepts(context) {
selected.push(context.atom.index().get());
}
});
selection_from_sorted(selected)
}
pub(super) fn visit<'a>(
structure: &'a Structure,
universe: &AtomSelection,
mut visitor: impl FnMut(AtomContext<'a>),
) {
let mut universe = SelectionCursor::new(universe.iter());
if universe.is_exhausted() {
return;
}
for chain in structure.data().chains() {
for residue in chain.residues() {
for atom in residue.atoms() {
let index = atom.index().get();
if universe.matches(index) {
visitor(AtomContext {
atom,
residue,
chain,
});
if universe.is_exhausted() {
return;
}
}
}
}
}
}
pub(super) fn selection_from_sorted(selected: Vec<u32>) -> AtomSelection {
if selected.is_empty() {
AtomSelection::Empty
} else {
AtomSelection::from_sorted(selected)
}
}
pub(crate) fn scan_residues<'a>(
structure: &'a Structure,
universe: &AtomSelection,
mut accepts: impl FnMut(ChainRef<'a>, ResidueRef<'a>) -> bool,
) -> AtomSelection {
if universe.is_empty() {
return AtomSelection::Empty;
}
let residues = &structure.data().topology.residues;
let mut runs = Vec::new();
for chain in structure.data().chains() {
for residue in chain.residues() {
if accepts(chain, residue)
&& let Some(atoms) = residues.atoms(residue.index())
{
runs.push(atoms);
}
}
}
let selected = AtomSelection::from_runs(runs);
match universe {
AtomSelection::All(_) => selected,
_ => selected.intersect(universe),
}
}
pub(crate) fn scan_chunks(
structure: &Structure,
universe: &AtomSelection,
mut accepts: impl FnMut(&AtomChunk, u32) -> bool,
) -> AtomSelection {
if universe.is_empty() {
return AtomSelection::Empty;
}
let mut selected = match usize::try_from(universe.len()) {
Ok(ceiling) => Vec::with_capacity(ceiling),
Err(_) => Vec::new(),
};
let everything = matches!(universe, AtomSelection::All(_));
let mut cursor = SelectionCursor::new(universe.iter());
for chunk in structure.data().chunks.iter() {
let atoms = chunk.atoms();
for position in atoms.clone() {
if !everything && !cursor.matches(position) {
continue;
}
if accepts(chunk, position - atoms.start) {
selected.push(position);
}
}
if !everything && cursor.is_exhausted() {
break;
}
}
selection_from_sorted(selected)
}
pub(super) struct F32Column {
pub(super) plain: fn(&AtomChunk) -> Option<&[f32]>,
pub(super) validity: fn(&AtomChunk) -> &ValidityMask,
pub(super) get: fn(&AtomChunk, u32) -> Option<(f32, Presence)>,
}
pub(super) fn scan_f32(
structure: &Structure,
universe: &AtomSelection,
column: &F32Column,
test: impl Fn(f64) -> bool,
) -> AtomSelection {
let everything = matches!(universe, AtomSelection::All(_));
if !everything {
return scan_chunks(structure, universe, |chunk, local| {
(column.get)(chunk, local)
.is_some_and(|(value, presence)| presence.is_present() && test(f64::from(value)))
});
}
if universe.is_empty() {
return AtomSelection::Empty;
}
let mut selected = Vec::new();
for chunk in structure.data().chunks.iter() {
let atoms = chunk.atoms();
let plain = (column.plain)(chunk).filter(|values| {
(column.validity)(chunk).is_all_present()
&& values.len() as u64 == u64::from(atoms.end - atoms.start)
});
if let Some(values) = plain {
selected.extend(
values
.iter()
.zip(atoms.clone())
.filter(|(value, _)| test(f64::from(**value)))
.map(|(_, position)| position),
);
} else {
for position in atoms.clone() {
let hit =
(column.get)(chunk, position - atoms.start).is_some_and(|(value, presence)| {
presence.is_present() && test(f64::from(value))
});
if hit {
selected.push(position);
}
}
}
}
selection_from_sorted(selected)
}