Skip to main content

molframe_core/structure/disorder/
compat.rs

1//! The one definition of which alternate-conformation labels coexist.
2//!
3//! Anything that pairs atoms (clash detection, contact search, geometry
4//! checks, bonding) must agree on this rule, or atoms from different
5//! conformers get paired as if they were present together.
6
7use crate::symbol::AltId;
8
9/// Whether two atoms with these alternate labels can be present together.
10///
11/// A blank label marks an atom present in every conformation, so it is
12/// compatible with any label, blank or not. Two non-blank labels are
13/// compatible only when they are the same label; different non-blank labels
14/// are different conformers of the same site and never coexist.
15///
16/// An atom without any recorded label should be passed as [`AltId::BLANK`].
17///
18/// Runs in `O(1)` time and allocates no memory.
19#[must_use]
20#[inline]
21pub fn altloc_compatible(left: AltId, right: AltId) -> bool {
22    left.is_blank() || right.is_blank() || left == right
23}
24
25/// The index pairs `(i, j)` with `i < j` whose labels are compatible.
26///
27/// Pairs come out in ascending `(i, j)` order, so results are deterministic.
28/// Runs in `O(n²)` time over the labels and allocates nothing; use it to
29/// filter candidate pairs when `n` is small (one residue, one contact cell),
30/// and [`altloc_compatible`] directly when pairs come from a spatial search.
31pub fn compatible_pairs(labels: &[AltId]) -> impl Iterator<Item = (usize, usize)> + '_ {
32    (0..labels.len()).flat_map(move |first| {
33        ((first + 1)..labels.len())
34            .filter(move |second| altloc_compatible(labels[first], labels[*second]))
35            .map(move |second| (first, second))
36    })
37}
38
39#[cfg(test)]
40#[path = "compat_tests.rs"]
41mod tests;