All unnormalized associated Legendre functions for , via the Bonnet recurrence, in one pass (amortizing shared recurrence terms across every at fixed -- far cheaper than independent ASSOC_LEGENDRE calls). Condon-Shortley phase; same recurrence and same stability limit as ASSOC_LEGENDRE (see ASSOC_LEGENDRE_NORM_ALL for the far-more-stable normalized alternative, itself safe to ).
Warning
Numerical stability: verified accurate (relative error
< 3e-14) for at any -- the worst
case, , occurs near the equator (
degrees) at ; safety improves toward either pole. This
supersedes an earlier "" claim, verified too
optimistic by direct comparison against an arbitrary-precision
reference. Full methodology and per-degree table:
STABILITY_FINDINGS.md at the repo root, Part 2 (regenerable via the
opt-in VSH_BUILD_STABILITY CMake target's vsh_unnorm_stability
executable).
PLM_INDEX(l,m).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=dp), | intent(out) | :: | P((LMAX+1)*(LMAX+2)/2) | |||
| integer(kind=i4), | intent(in) | :: | LMAX |
Maximum degree, . |
||
| real(kind=dp), | intent(in) | :: | X |
Argument, (typically ). |
SUBROUTINE ASSOC_LEGENDRE_ALL_DP(P, LMAX, X) IMPLICIT NONE INTEGER(KIND=i4), INTENT(IN) :: LMAX REAL(KIND=dp), INTENT(IN) :: X REAL(KIND=dp), INTENT(OUT) :: P((LMAX+1)*(LMAX+2)/2) INTEGER(KIND=i4) :: L, M REAL(KIND=dp) :: SOMX2 SOMX2 = SQRT((1.0_dp - X) * (1.0_dp + X)) P(PLM_INDEX(0, 0)) = 1.0_dp DO M = 0, LMAX IF (M .GT. 0) & P(PLM_INDEX(M, M)) = -(2*M - 1) * SOMX2 * & P(PLM_INDEX(M-1, M-1)) IF (M .LT. LMAX) & P(PLM_INDEX(M+1, M)) = X * (2*M + 1) * & P(PLM_INDEX(M, M)) DO L = M+2, LMAX P(PLM_INDEX(L, M)) = & (X*(2*L-1)*P(PLM_INDEX(L-1, M)) - & (L+M-1)*P(PLM_INDEX(L-2, M))) / (L-M) END DO END DO END SUBROUTINE ASSOC_LEGENDRE_ALL_DP