molframe_core/symbol/altloc.rs
1//! Alternate-location labels.
2
3use super::interner::SymbolId;
4use std::fmt;
5
6/// An alternate-location label.
7///
8/// The blank label means "this atom belongs to every conformation" — a residue
9/// routinely mixes blank-labelled backbone atoms with labelled side-chain ones,
10/// which is exactly why picking the highest-occupancy atom one at a time can
11/// assemble a conformation that was never modelled.
12///
13/// A label of any length is representable, because refusing to truncate applies
14/// to labels as much as to chain identifiers.
15///
16/// # Examples
17///
18/// ```
19/// use molframe_core::{AltId, Interner};
20///
21/// let mut interner = Interner::new();
22/// let a = AltId::labelled(interner.intern("A")?);
23/// assert!(a.is_some_and(|label| !label.is_blank()));
24/// assert!(AltId::BLANK.is_blank());
25/// # Ok::<(), molframe_core::DictionaryFull>(())
26/// ```
27#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
28#[repr(transparent)]
29pub struct AltId(u32);
30
31impl AltId {
32 /// The label carried by an atom present in every conformation.
33 pub const BLANK: Self = Self(0);
34
35 /// Creates a label from an interned string.
36 ///
37 /// Identifiers emitted by [`crate::Interner`] are guaranteed to fit this
38 /// offset encoding without overflowing.
39 ///
40 /// Runs in `O(1)` time and allocates no memory.
41 #[must_use]
42 #[inline]
43 pub const fn labelled(symbol: SymbolId) -> Option<Self> {
44 match symbol.get().checked_add(1) {
45 Some(raw) => Some(Self(raw)),
46 None => None,
47 }
48 }
49
50 /// Returns true for the blank label.
51 ///
52 /// Runs in `O(1)` time and allocates no memory.
53 #[must_use]
54 #[inline]
55 pub const fn is_blank(self) -> bool {
56 self.0 == 0
57 }
58
59 /// The interned label, or `None` when blank.
60 ///
61 /// Runs in `O(1)` time and allocates no memory.
62 #[must_use]
63 #[inline]
64 pub const fn symbol(self) -> Option<SymbolId> {
65 match self.0 {
66 0 => None,
67 raw => Some(SymbolId::from_raw(raw - 1)),
68 }
69 }
70
71 /// The raw value, for storage in a column.
72 ///
73 /// Runs in `O(1)` time and allocates no memory.
74 #[must_use]
75 #[inline]
76 pub const fn get(self) -> u32 {
77 self.0
78 }
79
80 /// Rebuilds a label from a stored value.
81 ///
82 /// Runs in `O(1)` time and allocates no memory.
83 #[must_use]
84 #[inline]
85 pub const fn from_raw(raw: u32) -> Self {
86 Self(raw)
87 }
88}
89
90impl fmt::Debug for AltId {
91 /// Formats the blank label or its underlying symbol.
92 ///
93 /// Runs in `O(1)` structural work plus the cost of formatting the symbol,
94 /// and allocates no intermediate heap storage.
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 match self.symbol() {
97 Some(symbol) => write!(f, "AltId({symbol:?})"),
98 None => f.write_str("AltId(blank)"),
99 }
100 }
101}
102
103#[cfg(test)]
104#[path = "altloc_tests.rs"]
105mod tests;