/// <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);
        }