Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pymc3/distributions/continuous.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ def __init__(self, sd=None, tau=None, *args, **kwargs):
assert_negative_support(sd, 'sd', 'HalfNormal')

def random(self, point=None, size=None, repeat=None):
sd = draw_values([self.sd], point=point)
sd = draw_values([self.sd], point=point)[0]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is that required?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

draw_values used to return a list if it was passed more than one value and a single value otherwise. I didn't like that and change it to return always a list. I am playing around with sample_ppc a bit, and there it could be that the list of variable to draw is dynamic, which would make the previous behaviour somewhat strange. But I can change it back if you disagree.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I don't disagree.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it effect the speed? Since you commented in #2296 that draw_values might also cause slowdown in random method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so

return generate_samples(stats.halfnorm.rvs, loc=0., scale=sd,
dist_shape=self.shape,
size=size)
Expand Down Expand Up @@ -578,7 +578,7 @@ def __init__(self, lam, *args, **kwargs):
assert_negative_support(lam, 'lam', 'Exponential')

def random(self, point=None, size=None, repeat=None):
lam = draw_values([self.lam], point=point)
lam = draw_values([self.lam], point=point)[0]
return generate_samples(np.random.exponential, scale=1. / lam,
dist_shape=self.shape,
size=size)
Expand Down Expand Up @@ -962,7 +962,7 @@ def _random(self, beta, size=None):
return beta * np.abs(np.tan(np.pi * (u - 0.5)))

def random(self, point=None, size=None, repeat=None):
beta = draw_values([self.beta], point=point)
beta = draw_values([self.beta], point=point)[0]
return generate_samples(self._random, beta,
dist_shape=self.shape,
size=size)
Expand Down
8 changes: 4 additions & 4 deletions pymc3/distributions/discrete.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def __init__(self, p, *args, **kwargs):
self.mode = tt.cast(tround(p), 'int8')

def random(self, point=None, size=None, repeat=None):
p = draw_values([self.p], point=point)
p = draw_values([self.p], point=point)[0]
return generate_samples(stats.bernoulli.rvs, p,
dist_shape=self.shape,
size=size)
Expand Down Expand Up @@ -286,7 +286,7 @@ def __init__(self, mu, *args, **kwargs):
self.mode = tt.floor(mu).astype('int32')

def random(self, point=None, size=None, repeat=None):
mu = draw_values([self.mu], point=point)
mu = draw_values([self.mu], point=point)[0]
return generate_samples(stats.poisson.rvs, mu,
dist_shape=self.shape,
size=size)
Expand Down Expand Up @@ -398,7 +398,7 @@ def __init__(self, p, *args, **kwargs):
self.mode = 1

def random(self, point=None, size=None, repeat=None):
p = draw_values([self.p], point=point)
p = draw_values([self.p], point=point)[0]
return generate_samples(np.random.geometric, p,
dist_shape=self.shape,
size=size)
Expand Down Expand Up @@ -559,7 +559,7 @@ def __init__(self, c, *args, **kwargs):
self.mean = self.median = self.mode = self.c = c = tt.as_tensor_variable(c)

def random(self, point=None, size=None, repeat=None):
c = draw_values([self.c], point=point)
c = draw_values([self.c], point=point)[0]
dtype = np.array(c).dtype

def _random(c, dtype=dtype, size=None):
Expand Down
109 changes: 56 additions & 53 deletions pymc3/distributions/distribution.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import numbers
import numpy as np
import theano.tensor as tt
from theano import function
Expand Down Expand Up @@ -48,7 +49,8 @@ def dist(cls, *args, **kwargs):
dist.__init__(*args, **kwargs)
return dist

def __init__(self, shape, dtype, testval=None, defaults=(), transform=None, broadcastable=None):
def __init__(self, shape, dtype, testval=None, defaults=(),
transform=None, broadcastable=None):
self.shape = np.atleast_1d(shape)
if False in (np.floor(self.shape) == self.shape):
raise TypeError("Expected int elements in shape")
Expand All @@ -70,8 +72,10 @@ def get_test_val(self, val, defaults):
return self.getattr_value(val)

if val is None:
raise AttributeError(str(self) + " has no finite default value to use, checked: " +
str(defaults) + " pass testval argument or adjust so value is finite.")
raise AttributeError("%s has no finite default value to use, "
"checked: %s. Pass testval argument or "
"adjust so value is finite."
% (self, str(defaults)))

def getattr_value(self, val):
if isinstance(val, string_types):
Expand All @@ -97,7 +101,8 @@ def TensorType(dtype, shape, broadcastable=None):

class NoDistribution(Distribution):

def __init__(self, shape, dtype, testval=None, defaults=(), transform=None, parent_dist=None, *args, **kwargs):
def __init__(self, shape, dtype, testval=None, defaults=(),
transform=None, parent_dist=None, *args, **kwargs):
super(NoDistribution, self).__init__(shape=shape, dtype=dtype,
testval=testval, defaults=defaults,
*args, **kwargs)
Expand Down Expand Up @@ -137,7 +142,8 @@ def __init__(self, shape=(), dtype=None, defaults=('mode',),
class Continuous(Distribution):
"""Base class for continuous distributions"""

def __init__(self, shape=(), dtype=None, defaults=('median', 'mean', 'mode'), *args, **kwargs):
def __init__(self, shape=(), dtype=None, defaults=('median', 'mean', 'mode'),
*args, **kwargs):
if dtype is None:
dtype = theano.config.floatX
super(Continuous, self).__init__(
Expand Down Expand Up @@ -190,18 +196,13 @@ def draw_values(params, point=None):
if param.name in named_nodes:
named_nodes.pop(param.name)
for name, node in named_nodes.items():
if not isinstance(node, (tt.sharedvar.TensorSharedVariable,
if not isinstance(node, (tt.sharedvar.SharedVariable,
tt.TensorConstant)):
givens[name] = (node, draw_value(node, point=point))
values = [None for _ in params]
for i, param in enumerate(params):
# "Homogonise" output
values[i] = np.atleast_1d(draw_value(
param, point=point, givens=givens.values()))
if len(values) == 1:
return values[0]
else:
return values
givens[name] = (node, _draw_value(node, point=point))
values = []
for param in params:
values.append(_draw_value(param, point=point, givens=givens.values()))
return values


@memoize
Expand Down Expand Up @@ -229,43 +230,45 @@ def _compile_theano_function(param, vars, givens=None):
allow_input_downcast=True)


def draw_value(param, point=None, givens=()):
if hasattr(param, 'name'):
if hasattr(param, 'model'):
if point is not None and param.name in point:
value = point[param.name]
elif hasattr(param, 'random') and param.random is not None:
value = param.random(point=point, size=None)
else:
value = param.tag.test_value
else:
input_pairs = ([g[0] for g in givens],
[g[1] for g in givens])
def _draw_value(param, point=None, givens=None):
"""Draw a random value from a distribution or return a constant.

value = _compile_theano_function(param,
input_pairs[0])(*input_pairs[1])
Parameters
----------
param : number, array like, theano variable or pymc3 random variable
The value or distribution. Constants or shared variables
will be converted to an array and returned. Theano variables
are evaluated. If `param` is a pymc3 random variables, draw
a new value from it and return that, unless a value is specified
in `point`.
point : dict, optional
A dictionary from pymc3 variable names to their values.
givens : dict, optional
A dictionary from theano variables to their values. These values
are used to evaluate `param` if it is a theano variable.
"""
if isinstance(param, numbers.Number):
return param
elif isinstance(param, np.ndarray):
return param
elif isinstance(param, tt.TensorConstant):
return param.value
elif isinstance(param, tt.sharedvar.SharedVariable):
return param.get_value()
elif isinstance(param, tt.TensorVariable):
if point and hasattr(param, 'model') and param.name in point:
return point[param.name]
elif hasattr(param, 'random') and param.random is not None:
return param.random(point=point, size=None)
else:
if givens:
variables, values = list(zip(*givens))
else:
variables = values = []
func = _compile_theano_function(param, variables)
return func(*values)
else:
value = param

# Sanitising values may be necessary.
if hasattr(value, 'value'):
value = value.value
elif hasattr(value, 'get_value'):
value = value.get_value()

if hasattr(param, 'dtype'):
value = np.atleast_1d(value).astype(param.dtype)
if hasattr(param, 'shape'):
try:
shape = param.shape.tag.test_value
except AttributeError:
try:
shape = param.shape.eval()
except AttributeError:
shape = param.shape
if len(shape) == 0 and len(value) == 1:
value = value[0]
return value
raise ValueError('Unexpected type in draw_value: %s' % type(param))


def broadcast_shapes(*args):
Expand Down Expand Up @@ -490,8 +493,8 @@ class Bound(object):
# or within the model context
NegativeNormal = pm.Bound(pm.Normal, upper=0.0)
par2 = NegativeNormal('par2', mu=0.0, sd=1.0, testval=1.0)
# or you can define it implicitly within the model context

# or you can define it implicitly within the model context
par3 = pm.Bound(pm.Normal, lower=-1.0, upper=1.0)(
'par3', mu=0.0, sd=1.0, testval=1.0)
"""
Expand Down
2 changes: 1 addition & 1 deletion pymc3/distributions/multivariate.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ def __init__(self, a, transform=transforms.stick_breaking,
np.nan)

def random(self, point=None, size=None):
a = draw_values([self.a], point=point)
a = draw_values([self.a], point=point)[0]

def _random(a, size=None):
return stats.dirichlet.rvs(a, None if size == a.shape else size)
Expand Down
2 changes: 1 addition & 1 deletion pymc3/step_methods/elliptical_slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def astep(self, q0, logp):

# Draw from the normal prior by multiplying the Cholesky decomposition
# of the covariance with draws from a standard normal
chol = draw_values([self.prior_chol])
chol = draw_values([self.prior_chol])[0]
nu = np.dot(chol, nr.randn(chol.shape[0]))
y = logp(q0) - nr.standard_exponential()

Expand Down
92 changes: 92 additions & 0 deletions pymc3/tests/test_random.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import pymc3 as pm
import numpy as np
import numpy.testing as npt
import pytest
import theano.tensor as tt
import theano

from pymc3.distributions.distribution import _draw_value, draw_values


def test_draw_value():
npt.assert_equal(_draw_value(np.array([5, 6])), [5, 6])
npt.assert_equal(_draw_value(np.array(5.)), 5)

npt.assert_equal(_draw_value(tt.constant([5., 6.])), [5, 6])
assert _draw_value(tt.constant(5)) == 5
npt.assert_equal(_draw_value(2 * tt.constant([5., 6.])), [10, 12])

val = theano.shared(np.array([5., 6.]))
npt.assert_equal(_draw_value(val), [5, 6])
npt.assert_equal(_draw_value(2 * val), [10, 12])

a = tt.scalar('a')
a.tag.test_value = 6
npt.assert_equal(_draw_value(2 * a, givens=[(a, 1)]), 2)

assert _draw_value(5) == 5
assert _draw_value(5.) == 5
assert isinstance(_draw_value(5.), type(5.))
assert isinstance(_draw_value(5), type(5))

with pm.Model():
mu = 2 * tt.constant(np.array([5., 6.])) + theano.shared(np.array(5))
a = pm.Normal('a', mu=mu, sd=5, shape=2)

val1 = _draw_value(a)
val2 = _draw_value(a)
assert np.all(val1 != val2)

with pytest.raises(ValueError) as err:
_draw_value([])
err.match('Unexpected type')


class TestDrawValues(object):
def test_empty(self):
assert draw_values([]) == []

def test_vals(self):
npt.assert_equal(draw_values([np.array([5, 6])])[0], [5, 6])
npt.assert_equal(draw_values([np.array(5.)])[0], 5)

npt.assert_equal(draw_values([tt.constant([5., 6.])])[0], [5, 6])
assert draw_values([tt.constant(5)])[0] == 5
npt.assert_equal(draw_values([2 * tt.constant([5., 6.])])[0], [10, 12])

val = theano.shared(np.array([5., 6.]))
npt.assert_equal(draw_values([val])[0], [5, 6])
npt.assert_equal(draw_values([2 * val])[0], [10, 12])

def test_simple_model(self):
with pm.Model():
mu = 2 * tt.constant(np.array([5., 6.])) + theano.shared(np.array(5))
a = pm.Normal('a', mu=mu, sd=5, shape=2)

val1 = draw_values([a])
val2 = draw_values([a])
assert np.all(val1[0] != val2[0])

point = {'a': np.array([3., 4.])}
npt.assert_equal(draw_values([a], point=point), [point['a']])

def test_dep_vars(self):
with pm.Model():
mu = 2 * tt.constant(np.array([5., 6.])) + theano.shared(np.array(5))
sd = pm.HalfNormal('sd', shape=2)
tau = 1 / sd ** 2
a = pm.Normal('a', mu=mu, tau=tau, shape=2)

point = {'a': np.array([1., 2.])}
npt.assert_equal(draw_values([a], point=point), [point['a']])

with pytest.raises(theano.gof.MissingInputError):
draw_values([a])

# We need the untransformed vars
with pytest.raises(theano.gof.MissingInputError):
draw_values([a], point={'sd': np.array([2., 3.])})

val1 = draw_values([a], point={'sd_log__': np.array([2., 3.])})[0]
val2 = draw_values([a], point={'sd_log__': np.array([2., 3.])})[0]
assert np.all(val1 != val2)
Loading