Skip to content

Gridcell rotations #3096

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

Merged
merged 3 commits into from
Jul 31, 2018
Merged

Gridcell rotations #3096

merged 3 commits into from
Jul 31, 2018

Conversation

pp-mo
Copy link
Member

@pp-mo pp-mo commented Jul 26, 2018

**WIP - DO NOT MERGE probably can now merge, once caveats are established
Here is a first look at the gridcell angles code.

The tests are currently a tangled mess of exploratory code + not fit for human consumption !
Eventually, I will have just the 2 new routines and proper unit tests for them.

There is a fair bit that still needs to be done, even in the main code :

  • improve documentation generally
  • provide for coords in alternative coord-systems, by projecting to ordinary lat-lon
  • review how coords are identified (axis rather than 'longitude' + 'latitude' names)

if old_style:
mid_lons = np.deg2rad(q[0])

pr = _3d_xyz_from_latlon(r[0], r[1]) - _3d_xyz_from_latlon(p[0], p[1])

Choose a reason for hiding this comment

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

E222 multiple spaces after operator

lmb_hatvec_y,
lmb_hatvec_z)])

pr = _3d_xyz_from_latlon(r[0], r[1]) - _3d_xyz_from_latlon(p[0], p[1])

Choose a reason for hiding this comment

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

E222 multiple spaces after operator

@@ -37,6 +37,9 @@
import iris.coord_systems
import iris.exceptions
from iris.util import _meshgrid
from ._grid_angles import (

Choose a reason for hiding this comment

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

F401 '._grid_angles.true_vectors_from_grid_vectors as rotate_grid_vectors' imported but unused
F401 '._grid_angles.gridcell_angles' imported but unused

import iris.tests as tests

import numpy as np
import numpy.ma as ma

Choose a reason for hiding this comment

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

F401 'numpy.ma' imported but unused


import cartopy.crs as ccrs
from iris.cube import Cube
from iris.coords import DimCoord, AuxCoord

Choose a reason for hiding this comment

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

F401 'iris.coords.DimCoord' imported but unused

## for iy in range(ny):
## for ix in range(nx):
## plt.plot([x0s[iy, ix], x1s[iy, ix]],
## [y0s[iy, ix], y1s[iy, ix]],

Choose a reason for hiding this comment

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

E266 too many leading '#' for block comment

## for ix in range(nx):
## plt.plot([x0s[iy, ix], x1s[iy, ix]],
## [y0s[iy, ix], y1s[iy, ix]],
## 'o-', markersize=4., markeredgewidth=0.,

Choose a reason for hiding this comment

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

E266 too many leading '#' for block comment

## plt.plot([x0s[iy, ix], x1s[iy, ix]],
## [y0s[iy, ix], y1s[iy, ix]],
## 'o-', markersize=4., markeredgewidth=0.,
## color='green', # scale_units='xy', scale=scale,

Choose a reason for hiding this comment

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

E266 too many leading '#' for block comment

## [y0s[iy, ix], y1s[iy, ix]],
## 'o-', markersize=4., markeredgewidth=0.,
## color='green', # scale_units='xy', scale=scale,
## transform=data_proj)

Choose a reason for hiding this comment

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

E266 too many leading '#' for block comment




ax.set_global()

Choose a reason for hiding this comment

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

E303 too many blank lines (3)

Copy link
Member

@lbdreyer lbdreyer left a comment

Choose a reason for hiding this comment

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

Not done! I'm making my way though the _angle function and then just have tests to look through, but I thought I'd submit this for now

uu, vv = mags * np.cos(angs), mags * np.sin(angs)

# Promote all to masked arrays, and also apply mask at bad (NaN) angles.
mask = np.isnan(aa)
Copy link
Member

Choose a reason for hiding this comment

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

Can aa i.e. the grid cell angles also be masked, or just nans?

Copy link
Member Author

Choose a reason for hiding this comment

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

I hadn't thought of that.
I think it doesn't much matter, as the results would just be NaN at those points.

Copy link
Member Author

Choose a reason for hiding this comment

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

... however, now I'm wondering if we should return masked arrays in every case.
Iris is usually more intelligent than that.

Valid values are 'lower-left, lower-right', which takes the angle from
the lower left to the lower right corner, and 'mid-lhs, mid-rhs' which
takes an angles between the average of the left-hand and right-hand
pairs of corners. The default is 'mid-lhs, mid-rhs'.
Copy link
Member

Choose a reason for hiding this comment

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

Any chance it would ever need to be upper-left, upper right?

Copy link
Member Author

Choose a reason for hiding this comment

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

The original code I was working from (ex-IDL) used only point values.
I extended it to use bounds because that is much more logical, and also works properly for regional data.
What I found was that, while 'mid-lhs..mid-rhs' was a more obvious choice, 'bottom-left..bottom-right' was closer to the 'official' cell angles in the model output file (when included).
So I hedged my bets by making it switchable, but only supported 2 options = 'seems most logical' and 'best match to official values'.

We can perhaps standardise on just the 'mid-lhs..mid-rhs' option, if I could convince myself that the differences are not significant. I don't really want to make 'bottom-left..bottom-right' the default, as it seems the poorer choice.

Copy link
Member Author

@pp-mo pp-mo Jul 30, 2018

Choose a reason for hiding this comment

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

standardise on just the 'mid-lhs..mid-rhs' option, if I could convince myself that the differences are not significant

But unfortunately I checked, and the discrepancies are significant in some areas. At least a few degrees for practical purposes.
Also, the errors for quasi-ideal square-ish gridcells rotated at various angles seem to be ~linear in the size of the cells for the 'bottom-left..bottom-right' method while ~square for the 'mid-lhs..mid-rhs'.
So that means I'm not comfortable 'standardising' on either of these exclusively 😞


"""
cube = None
if hasattr(x, 'add_aux_coord'):
Copy link
Member

Choose a reason for hiding this comment

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

Why aren't you going isinstance?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because duck typing is "Better" ™️ !

raise ValueError(msg.format(type(x), type(y)))

x_coord, y_coord = None, None
if hasattr(x, 'bounds') and hasattr(y, 'bounds'):
Copy link
Member

Choose a reason for hiding this comment

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

As before, why not check for isinstance to check it's a cube?

msg = 'Input {!r} is a Coordinate, but {!r} is not.'
raise ValueError(*is_and_not)

# Now have either 2 points arrays or 2 bounds arrays.
Copy link
Member

Choose a reason for hiding this comment

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

Can this be more explicit, i.e.
# Now we should have either 2 points arrays of shape (nx, ny) or 2 bounds arrays of shape (nx, ny, 4)

Copy link
Member Author

Choose a reason for hiding this comment

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

ok!

if x.ndim == 2:
# PROBLEM: we can't use this if data is not full-longitudes,
# i.e. rhs of array must connect to lhs (aka 'circular' coordinate).
# But we have no means of checking that ?
Copy link
Member

Choose a reason for hiding this comment

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

⚠️
This is concerning

Copy link
Member Author

@pp-mo pp-mo Jul 27, 2018

Choose a reason for hiding this comment

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

Yes, but that's how it is.
I don't see any practical alternative.
Instead, I have stated this behaviour in the docstring.

The only extra thing I can imagine to do here would be to insist on 'circular' if we are passed an x-coord, but I'm very down on that : As a rare example of non-CF metadata, I think 'circular' it is a bit of a mistake, and I don't think Iris uses the concept at all consistently -- I guess it is probably is not set in many cases where it would be appropriate.

# NOTE: so with bounds, we *don't* need full circular longitudes.
xyz = _3d_xyz_from_latlon(x, y)
angle_boundpoints_vals = {'mid-lhs, mid-rhs': '03_to_12',
'lower-left, lower-right': '0_to_1'}
Copy link
Member

Choose a reason for hiding this comment

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

Kinda confusing names 03_to_12 and 0_to_1. I initially thought it may have something to do with a clock.
I understand them now, but I will try to think up something informative

'must be one of {}')
raise ValueError(msg.format(cell_angle_boundpoints,
list(angle_boundpoints_vals.keys())))
if not x_coord:
Copy link
Member

Choose a reason for hiding this comment

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

Maybe better to do
if not x_coord and y_coord
I'm not sure if you catch the example someone supplying giving gridcell_angles a coord and an array as arguments. So maybe it would, instead, be better to check that earlier?

Copy link
Member Author

@pp-mo pp-mo Jul 27, 2018

Choose a reason for hiding this comment

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

The code above checked for "one coord + one non-coord", and will error that, here.


angles : (2-dimensional cube)

Cube of angles of grid-x vector from true Eastward direction for
Copy link
Member

Choose a reason for hiding this comment

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

How about, if the input was a cube, the grid cell angles are an aux coord on the original cube? Or is that too confusing?

It just seems wasteful to have a copy of the lat and lon coord somewhere else. I guess this is an example of where shared coordinates would be useful

Copy link
Member Author

Choose a reason for hiding this comment

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

We can have gridcell angles in the file, but as they are not a CF concept they never appear as an aux-coord, but instead just as a separate cube.

Talking of inefficient, of course the "angles" cube has another copy of the lats + lons with all their bounds!
We really do need to fix this ...

import iris


def _3d_xyz_from_latlon(lon, lat):
Copy link
Member

Choose a reason for hiding this comment

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

These functions ( _3d_xyz_from_latlon and '_latlon_from_xyz') seems quite general purpose functions (not just specific to the case of rotating grid cells)... could we not put them somewhere else so they can be used by other things?

Copy link
Member

Choose a reason for hiding this comment

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

@pp-mo have you logged this suggestion in a labelled issue yet? Might be better to merge first and think about where things should live later.

@pp-mo pp-mo changed the base branch from master to 2d_coords July 30, 2018 10:10
@pp-mo
Copy link
Member Author

pp-mo commented Jul 30, 2018

**WIP - DO NOT MERGE probably can now merge, once caveats are established

TODO:

  • I have already prepared somemore changes + tidying to this
  • once we have resolved the outstanding stuff here, I will merge that in
  • also various additional (minor) testing is wanted
  • which I propose to defer for now

@pp-mo pp-mo mentioned this pull request Jul 30, 2018
Return angle (in _radians_) of grid wrt local east.
Anticlockwise +ve, as usual.
{P, Q, R} are consecutive points in the same row,
eg {v(i,j),f(i,j),v(i+1,j)}, or {T(i-1,j),T(i,j),T(i+1,j)}
Copy link
Member

Choose a reason for hiding this comment

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

Should this not be:
{v(i,j),v(i,j),v(i+1,j)}

Why are v and T used here?

Also, I assume these function docs strings are not quite finalised yet? So I will reserve comments on the wording till then

Copy link
Member Author

@pp-mo pp-mo Jul 30, 2018

Choose a reason for hiding this comment

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

This is inherited docs + it does need improving.
Let's defer that : #3109

( I think the 'v', 'f', and 'T' are referring to different types of model value sited at different gridcell locations (i.e. stagger), so 'v' is on a v-grid, while 'T' (and 'f'?) will be on the 'T' grid. )

"""
# old_style = True
old_style = False
if old_style:
Copy link
Member

Choose a reason for hiding this comment

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

How do you plan to determine whether or not old_style should be used?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry, this was an alternative calculation that I used for evaluation.
I have since removed it (cf. pp-mo#45)

Though... I accept that I still don't have a full understanding of this calculation 😬 .
We should probably make more effort to explain exactly what this is doing.
Put that also down to #3109 !

Args:

* xyz: (array)
positions array, of dims (3, <others>), where index 0 maps x/y/z.
Copy link
Member

Choose a reason for hiding this comment

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

This is a bit confusing. I know that the docs in general are not finished though, so I won't go on.

Returns:

lonlat : (array)
spherical angles, of dims (2, <others>), in radians.
Copy link
Member

Choose a reason for hiding this comment

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

so many commas...

Copy link
Member Author

Choose a reason for hiding this comment

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

We need to work out how to capture outstanding review issues on things we merged in advance.
Perhaps like this ... #3114 ?

@lbdreyer lbdreyer merged commit c098c5a into SciTools:2d_coords Jul 31, 2018
@pp-mo pp-mo mentioned this pull request Jul 31, 2018
@pp-mo pp-mo deleted the vector_plots branch January 8, 2019 12:57
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.

4 participants