-
Notifications
You must be signed in to change notification settings - Fork 8
Improving Spectral #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
957caaf
859a87a
6766e52
ae65554
2688f07
cb59792
0226b3b
b1154da
cb184a0
8bb4df7
18926e3
d11d1a7
22865d3
825e6dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,40 +7,50 @@ | |
| from scipy import interpolate, sparse | ||
| from scipy.special import legendre | ||
| from sklearn.linear_model import Lasso | ||
| from specderiv import cheb_deriv, fourier_deriv | ||
|
|
||
|
|
||
| @register("spectral") | ||
| class Spectral(Derivative): | ||
| def __init__(self, **kwargs): | ||
| def __init__(self, order=1, axis=0, basis='fourier', filter=None): | ||
| """ | ||
| Compute the numerical derivative by first computing the FFT. In Fourier space, derivatives are multiplication | ||
| by i*phase; compute the IFFT after. | ||
|
|
||
| Args: | ||
| **kwargs: Optional keyword arguments. | ||
|
|
||
| Compute the numerical derivative by spectral methods. In Fourier space, derivatives are multiplication | ||
| by i*phase; compute the inverse transform after. Use either Fourier modes of Chebyshev polynomials as | ||
| the basis. | ||
|
|
||
| Keyword Args: | ||
| filter: Optional. A function that takes in frequencies and outputs weights to scale the coefficient at | ||
| the input frequency in Fourier space. Input frequencies are the discrete fourier transform sample | ||
| frequencies associated with the domain variable. Look into python signal processing resources in | ||
| scipy.signal for common filters. | ||
|
|
||
| order (int): order of the derivative, defaults to 1st order | ||
| axis (int): the dimension of the data along which to differentiate, defaults to first dimension | ||
| basis (str): 'fourier' or 'chebyshev', the set of basis functions to use for differentiation | ||
| Note `basis='fourier'` assumes your function is periodic and sampled over a period of its domain, | ||
| [a, b), and `basis='chebyshev'` assumes your function is sampled at cosine-spaced points on the | ||
| domain [a, b]. | ||
| filter: Optional. A function that takes in basis function indices and outputs weights, which scale | ||
| the corresponding modes in the basis-space interpolation before derivatives are taken, e.g. | ||
| `lambda k: k<10` will keep only the first ten modes. With the Fourier basis, k corresponds to | ||
| wavenumbers, so common filters from scipy.signal can be used. In the Chebyshev basis, modes do | ||
| not directly correspond to frequencies, so high frequency noise can not be separated quite as | ||
| cleanly, however it still may be helpful to dampen higher modes. | ||
| """ | ||
| # Filter function. Default: Identity filter | ||
| self.filter = kwargs.get('filter', np.vectorize(lambda f: 1)) | ||
| self._x_hat = None | ||
| self._freq = None | ||
|
|
||
| def _dglobal(self, t, x): | ||
| self._x_hat = np.fft.fft(x) | ||
| self._freq = np.fft.fftfreq(t.size, d=(t[1] - t[0])) | ||
| self.order = order | ||
| self.axis = axis | ||
| if basis not in ['chebyshev', 'fourier']: | ||
| raise ValueError("Only chebyshev and fourier bases are allowed.") | ||
| self.basis = basis | ||
| self.filter = filter | ||
|
|
||
| @_memoize_arrays(1) # the memoization is 1 deep, as in only remembers the most recent args | ||
| def _global(self, t, x): | ||
pavelkomarov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if self.basis == 'chebyshev': | ||
| return cheb_deriv(x, t, self.order, self.axis, self.filter) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It struck me just now that filtering by frequency on cosine-spaced points isn’t totally sound: high-frequency variations look lower frequency around the edges where sample density is higher.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been pondering this more pavelkomarov/spectral-derivatives#14. Basically what I'm currently doing is keeping
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've made a notebook to empirically compare spectral filtering in the Fourier and Chebyshev bases, and pulled in a little of Floris' PyNumDiff to compare with Savitzky-Golay filtering + Gaussian smoothing. Fourier spectral is the gold standard, but even it can be fragile. Chebyshev can work okay (not great but really not worse than |
||
| else: # self.basis == 'fourier' | ||
| return fourier_deriv(x, t, self.order, self.axis, self.filter) | ||
|
|
||
| def compute(self, t, x, i): | ||
| return next(self.compute_for(t, x, [i])) | ||
|
|
||
| def compute_for(self, t, x, indices): | ||
| self._dglobal(t, x) | ||
| res = np.fft.ifft(1j * 2 * np.pi * self._freq * self.filter(self._freq) * self._x_hat).real | ||
| res = self._global(t, x) # cached | ||
| for i in indices: | ||
| yield res[i] | ||
|
|
||
|
|
@@ -212,7 +222,6 @@ def __init__(self, alpha=None): | |
| """ | ||
| self.alpha = alpha | ||
|
|
||
|
|
||
| @_memoize_arrays(1) | ||
| def _global(self, t, z, alpha): | ||
| if alpha is None: | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.