Skip to content

Commit 9fb85ff

Browse files
authored
Merge pull request #313 from hsnyder/master
legendre polynomials and gaussian quadrature
2 parents c598899 + 5c53579 commit 9fb85ff

11 files changed

+957
-0
lines changed

doc/specs/stdlib_quadrature.md

+92
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,95 @@ program demo_simps_weights
186186
! 64.0
187187
end program demo_simps_weights
188188
```
189+
190+
## `gauss_legendre` - Gauss-Legendre quadrature (a.k.a. Gaussian quadrature) nodes and weights
191+
192+
### Status
193+
194+
Experimental
195+
196+
### Description
197+
198+
Computes Gauss-Legendre quadrature (also known as simply Gaussian quadrature) nodes and weights,
199+
for any `N` (number of nodes).
200+
Using the nodes `x` and weights `w`, you can compute the integral of some function `f` as follows:
201+
`integral = sum(f(x) * w)`.
202+
203+
Only double precision is supported - if lower precision is required, you must do the appropriate conversion yourself.
204+
Accuracy has been validated up to N=64 by comparing computed results to tablulated values known to be accurate to machine precision
205+
(maximum difference from those values is 2 epsilon).
206+
207+
### Syntax
208+
209+
`subroutine [[stdlib_quadrature(module):gauss_legendre(interface)]] (x, w[, interval])`
210+
211+
### Arguments
212+
213+
`x`: Shall be a rank-one array of type `real(real64)`. It is an *output* argument, representing the quadrature nodes.
214+
215+
`w`: Shall be a rank-one array of type `real(real64)`, with the same dimension as `x`.
216+
It is an *output* argument, representing the quadrature weights.
217+
218+
`interval`: (Optional) Shall be a two-element array of type `real(real64)`.
219+
If present, the nodes and weigts are calculated for integration from `interval(1)` to `interval(2)`.
220+
If not specified, the default integral is -1 to 1.
221+
222+
### Example
223+
224+
```fortran
225+
program integrate
226+
use iso_fortran_env, dp => real64
227+
implicit none
228+
229+
integer, parameter :: N = 6
230+
real(dp), dimension(N) :: x,w
231+
call gauss_legendre(x,w)
232+
print *, "integral of x**2 from -1 to 1 is", sum(x**2 * w)
233+
end program
234+
```
235+
236+
## `gauss_legendre_lobatto` - Gauss-Legendre-Lobatto quadrature nodes and weights
237+
238+
### Status
239+
240+
Experimental
241+
242+
### Description
243+
244+
Computes Gauss-Legendre-Lobatto quadrature nodes and weights,
245+
for any `N` (number of nodes).
246+
Using the nodes `x` and weights `w`, you can compute the integral of some function `f` as follows:
247+
`integral = sum(f(x) * w)`.
248+
249+
Only double precision is supported - if lower precision is required, you must do the appropriate conversion yourself.
250+
Accuracy has been validated up to N=64 by comparing computed results to tablulated values known to be accurate to machine precision
251+
(maximum difference from those values is 2 epsilon).
252+
253+
### Syntax
254+
255+
`subroutine [[stdlib_quadrature(module):gauss_legendre_lobatto(interface)]] (x, w[, interval])`
256+
257+
### Arguments
258+
259+
`x`: Shall be a rank-one array of type `real(real64)`. It is an *output* argument, representing the quadrature nodes.
260+
261+
`w`: Shall be a rank-one array of type `real(real64)`, with the same dimension as `x`.
262+
It is an *output* argument, representing the quadrature weights.
263+
264+
`interval`: (Optional) Shall be a two-element array of type `real(real64)`.
265+
If present, the nodes and weigts are calculated for integration from `interval(1)` to `interval(2)`.
266+
If not specified, the default integral is -1 to 1.
267+
268+
### Example
269+
270+
```fortran
271+
program integrate
272+
use iso_fortran_env, dp => real64
273+
implicit none
274+
275+
integer, parameter :: N = 6
276+
real(dp), dimension(N) :: x,w
277+
call gauss_legendre_lobatto(x,w)
278+
print *, "integral of x**2 from -1 to 1 is", sum(x**2 * w)
279+
end program
280+
```

doc/specs/stdlib_specialfunctions.md

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
title: specialfunctions
3+
---
4+
5+
# Special functions
6+
7+
[TOC]
8+
9+
## `legendre` - Calculate Legendre polynomials
10+
11+
### Status
12+
13+
Experimental
14+
15+
### Description
16+
17+
Computes the value of the n-th Legendre polynomial at a specified point.
18+
Currently only 64 bit floating point is supported.
19+
20+
This is an `elemental` function.
21+
22+
### Syntax
23+
24+
`result = [[stdlib_specialfunctions(module):legendre(interface)]] (n, x)`
25+
26+
### Arguments
27+
28+
`n`: Shall be a scalar of type `real(real64)`.
29+
30+
`x`: Shall be a scalar or array (this function is elemental) of type `real(real64)`.
31+
32+
### Return value
33+
34+
The function result will be the value of the `n`-th Legendre polynomial, evaluated at `x`.
35+
36+
37+
38+
## `dlegendre` - Calculate first derivatives of Legendre polynomials
39+
40+
### Status
41+
42+
Experimental
43+
44+
### Description
45+
46+
Computes the value of the first derivative of the n-th Legendre polynomial at a specified point.
47+
Currently only 64 bit floating point is supported.
48+
49+
This is an `elemental` function.
50+
51+
### Syntax
52+
53+
`result = [[stdlib_specialfunctions(module):dlegendre(interface)]] (n, x)`
54+
55+
### Arguments
56+
57+
`n`: Shall be a scalar of type `real(real64)`.
58+
59+
`x`: Shall be a scalar or array (this function is elemental) of type `real(real64)`.
60+
61+
### Return value
62+
63+
The function result will be the value of the first derivative of the `n`-th Legendre polynomial, evaluated at `x`.

src/CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ set(SRC
5050
stdlib_logger.f90
5151
stdlib_strings.f90
5252
stdlib_system.F90
53+
stdlib_specialfunctions.f90
54+
stdlib_specialfunctions_legendre.f90
55+
stdlib_quadrature_gauss.f90
5356
${outFiles}
5457
)
5558

src/Makefile.manual

+9
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,12 @@ SRCFYPP =\
3030

3131
SRC = f18estop.f90 \
3232
stdlib_error.f90 \
33+
stdlib_specialfunctions.f90 \
34+
stdlib_specialfunctions_legendre.f90 \
35+
stdlib_io.f90 \
3336
stdlib_kinds.f90 \
3437
stdlib_logger.f90 \
38+
stdlib_quadrature_gauss.f90 \
3539
stdlib_strings.f90 \
3640
$(SRCGEN)
3741

@@ -66,6 +70,8 @@ stdlib_bitsets.o: stdlib_kinds.o
6670
stdlib_bitsets_64.o: stdlib_bitsets.o
6771
stdlib_bitsets_large.o: stdlib_bitsets.o
6872
stdlib_error.o: stdlib_optval.o
73+
stdlib_specialfunctions.o: stdlib_kinds.o
74+
stdlib_specialfunctions_legendre.o: stdlib_kinds.o stdlib_specialfunctions.o
6975
stdlib_io.o: \
7076
stdlib_error.o \
7177
stdlib_optval.o \
@@ -78,6 +84,9 @@ stdlib_linalg_diag.o: \
7884
stdlib_logger.o: stdlib_ascii.o stdlib_optval.o
7985
stdlib_optval.o: stdlib_kinds.o
8086
stdlib_quadrature.o: stdlib_kinds.o
87+
88+
stdlib_quadrature_gauss.o: stdlib_kinds.o stdlib_quadrature.o
89+
8190
stdlib_quadrature_simps.o: \
8291
stdlib_quadrature.o \
8392
stdlib_error.o \

src/stdlib_quadrature.fypp

+26
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ module stdlib_quadrature
1212
public :: trapz_weights
1313
public :: simps
1414
public :: simps_weights
15+
public :: gauss_legendre
16+
public :: gauss_legendre_lobatto
1517

1618

1719
interface trapz
@@ -90,6 +92,28 @@ module stdlib_quadrature
9092
end interface simps_weights
9193

9294

95+
interface gauss_legendre
96+
!! version: experimental
97+
!!
98+
!! Computes Gauss-Legendre quadrature nodes and weights.
99+
pure module subroutine gauss_legendre_fp64 (x, w, interval)
100+
real(dp), intent(out) :: x(:), w(:)
101+
real(dp), intent(in), optional :: interval(2)
102+
end subroutine
103+
end interface gauss_legendre
104+
105+
106+
interface gauss_legendre_lobatto
107+
!! version: experimental
108+
!!
109+
!! Computes Gauss-Legendre-Lobatto quadrature nodes and weights.
110+
pure module subroutine gauss_legendre_lobatto_fp64 (x, w, interval)
111+
real(dp), intent(out) :: x(:), w(:)
112+
real(dp), intent(in), optional :: interval(2)
113+
end subroutine
114+
end interface gauss_legendre_lobatto
115+
116+
93117
! Interface for a simple f(x)-style integrand function.
94118
! Could become fancier as we learn about the performance
95119
! ramifications of different ways to do callbacks.
@@ -103,4 +127,6 @@ module stdlib_quadrature
103127
#:endfor
104128
end interface
105129

130+
131+
106132
end module stdlib_quadrature

src/stdlib_quadrature_gauss.f90

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
submodule (stdlib_quadrature) stdlib_quadrature_gauss
2+
use stdlib_specialfunctions, only: legendre, dlegendre
3+
implicit none
4+
5+
real(dp), parameter :: pi = acos(-1._dp)
6+
real(dp), parameter :: tolerance = 4._dp * epsilon(1._dp)
7+
integer, parameter :: newton_iters = 100
8+
9+
contains
10+
11+
pure module subroutine gauss_legendre_fp64 (x, w, interval)
12+
real(dp), intent(out) :: x(:), w(:)
13+
real(dp), intent(in), optional :: interval(2)
14+
15+
associate (n => size(x)-1 )
16+
select case (n)
17+
case (0)
18+
x = 0
19+
w = 2
20+
case (1)
21+
x(1) = -sqrt(1._dp/3._dp)
22+
x(2) = -x(1)
23+
w = 1
24+
case default
25+
block
26+
integer :: i,j
27+
real(dp) :: leg, dleg, delta
28+
29+
do i = 0, (n+1)/2 - 1
30+
! use Gauss-Chebyshev points as an initial guess
31+
x(i+1) = -cos((2*i+1)/(2._dp*n+2._dp) * pi)
32+
do j = 1, newton_iters
33+
leg = legendre(n+1,x(i+1))
34+
dleg = dlegendre(n+1,x(i+1))
35+
delta = -leg/dleg
36+
x(i+1) = x(i+1) + delta
37+
if ( abs(delta) <= tolerance * abs(x(i+1)) ) exit
38+
end do
39+
x(n-i+1) = -x(i+1)
40+
41+
dleg = dlegendre(n+1,x(i+1))
42+
w(i+1) = 2._dp/((1-x(i+1)**2)*dleg**2)
43+
w(n-i+1) = w(i+1)
44+
end do
45+
46+
if (mod(n,2) == 0) then
47+
x(n/2+1) = 0
48+
49+
dleg = dlegendre(n+1, 0.0_dp)
50+
w(n/2+1) = 2._dp/(dleg**2)
51+
end if
52+
end block
53+
end select
54+
end associate
55+
56+
if (present(interval)) then
57+
associate ( a => interval(1) , b => interval(2) )
58+
x = 0.5_dp*(b-a)*x+0.5_dp*(b+a)
59+
x(1) = interval(1)
60+
x(size(x)) = interval(2)
61+
w = 0.5_dp*(b-a)*w
62+
end associate
63+
end if
64+
end subroutine
65+
66+
pure module subroutine gauss_legendre_lobatto_fp64 (x, w, interval)
67+
real(dp), intent(out) :: x(:), w(:)
68+
real(dp), intent(in), optional :: interval(2)
69+
70+
associate (n => size(x)-1)
71+
select case (n)
72+
case (1)
73+
x(1) = -1
74+
x(2) = 1
75+
w = 1
76+
case default
77+
block
78+
integer :: i,j
79+
real(dp) :: leg, dleg, delta
80+
81+
x(1) = -1._dp
82+
x(n+1) = 1._dp
83+
w(1) = 2._dp/(n*(n+1._dp))
84+
w(n+1) = 2._dp/(n*(n+1._dp))
85+
86+
do i = 1, (n+1)/2 - 1
87+
! initial guess from an approximate form given by SV Parter (1999)
88+
x(i+1) = -cos( (i+0.25_dp)*pi/n - 3/(8*n*pi*(i+0.25_dp)))
89+
do j = 1, newton_iters
90+
leg = legendre(n+1,x(i+1)) - legendre(n-1,x(i+1))
91+
dleg = dlegendre(n+1,x(i+1)) - dlegendre(n-1,x(i+1))
92+
delta = -leg/dleg
93+
x(i+1) = x(i+1) + delta
94+
if ( abs(delta) <= tolerance * abs(x(i+1)) ) exit
95+
end do
96+
x(n-i+1) = -x(i+1)
97+
98+
leg = legendre(n, x(i+1))
99+
w(i+1) = 2._dp/(n*(n+1._dp)*leg**2)
100+
w(n-i+1) = w(i+1)
101+
end do
102+
103+
if (mod(n,2) == 0) then
104+
x(n/2+1) = 0
105+
106+
leg = legendre(n, 0.0_dp)
107+
w(n/2+1) = 2._dp/(n*(n+1._dp)*leg**2)
108+
end if
109+
end block
110+
end select
111+
end associate
112+
113+
if (present(interval)) then
114+
associate ( a => interval(1) , b => interval(2) )
115+
x = 0.5_dp*(b-a)*x+0.5_dp*(b+a)
116+
x(1) = interval(1)
117+
x(size(x)) = interval(2)
118+
w = 0.5_dp*(b-a)*w
119+
end associate
120+
end if
121+
end subroutine
122+
end submodule

0 commit comments

Comments
 (0)