Created
October 30, 2014 10:08
-
-
Save rockdoc/e3a55de71e94377c7f1e to your computer and use it in GitHub Desktop.
Unit tests for climatology dates created by Iris time-based aggregation operations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| Test the structure and content of climatological data generated by Iris. | |
| For example, as a result of calling the cube.aggregated_by method with | |
| various auxiliary coordinates. | |
| """ | |
| import sys | |
| import unittest | |
| import iris | |
| import iris.coord_categorisation | |
| import numpy as np | |
| import numpy.testing as npt | |
| from datetime import datetime | |
| class TestIrisClimatology(unittest.TestCase) : | |
| """ | |
| Test monthly mean climatological data generated by Iris using month number | |
| as an auxiliary coordinate. | |
| """ | |
| def setUp(self) : | |
| self.time_origin = 'days since 1850-01-01 00:00:00' | |
| self.calendar = '360_day' | |
| self.mid_month = 16 | |
| def test_clim_dates_for_odd_year_span(self): | |
| """ | |
| Create a monthly mean climatology cube spanning an odd number of years. | |
| Check that the time coords in the resulting cube match the expected CF | |
| dates. This test should pass. | |
| """ | |
| self.check_climatology_dates(2000, 2010) | |
| def test_clim_dates_for_even_year_span(self): | |
| """ | |
| Create a monthly mean climatology cube spanning an even number of years. | |
| Check that the time coords in the resulting cube match the expected CF | |
| dates. This test should fail. | |
| """ | |
| self.check_climatology_dates(2000, 2009) | |
| def test_clim_time_slices(self): | |
| """ | |
| Create a monthly mean climatology for both odd and even number of years. | |
| Then test to see that time slices for the same month are equal, and unequal | |
| for different months. | |
| """ | |
| odd_cube = self.create_climatology_cube(2000, 2010) | |
| even_cube = self.create_climatology_cube(2000, 2009) | |
| # Check that time slices for the same month are equal | |
| self.assertTrue(np.array_equal(odd_cube.data[0,...], even_cube.data[0,...])) | |
| self.assertTrue(np.array_equal(odd_cube.data[-1,...], even_cube.data[-1,...])) | |
| # Check that time slices for different months are unequal | |
| self.assertFalse(np.array_equal(odd_cube.data[0,...], even_cube.data[1,...])) | |
| self.assertFalse(np.array_equal(odd_cube.data[-1,...], even_cube.data[-2,...])) | |
| # All code from this point on consists of support methods and functions. | |
| def check_climatology_dates(self, start_year, end_year): | |
| """Create a climatology cube and compare the dates on the time coordinate""" | |
| result = self.create_climatology_cube(start_year, end_year) | |
| iris_dates = result.coord('time').points | |
| cf_dates = make_cf_clim_dates(start_year, end_year, | |
| self.time_origin, self.calendar, self.mid_month) | |
| # Convert opaque time coords to human-readable date strings. | |
| iris_date_strings = get_date_strings(iris_dates, self.time_origin, self.calendar) | |
| cf_date_strings = get_date_strings(cf_dates, self.time_origin, self.calendar) | |
| # Test for equivalence between Iris dates and CF-style dates. | |
| msg = "\n\nGot Iris dates: {0:s}\n\nExpected CF dates: {1:s}".format( | |
| ', '.join(iris_date_strings), | |
| ', '.join(cf_date_strings)) | |
| # Either of these assertion statements could be used. | |
| self.assertListEqual(iris_date_strings, cf_date_strings, msg) | |
| #npt.assert_array_equal(iris_dates, cf_dates, err_msg=msg) | |
| def create_climatology_cube(self, start_year, end_year): | |
| """ | |
| Use the cube.aggregated_by method to create a monthly mean climatology | |
| spanning the period start_year to end_year. | |
| """ | |
| cube = self.make_tzy_cube(start_year, end_year) | |
| # Create an auxiliary coordinate based on month number. | |
| iris.coord_categorisation.add_month_number(cube, 'time') | |
| month_crd = cube.coord('month_number') | |
| # Use this new coordinate to compute the monthly mean climatology. | |
| result = cube.aggregated_by(['month_number'], iris.analysis.MEAN) | |
| return result | |
| def make_tzy_cube(self, start_year, end_year): | |
| """Make a T-Z-Y cube containing data""" | |
| time_coord = make_time_coord(start_year, end_year, | |
| self.time_origin, self.calendar, self.mid_month) | |
| ht_coord = make_height_coord() | |
| lat_coord = make_lat_coord() | |
| data = make_monthly_data(time_coord, ht_coord, lat_coord) | |
| cube = iris.cube.Cube(data, standard_name='air_temperature', units='degc') | |
| cube.add_dim_coord(time_coord, 0) | |
| cube.add_dim_coord(ht_coord, 1) | |
| cube.add_dim_coord(lat_coord, 2) | |
| return cube | |
| def make_monthly_data(time_coord, ht_coord, lat_coord): | |
| """Make cube data in which hyperslabs for the same month contain the same value""" | |
| nt = len(time_coord.points) | |
| nz = len(ht_coord.points) | |
| ny = len(lat_coord.points) | |
| shape = (nt, nz, ny) | |
| data = np.zeros(shape, dtype=np.float32) | |
| # there may be an efficient numpy routine to achieve the following | |
| for m in range(12) : data[m::12,:,:] = m+1 | |
| return data | |
| def make_random_data(time_coord, ht_coord, lat_coord): | |
| """Make some random cube data""" | |
| nt = len(time_coord.points) | |
| nz = len(ht_coord.points) | |
| ny = len(lat_coord.points) | |
| return np.random.uniform(size=(nt, nz, ny)) | |
| def make_time_coord(syear, eyear, time_origin, calendar, mid_month): | |
| """Make a time coord object spanning syear to eyear in monthly intervals""" | |
| tunit = iris.unit.Unit(time_origin, calendar) | |
| times = [] | |
| for y in range(syear, eyear+1) : | |
| for m in range(1, 13) : | |
| dt = datetime(y, m, mid_month) | |
| times.append(tunit.date2num(dt)) | |
| time_coord = iris.coords.DimCoord(times, standard_name='time', units=tunit) | |
| return time_coord | |
| def make_height_coord(nheights=10): | |
| """Make a simple monotonic height coordinate object""" | |
| points = np.arange(nheights, dtype=np.float32) | |
| ht_coord = iris.coords.DimCoord(points, standard_name='height', units='m') | |
| ht_coord.guess_bounds() | |
| return ht_coord | |
| def make_lat_coord(nlats=19): | |
| """Make a simple latitude coordinate object""" | |
| points = np.linspace(-90., 90., nlats) | |
| lat_coord = iris.coords.DimCoord(points, standard_name='latitude', | |
| units='degrees_north') | |
| lat_coord.guess_bounds() | |
| return lat_coord | |
| def make_cf_clim_dates(syear, eyear, time_origin, calendar, mid_month): | |
| """Make an array of CF-style dates for a multi-year climatology""" | |
| tunit = iris.unit.Unit(time_origin, calendar) | |
| midyear = (syear+eyear)/2 | |
| dates = [tunit.date2num(datetime(midyear, month, mid_month)) for | |
| month in range(1,13)] | |
| return np.array(dates, dtype=np.float32) | |
| def get_date_strings(dates, time_origin, calendar, format='%d-%b-%Y'): | |
| """Convert numeric dates to date strings using the specified format""" | |
| tunit = iris.unit.Unit(time_origin, calendar) | |
| date_strings = [] | |
| for date in dates : | |
| dt = tunit.num2date(date) | |
| date_strings.append(datetime(dt.year, dt.month, dt.day).strftime(format)) | |
| return date_strings | |
| if __name__ == '__main__' : | |
| unittest.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment