/// <summary> /// Create a <see cref="CircularEngine"/> object. /// </summary> /// <param name="gradp">should the gradient be calculated.</param> /// <param name="norm">the normalization for the associated Legendre polynomials.</param> /// <param name="c">an array of coeff objects.</param> /// <param name="f">array of coefficient multipliers. <c>f[0]</c> should be <c>1</c>.</param> /// <param name="p">the radius of the circle = sqrt(<i>x</i>^2 + <i>y</i>^2).</param> /// <param name="z">the height of the circle.</param> /// <param name="a">the normalizing radius.</param> /// <returns>A <see cref="CircularEngine"/> instance.</returns> /// <remarks> /// If you need to evaluate the spherical harmonic sum for several points with constant /// <i>f</i>, <i>p</i> = sqrt(<i>x</i>^2 + <i>y</i>^2), <i>z</i>, and <i>a</i>, it is more efficient to construct call /// <see cref="Circle(bool, Normalization, ReadOnlySpan{Coeff}, ReadOnlySpan{double}, double, double, double)"/> /// to give a <see cref="CircularEngine"/> object and then call <see cref="CircularEngine.Evaluate(double,double)"/> /// with arguments <i>x</i>/<i>p</i> and <i>y</i>/<i>p</i>. /// </remarks> public static CircularEngine Circle(bool gradp, Normalization norm, ReadOnlySpan <Coeff> c, ReadOnlySpan <double> f, double p, double z, double a) { var L = c.Length; Debug.Assert(Enum.IsDefined(norm.GetType(), norm), "Unknown normalization"); int N = c[0].Nmx, M = c[0].Mmx; double r = Hypot(z, p), t = r != 0 ? z / r : 0, // cos(theta); at origin, pick theta = pi/2 u = r != 0 ? Max(p / r, Eps) : 1, // sin(theta); but avoid the pole q = a / r; double q2 = Sq(q), tu = t / u; var circ = new CircularEngine(M, gradp, norm, a, r, u, t); Span <int> k = stackalloc int[L]; var root = SqrtTable; for (int m = M; m >= 0; --m) { // m = M .. 0 // Initialize inner sum double wc = 0, wc2 = 0, ws = 0, ws2 = 0, // w [N - m + 1], w [N - m + 2] wrc = 0, wrc2 = 0, wrs = 0, wrs2 = 0, // wr[N - m + 1], wr[N - m + 2] wtc = 0, wtc2 = 0, wts = 0, wts2 = 0; // wt[N - m + 1], wt[N - m + 2] for (int l = 0; l < L; ++l) { k[l] = c[l].IndexOf(N, m) + 1; } for (int n = N; n >= m; --n) { // n = N .. m; l = N - m .. 0 double w, A = 0, Ax = 0, B = 0, R; // alpha[l], beta[l + 1] switch (norm) { case Normalization.Full: w = root[2 * n + 1] / (root[n - m + 1] * root[n + m + 1]); Ax = q * w * root[2 * n + 3]; A = t * Ax; B = -q2 * root[2 * n + 5] / (w * root[n - m + 2] * root[n + m + 2]); break; case Normalization.Schmidt: w = root[n - m + 1] * root[n + m + 1]; Ax = q * (2 * n + 1) / w; A = t * Ax; B = -q2 * w / (root[n - m + 2] * root[n + m + 2]); break; default: break; // To suppress warning message from Visual Studio } R = c[0].Cv(--k[0]); for (int l = 1; l < L; ++l) { R += c[l].Cv(--k[l], n, m, f[l]); } R *= Scale; w = A * wc + B * wc2 + R; wc2 = wc; wc = w; if (gradp) { w = A * wrc + B * wrc2 + (n + 1) * R; wrc2 = wrc; wrc = w; w = A * wtc + B * wtc2 - u * Ax * wc2; wtc2 = wtc; wtc = w; } if (m != 0) { R = c[0].Sv(k[0]); for (int l = 1; l < L; ++l) { R += c[l].Sv(k[l], n, m, f[l]); } R *= Scale; w = A * ws + B * ws2 + R; ws2 = ws; ws = w; if (gradp) { w = A * wrs + B * wrs2 + (n + 1) * R; wrs2 = wrs; wrs = w; w = A * wts + B * wts2 - u * Ax * ws2; wts2 = wts; wts = w; } } } if (!gradp) { circ.SetCoeff(m, wc, ws); } else { // Include the terms Sc[m] * P'[m,m](t) and Ss[m] * P'[m,m](t) wtc += m * tu * wc; wts += m * tu * ws; circ.SetCoeff(m, wc, ws, wrc, wrs, wtc, wts); } } return(circ); }
/// <summary> /// /// </summary> /// <param name="c">an array of coeff objects.</param> /// <param name="f">array of coefficient multipliers. <c>f[0]</c> should be <c>1</c>.</param> /// <param name="x">the <i>x</i> component of the cartesian position.</param> /// <param name="y">the <i>y</i> component of the cartesian position.</param> /// <param name="z">the <i>z</i> component of the cartesian position.</param> /// <param name="a">the normalizing radius.</param> /// <param name="gradx">the <i>x</i> component of the gradient.</param> /// <param name="grady">the <i>y</i> component of the gradient.</param> /// <param name="gradz">the <i>z</i> component of the gradient.</param> /// <param name="gradp">should the gradient be calculated.</param> /// <param name="norm">the normalization for the associated Legendre polynomials.</param> /// <returns>the spherical harmonic sum.</returns> /// <remarks> /// See the SphericalHarmonic class for the definition of the sum. /// The coefficients used by this function are, for example, <c>c[0].Cv + f[1] * c[1].Cv + ... + f[L−1] * c[L−1].Cv</c>. /// (Note that <c>f[0]</c> is <i>not</i> used.) The upper limits on the sum are determined by <c>c[0].Nmx</c> and <c>c[0].Mmx</c>; /// these limits apply to <i>all</i> the components of the coefficients. /// <para> /// Clenshaw summation is used which permits the evaluation of the sum without the need to allocate temporary arrays. /// Thus this function never throws an exception. /// </para> /// </remarks> public static double Value(bool gradp, Normalization norm, ReadOnlySpan <Coeff> c, ReadOnlySpan <double> f, double x, double y, double z, double a, out double gradx, out double grady, out double gradz) { var L = c.Length; Debug.Assert(Enum.IsDefined(norm.GetType(), norm), "Unknown normalization"); gradx = grady = gradz = double.NaN; int N = c[0].Nmx, M = c[0].Mmx; double p = Hypot(x, y), cl = p != 0 ? x / p : 1, // cos(lambda); at pole, pick lambda = 0 sl = p != 0 ? y / p : 0, // sin(lambda) r = Hypot(z, p), t = r != 0 ? z / r : 0, // cos(theta); at origin, pick theta = pi/2 u = r != 0 ? Max(p / r, Eps) : 1, // sin(theta); but avoid the pole q = a / r; double q2 = Sq(q), uq = u * q, uq2 = Sq(uq), tu = t / u; // Initialize outer sum double vc = 0, vc2 = 0, vs = 0, vs2 = 0; // v [N + 1], v [N + 2] // vr, vt, vl and similar w variable accumulate the sums for the // derivatives wrt r, theta, and lambda, respectively. double vrc = 0, vrc2 = 0, vrs = 0, vrs2 = 0; // vr[N + 1], vr[N + 2] double vtc = 0, vtc2 = 0, vts = 0, vts2 = 0; // vt[N + 1], vt[N + 2] double vlc = 0, vlc2 = 0, vls = 0, vls2 = 0; // vl[N + 1], vl[N + 2] Span <int> k = stackalloc int[L]; var root = SqrtTable; for (int m = M; m >= 0; --m) { // m = M .. 0 // Initialize inner sum double wc = 0, wc2 = 0, ws = 0, ws2 = 0, // w [N - m + 1], w [N - m + 2] wrc = 0, wrc2 = 0, wrs = 0, wrs2 = 0, // wr[N - m + 1], wr[N - m + 2] wtc = 0, wtc2 = 0, wts = 0, wts2 = 0; // wt[N - m + 1], wt[N - m + 2] for (int l = 0; l < L; ++l) { k[l] = c[l].IndexOf(N, m) + 1; } for (int n = N; n >= m; --n) { // n = N .. m; l = N - m .. 0 double w, A = 0, Ax = 0, B = 0, R; // alpha[l], beta[l + 1] switch (norm) { case Normalization.Full: w = root[2 * n + 1] / (root[n - m + 1] * root[n + m + 1]); Ax = q * w * root[2 * n + 3]; A = t * Ax; B = -q2 * root[2 * n + 5] / (w * root[n - m + 2] * root[n + m + 2]); break; case Normalization.Schmidt: w = root[n - m + 1] * root[n + m + 1]; Ax = q * (2 * n + 1) / w; A = t * Ax; B = -q2 * w / (root[n - m + 2] * root[n + m + 2]); break; default: break; // To suppress warning message from Visual Studio } R = c[0].Cv(--k[0]); for (int l = 1; l < L; ++l) { R += c[l].Cv(--k[l], n, m, f[l]); } R *= Scale; w = A * wc + B * wc2 + R; wc2 = wc; wc = w; if (gradp) { w = A * wrc + B * wrc2 + (n + 1) * R; wrc2 = wrc; wrc = w; w = A * wtc + B * wtc2 - u * Ax * wc2; wtc2 = wtc; wtc = w; } if (m != 0) { R = c[0].Sv(k[0]); for (int l = 1; l < L; ++l) { R += c[l].Sv(k[l], n, m, f[l]); } R *= Scale; w = A * ws + B * ws2 + R; ws2 = ws; ws = w; if (gradp) { w = A * wrs + B * wrs2 + (n + 1) * R; wrs2 = wrs; wrs = w; w = A * wts + B * wts2 - u * Ax * ws2; wts2 = wts; wts = w; } } } // Now Sc[m] = wc, Ss[m] = ws // Sc'[m] = wtc, Ss'[m] = wtc if (m != 0) { double v = 0, A = 0, B = 0; // alpha[m], beta[m + 1] switch (norm) { case Normalization.Full: v = root[2] * root[2 * m + 3] / root[m + 1]; A = cl * v * uq; B = -v * root[2 * m + 5] / (root[8] * root[m + 2]) * uq2; break; case Normalization.Schmidt: v = root[2] * root[2 * m + 1] / root[m + 1]; A = cl * v * uq; B = -v * root[2 * m + 3] / (root[8] * root[m + 2]) * uq2; break; default: break; // To suppress warning message from Visual Studio } v = A * vc + B * vc2 + wc; vc2 = vc; vc = v; v = A * vs + B * vs2 + ws; vs2 = vs; vs = v; if (gradp) { // Include the terms Sc[m] * P'[m,m](t) and Ss[m] * P'[m,m](t) wtc += m * tu * wc; wts += m * tu * ws; v = A * vrc + B * vrc2 + wrc; vrc2 = vrc; vrc = v; v = A * vrs + B * vrs2 + wrs; vrs2 = vrs; vrs = v; v = A * vtc + B * vtc2 + wtc; vtc2 = vtc; vtc = v; v = A * vts + B * vts2 + wts; vts2 = vts; vts = v; v = A * vlc + B * vlc2 + m * ws; vlc2 = vlc; vlc = v; v = A * vls + B * vls2 - m * wc; vls2 = vls; vls = v; } } else { double A = 0, B = 0, qs; switch (norm) { case Normalization.Full: A = root[3] * uq; // F[1]/(q*cl) or F[1]/(q*sl) B = -root[15] / 2 * uq2; // beta[1]/q break; case Normalization.Schmidt: A = uq; B = -root[3] / 2 * uq2; break; default: break; // To suppress warning message from Visual Studio } qs = q / Scale; vc = qs * (wc + A * (cl * vc + sl * vs) + B * vc2); if (gradp) { qs /= r; // The components of the gradient in spherical coordinates are // r: dV/dr // theta: 1/r * dV/dtheta // lambda: 1/(r*u) * dV/dlambda vrc = -qs * (wrc + A * (cl * vrc + sl * vrs) + B * vrc2); vtc = qs * (wtc + A * (cl * vtc + sl * vts) + B * vtc2); vlc = qs / u * (A * (cl * vlc + sl * vls) + B * vlc2); } } } if (gradp) { // Rotate into cartesian (geocentric) coordinates gradx = cl * (u * vrc + t * vtc) - sl * vlc; grady = sl * (u * vrc + t * vtc) + cl * vlc; gradz = t * vrc - u * vtc; } return(vc); }