Skip to content
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

New differential operators: Laplacian, grad, div, and curl #293

Merged
merged 23 commits into from
Jan 11, 2022

Conversation

davidhassell
Copy link
Collaborator

@davidhassell davidhassell commented Jan 4, 2022

Fixes #292
Fixes #178

@davidhassell davidhassell added this to the 3.12.0 milestone Jan 4, 2022
@davidhassell
Copy link
Collaborator Author

davidhassell commented Jan 4, 2022

I'm not sure why the test action is failing for Python 3.7, it works for me at Python 3.8. Any ideas?

@sadielbartholomew
Copy link
Member

Any ideas?

As you likely saw it was one failure due to a partitioning reshape issue, which seems funky. I also do not know immediately what might be up. I have re-run the Actions jobs to see if we get the same result again, in case there is some test flakiness involved (also not good and would need fixing, of course)...

@sadielbartholomew
Copy link
Member

After re-running, the funky test failure has not emerged, but of the two jobs that ran before cancelling the others, both Python 3.9, the Ubuntu passed but the Mac OS failed on some some genuine failures relating to this PR:

======================================================================
FAIL: test_div_xy (test_Maths.MathTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/runner/work/cf-python/cf-python/main/cf/test/test_Maths.py", line 141, in test_div_xy
    self.assertTrue((d.data == d0.data).all())
AssertionError: False is not true

======================================================================
FAIL: test_Field_laplacian_xy (test_Field.FieldTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/runner/work/cf-python/cf-python/main/cf/test/test_Field.py", line 2728, in test_Field_laplacian_xy
    self.assertTrue(lp.equals(lp0))
AssertionError: False is not true

======================================================================
FAIL: test_Field_grad_xy (test_Field.FieldTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/runner/work/cf-python/cf-python/main/cf/test/test_Field.py", line 2661, in test_Field_grad_xy
    self.assertTrue((x.data == x0.data).all())
AssertionError: False is not true

----------------------------------------------------------------------
Ran 340 tests in 1414.374s

FAILED (failures=3)

I am not immediately sure why these are apparently failing for only Mac OS - something you might want to ponder whist I review the PR?

@davidhassell
Copy link
Collaborator Author

Thanks, Sadie - I'll investigate the new error. I'll probably commit some print statements, just for the purpose of diagnosis.

@davidhassell davidhassell marked this pull request as draft January 6, 2022 09:30
@davidhassell davidhassell marked this pull request as ready for review January 6, 2022 09:33
@davidhassell davidhassell marked this pull request as draft January 6, 2022 09:33
@davidhassell davidhassell marked this pull request as ready for review January 6, 2022 11:32
@davidhassell davidhassell marked this pull request as draft January 6, 2022 11:32
@davidhassell davidhassell marked this pull request as ready for review January 6, 2022 16:45
@davidhassell davidhassell marked this pull request as draft January 6, 2022 16:52
@davidhassell davidhassell reopened this Jan 7, 2022
@davidhassell davidhassell reopened this Jan 7, 2022
@codecov-commenter
Copy link

Codecov Report

Merging #293 (1d1bd37) into master (a08ac1f) will decrease coverage by 2.21%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #293      +/-   ##
==========================================
- Coverage   73.80%   71.59%   -2.20%     
==========================================
  Files          85       88       +3     
  Lines       19179    19778     +599     
==========================================
+ Hits        14153    14159       +6     
- Misses       5026     5619     +593     
Flag Coverage Δ
unittests 71.59% <ø> (-2.20%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
main/cf/data/collapse_functions.py 63.80% <0.00%> (-34.19%) ⬇️
main/cf/maths.py 66.17% <0.00%> (-17.41%) ⬇️
main/cf/data/data.py 70.50% <0.00%> (-8.49%) ⬇️
main/cf/domain.py 67.00% <0.00%> (-8.00%) ⬇️
main/cf/constructs.py 91.43% <0.00%> (-4.02%) ⬇️
main/cf/data/partitionmatrix.py 93.34% <0.00%> (-1.53%) ⬇️
main/cf/regrid/regridoperator.py 94.34% <0.00%> (-1.21%) ⬇️
main/cf/data/partition.py 84.84% <0.00%> (-1.05%) ⬇️
main/cf/dimensioncoordinate.py 68.68% <0.00%> (-0.96%) ⬇️
main/cf/read_write/read.py 92.18% <0.00%> (-0.84%) ⬇️
... and 30 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b77e5a2...1d1bd37. Read the comment docs.

@davidhassell
Copy link
Collaborator Author

Hi Sadie - I give up. I can't fathom the automated tests - they seem either pass or time out, now. So I'm putting this up for review, as it seems OK, as best I can make it. Thanks.

@davidhassell davidhassell marked this pull request as ready for review January 10, 2022 14:34
@sadielbartholomew
Copy link
Member

Thanks David, I have noticed the automated test jobs hanging on other PRs so I doubt it is an issue stemming from this PR. Sorry for the trouble, which I will investigate shortly, though I will review this first...

Copy link
Member

@sadielbartholomew sadielbartholomew left a comment

Choose a reason for hiding this comment

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

I'm mid-review, now reviewing the details of the method implementations, but have checked the tests and the new documentation (with much consultation of Wolfram MathWorld and working around their usage of a different angle symbol convention for spherical polar coordinates!), both of which look great and more than suitably comprehensive.

Just noting some initial comments and suggestions to consider, in the meantime, including the following general comment...


The unit tests look very good, and are certainly sufficient for testing, though I was wondering if we could test on some simple vector calculus identities to act as a basic integration test and further validate some, if not all of, the new operators, as that would be simple and quick to add.

For example whilst playing around with the new methods I was checking, based on two of the identities listed here, whether the divergence of the curl of some (of any) field ended up being close enough (considering floating point calculations) to zero, and likewise with the curl of the gradient, like so:

In [64]: import cf
    ...: a = cf.example_field(0)
    ...: 
    ...: a_x, a_y = a.grad_xy(radius="earth", one_sided_at_boundary=True)
    ...: c = cf.curl_xy(a_x, a_y, radius="earth", one_sided_at_boundary=True)
    ...: d = cf.div_xy(a_x, a_y, radius="earth", one_sided_at_boundary=True)
    ...: 
    ...: dc = cf.div_xy(c, c, radius="earth", one_sided_at_boundary=True)
    ...: print(dc.identity())  # check name reflects context
    ...: print(dc.array)  # should be zero within calc approximation
    ...: zeros = dc.copy()
    ...: zeros[...] = 0
    ...: print(cf.equals(dc, zeros, verbose=3))
    ...: 
    ...: cg = cf.curl_xy(a_x, a_y, radius="earth", one_sided_at_boundary=True)
    ...: print(cg.identity())  # check name reflects context
    ...: print(cg.array)  # should be zero within calc approximation
    ...: zeros = cg.copy()
    ...: zeros[...] = 0
    ...: print(cf.equals(cg, zeros, verbose=3))
long_name=Horizontal divergence of (long_name=Horizontal curl of (long_name=X gradient of specific_humidity, long_name=Y gradient of specific_humidity), long_name=Horizontal curl of (long_name=X gradient of specific_humidity, long_name=Y gradient of specific_humidity))
[[ 3.00738521e-37 -7.51846302e-38  0.00000000e+00  4.94399257e-37
   8.17934420e-37 -1.25764388e-36 -5.17195900e-37  6.88059993e-37]
 [-6.48640473e-38 -2.71237017e-38  5.93601504e-38  2.20155877e-38
  -5.81820676e-38 -6.76206288e-38  8.29496038e-38  5.34651036e-38]
 [-5.35128390e-38  1.96619143e-38 -8.10800592e-39 -2.10808154e-38
   1.62160118e-38  1.13512083e-38 -1.94592142e-38 -1.29728095e-38]
 [-4.81471642e-38  6.09340161e-38 -2.73335270e-38  5.18912379e-38
   1.42671699e-38 -1.53289750e-38  3.10583659e-38 -3.14495159e-38]
 [ 1.42683698e-37  6.13375424e-37 -1.42683698e-37 -4.22686361e-38
   1.21925124e-38  8.05513260e-37  6.37863089e-38 -8.71473313e-37]]
True
long_name=Horizontal curl of (long_name=X gradient of specific_humidity, long_name=Y gradient of specific_humidity)
[[ 0.00000000e+00 -5.01626996e-31  0.00000000e+00 -5.01626996e-31
   5.01626996e-31  2.00650799e-30 -1.00325399e-30 -5.01626996e-31]
 [ 3.67216448e-31 -2.75412336e-31  0.00000000e+00  1.83608224e-31
   0.00000000e+00 -9.18041120e-32  1.83608224e-31  1.83608224e-31]
 [ 0.00000000e+00  1.62288275e-32 -3.24576551e-32  0.00000000e+00
   1.62288275e-32 -3.24576551e-32  0.00000000e+00  3.24576551e-32]
 [-3.67216448e-31  4.87709345e-32 -9.18041120e-32 -1.83608224e-31
   2.75412336e-31  9.18041120e-32 -1.83608224e-31  0.00000000e+00]
 [-1.25406749e-30  5.64330371e-31  0.00000000e+00  0.00000000e+00
  -4.98977600e-31  1.00325399e-30  1.00325399e-30  0.00000000e+00]]
True

So my suggestion is a small test method called test_diff_operators or similar which tests some basic combinations, like the above (not essential, but could be useful).

Changelog.rst Show resolved Hide resolved
cf/field.py Show resolved Hide resolved
cf/field.py Outdated Show resolved Hide resolved
Copy link
Member

@sadielbartholomew sadielbartholomew left a comment

Choose a reason for hiding this comment

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

Some final comments, but other than those registered this is all good and ready to merge. Great stuff!

cf/field.py Outdated
"""
from numpy import pi

f = _inplace_enabled_define_and_cleanup(self)
Copy link
Member

@sadielbartholomew sadielbartholomew Jan 10, 2022

Choose a reason for hiding this comment

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

In-place Laplacian calculation doesn't seem to work properly, though I can't immediately see why since the standard in-place logic and decorator are all there - any ideas what is going on? I see:

In [15]: import cf
    ...: a = cf.example_field(0)
    ...: print("A IS", a, a.array)
    ...: 
    ...: b = a.laplacian_xy(radius="earth")
    ...: print("B IS", b, b.array)
    ...: 
    ...: c = a.laplacian_xy(radius="earth", inplace=True)
    ...: print("IN-PLACE A IS", a, a.array, "C IS", c)
    ...: cf.equals(a, b, verbose=3)
A IS Field: specific_humidity (ncvar%q)
----------------------------------
Data            : specific_humidity(latitude(5), longitude(8)) 1
Cell methods    : area: mean
Dimension coords: latitude(5) = [-75.0, ..., 75.0] degrees_north
                : longitude(8) = [22.5, ..., 337.5] degrees_east
                : time(1) = [2019-01-01 00:00:00] [[0.007 0.034 0.003 0.014 0.018 0.037 0.024 0.029]
 [0.023 0.036 0.045 0.062 0.046 0.073 0.006 0.066]
 [0.11  0.131 0.124 0.146 0.087 0.103 0.057 0.011]
 [0.029 0.059 0.039 0.07  0.058 0.072 0.009 0.017]
 [0.006 0.036 0.019 0.035 0.018 0.037 0.034 0.013]]
B IS Field: long_name=Horizontal Laplacian of specific_humidity (ncvar%q)
--------------------------------------------------------------------
Data            : long_name=Horizontal Laplacian of specific_humidity(latitude(5), longitude(8)) m-2.rad-2
Cell methods    : area: mean
Dimension coords: latitude(5) = [-75.0, ..., 75.0] degrees_north
                : longitude(8) = [22.5, ..., 337.5] degrees_east
                : time(1) = [2019-01-01 00:00:00] [[-- -- -- -- -- -- -- --]
 [-- -- -- -- -- -- -- --]
 [-2.143064530824368e-15 -2.6749442771355633e-15 -2.4238409002370553e-15
  -2.6377524366954036e-15 -1.0992306748059246e-15 -1.6075152632499628e-15
  3.5426406417251047e-16 2.2860907541031372e-15]
 [-- -- -- -- -- -- -- --]
 [-- -- -- -- -- -- -- --]]
IN-PLACE A IS Field: specific_humidity (ncvar%q)
----------------------------------
Data            : specific_humidity(latitude(5), longitude(8)) 1
Cell methods    : area: mean
Dimension coords: latitude(5) = [-1.3089969389957472, ..., 1.3089969389957472] radians
                : longitude(8) = [0.39269908169872414, ..., 5.8904862254808625] radians
                : time(1) = [2019-01-01 00:00:00] [[0.007 0.034 0.003 0.014 0.018 0.037 0.024 0.029]
 [0.023 0.036 0.045 0.062 0.046 0.073 0.006 0.066]
 [0.11  0.131 0.124 0.146 0.087 0.103 0.057 0.011]
 [0.029 0.059 0.039 0.07  0.058 0.072 0.009 0.017]
 [0.006 0.036 0.019 0.035 0.018 0.037 0.034 0.013]] C IS None
Field: Different Units: <Units: 1> != <Units: m-2.rad-2>
Out[15]: False

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah - got it. The culprit is that f is reassigned:

 f = _inplace_enabled_define_and_cleanup(self)
 <snip>
 f = d2f_dx2 + d2f_dy2
 return f

I'll fix that and add the test that caught it.

Copy link
Collaborator Author

@davidhassell davidhassell Jan 11, 2022

Choose a reason for hiding this comment

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

Hmm. Weird things started happening with NaNs when it was properly in-place. I think the answer is to remove inplace from laplacian_xy for now, and see if anyone ever notices (there would be no real performance benefit from it, in this case, anyway)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

(Also, none of the other new methods/function have inplace, so it sort of consistent)

Copy link
Member

Choose a reason for hiding this comment

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

I think the answer is to remove inplace from laplacian_xy for now, and see if anyone ever notices (there would be no real performance benefit from it, in this case, anyway)

(Also, none of the other new methods/function have inplace, so it sort of consistent)

Sounds good to me! If anyone asks it is for consistency...

cf/test/test_Field.py Outdated Show resolved Hide resolved
@davidhassell
Copy link
Collaborator Author

New test test_differential_operators, as suggested.

Copy link
Member

@sadielbartholomew sadielbartholomew left a comment

Choose a reason for hiding this comment

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

Excellent, I'm fully happy so please merge when ready.

@davidhassell
Copy link
Collaborator Author

Great - all done, then. Thanks for your review that has improved things a lot.

@davidhassell davidhassell merged commit 1aa619d into NCAS-CMS:master Jan 11, 2022
@davidhassell davidhassell deleted the differential-operators branch November 15, 2022 09:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

New differential operators: Laplacian, grad, div and curl Function for calculating horizontal divergence
3 participants