molframe-core 0.6.4

Core types, storage and analysis contracts for molframe
Documentation
//! The individual decisions a policy records.
//!
//! Each is a small closed set with a documented default. Naming them as types
//! rather than as strings is what lets a policy be compared, fingerprinted and
//! enumerated — an audit that varies one decision at a time needs to know what
//! the alternatives are.

use std::fmt;
use std::hash::{Hash, Hasher};

/// Which arrangement of the contents to analyse.
#[derive(Clone, PartialEq, Debug, Default)]
#[non_exhaustive]
pub enum AssemblyChoice {
    /// The contents of the file as deposited.
    #[default]
    AsymmetricUnit,
    /// A biological assembly the file defines, by its own identifier.
    Biological(Box<str>),
    /// Everything within a radius, generated by crystal symmetry.
    Crystal {
        /// Radius in ångström.
        radius: f32,
    },
}

impl Hash for AssemblyChoice {
    /// Hashes the radius by its bit pattern, because a float has no total
    /// ordering and a fingerprint needs one.
    fn hash<H: Hasher>(&self, state: &mut H) {
        match self {
            Self::AsymmetricUnit => 0u8.hash(state),
            Self::Biological(id) => {
                1u8.hash(state);
                id.hash(state);
            }
            Self::Crystal { radius } => {
                2u8.hash(state);
                radius.to_bits().hash(state);
            }
        }
    }
}

/// Which model to analyse.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
#[non_exhaustive]
pub enum ModelChoice {
    /// The first model.
    #[default]
    First,
    /// One model, by position.
    Index(u32),
    /// Every model, analysed separately.
    All,
    /// Every model, analysed as an ensemble.
    Ensemble,
}

/// How to resolve alternate conformations.
#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)]
#[non_exhaustive]
pub enum AltlocPolicy {
    /// Every alternate location stays visible. The right default for reading.
    KeepAll,
    /// One self-consistent label per region. The right default for analysis.
    #[default]
    ConformerConsistent,
    /// One named label.
    Label(Box<str>),
    /// The first label listed.
    First,
    /// The highest-occupancy conformation of each residue.
    HighestOccupancyPerResidue,
    /// The highest-occupancy atom, one atom at a time.
    ///
    /// This can take side-chain atoms from two different conformations of the
    /// same residue and produce a geometry that was never modelled. It is
    /// available, it is never a default, and choosing it is recorded.
    HighestOccupancyPerAtom,
}

impl AltlocPolicy {
    /// Returns true for the policy that can assemble a conformation nobody
    /// observed.
    #[must_use]
    pub const fn is_hazardous(&self) -> bool {
        matches!(self, Self::HighestOccupancyPerAtom)
    }
}

/// Which identifier namespace an unqualified selector means.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
#[non_exhaustive]
pub enum Namespace {
    /// The file's normalised identifiers.
    Label,
    /// The depositor's identifiers. What the literature uses.
    #[default]
    Auth,
    /// Neither: every selector must say which namespace it means.
    Explicit,
}

/// What to do about atoms a component should have but the model does not.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
#[non_exhaustive]
pub enum MissingPolicy {
    /// Compute over what is present and say nothing.
    Ignore,
    /// Compute over what is present and report the shortfall as coverage.
    #[default]
    Report,
    /// Decline to produce a number.
    Indeterminate,
    /// Fail.
    Fail,
}

/// How hydrogens enter a calculation.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
#[non_exhaustive]
pub enum HydrogenPolicy {
    /// Only hydrogens the file modelled.
    #[default]
    ExplicitOnly,
    /// Modelled hydrogens, plus inferred ones where they are missing.
    IncludeInferred,
    /// No hydrogens at all.
    Exclude,
}

/// How two atoms are decided to correspond.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
#[non_exhaustive]
pub enum EquivalencePolicy {
    /// By name alone.
    None,
    /// By the chemistry of the component, so that the interchangeable oxygens
    /// of a carboxylate are recognised as interchangeable.
    #[default]
    Ccd,
    /// By a correspondence the caller supplies.
    Explicit,
}

/// Whether symmetry copies participate.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
#[non_exhaustive]
pub enum SymmetryPolicy {
    /// No copies.
    #[default]
    None,
    /// Crystallographic copies.
    Crystallographic,
    /// Copies from the biological assembly.
    BiologicalAssembly,
}

/// How two structures are put into correspondence before comparison.
#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)]
#[non_exhaustive]
pub enum AlignmentPolicy {
    /// They already correspond.
    #[default]
    None,
    /// Over a selection the caller names.
    Explicit(Box<str>),
    /// Whole-length alignment.
    Global,
    /// Best local alignment.
    Local,
}

/// The width accumulation runs at.
///
/// This is the type sums are taken in, not the type positions are stored in.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
#[non_exhaustive]
pub enum Precision {
    /// Single precision.
    F32,
    /// Double precision.
    #[default]
    F64,
}

/// How periodic boundaries are handled.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
#[non_exhaustive]
pub enum PeriodicPolicy {
    /// Not periodic.
    #[default]
    None,
    /// Periodic, considering every relevant image.
    Pbc,
    /// Periodic, considering only the nearest image.
    MinimumImage,
}

/// Which published set of van der Waals radii to use.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
#[non_exhaustive]
pub enum RadiiSet {
    /// Bondi.
    #[default]
    Bondi,
    /// Amber, united-atom.
    AmberUnited,
    /// Charmm.
    Charmm,
    /// Alvarez.
    Alvarez,
}

/// What counts as a contact.
#[derive(Clone, Copy, PartialEq, Debug)]
#[non_exhaustive]
pub enum ContactDefinition {
    /// Within a distance, with a tolerance added to the radii.
    DistanceCutoff {
        /// Tolerance in ångström.
        tolerance: f32,
    },
    /// Sharing solvent-accessible surface.
    SurfaceBased {
        /// Probe radius in ångström.
        probe: f32,
    },
}

impl Default for ContactDefinition {
    fn default() -> Self {
        Self::DistanceCutoff { tolerance: 0.5 }
    }
}

/// How close two floating-point values must be to count as equal.
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Tolerance {
    /// Relative tolerance.
    pub relative: f64,
    /// Absolute tolerance.
    pub absolute: f64,
}

impl Default for Tolerance {
    fn default() -> Self {
        Self {
            relative: 1e-9,
            absolute: 1e-12,
        }
    }
}

/// The name and version of a policy profile.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct ProfileId(&'static str);

impl ProfileId {
    /// The profile [`AnalysisPolicy::default`](crate::AnalysisPolicy::default) returns.
    pub const DEFAULT: Self = Self("molframe-default-1.0");

    /// The profile's name.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        self.0
    }
}

impl fmt::Display for ProfileId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.0)
    }
}

/// Which field of a policy something refers to.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum PolicyField {
    /// Which arrangement of the contents.
    Assembly,
    /// Which model.
    Model,
    /// How alternate conformations resolve.
    Altloc,
    /// Which identifier namespace.
    Identifiers,
    /// What to do about atoms that were not modelled.
    MissingAtoms,
    /// How hydrogens enter.
    Hydrogens,
    /// How atoms are matched.
    AtomEquivalence,
    /// Whether symmetry copies participate.
    Symmetry,
    /// How structures are aligned.
    Alignment,
    /// Accumulation width.
    Precision,
    /// Periodic handling.
    Periodic,
    /// Which radii.
    VdwRadii,
    /// What counts as a contact.
    ContactDef,
    /// Floating-point tolerance.
    FloatTolerance,
}

impl PolicyField {
    /// Every field, for a caller enumerating the policy space.
    pub const ALL: [Self; 14] = [
        Self::Assembly,
        Self::Model,
        Self::Altloc,
        Self::Identifiers,
        Self::MissingAtoms,
        Self::Hydrogens,
        Self::AtomEquivalence,
        Self::Symmetry,
        Self::Alignment,
        Self::Precision,
        Self::Periodic,
        Self::VdwRadii,
        Self::ContactDef,
        Self::FloatTolerance,
    ];

    /// The field a written name refers to.
    #[must_use]
    pub fn from_name(name: &str) -> Option<Self> {
        Self::ALL.into_iter().find(|field| field.name() == name)
    }

    /// The field's name, as it appears in a written policy.
    #[must_use]
    pub const fn name(self) -> &'static str {
        match self {
            Self::Assembly => "assembly",
            Self::Model => "model",
            Self::Altloc => "altloc",
            Self::Identifiers => "identifiers",
            Self::MissingAtoms => "missing_atoms",
            Self::Hydrogens => "hydrogens",
            Self::AtomEquivalence => "atom_equivalence",
            Self::Symmetry => "symmetry",
            Self::Alignment => "alignment",
            Self::Precision => "precision",
            Self::Periodic => "periodic",
            Self::VdwRadii => "vdw_radii",
            Self::ContactDef => "contact_def",
            Self::FloatTolerance => "float_tolerance",
        }
    }
}