Skip to main content

molpack/restraint/
mod.rs

1//! `AtomRestraint` trait and concrete soft-penalty types for molecular packing.
2//!
3//! Each `*Restraint` struct is a **concrete, independent type** holding its own
4//! geometric parameters — no `Builtin*` wrapper, no tagged-union `{kind, params[9]}`
5//! blob, no builder pattern. User extensions `impl AtomRestraint` identically and sit
6//! beside the 14 Packmol-originals in type space.
7//!
8//! Numerical equivalence to the Fortran `comprest.f90` (value) and `gwalls.f90`
9//! (gradient) is preserved branch-for-branch; see `docs/packmol_parity.md`.
10//!
11//! **Gradient convention**: `AtomRestraint::fg` accumulates INTO `g` with `+=`.
12//! Do not overwrite; many restraints may contribute to the same atom.
13//!
14//! **Two-scale contract** (Packmol convention): linear penalties
15//! (box / cube / plane, kinds 2/3/6/7/10/11) use `scale`; quadratic penalties
16//! (sphere / ellipsoid / cylinder / gaussian, kinds 4/5/8/9/12/13/14/15) use
17//! `scale2`. Each `impl AtomRestraint` decides internally which to consume.
18//!
19//! Direction-3 rule (see spec §0 bullet 9): all molrs-pack extension points
20//! follow `pub trait X` + N concrete pub structs that `impl X`; user-defined
21//! structs `impl X` the same way. No `Builtin*` prefix, no wrapper, no builder.
22
23use molrs::types::F;
24
25// ============================================================================
26// Trait
27// ============================================================================
28
29/// Soft-penalty restraint evaluated per atom during packing.
30///
31/// - `f` — value only (line-search interpolation)
32/// - `fg` — fused value + gradient; gradient accumulates INTO `g` with `+=`
33/// - `is_parallel_safe` — if `false`, scheduler serializes this restraint
34///   (Python-backed restraints MUST return `false`)
35/// - `name` — human-readable identifier (default: `std::any::type_name::<Self>()`)
36/// - `periodic_box` — opt-in: a restraint may declare that it defines a
37///   periodic axis-aligned box (`min`, `max`, `periodic[k]` per axis).
38///   At most one periodic box may be declared across all restraints on a
39///   packing run; the packer resolves multiple declarations by requiring
40///   identical bounds. Default `None` (non-periodic); only
41///   [`InsideBoxRestraint`] overrides it.
42///
43/// `Debug` is required on concrete impls so `Target` / `Molpack` remain
44/// printable for diagnostics. All built-in restraints derive it; user types
45/// should do the same.
46pub trait AtomRestraint: Send + Sync + std::fmt::Debug {
47    fn f(&self, x: &[F; 3], scale: F, scale2: F) -> F;
48    fn fg(&self, x: &[F; 3], scale: F, scale2: F, g: &mut [F; 3]) -> F;
49    fn is_parallel_safe(&self) -> bool {
50        true
51    }
52    fn name(&self) -> &'static str {
53        std::any::type_name::<Self>()
54    }
55    /// Declare that this restraint defines a periodic box for the
56    /// pair-kernel minimum-image wrap. Return `Some((min, max, periodic))`
57    /// where `periodic[k] == true` marks axis `k` as wrapping. Default
58    /// `None` means this restraint does not imply PBC.
59    fn periodic_box(&self) -> Option<([F; 3], [F; 3], [bool; 3])> {
60        None
61    }
62}
63
64/// Blanket impl so `Box<dyn AtomRestraint>` itself implements the trait.
65impl AtomRestraint for Box<dyn AtomRestraint> {
66    #[inline]
67    fn f(&self, x: &[F; 3], scale: F, scale2: F) -> F {
68        (**self).f(x, scale, scale2)
69    }
70    #[inline]
71    fn fg(&self, x: &[F; 3], scale: F, scale2: F, g: &mut [F; 3]) -> F {
72        (**self).fg(x, scale, scale2, g)
73    }
74    #[inline]
75    fn is_parallel_safe(&self) -> bool {
76        (**self).is_parallel_safe()
77    }
78    #[inline]
79    fn name(&self) -> &'static str {
80        (**self).name()
81    }
82    #[inline]
83    fn periodic_box(&self) -> Option<([F; 3], [F; 3], [bool; 3])> {
84        (**self).periodic_box()
85    }
86}
87
88mod collective;
89mod geometric;
90
91pub use collective::{
92    ExponentialPlane, ExponentialPoint, GaussianPlane, GaussianPoint, Restraint, TabulatedPlane,
93    TabulatedPoint,
94};
95pub use geometric::{
96    AboveGaussianRestraint, AbovePlaneRestraint, BelowGaussianRestraint, BelowPlaneRestraint,
97    InsideBoxRestraint, InsideCubeRestraint, InsideCylinderRestraint, InsideEllipsoidRestraint,
98    InsideSphereRestraint, OutsideBoxRestraint, OutsideCubeRestraint, OutsideCylinderRestraint,
99    OutsideEllipsoidRestraint, OutsideSphereRestraint,
100};