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.cos;
42 import static org.apache.commons.math3.util.FastMath.pow;
43 import static org.apache.commons.math3.util.FastMath.sin;
44
45 import java.util.function.DoubleUnaryOperator;
46
47 /**
48 * A SquaredTrigSwitch implements a 0-1 switch of form f(x) = sin^2(ax) or of form f(x) = cos^2(ax).
49 * Cosine implementation is achieved by phase-shifting a sine wave.
50 *
51 * @author Jacob M. Litman
52 * @author Michael J. Schnieders
53 */
54 public class SquaredTrigSwitch implements UnivariateSwitchingFunction {
55
56 private static final double PI_OVER_TWO = Math.PI * 0.5;
57 private final double multiplier;
58 private final double halfPeriod;
59 private final DoubleUnaryOperator xTransform;
60 private final boolean cosine;
61
62 /**
63 * Default constructor, creating a switch sin^2(pi*x/2), or cos^2 if flag set. The sine form will
64 * switch 0-1, and the cosine form switch 1-0.
65 *
66 * @param cosine Use a cos^2(pi*x/2) transform instead of sin^2(pi*x/2).
67 */
68 public SquaredTrigSwitch(boolean cosine) {
69 this(PI_OVER_TWO, cosine);
70 }
71
72 /**
73 * Constructor permitting a custom frequency "a" in the form sin^2(a*x) or cos^2(a*x). The sine
74 * form will switch 0-1, and the cosine form switch 1-0.
75 *
76 * @param coefficient Value of a
77 * @param cosine Use a cos^2(ax) transform instead of sin^2(ax).
78 */
79 public SquaredTrigSwitch(double coefficient, boolean cosine) {
80 multiplier = coefficient;
81 halfPeriod = PI_OVER_TWO / multiplier;
82 xTransform = cosine ? this::cosineTransform : this::sineTransform;
83 this.cosine = cosine;
84 }
85
86 /** {@inheritDoc} */
87 @Override
88 public boolean constantOutsideBounds() {
89 return false;
90 }
91
92 /** {@inheritDoc} */
93 @Override
94 public double firstDerivative(double x) throws IllegalArgumentException {
95 x = xTransform.applyAsDouble(x);
96 return 2.0 * sin(x) * cos(x) * multiplier;
97 }
98
99 /** {@inheritDoc} */
100 @Override
101 public int getHighestOrderZeroDerivative() {
102 return 1;
103 }
104
105 /** {@inheritDoc} */
106 @Override
107 public double getOneBound() {
108 return cosine ? 0 : halfPeriod;
109 }
110
111 /**
112 * Get the repeating period of this switch.
113 *
114 * @return Period
115 */
116 public double getPeriod() {
117 return 2.0 * halfPeriod;
118 }
119
120 /** {@inheritDoc} */
121 @Override
122 public double getZeroBound() {
123 return cosine ? halfPeriod : 0;
124 }
125
126 /**
127 * Return true if a cos^2(ax) transform, false if a sin^2(ax) transform.
128 *
129 * @return a boolean.
130 */
131 public boolean isCosine() {
132 return cosine;
133 }
134
135 /** {@inheritDoc} */
136 @Override
137 public double nthDerivative(double x, int order) throws IllegalArgumentException {
138 if (order < 1) {
139 throw new IllegalArgumentException("Order must be >= 1");
140 }
141 x = xTransform.applyAsDouble(x);
142 double sinVal = sin(x);
143 double cosVal = cos(x);
144 double multPow = pow(multiplier, order);
145
146 return switch (order % 4) {
147 case 0 -> pow(2.0, order - 1) * multPow * ((sinVal * sinVal) - (cosVal * cosVal));
148 case 1 -> pow(2.0, order) * multPow * sinVal * cosVal;
149 case 2 -> pow(2.0, order - 1) * multPow * ((cosVal * cosVal) - (sinVal * sinVal));
150 case 3 -> -1.0 * pow(2.0, order) * multPow * sinVal * cosVal;
151 default -> throw new ArithmeticException("A positive number modulo 4 was not 0-3");
152 };
153 }
154
155 /** {@inheritDoc} */
156 @Override
157 public double secondDerivative(double x) throws IllegalArgumentException {
158 double val = 2.0 * multiplier * multiplier;
159 x = xTransform.applyAsDouble(x);
160 double cosTerm = cos(x);
161 double sinTerm = sin(x);
162
163 return val * ((cosTerm * cosTerm) - (sinTerm * sinTerm));
164 }
165
166 /** {@inheritDoc} */
167 @Override
168 public boolean symmetricToUnity() {
169 return true;
170 }
171
172 /** {@inheritDoc} */
173 @Override
174 public String toString() {
175 if (cosine) {
176 return format("Cosine-squared switching function of form f(x) = cos^2(%8.4g * x)", multiplier);
177 } else {
178 return format("Sine-squared switching function of form f(x) = sin^2(%8.4g * x)", multiplier);
179 }
180 }
181
182 /** {@inheritDoc} */
183 @Override
184 public boolean validOutsideBounds() {
185 return true;
186 }
187
188 /** {@inheritDoc} */
189 @Override
190 public double valueAt(double x) throws IllegalArgumentException {
191 x = xTransform.applyAsDouble(x);
192 x = sin(x);
193 x *= x;
194 return x;
195 }
196
197 /**
198 * Operate on a*x.
199 *
200 * @param x Input
201 * @return Position along sine wave.
202 */
203 private double sineTransform(double x) {
204 return x * multiplier;
205 }
206
207 /**
208 * Operate on (pi/2) + a*x, period-shifting a sine wave into the desired cosine wave.
209 *
210 * @param x Input
211 * @return Position along cosine wave.
212 */
213 private double cosineTransform(double x) {
214 return PI_OVER_TWO + (x * multiplier);
215 }
216 }