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.potential.utils;
39  
40  import static java.lang.Math.ulp;
41  import static org.junit.Assert.assertEquals;
42  import static org.junit.Assert.assertFalse;
43  import static org.junit.Assert.assertTrue;
44  
45  import ffx.numerics.switching.MultiplicativeSwitch;
46  import ffx.numerics.switching.PowerSwitch;
47  import ffx.numerics.switching.SquaredTrigSwitch;
48  import ffx.numerics.switching.UnivariateSwitchingFunction;
49  import java.util.logging.Logger;
50  
51  import ffx.utilities.FFXTest;
52  import org.apache.commons.math3.util.FastMath;
53  import org.junit.Test;
54  
55  /**
56   * Test the various switching functions.
57   *
58   * @author Michael J. Schnieders
59   * @author Jacob M. Litman
60   * @since 1.0
61   */
62  public class SwitchFunctionTest extends FFXTest {
63    private static final Logger logger = Logger.getLogger(SwitchFunctionTest.class.getName());
64  
65    // A set of standard multiples of ulp(0) and ulp(1)
66    private static final double ULP_ONE_2 = 2.0 * Math.ulp(1.0);
67    private static final double ULP_ONE_10 = 5.0 * ULP_ONE_2;
68    private static final double ULP_ONE_100 = 50.0 * ULP_ONE_2;
69  
70    private static final double ULP_ZERO_2 = 2.0 * Math.ulp(0.0);
71    private static final double ULP_ZERO_10 = 5.0 * ULP_ZERO_2;
72    private static final double ULP_ZERO_100 = 50.0 * ULP_ZERO_2;
73  
74    private static final double LOOSE_TOLERANCE = 0.000001;
75    private static final double MID_TOLERANCE = 1.0E-10;
76  
77    /** Tests multiplicative switch functions. */
78    @Test
79    public void multSwitchTest() {
80      logger.info(" Testing multiplicative switch functionality");
81      MultiplicativeSwitch sf = new MultiplicativeSwitch();
82      standardTest(sf);
83  
84      assertEquals(
85          "Default multiplicative switch zero bound != 0.0", 0.0, sf.getZeroBound(), ULP_ZERO_2);
86      assertEquals("Default power-switch one bound != 1.0", 1.0, sf.getOneBound(), ULP_ONE_2);
87      assertFalse("Power switches are not constant outside the bounds.", sf.constantOutsideBounds());
88      assertFalse("Power switches are not valid outside the bounds.", sf.validOutsideBounds());
89      assertEquals(
90          "Default power-switch max-zero-derivative should return 2",
91          2,
92          sf.getHighestOrderZeroDerivative());
93      assertTrue(
94          "Default power-switch should be equal unity with symmetric inputs", sf.symmetricToUnity());
95  
96      sf = new MultiplicativeSwitch(1.0, 0.0);
97      standardTest(sf);
98  
99      sf = new MultiplicativeSwitch(9.0, 7.2);
100     standardTest(sf, LOOSE_TOLERANCE);
101   }
102 
103   /** Tests interpolation via the PowerSwitch class. */
104   @Test
105   public void testPowerInterpolation() {
106     logger.info(" Testing default power switch");
107     PowerSwitch funct = new PowerSwitch();
108     standardTest(funct);
109     assertEquals("Default power-switch zero bound != 0.0", 0.0, funct.getZeroBound(), ULP_ZERO_2);
110     assertEquals("Default power-switch one bound != 1.0", 1.0, funct.getOneBound(), ULP_ONE_2);
111     assertFalse(
112         "Power switches are not constant outside the bounds.", funct.constantOutsideBounds());
113     assertFalse("Power switches are not valid outside the bounds.", funct.validOutsideBounds());
114     assertEquals(
115         "Default power-switch max-zero-derivative should return 0",
116         0,
117         funct.getHighestOrderZeroDerivative());
118     assertTrue(
119         "Default power-switch should be equal unity with symmetric inputs",
120         funct.symmetricToUnity());
121 
122     for (double x = 0; x <= 1.0; x += 0.01) {
123       double delta = 10.0 * ulp(x);
124       double valAt = funct.valueAt(x);
125       assertEquals(
126           String.format(
127               "Value of default power-switch at %8.4g should be itself, was %8.4g", x, valAt),
128           x,
129           valAt,
130           delta);
131       double derivAt = funct.firstDerivative(x);
132       assertEquals(
133           String.format(
134               "First derivative of default power switch at %8.4g should always be 1.0, was %8.4g",
135               x, derivAt),
136           1.0,
137           derivAt,
138           ULP_ONE_10);
139       double d2 = funct.secondDerivative(x);
140       assertEquals(
141           String.format(
142               "Second derivative of default power switch at %8.4g should always be 0.0, was %8.4g",
143               x, d2),
144           0.0,
145           d2,
146           ULP_ZERO_10);
147     }
148 
149     logger.info(" Testing manually-constructed default power switch");
150     funct = new PowerSwitch(1.0, 1.0);
151     standardTest(funct);
152     assertEquals("Default power-switch zero bound != 0.0", 0.0, funct.getZeroBound(), ULP_ZERO_2);
153     assertEquals("Default power-switch one bound != 1.0", 1.0, funct.getOneBound(), ULP_ONE_2);
154     assertFalse(
155         "Power switches are not constant outside the bounds.", funct.constantOutsideBounds());
156     assertFalse("Power switches are not valid outside the bounds.", funct.validOutsideBounds());
157     assertEquals(
158         "Default power-switch max-zero-derivative should return 0",
159         0,
160         funct.getHighestOrderZeroDerivative());
161     assertTrue(
162         "Default power-switch should be equal unity with symmetric inputs",
163         funct.symmetricToUnity());
164 
165     for (double x = 0; x <= 1.0; x += 0.01) {
166       double delta = 10.0 * ulp(x);
167       double valAt = funct.valueAt(x);
168       assertEquals(
169           String.format(
170               "Value of default power-switch at %8.4g should be itself, was %8.4g", x, valAt),
171           x,
172           valAt,
173           delta);
174       double derivAt = funct.firstDerivative(x);
175       assertEquals(
176           String.format(
177               "First derivative of default power switch at %8.4g should always be 1.0, was %8.4g",
178               x, derivAt),
179           1.0,
180           derivAt,
181           ULP_ONE_10);
182       double d2 = funct.secondDerivative(x);
183       assertEquals(
184           String.format(
185               "Second derivative of default power switch at %8.4g should always be 0.0, was %8.4g",
186               x, d2),
187           0.0,
188           d2,
189           ULP_ZERO_10);
190     }
191 
192     logger.info(" Testing linear power switch with doubled bounds");
193     funct = new PowerSwitch(0.5, 1.0);
194     standardTest(funct);
195     assertEquals(
196         String.format("Power-switch %s zero bound != 0.0", funct.toString()),
197         0.0,
198         funct.getZeroBound(),
199         ULP_ZERO_2);
200     assertEquals(
201         String.format("Power-switch %s one bound != 2.0", funct.toString()),
202         2.0,
203         funct.getOneBound(),
204         2.0 * ulp(2.0));
205     assertFalse(
206         "Power switches are not constant outside the bounds.", funct.constantOutsideBounds());
207     assertFalse("Power switches are not valid outside the bounds.", funct.validOutsideBounds());
208     assertEquals(
209         String.format("Power-switch %s max-zero-derivative should return 0", funct.toString()),
210         0,
211         funct.getHighestOrderZeroDerivative());
212     assertTrue(
213         String.format(
214             "Power-switch %s should be equal unity with symmetric inputs", funct.toString()),
215         funct.symmetricToUnity());
216 
217     for (double x = 0; x <= 1.0; x += 0.01) {
218       double delta = 10.0 * ulp(x);
219       double valAt = funct.valueAt(x);
220       assertEquals(
221           String.format(
222               "Value of power-switch %s at %8.4g should be 0.5 * itself, was %8.4g",
223               funct.toString(), x, valAt),
224           0.5 * x,
225           valAt,
226           delta);
227       double derivAt = funct.firstDerivative(x);
228       assertEquals(
229           String.format(
230               "First derivative of power-switch %s at %8.4g should always be 0.5, was %8.4g",
231               funct.toString(), x, derivAt),
232           0.5,
233           derivAt,
234           ULP_ONE_10);
235       double d2 = funct.secondDerivative(x);
236       assertEquals(
237           String.format(
238               "Second derivative of power-switch %s at %8.4g should always be 0.0, was %8.4g",
239               funct.toString(), x, d2),
240           0.0,
241           d2,
242           ULP_ZERO_10);
243     }
244 
245     logger.info(" Testing power-2 switching function");
246     funct = new PowerSwitch(1.0, 2.0);
247     standardTest(funct);
248     assertEquals(
249         String.format("Power-switch %s zero bound != 0.0", funct.toString()),
250         0.0,
251         funct.getZeroBound(),
252         ULP_ZERO_2);
253     assertEquals(
254         String.format("Power-switch %s one bound != 1.0", funct.toString()),
255         1.0,
256         funct.getOneBound(),
257         ULP_ONE_2);
258     assertFalse(
259         "Power switches are not constant outside the bounds.", funct.constantOutsideBounds());
260     assertFalse("Power switches are not valid outside the bounds.", funct.validOutsideBounds());
261     assertEquals(
262         String.format("Power-switch %s max-zero-derivative should return 0", funct.toString()),
263         0,
264         funct.getHighestOrderZeroDerivative());
265 
266     double beta = funct.getExponent();
267     for (double x = 0; x <= 1.0; x += 0.01) {
268       double delta = 10.0 * ulp(x);
269       double valAt = funct.valueAt(x);
270       double trueVal = x * x;
271       assertEquals(
272           String.format(
273               "Value of power-switch %s at %8.4g should be %8.4g, was %8.4g",
274               funct.toString(), x, trueVal, valAt),
275           trueVal,
276           valAt,
277           delta);
278 
279       double derivAt = funct.firstDerivative(x);
280       trueVal = beta * FastMath.pow(x, (beta - 1.0));
281       assertEquals(
282           String.format(
283               "First derivative of power-switch %s at %8.4g should be %8.4g, was %8.4g",
284               funct.toString(), x, trueVal, derivAt),
285           trueVal,
286           derivAt,
287           10.0 * ulp(trueVal));
288 
289       // trueVal = beta * (beta - 1.0) * FastMath.pow(x, (beta-2.0));
290       trueVal = 2;
291       double d2 = funct.secondDerivative(x);
292       assertEquals(
293           String.format(
294               "Second derivative of power-switch %s at %8.4g should always be %8.4g, was %8.4g",
295               funct.toString(), x, trueVal, d2),
296           trueVal,
297           d2,
298           ULP_ZERO_10);
299     }
300 
301     logger.info(" Testing power-2 switching function with double-wide bounds");
302     funct = new PowerSwitch(0.5, 2.0);
303     double ub = funct.getOneBound();
304     standardTest(funct);
305     assertEquals(
306         String.format("Power-switch %s zero bound != 0.0", funct.toString()),
307         0.0,
308         funct.getZeroBound(),
309         ULP_ZERO_2);
310     assertEquals(
311         String.format("Power-switch %s one bound != 2.0", funct.toString()),
312         2.0,
313         funct.getOneBound(),
314         2.0 * ulp(2.0));
315     assertFalse(
316         "Power switches are not constant outside the bounds.", funct.constantOutsideBounds());
317     assertFalse("Power switches are not valid outside the bounds.", funct.validOutsideBounds());
318     assertEquals(
319         String.format("Power-switch %s max-zero-derivative should return 0", funct.toString()),
320         0,
321         funct.getHighestOrderZeroDerivative());
322 
323     beta = funct.getExponent();
324     for (double x = 0; x <= ub; x += 0.01) {
325       double delta = 10.0 * ulp(x);
326       double valAt = funct.valueAt(x);
327       double trueVal = x * x * 0.25;
328       assertEquals(
329           String.format(
330               "Value of power-switch %s at %8.4g should be %8.4g, was %8.4g",
331               funct.toString(), x, trueVal, valAt),
332           trueVal,
333           valAt,
334           delta);
335 
336       double derivAt = funct.firstDerivative(x);
337       trueVal = beta * 0.25 * FastMath.pow(x, (beta - 1.0));
338       assertEquals(
339           String.format(
340               "First derivative of power-switch %s at %8.4g should be %8.4g, was %8.4g",
341               funct.toString(), x, trueVal, derivAt),
342           trueVal,
343           derivAt,
344           10.0 * ulp(trueVal));
345 
346       // trueVal = beta * (beta - 1.0) * FastMath.pow(x, (beta-2.0));
347       trueVal = 0.5;
348       double d2 = funct.secondDerivative(x);
349       assertEquals(
350           String.format(
351               "Second derivative of power-switch %s at %8.4g should always be %8.4g, was %8.4g",
352               funct.toString(), x, trueVal, d2),
353           trueVal,
354           d2,
355           ULP_ZERO_10);
356     }
357 
358     logger.info(" Testing power-4 switching function");
359     funct = new PowerSwitch(1.0, 4.0);
360     ub = funct.getOneBound();
361     standardTest(funct);
362     assertEquals(
363         String.format("Power-switch %s zero bound != 0.0", funct.toString()),
364         0.0,
365         funct.getZeroBound(),
366         ULP_ZERO_2);
367     assertEquals(
368         String.format("Power-switch %s one bound != 1.0", funct.toString()),
369         1.0,
370         funct.getOneBound(),
371         ULP_ONE_2);
372     assertFalse(
373         "Power switches are not constant outside the bounds.", funct.constantOutsideBounds());
374     assertFalse("Power switches are not valid outside the bounds.", funct.validOutsideBounds());
375     assertEquals(
376         String.format("Power-switch %s max-zero-derivative should return 0", funct.toString()),
377         0,
378         funct.getHighestOrderZeroDerivative());
379 
380     beta = funct.getExponent();
381     for (double x = 0; x <= ub; x += 0.01) {
382       double delta = 10.0 * ulp(x);
383       double valAt = funct.valueAt(x);
384       double trueVal = x * x * x * x;
385       assertEquals(
386           String.format(
387               "Value of power-switch %s at %8.4g should be %8.4g, was %8.4g",
388               funct.toString(), x, trueVal, valAt),
389           trueVal,
390           valAt,
391           delta);
392 
393       double derivAt = funct.firstDerivative(x);
394       trueVal = beta * FastMath.pow(x, (beta - 1.0));
395       assertEquals(
396           String.format(
397               "First derivative of power-switch %s at %8.4g should be %8.4g, was %8.4g",
398               funct.toString(), x, trueVal, derivAt),
399           trueVal,
400           derivAt,
401           10.0 * ulp(trueVal));
402 
403       trueVal = beta * (beta - 1.0) * FastMath.pow(x, (beta - 2.0));
404       double d2 = funct.secondDerivative(x);
405       assertEquals(
406           String.format(
407               "Second derivative of power-switch %s at %8.4g should always be %8.4g, was %8.4g",
408               funct.toString(), x, trueVal, d2),
409           trueVal,
410           d2,
411           ULP_ZERO_10);
412     }
413 
414     logger.info(" Testing power-4 switching function with double-wide bounds");
415     funct = new PowerSwitch(0.5, 4.0);
416     ub = funct.getOneBound();
417     standardTest(funct);
418     assertEquals(
419         String.format("Power-switch %s zero bound != 0.0", funct.toString()),
420         0.0,
421         funct.getZeroBound(),
422         ULP_ZERO_2);
423     assertEquals(
424         String.format("Power-switch %s one bound != 2.0", funct.toString()),
425         2.0,
426         funct.getOneBound(),
427         2.0 * ulp(2.0));
428     assertFalse(
429         "Power switches are not constant outside the bounds.", funct.constantOutsideBounds());
430     assertFalse("Power switches are not valid outside the bounds.", funct.validOutsideBounds());
431     assertEquals(
432         String.format("Power-switch %s max-zero-derivative should return 0", funct.toString()),
433         0,
434         funct.getHighestOrderZeroDerivative());
435 
436     beta = funct.getExponent();
437     for (double x = 0; x <= ub; x += 0.01) {
438       double delta = 10.0 * ulp(x);
439       double valAt = funct.valueAt(x);
440       double trueVal = 0.0625 * x * x * x * x;
441       assertEquals(
442           String.format(
443               "Value of power-switch %s at %8.4g should be %8.4g, was %8.4g",
444               funct.toString(), x, trueVal, valAt),
445           trueVal,
446           valAt,
447           delta);
448 
449       double derivAt = funct.firstDerivative(x);
450       trueVal = beta * 0.0625 * FastMath.pow(x, (beta - 1.0));
451       assertEquals(
452           String.format(
453               "First derivative of power-switch %s at %8.4g should be %8.4g, was %8.4g",
454               funct.toString(), x, trueVal, derivAt),
455           trueVal,
456           derivAt,
457           10.0 * ulp(trueVal));
458 
459       trueVal = 0.0625 * beta * (beta - 1.0) * FastMath.pow(x, (beta - 2.0));
460       double d2 = funct.secondDerivative(x);
461       assertEquals(
462           String.format(
463               "Second derivative of power-switch %s at %8.4g should always be %8.4g, was %8.4g",
464               funct.toString(), x, trueVal, d2),
465           trueVal,
466           d2,
467           ULP_ZERO_10);
468     }
469 
470     logger.info(" Testing square-root switching function");
471     funct = new PowerSwitch(1.0, 0.5);
472     ub = funct.getOneBound();
473     standardTest(funct);
474     assertEquals(
475         String.format("Power-switch %s zero bound != 0.0", funct.toString()),
476         0.0,
477         funct.getZeroBound(),
478         ULP_ZERO_2);
479     assertEquals(
480         String.format("Power-switch %s one bound != 1.0", funct.toString()),
481         1.0,
482         funct.getOneBound(),
483         ULP_ONE_2);
484     assertFalse(
485         "Power switches are not constant outside the bounds.", funct.constantOutsideBounds());
486     assertFalse("Power switches are not valid outside the bounds.", funct.validOutsideBounds());
487     assertEquals(
488         String.format("Power-switch %s max-zero-derivative should return 0", funct.toString()),
489         0,
490         funct.getHighestOrderZeroDerivative());
491 
492     beta = funct.getExponent();
493     for (double x = 0; x <= ub; x += 0.01) {
494       double delta = 10.0 * ulp(x);
495       double valAt = funct.valueAt(x);
496       double trueVal = FastMath.sqrt(x);
497       assertEquals(
498           String.format(
499               "Value of power-switch %s at %8.4g should be %8.4g, was %8.4g",
500               funct.toString(), x, trueVal, valAt),
501           trueVal,
502           valAt,
503           delta);
504 
505       double derivAt = funct.firstDerivative(x);
506       trueVal = beta * FastMath.pow(x, (beta - 1.0));
507       assertEquals(
508           String.format(
509               "First derivative of power-switch %s at %8.4g should be %8.4g, was %8.4g",
510               funct.toString(), x, trueVal, derivAt),
511           trueVal,
512           derivAt,
513           10.0 * ulp(trueVal));
514 
515       trueVal = beta * (beta - 1.0) * FastMath.pow(x, (beta - 2.0));
516       double d2 = funct.secondDerivative(x);
517       assertEquals(
518           String.format(
519               "Second derivative of power-switch %s at %8.4g should always be %8.4g, was %8.4g",
520               funct.toString(), x, trueVal, d2),
521           trueVal,
522           d2,
523           ULP_ZERO_10);
524     }
525 
526     logger.info(" Testing square-root switching function with double-wide bounds");
527     funct = new PowerSwitch(0.5, 0.5);
528     ub = funct.getOneBound();
529     standardTest(funct);
530     assertEquals(
531         String.format("Power-switch %s zero bound != 0.0", funct.toString()),
532         0.0,
533         funct.getZeroBound(),
534         ULP_ZERO_2);
535     assertEquals(
536         String.format("Power-switch %s one bound != 2.0", funct.toString()),
537         2.0,
538         funct.getOneBound(),
539         2.0 * ulp(2.0));
540     assertFalse(
541         "Power switches are not constant outside the bounds.", funct.constantOutsideBounds());
542     assertFalse("Power switches are not valid outside the bounds.", funct.validOutsideBounds());
543     assertEquals(
544         String.format("Power-switch %s max-zero-derivative should return 0", funct.toString()),
545         0,
546         funct.getHighestOrderZeroDerivative());
547 
548     beta = funct.getExponent();
549     double sqrt05 = FastMath.sqrt(0.5);
550     for (double x = 0; x <= ub; x += 0.01) {
551       double delta = 10.0 * ulp(x);
552       double valAt = funct.valueAt(x);
553       double trueVal = sqrt05 * FastMath.sqrt(x);
554       assertEquals(
555           String.format(
556               "Value of power-switch %s at %8.4g should be %8.4g, was %8.4g",
557               funct.toString(), x, trueVal, valAt),
558           trueVal,
559           valAt,
560           delta);
561 
562       double derivAt = funct.firstDerivative(x);
563       trueVal = 0.5 * beta * FastMath.pow(0.5 * x, (beta - 1.0));
564       assertEquals(
565           String.format(
566               "First derivative of power-switch %s at %8.4g should be %8.4g, was %8.4g",
567               funct.toString(), x, trueVal, derivAt),
568           trueVal,
569           derivAt,
570           10.0 * ulp(trueVal));
571 
572       trueVal = 0.25 * beta * (beta - 1.0) * FastMath.pow(0.5 * x, (beta - 2.0));
573       double d2 = funct.secondDerivative(x);
574       assertEquals(
575           String.format(
576               "Second derivative of power-switch %s at %8.4g should always be %8.4g, was %8.4g",
577               funct.toString(), x, trueVal, d2),
578           trueVal,
579           d2,
580           ULP_ZERO_10);
581     }
582   }
583 
584   @Test
585   public void trigTest() {
586     logger.info(" Testing trigonometric switch functionality");
587     double piOverTwo = Math.PI * 0.5;
588 
589     SquaredTrigSwitch sf = new SquaredTrigSwitch(false);
590     standardTest(sf, MID_TOLERANCE);
591     double a = piOverTwo;
592     assertEquals("Default sine switch zero bound != 0.0", 0.0, sf.getZeroBound(), ULP_ZERO_2);
593     assertEquals("Default sine switch one bound != 1.0", 1.0, sf.getOneBound(), ULP_ONE_2);
594     assertFalse("Sine switches are not constant outside the bounds.", sf.constantOutsideBounds());
595     assertTrue("Sine switches are valid outside the bounds.", sf.validOutsideBounds());
596     assertEquals(
597         "Default sine switch max-zero-derivative should return 1",
598         1,
599         sf.getHighestOrderZeroDerivative());
600     assertTrue(
601         "Default sine switch should be equal unity with symmetric inputs", sf.symmetricToUnity());
602 
603     for (double x = 0; x <= 1.0; x += 0.01) {
604       double delta = 10.0 * ulp(x);
605       double ax = a * x;
606       double sinOf = FastMath.sin(ax);
607       double cosOf = FastMath.cos(ax);
608 
609       double valAt = sf.valueAt(x);
610       double trueVal = sinOf * sinOf;
611       assertEquals(
612           String.format(
613               "Value of default sine switch at %8.4g should be %8.4g, was %8.4g",
614               x, trueVal, valAt),
615           trueVal,
616           valAt,
617           delta);
618 
619       double derivAt = sf.firstDerivative(x);
620       trueVal = 2.0 * a * sinOf * cosOf;
621       delta = (trueVal < 1.0E-10) ? 1.0E-14 : 100.0 * ulp(trueVal);
622       assertEquals(
623           String.format(
624               "First derivative of default sine switch at %8.4g should be %8.4g, was %8.4g",
625               x, trueVal, derivAt),
626           trueVal,
627           derivAt,
628           delta);
629 
630       double d2 = sf.secondDerivative(x);
631       trueVal = 2.0 * a * a * ((cosOf * cosOf) - (sinOf * sinOf));
632       delta = (trueVal < 1.0E-10) ? 1.0E-14 : 100.0 * ulp(trueVal);
633       assertEquals(
634           String.format(
635               "Second derivative of default sine switch at %8.4g should be %8.4g, was %8.4g",
636               x, trueVal, d2),
637           trueVal,
638           d2,
639           delta);
640     }
641 
642     logger.info(" Testing manually-constructed default sine-squared switch.");
643 
644     sf = new SquaredTrigSwitch(piOverTwo, false);
645     standardTest(sf, MID_TOLERANCE);
646     a = piOverTwo;
647     assertEquals("Default sine switch zero bound != 0.0", 0.0, sf.getZeroBound(), ULP_ZERO_2);
648     assertEquals("Default sine switch one bound != 1.0", 1.0, sf.getOneBound(), ULP_ONE_2);
649     assertFalse("Sine switches are not constant outside the bounds.", sf.constantOutsideBounds());
650     assertTrue("Sine switches are valid outside the bounds.", sf.validOutsideBounds());
651     assertEquals(
652         "Default sine switch max-zero-derivative should return 1",
653         1,
654         sf.getHighestOrderZeroDerivative());
655     assertTrue(
656         "Default sine switch should be equal unity with symmetric inputs", sf.symmetricToUnity());
657 
658     for (double x = 0; x <= 1.0; x += 0.01) {
659       double delta = 10.0 * ulp(x);
660       double ax = a * x;
661       double sinOf = FastMath.sin(ax);
662       double cosOf = FastMath.cos(ax);
663 
664       double valAt = sf.valueAt(x);
665       double trueVal = sinOf * sinOf;
666       assertEquals(
667           String.format(
668               "Value of default sine switch at %8.4g should be %8.4g, was %8.4g",
669               x, trueVal, valAt),
670           trueVal,
671           valAt,
672           delta);
673 
674       double derivAt = sf.firstDerivative(x);
675       trueVal = 2.0 * a * sinOf * cosOf;
676       delta = (trueVal < 1.0E-10) ? 1.0E-14 : 100.0 * ulp(trueVal);
677       assertEquals(
678           String.format(
679               "First derivative of default sine switch at %8.4g should be %8.4g, was %8.4g",
680               x, trueVal, derivAt),
681           trueVal,
682           derivAt,
683           delta);
684 
685       double d2 = sf.secondDerivative(x);
686       trueVal = 2.0 * a * a * ((cosOf * cosOf) - (sinOf * sinOf));
687       delta = (trueVal < 1.0E-10) ? 1.0E-14 : 100.0 * ulp(trueVal);
688       assertEquals(
689           String.format(
690               "Second derivative of default sine switch at %8.4g should be %8.4g, was %8.4g",
691               x, trueVal, d2),
692           trueVal,
693           d2,
694           delta);
695     }
696 
697     logger.info(" Testing default cosine-squared switch.");
698 
699     sf = new SquaredTrigSwitch(true);
700     standardTest(sf, MID_TOLERANCE);
701     a = piOverTwo;
702     assertEquals("Default cosine switch zero bound != 1.0", 1.0, sf.getZeroBound(), ULP_ONE_2);
703     assertEquals("Default cosine switch one bound != 0.0", 0.0, sf.getOneBound(), ULP_ZERO_2);
704     assertFalse("Cosine switches are not constant outside the bounds.", sf.constantOutsideBounds());
705     assertTrue("Cosine switches are valid outside the bounds.", sf.validOutsideBounds());
706     assertEquals(
707         "Default cosine switch max-zero-derivative should return 1",
708         1,
709         sf.getHighestOrderZeroDerivative());
710     assertTrue(
711         "Default cosine switch should be equal unity with symmetric inputs", sf.symmetricToUnity());
712 
713     for (double x = 0; x <= 1.0; x += 0.01) {
714       double delta = 10.0 * ulp(x);
715       double ax = a * x;
716       double sinOf = FastMath.sin(ax);
717       double cosOf = FastMath.cos(ax);
718 
719       double valAt = sf.valueAt(x);
720       double trueVal = cosOf * cosOf;
721       assertEquals(
722           String.format(
723               "Value of default cosine switch at %8.4g should be %8.4g, was %8.4g",
724               x, trueVal, valAt),
725           trueVal,
726           valAt,
727           delta);
728 
729       double derivAt = sf.firstDerivative(x);
730       trueVal = -2.0 * a * sinOf * cosOf;
731       delta = (trueVal < 1.0E-10) ? 1.0E-14 : 100.0 * ulp(trueVal);
732       assertEquals(
733           String.format(
734               "First derivative of default cosine switch at %8.4g should be %8.4g, was %8.4g",
735               x, trueVal, derivAt),
736           trueVal,
737           derivAt,
738           delta);
739 
740       double d2 = sf.secondDerivative(x);
741       trueVal = 2.0 * a * a * ((sinOf * sinOf) - (cosOf * cosOf));
742       delta = (trueVal < 1.0E-10) ? 1.0E-14 : 100.0 * ulp(trueVal);
743       assertEquals(
744           String.format(
745               "Second derivative of default cosine switch at %8.4g should be %8.4g, was %8.4g",
746               x, trueVal, d2),
747           trueVal,
748           d2,
749           delta);
750     }
751 
752     logger.info(" Testing manually constructed default cosine-squared switch.");
753 
754     sf = new SquaredTrigSwitch(piOverTwo, true);
755     standardTest(sf, MID_TOLERANCE);
756     a = piOverTwo;
757     assertEquals("Default cosine switch zero bound != 1.0", 1.0, sf.getZeroBound(), ULP_ONE_2);
758     assertEquals("Default cosine switch one bound != 0.0", 0.0, sf.getOneBound(), ULP_ZERO_2);
759     assertFalse("Cosine switches are not constant outside the bounds.", sf.constantOutsideBounds());
760     assertTrue("Cosine switches are valid outside the bounds.", sf.validOutsideBounds());
761     assertEquals(
762         "Default cosine switch max-zero-derivative should return 1",
763         1,
764         sf.getHighestOrderZeroDerivative());
765     assertTrue(
766         "Default cosine switch should be equal unity with symmetric inputs", sf.symmetricToUnity());
767 
768     for (double x = 0; x <= 1.0; x += 0.01) {
769       double delta = 10.0 * ulp(x);
770       double ax = a * x;
771       double sinOf = FastMath.sin(ax);
772       double cosOf = FastMath.cos(ax);
773 
774       double valAt = sf.valueAt(x);
775       double trueVal = cosOf * cosOf;
776       assertEquals(
777           String.format(
778               "Value of default cosine switch at %8.4g should be %8.4g, was %8.4g",
779               x, trueVal, valAt),
780           trueVal,
781           valAt,
782           delta);
783 
784       double derivAt = sf.firstDerivative(x);
785       trueVal = -2.0 * a * sinOf * cosOf;
786       delta = (trueVal < 1.0E-10) ? 1.0E-14 : 100.0 * ulp(trueVal);
787       assertEquals(
788           String.format(
789               "First derivative of default cosine switch at %8.4g should be %8.4g, was %8.4g",
790               x, trueVal, derivAt),
791           trueVal,
792           derivAt,
793           delta);
794 
795       double d2 = sf.secondDerivative(x);
796       trueVal = 2.0 * a * a * ((sinOf * sinOf) - (cosOf * cosOf));
797       delta = (trueVal < 1.0E-10) ? 1.0E-14 : 100.0 * ulp(trueVal);
798       assertEquals(
799           String.format(
800               "Second derivative of default cosine switch at %8.4g should be %8.4g, was %8.4g",
801               x, trueVal, d2),
802           trueVal,
803           d2,
804           delta);
805     }
806 
807     logger.info(" Testing sine-squared switch with unadjusted (pi/2) bounds.");
808 
809     a = 1.0;
810     sf = new SquaredTrigSwitch(a, false);
811     standardTest(sf, MID_TOLERANCE);
812     assertEquals(
813         String.format("Sine switch %s zero bound != 0.0", sf), 0.0, sf.getZeroBound(), ULP_ZERO_2);
814     assertEquals(
815         String.format("Sine switch %s one bound != 1.0", sf),
816         piOverTwo,
817         sf.getOneBound(),
818         ULP_ONE_2);
819     assertFalse("Sine switches are not constant outside the bounds.", sf.constantOutsideBounds());
820     assertTrue("Sine switches are valid outside the bounds.", sf.validOutsideBounds());
821     assertEquals(
822         "Sine switch max-zero-derivative should return 1", 1, sf.getHighestOrderZeroDerivative());
823     assertTrue("Sine switch should be equal unity with symmetric inputs", sf.symmetricToUnity());
824 
825     for (double x = 0; x <= 1.0; x += 0.01) {
826       double delta = 10.0 * ulp(x);
827       double ax = a * x;
828       double sinOf = FastMath.sin(ax);
829       double cosOf = FastMath.cos(ax);
830 
831       double valAt = sf.valueAt(x);
832       double trueVal = sinOf * sinOf;
833       assertEquals(
834           String.format(
835               "Value of sine switch %s at %8.4g should be %8.4g, was %8.4g", sf, x, trueVal, valAt),
836           trueVal,
837           valAt,
838           delta);
839 
840       double derivAt = sf.firstDerivative(x);
841       trueVal = 2.0 * a * sinOf * cosOf;
842       delta = (trueVal < 1.0E-10) ? 1.0E-14 : 100.0 * ulp(trueVal);
843       assertEquals(
844           String.format(
845               "First derivative of sine switch %s at %8.4g should be %8.4g, was %8.4g",
846               sf, x, trueVal, derivAt),
847           trueVal,
848           derivAt,
849           delta);
850 
851       double d2 = sf.secondDerivative(x);
852       trueVal = 2.0 * a * a * ((cosOf * cosOf) - (sinOf * sinOf));
853       delta = (trueVal < 1.0E-10) ? 1.0E-14 : 100.0 * ulp(trueVal);
854       assertEquals(
855           String.format(
856               "Second derivative of sine switch %s at %8.4g should be %8.4g, was %8.4g",
857               sf, x, trueVal, d2),
858           trueVal,
859           d2,
860           delta);
861     }
862 
863     logger.info(" Testing  sine-squared switch with doubled (2.0) bounds.");
864 
865     a = 0.5 * piOverTwo;
866     sf = new SquaredTrigSwitch(a, false);
867     standardTest(sf, MID_TOLERANCE);
868     assertEquals(
869         String.format("Sine switch %s zero bound != 0.0", sf), 0.0, sf.getZeroBound(), ULP_ZERO_2);
870     assertEquals(
871         String.format("Sine switch %s one bound != 1.0", sf),
872         2.0,
873         sf.getOneBound(),
874         2.0 * ulp(2.0));
875     assertFalse("Sine switches are not constant outside the bounds.", sf.constantOutsideBounds());
876     assertTrue("Sine switches are valid outside the bounds.", sf.validOutsideBounds());
877     assertEquals(
878         "Sine switch max-zero-derivative should return 1", 1, sf.getHighestOrderZeroDerivative());
879     assertTrue("Sine switch should be equal unity with symmetric inputs", sf.symmetricToUnity());
880 
881     for (double x = 0; x <= 1.0; x += 0.01) {
882       double delta = 10.0 * ulp(x);
883       double ax = a * x;
884       double sinOf = FastMath.sin(ax);
885       double cosOf = FastMath.cos(ax);
886 
887       double valAt = sf.valueAt(x);
888       double trueVal = sinOf * sinOf;
889       assertEquals(
890           String.format(
891               "Value of sine switch %s at %8.4g should be %8.4g, was %8.4g", sf, x, trueVal, valAt),
892           trueVal,
893           valAt,
894           delta);
895 
896       double derivAt = sf.firstDerivative(x);
897       trueVal = 2.0 * a * sinOf * cosOf;
898       delta = (trueVal < 1.0E-10) ? 1.0E-14 : 100.0 * ulp(trueVal);
899       assertEquals(
900           String.format(
901               "First derivative of sine switch %s at %8.4g should be %8.4g, was %8.4g",
902               sf, x, trueVal, derivAt),
903           trueVal,
904           derivAt,
905           delta);
906 
907       double d2 = sf.secondDerivative(x);
908       trueVal = 2.0 * a * a * ((cosOf * cosOf) - (sinOf * sinOf));
909       delta = (trueVal < 1.0E-10) ? 1.0E-14 : 100.0 * ulp(trueVal);
910       assertEquals(
911           String.format(
912               "Second derivative of sine switch %s at %8.4g should be %8.4g, was %8.4g",
913               sf, x, trueVal, d2),
914           trueVal,
915           d2,
916           delta);
917     }
918 
919     logger.info(" Testing cosine-squared switch with unadjusted (pi/2) bounds.");
920 
921     a = 1.0;
922     sf = new SquaredTrigSwitch(a, true);
923     standardTest(sf, MID_TOLERANCE);
924     assertEquals(
925         String.format("Cosine switch %s zero bound != 1.0", sf),
926         piOverTwo,
927         sf.getZeroBound(),
928         2.0 * ulp(piOverTwo));
929     assertEquals(
930         String.format("Cosine switch %s one bound != 0.0", sf), 0.0, sf.getOneBound(), ULP_ZERO_2);
931     assertFalse("Cosine switches are not constant outside the bounds.", sf.constantOutsideBounds());
932     assertTrue("Cosine switches are valid outside the bounds.", sf.validOutsideBounds());
933     assertEquals(
934         "Cosine switch max-zero-derivative should return 1", 1, sf.getHighestOrderZeroDerivative());
935     assertTrue("Cosine switch should be equal unity with symmetric inputs", sf.symmetricToUnity());
936 
937     for (double x = 0; x <= 1.0; x += 0.01) {
938       double delta = 50.0 * ulp(x);
939       double ax = a * x;
940       double sinOf = FastMath.sin(ax);
941       double cosOf = FastMath.cos(ax);
942 
943       double valAt = sf.valueAt(x);
944       double trueVal = cosOf * cosOf;
945       assertEquals(
946           String.format(
947               "Value of cosine switch %s at %8.4g should be %8.4g, was %8.4g",
948               sf, x, trueVal, valAt),
949           trueVal,
950           valAt,
951           delta);
952 
953       double derivAt = sf.firstDerivative(x);
954       trueVal = -2.0 * a * sinOf * cosOf;
955       delta = (trueVal < 1.0E-10) ? 1.0E-14 : 200.0 * ulp(trueVal);
956       assertEquals(
957           String.format(
958               "First derivative of cosine switch %s at %8.4g should be %8.4g, was %8.4g",
959               sf, x, trueVal, derivAt),
960           trueVal,
961           derivAt,
962           delta);
963 
964       double d2 = sf.secondDerivative(x);
965       trueVal = 2.0 * a * a * ((sinOf * sinOf) - (cosOf * cosOf));
966       delta = (trueVal < 1.0E-10) ? 1.0E-14 : 200.0 * ulp(trueVal);
967       assertEquals(
968           String.format(
969               "Second derivative of cosine switch %s at %8.4g should be %8.4g, was %8.4g",
970               sf, x, trueVal, d2),
971           trueVal,
972           d2,
973           delta);
974     }
975 
976     logger.info(" Testing cosine-squared switch with doubled (2.0) bounds..");
977 
978     a = 0.5 * piOverTwo;
979     sf = new SquaredTrigSwitch(a, true);
980     standardTest(sf, MID_TOLERANCE);
981     assertEquals(
982         String.format("Cosine switch %s zero bound != 2.0", sf),
983         2.0,
984         sf.getZeroBound(),
985         2.0 * ulp(2.0));
986     assertEquals(
987         String.format("Cosine switch %s one bound != 0.0", sf), 0.0, sf.getOneBound(), ULP_ZERO_2);
988     assertFalse("Cosine switches are not constant outside the bounds.", sf.constantOutsideBounds());
989     assertTrue("Cosine switches are valid outside the bounds.", sf.validOutsideBounds());
990     assertEquals(
991         "Cosine switch max-zero-derivative should return 1", 1, sf.getHighestOrderZeroDerivative());
992     assertTrue("Cosine switch should be equal unity with symmetric inputs", sf.symmetricToUnity());
993 
994     for (double x = 0; x <= 1.0; x += 0.01) {
995       double delta = 50.0 * ulp(x);
996       double ax = a * x;
997       double sinOf = FastMath.sin(ax);
998       double cosOf = FastMath.cos(ax);
999 
1000       double valAt = sf.valueAt(x);
1001       double trueVal = cosOf * cosOf;
1002       assertEquals(
1003           String.format(
1004               "Value of cosine switch %s at %8.4g should be %8.4g, was %8.4g",
1005               sf, x, trueVal, valAt),
1006           trueVal,
1007           valAt,
1008           delta);
1009 
1010       double derivAt = sf.firstDerivative(x);
1011       trueVal = -2.0 * a * sinOf * cosOf;
1012       delta = (trueVal < 1.0E-10) ? 1.0E-14 : 200.0 * ulp(trueVal);
1013       assertEquals(
1014           String.format(
1015               "First derivative of cosine switch %s at %8.4g should be %8.4g, was %8.4g",
1016               sf, x, trueVal, derivAt),
1017           trueVal,
1018           derivAt,
1019           delta);
1020 
1021       double d2 = sf.secondDerivative(x);
1022       trueVal = 2.0 * a * a * ((sinOf * sinOf) - (cosOf * cosOf));
1023       delta = (trueVal < 1.0E-10) ? 1.0E-14 : 200.0 * ulp(trueVal);
1024       assertEquals(
1025           String.format(
1026               "Second derivative of cosine switch %s at %8.4g should be %8.4g, was %8.4g",
1027               sf, x, trueVal, d2),
1028           trueVal,
1029           d2,
1030           delta);
1031     }
1032   }
1033 
1034   /**
1035    * Standard set of tests that all implementations of UnivariateSwitchingFunction should pass; by
1036    * default uses tight tolerances.
1037    *
1038    * @param sf Switching function to test.
1039    */
1040   private void standardTest(UnivariateSwitchingFunction sf) {
1041     standardTest(sf, ULP_ONE_100);
1042   }
1043 
1044   /**
1045    * Standard set of tests that all implementations of UnivariateSwitchingFunction should pass. If
1046    * looseTolerances is set, uses a much looser tolerance for acceptance (1/1 million) instead of
1047    * the default tolerances, approximately 100*ulp(0) and 100*ulp(1).
1048    *
1049    * @param sf Switching function to test.
1050    * @param tolerance Use looser tolerances for test acceptance
1051    */
1052   private void standardTest(UnivariateSwitchingFunction sf, double tolerance) {
1053     double oneBound = sf.getOneBound();
1054     double zeroBound = sf.getZeroBound();
1055     double increment = ((oneBound - zeroBound) * 0.01);
1056 
1057     double minBound = 0.0 - tolerance;
1058     double maxBound = 1.0 + tolerance;
1059 
1060     for (int i = 0; i < 101; i++) {
1061       double pastLB = i * increment;
1062       // Slightly convoluted logic to avoid any round-off errors at ub.
1063       double x = (i == 100) ? oneBound : zeroBound + pastLB;
1064       double val = sf.valueAt(x);
1065       assertTrue(
1066           String.format(
1067               "Switching function %s value at %8.4g was %8.4g, not in the range 0-1 inclusive",
1068               sf.toString(), x, val),
1069           val >= minBound && val <= maxBound);
1070 
1071       if (sf.symmetricToUnity()) {
1072         double symmX = oneBound - pastLB;
1073         double symmVal = sf.valueAt(symmX);
1074         assertEquals(
1075             String.format(
1076                 "Switching function %s should be "
1077                     + "symmetrical; values %7.4f and %7.4f at %7.4f and %7.4f "
1078                     + "do not sum to unity",
1079                 sf.toString(), val, symmVal, x, symmX),
1080             1.0,
1081             (val + symmVal),
1082             tolerance);
1083       }
1084     }
1085 
1086     boolean validOutside = sf.validOutsideBounds();
1087     boolean constantOutside = sf.constantOutsideBounds();
1088     double valAtUB = sf.valueAt(sf.getOneBound());
1089     double valAtLB = sf.valueAt(sf.getZeroBound());
1090     if (Math.abs(valAtLB) < tolerance) {
1091       assertEquals(
1092           String.format(
1093               "Switching function %s value at zero bound %8.4g was not 0.0 or 1.0, was %8.4g",
1094               sf.toString(), zeroBound, valAtLB),
1095           0.0,
1096           valAtLB,
1097           tolerance);
1098       assertEquals(
1099           String.format(
1100               "Switching function %s value at one bound %8.4g was not 0.0 or 1.0, was %8.4g",
1101               sf.toString(), oneBound, valAtUB),
1102           1.0,
1103           valAtUB,
1104           tolerance);
1105     } else {
1106       assertEquals(
1107           String.format(
1108               "Switching function %s value at zero bound %8.4g was not 0.0 or 1.0, was %8.4g",
1109               sf.toString(), zeroBound, valAtLB),
1110           1.0,
1111           valAtLB,
1112           tolerance);
1113       assertEquals(
1114           String.format(
1115               "Switching function %s value at one bound %8.4g was not 0.0 or 1.0, was %8.4g",
1116               sf.toString(), oneBound, valAtUB),
1117           0.0,
1118           valAtUB,
1119           tolerance);
1120       logger.info(
1121           String.format(
1122               " Value of switching function %s at zero bound was 1.0, not 0.0; switching functions usually start at 0",
1123               sf));
1124     }
1125     if (validOutside || constantOutside) {
1126       for (int i = 1; i < 251; i++) {
1127         double pastBounds = i * increment;
1128         double x = zeroBound - pastBounds;
1129         double val = sf.valueAt(x);
1130         assertTrue(
1131             String.format(
1132                 "Switching function %s value at %8.4g (outside lb-ub) was %8.4g, not in the range 0-1 inclusive",
1133                 sf.toString(), x, val),
1134             val > minBound && val < maxBound);
1135         if (constantOutside) {
1136           assertEquals(
1137               String.format(
1138                   "Switching function %s value at %8.4g was %8.4g, did not match zero bound value %8.4g",
1139                   sf.toString(), x, val, valAtLB),
1140               valAtLB,
1141               val,
1142               tolerance);
1143         }
1144 
1145         x = oneBound + pastBounds;
1146         val = sf.valueAt(x);
1147         assertTrue(
1148             String.format(
1149                 "Switching function %s value at %8.4g (outside lb-ub) was %8.4g, not in the range 0-1 inclusive",
1150                 sf.toString(), x, val),
1151             val >= 0.0 && val <= 1.0);
1152         if (constantOutside) {
1153           assertEquals(
1154               String.format(
1155                   "Switching function %s value at %8.4g was %8.4g, did not match one bound value %8.4g",
1156                   sf.toString(), x, val, valAtUB),
1157               valAtUB,
1158               val,
1159               tolerance);
1160         }
1161       }
1162     }
1163 
1164     int maxZeroOrder = sf.getHighestOrderZeroDerivative();
1165     if (maxZeroOrder >= 1) {
1166       double deriv = sf.firstDerivative(zeroBound);
1167       assertEquals(
1168           String.format(
1169               "Switching function %s first derivative at lb %8.4g was nonzero value %8.4g",
1170               sf.toString(), zeroBound, deriv),
1171           0.0,
1172           deriv,
1173           tolerance);
1174       deriv = sf.firstDerivative(oneBound);
1175       assertEquals(
1176           String.format(
1177               "Switching function %s first derivative at ub %8.4g was nonzero value %8.4g",
1178               sf.toString(), oneBound, deriv),
1179           0.0,
1180           deriv,
1181           tolerance);
1182       if (maxZeroOrder >= 2) {
1183         deriv = sf.secondDerivative(zeroBound);
1184         assertEquals(
1185             String.format(
1186                 "Switching function %s second derivative at lb %8.4g was nonzero value %8.4g",
1187                 sf.toString(), zeroBound, deriv),
1188             0.0,
1189             deriv,
1190             tolerance);
1191         deriv = sf.secondDerivative(oneBound);
1192         assertEquals(
1193             String.format(
1194                 "Switching function %s second derivative at ub %8.4g was nonzero value %8.4g",
1195                 sf.toString(), oneBound, deriv),
1196             0.0,
1197             deriv,
1198             tolerance);
1199         for (int i = 3; i <= maxZeroOrder; i++) {
1200           deriv = sf.nthDerivative(zeroBound, i);
1201           assertEquals(
1202               String.format(
1203                   "Switching function %s %d-order derivative at lb %8.4g was nonzero value %8.4g",
1204                   sf.toString(), i, zeroBound, deriv),
1205               0.0,
1206               deriv,
1207               tolerance);
1208           deriv = sf.nthDerivative(oneBound, i);
1209           assertEquals(
1210               String.format(
1211                   "Switching function %s %d-order derivative at ub %8.4g was nonzero value %8.4g",
1212                   sf.toString(), i, oneBound, deriv),
1213               0.0,
1214               deriv,
1215               tolerance);
1216         }
1217       }
1218     }
1219 
1220     int maxOrderAtZero = sf.highestOrderZeroDerivativeAtZeroBound();
1221     if (maxOrderAtZero != maxZeroOrder && maxOrderAtZero > 0) {
1222       double deriv = sf.firstDerivative(zeroBound);
1223       assertEquals(
1224           String.format(
1225               "Switching function %s first derivative at lb %8.4g was nonzero value %8.4g",
1226               sf.toString(), zeroBound, deriv),
1227           0.0,
1228           deriv,
1229           tolerance);
1230       if (maxOrderAtZero >= 2) {
1231         deriv = sf.secondDerivative(zeroBound);
1232         assertEquals(
1233             String.format(
1234                 "Switching function %s second derivative at lb %8.4g was nonzero value %8.4g",
1235                 sf.toString(), zeroBound, deriv),
1236             0.0,
1237             deriv,
1238             tolerance);
1239         for (int i = 3; i <= maxZeroOrder; i++) {
1240           deriv = sf.nthDerivative(zeroBound, i);
1241           assertEquals(
1242               String.format(
1243                   "Switching function %s %d-order derivative at lb %8.4g was nonzero value %8.4g",
1244                   sf.toString(), i, zeroBound, deriv),
1245               0.0,
1246               deriv,
1247               tolerance);
1248         }
1249       }
1250     }
1251   }
1252 }