-
Notifications
You must be signed in to change notification settings - Fork 25
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
Handle simple fixed factor filtering internally #75
Handle simple fixed factor filtering internally #75
Conversation
- This is a more realistic and stronger test
* Pass REGULAR and REGULAR_WITH_LAND Laplacian the additional grid variable "area" * Fix typos and implement Scott's comments
…into fixed-factor-filtering
…ters into fixed-factor-filtering
…ters into fixed-factor-filtering
…ters into fixed-factor-filtering
Check out this pull request on See visual diffs & provide feedback on Jupyter Notebooks. Powered by ReviewNB |
Codecov Report
@@ Coverage Diff @@
## master #75 +/- ##
==========================================
+ Coverage 98.47% 98.66% +0.19%
==========================================
Files 7 7
Lines 719 824 +105
==========================================
+ Hits 708 813 +105
Misses 11 11
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
…ters into fixed-factor-filtering
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.
These changes look good to me. @rabernat might want to take a look for Python style issues.
* before: REGULAR and REGULAR_WITH_LAND required an area grid variable that is needed for fixed factor filtering; this is reverted * instead: introduce two separate grid types TRANSFORM_TO_REGULAR and TRANSFORM_TO_REGULAR_WITH_LAND that are specifically for fixed factor filtering * expand docstrings of Laplacians * updated tests to mirror name changes
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.
First, let me apologize deeply and sincerely for taking so long to review this. The past month has been extremely challenging for me work-wise due to family and travel-related reasons.
I really like the spirit of this PR. I think it makes things much simpler to implement the weighting internally. However, I have one large-ish suggestions for the implementation.
I don't think it makes sense to check the type of the kernel in filters.py
and then do things differently based on what we find. That is a coding pattern that indicates poor separation of concerns between modules. Instead, I think kernels.py should take care of the weighting.
I think the best way to accomplish this would be to expand the Kernel interface to include prepare
and finalize
methods in the BaseLaplacian classes.
The default would be to do nothing, e.g.:
def prepare(self, field):
return field
But implementations could override this, e.g.
@dataclass
class AreaWeightedLaplacian(BaseLaplacian)
area: ArrayLike
def prepare(self, field):
return field * self.area
def __call__(self, field):
# do stuff
def finalize(self, field):
return field / self.area
This could even be a mixin, so we don't have to redefine the core laplacian __call__
functions, e.g.
@dataclass
class AreaWeightedMixin(BaseLaplacian)
area: ArrayLike
def prepare(self, field):
return field * self.area
def finalize(self, field):
return field / self.area
@dataclass
class RegularLaplacianWithArea(AreaWeightedMixin, RegularLaplacian):
pass
Then filter.py
would just call field = kernel.prepare(field)
, rather than implementing the weighting directly.
Does this suggestion make sense? Happy to chat more, and sorry again for my slowness.
I really appreciate your diligent and careful work on this project.
gcm_filters/filter.py
Outdated
"--> dx_min is set to 1", | ||
stacklevel=2, | ||
) | ||
self.dx_min = 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.
What scenario are you imagining here with this check / warming? Rather than overwriting dx_min
, why don't we just raise a ValueError
here and make the user fix it explicitly?
gcm_filters/filter.py
Outdated
@@ -309,8 +328,8 @@ def __post_init__(self): | |||
] | |||
if filter_factor >= max_filter_factor: | |||
warnings.warn( | |||
"Warning: Filter scale much larger than grid scale -> numerical instability possible", | |||
UserWarning, | |||
"Filter scale much larger than grid scale -> numerical instability possible", |
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.
Can we link to a documentation page with more context?
gcm_filters/filter.py
Outdated
raise ValueError( | ||
f"Provided Laplacian {self.Laplacian} is a vector Laplacian. " | ||
f"The ``.apply`` method is only suitable for scalar Laplacians." | ||
) | ||
if issubclass(self.Laplacian, BaseScalarLaplacianWithArea): | ||
# simple fixed factor filtering multiplies field by area before filtering | ||
field = field * self.grid_ds["area"] |
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 putting this login in the filter
module, why not put it into the kernel itself?
""" | ||
|
||
area: ArrayType | ||
|
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.
There should be a way to make the class itself do the area weighting / deweighting. To me that would be a lot cleaner than doing it manually in the filter
module (better separation of concerns).
* Introduce mixin class AreaWeightedMixin that handles area weighting and deweigting in kernel.py * Take advantage of multiple class inheritance in kernel.py for - RegularLaplacianWithArea - RegularLaplacianWithLandMaskAndArea * filter_func now calls prepare and finalize methods of the Laplacian classes * Update all tests
* ... if dx_min is not equal to 1 for simple fixed factor filtering * update test accordingly
Thanks for this suggestion, Ryan! I really like it; it makes things way cleaner. I updated the PR, and incorporated all your comments and suggestions. |
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 the super quick revisions! 🚀
One more round of minor comments, focused on the naming of stuff and documentation. Then LGTM!
gcm_filters/kernels.py
Outdated
1) Field on locally orthogonal grid is transformed to field on regularly spaced Cartesian | ||
grid with dx = dy = 1, through multiplication by cell area of original grid. | ||
2) Laplacian acts on regular Cartesian grid. | ||
3) Diffused field on regular Cartesian grid is transformed back to field on original grid, | ||
through division by cell area of original grid. |
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.
If you format this as a proper RST list, it will render correctly in the docs. As is, the list is "inline" and doesn't quite look right: https://gcm-filters--75.org.readthedocs.build/en/75/api.html#gcm_filters.kernels.RegularLaplacianWithArea
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 - the lists render correctly now.
I also tried to get rid off the hyphen that appears in the API at the beginning of the rendered docstrings for some of the Laplacians (but not for others). I couldn't resolve this issue. Any ideas?
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 do not, but this is relatively minor, so not a big deal to me.
|
||
Attributes | ||
---------- | ||
area: cell area of original grid |
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.
area: cell area of original grid |
When you use the AreaWeightedMixin
, you don't need area
as part of this class.
|
||
Attributes | ||
---------- | ||
area: cell area of original grid |
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.
area: cell area of original grid |
When you use the AreaWeightedMixin
, you don't need area
as part 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.
True, except that the function required_grid_vars
will not pick up area
if I don't redefine it here.
This is only an issue for the classes RegularLaplacianWithLandMaskAndArea
and TripolarRegularLaplacianTpoint
where additional attributes have to be defined (in addition to what is inherited from the superclasses). In contrast, it is not an issue for the RegularLaplacianWithArea
which does not need any additional attributes.
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.
Ok that makes sense. I don't really understand why return list(self.__annotations__)
in BaseScalarLaplacian
does not pick up on the mixin attributes; it must have to do with the nitty-gritty details of class inheritance. Perhaps fixable somehow in the required_grid_args
but not worth more effort here.
gcm_filters/kernels.py
Outdated
pass | ||
|
||
|
||
ALL_KERNELS[GridType.TRANSFORMED_TO_REGULAR] = RegularLaplacianWithArea |
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 find this naming confusing. "Transformed" is ambiguous, and it doesn't indicate anything about the area weighting. What aboutREGULAR_AREA_WEIGHTED
?
This is not a dealbreaker for me...but I'm just trying to think about what is most obvious for users.
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 went back and forth with the naming convention here; something similar to what you suggest here has been in the mix too! So I'm glad to hear that you find AREA_WEIGHTED
most intuitive.
gcm_filters/kernels.py
Outdated
|
||
|
||
ALL_KERNELS[ | ||
GridType.TRANSFORMED_TO_REGULAR_WITH_LAND |
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.
Same. Do we really want to call this "TRANSFORMED"? Or would "AREA_WEIGHTED" be more clear?
* change Laplacian naming convention from TRANSFORMED_TO to AREA_WEIGHTED * update tests according to new naming convention * reformat docstrings in kernel.py module so lists show up properly in API * link docstrings to kernel methods
Since I changed the names of 3 Laplacians (from I could also do the notebook update as part of PR #78, which is a big docs update. |
Let's go with that! I think we should merge this now. |
Issue
This PR fixes issue #71.
For applying a simple fixed factor filter (as for example described in the "Anisoptropic Filtering" section of the Filter Theory), the user was required to manually go through the following steps:
The first step is essentially a coordinate transformation where the original (locally orthogonal) grid is transformed to a uniform Cartesian grid with dx=dy=1. The third step is the reverse coordinate transformation.
Changes
These steps are now handled internally by the code.
I introduced a new Laplacian base class
BaseScalarLaplacianWithArea
for the Laplacians that are for simple fixed factor filtering:TRANFORMED_TO_REGULAR
TRANSFORMED_TO_REGULAR_WITH_LAND
TRIPOLAR_TRANSFORMED_TO_REGULAR_WITH_LAND
.Steps 1 and 3 from above are handled as part of the filter class for all Laplacians that are a subclass of
BaseScalarLaplacianWithArea
.Tutorial changes
I updated all tutorials to reflect the new way of doing simple fixed factor filtering.
Old:
New:
While updating the tutorials, I also fixed some typos and clarified some statements. Thanks @sdbachman for reading through the documentation and for providing these comments.