Doc fix for transpose#521
Conversation
| """ | ||
| val = getattr(self, name) | ||
| solution = getattr(self, "solution", None) | ||
| if solution is None or not isinstance(val, ufl.core.expr.Expr): |
There was a problem hiding this comment.
Should you not check whether val is in "nonlinear_coefficients"? e.g. this may be the diffusion term asking for the diffusivity but it's not used as a non-linear coefficient, but we are using a different non-linear coefficient elsewhere in the equation. In that case solution is not None and so it will try to replace solution; if we simply wanted to lag diffusivity (hence it's not non-linear but does depend on solution) that would do the wrong thing.
There was a problem hiding this comment.
I think this is now fully addressed in the redesign. resolution is now gated on membership in nonlinear_coefficients, not on solution is not None, so a coefficient that merely happens to depend on the solution is never substituted unless you explicitly declare it
|
So this is tricky business with implications for future extensions that I'd like to consider. So we're trying to deal with coefficients that depend on the solution, making the term nonlinear. Of course, the distinction between coefficients and the actual term is a bit arbitrary, e.g. you could implement a specific nonlinear diffusion term where the solution-dependent diffusion is just hard-coded as function of trial - but that's of course not very flexible. Other example we could use the source_term, declare the "source" coefficient to be a nonlinear coefficient Note that in the Sorry this a bit of blurb, just wanted to think ahead a bit - but I think your solution should of using ufl.replace works nicely for all of that. So I think the assumption should be that all terms are linear in trial (as far as trial appears explicitly in the formula) as they are now, and then non-linearity may enter through "non-linear coefficients" . Two points I like a little less:
One way to deal with 2. is to only populate those equation attributes that are not non-linear, i.e. if you declare diffusivity to be non-linear, and a term tries to access I know this might be a pain, if you want to expose this directly to the user you'd have to pass these through to the solver interfaces - but tbh I think it might be nicer to hide this from the user. This is ultimately a time-discretisation choice. So one way one to do this is to say the user provides the expressions for the various coefficients to the solver, which may or may not depend on solution - and then the solver decides whether or not these expressions are passed on as non_linear coefficients or as standard eq_attrs. This also limits the number of changes if you just want to do it for one solver at the moment. |
|
firedrakeproject/firedrake#5119 Is this PR relevant here? |
|
Not directly, but it is a very interesting PR - and a further example indeed of what you could do with lagging different terms/coefficients. |
|
Thanks @stephankramer . Excellent info. Let me first play back what I understood, so we're sure we're talking about the same thing. If I read you correctly, you're happy with using Now here is the part that I think you are unhappy with: that I've smuggled solution and nonlinear_coefficients into eq_attrs, second, that the whole thing relies on every term author remembering to call resolve_coefficient instead of reading eq.diffusivity directly, and if they forget there's no error, just a quietly wrong answer. Third, that whether a coefficient is treated as nonlinear or lagged is a time-discretisation decision that the user setting up the physics shouldn't have to know about, so it ought to be hidden inside the solver (so basically somehow exposing this in the equation). If thats a fair summary, how about we:
That last bit matters to me too, I didn't want this to be something every solver has to grow an argument for. On the pointwise guarantee, I'd add a check at construction that walks the coefficient expression and rejects it if it finds a spatial derivative or a facet restriction sitting on top of anything that contains the solution. One thing I'd like your read on: for the mixed/coupled case, do we substitute the whole mixed solution or the indexed sub-component? It doesn't bite for the scalar Richards case I need right now, but still .. |
|
I think your summary is accurate. Maybe I've come on a bit too strong about my "mental model" - I didn't necessarily ask for guardrails, I just wanted to sketch how I see these terms - also my view is of course not the only valid way! So it breaks down in two things:
both implementing The other potential slight mis-communication is that I didn't necessarily advocate for there to be functionality where the non-linear coefficients are replaced by something other than the trial function provided to residual. In the lagging case examples above, which are indeed a strong motivation for me, I was just thinking that if you are lagging diffusivity with So having that out of the way, I like the idea of doing the substitution before we call the terms - leaving the access of coefficients inside the terms exactly as they were - otherwise we indeed would have to be calling the resolve machinery everywhere, as potentially any coefficient could be declared as non-linear, there basically wouldn't be any "normal" coefficient left if we were to be consistent. So that's great. I do think this exposes something I never quite liked in the design of the Equation class, that it serves two purposes: 1. Something that represents the equation as a whole, that does prep-work for the equation as a whole, contains the terms of the equation 2. a bucket of things we want to pass on to the term. Clearly none of the things I mentioned under 1. should be accessed by an individual term. This sort of sandwiching of classes, where class A contains objects of class B that can also access class A, leads to very hard follow data flow (class B here are the term, which we implement as functions, except they really aren't!). Also clearly Equation is not a dataclass. It only is in its second role and even there it's overkill. So I would advocate....hm actually this is a bit out-of-scope as it requires some more change, what I was going to say: I would have Equation as a normal class with a normal Finally, approximation I think that may also be a design flaw if we want to generalize things further down the line. The trouble is we now have two different ways of passing on coefficient expresssions, directly in eq_attrs or via approximation, and from a term's perspective it's really not clear which one it should be. We now have viscosity term that does Final, final point, on the coupled case with mixed-functions. Yeah, we need to think a bit more about this. Maybe there needs to be something like a CoupledEquation which has its own |
|
Let me know what you think about the 2 things I think aren't needed (checking for gradients and substitution with something else than trial) cause that may simplify the changes quite a bit, and then I can have another look. |
| _SPATIAL_DERIV = (Grad, Div, NablaGrad, NablaDiv, Curl, ReferenceGrad, ReferenceDiv) | ||
|
|
||
|
|
||
| def _expr_contains(expr: ufl.core.expr.Expr, source: Any) -> bool: |
There was a problem hiding this comment.
We have a depends_on() in utility.py. Can we not use that?
| name: self._resolve(name, trial, term_key) | ||
| for name in self._nonlinear_coeffs | ||
| } | ||
| forms.append(term(_BoundEquation(self, resolved), trial)) |
There was a problem hiding this comment.
This creates a new _BoundEquation for every term, even though they are all the same.
|
Actually, scratch all that. My "mental model" was based on the idea that we call If that's right, and I'm not overlooking something then a) we don't need any of the stuff we discussed here and b) we could (in a future PR) make things even simpler in the terms if we scratch the trial argument, e.g. the scalar advection and diffusion terms would just use |
ad25749 to
180ec15
Compare
|
Hi @stephankramer thanks for looking through all this! I've been trying to follow on because I suggested to @sghelichkhani here #508 to try and simplify the PR by using the existing diffusion term in I think this PR came about because Sia wasn't getting the same results when using the nonlinear Richards equation which has a diffusivity that depends on the solution... Judging by your last message @stephankramer I think the above should just work... @sghelichkhani was there an error message or was it just giving different results when you tried swapping the Richards diffusion term to this one...? |
180ec15 to
9d137c0
Compare
|
Yes! Thanks @stephankramer and @w3168 . This review as annoying as it was for me ended up being the most effective one!!! To be honest I spent the past few hours struggling to find out what the issue was ... A(t this point it might be the same confusion mentioned by @stephankramer . My thinking was that diffusivity(solution) is not the same as diffusivity(trial). Because self.solution would be to lag diffusivity. BUT I hope/think Irksome here deals with this issue by recognising the solution is the same as trial and would fix this! Right? |
|
Yeah I think as long as K is defined with the same solution passed on to Irksome's timestepper, irksome/tools.py:122-125 does the correct thing by replacing every instances with the stage solution. Very smart!!! |
|
So as much as this was a big pain, thanks @w3168 for suggesting to rewrite this :-D |
|
So this PR is just a doc fix! Feel free to approve and I merge! |
To be precise, the point is that because we only call residual once, we know what value we provide to the trial argument and that value is the firedrake Function that we also declare to IRKSome to be the This also means that we (for now) don't have to worry about some coefficients coming differently via Final point for future reference: if you want to do lagging in the simplest way, which is to use the values currently stored in the |
stephankramer
left a comment
There was a problem hiding this comment.
I just like to point out the irony of the fact that after this long journey, with AI-assisted code, all we end up with is a single bloody em-dash! :-)
First of five PRs decomposing the Richards-equation foundation work (see
ANATOMY-BRANCHES.mdonsghelichkhani/richards-corefor the full split). This is the equation-infrastructure piece; nothing Richards-specific lands here.Equationgains two reservedeq_attrs(solutionandnonlinear_coefficients) plus a newresolve_coefficientmethod that substitutes the equation's solution by the current trial viaufl.replace. Term implementations callresolve_coefficientinstead of reading the attribute directly, so a single residual term can serve both linear and nonlinear coefficients (the substitution is a no-op when the expression does not contain the solution).scalar_equation.diffusion_termis migrated to use the new mechanism. Existing callers that pass a solution-independent diffusivity see no behavioural change. New callers (e.g. Richards'K(h)in the follow-up PR) can declare the coefficient as a UFL expression in the equation'ssolutionand list its attribute name innonlinear_coefficients. TheEquationconstructor validates that the named attribute is a UFL expression actually containingsolution, catching the common foot-gun of building the coefficient against a differentFunction.About 70 lines across two files.