def test_pi_max_shanks_ind(self): """4 * sum((-1)**k * (2 * k+1)**(-1))""" nn = 10 seq = np.array([4*sum((-1)**n/(2*n+1) for n in range(m)) for m in range(1, nn)]) assert_allclose(shanks(seq, -50), np.pi, atol=1e-8)
def __call__(self, s, a=0, b=np.inf): """transform f(r, *args) at s Parameters ---------- s : scalar transform variable, i.e. coordinate to evaluate transform at a, b : float, optional limits of integration. default a=0, b=np.inf. A hankel transform is usually from 0 to infinity. However, if you have a function that is defined on [a,b] and zero elsewhere then you can restrict integration to that interval (shanks_ind will be ignored if b!=np.inf). Returns ------- F : float Transformed functin evaluated at s. err_est : float Error estimate. For each interval (i.e. between bessel zeros and any specified points) sum up 200*abs(G-K)**1.5. The error is calculated before any shanks extrapolation so the error is just a measure of the difference between the coarse Gauss quadrature and the finer Kronrod quadrature. """ integrand = functools.partial(self._integrand, s) zeros = self.jn_0s / s if not self.points is None: #zeros becomes both zeros and interesting points/singularities zeros = np.unique( list(zeros) + list(self.points[(self.points < zeros[-1]) & (self.points > 0)])) if (a != 0) or (b != np.inf): zeros = np.unique(zeros.clip(a, b)) #1st segment igral0, err_est0 = gk_quad(integrand, zeros[0], zeros[1], self.args, self.ng0) #remaining segments if len(zeros) > 2: igralm, err_estm = gk_quad(integrand, zeros[1:-1], zeros[2:], self.args, self.ng) else: return igral0[0], err_est0[0] if (self.shanks_ind is None) or (b != np.inf): igral = igral0 + np.sum(igralm) else: igralm.cumsum(out=igralm) igral = igral0 + shanks(igralm, self.shanks_ind) err_est = (200 * np.abs(err_est0))**1.5 + np.sum( (200 * np.abs(err_estm))**1.5) return igral[0], err_est[0]
def test_pi_max_shanks_ind(self): """4 * sum((-1)**k * (2 * k+1)**(-1))""" nn = 10 seq = np.array([ 4 * sum((-1)**n / (2 * n + 1) for n in range(m)) for m in range(1, nn) ]) assert_allclose(shanks(seq, -50), np.pi, atol=1e-8)
def vcosine_transform(f, s, args=(), m=20, ng=20, shanks_ind=None): """Cosine transform of f(x) at transform variable s This is a vectorized cosine transform. Parameters ---------- f : function or method Function to apply cosine trasnform to. f is called with f(x, *args). s : 1d array Coordinate(s) to evaluate transform at. args : tuple, optional arguments to pass to f m : int, optional Number of segments to break the integration interval into. Each segment will be between the zeros of the cos function, Default m=20. ng : [2-20, 32, 64, 100], optional Number of gauss points to use in integration., Default ng=20. shanks_ind : int, optional Start position of intervals to start shanks extrapolation. Default shanks_ind=None i.e. no extrapolation. Be careful when using shanks extrapolation; make sure you only begin to use it after the intgrand is well behaved. Returns ------- f : 1d array of float Value of transform at s Notes ----- Careful with singularities. Because there is no way to increase the integration points at a particular spot the infinite behaviur may not be captured well. For example x**-0.5 should transform to sqrt(pi/2*w) but due to the sinularity at x=0 it does not converge well even using ng=100. """ si = np.atleast_1d(s) xk_, wk = gauss_legendre_abscissae_and_weights(ng) # integration intervals zeros = np.zeros(m + 1, dtype=float) zeros[1:] = (2 * np.arange(m) + 1) * np.pi/2 aj = zeros[0:-1] bj = zeros[1:] #dims of array: # 0 or i dim is each transform coordinate # 1 or j dim is each integration interval # 2 or k dim is each integration point # 2 dim will be summed to get integral of each interval # 1 dim will be summed or shanks'ed to give transform at each coord # si = si[:, np.newaxis, np.newaxis] aj = aj[np.newaxis, :, np.newaxis] bj = bj[np.newaxis, :, np.newaxis] xk_ = xk_[np.newaxis, np.newaxis, :] wk = wk[np.newaxis, np.newaxis, :] aij = aj / si bij = bj / si bma = (bij - aij) / 2 # b minus a bpa = (aij + bij) /2 # b plus a xijk = bma * xk_ + bpa # xj_ are in [-1, 1] so need to transform to [a, b] fijk = f(xijk, *args) fijk *= np.cos(si * xijk) # fijk *= xijk igral = bma[:,:,0] * np.sum(fijk * wk, axis=2) if shanks_ind is None: return igral.sum(axis=1) else: #extrapolate igral.cumsum(axis=1 , out=igral) return shanks(igral, shanks_ind)
def vhankel_transform(f, r, args=(), order=0, m=20, ng=20, shanks_ind=None): """Hankel transform of f(r) This is a vectorised Hankel transform Parameters ---------- f : function or method Function to apply hankel trasnform to. f is called with f(s, *args). r : 1d array Coordinate(s) to evaluate transform at. args : tuple, optional Arguments to pass to f, default args=(). order : integer, optional Order of hankel transform. Default order=0. m : int, optional Number of segments to break the integration interval into. Each segment will be between the zeros of the bessel function. Default m=20. ng : [2-20, 32, 64, 100], optional Number of gauss points to use in integration. Default ng=20. shanks_ind : int, optional Start position of intervals to start shanks extrapolation. Default shanks_ind=None i.e. no extrapolation. Be careful when using shanks extrapolation; make sure you only begin to use it after the intgrand is well behaved. Returns ------- f : 1d array of float Value of transform at r. Notes ----- The Hankel Transform of order :math:`\\nu` is given by: .. math:: F_\\nu(s)=\mathcal{H}_\\nu\\{f(r)\\} = \\int_0^{\\infty}rf(r)J_\\nu(sr)\\,\\mathrm{d}r Provided :math:`\\nu\\gt1/2` the inverse hankel transform is the same as the normal transform: .. math:: f(r)=\mathcal{H}_{\\nu}^{-1}\\{F_{\\nu}(s)\\} = \\int_0^{\\infty}sF_\\nu(s)J_\\nu(sr)\\,\\mathrm{d}s Note that because this implementation does not allow for input of extra point to break up the integration inteval, there is no way to account for singularities and other oscillations. If you need this control then see the HankelTransform class which is not vectorized but provides a few more options. See Also -------- HankelTransform : Non vectorised Hankel transform. References ---------- .. [1] Piessens, Robert. 2000. 'Chapter 9 The Hankel Transform'. In The Transforms and Applications Handbook, edited by Alexander D. Poularikas, 2nd edition. Boca Raton, USA: CRC Press. """ ri = np.atleast_1d(r) xk_, wk = gauss_legendre_abscissae_and_weights(ng) # integration intervals zeros = np.zeros(m + 1, dtype=float) zeros[1:] = jn_zeros(order, m) aj = zeros[0:-1] bj = zeros[1:] ri = ri[:, np.newaxis, np.newaxis] aj = aj[np.newaxis, :, np.newaxis] bj = bj[np.newaxis, :, np.newaxis] xk_ = xk_[np.newaxis, np.newaxis, :] wk = wk[np.newaxis, np.newaxis, :] aij = aj / ri bij = bj / ri bma = (bij - aij) / 2 # b minus a bpa = (aij + bij) /2 # b plus a xijk = bma * xk_ + bpa # xj_ are in [-1, 1] so need to transform to [a, b] fijk = f(xijk, *args) fijk *= jn(order, ri * xijk) fijk *= xijk igral = bma[:,:,0] * np.sum(fijk * wk, axis=2) if shanks_ind is None: return igral.sum(axis=1) else: #extrapolate igral.cumsum(axis=1 , out=igral) return shanks(igral, shanks_ind)
def __call__(self, s, a=0, b=np.inf): """transform f(r, *args) at s Parameters ---------- s : scalar transform variable, i.e. coordinate to evaluate transform at a, b : float, optional limits of integration. default a=0, b=np.inf. A hankel transform is usually from 0 to infinity. However, if you have a function that is defined on [a,b] and zero elsewhere then you can restrict integration to that interval (shanks_ind will be ignored if b!=np.inf). Returns ------- F : float Transformed functin evaluated at s. err_est : float Error estimate. For each interval (i.e. between bessel zeros and any specified points) sum up 200*abs(G-K)**1.5. The error is calculated before any shanks extrapolation so the error is just a measure of the difference between the coarse Gauss quadrature and the finer Kronrod quadrature. """ integrand = functools.partial(self._integrand, s) zeros = self.jn_0s / s if not self.points is None: #zeros becomes both zeros and interesting points/singularities zeros = np.unique(list(zeros) + list(self.points[(self.points < zeros[-1]) & (self.points>0)])) if (a!=0) or (b!=np.inf): zeros = np.unique(zeros.clip(a, b)) #1st segment igral0, err_est0 = gk_quad(integrand, zeros[0], zeros[1], self.args, self.ng0) #remaining segments if len(zeros)>2: igralm, err_estm = gk_quad(integrand, zeros[1:-1], zeros[2:], self.args, self.ng) else: return igral0[0], err_est0[0] if (self.shanks_ind is None) or (b!=np.inf): igral = igral0 + np.sum(igralm) else: igralm.cumsum(out=igralm) igral = igral0 + shanks(igralm, self.shanks_ind) err_est = (200*np.abs(err_est0))**1.5 + np.sum((200*np.abs(err_estm))**1.5) return igral[0], err_est[0]
def vcosine_transform(f, s, args=(), m=20, ng=20, shanks_ind=None): """Cosine transform of f(x) at transform variable s This is a vectorized cosine transform. Parameters ---------- f : function or method Function to apply cosine trasnform to. f is called with f(x, *args). s : 1d array Coordinate(s) to evaluate transform at. args : tuple, optional arguments to pass to f m : int, optional Number of segments to break the integration interval into. Each segment will be between the zeros of the cos function, Default m=20. ng : [2-20, 32, 64, 100], optional Number of gauss points to use in integration., Default ng=20. shanks_ind : int, optional Start position of intervals to start shanks extrapolation. Default shanks_ind=None i.e. no extrapolation. Be careful when using shanks extrapolation; make sure you only begin to use it after the intgrand is well behaved. Returns ------- f : 1d array of float Value of transform at s Notes ----- Careful with singularities. Because there is no way to increase the integration points at a particular spot the infinite behaviur may not be captured well. For example x**-0.5 should transform to sqrt(pi/2*w) but due to the sinularity at x=0 it does not converge well even using ng=100. """ si = np.atleast_1d(s) xk_, wk = gauss_legendre_abscissae_and_weights(ng) # integration intervals zeros = np.zeros(m + 1, dtype=float) zeros[1:] = (2 * np.arange(m) + 1) * np.pi / 2 aj = zeros[0:-1] bj = zeros[1:] #dims of array: # 0 or i dim is each transform coordinate # 1 or j dim is each integration interval # 2 or k dim is each integration point # 2 dim will be summed to get integral of each interval # 1 dim will be summed or shanks'ed to give transform at each coord # si = si[:, np.newaxis, np.newaxis] aj = aj[np.newaxis, :, np.newaxis] bj = bj[np.newaxis, :, np.newaxis] xk_ = xk_[np.newaxis, np.newaxis, :] wk = wk[np.newaxis, np.newaxis, :] aij = aj / si bij = bj / si bma = (bij - aij) / 2 # b minus a bpa = (aij + bij) / 2 # b plus a xijk = bma * xk_ + bpa # xj_ are in [-1, 1] so need to transform to [a, b] fijk = f(xijk, *args) fijk *= np.cos(si * xijk) # fijk *= xijk igral = bma[:, :, 0] * np.sum(fijk * wk, axis=2) if shanks_ind is None: return igral.sum(axis=1) else: #extrapolate igral.cumsum(axis=1, out=igral) return shanks(igral, shanks_ind)
def vhankel_transform(f, r, args=(), order=0, m=20, ng=20, shanks_ind=None): """Hankel transform of f(r) This is a vectorised Hankel transform Parameters ---------- f : function or method Function to apply hankel trasnform to. f is called with f(s, *args). r : 1d array Coordinate(s) to evaluate transform at. args : tuple, optional Arguments to pass to f, default args=(). order : integer, optional Order of hankel transform. Default order=0. m : int, optional Number of segments to break the integration interval into. Each segment will be between the zeros of the bessel function. Default m=20. ng : [2-20, 32, 64, 100], optional Number of gauss points to use in integration. Default ng=20. shanks_ind : int, optional Start position of intervals to start shanks extrapolation. Default shanks_ind=None i.e. no extrapolation. Be careful when using shanks extrapolation; make sure you only begin to use it after the intgrand is well behaved. Returns ------- f : 1d array of float Value of transform at r. Notes ----- The Hankel Transform of order :math:`\\nu` is given by: .. math:: F_\\nu(s)=\mathcal{H}_\\nu\\{f(r)\\} = \\int_0^{\\infty}rf(r)J_\\nu(sr)\\,\\mathrm{d}r Provided :math:`\\nu\\gt1/2` the inverse hankel transform is the same as the normal transform: .. math:: f(r)=\mathcal{H}_{\\nu}^{-1}\\{F_{\\nu}(s)\\} = \\int_0^{\\infty}sF_\\nu(s)J_\\nu(sr)\\,\\mathrm{d}s Note that because this implementation does not allow for input of extra point to break up the integration inteval, there is no way to account for singularities and other oscillations. If you need this control then see the HankelTransform class which is not vectorized but provides a few more options. See Also -------- HankelTransform : Non vectorised Hankel transform. References ---------- .. [1] Piessens, Robert. 2000. 'Chapter 9 The Hankel Transform'. In The Transforms and Applications Handbook, edited by Alexander D. Poularikas, 2nd edition. Boca Raton, USA: CRC Press. """ ri = np.atleast_1d(r) xk_, wk = gauss_legendre_abscissae_and_weights(ng) # integration intervals zeros = np.zeros(m + 1, dtype=float) zeros[1:] = jn_zeros(order, m) aj = zeros[0:-1] bj = zeros[1:] ri = ri[:, np.newaxis, np.newaxis] aj = aj[np.newaxis, :, np.newaxis] bj = bj[np.newaxis, :, np.newaxis] xk_ = xk_[np.newaxis, np.newaxis, :] wk = wk[np.newaxis, np.newaxis, :] aij = aj / ri bij = bj / ri bma = (bij - aij) / 2 # b minus a bpa = (aij + bij) / 2 # b plus a xijk = bma * xk_ + bpa # xj_ are in [-1, 1] so need to transform to [a, b] fijk = f(xijk, *args) fijk *= jn(order, ri * xijk) fijk *= xijk igral = bma[:, :, 0] * np.sum(fijk * wk, axis=2) if shanks_ind is None: return igral.sum(axis=1) else: #extrapolate igral.cumsum(axis=1, out=igral) return shanks(igral, shanks_ind)