View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2024.
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 }