1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.commons.math4.legacy.analysis.function; 19 20 import org.apache.commons.math4.legacy.analysis.differentiation.DerivativeStructure; 21 import org.apache.commons.math4.legacy.analysis.differentiation.UnivariateDifferentiableFunction; 22 import org.apache.commons.math4.legacy.exception.DimensionMismatchException; 23 import org.apache.commons.math4.core.jdkmath.JdkMath; 24 25 /** 26 * <a href="http://en.wikipedia.org/wiki/Sinc_function">Sinc</a> function, 27 * defined by 28 * <pre><code> 29 * sinc(x) = 1 if x = 0, 30 * sin(x) / x otherwise. 31 * </code></pre> 32 * 33 * @since 3.0 34 */ 35 public class Sinc implements UnivariateDifferentiableFunction { 36 /** 37 * Value below which the computations are done using Taylor series. 38 * <p> 39 * The Taylor series for sinc even order derivatives are: 40 * <pre> 41 * d^(2n)sinc/dx^(2n) = Sum_(k>=0) (-1)^(n+k) / ((2k)!(2n+2k+1)) x^(2k) 42 * = (-1)^n [ 1/(2n+1) - x^2/(4n+6) + x^4/(48n+120) - x^6/(1440n+5040) + O(x^8) ] 43 * </pre> 44 * </p> 45 * <p> 46 * The Taylor series for sinc odd order derivatives are: 47 * <pre> 48 * d^(2n+1)sinc/dx^(2n+1) = Sum_(k>=0) (-1)^(n+k+1) / ((2k+1)!(2n+2k+3)) x^(2k+1) 49 * = (-1)^(n+1) [ x/(2n+3) - x^3/(12n+30) + x^5/(240n+840) - x^7/(10080n+45360) + O(x^9) ] 50 * </pre> 51 * </p> 52 * <p> 53 * So the ratio of the fourth term with respect to the first term 54 * is always smaller than x^6/720, for all derivative orders. 55 * This implies that neglecting this term and using only the first three terms induces 56 * a relative error bounded by x^6/720. The SHORTCUT value is chosen such that this 57 * relative error is below double precision accuracy when |x| <= SHORTCUT. 58 * </p> 59 */ 60 private static final double SHORTCUT = 6.0e-3; 61 /** For normalized sinc function. */ 62 private final boolean normalized; 63 64 /** 65 * The sinc function, {@code sin(x) / x}. 66 */ 67 public Sinc() { 68 this(false); 69 } 70 71 /** 72 * Instantiates the sinc function. 73 * 74 * @param normalized If {@code true}, the function is 75 * <code> sin(πx) / πx</code>, otherwise {@code sin(x) / x}. 76 */ 77 public Sinc(boolean normalized) { 78 this.normalized = normalized; 79 } 80 81 /** {@inheritDoc} */ 82 @Override 83 public double value(final double x) { 84 final double scaledX = normalized ? JdkMath.PI * x : x; 85 if (JdkMath.abs(scaledX) <= SHORTCUT) { 86 // use Taylor series 87 final double scaledX2 = scaledX * scaledX; 88 return ((scaledX2 - 20) * scaledX2 + 120) / 120; 89 } else { 90 // use definition expression 91 return JdkMath.sin(scaledX) / scaledX; 92 } 93 } 94 95 /** {@inheritDoc} 96 * @since 3.1 97 */ 98 @Override 99 public DerivativeStructure value(final DerivativeStructure t) 100 throws DimensionMismatchException { 101 102 final double scaledX = (normalized ? JdkMath.PI : 1) * t.getValue(); 103 final double scaledX2 = scaledX * scaledX; 104 105 double[] f = new double[t.getOrder() + 1]; 106 107 if (JdkMath.abs(scaledX) <= SHORTCUT) { 108 109 for (int i = 0; i < f.length; ++i) { 110 final int k = i / 2; 111 if ((i & 0x1) == 0) { 112 // even derivation order 113 f[i] = (((k & 0x1) == 0) ? 1 : -1) * 114 (1.0 / (i + 1) - scaledX2 * (1.0 / (2 * i + 6) - scaledX2 / (24 * i + 120))); 115 } else { 116 // odd derivation order 117 f[i] = (((k & 0x1) == 0) ? -scaledX : scaledX) * 118 (1.0 / (i + 2) - scaledX2 * (1.0 / (6 * i + 24) - scaledX2 / (120 * i + 720))); 119 } 120 } 121 } else { 122 123 final double inv = 1 / scaledX; 124 final double cos = JdkMath.cos(scaledX); 125 final double sin = JdkMath.sin(scaledX); 126 127 f[0] = inv * sin; 128 129 // the nth order derivative of sinc has the form: 130 // dn(sinc(x)/dxn = [S_n(x) sin(x) + C_n(x) cos(x)] / x^(n+1) 131 // where S_n(x) is an even polynomial with degree n-1 or n (depending on parity) 132 // and C_n(x) is an odd polynomial with degree n-1 or n (depending on parity) 133 // S_0(x) = 1, S_1(x) = -1, S_2(x) = -x^2 + 2, S_3(x) = 3x^2 - 6... 134 // C_0(x) = 0, C_1(x) = x, C_2(x) = -2x, C_3(x) = -x^3 + 6x... 135 // the general recurrence relations for S_n and C_n are: 136 // S_n(x) = x S_(n-1)'(x) - n S_(n-1)(x) - x C_(n-1)(x) 137 // C_n(x) = x C_(n-1)'(x) - n C_(n-1)(x) + x S_(n-1)(x) 138 // as per polynomials parity, we can store both S_n and C_n in the same array 139 final double[] sc = new double[f.length]; 140 sc[0] = 1; 141 142 double coeff = inv; 143 for (int n = 1; n < f.length; ++n) { 144 145 double s = 0; 146 double c = 0; 147 148 // update and evaluate polynomials S_n(x) and C_n(x) 149 final int kStart; 150 if ((n & 0x1) == 0) { 151 // even derivation order, S_n is degree n and C_n is degree n-1 152 sc[n] = 0; 153 kStart = n; 154 } else { 155 // odd derivation order, S_n is degree n-1 and C_n is degree n 156 sc[n] = sc[n - 1]; 157 c = sc[n]; 158 kStart = n - 1; 159 } 160 161 // in this loop, k is always even 162 for (int k = kStart; k > 1; k -= 2) { 163 164 // sine part 165 sc[k] = (k - n) * sc[k] - sc[k - 1]; 166 s = s * scaledX2 + sc[k]; 167 168 // cosine part 169 sc[k - 1] = (k - 1 - n) * sc[k - 1] + sc[k -2]; 170 c = c * scaledX2 + sc[k - 1]; 171 } 172 sc[0] *= -n; 173 s = s * scaledX2 + sc[0]; 174 175 coeff *= inv; 176 f[n] = coeff * (s * sin + c * scaledX * cos); 177 } 178 } 179 180 if (normalized) { 181 double scale = JdkMath.PI; 182 for (int i = 1; i < f.length; ++i) { 183 f[i] *= scale; 184 scale *= JdkMath.PI; 185 } 186 } 187 188 return t.compose(f); 189 } 190 }