def test_lambdify(): # Test lambdify with implemented functions # first test basic (sympy) lambdify f = sympy.cos yield assert_equal(lambdify(x, f(x))(0), 1) yield assert_equal(lambdify(x, 1 + f(x))(0), 2) yield assert_equal(lambdify((x, y), y + f(x))(0, 1), 2) # make an implemented function and test f = implemented_function("f", lambda x : x+100) yield assert_equal(lambdify(x, f(x))(0), 100) yield assert_equal(lambdify(x, 1 + f(x))(0), 101) yield assert_equal(lambdify((x, y), y + f(x))(0, 1), 101) # Error for functions with same name and different implementation f2 = implemented_function("f", lambda x : x+101) yield assert_raises(ValueError, lambdify, x, f(f2(x))) # our lambdify, like sympy's lambdify, can also handle tuples, # lists, dicts as expressions lam = lambdify(x, (f(x), x)) yield assert_equal(lam(3), (103, 3)) lam = lambdify(x, [f(x), x]) yield assert_equal(lam(3), [103, 3]) lam = lambdify(x, [f(x), (f(x), x)]) yield assert_equal(lam(3), [103, (103, 3)]) lam = lambdify(x, {f(x): x}) yield assert_equal(lam(3), {103: 3}) lam = lambdify(x, {f(x): x}) yield assert_equal(lam(3), {103: 3}) lam = lambdify(x, {x: f(x)}) yield assert_equal(lam(3), {3: 103})
def test_lambdify(): # Test lambdify with implemented functions # first test basic (sympy) lambdify f = sympy.cos assert_equal(lambdify(x, f(x))(0), 1) assert_equal(lambdify(x, 1 + f(x))(0), 2) assert_equal(lambdify((x, y), y + f(x))(0, 1), 2) # make an implemented function and test f = implemented_function("f", lambda x: x + 100) assert_equal(lambdify(x, f(x))(0), 100) assert_equal(lambdify(x, 1 + f(x))(0), 101) assert_equal(lambdify((x, y), y + f(x))(0, 1), 101) # Error for functions with same name and different implementation f2 = implemented_function("f", lambda x: x + 101) assert_raises(ValueError, lambdify, x, f(f2(x))) # our lambdify, like sympy's lambdify, can also handle tuples, # lists, dicts as expressions lam = lambdify(x, (f(x), x)) assert_equal(lam(3), (103, 3)) lam = lambdify(x, [f(x), x]) assert_equal(lam(3), [103, 3]) lam = lambdify(x, [f(x), (f(x), x)]) assert_equal(lam(3), [103, (103, 3)]) lam = lambdify(x, {f(x): x}) assert_equal(lam(3), {103: 3}) lam = lambdify(x, {f(x): x}) assert_equal(lam(3), {103: 3}) lam = lambdify(x, {x: f(x)}) assert_equal(lam(3), {3: 103})
def test_2d(): B1, B2 = [gen_BrownianMotion() for _ in range(2)] B1s = implemented_function("B1", B1) B2s = implemented_function("B2", B2) s, t = sympy.symbols('s', 't') e = B1s(s)+B2s(t) ee = lambdify((s,t), e) yield assert_almost_equal(ee(B1.x, B2.x), B1.y + B2.y)
def test_alias(): x = F.Term('x') f = implemented_function('f', lambda x: 2*x) g = implemented_function('g', lambda x: np.sqrt(x)) ff = F.Formula([f(x), g(x)**2]) n = F.make_recarray([2,4,5], 'x') assert_almost_equal(ff.design(n)['f(x)'], n['x']*2) assert_almost_equal(ff.design(n)['g(x)**2'], n['x'])
def test_2d(): B1, B2 = [gen_BrownianMotion() for _ in range(2)] B1s = implemented_function("B1", B1) B2s = implemented_function("B2", B2) s, t = sympy.symbols(('s', 't')) e = B1s(s) + B2s(t) ee = lambdify((s, t), e) assert_almost_equal(ee(B1.x, B2.x), B1.y + B2.y)
def natural_spline(t, knots=None, order=3, intercept=False): """ Return a Formula containing a natural spline Spline for a Term with specified `knots` and `order`. Parameters ---------- t : ``Term`` knots : None or sequence, optional Sequence of float. Default None (same as empty list) order : int, optional Order of the spline. Defaults to a cubic (==3) intercept : bool, optional If True, include a constant function in the natural spline. Default is False Returns ------- formula : Formula A Formula with (len(knots) + order) Terms (if intercept=False, otherwise includes one more Term), made up of the natural spline functions. Examples -------- >>> x = Term('x') >>> n = natural_spline(x, knots=[1,3,4], order=3) >>> xval = np.array([3,5,7.]).view(np.dtype([('x', np.float)])) >>> n.design(xval, return_float=True) array([[ 3., 9., 27., 8., 0., -0.], [ 5., 25., 125., 64., 8., 1.], [ 7., 49., 343., 216., 64., 27.]]) >>> d = n.design(xval) >>> print d.dtype.descr [('ns_1(x)', '<f8'), ('ns_2(x)', '<f8'), ('ns_3(x)', '<f8'), ('ns_4(x)', '<f8'), ('ns_5(x)', '<f8'), ('ns_6(x)', '<f8')] """ if knots is None: knots = {} fns = [] for i in range(order+1): n = 'ns_%d' % i def f(x, i=i): return x**i s = implemented_function(n, f) fns.append(s(t)) for j, k in enumerate(knots): n = 'ns_%d' % (j+i+1,) def f(x, k=k, order=order): return (x-k)**order * np.greater(x, k) s = implemented_function(n, f) fns.append(s(t)) if not intercept: fns.pop(0) ff = Formula(fns) return ff
def test_implemented_function(): # Here we check if the default returned functions are anonymous - in # the sense that we can have more than one function with the same name f = implemented_function('f', lambda x: 2 * x) g = implemented_function('f', lambda x: np.sqrt(x)) l1 = lambdify(x, f(x)) l2 = lambdify(x, g(x)) assert_equal(str(f(x)), str(g(x))) assert_equal(l1(3), 6) assert_equal(l2(3), np.sqrt(3)) # check that we can pass in a sympy function as input func = sympy.Function('myfunc') assert_false(hasattr(func, '_imp_')) f = implemented_function(func, lambda x: 2 * x) assert_true(hasattr(func, '_imp_'))
def test_implemented_function(): # Here we check if the default returned functions are anonymous - in # the sense that we can have more than one function with the same name f = implemented_function('f', lambda x: 2*x) g = implemented_function('f', lambda x: np.sqrt(x)) l1 = lambdify(x, f(x)) l2 = lambdify(x, g(x)) yield assert_equal(str(f(x)), str(g(x))) yield assert_equal(l1(3), 6) yield assert_equal(l2(3), np.sqrt(3)) # check that we can pass in a sympy function as input func = sympy.Function('myfunc') yield assert_false(hasattr(func, 'alias')) f = implemented_function(func, lambda x: 2*x) yield assert_true(hasattr(func, 'alias'))
def interp(times, values, fill=0, name=None, **kw): """ Generic interpolation function of t given `times` and `values` Imterpolator such that: f(times[i]) = values[i] if t < times[0] or t > times[-1]: f(t) = fill See ``scipy.interpolate.interp1d`` for details of interpolation types and other keyword arguments. Default is 'kind' is linear, making this function, by default, have the same behavior as ``linear_interp``. Parameters ---------- times : array-like Increasing sequence of times values : array-like Values at the specified times fill : None or float, optional Value on the interval (-np.inf, times[0]). Default 0. If None, raises error outside bounds name : None or str, optional Name of symbolic expression to use. If None, a default is used. \*\*kw : keyword args, optional passed to ``interp1d`` Returns ------- f : sympy expression A Function of t. Examples -------- >>> s = interp([0,4,5.],[2.,4,6]) >>> tval = np.array([-0.1,0.1,3.9,4.1,5.1]) >>> res = lambdify_t(s)(tval) 0 outside bounds by default >>> np.allclose(res, [0, 2.05, 3.95, 4.2, 0]) True """ if not fill is None: if kw.get('bounds_error') is True: raise ValueError('fill conflicts with bounds error') fv = kw.get('fill_value') if not (fv is None or fv is fill or fv == fill): # allow for fill=np.nan raise ValueError('fill conflicts with fill_value') kw['bounds_error'] = False kw['fill_value'] = fill interpolator = interp1d(times, values, **kw) # make a new name if none provided if name is None: name = 'interp%d' % interp.counter interp.counter += 1 s = implemented_function(name, interpolator) return s(T)
def interp(times, values, fill=0, name=None, **kw): """ Generic interpolation function of t given `times` and `values` Imterpolator such that: f(times[i]) = values[i] if t < times[0]: f(t) = fill See ``scipy.interpolate.interp1d`` for details of interpolation types and other keyword arguments. Default is 'kind' is linear, making this function, by default, have the same behavior as ``linear_interp``. Parameters ---------- times : array-like Increasing sequence of times values : array-like Values at the specified times fill : float, optional Value on the interval (-np.inf, times[0]). Default 0. name : None or str, optional Name of symbolic expression to use. If None, a default is used. **kw : keyword args, optional passed to ``interp1d`` Returns ------- f : sympy expression A Function of t. Examples -------- >>> s = interp([0,4,5.],[2.,4,6], bounds_error=False) >>> tval = np.array([-0.1,0.1,3.9,4.1,5.1]) >>> res = lambdify_t(s)(tval) >>> # nans outside bounds >>> np.isnan(res) array([ True, False, False, False, True], dtype=bool) >>> # interpolated values otherwise >>> np.allclose(res[1:-1], [2.05, 3.95, 4.2]) True """ interpolator = interp1d(times, values, **kw) # make a new name if none provided if name is None: name = "interp%d" % interp.counter interp.counter += 1 s = implemented_function(name, interpolator) return s(T)
def test_1d(): B = gen_BrownianMotion() Bs = implemented_function("B", B) t = sympy.Symbol('t') expr = 3*sympy.exp(Bs(t)) + 4 expected = 3*np.exp(B.y)+4 ee_vec = lambdify(t, expr) yield assert_almost_equal(ee_vec(B.x), expected) # with any arbitrary symbol b = sympy.Symbol('b') expr = 3*sympy.exp(Bs(b)) + 4 ee_vec = lambdify(b, expr) yield assert_almost_equal(ee_vec(B.x), expected)
def test_1d(): B = gen_BrownianMotion() Bs = implemented_function("B", B) t = sympy.Symbol('t') expr = 3 * sympy.exp(Bs(t)) + 4 expected = 3 * np.exp(B.y) + 4 ee_vec = lambdify(t, expr, "numpy") assert_almost_equal(ee_vec(B.x), expected) # with any arbitrary symbol b = sympy.Symbol('b') expr = 3 * sympy.exp(Bs(b)) + 4 ee_vec = lambdify(b, expr, "numpy") assert_almost_equal(ee_vec(B.x), expected)
def step_function(times, values, name=None, fill=0): """ Right-continuous step function of time t Function of t such that f(times[i]) = values[i] if t < times[0]: f(t) = fill Parameters ---------- times : (N,) sequence Increasing sequence of times values : (N,) sequence Values at the specified times fill : float Value on the interval (-np.inf, times[0]) name : str Name of symbolic expression to use. If None, a default is used. Returns ------- f_t : sympy expr Sympy expression f(t) where f is a sympy implemented anonymous function of time that implements the step function. To get the numerical version of the function, use ``lambdify_t(f_t)`` Examples -------- >>> s = step_function([0,4,5],[2,4,6]) >>> tval = np.array([-0.1,3.9,4.1,5.1]) >>> lam = lambdify_t(s) >>> lam(tval) array([ 0., 2., 4., 6.]) """ if name is None: name = 'step%d' % step_function.counter step_function.counter += 1 def _imp(x): x = np.asarray(x) f = np.zeros(x.shape) + fill for time, val in zip(times, values): f[x >= time] = val return f s = implemented_function(name, _imp) return s(T)
def linBspline(knots): """ Create a linear B spline that is zero outside [knots[0], knots[-1]] (knots is assumed to be sorted). """ fns = [] knots = np.array(knots) for i in range(knots.shape[0]-2): name = 'bs_%s' % i k1, k2, k3 = knots[i:i+3] d1 = k2-k1 def anon(x,k1=k1,k2=k2,k3=k3): return ((x-k1) / d1 * np.greater(x, k1) * np.less_equal(x, k2) + (k3-x) / d1 * np.greater(x, k2) * np.less(x, k3)) fns.append(implemented_function(name, anon)) return fns
def linBspline(knots): """ Create linear B spline that is zero outside [knots[0], knots[-1]] (knots is assumed to be sorted). """ fns = [] knots = np.array(knots) for i in range(knots.shape[0] - 2): name = 'bs_%s' % i k1, k2, k3 = knots[i:i + 3] d1 = k2 - k1 def anon(x, k1=k1, k2=k2, k3=k3): return ((x - k1) / d1 * np.greater(x, k1) * np.less_equal(x, k2) + (k3 - x) / d1 * np.greater(x, k2) * np.less(x, k3)) fns.append(implemented_function(name, anon)) return fns
def define(name, expr): """ Create function of t expression from arbitrary expression `expr` Take an arbitrarily complicated expression `expr` of 't' and make it an expression that is a simple function of t, of form ``'%s(t)' % name`` such that when it evaluates (via ``lambdify``) it has the right values. Parameters ---------- expr : sympy expression with only 't' as a Symbol name : str Returns ------- nexpr: sympy expression Examples -------- >>> t = Term('t') >>> expr = t**2 + 3*t >>> print expr #doctest: +SYMPY_EQUAL 3*t + t**2 >>> newexpr = define('f', expr) >>> print newexpr f(t) >>> f = lambdify_t(newexpr) >>> f(4) 28 >>> 3*4+4**2 28 """ # make numerical implementation of expression v = lambdify(T, expr, "numpy") # convert numerical implementation to sympy function f = implemented_function(name, v) # Return expression that is function of time return f(T)
def taylor_approx(hrf2decompose, time=None, delta=None): """ A Taylor series approximation of an HRF shifted by times `delta` Returns original HRF and gradient of HRF Parameters ---------- hrf2decompose : sympy expression An expression that can be lambdified as a function of 't'. This is the HRF to be expanded in PCA time : None or np.ndarray, optional None gives default value of np.linspace(-15,50,3251) chosen to match fMRIstat implementation. This corresponds to a time interval of 0.02. Presumed to be equally spaced. delta : None or np.ndarray, optional None results in default value of np.arange(-4.5, 4.6, 0.1) chosen to match fMRIstat implementation. Returns ------- hrf : [sympy expressions] Sequence length 2 comprising (`hrf2decompose`, ``dhrf``) where ``dhrf`` is the first derivative of `hrf2decompose`. approx : TODO References ---------- Liao, C.H., Worsley, K.J., Poline, J-B., Aston, J.A.D., Duncan, G.H., Evans, A.C. (2002). \'Estimating the delay of the response in fMRI data.\' NeuroImage, 16:593-606. """ if time is None: time = np.linspace(-15,50,3251) dt = time[1] - time[0] if delta is None: delta = np.arange(-4.5, 4.6, 0.1) # make numerical implementation from hrf function and symbol t. # hrft returns function values when called with values for time as # input. hrft = lambdify_t(hrf2decompose(T)) # interpolator for negative gradient of hrf dhrft = interp1d(time, -np.gradient(hrft(time), dt), bounds_error=False, fill_value=0.) dhrft.y *= 2 # Create stack of time-shifted HRFs. Time varies over row, delta # over column. ts_hrf_vals = np.array([hrft(time - d) for d in delta]).T # hrf, dhrf W = np.array([hrft(time), dhrft(time)]).T # regress hrf, dhrf at times against stack of time-shifted hrfs WH = np.dot(npl.pinv(W), ts_hrf_vals) # put these into interpolators to get estimated coefficients for any # value of delta coef = [interp1d(delta, w, bounds_error=False, fill_value=0.) for w in WH] def approx(time, delta): value = (coef[0](delta) * hrft(time) + coef[1](delta) * dhrft(time)) return value approx.coef = coef approx.components = [hrft, dhrft] (approx.theta, approx.inverse, approx.dinverse, approx.forward, approx.dforward) = invertR(delta, approx.coef) dhrf = implemented_function('d%s' % str(hrf2decompose), dhrft) return [hrf2decompose, dhrf], approx
# Glover canonical HRF models # they are both Sympy objects def _getint(f, dt=0.02, t=50): # numerical integral of function lf = lambdify_t(f) tt = np.arange(dt,t+dt,dt) return lf(tt).sum() * dt _gexpr = gamma_expr(5.4, 5.2) - 0.35 * gamma_expr(10.8, 7.35) _gexpr = _gexpr / _getint(_gexpr) _glover = lambdify_t(_gexpr) glover = implemented_function('glover', _glover) glovert = lambdify_t(glover(T)) # Derivative of Glover HRF _dgexpr = _gexpr.diff(T) dpos = sympy.Derivative((T >= 0), T) _dgexpr = _dgexpr.subs(dpos, 0) _dgexpr = _dgexpr / _getint(sympy_abs(_dgexpr)) _dglover = lambdify_t(_dgexpr) dglover = implemented_function('dglover', _dglover) dglovert = lambdify_t(dglover(T)) del(_glover); del(_gexpr); del(dpos); del(_dgexpr); del(_dglover) # AFNI's HRF
def spectral_decomposition(hrf2decompose, time=None, delta=None, ncomp=2): """ PCA decomposition of symbolic HRF shifted over time Perform a PCA expansion of a symbolic HRF, time shifted over the values in delta, returning the first ncomp components. This smooths out the HRF as compared to using a Taylor series approximation. Parameters ---------- hrf2decompose : sympy expression An expression that can be lambdified as a function of 't'. This is the HRF to be expanded in PCA time : None or np.ndarray, optional None gives default value of np.linspace(-15,50,3251) chosen to match fMRIstat implementation. This corresponds to a time interval of 0.02. Presumed to be equally spaced. delta : None or np.ndarray, optional None results in default value of np.arange(-4.5, 4.6, 0.1) chosen to match fMRIstat implementation. ncomp : int, optional Number of principal components to retain. Returns ------- hrf : [sympy expressions] A sequence length `ncomp` of symbolic HRFs that are the principal components. approx : TODO """ if time is None: time = np.linspace(-15,50,3251) dt = time[1] - time[0] if delta is None: delta = np.arange(-4.5, 4.6, 0.1) # make numerical implementation from hrf function and symbol t. # hrft returns function values when called with values for time as # input. hrft = lambdify_t(hrf2decompose(T)) # Create stack of time-shifted HRFs. Time varies over row, delta # over column. ts_hrf_vals = np.array([hrft(time - d) for d in delta]).T ts_hrf_vals = np.nan_to_num(ts_hrf_vals) # PCA U, S, V = npl.svd(ts_hrf_vals, full_matrices=0) # make interpolators from the generated bases basis = [] for i in range(ncomp): b = interp1d(time, U[:, i], bounds_error=False, fill_value=0.) # normalize components witn integral of abs of first component if i == 0: d = np.fabs((b(time) * dt).sum()) b.y /= d basis.append(b) # reconstruct time courses for all bases W = np.array([b(time) for b in basis]).T # regress basis time courses against original time shifted time # courses, ncomps by len(delta) parameter matrix WH = np.dot(npl.pinv(W), ts_hrf_vals) # put these into interpolators to get estimated coefficients for any # value of delta coef = [interp1d(delta, w, bounds_error=False, fill_value=0.) for w in WH] # swap sign of first component to match that of input HRF. Swap # other components if we swap the first, to standardize signs of # components across SVD implementations. if coef[0](0) < 0: # coefficient at time shift of 0 for i in range(ncomp): coef[i].y *= -1. basis[i].y *= -1. def approx(time, delta): value = 0 for i in range(ncomp): value += coef[i](delta) * basis[i](time) return value approx.coef = coef approx.components = basis (approx.theta, approx.inverse, approx.dinverse, approx.forward, approx.dforward) = invertR(delta, approx.coef) # construct aliased functions from bases symbasis = [] for i, b in enumerate(basis): symbasis.append( implemented_function('%s%d' % (str(hrf2decompose), i), b)) return symbasis, approx
def natural_spline(t, knots=None, order=3, intercept=False): """ Return a Formula containing a natural spline Spline for a Term with specified `knots` and `order`. Parameters ---------- t : ``Term`` knots : None or sequence, optional Sequence of float. Default None (same as empty list) order : int, optional Order of the spline. Defaults to a cubic (==3) intercept : bool, optional If True, include a constant function in the natural spline. Default is False Returns ------- formula : Formula A Formula with (len(knots) + order) Terms (if intercept=False, otherwise includes one more Term), made up of the natural spline functions. Examples -------- >>> x = Term('x') >>> n = natural_spline(x, knots=[1,3,4], order=3) >>> xval = np.array([3,5,7.]).view(np.dtype([('x', np.float)])) >>> n.design(xval, return_float=True) array([[ 3., 9., 27., 8., 0., -0.], [ 5., 25., 125., 64., 8., 1.], [ 7., 49., 343., 216., 64., 27.]]) >>> d = n.design(xval) >>> print d.dtype.descr [('ns_1(x)', '<f8'), ('ns_2(x)', '<f8'), ('ns_3(x)', '<f8'), ('ns_4(x)', '<f8'), ('ns_5(x)', '<f8'), ('ns_6(x)', '<f8')] """ if knots is None: knots = {} fns = [] for i in range(order + 1): n = 'ns_%d' % i def f(x, i=i): return x**i s = implemented_function(n, f) fns.append(s(t)) for j, k in enumerate(knots): n = 'ns_%d' % (j + i + 1, ) def f(x, k=k, order=order): return (x - k)**order * np.greater(x, k) s = implemented_function(n, f) fns.append(s(t)) if not intercept: fns.pop(0) ff = Formula(fns) return ff