// // Spherical // /// <summary> /// Returns the associated legendre polynomial with angle parameterization P{l,m}( cos(theta) ) /// </summary> /// <param name="n"></param> /// <param name="m"></param> /// <param name="theta"></param> /// <returns></returns> static double LegendrePAngle(int n, int m, double theta) { if (n < 0) { n = -(n + 1); // P{n,m}(x) = P{-n-1,m}(x) } if (m == 0) { return(Math2.LegendreP(n, Math.Cos(theta))); } // if ( abs(m) < n ) return 0 by definition; handling int.MinValue too if (m < -n || m > n) { return(0); } // P{n,-m} = (-1)^m * (n-m)!/(n+m)! * P{n,m} if (m < 0) { double legP = LegendrePAngle(n, -m, theta); if (legP == 0) { return(legP); } if (IsOdd(m)) { legP = -legP; } int MaxFactorial = Math2.FactorialTable.Length - 1; if (n - m <= MaxFactorial) { double ratio = Math2.FactorialTable[n + m] / Math2.FactorialTable[n - m]; return(ratio * legP); } if (n - m <= 300) { // break up the Tgamma ratio into two separate factors // to prevent an underflow: a!/b! = (a!/170!) * (170!/b!) // 170!/300! ~= 2.37e-308 > double.MinNormalizedValue return((legP * (Math2.FactorialTable[n + m] / Math2.FactorialTable[MaxFactorial])) * Math2.TgammaRatio(MaxFactorial + 1, n + 1 - m)); } #if false // since we are setting Lmax = 128, so Mmax = 128, and Lmax+Mmax = 256 // so this should never be called, but left here for completeness // 170!/300! ~= 2.37e-308 > double.MinNormalizedValue double ratio = Math2.TgammaRatio(l + m + 1, l + 1 - m); if (ratio >= DoubleLimits.MinNormalizedValue) { return(ratio * legP); } double value = Math.Exp(Math2.Lgamma(l + m + 1) - Math2.Lgamma(l + 1 - m) + Math.Log(Math.Abs(legP))); return(Math.Sign(legP) * value); #else Policies.ReportNotImplementedError("LegendrePAngle(l: {0}, m: {1}, theta: {2}): Not implemented for |l| >= 128", n, m, theta); return(double.NaN); #endif } // at this point n >= 0 && 0 <= m <= n Debug.Assert(n >= 0 && (m >= 0 && m <= n)); // use the recurrence relations // P{n, n} = (-1)^n * (2*n - 1)!! * (1 - x^2)^(n/2) // P{n+1, n} = (2*n + 1) * x * P{n, n} // (n-m+1)*P{n+1,m} = (2*n + 1)* x * P{n,m} - (n+m)*P{n-1, m} // see: http://en.wikipedia.org/wiki/Associated_Legendre_polynomials double x = Math.Cos(theta); double sin_theta = Math.Sin(theta); // the following computes (2m-1)!! * sin_theta^m // taking special care not to underflow too soon // double pmm = Math.Pow(sin_theta, m) * Math2.Factorial2(2 * m - 1) int exp = 0; double mant = Math2.Frexp(sin_theta, out exp); double pmmScaled = Math.Pow(mant, m) * Math2.Factorial2(2 * m - 1); double pmm = Math2.Ldexp(pmmScaled, m * exp); if (IsOdd(m)) { pmm = -pmm; } if (n == m) { return(pmm); } // p0 - current // p1 - next double p0 = pmm; double p1 = (2.0 * m + 1.0) * x * p0; //P{m+1,m}(x) for (uint k = (uint)m + 1; k < (uint)n; k++) { double next = ((2 * k + 1) * x * p1 - (k + (uint)m) * p0) / (k - (uint)m + 1); p0 = p1; p1 = next; } return(p1); }
/// <summary> /// Returns the Associated Legendre Polynomial of the first kind /// </summary> /// <param name="n">Polynomial degree. Limited to |<paramref name="n"/>| ≤ 127</param> /// <param name="m">Polynomial order</param> /// <param name="x">Requires |<paramref name="x"/>| ≤ 1</param> public static double LegendreP(int n, int m, double x) { if (!(x >= -1 && x <= 1)) { Policies.ReportDomainError("LegendreP(n: {0}, m: {1}, x: {2}): Requires |x| <= 1", n, m, x); return(double.NaN); } if (n <= -128 || n >= 128) { Policies.ReportNotImplementedError("LegendreP(n: {0}, m: {1}, x: {2}): Not implemented for |n| >= 128", n, m, x); return(double.NaN); } // P{l,m}(x) == P{-l-1,m}(x); watch overflow if (n < 0) { n = -(n + 1); } if (m == 0) { return(Math2.LegendreP(n, x)); } // by definition P{n,m}(x) == 0 when |m| > l if (m < -n || m > n) { return(0); } // P{n,-m} = (-1)^m * (n-m)!/(n+m)! * P{n,m} if (m < 0) { double legP = LegendreP(n, -m, x); if (legP == 0) { return(legP); } if (IsOdd(m)) { legP = -legP; } int MaxFactorial = Math2.FactorialTable.Length - 1; if (n - m <= MaxFactorial) { double ratio = Math2.FactorialTable[n + m] / Math2.FactorialTable[n - m]; return(ratio * legP); } if (n - m <= 300) { // break up the Tgamma ratio into two separate factors // to prevent an underflow: a!/b! = (a!/170!) * (170!/b!) // 170!/300! ~= 2.37e-308 > double.MinNormalizedValue return((legP * (Math2.FactorialTable[n + m] / Math2.FactorialTable[MaxFactorial])) * Math2.TgammaRatio(MaxFactorial + 1, n + 1 - m)); } #if false // since we are setting Nmax = 128, so Mmax = 128, and Nmax+Mmax = 256 // so this should never be called, but left here for completeness // 170!/300! ~= 2.37e-308 > double.MinNormalizedValue double ratioL = Math2.TgammaRatio(n + m + 1, n + 1 - m); if (ratioL >= DoubleLimits.MinNormalizedValue) { return(ratioL * legP); } double value = Math.Exp(Math2.Lgamma(n + m + 1) - Math2.Lgamma(n + 1 - m) + Math.Log(Math.Abs(legP))); return(Math.Sign(legP) * value); #else Policies.ReportNotImplementedError("LegendreP(n: {0}, m: {1}, x: {2}): Not implemented for |n| >= 128", n, m, x); return(double.NaN); #endif } // at this point n >= 0 && 0 <= m <= n Debug.Assert(n >= 0 && (m >= 0 && m <= n)); // use the following recurrence relations: // P{n, n} = (-1)^n * (2*n - 1)!! * (1 - x^2)^(n/2) // P{n+1, n} = (2*n + 1) * x * P{n, n} // (n-m+1)*P{n+1,m} = (2*n + 1)* x * P{n,m} - (n+m)*P{n-1, m} // see: http://en.wikipedia.org/wiki/Associated_Legendre_polynomials // the following is: // pmm = Math2.Factorial2(2 * m - 1) * Math.Pow(1.0 - x*x, m/2.0); // being careful not to underflow too soon double pmm = 0; if (Math.Abs(x) > Constants.RecipSqrt2) { double inner = (1 - x) * (1 + x); int exp = 0; double mant = Math2.Frexp(inner, out exp); // careful: use integer division double pmm_scaled = Math.Pow(mant, m / 2) * Math2.Factorial2(2 * m - 1); pmm = Math2.Ldexp(pmm_scaled, (m / 2) * exp); if (IsOdd(m)) { pmm *= Math.Sqrt(inner); } } else { double p_sin_theta = Math.Exp(m / 2.0 * Math2.Log1p(-x * x)); //Math.Pow(1.0 - x*x, m/2.0); pmm = Math2.Factorial2(2 * m - 1) * p_sin_theta; } if (IsOdd(m)) { pmm = -pmm; } if (n == m) { return(pmm); } // p0 - current // p1 - next double p0 = pmm; double p1 = (2.0 * m + 1.0) * x * p0; //P{m+1,m}(x) for (uint k = (uint)m + 1; k < (uint)n; k++) { double next = ((2 * k + 1) * x * p1 - (k + (uint)m) * p0) / (k - (uint)m + 1); p0 = p1; p1 = next; } return(p1); }