Skip to content

Instantly share code, notes, and snippets.

@rockdoc
Created October 30, 2014 10:08
Show Gist options
  • Select an option

  • Save rockdoc/e3a55de71e94377c7f1e to your computer and use it in GitHub Desktop.

Select an option

Save rockdoc/e3a55de71e94377c7f1e to your computer and use it in GitHub Desktop.
Unit tests for climatology dates created by Iris time-based aggregation operations
"""
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