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

Region: explicit W/E/S/N bounds and cyclic longitude support; Longitude class and longitude module #266

Merged
merged 17 commits into from
May 2, 2018

Conversation

spencerahill
Copy link
Owner

Closes #229

Still need to implement the cyclic longitudes logic we converged on in #229; the new logic that's currently in region.py that touches this will be updated, as will the related tests.

Still also need tests and to finish implementation of support for non-aospy data. But wanted to open this PR to get a stake in the ground.

@spencerkclark running the tests on my local machine, the tutorial notebook test failed because I didn't update the Region definitions in the notebook to the new call signature. But now I can't remember how we generated the tutorial notebook. Do you recall? I could manipulate the .ipynb file directly, but this seems fraught.

@@ -3,7 +3,7 @@
import xarray as xr

from aospy import Region
from aospy.region import _get_land_mask
from aospy.region import _arr_to_0360_lon, _get_land_mask, _lon_to_0360
Copy link
Contributor

Choose a reason for hiding this comment

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

F401 'aospy.region._arr_to_0360_lon' imported but unused
F401 'aospy.region._lon_to_0360' imported but unused

# def test_mask_var_retain_orig_lons(data_for_reg_calcs, lons_orig,
# reg_lon_bounds):
# # Each 'reg_lon_bounds' was chosen so that, with the corresponding
# # 'lons_orig', the result will match the 'expected_data' (for convenience)
Copy link
Contributor

Choose a reason for hiding this comment

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

E501 line too long (80 > 79 characters)

@spencerkclark
Copy link
Collaborator

But now I can't remember how we generated the tutorial notebook. Do you recall? I could manipulate the .ipynb file directly, but this seems fraught.

Yeah don't edit the the .ipynb file directly :). The easiest thing is to just open it in Jupyter, make the changes, and save it.

@@ -3,7 +3,12 @@
import xarray as xr

from aospy import Region
from aospy.region import _get_land_mask
from aospy.region import (
Copy link
Contributor

Choose a reason for hiding this comment

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

F401 'aospy.region._lon_to_0360' imported but unused
F401 'aospy.region._arr_to_0360_lon' imported but unused

@spencerahill
Copy link
Owner Author

The calc test suite is now failing across the board due to a problem with the tearDown, e.g. here.

I don't see an obvious way that what I introduced caused this, although this is the first time we're seeing it. I just added a try/except guard to see if that solves it.

@spencerahill
Copy link
Owner Author

spencerahill commented Apr 24, 2018

I just added a try/except guard to see if that solves it.

Hmm, now these have been replaced by even less intuitive ones, e.g. here

It seems like the example objects within the CI environments have somehow been corrupted. Maybe due to the change in Region call signature? In contrast, these tests are all passing on my local machine.

@spencerkclark
Copy link
Collaborator

Hmm, now these have been replaced by even less intuitive ones, e.g. here

I think those were always there -- see L1828-L1873 in the old build for example. The tear down errors were just side effects of the results never being computed/saved to files.

Xarray recently released a new version (version 0.10.3); which version are you on locally?

@spencerahill
Copy link
Owner Author

Xarray recently released a new version (version 0.10.3); which version are you on locally?

Good call. I was on 10.2; after updating to 10.3 I can reproduce. I'll open a separate issue to track this.

Copy link
Collaborator

@spencerkclark spencerkclark left a comment

Choose a reason for hiding this comment

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

I still need to look at the code, but I read through the docstrings (which are in general very nice!) and noticed a few minor things that I didn't want to forget.

aospy/region.py Outdated
boundaries are more complicated than a single lat-lon rectangle,
use `mask_bounds` instead.
west_bound, east_bound, south_bound, north_bound : scalar, optional
The western, eastern southern, and northern boundaries,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Typo: add a comma between "eastern" and "southern."

(i.e. it crosses the Prime Meridian for data with longitudes spanning
0 to 360), it must be defined as the union of two sections extending to
the east and to the west of the Prime Meridian.
Note that longitudes spanning (-180, 180), (0, 360), or any other range
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should (0, 360) be (180, 540) to correspond with the example after the colon?

aospy/region.py Outdated
>>> globe = Region(name='globe', lat_bounds=(-90, 90),
... lon_bounds=(0, 360), do_land_mask=False)
>>> globe = Region(name='globe', west_bound=0, east_bound=360,
... south_bound=0, north_bound=360, do_land_mask=False)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm guessing for this example south_bound=-90 and north_bound=90 should be used.

@spencerahill
Copy link
Owner Author

I still need to look at the code, but I read through the docstrings (which are in general very nice!) and noticed a few minor things that I didn't want to forget.

Thanks! FYI I've realized after this commit that the logic I implemented is incomplete (i.e. wrong)...I'm working on a better solution and will ping you when it's ready for a proper review.

@@ -1,6 +1,6 @@
"""Subpackage comprising various utility functions used elsewhere in aospy."""
from . import io
from . import longitude
Copy link
Contributor

Choose a reason for hiding this comment

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

F401 '.longitude' imported but unused

@@ -1,6 +1,6 @@
"""Subpackage comprising various utility functions used elsewhere in aospy."""
from . import io
from . import longitude
from .longitude import Longitude
Copy link
Contributor

Choose a reason for hiding this comment

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

F401 '.longitude.Longitude' imported but unused

@spencerahill
Copy link
Owner Author

spencerahill commented May 1, 2018

OK, c.f. #272, I have re-done the logic using the new utils.longitude module and Longitude class. Remaining items to address for my own organizational purposes:

  • Make a decision about lon_cyclic (actually I think I've worked this out; new commit forthcoming)
  • Add Longitude to main docs
  • Double-check docs for Region
  • What's new

@spencerahill
Copy link
Owner Author

C.f. my comment #267 (comment), I think after all that dealing with non-cyclic longitudes is probably outside of the scope of this PR (despite that being partly the original motivation). Frankly, I am almost out of spare cycles for the next few weeks, and I want to get what's here so far in.

So we would return to the cyclic points in conjunction with how we treat region boundaries in a future PR. @spencerkclark does that sound OK to you? Sorry that I turned this one into such a long and winding road.

Either way, I'd appreciate a review of this PR when you get the chance. Thanks!

@spencerkclark
Copy link
Collaborator

So we would return to the cyclic points in conjunction with how we treat region boundaries in a future PR. @spencerkclark does that sound OK to you?

Totally fine! I'll try and give things a review over the next few days.

Copy link
Collaborator

@spencerkclark spencerkclark left a comment

Choose a reason for hiding this comment

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

Thanks @spencerahill, this is going to be a big upgrade for our region handling. I have mostly cosmetic comments, the one exception being one on the comparison of Longitude objects to DataArrays.

if isinstance(other, Longitude):
cond = (self.hemisphere == other.hemisphere and
self.longitude == other.longitude)
if cond:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe this if-else block is equivalent to simply returning cond.

(Longitude(0), Longitude(0)),
(Longitude('0E'), 0),
(Longitude('0E'), 720),
(Longitude('0E'), [0, 720])])
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here and for the other operators, should we test arrays on either the right or left side of the operator? For example:

@pytest.mark.parametrize(
('obj1', 'obj2'),
[(Longitude('100W'), Longitude('100W')),
...
(Longitude('0E'), [0, 720]),
([0, 720], Longitude('0E'))])

Copy link
Collaborator

Choose a reason for hiding this comment

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

It would also be good to add test coverage for use with DataArrays.

else:
return False
else:
return np.equal(other, self)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here and in the other operators we need special logic if other is a DataArray; as written this currently doesn't work:

In [1]: import xarray as xr

In [2]: from longitude import Longitude

In [3]: Longitude(0.) == xr.DataArray([0., 720])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-85150267fe77> in <module>()
----> 1 Longitude(0.) == xr.DataArray([0., 720])

/Users/spencerclark/Downloads/lon_sah2/longitude.py in func_other_to_lon(self, other)
     72 def other_to_lon(func):
     73     def func_other_to_lon(self, other):
---> 74         return func(self, _maybe_cast_to_lon(other))
     75     return func_other_to_lon
     76

/Users/spencerclark/Downloads/lon_sah2/longitude.py in __eq__(self, other)
    136                 return False
    137         else:
--> 138             return np.equal(other, self)
    139
    140     @other_to_lon

TypeError: operand type(s) all returned NotImplemented from __array_ufunc__(<ufunc 'equal'>, '__call__', <xarray.DataArray (dim_0: 2)>
array([  0., 720.])
Dimensions without coordinates: dim_0, Longitude('0.0E')): 'DataArray', 'Longitude'

I think the following should fix things:

if isinstance(other, xr.DataArray):
    return xr.apply_ufunc(np.equal, other, self)
else:
    return np.equal(other, self)

def __str__(self):
return 'Geographical region "' + self.name + '"'

__repr__ = __str__
def _make_mask(self, data, lon_str=LON_STR, lat_str=LAT_STR):
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is the reasoning behind having this function take lon_str and lat_str as arguments? As part of the data-loading process don't we automatically rename the longitude and latitude coordinates LON_STR and LAT_STR? Is it that you are envisioning using these functions outside of aospy?

Copy link
Owner Author

Choose a reason for hiding this comment

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

That's right. This gets called by mask_var, which in turn gets called by ts and its siblings, which I want to make available to non-aospy data.

aospy/region.py Outdated
"south, north). Value given: "
"{}".format(rect_bounds))
else:
lons = _lon_bounds_to_lon_obj(rect_bounds[:2])
Copy link
Collaborator

Choose a reason for hiding this comment

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

Even though it's an extra line, I have a slight preference for using tuple unpacking to extract the bounds here before converting to Longitude objects (for clarity):

west_bound, east_bound, south_bound, north_bound = rect_bounds
lons = _lon_bounds_to_lon_obj([west_bound, east_bound])
bounds.append(tuple(lons + [south_bound, north_bound]))

Copy link
Owner Author

Choose a reason for hiding this comment

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

I'm going to switch to a namedtuple, which will make this logic cleaner

region = Region(name='test', mask_bounds=bounds_in)
assert isinstance(region.mask_bounds, tuple)
assert len(region.mask_bounds) == 2
for n, bounds in enumerate(region.mask_bounds):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Slight preference for using zip and tuple unpacking here rather than enumerate and indexing the bounds_in list/tuples:

for (west, east, south, north), bounds in zip(bounds_in, region.mask_bounds):
    assert isinstance(bounds, tuple)
    assert np.all(bounds == (Longitude(west), Longitude(east), south, north))

(1, 2, 3)])


# def test_make_mask_latlon_rect(data_for_reg_calcs):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a reason behind these commented-out tests?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Sorry, forgot to remove these

@spencerahill
Copy link
Owner Author

@spencerkclark thanks, your comments all look good. Will address + flesh out the docs.

aospy/region.py Outdated
SFC_AREA_STR,
YEAR_STR
)
from .utils.longitude import Longitude, _maybe_cast_to_lon
Copy link
Contributor

Choose a reason for hiding this comment

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

F401 '.utils.longitude.Longitude' imported but unused

@spencerahill
Copy link
Owner Author

spencerahill commented May 2, 2018

OK, I think this is ready to go. Will merge once tests pass (EDIT and add a What's New). Thanks @spencerkclark for the invaluable help.

@spencerahill spencerahill merged commit 7162010 into develop May 2, 2018
@spencerahill spencerahill deleted the fix-region-lon-bounds branch May 2, 2018 22:02
@spencerahill spencerahill changed the title WIP Region: explicit W/E/S/N bounds and cyclic longitude support Region: explicit W/E/S/N bounds and cyclic longitude support; Longitude class and longitude module May 14, 2018
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.

None yet

3 participants