#:include "common.fypp" #:set RC_KINDS_TYPES = REAL_KINDS_TYPES + CMPLX_KINDS_TYPES module stdlib_stats_distribution_normal use ieee_arithmetic, only: ieee_value, ieee_quiet_nan use stdlib_kinds, only: sp, dp, xdp, qp, int32 use stdlib_random, only: dist_rand use stdlib_stats_distribution_uniform, only: uni => rvs_uniform implicit none private real(dp), parameter :: HALF = 0.5_dp, ONE = 1.0_dp, TWO = 2.0_dp integer :: kn(0:127) real(dp) :: wn(0:127), fn(0:127) logical :: zig_norm_initialized = .false. public :: rvs_normal public :: pdf_normal public :: cdf_normal interface rvs_normal !! version: experimental !! !! Normal Distribution Random Variates !! ([Specification](../page/specs/stdlib_stats_distribution_normal.html# !! rvs_normal-normal-distribution-random-variates)) !! module procedure rvs_norm_0_rsp !0 dummy variable #:for k1, t1 in RC_KINDS_TYPES module procedure rvs_norm_${t1[0]}$${k1}$ !2 dummy variables #:endfor #:for k1, t1 in RC_KINDS_TYPES module procedure rvs_norm_array_${t1[0]}$${k1}$ !3 dummy variables #:endfor end interface rvs_normal interface pdf_normal !! version: experimental !! !! Normal Distribution Probability Density Function !! ([Specification](../page/specs/stdlib_stats_distribution_normal.html# !! pdf_normal-normal-distribution-probability-density-function)) !! #:for k1, t1 in RC_KINDS_TYPES module procedure pdf_norm_${t1[0]}$${k1}$ #:endfor end interface pdf_normal interface cdf_normal !! version: experimental !! !! Normal Distribution Cumulative Distribution Function !! ([Specification](../page/specs/stdlib_stats_distribution_normal.html# !! cdf_normal-normal-distribution-cumulative-distribution-function)) !! #:for k1, t1 in RC_KINDS_TYPES module procedure cdf_norm_${t1[0]}$${k1}$ #:endfor end interface cdf_normal contains impure subroutine zigset ! Marsaglia & Tsang generator for random normals & random exponentials. ! Translated from C by Alan Miller (amiller@bigpond.net.au), released as public ! domain (https://jblevins.org/mirror/amiller/) ! ! Marsaglia, G. & Tsang, W.W. (2000) `The ziggurat method for generating ! random variables', J. Statist. Software, v5(8). ! ! This is an electronic journal which can be downloaded from: ! http://www.jstatsoft.org/v05/i08 ! ! Latest version - 1 January 2001 ! real(dp), parameter :: M1 = 2147483648.0_dp, vn = 0.00991256303526217_dp real(dp) :: dn, tn, q integer :: i dn = 3.442619855899_dp tn = dn !tables for random normals q = vn*exp(HALF*dn*dn) kn(0) = int((dn/q)*M1, kind=int32) kn(1) = 0 wn(0) = q/M1 wn(127) = dn/M1 fn(0) = ONE fn(127) = exp(-HALF*dn*dn) do i = 126, 1, -1 dn = sqrt(-TWO*log(vn/dn + exp(-HALF*dn*dn))) kn(i + 1) = int((dn/tn)*M1, kind=int32) tn = dn fn(i) = exp(-HALF*dn*dn) wn(i) = dn/M1 end do zig_norm_initialized = .true. end subroutine zigset #:for k1, t1 in REAL_KINDS_TYPES impure function rvs_norm_0_${t1[0]}$${k1}$ () result(res) ! ! Standard normal random variate (0,1) ! ${t1}$ :: res ${t1}$, parameter :: r = 3.442619855899_${k1}$, rr = 1.0_${k1}$/r ${t1}$ :: x, y integer :: hz, iz if (.not. zig_norm_initialized) call zigset iz = 0 hz = dist_rand(1_int32) !32bit random integer iz = iand(hz, 127) !random integer in [0, 127] if (abs(hz) < kn(iz)) then res = hz*wn(iz) else L1: do L2: if (iz == 0) then do x = -log(uni(1.0_${k1}$))*rr y = -log(uni(1.0_${k1}$)) if (y + y >= x*x) exit end do res = r + x if (hz <= 0) res = -res exit L1 end if L2 x = hz*wn(iz) if (fn(iz) + uni(1.0_${k1}$)*(fn(iz - 1) - fn(iz)) < & exp(-HALF*x*x)) then res = x exit L1 end if hz = dist_rand(1_int32) iz = iand(hz, 127) if (abs(hz) < kn(iz)) then res = hz*wn(iz) exit L1 end if end do L1 end if end function rvs_norm_0_${t1[0]}$${k1}$ #:endfor #:for k1, t1 in REAL_KINDS_TYPES impure elemental & function rvs_norm_${t1[0]}$${k1}$ (loc, scale) result(res) ! ! Normal random variate (loc, scale) ! ${t1}$, intent(in) :: loc, scale ${t1}$ :: res if (scale <= 0._${k1}$) then res = ieee_value(1._${k1}$, ieee_quiet_nan) else res = rvs_norm_0_${t1[0]}$${k1}$ () res = res*scale + loc end if end function rvs_norm_${t1[0]}$${k1}$ #:endfor #:for k1, t1 in CMPLX_KINDS_TYPES impure elemental function rvs_norm_${t1[0]}$${k1}$ (loc, scale) result(res) ! ! Normally distributed complex. The real part and imaginary part are & ! independent of each other. ! ${t1}$, intent(in) :: loc, scale ${t1}$ :: res real(${k1}$) :: tr, ti tr = rvs_norm_r${k1}$ (loc%re, scale%re) ti = rvs_norm_r${k1}$ (loc%im, scale%im) res = cmplx(tr, ti, kind=${k1}$) end function rvs_norm_${t1[0]}$${k1}$ #:endfor #:for k1, t1 in REAL_KINDS_TYPES impure function rvs_norm_array_${t1[0]}$${k1}$ (loc, scale, array_size) result(res) ${t1}$, intent(in) :: loc, scale integer, intent(in) :: array_size ${t1}$ :: res(array_size) ${t1}$, parameter :: r = 3.442619855899_${k1}$, rr = 1.0_${k1}$/r ${t1}$ :: x, y, re integer :: hz, iz, i if (.not. zig_norm_initialized) call zigset if (scale <= 0._${k1}$) then res = ieee_value(1._${k1}$, ieee_quiet_nan) return end if do i = 1, array_size iz = 0 hz = dist_rand(1_int32) iz = iand(hz, 127) if (abs(hz) < kn(iz)) then re = hz*wn(iz) else L1: do L2: if (iz == 0) then do x = -log(uni(1.0_${k1}$))*rr y = -log(uni(1.0_${k1}$)) if (y + y >= x*x) exit end do re = r + x if (hz <= 0) re = -re exit L1 end if L2 x = hz*wn(iz) if (fn(iz) + uni(1.0_${k1}$)*(fn(iz - 1) - fn(iz)) < & exp(-HALF*x*x)) then re = x exit L1 end if hz = dist_rand(1_int32) iz = iand(hz, 127) if (abs(hz) < kn(iz)) then re = hz*wn(iz) exit L1 end if end do L1 end if res(i) = re*scale + loc end do end function rvs_norm_array_${t1[0]}$${k1}$ #:endfor #:for k1, t1 in CMPLX_KINDS_TYPES impure function rvs_norm_array_${t1[0]}$${k1}$ (loc, scale, array_size) result(res) ${t1}$, intent(in) :: loc, scale integer, intent(in) :: array_size integer :: i ${t1}$ :: res(array_size) real(${k1}$) :: tr, ti do i = 1, array_size tr = rvs_norm_r${k1}$ (loc%re, scale%re) ti = rvs_norm_r${k1}$ (loc%im, scale%im) res(i) = cmplx(tr, ti, kind=${k1}$) end do end function rvs_norm_array_${t1[0]}$${k1}$ #:endfor #:for k1, t1 in REAL_KINDS_TYPES elemental function pdf_norm_${t1[0]}$${k1}$ (x, loc, scale) result(res) ! ! Normal distribution probability density function ! ${t1}$, intent(in) :: x, loc, scale ${t1}$ :: res ${t1}$, parameter :: sqrt_2_pi = sqrt(2.0_${k1}$*acos(-1.0_${k1}$)) if (scale <= 0._${k1}$) then res = ieee_value(1._${k1}$, ieee_quiet_nan) else res = exp(-0.5_${k1}$*((x - loc)/scale)*(x - loc)/scale)/ & (sqrt_2_Pi*scale) end if end function pdf_norm_${t1[0]}$${k1}$ #:endfor #:for k1, t1 in CMPLX_KINDS_TYPES elemental function pdf_norm_${t1[0]}$${k1}$ (x, loc, scale) result(res) ${t1}$, intent(in) :: x, loc, scale real(${k1}$) :: res res = pdf_norm_r${k1}$ (x%re, loc%re, scale%re) res = res*pdf_norm_r${k1}$ (x%im, loc%im, scale%im) end function pdf_norm_${t1[0]}$${k1}$ #:endfor #:for k1, t1 in REAL_KINDS_TYPES elemental function cdf_norm_${t1[0]}$${k1}$ (x, loc, scale) result(res) ! ! Normal distribution cumulative distribution function ! ${t1}$, intent(in) :: x, loc, scale ${t1}$ :: res ${t1}$, parameter :: sqrt_2 = sqrt(2.0_${k1}$) if (scale <= 0._${k1}$) then res = ieee_value(1._${k1}$, ieee_quiet_nan) else res = erfc(-(x - loc)/(scale*sqrt_2))/2.0_${k1}$ end if end function cdf_norm_${t1[0]}$${k1}$ #:endfor #:for k1, t1 in CMPLX_KINDS_TYPES elemental function cdf_norm_${t1[0]}$${k1}$ (x, loc, scale) result(res) ${t1}$, intent(in) :: x, loc, scale real(${k1}$) :: res res = cdf_norm_r${k1}$ (x%re, loc%re, scale%re) res = res*cdf_norm_r${k1}$ (x%im, loc%im, scale%im) end function cdf_norm_${t1[0]}$${k1}$ #:endfor end module stdlib_stats_distribution_normal