@@ -99,7 +99,8 @@ impl<T: Real> PairKernel<T> for LennardJones {
9999/// - `a`: The repulsion pre-factor $A = \frac{6 D_0}{\zeta-6} e^{\zeta}$.
100100/// - `b`: The repulsion decay constant $B = \zeta / R_0$.
101101/// - `c`: The attraction pre-factor $C = \frac{\zeta D_0 R_0^6}{\zeta-6}$.
102- /// - `r_fusion_sq`: The squared distance threshold for regularization.
102+ /// - `r_max_sq`: The squared distance of the local energy maximum $r_{\text{max}}^2$.
103+ /// - `two_e_max`: Twice the energy at the local maximum, $2 E(r_{\text{max}})$.
103104///
104105/// # Inputs
105106///
@@ -108,105 +109,90 @@ impl<T: Real> PairKernel<T> for LennardJones {
108109/// # Implementation Notes
109110///
110111/// - The kernel operates on the computationally efficient $A, B, C$ form.
111- /// - A branchless regularization is applied for $r^2 < r_{fusion}^2$ using a mathematical mask
112- /// to prevent energy collapse at very short distances, ensuring numerical stability.
112+ /// - For $r < r_{\text{max}}$, the energy is reflected about the local maximum:
113+ /// $E_{\text{ref}}(r) = 2 E_{\text{max}} - E(r)$. This produces a repulsive wall
114+ /// that diverges to $+\infty$ as $r \to 0$ via the $C/r^6$ attraction term,
115+ /// while maintaining $C^1$ continuity at $r_{\text{max}}$ (where $E'(r_{\text{max}}) = 0$).
116+ /// - A branchless sign-flip replaces the traditional constant penalty, providing
117+ /// physically correct short-range behavior at zero additional runtime cost.
113118/// - Requires one `sqrt` and one `exp` call, making it computationally more demanding than LJ.
114119/// - Power chain `inv_r2 -> inv_r6 -> inv_r8` is used for the attractive term.
115120#[ derive( Clone , Copy , Debug , Default ) ]
116121pub struct Buckingham ;
117122
118123impl < T : Real > PairKernel < T > for Buckingham {
119- type Params = ( T , T , T , T ) ;
124+ type Params = ( T , T , T , T , T ) ;
120125
121126 /// Computes only the potential energy.
122127 ///
123128 /// # Formula
124129 ///
125- /// $$ E = A e^{-B r} - C / r^6 $$
130+ /// $$ E(r) = \begin{cases} A e^{-Br} - C/r^6 & r \ge r_{\text{max}} \\ 2 E_{\text{max}} - (A e^{-Br} - C/ r^6) & r < r_{\text{max}} \end{cases} $$
126131 #[ inline( always) ]
127- fn energy ( r_sq : T , ( a, b, c, r_fusion_sq ) : Self :: Params ) -> T {
128- let is_safe = T :: from ( ( r_sq >= r_fusion_sq ) as u8 as f32 ) ;
129- let effective_r_sq = r_sq . max ( r_fusion_sq ) ;
132+ fn energy ( r_sq : T , ( a, b, c, r_max_sq , two_e_max ) : Self :: Params ) -> T {
133+ let mask = T :: from ( ( r_sq >= r_max_sq ) as u8 as f32 ) ;
134+ let sign = mask + mask - T :: from ( 1.0 ) ;
130135
131- let r = effective_r_sq . sqrt ( ) ;
132- let inv_r2 = effective_r_sq . recip ( ) ;
136+ let r = r_sq . sqrt ( ) ;
137+ let inv_r2 = r_sq . recip ( ) ;
133138 let inv_r6 = inv_r2 * inv_r2 * inv_r2;
134139
135- let repulsion = a * T :: exp ( -b * r) ;
136- let attraction = c * inv_r6;
137- let energy_unsafe = repulsion - attraction;
138-
139- const FUSION_ENERGY_PENALTY : f32 = 1.0e6 ;
140- let penalty = T :: from ( FUSION_ENERGY_PENALTY ) ;
140+ let e_buck = a * T :: exp ( -b * r) - c * inv_r6;
141141
142- energy_unsafe * is_safe + penalty * ( T :: from ( 1.0 ) - is_safe )
142+ sign * e_buck + ( T :: from ( 1.0 ) - mask ) * two_e_max
143143 }
144144
145145 /// Computes only the force pre-factor $D$.
146146 ///
147147 /// # Formula
148148 ///
149- /// $$ D = \frac{A B e^{-B r}}{r} - \frac{6 C}{r^8} $$
149+ /// $$ D(r) = \text{sign}(r) \left( \frac{A B e^{-Br}}{r} - \frac{6C}{r^8} \right) $$
150+ ///
151+ /// where $\text{sign}(r) = +1$ for $r \ge r_{\text{max}}$ and $-1$ otherwise.
152+ /// At the maximum, $D(r_{\text{max}}) = 0$ from both sides, ensuring $C^1$ continuity.
150153 ///
151154 /// This factor is defined such that the force vector can be computed
152155 /// by a single vector multiplication: $\vec{F} = -D \cdot \vec{r}$.
153156 #[ inline( always) ]
154- fn diff ( r_sq : T , ( a, b, c, r_fusion_sq ) : Self :: Params ) -> T {
155- let is_safe = T :: from ( ( r_sq >= r_fusion_sq ) as u8 as f32 ) ;
156- let effective_r_sq = r_sq . max ( r_fusion_sq ) ;
157+ fn diff ( r_sq : T , ( a, b, c, r_max_sq , _two_e_max ) : Self :: Params ) -> T {
158+ let mask = T :: from ( ( r_sq >= r_max_sq ) as u8 as f32 ) ;
159+ let sign = mask + mask - T :: from ( 1.0 ) ;
157160
158- let inv_r = effective_r_sq . rsqrt ( ) ;
159- let r = effective_r_sq * inv_r;
161+ let inv_r = r_sq . rsqrt ( ) ;
162+ let r = r_sq * inv_r;
160163 let inv_r2 = inv_r * inv_r;
161164 let inv_r4 = inv_r2 * inv_r2;
162165 let inv_r8 = inv_r4 * inv_r4;
163166
164167 let exp_term = T :: exp ( -b * r) ;
168+ let d_buck = a * b * exp_term * inv_r - T :: from ( 6.0 ) * c * inv_r8;
165169
166- let repulsion_factor = a * b * exp_term * inv_r;
167- let attraction_factor = T :: from ( 6.0 ) * c * inv_r8;
168- let diff_unsafe = repulsion_factor - attraction_factor;
169-
170- const FUSION_FORCE_PENALTY : f32 = 1.0e6 ;
171- let penalty = T :: from ( FUSION_FORCE_PENALTY ) ;
172-
173- diff_unsafe * is_safe + penalty * ( T :: from ( 1.0 ) - is_safe)
170+ sign * d_buck
174171 }
175172
176173 /// Computes both energy and force pre-factor efficiently.
177174 ///
178175 /// This method reuses intermediate calculations to minimize operations.
179176 #[ inline( always) ]
180- fn compute ( r_sq : T , ( a, b, c, r_fusion_sq ) : Self :: Params ) -> EnergyDiff < T > {
181- let is_safe = T :: from ( ( r_sq >= r_fusion_sq ) as u8 as f32 ) ;
182- let effective_r_sq = r_sq . max ( r_fusion_sq ) ;
177+ fn compute ( r_sq : T , ( a, b, c, r_max_sq , two_e_max ) : Self :: Params ) -> EnergyDiff < T > {
178+ let mask = T :: from ( ( r_sq >= r_max_sq ) as u8 as f32 ) ;
179+ let sign = mask + mask - T :: from ( 1.0 ) ;
183180
184- let inv_r = effective_r_sq . rsqrt ( ) ;
185- let r = effective_r_sq * inv_r;
181+ let inv_r = r_sq . rsqrt ( ) ;
182+ let r = r_sq * inv_r;
186183 let inv_r2 = inv_r * inv_r;
187184 let inv_r4 = inv_r2 * inv_r2;
188185 let inv_r6 = inv_r4 * inv_r2;
189186 let inv_r8 = inv_r6 * inv_r2;
190187
191188 let exp_term = T :: exp ( -b * r) ;
192189
193- let repulsion_energy = a * exp_term;
194- let attraction_energy = c * inv_r6;
195- let energy_unsafe = repulsion_energy - attraction_energy;
196-
197- let repulsion_force = repulsion_energy * b * inv_r;
198- let attraction_force = T :: from ( 6.0 ) * c * inv_r8;
199- let diff_unsafe = repulsion_force - attraction_force;
200-
201- const FUSION_ENERGY_PENALTY : f32 = 1.0e6 ;
202- const FUSION_FORCE_PENALTY : f32 = 1.0e6 ;
203-
204- let energy_penalty = T :: from ( FUSION_ENERGY_PENALTY ) ;
205- let force_penalty = T :: from ( FUSION_FORCE_PENALTY ) ;
190+ let e_buck = a * exp_term - c * inv_r6;
191+ let d_buck = a * b * exp_term * inv_r - T :: from ( 6.0 ) * c * inv_r8;
206192
207193 EnergyDiff {
208- energy : energy_unsafe * is_safe + energy_penalty * ( T :: from ( 1.0 ) - is_safe ) ,
209- diff : diff_unsafe * is_safe + force_penalty * ( T :: from ( 1.0 ) - is_safe ) ,
194+ energy : sign * e_buck + ( T :: from ( 1.0 ) - mask ) * two_e_max ,
195+ diff : sign * d_buck ,
210196 }
211197 }
212198}
0 commit comments