Skip to content
This repository was archived by the owner on Jun 14, 2025. It is now read-only.

Conversation

@cmikida2
Copy link
Contributor

@cmikida2 cmikida2 commented Sep 10, 2020

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)

@inducer
Copy link
Owner

inducer commented Sep 10, 2020

Thanks! A few checks failed. Could you take a look?

@cmikida2
Copy link
Contributor Author

Yeah, I'm already on it. It's small stuff, should be easily fixed soon. Will comment again when done.

@cmikida2
Copy link
Contributor Author

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.

Copy link
Owner

@inducer inducer left a 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.

@cmikida2
Copy link
Contributor Author

Sorry about that - CI is now actually running (and passing) the implicit tests with scipy.

@inducer
Copy link
Owner

inducer commented Oct 24, 2020

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?

Copy link
Owner

@inducer inducer left a 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.

Comment on lines +414 to +416
User-supplied context:
<state> + component_id: The value that is integrated
<func> + component_id: The right hand side
Copy link
Owner

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

Comment on lines +611 to +612
# but if we are comparing with IMEX MRAM, we need one more
# bootstrap step.
Copy link
Owner

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?

Comment on lines 636 to 742
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)
Copy link
Owner

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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Bootstrap
# {{{ bootstrap

cb_bootstrap.switch_phase("primary")
else:
with cb_bootstrap.if_(self.step, "==", self.hist_length - 1):
cb_bootstrap.switch_phase("primary")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cb_bootstrap.switch_phase("primary")
cb_bootstrap.switch_phase("primary")
# }}}

Comment on lines +420 to +421
# {{{ am method

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# {{{ am method
# {{{ am method

if state_filter_name is not None:
self.state_filter = var("<func>" + state_filter_name)
else:
self.state_filter = None
Copy link
Owner

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)
Copy link
Owner

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?

Comment on lines +592 to +597
# 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])
Copy link
Owner

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
Copy link
Owner

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.

@cmikida2
Copy link
Contributor Author

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.

@cmikida2
Copy link
Contributor Author

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.

Base automatically changed from master to main March 8, 2021 05:09
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants