Skip to content

Commit b45b599

Browse files
authored
Add associated Legendre Polynomials P_l,m (#7)
* Add associated Legendre Polynomials P_l,m * Correct argument names in documentation * Correct example output in doc strings * Correct output in documentation * New version 0.3.4
1 parent ddb00f2 commit b45b599

File tree

5 files changed

+283
-35
lines changed

5 files changed

+283
-35
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "LegendrePolynomials"
22
uuid = "3db4a2ba-fc88-11e8-3e01-49c72059a882"
3-
version = "0.3.3"
3+
version = "0.3.4"
44

55
[deps]
66
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"

README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ julia> Pl(0.5, 20)
2626
-0.04835838106737356
2727
```
2828

29+
To compute the associated Legendre polynomial of degree `l,m` at the argument `x`, use `Plm(x, l, m)`:
30+
31+
```julia
32+
julia> Plm(0.5, 10, 5)
33+
30086.169706116176
34+
```
35+
2936
To compute the n-th derivative of the Legendre polynomial of degree `l` at the argument `x`, use `dnPl(x, l, n)`:
3037

3138
```julia
@@ -46,6 +53,19 @@ julia> collectPl(0.5, lmax = 5)
4653
0.08984375
4754
```
4855

56+
To compute all the associated Legendre polynomials for `0 <= l <= lmax`, use `collectPlm(x; m, lmax)`
57+
58+
```julia
59+
julia> collectPlm(0.5, lmax = 5, m = 3)
60+
6-element OffsetArray(::Vector{Float64}, 0:5) with eltype Float64 with indices 0:5:
61+
0.0
62+
0.0
63+
0.0
64+
-9.742785792574933
65+
-34.099750274012266
66+
-42.62468784251533
67+
```
68+
4969
To compute all the n-th derivatives for `0 <= l <= lmax`, use `collectdnPl(x; n, lmax)`
5070

5171
```julia
@@ -63,4 +83,4 @@ Check the documentation for other usage.
6383

6484
# License
6585

66-
This project is licensed under the MIT License - see the [LICENSE](https://github.com/jishnub/LegendrePolynomials.jl/blob/master/LICENSE) file for details.
86+
This project is licensed under the MIT License - see the [LICENSE](https://github.com/jishnub/LegendrePolynomials.jl/blob/master/LICENSE) file for details.

docs/src/index.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ end
77

88
# Introduction
99

10-
Compute [Legendre polynomials](https://en.wikipedia.org/wiki/Legendre_polynomials) and their derivatives using a 3-term recursion relation (Bonnet’s recursion formula).
10+
Compute [Legendre polynomials](https://en.wikipedia.org/wiki/Legendre_polynomials), [Associated Legendre polynomials](https://en.wikipedia.org/wiki/Associated_Legendre_polynomials), and their derivatives using a 3-term recursion relation (Bonnet’s recursion formula).
1111

1212
```math
1313
P_\ell(x) = \left((2\ell-1) x P_{\ell-1}(x) - (\ell-1)P_{\ell - 2}(x)\right)/\ell
@@ -19,10 +19,12 @@ Currently this package evaluates the standard polynomials that satisfy ``P_\ell(
1919
\int_{-1}^1 P_m(x) P_n(x) dx = \frac{2}{2n+1} \delta_{mn}.
2020
```
2121

22-
There are four main functions:
22+
There are six main functions:
2323

2424
* [`Pl(x,l)`](@ref Pl): this evaluates the Legendre polynomial for a given degree `l` at the argument `x`. The argument needs to satisfy `-1 <= x <= 1`.
2525
* [`collectPl(x; lmax)`](@ref collectPl): this evaluates all the polynomials for `l` lying in `0:lmax` at the argument `x`. As before the argument needs to lie in the domain of validity. Functionally this is equivalent to `Pl.(x, 0:lmax)`, except `collectPl` evaluates the result in one pass, and is therefore faster. There is also the in-place version [`collectPl!`](@ref) that uses a pre-allocated array.
26+
* [`Plm(x, l, m)`](@ref Plm): this evaluates the associated Legendre polynomial ``P_\ell,m(x)`` at the argument ``x``. The argument needs to satisfy `-1 <= x <= 1`.
27+
* [`collectPlm(x; m, lmax)`](@ref collectPlm): this evaluates the associated Legendre polynomials with coefficient `m` for `l = 0:lmax`. There is also an in-place version [`collectPlm!`](@ref) that uses a pre-allocated array.
2628
* [`dnPl(x, l, n)`](@ref dnPl): this evaluates the ``n``-th derivative of the Legendre polynomial ``P_\ell(x)`` at the argument ``x``. The argument needs to satisfy `-1 <= x <= 1`.
2729
* [`collectdnPl(x; n, lmax)`](@ref collectdnPl): this evaluates the ``n``-th derivative of all the Legendre polynomials for `l = 0:lmax`. There is also an in-place version [`collectdnPl!`](@ref) that uses a pre-allocated array.
2830

@@ -35,6 +37,13 @@ julia> Pl(0.5, 3)
3537
-0.4375
3638
```
3739

40+
Evaluate the associated Legendre Polynomial one `l,m` pair as `Plm(x, l, m)`:
41+
42+
```jldoctest
43+
julia> Plm(0.5, 3, 2)
44+
5.625
45+
```
46+
3847
Evaluate the `n`th derivative for one `l` as `dnPl(x, l, n)`:
3948

4049
```jldoctest
@@ -53,6 +62,19 @@ julia> collectPl(0.5, lmax = 3)
5362
-0.4375
5463
```
5564

65+
Evaluate all the associated Legendre Polynomials for coefficient `m` as `collectPlm(x; lmax, m)`:
66+
67+
```jldoctest
68+
julia> collectPlm(0.5, lmax = 5, m = 3)
69+
6-element OffsetArray(::Vector{Float64}, 0:5) with eltype Float64 with indices 0:5:
70+
0.0
71+
0.0
72+
0.0
73+
-9.742785792574933
74+
-34.099750274012266
75+
-42.62468784251533
76+
```
77+
5678
Evaluate all the `n`th derivatives as `collectdnPl(x; lmax, n)`:
5779

5880
```jldoctest

src/LegendrePolynomials.jl

Lines changed: 172 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ using OffsetArrays
55
export Pl
66
export collectPl
77
export collectPl!
8+
export Plm
9+
export collectPlm
10+
export collectPlm!
811
export dnPl
912
export collectdnPl
1013
export collectdnPl!
@@ -27,6 +30,18 @@ end
2730
convert(T, Pl)
2831
end
2932

33+
@inline function Plm_recursion(::Type{T}, ℓ, m, P_m_lm1, P_m_lm2, x) where {T}
34+
P_m_l = ((2-1) * x * P_m_lm1 - (ℓ+m-1) * P_m_lm2) / (ℓ-m)
35+
convert(T, P_m_l)
36+
end
37+
38+
# special case
39+
# l == m, in which case there are no (l-1,m) and (l-2,m) terms
40+
@inline function Plm_recursion_m(::Type{T}, ℓ, m, P_mm1_lm1, x) where {T}
41+
P_m_l = -(2-1) * sqrt(1 - x^2) * P_mm1_lm1
42+
convert(T, P_m_l)
43+
end
44+
3045
@inline function dPl_recursion(::Type{T}, ℓ, n, P_n_lm1, P_nm1_lm1, P_n_lm2, x) where {T}
3146
P_n_l = ((2- 1) * (x * P_n_lm1 + n * P_nm1_lm1) - (ℓ - 1) * P_n_lm2)/
3247
convert(T, P_n_l)
@@ -53,7 +68,7 @@ Return an iterator that generates the values of the Legendre polynomials ``P_\\e
5368
If `lmax` is specified then only the values of ``P_\\ell(x)`` from `0` to `lmax` are returned.
5469
5570
# Examples
56-
```jldoctest
71+
``jldoctest
5772
julia> import LegendrePolynomials: LegendrePolynomialIterator
5873
5974
julia> iter = LegendrePolynomialIterator(0.5, 4);
@@ -69,15 +84,15 @@ julia> collect(iter)
6984
julia> iter = LegendrePolynomialIterator(0.5);
7085
7186
julia> collect(Iterators.take(iter, 5)) # evaluete 5 elements (l = 0:4)
72-
5-element Vector{Float64}:
87+
5-element Array{Float64,1}:
7388
1.0
7489
0.5
7590
-0.125
7691
-0.4375
7792
-0.2890625
7893
7994
julia> collect(Iterators.take(Iterators.drop(iter, 100), 5)) # evaluate Pl for l = 100:104
80-
5-element Vector{Float64}:
95+
5-element Array{Float64,1}:
8196
-0.0605180259618612
8297
0.02196749072249231
8398
0.08178451892628381
@@ -159,6 +174,92 @@ function doublefactorial(T, n)
159174
convert(T, p)
160175
end
161176

177+
function _checkvalues_m(x, l, m)
178+
assertnonnegative(l)
179+
checkdomain(x)
180+
m >= 0 || throw(ArgumentError("coefficient m must be >= 0"))
181+
end
182+
183+
Base.@propagate_inbounds function _unsafePlm!(cache, x, l, m)
184+
# unsafe, assumes 1-based indexing
185+
checklength(cache, l - m + 1)
186+
# handle m == 0
187+
collectPl!(cache, x, lmax = l - m)
188+
189+
# handle m > 0
190+
for mi in 1:m
191+
# We denote the terms as P_mi_li
192+
193+
# li == mi
194+
P_mim1_mim1 = cache[1]
195+
P_mi_mi = Plm_recursion_m(eltype(cache), mi, mi, P_mim1_mim1, x)
196+
cache[1] = P_mi_mi
197+
198+
if l > m
199+
# li == mi + 1
200+
# P_mim1_mi = cache[2]
201+
# P_mi_mip1 = Plm_recursion(eltype(cache), mi + 1, mi, P_mi_mi, P_mim1_mi, nothing, x)
202+
# cache[2] = P_mi_mip1
203+
P_mi_lim2 = zero(x)
204+
P_mi_lim1 = cache[1]
205+
P_mi_li = Plm_recursion(eltype(cache), mi + 1, mi, P_mi_lim1, P_mi_lim2, x)
206+
cache[2] = P_mi_li
207+
208+
for li in mi+2:min(l, l - m + mi)
209+
P_mi_lim2 = cache[li - mi - 1]
210+
P_mi_lim1 = cache[li - mi]
211+
P_mi_li = Plm_recursion(eltype(cache), li, mi, P_mi_lim1, P_mi_lim2, x)
212+
cache[li - mi + 1] = P_mi_li
213+
end
214+
end
215+
end
216+
nothing
217+
end
218+
219+
"""
220+
Plm(x, l::Integer, m::Integer, [cache::AbstractVector])
221+
222+
Compute the associatedLegendre polynomial ``P_\\ell,m(x)``.
223+
Optionally a pre-allocated vector `cache` may be provided, which must have a minimum length of `l - m + 1`
224+
and may be overwritten during the computation.
225+
226+
The coefficient `m` must be non-negative. For `m == 0` this function just returns
227+
Legendre polynomials.
228+
229+
# Examples
230+
231+
```jldoctest
232+
julia> Plm(0.5, 3, 2)
233+
5.625
234+
235+
julia> Plm(0.5, 4, 0) == Pl(0.5, 4)
236+
true
237+
```
238+
"""
239+
Base.@propagate_inbounds function Plm(x, l::Integer, m::Integer,
240+
A = begin
241+
_checkvalues_m(x, l, m)
242+
# do not allocate A if the value is trivially zero
243+
if l < m
244+
return zero(polytype(x))
245+
end
246+
zeros(polytype(x), l - m + 1)
247+
end
248+
)
249+
250+
_checkvalues_m(x, l, m)
251+
# check if the value is trivially zero in case A is provided in the function call
252+
if l < m
253+
return zero(eltype(A))
254+
end
255+
256+
cache = OffsetArrays.no_offset_view(A)
257+
# function barrier, as no_offset_view may be type-unstable
258+
_unsafePlm!(cache, x, l, m)
259+
260+
return cache[l - m + 1]
261+
end
262+
162263
function _checkvalues(x, l, n)
163264
assertnonnegative(l)
164265
checkdomain(x)
@@ -304,7 +405,73 @@ julia> collectPl(0.5, lmax = 4)
304405
collectPl(x; lmax::Integer) = collect(LegendrePolynomialIterator(x, lmax))
305406

306407
"""
307-
collectdnPl(x; n::Integer, lmax::Integer)
408+
collectPlm(x; lmax::Integer, m::Integer)
409+
410+
Compute the associated Legendre Polynomial ``P_\\ell,m(x)`` for the argument `x` and all degrees `l = 0:lmax`.
411+
412+
The coefficient `m` must be greater than or equal to zero.
413+
414+
Returns `v` with indices `0:lmax`, where `v[l] == Plm(x, l, m)`.
415+
416+
# Examples
417+
418+
```jldoctest
419+
julia> collectPlm(0.5, lmax = 3, m = 2)
420+
4-element OffsetArray(::Vector{Float64}, 0:3) with eltype Float64 with indices 0:3:
421+
0.0
422+
0.0
423+
2.25
424+
5.625
425+
```
426+
"""
427+
function collectPlm(x; lmax::Integer, m::Integer)
428+
assertnonnegative(lmax)
429+
m >= 0 || throw(ArgumentError("coefficient m must be >= 0"))
430+
v = zeros(polytype(x), 0:lmax)
431+
if lmax >= m
432+
collectPlm!(parent(v), x; lmax = lmax, m = m)
433+
end
434+
v
435+
end
436+
437+
"""
438+
collectPlm!(v::AbstractVector, x; lmax::Integer, m::Integer)
439+
440+
Compute the associated Legendre Polynomial ``P_\\ell,m(x)`` for the argument `x` and all degrees `l = 0:lmax`,
441+
and store the result in `v`.
442+
443+
The coefficient `m` must be greater than or equal to zero.
444+
445+
At output, `v[l + firstindex(v)] == Plm(x, l, m)` for `l = 0:lmax`.
446+
447+
# Examples
448+
449+
```jldoctest
450+
julia> v = zeros(4);
451+
452+
julia> collectPlm!(v, 0.5, lmax = 3, m = 2)
453+
4-element Vector{Float64}:
454+
0.0
455+
0.0
456+
2.25
457+
5.625
458+
```
459+
"""
460+
function collectPlm!(v, x; lmax::Integer, m::Integer)
461+
assertnonnegative(lmax)
462+
m >= 0 || throw(ArgumentError("coefficient m must be >= 0"))
463+
checklength(v, lmax + 1)
464+
465+
# trivially zero for l < m
466+
fill!((@view v[(0:m-1) .+ firstindex(v)]), zero(eltype(v)))
467+
# populate the other elements
468+
@inbounds Plm(x, lmax, m, @view v[(m:lmax) .+ firstindex(v)])
469+
470+
v
471+
end
472+
473+
"""
474+
collectdnPl(x; lmax::Integer, n::Integer)
308475
309476
Compute the ``n``-th derivative of a Legendre Polynomial ``P_\\ell(x)`` for the argument `x` and all degrees `l = 0:lmax`.
310477
@@ -345,7 +512,7 @@ At output, `v[l + firstindex(v)] == dnPl(x, l, n)` for `l = 0:lmax`.
345512
346513
# Examples
347514
348-
```jldoctest
515+
``jldoctest
349516
julia> v = zeros(4);
350517
351518
julia> collectdnPl!(v, 0.5, lmax = 3, n = 2)

0 commit comments

Comments
 (0)