-
Notifications
You must be signed in to change notification settings - Fork 24
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
Fix dimensionality #146
Fix dimensionality #146
Conversation
Adds a boolean `is_nondimensional` to the Laplacian classes. When True, the filter scales by `dx_min ** 2` to restore dimensionality. Had to add `dx_min_sq` (equals `dx_min ** 2` to the`filter_spec` to get it to work.
gcm_filters/filter.py
Outdated
ufield_bar += ( | ||
utemp_l * 2 * np.real(s_b) / np.abs(s_b) ** 2 | ||
+ utemp_b * 1 / np.abs(s_b) ** 2 | ||
) |
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.
Lines 271-274 should not be part of the if-statement.
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.
Good catch! That would have been bad.
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.
Wondering why the tests did not catch this? 😕
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's great to see this fix Ian! I can see that this resolves the original bug.
I'd like to brainstorm a bit whether there is a simpler way to do this, which doesn't require us to carry around this is_nondimensional
attribute with all of the Laplacians.
Looking at the code I am struck by the fact that the is_nondimensional
condition seems to somehow be closely tied to the value of dx_min
. Would it be possible to simply define a nondimensional Laplacian as a Laplaican for which dx_min==1
? Are there scenarios that would break this assumption.
A corollary of this is the suggestion that perhaps we want to attach dx_min to the Laplacian / grid, rather than to the Filter itself. After all, dx_min is a property of the grid itself, not the filter.
Thanks for considering these comments. I'm just starting to really dig into this, but I know you have already through this through deeply.
Great work, Ian! If we stick to the
Let me know if you would like help with these tests. But obviously, before implementing these test cases in |
Replying to @rabernat:
The nondimensional Laplacians don't just have minimum grid spacing 1; they have every cell being a unit square. Identifying regular Laplacians with
The Filter does need to know about
As noted above, the filter needs An more-natural alternative would be to add |
I added some tests as suggested by @NoraLoose. In |
Codecov Report
@@ Coverage Diff @@
## master #146 +/- ##
==========================================
- Coverage 98.49% 97.98% -0.51%
==========================================
Files 9 9
Lines 997 1044 +47
==========================================
+ Hits 982 1023 +41
- Misses 15 21 +6
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
The reason code coverage decreased is that I implemented the fix for non-dimensional vector Laplacians, but we don't have any non-dimensional vector Laplacians, so those lines don't get touched by the tests. |
Hi Ian! Thanks for continuing to work on this PR. I have been a bit overwhelmed with getting ready for / participating in OSM for the past two weeks. Next week I intend to spend some time reviewing this PR in detail. (I am still confused about some parts of this and need to take some time to really understand what is happening.) Thanks for your patience. |
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 great Ian. Sorry for taking so long to review this. I really appreciate your patience.
A minor nit, I would prefer dimensional = True
, rather is_nondimensional = False
, since i find double negatives to be confusing. If you agree, you can change the name and invert the parameter. But this is annoying and not that important, so feel free to ignore that suggestion.
The only real suggestion I have is a minor performance optimization.
gcm_filters/filter.py
Outdated
@@ -191,13 +194,18 @@ def filter_func(field, *args): | |||
if filter_spec.is_laplacian[i]: | |||
s_l = np.real(filter_spec.s[i]) | |||
tendency = laplacian(field_bar) # Compute Laplacian | |||
if Laplacian.is_nondimensional: | |||
tendency /= filter_spec.dx_min_sq # dimensionalize |
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 would be great to be able to skip this step if dx_min_sq==1
, which will probably often be the case.
Perhaps we could create a helper function like this
def _maybe_dimensionalize(data, dx_min_sq):
if ds_min_sq == 1:
return data
else:
return data / dx_min_sq
and use it wherever the normalization occurs
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 great Ian. Sorry for taking so long to review this. I really appreciate your patience.
No problem!
A minor nit, I would prefer dimensional = True, rather is_nondimensional = False, since i find double negatives to be confusing. If you agree, you can change the name and invert the parameter. But this is annoying and not that important, so feel free to ignore that suggestion.
Good idea. Avoiding the double negative will not fail (!) to make it clearer.
The only real suggestion I have is a minor performance optimization.
I don't understand how adding a function would improve performance here, probably because I have a wrong idea for how to use the new function.
But I do agree that the if statement is going to slow the whole code down unnecessarily. How about wrapping the whole block iteration in an if statement? If dimensional then we iterate as-is; it not dimensional then we divide by dx_min_sq
at every iteration. Right now the if statement hits every time we use the Laplacian. If we instead put the test outside the loop then the if statement only hits once.
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.
Rather than saying
tendency /= filter_spec.dx_min_sq
you would say
tendency = _maybe_nondimensionalize(tendency, filter_spec.dx_min_sq)
You could accomplish the same thing with yet another nested if block, but I just find that syntax a little easier. Calling the function and then immediately returning the same array back is cheap. The expensive part is dividing a big array by 1 unnecessarily; that's what we want to avoid.
Alternatively, you could change all of the
if Laplacian.is_nondimensional:
calls to
if Laplacian.is_nondimensional and filter_spec.dx_min_sq != 1:
to accomplish the same thing.
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.
Oh, I see. I thought it was the if statement that was the problem.
I think your fix is cleaner than mine, so let's go with that. I can work on both the double negative and the maybe_nondimensionalize
and push something later today
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.
Awesome. When you're done feel free to self-merge so this is not blocking on me any longer.
Since black is evidently broken at the moment, I had to also pin version 8.0.4. Once it gets fixed this can be reverted.
Had some problems related to this psf/black#2964. My changes to the pre-commit configuration can be rolled back once this is fixed. |
This PR addresses the problem of dimensionality described in #136. A quick summary: If a user tries any of the
REGULAR
Laplacians and uses a value ofdx_min
other than 1, they will get wrong answers. This PR will allow users to put in dimensional values ofdx_min
andfilter_scale
(in any system of units) and still get the right answers.I am attempting to be minimally invasive. I've added a boolean class attribute
is_nondimensional
to all of the Laplacians. If the Laplacian is nondimensional, then at each step of the filter (infilter.py
) it re-dimensionalizes by dividing bydx_min ** 2
. Thefilter_func
's don't have access todx_min
so I addeddx_min_sq = dx_min ** 2
to the filter spec.I initially started with @NoraLoose's suggestion (from #136) of defining new Laplacian classes, but as I worked on it it seemed like it would require bigger changes. For example,
_create_filter_func
accepts aBaseScalarLaplacian
and I didn't want to have to define four new versions of_create_filter_func
(scalar vs vector; dimensional vs non).I also added a Note in the Basic Filtering section of the docs, saying that any
grid_vars
should have units that are consistent withfilter_scale
anddx_min
, but that the specific system of units doesn't matter.