1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//! Collective (group-level) restraints — the **geometric distribution-matching**
//! family.
//!
//! Where a per-atom [`AtomRestraint`](crate::restraint::AtomRestraint) sees one atom at a
//! time and contributes an independent external field `∑ᵢ U(xᵢ)`, a
//! [`Restraint`] sees *every* copy of a species at once and returns a
//! single penalty whose gradient is **coupled across the whole group**. That
//! coupling is what lets a species *follow* a target spatial distribution: a
//! per-atom field built from a target density is minimised by collapsing every
//! atom onto the density's mode, whereas a distribution-distance penalty is
//! minimised when the empirical distribution *equals* the target.
//!
//! # Structure of this family
//!
//! Every member matches a target **distribution** of a scalar reaction
//! coordinate ξ defined by a **geometry**, via the squared 1-D Wasserstein
//! (sorted-CDF) metric ([`engine`]). The two axes are orthogonal:
//!
//! - **geometry** ([`geometry`]) — maps Cartesian coordinates to ξ and scatters
//! `∂L/∂ξ` back onto them: `plane` (ξ = signed distance to a plane → a slab),
//! `point` (ξ = distance to a centre → a spherical shell), …
//! - **distribution** — the target quantile function `q(p) = F⁻¹(p)`: Gaussian,
//! exponential, …
//!
//! Concrete types are the cross product, named `<Distribution><Geometry>`
//! and implementing [`Restraint`] directly (no wrapper, no builder —
//! same direction-3 convention as the per-atom restraints):
//! [`GaussianPlane`], [`GaussianPoint`], … Adding a distribution is a new
//! quantile function; adding a geometry is a new ξ/scatter pair; a new concrete
//! type then composes the two through the shared [`engine`].
//!
//! **Gradient convention** mirrors [`AtomRestraint`](crate::restraint::AtomRestraint):
//! `fg` accumulates `∂L/∂coords[i]` INTO `grads[i]` with `+=`. `coords` and
//! `grads` have equal length (one entry per atom in the group, same order).
use F;
// ============================================================================
// Trait
// ============================================================================
/// Group-level penalty over all copies of one species.
///
/// Unlike [`AtomRestraint`](crate::restraint::AtomRestraint), which is evaluated once
/// per atom with only that atom's coordinate, a `Restraint` is
/// evaluated once per group with the coordinates of *all* copies. Its gradient
/// may therefore couple every particle to every other — exactly what a
/// distribution-matching penalty needs.
pub use ;
pub use ;
pub use ;
/// Shared test helpers for the concrete `<Distribution><Geometry>` types:
/// a dependency-free RNG and a finite-difference gradient check.
pub