-
Notifications
You must be signed in to change notification settings - Fork 170
RF Eigenproblem #4529
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: main
Are you sure you want to change the base?
RF Eigenproblem #4529
Conversation
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.
Some early feedback. I think @JHopeCollins needs to review this.
firedrake/rf_eigensolver.py
Outdated
|
||
|
||
class RFEigenproblem: | ||
"""Generalised linear eigenvalue problem. |
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.
Docstring does not really describe the reduced functional aspect of this class.
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 modified the docstring. Now I believe it is clearer.
PETSc = None | ||
|
||
|
||
def new_restricted_control_variable(reduced_functional, function_space, dual=False): |
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.
Please use type hints throughout.
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 added type hints where I believed arguments are unclear.
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.
Thanks, but please include them everywhere that you've written a docstring.
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.
Done.
firedrake/rf_eigensolver.py
Outdated
|
||
|
||
class RFEigensolver: | ||
r"""Solve a LinearEigenproblem. |
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.
Shouldn't this be a RFEigenProblem
?
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 think now it is clearer. Apologies, forgot to change.
firedrake/rf_eigensolver.py
Outdated
|
||
|
||
class RFEigenproblem: | ||
"""Generalised eigenvalue problem. |
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 don't think that this line really describes what is going on. This is just copied from eigensolver.py
.
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.
Updated for more clarity.
firedrake/rf_eigensolver.py
Outdated
apply_riez : bool | ||
If True, when defining A's adjoint action, we can output into the dual space. | ||
action : str | ||
Determine whether forward action is TLM or adjoint. |
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.
Please enumerate the valid options here.
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.
The action
docstring is now duplicated so this one can be removed.
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.
This is really great, thanks for your contribution!
I've left a lot of comments/suggestions but the vast majority are minor style changes, in general I'm very happy with this.
If you have the time, then a little demo would be fantastic to have. It could just be showing the Helmholtz problem, but it will be much easier for people to find as a demo than in the tests.
problem_form = LinearEigenproblem(a, bcs=bc, restrict=True) | ||
solver_form = LinearEigensolver(problem_form, n_evals=num_eigenvalues) | ||
solver_form.solve() | ||
nconv = solver_form.es.getConverged() |
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.
Should we be checking how many converged here?
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 have tried but faced a bit of obstacles. The aim of the test is to check that two eigenvalue abstractions outputs the same results.
evals_rf = [ | ||
1 / solver_rf.eigenvalue(i) for i in range(0, min(num_eigenvalues, nconv)) | ||
] |
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.
Why are we grabbing eigenvalues from the LinearEigenSolver
but inverse eigenvalues from the RFEigenSolver
?
Is this the right thing to do? If so, please add a comment explaining why.
evals_rf = [ | |
1 / solver_rf.eigenvalue(i) for i in range(0, min(num_eigenvalues, nconv)) | |
] | |
evals_rf = [ | |
1 / solver_rf.eigenvalue(i) for i in range(min(num_eigenvalues, nconv)) | |
] |
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.
This is done because the eigenvalue problem in LinearEigenproblem
arises from the spatial discretisation of the bilinear forms arising from the Helmholtz PDE with forcing function f=cu
for eigenvalue c
. The solution operator in this case is a mapping from the forcing function to the solution. A wave-of-hands argument is that Jf=u
with the f
substituted from above, and using the fact that J is linear (which it is as the PDE is steady and linear) rearranges to `Ju = 1/cu'.
A = self._problem.A | ||
M = self._problem.M | ||
if not isinstance(A, PETSc.Mat): | ||
raise TypeError(f"A must be a PETSc matrix, got {A.__class__}") | ||
if not isinstance(M, PETSc.Mat) and (M is not None): | ||
raise TypeError(f"M must be a PETSc matrix or None (identity), got {M.__class__}") | ||
|
||
self.es.setDimensions(nev=self.n_evals, ncv=self.ncv, mpd=self.mpd) | ||
self.es.setOperators(A, M) |
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.
The operators should be set in __init__
, before the call to set_from_options
. This is so that:
- The solver actually has access to the Mats when everything is being set up during
set_from_options
, - We don't incorrectly/unnecessarily reset the operators every time we call
solve
.
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.
Agreed, this change could probably be mirrored in eigensolver.py
in the LinearEigensolver
class.
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.
Yes, if they are also set in solve
in the LinearEigenSolver
then that is also a bug.
They also need to be before the call to set_from_option
in the init to make sure that it's all set up correctly.
if isinstance(variables, (tuple, list)): | ||
if isinstance(variables[0], Function): | ||
return tuple(Function(function_space).interpolate(v) for v in variables) | ||
else: | ||
return tuple( | ||
Cofunction(function_space.dual()).interpolate(v) for v in variables | ||
) | ||
elif isinstance(variables, Function): | ||
return Function(function_space).interpolate(variables) | ||
else: | ||
return Cofunction(function_space.dual()).interpolate(variables) |
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.
You can use pyadjoint.Enlist
to handle whether we have a list or not. I think this is the right way to do it.
if isinstance(variables, (tuple, list)): | |
if isinstance(variables[0], Function): | |
return tuple(Function(function_space).interpolate(v) for v in variables) | |
else: | |
return tuple( | |
Cofunction(function_space.dual()).interpolate(v) for v in variables | |
) | |
elif isinstance(variables, Function): | |
return Function(function_space).interpolate(variables) | |
else: | |
return Cofunction(function_space.dual()).interpolate(variables) | |
from ufl.duals import is_primal | |
variables = Enlist(variables) | |
V = function_space if is_primal(variables[0]) else function_space.dual() | |
interp_vars = [Function(V).interpolate(v) for v in variables) | |
return variables.delist(interp_vars) |
# Check adjoint action | ||
problem_rf_true = RFEigenproblem( | ||
J, bcs=bc, apply_riesz=True, restrict=restrict, action="adjoint" | ||
) | ||
problem_rf_false = RFEigenproblem( | ||
J, bcs=bc, apply_riesz=False, restrict=restrict, action="adjoint" | ||
) |
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.
Seeing as we're specifically testing the implicit matrix here, I think it'd be simpler to just construct them explicitly rather than going via the RFEigenProblem.
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 actually thought that the code is less messy in this case. Otherwise, would have to do casework depending on restricted an unrestricted stuff. Also, would have to add functions to determine restricted space. Much more clunky whilst this happens under the hood of RFEigenproblem
.
Co-authored-by: Josh Hope-Collins <[email protected]>
…ero more effective.
…redrake into rf_eigenproblem_clean
Co-authored-by: Josh Hope-Collins <[email protected]>
This PR introduces a new eigenproblem/eigensolver abstraction that enables one to compute spectra of the linearisation of a sequence of Firedrake operations. The RFEigenproblem/RFEigensolver tandem closely mirror the LinearEigenproblem/LinearEigensolver tandem.
The main changes are introduced in the RFEigenproblem abstraction. Inspired by the interfacing and ability to define matrices implicitly introduced by @JHopeCollins in the JHopeCollins/tao-rf-mats branch, a new context RestrictedReducedFunctionalCtx object is used in the creation of the RestrictedReducedFunctionalMat which represents the linearisation of a sequence of FIredrake operations on a restricted function space.