Skip to content

Commit 2824027

Browse files
awray3Sayam753
andauthored
Add .random method to MvGaussianRandomWalk (#4388)
* Implement random for MvGaussianRandomWalk Implements the random method for MvGaussianRandomWalk, partially fixing #4337. * Update docstring for GaussianRandomWalk._random() * Add example to random docstring * Modify MvGaussianRandomWalk.random Improves the implementation by using MvNormal.random as suggested by @Sayam753. Also updated its docstring to add more examples. * Add TestMvGaussianRandomWalk * Pass entire shape to MvNormal This commit rewrites the random method to pass the entire shape into MvNormal. MvGaussianRandomWalk also now supports an integer shape that must match the dimensionality of mu. In this case it is assumed that there are no time steps, and a random sample from the multivariate normal distribution will be returned. * Fix rebase issue * Update pymc3/distributions/timeseries.py Remove equality since the shape parameter cannot have more than 2 dimensions. Co-authored-by: Sayam Kumar <[email protected]> * Update comment pymc3/distributions/timeseries.py Co-authored-by: Sayam Kumar <[email protected]> * Update pymc3/distributions/timeseries.py Adds a more explicit conditional on the time_axis statement. Co-authored-by: Sayam Kumar <[email protected]> * Fix syntax error * Add tests for MvGaussianRandomWalk with RV params Added tests for both chol and cov to be random variables. * Improve comment clarity in MvNormal._random Moves the brittle comment in MvRandom._random's docstring to the actual location in the code for clarity. * Update docstring formatting * Update RELEASE-NOTES.md Co-authored-by: Sayam Kumar <[email protected]>
1 parent acb8da0 commit 2824027

File tree

3 files changed

+122
-2
lines changed

3 files changed

+122
-2
lines changed

RELEASE-NOTES.md

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ It also brings some dreadfully awaited fixes, so be sure to go through the chang
2020
- `OrderedProbit` distribution added (see [#4232](https://github.com/pymc-devs/pymc3/pull/4232)).
2121
- `plot_posterior_predictive_glm` now works with `arviz.InferenceData` as well (see [#4234](https://github.com/pymc-devs/pymc3/pull/4234))
2222
- Add `logcdf` method to all univariate discrete distributions (see [#4387](https://github.com/pymc-devs/pymc3/pull/4387)).
23+
- Add `random` method to `MvGaussianRandomWalk` (see [#4388](https://github.com/pymc-devs/pymc3/pull/4388))
2324

2425
### Maintenance
2526
- Fixed bug whereby partial traces returns after keyboard interrupt during parallel sampling had fewer draws than would've been available [#4318](https://github.com/pymc-devs/pymc3/pull/4318)

pymc3/distributions/timeseries.py

+71-2
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,6 @@ def _random(self, sigma, mu, size, sample_shape):
275275
"""Implement a Gaussian random walk as a cumulative sum of normals.
276276
axis = len(size) - 1 denotes the axis along which cumulative sum would be calculated.
277277
This might need to be corrected in future when issue #4010 is fixed.
278-
Lines 318-322 ties the starting point of each instance of random walk to 0"
279278
"""
280279
if size[len(sample_shape)] == sample_shape:
281280
axis = len(sample_shape)
@@ -284,6 +283,8 @@ def _random(self, sigma, mu, size, sample_shape):
284283
rv = stats.norm(mu, sigma)
285284
data = rv.rvs(size).cumsum(axis=axis)
286285
data = np.array(data)
286+
287+
# the following lines center the random walk to start at the origin.
287288
if len(data.shape) > 1:
288289
for i in range(data.shape[0]):
289290
data[i] = data[i] - data[i][0]
@@ -435,7 +436,7 @@ def __init__(
435436

436437
self.init = init
437438
self.innovArgs = (mu, cov, tau, chol, lower)
438-
self.innov = multivariate.MvNormal.dist(*self.innovArgs)
439+
self.innov = multivariate.MvNormal.dist(*self.innovArgs, shape=self.shape)
439440
self.mean = tt.as_tensor_variable(0.0)
440441

441442
def logp(self, x):
@@ -452,6 +453,10 @@ def logp(self, x):
452453
-------
453454
TensorVariable
454455
"""
456+
457+
if x.ndim == 1:
458+
x = x[np.newaxis, :]
459+
455460
x_im1 = x[:-1]
456461
x_i = x[1:]
457462

@@ -460,6 +465,70 @@ def logp(self, x):
460465
def _distr_parameters_for_repr(self):
461466
return ["mu", "cov"]
462467

468+
def random(self, point=None, size=None):
469+
"""
470+
Draw random values from MvGaussianRandomWalk.
471+
472+
Parameters
473+
----------
474+
point: dict, optional
475+
Dict of variable values on which random values are to be
476+
conditioned (uses default point if not specified).
477+
size: int or tuple of ints, optional
478+
Desired size of random sample (returns one sample if not
479+
specified).
480+
481+
Returns
482+
-------
483+
array
484+
485+
486+
Examples
487+
-------
488+
.. code-block:: python
489+
490+
with pm.Model():
491+
mu = np.array([1.0, 0.0])
492+
cov = np.array([[1.0, 0.0],
493+
[0.0, 2.0]])
494+
495+
# draw one sample from a 2-dimensional Gaussian random walk with 10 timesteps
496+
sample = MvGaussianRandomWalk(mu, cov, shape=(10, 2)).random()
497+
498+
# draw three samples from a 2-dimensional Gaussian random walk with 10 timesteps
499+
sample = MvGaussianRandomWalk(mu, cov, shape=(10, 2)).random(size=3)
500+
501+
# draw four samples from a 2-dimensional Gaussian random walk with 10 timesteps,
502+
# indexed with a (2, 2) array
503+
sample = MvGaussianRandomWalk(mu, cov, shape=(10, 2)).random(size=(2, 2))
504+
"""
505+
506+
# for each draw specified by the size input, we need to draw time_steps many
507+
# samples from MvNormal.
508+
509+
size = to_tuple(size)
510+
multivariate_samples = self.innov.random(point=point, size=size)
511+
# this has shape (size, self.shape)
512+
if len(self.shape) == 2:
513+
# have time dimension in first slot of shape. Therefore the time
514+
# component can be accessed with the index equal to the length of size.
515+
time_axis = len(size)
516+
multivariate_samples = multivariate_samples.cumsum(axis=time_axis)
517+
if time_axis != 0:
518+
# this for loop covers the case where size is a tuple
519+
for idx in np.ndindex(size):
520+
multivariate_samples[idx] = (
521+
multivariate_samples[idx] - multivariate_samples[idx][0]
522+
)
523+
else:
524+
# size was passed as None
525+
multivariate_samples = multivariate_samples - multivariate_samples[0]
526+
527+
# if the above statement fails, then only a spatial dimension was passed in for self.shape.
528+
# Therefore don't subtract off the initial value since otherwise you get all zeros
529+
# as your output.
530+
return multivariate_samples
531+
463532

464533
class MvStudentTRandomWalk(MvGaussianRandomWalk):
465534
r"""

pymc3/tests/test_distributions_random.py

+50
Original file line numberDiff line numberDiff line change
@@ -1720,3 +1720,53 @@ def test_matrix_normal_random_with_random_variables():
17201720
prior = pm.sample_prior_predictive(2)
17211721

17221722
assert prior["mu"].shape == (2, D, K)
1723+
1724+
1725+
class TestMvGaussianRandomWalk(SeededTest):
1726+
@pytest.mark.parametrize(
1727+
["sample_shape", "dist_shape", "mu_shape", "param"],
1728+
generate_shapes(include_params=True),
1729+
ids=str,
1730+
)
1731+
def test_with_np_arrays(self, sample_shape, dist_shape, mu_shape, param):
1732+
dist = pm.MvGaussianRandomWalk.dist(
1733+
mu=np.ones(mu_shape), **{param: np.eye(3)}, shape=dist_shape
1734+
)
1735+
output_shape = to_tuple(sample_shape) + dist_shape
1736+
assert dist.random(size=sample_shape).shape == output_shape
1737+
1738+
@pytest.mark.xfail
1739+
@pytest.mark.parametrize(
1740+
["sample_shape", "dist_shape", "mu_shape"],
1741+
generate_shapes(include_params=False),
1742+
ids=str,
1743+
)
1744+
def test_with_chol_rv(self, sample_shape, dist_shape, mu_shape):
1745+
with pm.Model() as model:
1746+
mu = pm.Normal("mu", 0.0, 1.0, shape=mu_shape)
1747+
sd_dist = pm.Exponential.dist(1.0, shape=3)
1748+
chol, corr, stds = pm.LKJCholeskyCov(
1749+
"chol_cov", n=3, eta=2, sd_dist=sd_dist, compute_corr=True
1750+
)
1751+
mv = pm.MvGaussianRandomWalk("mv", mu, chol=chol, shape=dist_shape)
1752+
prior = pm.sample_prior_predictive(samples=sample_shape)
1753+
1754+
assert prior["mv"].shape == to_tuple(sample_shape) + dist_shape
1755+
1756+
@pytest.mark.xfail
1757+
@pytest.mark.parametrize(
1758+
["sample_shape", "dist_shape", "mu_shape"],
1759+
generate_shapes(include_params=False),
1760+
ids=str,
1761+
)
1762+
def test_with_cov_rv(self, sample_shape, dist_shape, mu_shape):
1763+
with pm.Model() as model:
1764+
mu = pm.Normal("mu", 0.0, 1.0, shape=mu_shape)
1765+
sd_dist = pm.Exponential.dist(1.0, shape=3)
1766+
chol, corr, stds = pm.LKJCholeskyCov(
1767+
"chol_cov", n=3, eta=2, sd_dist=sd_dist, compute_corr=True
1768+
)
1769+
mv = pm.MvGaussianRandomWalk("mv", mu, cov=pm.math.dot(chol, chol.T), shape=dist_shape)
1770+
prior = pm.sample_prior_predictive(samples=sample_shape)
1771+
1772+
assert prior["mv"].shape == to_tuple(sample_shape) + dist_shape

0 commit comments

Comments
 (0)