-
Notifications
You must be signed in to change notification settings - Fork 4
Cory implicit adams #4
base: main
Are you sure you want to change the base?
Conversation
Backward Euler tableau
avoid extra RHS evals - also uses implicit DIRK for bootstrap
IMEX and fully explicit
|
Thanks! A few checks failed. Could you take a look? |
|
Yeah, I'm already on it. It's small stuff, should be easily fixed soon. Will comment again when done. |
|
All checks are now passing, but a caveat (I'd say a major one) is that none of the implicit tests are actually being run, as they require scipy to run, and that was causing a large number of failures initially because the CI doesn't have scipy (I think that's what the issue is?). The other implicit tests that already existed prior to me (IMEX stuff) have importorskip statements that circumvent this. Is there a way to add scipy to the CI so the implicit tests are run? Further, if there's a way for me to do this/if you want me to do it, let me know and I'm happy to handle it. |
inducer
left a comment
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.
A quick pre-review since I'd like the implicit CI to actually run.
|
Sorry about that - CI is now actually running (and passing) the implicit tests with scipy. |
|
Btw, I'm in the middle of reviewing this. If/when you go to add adaptivity, could you do that on a new branch off of this one? |
inducer
left a comment
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 for your work on this. Some comments below. I propose that we take this one step at a time. I've only reviewed the AM bits for now. Could you split those into a separate PR? Big PRs like this one are difficult and time-consuming to review.
| User-supplied context: | ||
| <state> + component_id: The value that is integrated | ||
| <func> + component_id: The right hand side |
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 suspect this won't render the way you intend. Check via
pip install sphinx
cd doc
make html
open _build/html/index.html
| # but if we are comparing with IMEX MRAM, we need one more | ||
| # bootstrap step. |
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'm not sure I fully understand this comment. What's being compared here?
| def rk_bootstrap(self, cb): | ||
| """Initialize the timestepper with an IMPLICIT RK method.""" | ||
|
|
||
| equations = [] | ||
| unknowns = set() | ||
| knowns = set() | ||
| rhs_var_to_unknown = {} | ||
|
|
||
| from leap.rk import IMPLICIT_ORDER_TO_RK_METHOD_BUILDER | ||
| rk_method = IMPLICIT_ORDER_TO_RK_METHOD_BUILDER[self.function_family.order] | ||
| rk_tableau = tuple(zip(rk_method.c, rk_method.a_implicit)) | ||
| rk_coeffs = rk_method.output_coeffs | ||
|
|
||
| if self.extra_bootstrap: | ||
| first_save_step = 2 | ||
| else: | ||
| first_save_step = 1 | ||
|
|
||
| with cb.if_(self.step, "==", first_save_step): | ||
| # Save the first RHS to the AM history | ||
| rhs_var = var("rhs_var") | ||
|
|
||
| cb(rhs_var, self.eval_rhs(self.t, self.state)) | ||
| cb(self.history[0], rhs_var) | ||
|
|
||
| if not self.static_dt: | ||
| cb(self.time_history[0], self.t) | ||
|
|
||
| # Stage loop | ||
| rhss = [var("rk_rhs_" + str(i)) for i in range(len(rk_tableau))] | ||
| for stage_num, (c, coeffs) in enumerate(rk_tableau): | ||
| stage = self.state + sum(self.dt * coeff * rhss[j] | ||
| for (j, coeff) | ||
| in enumerate(coeffs)) | ||
|
|
||
| if self.state_filter is not None: | ||
| stage = self.state_filter(stage) | ||
|
|
||
| # In a DIRK setting, the unknown is always the same RHS | ||
| # as the stage number. | ||
| unknowns.add(rhss[stage_num]) | ||
| unkvar = cb.fresh_var('unk_s%d' % (stage_num)) | ||
| rhs_var_to_unknown[rhss[stage_num]] = unkvar | ||
| from dagrt.expression import collapse_constants | ||
| solve_expression = collapse_constants( | ||
| rhss[stage_num] - self.eval_rhs(self.t + c*self.dt, stage), | ||
| list(unknowns) + [self.state], | ||
| cb.assign, cb.fresh_var) | ||
| equations.append(solve_expression) | ||
|
|
||
| # {{{ emit solve if possible | ||
|
|
||
| if unknowns and len(unknowns) == len(equations): | ||
| # got a square system, let's solve | ||
| assignees = [unk.name for unk in unknowns] | ||
|
|
||
| from pymbolic import substitute | ||
| subst_dict = dict( | ||
| (rhs_var.name, rhs_var_to_unknown[rhs_var]) | ||
| for rhs_var in unknowns) | ||
|
|
||
| cb.assign_implicit( | ||
| assignees=assignees, | ||
| solve_components=[ | ||
| rhs_var_to_unknown[unk].name | ||
| for unk in unknowns], | ||
| expressions=[ | ||
| substitute(eq, subst_dict) | ||
| for eq in equations], | ||
|
|
||
| # TODO: Could supply a starting guess | ||
| other_params={ | ||
| "guess": self.state}, | ||
| solver_id="solve") | ||
|
|
||
| del equations[:] | ||
| knowns.update(unknowns) | ||
| unknowns.clear() | ||
|
|
||
| # }}} | ||
|
|
||
| # Merge the values of the RHSs. | ||
| rk_comb = sum(coeff * rhss[j] for j, coeff in enumerate(rk_coeffs)) | ||
|
|
||
| state_est = self.state + self.dt * rk_comb | ||
| if self.state_filter is not None: | ||
| state_est = self.state_filter(state_est) | ||
|
|
||
| # Assign the value of the new state. | ||
| cb(self.state, state_est) | ||
|
|
||
| # Save the "next" RHS to the AM history | ||
| rhs_next_var = var("rhs_next_var") | ||
|
|
||
| cb(rhs_next_var, self.eval_rhs(self.t + self.dt, self.state)) | ||
|
|
||
| for i in range(1, len(self.history)): | ||
| if self.extra_bootstrap: | ||
| save_crit = i+1 | ||
| else: | ||
| save_crit = i | ||
|
|
||
| with cb.if_(self.step, "==", save_crit): | ||
| cb(self.history[i], rhs_next_var) | ||
|
|
||
| if not self.static_dt: | ||
| cb(self.time_history[i], self.t + self.dt) |
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 expect that this is, at least to an extent, a version of the RK integration code, with some number of changes. Could you refactor this to use (an improved version of) generate_butcher to share code (or explain why that's not reasonable)?
| }, | ||
| initial_phase="initial") | ||
|
|
||
| # Bootstrap |
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.
| # Bootstrap | |
| # {{{ bootstrap |
| cb_bootstrap.switch_phase("primary") | ||
| else: | ||
| with cb_bootstrap.if_(self.step, "==", self.hist_length - 1): | ||
| cb_bootstrap.switch_phase("primary") |
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.
| cb_bootstrap.switch_phase("primary") | |
| cb_bootstrap.switch_phase("primary") | |
| # }}} |
| # {{{ am method | ||
|
|
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.
| # {{{ am method | |
| # {{{ am method |
| if state_filter_name is not None: | ||
| self.state_filter = var("<func>" + state_filter_name) | ||
| else: | ||
| self.state_filter = None |
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.
It seems this constructor is largely the same as the AB one. Why is its code duplicated? (e.g. instead of inheriting it from a common base)
| # }}} | ||
|
|
||
| # Update the state now that we've solved. | ||
| cb_primary(self.state, state_est) |
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.
How come the solve result is not passed through the state filter?
| # Rotate history and time history. | ||
| for i in range(self.hist_length - 1): | ||
| cb_primary(self.history[i], history[i + 1]) | ||
|
|
||
| if not self.static_dt: | ||
| cb_primary(self.time_history[i], time_history_data[i + 1]) |
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 imagine this time history management code occurs similarly somewhere else. Could you factor this out into a utility?
| print(eocrec.pretty_print()) | ||
|
|
||
| orderest = eocrec.estimate_order_of_convergence()[0, 1] | ||
| assert orderest > expected_order * 0.9 |
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.
It looks like this code largely duplicates what's already in test_imex.py. I would prefer if this could be expressed in one shared test.
|
Hi - yes, I can split the AM only parts into a separate PR for easier review. I'll also address the comments above in that PR, try to slim down the code/combine things where I can, and ping you again with a fresh PR when that's done. As for adaptivity, I had already started that work by branching from this branch, as you suggested above. For now, I'll obviously prioritize cleaning this up more before continuing with adaptivity. |
|
Hi - I've moved the single-rate implicit Adams only to a new pull request here.. I'm not sure how you want to manage this - I believe I've responded to all of the comments you've put above, but I'm not sure what protocol is/whether I should answer the above comments with references to the code in that PR, or if we just start fresh there. Happy to do whatever, and thank you again/in advance for your continued feedback. |
Adding implicit to Adams (Adams-Moulton), and incorporating the ability to treat certain RHS components implicitly in a multi-rate setting.
(Continuation of https://gitlab.tiker.net/inducer/leap/-/merge_requests/38)