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

name to grib #835

Merged
merged 2 commits into from
Nov 22, 2013
Merged

name to grib #835

merged 2 commits into from
Nov 22, 2013

Conversation

bblay
Copy link
Contributor

@bblay bblay commented Nov 12, 2013

Reviewer: cpelley

Enhance NAME to GRIB translation, such that comparable cubes can be loaded back in.
The internal user story is as follows:

>>> for i, cube in enumerate(iris.load(filename)): 
…        iris.save(cube, 'cube{}.grib2'.format(i))
should result in grib files that can be loaded back into Iris resulting in comparable cubes to the
source i.e. equal data and coords where filename is NAMEII_field.txt and also NAMEIII_field.txt
from the current test data. The name and units should be preserved if supported by GRIB2, and
set to unknown/missing otherwise.

TODO:

Raised https://github.com/SciTools/iris-code-generators/pull/23 to mirror the rule changes...

@ghost ghost assigned bblay Nov 13, 2013
@@ -393,7 +393,7 @@ def type_of_statistical_processing(cube, grib, coord):
if len(coord_names) == 1 and coord_names[0] == coord.name():
if cell_method.method == 'mean':
stat_code = 0
elif cell_method.method == 'accumulation':
elif cell_method.method in ['accumulation', 'sum']:
Copy link

Choose a reason for hiding this comment

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

Since your in this space anyway, 'accumulation' doesn't look like it's a valid cf cell method. Is this captured in any of the tests? Might be worth removing it.

Copy link

Choose a reason for hiding this comment

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

Actually... while you're here, why not do a dictionary lookup?

@cpelley
Copy link

cpelley commented Nov 13, 2013

Why is this PR limited to NAMEII/III fields? What about NAMEIII trajectories for example, is it not applicable here too?

# Prepare for comparison
grib_cube.coord('forecast_period').convert_units('minutes')
name_cube.rename('unknown')
name_cube.units = 'unknown'
Copy link

Choose a reason for hiding this comment

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

I'll take a close look at these comparisons once #833 is in.

Copy link

Choose a reason for hiding this comment

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

doing so now

@cpelley
Copy link

cpelley commented Nov 13, 2013

I think this PR definitely warrants adding information to the whatsnew. Mentioning that cell methods are now created and also support for grib export from NAME.

@@ -318,6 +318,18 @@ def convert(grib):

if \
(grib.edition == 2) and \
(grib.typeOfFirstFixedSurface == 103) and \
Copy link

Choose a reason for hiding this comment

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

For my own reference, this corresponds to 'Specified height level above ground'

@cpelley
Copy link

cpelley commented Nov 13, 2013

Over to you @bblay

@cpelley
Copy link

cpelley commented Nov 14, 2013

@rhattersley - Ideally, all code changes should be accompanied by one or more unit tests, and by zero or more integration tests. - #818

I'm quite happy with the integration tests here, I would have unit tested the grib load rules added by mocking out the grib API (which I believe is already in the iris codebase ) and also unittesting the changes made within lib/iris/fileformats/name_loaders.py

What do you think @rhattersley?

@bblay
Copy link
Contributor Author

bblay commented Nov 14, 2013

Why is this PR limited to NAMEII/III fields?

That's just the user story I'm working on , as pasted into this PR's description.

@bblay
Copy link
Contributor Author

bblay commented Nov 14, 2013

I would have unit tested the grib load rules added by mocking out the grib API ... and also unittesting the changes made within lib/iris/fileformats/name_loaders.py

Fair comment. I was probably wrong to omit those tests, but everything you mention is actually being tested already, which gives an interesting effort vs benefit question. I thought I'd see where that lead.

For starters, it's not too safe, as it relies on indirect tests which could change.
E.g the grib load changes are tested when the saved name cubes are loaded back in (pending other review action!),
but that might change one day, and then there'd be no testing of it...I'll add the tests.

@bblay
Copy link
Contributor Author

bblay commented Nov 14, 2013

Review actions + rebased onto latest master.

I'll add the tests.

I tested GRIB2 height level loading, but only one of the two new rules as it looked horribly mockist otherwise :D, and they're pretty much the same. For the name loading, that's properly tested already, as we can see in the results.

@bblay bblay closed this Nov 14, 2013
@bblay bblay reopened this Nov 14, 2013
@@ -393,7 +393,7 @@ def type_of_statistical_processing(cube, grib, coord):
if len(coord_names) == 1 and coord_names[0] == coord.name():
if cell_method.method == 'mean':
stat_code = 0
elif cell_method.method == 'accumulation':
elif cell_method.method == 'sum':
Copy link

Choose a reason for hiding this comment

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

Any reason, why you object to a dictionary lookup? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've done that, I just haven't pushed any of the review actions since the original request.

@cpelley
Copy link

cpelley commented Nov 14, 2013

You have not taken action on the issues surrounding your coordinates:

>>> coord='time'; print name_cube.coord(coord); print grib_cube.coord(coord)
DimCoord([2010-01-26 12:00:00], bounds=[[2010-01-26 09:00:00, 2010-01-26 12:00:00]], standard_name='time', calendar='gregorian')
DimCoord([2010-01-26 10:30:00], bounds=[[2010-01-26 09:00:00, 2010-01-26 12:00:00]], standard_name='time', calendar='gregorian')
>>> coord='height'; print name_cube.coord(coord); print grib_cube.coord(coord)
DimCoord(array([ 50.]), bounds=array([[   0.,  100.]]), standard_name='height', units=Unit('m'))
DimCoord(array([ 50.]), bounds=array([[   0.,  100.]]), standard_name=None, units=Unit('m'), long_name='height')

You will need to correct this and address your tests which don't currently compare the coordinates due to the logic you have used.

Fair comment. I was probably wrong to omit those tests, but everything you mention is actually being tested already, which gives an interesting effort vs benefit question. I thought I'd see where that lead.

I'm now more inclined to conduct unittesting :)
Unittesting and at at time mocking objects allows us to assert calling of functions/methods and ensures that the modification you make result in behaviour that you explicitly intended (this was not captured in your integration tests here). I only discovered issued when debugging a 'passing' test.

self.assertTrue(np.allclose(name_cube.data, grib_cube.data))
self.assertTrue(name_cube.is_compatible(grib_cube))
for coord in name_cube.coords():
if grib_cube.coords(coord):
Copy link

Choose a reason for hiding this comment

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

Your tests will pass whatever happens, as this won't be True

@cpelley
Copy link

cpelley commented Nov 14, 2013

Over to you @bblay

@bblay
Copy link
Contributor Author

bblay commented Nov 15, 2013

You have not taken action on the issues surrounding your coordinates

I haven't push any of the new review actions yet! 😀

@bblay
Copy link
Contributor Author

bblay commented Nov 19, 2013

There has been quite a big change in the way time is handled here, so I've made a new, single commit as a new starting point.

More specifically, I was not interpreting the NAME dates properly, confusing "run time" with "model start time". We can't actually get the model start time, so we can't calculate a CF-style forecast_period on loading NAME fields. This has the knock on effect of not being able to save to grib2 without changes to the grib saving ruies, which are in this new commit.

('integration', 'name_grib', 'NAMEII',
'{}_{}.cml'.format(i, name_cube.name()))))

def test_name3_field(self):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is pretty much identical to the name2 test, happy to squidge them together if preferred.

Copy link

Choose a reason for hiding this comment

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

I would have a private method which puts all this common stuff together, but still have two separate unittest functions, one for the NAMEII and the other for the NAMEIII which calls it.

Copy link

Choose a reason for hiding this comment

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

In future, I would not have added these 'acceptance tests', these are internally driven and should not replace a proper coverage using unittests.

Copy link

Choose a reason for hiding this comment

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

You have not pulled out the common code into a private method of the class. In addition, I would also mention that these integration tests will be moved into internal acceptance tests in future.

@bblay
Copy link
Contributor Author

bblay commented Nov 19, 2013

Back to you, @cpelley

@bblay
Copy link
Contributor Author

bblay commented Nov 20, 2013

just getting set up at home to fix that test...

@bblay bblay closed this Nov 20, 2013
if fp_coord is not None:
if fp_coord.has_bounds():
raise iris.exceptions.TranslationError(
"Bounds not expected for 'forecast_period'")
Copy link

Choose a reason for hiding this comment

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

Might be worth changing this message, the problem is with interpreting a bounded forecast period when exporting to GRIB specifically, is it not?

Copy link

Choose a reason for hiding this comment

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

scratch that, the exception will come up in grib_save_rules

self.assertEqual((coord.name(), coord.units, coord.points[0]),
('height', 'm', 2.0))

def test_grib2_height(self):
Copy link

Choose a reason for hiding this comment

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

move these to unittests dir

@cpelley
Copy link

cpelley commented Nov 22, 2013

In future can you put the cml changes in a separate commit :)

stat_code = 6
stat_codes = {'mean': 0, 'sum': 1, 'maximum': 2, 'minimum': 3,
'standard_deviation': 6}
stat_code = stat_codes[cell_method.method]
Copy link

Choose a reason for hiding this comment

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

The type_of_statistical_processing is still not being unittested

@cpelley
Copy link

cpelley commented Nov 22, 2013

Technically speaking, this is also the time to build the mock framework for both NameII and NameIII fields as unittests since you should check that _generate_cubes is being called with your cell_methods.


class TestNameToGRIB(tests.IrisTest):
def _assert_cube_equal(self, path_name, path_result):
pass
Copy link

Choose a reason for hiding this comment

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

_assert_cube_equal not being used

@bblay
Copy link
Contributor Author

bblay commented Nov 22, 2013

Technically speaking, this is also the time to build the mock framework for both NameII and NameIII fields as unittests

Just checking, that's not a request to do that now, right?

@cpelley
Copy link

cpelley commented Nov 22, 2013

Well you have changed them, I would have

@cpelley
Copy link

cpelley commented Nov 22, 2013

Just to mention.. this PR has now substantially increased in size, another side-effect of stringent unit-testing.

class TestNameToGRIB(tests.IrisTest):

def check_common(self, name_cube, grib_cube):
self.assertTrue(np.allclose(name_cube.data, name_cube.data))
Copy link

Choose a reason for hiding this comment

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

I would have used, the iris self.assertArrayAlmostEqua(a, b)
This results in a test failure with the latitude, longitude coords however.

On discussion with you, you have confirmed that the difference in precision can be accounted for in the way that grib packs data.

@cpelley
Copy link

cpelley commented Nov 22, 2013

OK, happy for merging

bblay added a commit that referenced this pull request Nov 22, 2013
@bblay bblay merged commit 20ea1f0 into SciTools:master Nov 22, 2013
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.

3 participants