1 // ******************************************************************************
2 //
3 // Title: Force Field X.
4 // Description: Force Field X - Software for Molecular Biophysics.
5 // Copyright: Copyright (c) Michael J. Schnieders 2001-2025.
6 //
7 // This file is part of Force Field X.
8 //
9 // Force Field X is free software; you can redistribute it and/or modify it
10 // under the terms of the GNU General Public License version 3 as published by
11 // the Free Software Foundation.
12 //
13 // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16 // details.
17 //
18 // You should have received a copy of the GNU General Public License along with
19 // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20 // Place, Suite 330, Boston, MA 02111-1307 USA
21 //
22 // Linking this library statically or dynamically with other modules is making a
23 // combined work based on this library. Thus, the terms and conditions of the
24 // GNU General Public License cover the whole combination.
25 //
26 // As a special exception, the copyright holders of this library give you
27 // permission to link this library with independent modules to produce an
28 // executable, regardless of the license terms of these independent modules, and
29 // to copy and distribute the resulting executable under terms of your choice,
30 // provided that you also meet, for each linked independent module, the terms
31 // and conditions of the license of that module. An independent module is a
32 // module which is not derived from or based on this library. If you modify this
33 // library, you may extend this exception to your version of the library, but
34 // you are not obligated to do so. If you do not wish to do so, delete this
35 // exception statement from your version.
36 //
37 // ******************************************************************************
38 package ffx.numerics.switching;
39
40 import static java.lang.String.format;
41 import static org.apache.commons.math3.util.FastMath.max;
42 import static org.apache.commons.math3.util.FastMath.min;
43 import static org.apache.commons.math3.util.FastMath.pow;
44
45 /**
46 * The 6 coefficients of the multiplicative polynomial switch are unique given the distances "a" and
47 * "b". They are found by solving a system of 6 equations, which define the boundary conditions of
48 * the switch. <br> f(a) = 1 <br> f'(a) = f"(a) = 0 <br> f(b) = f'(b) = f"(b) = 0
49 *
50 * @author Michael J. Schnieders
51 */
52 public class MultiplicativeSwitch implements UnivariateSwitchingFunction {
53
54 private final double b;
55 private final double a;
56 private final double c0;
57 private final double c1;
58 private final double c2;
59 private final double c3;
60 private final double c4;
61 private final double c5;
62 private final double twoC2;
63 private final double threeC3;
64 private final double fourC4;
65 private final double fiveC5;
66
67 /**
68 * Constructs a MultiplicativeSwitch that starts at f(0)=1 and ends at f(1)=0. The switch smoothly
69 * interpolates from 1 to 0 across that range, with zero first and second derivatives at off and
70 * cut.
71 */
72 public MultiplicativeSwitch() {
73 this(0.0, 1.0);
74 }
75
76 /**
77 * Constructs a MultiplicativeSwitch that starts at f(a)=1 and ends at f(b)=0. The switch smoothly
78 * interpolates from 1 to 0 across that range, with zero first and second derivatives at off and
79 * cut.
80 *
81 * @param a f(a)=1
82 * @param b f(b)=0
83 */
84 public MultiplicativeSwitch(double a, double b) {
85
86 // f(a) = 1.0
87 this.a = a;
88 // f(b) = 0.0
89 this.b = b;
90
91 double a2 = a * a;
92 double b2 = b * b;
93
94 double denominator = pow(b - a, 5.0);
95 c0 = b * b2 * (b2 - 5.0 * a * b + 10.0 * a2) / denominator;
96 c1 = -30.0 * a2 * b2 / denominator;
97 c2 = 30.0 * b * a * (b + a) / denominator;
98 c3 = -10.0 * (a2 + 4.0 * a * b + b2) / denominator;
99 c4 = 15.0 * (a + b) / denominator;
100 c5 = -6.0 / denominator;
101 twoC2 = 2.0 * c2;
102 threeC3 = 3.0 * c3;
103 fourC4 = 4.0 * c4;
104 fiveC5 = 5.0 * c5;
105 }
106
107 /** {@inheritDoc} */
108 @Override
109 public boolean constantOutsideBounds() {
110 return false;
111 }
112
113 /**
114 * First derivative of the switching function at r.
115 *
116 * @param r r
117 * @param r2 r^2
118 * @param r3 r^3
119 * @param r4 r^4
120 * @return First derivative of switch at r
121 */
122 public double dtaper(double r, double r2, double r3, double r4) {
123 return fiveC5 * r4 + fourC4 * r3 + threeC3 * r2 + twoC2 * r + c1;
124 }
125
126 /**
127 * First derivative of the switching function at r.
128 *
129 * @param r r
130 * @return First derivative of switch at r
131 */
132 public double dtaper(double r) {
133 // Minimize number of multiply operations by storing r^2.
134 double r2 = r * r;
135 return dtaper(r, r2, r2 * r, r2 * r2);
136 }
137
138 /** {@inheritDoc} */
139 @Override
140 public double firstDerivative(double x) {
141 return dtaper(x);
142 }
143
144 /** {@inheritDoc} */
145 @Override
146 public int getHighestOrderZeroDerivative() {
147 return 2;
148 }
149
150 /** {@inheritDoc} */
151 @Override
152 public double getOneBound() {
153 return max(a, b);
154 }
155
156 /**
157 * Get the value where the switch starts.
158 *
159 * @return Switch start.
160 */
161 public double getSwitchEnd() {
162 return b;
163 }
164
165 /**
166 * Get the value where the switch starts.
167 *
168 * @return Switch start.
169 */
170 public double getSwitchStart() {
171 return a;
172 }
173
174 /** {@inheritDoc} */
175 @Override
176 public double getZeroBound() {
177 return min(b, a);
178 }
179
180 /** {@inheritDoc} */
181 @Override
182 public double nthDerivative(double x, int order) throws IllegalArgumentException {
183 if (order < 1) {
184 throw new IllegalArgumentException("Order must be >= 1");
185 }
186 switch (order) {
187 case 1:
188 return dtaper(x);
189 case 2:
190 return secondDerivative(x);
191 case 3:
192 double val = 60.0 * c5 * x * x;
193 val += 24.0 * c4 * x;
194 val += 6.0 * c3;
195 return val;
196 case 4:
197 val = 120.0 * c5 * x;
198 val += 24.0 * c4;
199 return val;
200 case 5:
201 return 120.0 * c5;
202 default:
203 return 0;
204 }
205 }
206
207 /** {@inheritDoc} */
208 @Override
209 public double secondDerivative(double x) {
210 double x2 = x * x;
211 double val = 20.0 * c5 * x2 * x;
212 val += 12.0 * c4 * x2;
213 val += 6.0 * c3 * x;
214 val += 2.0 * c2;
215 return val;
216 }
217
218 /** {@inheritDoc} */
219 @Override
220 public boolean symmetricToUnity() {
221 return true;
222 }
223
224 /**
225 * Value of the switching function at r.
226 *
227 * @param r r
228 * @param r2 r^2
229 * @param r3 r^3
230 * @param r4 r^4
231 * @param r5 r^5
232 * @return Value of switch at r
233 */
234 public double taper(double r, double r2, double r3, double r4, double r5) {
235 return c5 * r5 + c4 * r4 + c3 * r3 + c2 * r2 + c1 * r + c0;
236 }
237
238 /**
239 * Value of the switching function at r.
240 *
241 * @param r r
242 * @return Value of switch at r
243 */
244 public double taper(double r) {
245 // Minimize number of multiply operations by storing r^2, r^3.
246 double r2 = r * r;
247 double r3 = r2 * r;
248 return taper(r, r2, r3, r2 * r2, r3 * r2);
249 }
250
251 /** {@inheritDoc} */
252 @Override
253 public String toString() {
254 return format(
255 "Multiplicative switch of form f(x) = %8.4g*x^5 + "
256 + "%8.4g*x^4 + %8.4g*x^3 + %8.4g*x^2 + %8.4g*x + %8.4g",
257 c5, c4, c3, c2, c1, c0);
258 }
259
260 /** {@inheritDoc} */
261 @Override
262 public boolean validOutsideBounds() {
263 return false;
264 }
265
266 /** {@inheritDoc} */
267 @Override
268 public double valueAt(double x) throws IllegalArgumentException {
269 return taper(x);
270 }
271 }