-
Notifications
You must be signed in to change notification settings - Fork 20
/
gigrnd.py
122 lines (96 loc) · 2.8 KB
/
gigrnd.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#!/usr/bin/env python
"""
Random variate generator for the generalized inverse Gaussian distribution.
Reference: L Devroye. Random variate generation for the generalized inverse Gaussian distribution.
Statistics and Computing, 24(2):239-246, 2014.
"""
import math
from numpy import random
def psi(x, alpha, lam):
f = -alpha*(math.cosh(x)-1.0)-lam*(math.exp(x)-x-1.0)
return f
def dpsi(x, alpha, lam):
f = -alpha*math.sinh(x)-lam*(math.exp(x)-1.0)
return f
def g(x, sd, td, f1, f2):
if (x >= -sd) and (x <= td):
f = 1.0
elif x > td:
f = f1
elif x < -sd:
f = f2
return f
def gigrnd(p, a, b):
# setup -- sample from the two-parameter version gig(lam,omega)
p = float(p); a = float(a); b = float(b)
lam = p
omega = math.sqrt(a*b)
if lam < 0:
lam = -lam
swap = True
else:
swap = False
alpha = math.sqrt(math.pow(omega,2)+math.pow(lam,2))-lam
# find t
x = -psi(1.0, alpha, lam)
if (x >= 0.5) and (x <= 2.0):
t = 1.0
elif x > 2.0:
if (alpha == 0) and (lam == 0):
t = 1.0
else:
t = math.sqrt(2.0/(alpha+lam))
elif x < 0.5:
if (alpha == 0) and (lam == 0):
t = 1.0
else:
t = math.log(4.0/(alpha+2.0*lam))
# find s
x = -psi(-1.0, alpha, lam)
if (x >= 0.5) and (x <= 2.0):
s = 1.0
elif x > 2.0:
if (alpha == 0) and (lam == 0):
s = 1.0
else:
s = math.sqrt(4.0/(alpha*math.cosh(1)+lam))
elif x < 0.5:
if (alpha == 0) and (lam == 0):
s = 1.0
elif alpha == 0:
s = 1.0/lam
elif lam == 0:
s = math.log(1.0+1.0/alpha+math.sqrt(1.0/math.pow(alpha,2)+2.0/alpha))
else:
s = min(1.0/lam, math.log(1.0+1.0/alpha+math.sqrt(1.0/math.pow(alpha,2)+2.0/alpha)))
# find auxiliary parameters
eta = -psi(t, alpha, lam)
zeta = -dpsi(t, alpha, lam)
theta = -psi(-s, alpha, lam)
xi = dpsi(-s, alpha, lam)
p = 1.0/xi
r = 1.0/zeta
td = t-r*eta
sd = s-p*theta
q = td+sd
# random variate generation
while True:
U = random.random()
V = random.random()
W = random.random()
if U < q/(p+q+r):
rnd = -sd+q*V
elif U < (q+r)/(p+q+r):
rnd = td-r*math.log(V)
else:
rnd = -sd+p*math.log(V)
f1 = math.exp(-eta-zeta*(rnd-t))
f2 = math.exp(-theta+xi*(rnd+s))
if W*g(rnd, sd, td, f1, f2) <= math.exp(psi(rnd, alpha, lam)):
break
# transform back to the three-parameter version gig(p,a,b)
rnd = math.exp(rnd)*(lam/omega+math.sqrt(1.0+math.pow(lam,2)/math.pow(omega,2)))
if swap:
rnd = 1.0/rnd
rnd = rnd/math.sqrt(a/b)
return rnd