-
Notifications
You must be signed in to change notification settings - Fork 26
Filter stack and radial covar optimizations #1388
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
base: develop
Are you sure you want to change the base?
Changes from all commits
49d17ff
822e9f9
66cf925
9680000
a88fd9b
f621822
c84a96c
798951b
4568e6f
4a562e7
45a6caf
af522e7
1713e0f
7ab9c37
780a166
af7d1bc
74e60bf
0a1f3c5
0dc76ee
0582bf7
faad4ca
1cf187a
042bad7
338ebfa
60ae2bf
2d9ad39
2c30fb5
760f013
3a93549
90b32a1
92c74b5
3658db3
820d9b6
c87f753
03eba11
6024124
f0e1429
e9d95fd
36a39f4
97e0326
756f3e0
2428e09
c6c76b2
37a71a3
3db308e
69748b3
10ce299
888fc5f
f262f4e
c3463b9
7214293
6c11b8f
091697d
df12950
8b9beee
8b8cec9
e0f2311
466311a
a7b0282
8d9e1ae
82423b2
84b5339
6b61e26
527f7af
76a5c1e
6400090
4c80909
a767ea9
f8cf103
ffadf43
aaa8702
39bcdb4
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 |
|---|---|---|
|
|
@@ -67,6 +67,13 @@ def _build(self): | |
| self._precomp["gl_nodes"] | ||
| ) | ||
|
|
||
| # Generate radial filter point set for radial optimized eval | ||
| # Weights appear a little sensitive to dtype, otherwise could use self._precomp["gl_nodes"] | ||
| k_vals, _ = lgwt(self.n_r, 0, self.kcut, dtype=np.float64) | ||
| self._filter_pts = np.pad( | ||
| 2 * np.pi * k_vals.reshape(1, -1), ((0, 1), (0, 0)) | ||
| ).astype(self.dtype) | ||
|
|
||
| def _precomp(self): | ||
| """ | ||
| Precomute the basis functions on a polar Fourier grid | ||
|
|
@@ -236,63 +243,127 @@ def _evaluate_t(self, x): | |
|
|
||
| return xp.asnumpy(v) | ||
|
|
||
| def filter_to_basis_mat(self, f, **kwargs): | ||
| def _filter_stack_to_basis_mats(self, f, **kwargs): | ||
| """ | ||
| See `SteerableBasis2D.filter_to_basis_mat`. | ||
| """ | ||
| # Note 'method' and 'truncate' not relevant for this optimized FFB code. | ||
| if kwargs.get("method", None) is not None: | ||
| # Note 'truncate' not relevant for this specific FFB code. | ||
| # `expand_method=radial` should have already been diverted | ||
| # by the wrapping code in `super().filter_stack_to_basis_mats`. | ||
| # Permits forcing 2d calc on a radial filter via `expand_method=evaluate_t` | ||
| expand_method = kwargs.get("expand_method", None) | ||
| if expand_method not in [None, "evaluate_t"]: | ||
| raise NotImplementedError( | ||
| "`FFBBasis2D.filter_to_basis_mat` method {method} not supported." | ||
| " Use `method=None`." | ||
| f"`FFBBasis2D.filter_to_basis_mat` expand_method '{expand_method}' not supported." | ||
| " Use `expand_method=` `None` or `evaluate_t`." | ||
| ) | ||
|
|
||
| pixel_size = kwargs.get("pixel_size", None) | ||
|
|
||
| # These form a circular dependence, import locally until time to clean up. | ||
| from aspire.basis.basis_utils import lgwt | ||
|
|
||
| # Get the filter's evaluate function. | ||
| h_fun = f.evaluate | ||
|
|
||
| # Set same dimensions as basis object | ||
| n_k = self.n_r | ||
| n_theta = self.n_theta | ||
| radial = self._precomp["radial"] | ||
| k_vals = self._precomp["gl_nodes"] | ||
|
|
||
| # get 2D grid in polar coordinate | ||
| k_vals, wts = lgwt(n_k, 0, 0.5, dtype=self.dtype) | ||
| k, theta = np.meshgrid( | ||
| k_vals, np.arange(n_theta) * 2 * np.pi / (2 * n_theta), indexing="ij" | ||
| k, theta = xp.meshgrid( | ||
| xp.asarray(k_vals), | ||
| xp.arange(n_theta) * 2 * np.pi / (2 * n_theta), | ||
| indexing="ij", | ||
| ) | ||
|
|
||
| # Get function values in polar 2D grid and average out angle contribution | ||
| omegax = k * np.cos(theta) | ||
| omegay = k * np.sin(theta) | ||
| omega = 2 * np.pi * np.vstack((omegax.flatten("C"), omegay.flatten("C"))) | ||
| omegax = k * xp.cos(theta) | ||
| omegay = k * xp.sin(theta) | ||
| omega = 2 * np.pi * xp.vstack((omegax.flatten("C"), omegay.flatten("C"))) | ||
|
|
||
| # This should return either a single 2d array, or stack of 2d arrays | ||
| # Reshape singleton to stack of 1. | ||
| h_vals2d = ( | ||
| h_fun(omega, pixel_size=pixel_size).reshape(n_k, n_theta).astype(self.dtype) | ||
| h_fun(omega, pixel_size=pixel_size) | ||
| .reshape(len(f), n_k, n_theta) | ||
| .astype(self.dtype) | ||
| ) | ||
| h_vals = np.sum(h_vals2d, axis=1) / n_theta | ||
| h_vals = h_vals2d.sum(axis=-1) / n_theta | ||
|
|
||
| h_basis = self.expand_radial_vec(h_vals, **kwargs) | ||
| return h_basis | ||
|
|
||
| def filter_to_basis_mat(self, f, **kwargs): | ||
| """ | ||
| See `SteerableBasis2D.filter_stack_to_basis_mats`. | ||
| """ | ||
| if len(f) != 1: | ||
| raise RuntimeError("Unexpected filter length.") | ||
| return self._filter_stack_to_basis_mats(f, **kwargs)[0] | ||
|
|
||
| def expand_radial_vec(self, radial_vec, **kwargs): | ||
| """ | ||
| Expands radial vector or stack of vetors `radial_vec` to basis matrix. | ||
|
|
||
| See `_filter_pts` for point set. | ||
|
|
||
| :param radial_vec: Array holding radial vector, | ||
| shaped (n_radial_pts) or (n_vectors, n_radial_pts) | ||
| :force_diag: Optionally flush off-diagonal elements to zero and return `DiagMatrix` | ||
| :return: List of `BlkDiagMatrix`, or list of `DiagMatrix` | ||
| """ | ||
| force_diag = kwargs.get("force_diag", False) | ||
|
|
||
| # Convert vector to (1,...) | ||
| if radial_vec.ndim == 1: | ||
| radial_vec = radial_vec.reshape(1, *radial_vec.shape) | ||
| # Optionally transfer to GPU | ||
| radial_vec = xp.asarray(radial_vec) | ||
|
|
||
| # Set same dimensions as basis object | ||
| n_k = self.n_r | ||
| radial = self._precomp["radial"] | ||
| k_vals = xp.asarray(self._precomp["gl_nodes"]) | ||
| wts = xp.asarray(self._precomp["gl_weights"]) | ||
|
|
||
| # Represent 1D function values in basis | ||
| h_basis = BlkDiagMatrix.empty(2 * self.ell_max + 1, dtype=self.dtype) | ||
| h_basis = [ | ||
| BlkDiagMatrix.empty(2 * self.ell_max + 1, dtype=self.dtype) | ||
| for _ in radial_vec | ||
| ] | ||
|
|
||
| ind_ell = 0 | ||
|
Collaborator
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. A lot of the work here seems to be doubling up what's done in
Collaborator
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. Yes, it is. The first half of those methods are totally different, but they are similar after that. I'll try seeing what calling
Collaborator
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. Yeah, that seemed to work out fine. I'll try similar for FLE.
Collaborator
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. Okay. So I was able to make both FFB and FLE call their own |
||
| # Reshapes for broadcasting | ||
| radial_vec = radial_vec.reshape(len(h_basis), n_k, 1) | ||
| k_vals = k_vals.reshape(1, n_k, 1) | ||
| wts = wts.reshape(1, n_k, 1) | ||
| for ell in range(0, self.ell_max + 1): | ||
| k_max = self.k_max[ell] | ||
| rmat = 2 * k_vals.reshape(n_k, 1) * self.r0[ell][0:k_max].T | ||
| basis_vals = np.zeros_like(rmat) | ||
| basis_vals = xp.zeros((n_k, k_max), dtype=self.dtype) | ||
| ind_radial = np.sum(self.k_max[0:ell]) | ||
| basis_vals[:, 0:k_max] = radial[ind_radial : ind_radial + k_max].T | ||
| h_basis_vals = basis_vals * h_vals.reshape(n_k, 1) | ||
| h_basis_ell = basis_vals.T @ ( | ||
| h_basis_vals * k_vals.reshape(n_k, 1) * wts.reshape(n_k, 1) | ||
| basis_vals[:, 0:k_max] = xp.asarray( | ||
| radial[ind_radial : ind_radial + k_max] | ||
| ).T | ||
| h_basis_vals = basis_vals * radial_vec | ||
| h_basis_ell = basis_vals.T @ (h_basis_vals * k_vals * wts) | ||
| h_basis_ell = xp.asnumpy(h_basis_ell) | ||
|
|
||
| # loop over filters in `radial_vec`, | ||
| # assigning current `ind_ell`. | ||
| for _filter in range(len(radial_vec)): | ||
| _tmp = h_basis[_filter][ind_ell] = h_basis_ell[_filter] | ||
| if ell > 0: | ||
| # for non-zero `elle` also assign `ind_ell+1` | ||
| h_basis[_filter][ind_ell + 1] = _tmp | ||
| # On the last filter, iterate the outer `ind_ell` | ||
| if _filter == len(radial_vec) - 1: | ||
| ind_ell += 1 | ||
| # For non-zero `ell`, iterate twice. | ||
| if ell > 0: | ||
| ind_ell += 1 | ||
| if force_diag: | ||
| logger.warning( | ||
| "Forcing block diagonal to diagonal. Zeroing all off diagonal values." | ||
| ) | ||
| h_basis[ind_ell] = h_basis_ell | ||
| ind_ell += 1 | ||
| if ell > 0: | ||
| h_basis[ind_ell] = h_basis[ind_ell - 1] | ||
| ind_ell += 1 | ||
| h_basis = [h.diag() for h in h_basis] | ||
|
|
||
| return h_basis | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But how do we know whether these radial pts are compatible with the radial grid used here (from
_precomp["gl_nodes"])?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the radial case,
gl_nodesdid not work well as-is, I had to remake them in double precision :/# Weights appear a little sensitive to dtype, otherwise could use self._precomp["gl_nodes"]I changed the occurrence of 0.5 to
self.kcutin (one new, one existing)lgwtcalls. kcut is fixed to 0.5. That would more/less enforce self consistency within the basis.As far as the input to the function... ASPIRE code is generating the radial vectors by using
basis._filter_pts. If that attribute doesn't exist the code I wrote to generate the radial vector will not succeed and the code will not reach this point. Now, If a user is cooking their own radial vector using their own code, that is their responsibility. They can read the code.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added
to both
expand_radial_vecdocstrings.