I have three question regarding decorators which I am not able to find answer to :
Q1)What do the arguments to decorators in PyMC (@Deterministic, @Stochastic) denote ?
Q2)
@pymc.stochastic(dtype=int)
def switchpoint(value=10, t_l=0, t_h=110):
def logp(value, t_l, t_h):
if value > t_h or value < t_l:
return -np.inf
else:
return -np.log(t_h - t_l + 1)
def random(t_l, t_h):
from numpy.random import random
return np.round( (t_l - t_h) * random() ) + t_l
1)print switchpoint.logp #prints log-probability as expected
2)print switchpoint.random #does not generate the random number
3)print switchpoint.random() # generates a random number
4)print switchpoint.logp() #error
If 2 did not work and 3 worked then 1 should not have worked and instaed 4 should have worked (which is opposite of what I observed). Can someone explain what is going on ?
Q3)
@pymc.stochastic(dtype=int)
def switchpoint(value=1900, t_l=1851, t_h=1962):
if value > t_h or value < t_l:
# Invalid values
return -np.inf
else:
# Uniform log-likelihood
return -np.log(t_h - t_l + 1)
Here it is not specified that it is logp
still if I type switchpoint.logp
, this piece of code is executed ?
Q1) The meaning of all the arguments to stochastic is documented here. The arguments to deterministic are the same, plus the additional ones documented here.
Q2) The difference in behavior is that there is some magic inside PyMC that actually executes the switchpoint.logp
function and turns it into a Python property
, while switchpoint.random
doesn't get this treatment, and is kept as a function.
If you're curious about what's actually going on, here's some of the relevant the source:
def get_logp(self):
if self.verbose > 1:
print '\t' + self.__name__ + ': log-probability accessed.'
logp = self._logp.get()
if self.verbose > 1:
print '\t' + self.__name__ + ': Returning log-probability ', logp
try:
logp = float(logp)
except:
raise TypeError, self.__name__ + ': computed log-probability ' + str(logp) + ' cannot be cast to float'
if logp != logp:
raise ValueError, self.__name__ + ': computed log-probability is NaN'
# Check if the value is smaller than a double precision infinity:
if logp <= d_neg_inf:
if self.verbose > 0:
raise ZeroProbability, self.errmsg + ": %s" %self._parents.value
else:
raise ZeroProbability, self.errmsg
return logp
def set_logp(self,value):
raise AttributeError, 'Potential '+self.__name__+'\'s log-probability cannot be set.'
logp = property(fget = get_logp, fset=set_logp, doc="Self's log-probability value conditional on parents.")
There's some other stuff going on there, like during the logp
function into something called a LazyFunction
, but that's the basic idea.
Q3) The stochastic
decorator has some (more) magic in it that uses code introspection to determine if random
and logp
sub functions are defined inside switchpoint
. If they are, it uses the logp
sub-function to compute logp
, if not, it just uses switchpoint
itself. That source code for that is here:
# This gets used by stochastic to check for long-format logp and random:
if probe:
# Define global tracing function (I assume this is for debugging??)
# No, it's to get out the logp and random functions, if they're in there.
def probeFunc(frame, event, arg):
if event == 'return':
locals = frame.f_locals
kwds.update(dict((k,locals.get(k)) for k in keys))
sys.settrace(None)
return probeFunc
sys.settrace(probeFunc)
# Get the functions logp and random (complete interface).
# Disable special methods to prevent the formation of a hurricane of Deterministics
cur_status = check_special_methods()
disable_special_methods()
try:
__func__()
except:
if 'logp' in keys:
kwds['logp']=__func__
else:
kwds['eval'] =__func__
# Reenable special methods.
if cur_status:
enable_special_methods()
for key in keys:
if not kwds.has_key(key):
kwds[key] = None
for key in ['logp', 'eval']:
if key in keys:
if kwds[key] is None:
kwds[key] = __func__
Again, there's some more stuff going on, and it's fairly complicated, but that's the basic idea.