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's name, as it appears in a written policy.
331 #[must_use]
332 pub const fn name(self) -> &'static str {
333 match self {
334 Self::Assembly => "assembly",
335 Self::Model => "model",
336 Self::Altloc => "altloc",
337 Self::Identifiers => "identifiers",
338 Self::MissingAtoms => "missing_atoms",
339 Self::Hydrogens => "hydrogens",
340 Self::AtomEquivalence => "atom_equivalence",
341 Self::Symmetry => "symmetry",
342 Self::Alignment => "alignment",
343 Self::Precision => "precision",
344 Self::Periodic => "periodic",
345 Self::VdwRadii => "vdw_radii",
346 Self::ContactDef => "contact_def",
347 Self::FloatTolerance => "float_tolerance",
348 }
349 }
350}