Skip to main content

molframe_core/contract/policy/
fields.rs

1//! The individual decisions a policy records.
2//!
3//! Each is a small closed set with a documented default. Naming them as types
4//! rather than as strings is what lets a policy be compared, fingerprinted and
5//! enumerated — an audit that varies one decision at a time needs to know what
6//! the alternatives are.
7
8use std::fmt;
9use std::hash::{Hash, Hasher};
10
11/// Which arrangement of the contents to analyse.
12#[derive(Clone, PartialEq, Debug, Default)]
13#[non_exhaustive]
14pub enum AssemblyChoice {
15    /// The contents of the file as deposited.
16    #[default]
17    AsymmetricUnit,
18    /// A biological assembly the file defines, by its own identifier.
19    Biological(Box<str>),
20    /// Everything within a radius, generated by crystal symmetry.
21    Crystal {
22        /// Radius in ångström.
23        radius: f32,
24    },
25}
26
27impl Hash for AssemblyChoice {
28    /// Hashes the radius by its bit pattern, because a float has no total
29    /// ordering and a fingerprint needs one.
30    fn hash<H: Hasher>(&self, state: &mut H) {
31        match self {
32            Self::AsymmetricUnit => 0u8.hash(state),
33            Self::Biological(id) => {
34                1u8.hash(state);
35                id.hash(state);
36            }
37            Self::Crystal { radius } => {
38                2u8.hash(state);
39                radius.to_bits().hash(state);
40            }
41        }
42    }
43}
44
45/// Which model to analyse.
46#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
47#[non_exhaustive]
48pub enum ModelChoice {
49    /// The first model.
50    #[default]
51    First,
52    /// One model, by position.
53    Index(u32),
54    /// Every model, analysed separately.
55    All,
56    /// Every model, analysed as an ensemble.
57    Ensemble,
58}
59
60/// How to resolve alternate conformations.
61#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)]
62#[non_exhaustive]
63pub enum AltlocPolicy {
64    /// Every alternate location stays visible. The right default for reading.
65    KeepAll,
66    /// One self-consistent label per region. The right default for analysis.
67    #[default]
68    ConformerConsistent,
69    /// One named label.
70    Label(Box<str>),
71    /// The first label listed.
72    First,
73    /// The highest-occupancy conformation of each residue.
74    HighestOccupancyPerResidue,
75    /// The highest-occupancy atom, one atom at a time.
76    ///
77    /// This can take side-chain atoms from two different conformations of the
78    /// same residue and produce a geometry that was never modelled. It is
79    /// available, it is never a default, and choosing it is recorded.
80    HighestOccupancyPerAtom,
81}
82
83impl AltlocPolicy {
84    /// Returns true for the policy that can assemble a conformation nobody
85    /// observed.
86    #[must_use]
87    pub const fn is_hazardous(&self) -> bool {
88        matches!(self, Self::HighestOccupancyPerAtom)
89    }
90}
91
92/// Which identifier namespace an unqualified selector means.
93#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
94#[non_exhaustive]
95pub enum Namespace {
96    /// The file's normalised identifiers.
97    Label,
98    /// The depositor's identifiers. What the literature uses.
99    #[default]
100    Auth,
101    /// Neither: every selector must say which namespace it means.
102    Explicit,
103}
104
105/// What to do about atoms a component should have but the model does not.
106#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
107#[non_exhaustive]
108pub enum MissingPolicy {
109    /// Compute over what is present and say nothing.
110    Ignore,
111    /// Compute over what is present and report the shortfall as coverage.
112    #[default]
113    Report,
114    /// Decline to produce a number.
115    Indeterminate,
116    /// Fail.
117    Fail,
118}
119
120/// How hydrogens enter a calculation.
121#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
122#[non_exhaustive]
123pub enum HydrogenPolicy {
124    /// Only hydrogens the file modelled.
125    #[default]
126    ExplicitOnly,
127    /// Modelled hydrogens, plus inferred ones where they are missing.
128    IncludeInferred,
129    /// No hydrogens at all.
130    Exclude,
131}
132
133/// How two atoms are decided to correspond.
134#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
135#[non_exhaustive]
136pub enum EquivalencePolicy {
137    /// By name alone.
138    None,
139    /// By the chemistry of the component, so that the interchangeable oxygens
140    /// of a carboxylate are recognised as interchangeable.
141    #[default]
142    Ccd,
143    /// By a correspondence the caller supplies.
144    Explicit,
145}
146
147/// Whether symmetry copies participate.
148#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
149#[non_exhaustive]
150pub enum SymmetryPolicy {
151    /// No copies.
152    #[default]
153    None,
154    /// Crystallographic copies.
155    Crystallographic,
156    /// Copies from the biological assembly.
157    BiologicalAssembly,
158}
159
160/// How two structures are put into correspondence before comparison.
161#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)]
162#[non_exhaustive]
163pub enum AlignmentPolicy {
164    /// They already correspond.
165    #[default]
166    None,
167    /// Over a selection the caller names.
168    Explicit(Box<str>),
169    /// Whole-length alignment.
170    Global,
171    /// Best local alignment.
172    Local,
173}
174
175/// The width accumulation runs at.
176///
177/// This is the type sums are taken in, not the type positions are stored in.
178#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
179#[non_exhaustive]
180pub enum Precision {
181    /// Single precision.
182    F32,
183    /// Double precision.
184    #[default]
185    F64,
186}
187
188/// How periodic boundaries are handled.
189#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
190#[non_exhaustive]
191pub enum PeriodicPolicy {
192    /// Not periodic.
193    #[default]
194    None,
195    /// Periodic, considering every relevant image.
196    Pbc,
197    /// Periodic, considering only the nearest image.
198    MinimumImage,
199}
200
201/// Which published set of van der Waals radii to use.
202#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
203#[non_exhaustive]
204pub enum RadiiSet {
205    /// Bondi.
206    #[default]
207    Bondi,
208    /// Amber, united-atom.
209    AmberUnited,
210    /// Charmm.
211    Charmm,
212    /// Alvarez.
213    Alvarez,
214}
215
216/// What counts as a contact.
217#[derive(Clone, Copy, PartialEq, Debug)]
218#[non_exhaustive]
219pub enum ContactDefinition {
220    /// Within a distance, with a tolerance added to the radii.
221    DistanceCutoff {
222        /// Tolerance in ångström.
223        tolerance: f32,
224    },
225    /// Sharing solvent-accessible surface.
226    SurfaceBased {
227        /// Probe radius in ångström.
228        probe: f32,
229    },
230}
231
232impl Default for ContactDefinition {
233    fn default() -> Self {
234        Self::DistanceCutoff { tolerance: 0.5 }
235    }
236}
237
238/// How close two floating-point values must be to count as equal.
239#[derive(Clone, Copy, PartialEq, Debug)]
240pub struct Tolerance {
241    /// Relative tolerance.
242    pub relative: f64,
243    /// Absolute tolerance.
244    pub absolute: f64,
245}
246
247impl Default for Tolerance {
248    fn default() -> Self {
249        Self {
250            relative: 1e-9,
251            absolute: 1e-12,
252        }
253    }
254}
255
256/// The name and version of a policy profile.
257#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
258pub struct ProfileId(&'static str);
259
260impl ProfileId {
261    /// The profile [`AnalysisPolicy::default`](crate::AnalysisPolicy::default) returns.
262    pub const DEFAULT: Self = Self("molframe-default-1.0");
263
264    /// The profile's name.
265    #[must_use]
266    pub const fn as_str(self) -> &'static str {
267        self.0
268    }
269}
270
271impl fmt::Display for ProfileId {
272    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273        f.write_str(self.0)
274    }
275}
276
277/// Which field of a policy something refers to.
278#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
279#[non_exhaustive]
280pub enum PolicyField {
281    /// Which arrangement of the contents.
282    Assembly,
283    /// Which model.
284    Model,
285    /// How alternate conformations resolve.
286    Altloc,
287    /// Which identifier namespace.
288    Identifiers,
289    /// What to do about atoms that were not modelled.
290    MissingAtoms,
291    /// How hydrogens enter.
292    Hydrogens,
293    /// How atoms are matched.
294    AtomEquivalence,
295    /// Whether symmetry copies participate.
296    Symmetry,
297    /// How structures are aligned.
298    Alignment,
299    /// Accumulation width.
300    Precision,
301    /// Periodic handling.
302    Periodic,
303    /// Which radii.
304    VdwRadii,
305    /// What counts as a contact.
306    ContactDef,
307    /// Floating-point tolerance.
308    FloatTolerance,
309}
310
311impl PolicyField {
312    /// Every field, for a caller enumerating the policy space.
313    pub const ALL: [Self; 14] = [
314        Self::Assembly,
315        Self::Model,
316        Self::Altloc,
317        Self::Identifiers,
318        Self::MissingAtoms,
319        Self::Hydrogens,
320        Self::AtomEquivalence,
321        Self::Symmetry,
322        Self::Alignment,
323        Self::Precision,
324        Self::Periodic,
325        Self::VdwRadii,
326        Self::ContactDef,
327        Self::FloatTolerance,
328    ];
329
330    /// The field a written name refers to.
331    #[must_use]
332    pub fn from_name(name: &str) -> Option<Self> {
333        Self::ALL.into_iter().find(|field| field.name() == name)
334    }
335
336    /// The field's name, as it appears in a written policy.
337    #[must_use]
338    pub const fn name(self) -> &'static str {
339        match self {
340            Self::Assembly => "assembly",
341            Self::Model => "model",
342            Self::Altloc => "altloc",
343            Self::Identifiers => "identifiers",
344            Self::MissingAtoms => "missing_atoms",
345            Self::Hydrogens => "hydrogens",
346            Self::AtomEquivalence => "atom_equivalence",
347            Self::Symmetry => "symmetry",
348            Self::Alignment => "alignment",
349            Self::Precision => "precision",
350            Self::Periodic => "periodic",
351            Self::VdwRadii => "vdw_radii",
352            Self::ContactDef => "contact_def",
353            Self::FloatTolerance => "float_tolerance",
354        }
355    }
356}