Source code for boutpy.boututils.calculus

""" Derivatives and integrals of periodic and non-periodic functions

B.Dudson, University of York, Nov 2009
"""

__all__ = ['deriv', 'integrate']

from numpy import zeros, arange, pi
from numpy.fft import rfft, irfft


[docs]def deriv(*args, **kwargs): """Take derivative of 1D array. result = deriv(y) result = deriv(x, y) Parameters ---------- x, y : ndarray If ``x`` is not given, periodic : bool, optional, defalut: False Domain is periodic if True. Returns ------- deriv : ndarray Derivatives of 1D array. """ nargs = len(args) if nargs == 1: var = args[0] x = arange(var.size) elif nargs == 2: x = args[0] var = args[1] else: raise RuntimeError("deriv must be given 1 or 2 arguments") try: periodic = kwargs['periodic'] except: periodic = False n = var.size if periodic: # Use FFTs to take derivatives f = rfft(var) f[0] = 0.0 # Zero constant term if n % 2 == 0: # Even n for i in arange(1, n / 2): f[i] *= 2.0j * pi * float(i) / float(n) f[-1] = 0.0 # Nothing from Nyquist frequency else: # Odd n for i in arange(1, (n - 1) / 2 + 1): f[i] *= 2.0j * pi * float(i) / float(n) return irfft(f) else: # Non-periodic function result = zeros(n) # Create empty array if n > 2: for i in arange(1, n - 1): # 2nd-order central difference in the middle of the domain result[i] = (var[i + 1] - var[i - 1]) / (x[i + 1] - x[i - 1]) # Use left,right-biased stencils on edges (2nd order) result[0] = (-1.5 * var[0] + 2. * var[1] - 0.5 * var[2]) / (x[1] - x[0]) result[n - 1] = (1.5 * var[n - 1] - 2. * var[n - 2] + 0.5 * var[n - 3]) / (x[n - 1] - x[n - 2]) elif n == 2: # Just 1st-order difference for both points result[0] = result[1] = (var[1] - var[0]) / (x[1] - x[0]) elif n == 1: result[0] = 0.0 return result
[docs]def integrate(var, periodic=False): """Integrate a 1D array Return array is the same size as the input """ if periodic: # Use FFT f = rfft(var) n = var.size # Zero frequency term result = f[0].real * arange(n, dtype=float) f[0] = 0. if n % 2 == 0: # Even n for i in arange(1, n / 2): f[i] /= 2.0j * pi * float(i) / float(n) f[-1] = 0.0 # Nothing from Nyquist frequency else: # Odd n for i in arange(1, (n - 1) / 2 + 1): f[i] /= 2.0j * pi * float(i) / float(n) return result + irfft(f) else: # Non-periodic function def int_total(f): """Integrate over a set of points""" n = f.size if n > 7: # Need to split into several segments # Use one 5-point, leaving at least 4-points return int_total(f[0:5]) + int_total(f[4:]) elif (n == 7) or (n == 6): # Try to keep 4th-order # Split into 4+4 or 4+3 return int_total(f[0:4]) + int_total(f[3:]) elif n == 5: # 6th-order Bool's rule return 4. * (7. * f[0] + 32. * f[1] + 12. * f[2] + 32. * f[3] + 7. * f[4]) / 90. elif n == 4: # 4th-order Simpson's 3/8ths rule return 3. * (f[0] + 3. * f[1] + 3. * f[2] + f[3]) / 8. elif n == 3: # 4th-order Simpson's rule return (f[0] + 4. * f[1] + f[2]) / 3. elif n == 2: # 2nd-order Trapezium rule return 0.5 * (f[0] + f[1]) else: print "WARNING: Integrating a single point" return 0.0 # Integrate using maximum number of grid-points n = var.size n2 = int(n / 2) result = zeros(n) for i in arange(n2, n): result[i] = int_total(var[0:(i + 1)]) for i in arange(1, n2): result[i] = result[-1] - int_total(var[i:]) return result