20
20
from aesara import scan
21
21
from aesara .tensor .random .op import RandomVariable
22
22
23
- from pymc .aesaraf import floatX , intX
23
+ from pymc .aesaraf import change_rv_size , floatX , intX
24
24
from pymc .distributions import distribution , logprob , multivariate
25
25
from pymc .distributions .continuous import Flat , Normal , get_tau_sigma
26
26
from pymc .distributions .shape_utils import to_tuple
35
35
"MvStudentTRandomWalk" ,
36
36
]
37
37
38
+ from pymc .util import check_dist_not_registered
39
+
38
40
39
41
class GaussianRandomWalkRV (RandomVariable ):
40
42
"""
@@ -107,10 +109,10 @@ def rng_fn(
107
109
init_size = (* size , 1 )
108
110
steps_size = (* size , steps )
109
111
110
- init_val = rng . normal (init , sigma , size = init_size )
112
+ init = np . reshape (init , init_size )
111
113
steps = rng .normal (loc = mu , scale = sigma , size = steps_size )
112
114
113
- grw = np .concatenate ([init_val , steps ], axis = - 1 )
115
+ grw = np .concatenate ([init , steps ], axis = - 1 )
114
116
115
117
return np .cumsum (grw , axis = - 1 )
116
118
@@ -132,8 +134,9 @@ class GaussianRandomWalk(distribution.Continuous):
132
134
innovation drift, defaults to 0.0
133
135
sigma: tensor_like of float, optional
134
136
sigma > 0, innovation standard deviation, defaults to 0.0
135
- init: tensor_like of float, optional
136
- Mean value of initialization, defaults to 0.0
137
+ init: Scalar PyMC distribution
138
+ Scalar distribution of the initial value, created with the `.dist()` API. Defaults to
139
+ Normal with same `mu` and `sigma` as the GaussianRandomWalk
137
140
steps: int
138
141
Number of steps in Gaussian Random Walks
139
142
size: int
@@ -142,14 +145,37 @@ class GaussianRandomWalk(distribution.Continuous):
142
145
143
146
rv_op = gaussianrandomwalk
144
147
148
+ def __new__ (cls , name , mu = 0.0 , sigma = 1.0 , init = None , steps : int = 1 , ** kwargs ):
149
+ check_dist_not_registered (init )
150
+ return super ().__new__ (cls , name , mu , sigma , init , steps , ** kwargs )
151
+
145
152
@classmethod
146
- def dist (cls , mu = 0.0 , sigma = 1.0 , * , steps : int , init = 0.0 , ** kwargs ) -> RandomVariable :
153
+ def dist (
154
+ cls , mu = 0.0 , sigma = 1.0 , init = None , steps : int = 1 , size = None , ** kwargs
155
+ ) -> RandomVariable :
156
+
157
+ mu = at .as_tensor_variable (floatX (mu ))
158
+ sigma = at .as_tensor_variable (floatX (sigma ))
159
+ steps = at .as_tensor_variable (intX (steps ))
147
160
148
- params = [at .as_tensor_variable (floatX (param )) for param in (mu , sigma , init )] + [
149
- at .as_tensor_variable (intX (steps ))
150
- ]
161
+ if init is None :
162
+ init = Normal .dist (mu , sigma , size = size )
163
+ else :
164
+ if not (
165
+ isinstance (init , at .TensorVariable )
166
+ and init .owner is not None
167
+ and isinstance (init .owner .op , RandomVariable )
168
+ and init .owner .op .ndim_supp == 0
169
+ ):
170
+ raise TypeError ("init must be a scalar distribution variable" )
171
+ if size is not None or shape is not None :
172
+ init = change_rv_size (init , to_tuple (size or shape ))
173
+ else :
174
+ # If not explicit, size is determined by the shape of mu and sigma
175
+ mu_ = at .broadcast_arrays (mu , sigma )[0 ]
176
+ init = change_rv_size (init , mu_ .shape )
151
177
152
- return super ().dist (params , ** kwargs )
178
+ return super ().dist ([ mu , sigma , init , steps ], size = size , ** kwargs )
153
179
154
180
def logp (
155
181
value : at .Variable ,
@@ -174,11 +200,13 @@ def logp(
174
200
"""
175
201
176
202
# Calculate initialization logp
203
+ init_logp = logprob .logp (init , value [..., 0 ])
204
+
177
205
# Make time series stationary around the mean value
178
- stationary_series = at .diff (value )
206
+ stationary_series = at .diff (value , axis = - 1 )
179
207
series_logp = logprob .logp (Normal .dist (mu , sigma ), stationary_series )
180
208
181
- return series_logp
209
+ return init_logp + series_logp . sum ( axis = - 1 )
182
210
183
211
184
212
class AR1 (distribution .Continuous ):
0 commit comments