You’re just doing a deterministic transformation of variables, so I think you’re overthinking it? As least as written in the PyMC2 code, the function will know both r
and f
when it’s time to do the integral, so you just need to re-write the function to be “pytensor aware”.
Actually the cumulative_trapezoid
function is quite simple. Here is a stripped down version with all the input checks removed:
def tupleset(t, i, value):
l = list(t)
l[i] = value
return tuple(l)
def cumulative_trapezoid_simple(y, x=None, dx=1.0, axis=-1):
y = np.asarray(y)
if x is None:
d = dx
else:
x = np.asarray(x)
if x.ndim == 1:
d = np.diff(x)
else:
d = np.diff(x, axis=axis)
nd = len(y.shape)
slice1 = tupleset((slice(None),)*nd, axis, slice(1, None))
slice2 = tupleset((slice(None),)*nd, axis, slice(None, -1))
res = np.cumsum(d * (y[slice1] + y[slice2]) / 2.0, axis=axis)
return res
It’s just some fancy slicing (which pytensor supports), differencing (supported), and cumsum (supported). Actually the only thing that won’t work is len(y.shape)
, but it’s bizarre they used this anyway (ndim
exists, and they know it since it was used for x
). So switch that, and switch the np
to pt
and get the following result:
from pytensor import tensor as pt
def cumulative_trapezoid_pt(y, x, dx=1.0, axis=-1):
if x is None:
d = dx
else:
if x.ndim == 1:
d = pt.diff(x)
else:
d = pt.diff(x, axis=axis)
nd = y.ndim
slice1 = tupleset((slice(None),)*nd, axis, slice(1, None))
slice2 = tupleset((slice(None),)*nd, axis, slice(None, -1))
res = pt.cumsum(d * (y[slice1] + y[slice2]) / 2.0, axis=axis)
return res
This function you can drop into your PyMC model. Here’s a small numerical check to make sure it works:
f = lambda x: 2 * x
x = np.linspace(0, 10, 100)
y = f(x)
# This code compiles the pytensor function, you **will not** need this in your pymc model
# it's included for completeness/transparency
x_pt = pt.dvector('x')
y_pt = pt.dvector('y')
res = cumulative_trapezoid_pt(y_pt, x_pt)
cumint = pytensor.function([y_pt, x_pt], res)
# Here are the actual checks
from scipy import integrate
np.allclose(integrate.cumulative_trapezoid(y, x), cumint(y, x)) # Out: True
np.allclose(cumint(y, x), x[1:] ** 2) # Out: True