A question about what Iris does when it tries to save a PP file with a length one coordinate cropped up at https://groups.google.com/forum/#!topic/scitools-iris/CVhGzusweeg.
I reproduced the problem, first by setting up a dummy cube:
import iris
import numpy as np
shape = (4, 1)
cube = iris.cube.Cube(np.arange(np.prod(shape)).reshape(shape).astype(np.float32),
standard_name='x_wind', units='m s-1')
cube.add_dim_coord(iris.coords.DimCoord(standard_name='latitude', points=range(shape[0])), 0)
cube.add_dim_coord(iris.coords.DimCoord(standard_name='longitude', points=range(shape[1])), 1)
cube
X Wind (m s-1) | latitude | longitude |
---|---|---|
Shape | 4 | 1 |
Dimension coordinates | ||
latitude | x | - |
longitude | - | x |
Followed by saving then subsequently loading the cube to PP:
iris.save(cube, 'test.pp')
iris.load_cube('test.pp')
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) ~/dev/scitools/iris/lib/iris/fileformats/pp.py in __getattr__(self, key) 890 try: --> 891 loc = self.HEADER_DICT[key] 892 except KeyError: KeyError: 'x' During handling of the above exception, another exception occurred: AttributeError Traceback (most recent call last) <ipython-input-2-c938d8c3ca6c> in <module>() 1 iris.save(cube, 'test.pp') ----> 2 iris.load_cube('test.pp') ~/dev/scitools/iris/lib/iris/__init__.py in load_cube(uris, constraint, callback) 371 raise ValueError('only a single constraint is allowed') 372 --> 373 cubes = _load_collection(uris, constraints, callback) 374 cubes = cubes.merged().cubes() 375 ~/dev/scitools/iris/lib/iris/__init__.py in _load_collection(uris, constraints, callback) 311 try: 312 cubes = _generate_cubes(uris, callback, constraints) --> 313 result = iris.cube._CubeFilterCollection.from_cubes(cubes, constraints) 314 except EOFError as e: 315 raise iris.exceptions.TranslationError( ~/dev/scitools/iris/lib/iris/cube.py in from_cubes(cubes, constraints) 143 pairs = [_CubeFilter(constraint) for constraint in constraints] 144 collection = _CubeFilterCollection(pairs) --> 145 for cube in cubes: 146 collection.add_cube(cube) 147 return collection ~/dev/scitools/iris/lib/iris/__init__.py in _generate_cubes(uris, callback, constraints) 298 if scheme == 'file': 299 part_names = [x[1] for x in groups] --> 300 for cube in iris.io.load_files(part_names, callback, constraints): 301 yield cube 302 elif scheme in ['http', 'https']: ~/dev/scitools/iris/lib/iris/io/__init__.py in load_files(filenames, callback, constraints) 205 if handling_format_spec.constraint_aware_handler: 206 for cube in handling_format_spec.handler(fnames, callback, --> 207 constraints): 208 yield cube 209 else: ~/dev/scitools/iris/lib/iris/fileformats/rules.py in load_cubes(filenames, user_callback, loader, filter_function) 427 all_fields_and_filenames, 428 converter=loader.converter, --> 429 user_callback_wrapper=loadcubes_user_callback_wrapper): 430 yield cube ~/dev/scitools/iris/lib/iris/fileformats/rules.py in _load_pairs_from_fields_and_filenames(fields_and_filenames, converter, user_callback_wrapper) 348 for field, filename in fields_and_filenames: 349 # Convert the field to a Cube, passing down the 'converter' function. --> 350 cube, factories, references = _make_cube(field, converter) 351 352 # Post modify the new cube with a user-callback. ~/dev/scitools/iris/lib/iris/fileformats/rules.py in _make_cube(field, converter) 291 def _make_cube(field, converter): 292 # Convert the field to a Cube. --> 293 metadata = converter(field) 294 295 cube_data = field.core_data() ~/dev/scitools/iris/lib/iris/fileformats/pp_load_rules.py in convert(f) 693 # All the other rules. 694 references, standard_name, long_name, units, attributes, cell_methods, \ --> 695 dim_coords_and_dims, other_aux_coords_and_dims = _all_other_rules(f) 696 aux_coords_and_dims.extend(other_aux_coords_and_dims) 697 ~/dev/scitools/iris/lib/iris/fileformats/pp_load_rules.py in _all_other_rules(f) 838 (len(f.lbcode) == 5 and f.lbcode.ix == 11))): 839 dim_coords_and_dims.append( --> 840 (DimCoord(f.x, standard_name=f._x_coord_name(), units='degrees', 841 bounds=f.x_bounds, circular=(f.lbhem in [0, 4]), 842 coord_system=f.coord_system()), ~/dev/scitools/iris/lib/iris/fileformats/pp.py in __getattr__(self, key) 897 cls = self.__class__.__name__ 898 msg = '{!r} object has no attribute {!r}'.format(cls, key) --> 899 raise AttributeError(msg) 900 901 if len(loc) == 1: AttributeError: 'PPField3' object has no attribute 'x'
This was raised as a bug in https://github.com/SciTools/iris/issues/3022.
The suggested workaround was to add another element to the longitude coordinate (and therefore double the amount of actual data the cube represents). This can be achieved in a number of ways, but the suggested route is to copy the cube, modify the copy's longitude points, then concatenate the original with the copy:
dummy_lon_cube = cube.copy()
dummy_lon_cube.coord('longitude').points = cube.coord('longitude').points + 1
dummy_cube = iris.cube.CubeList([cube, dummy_lon_cube]).concatenate_cube()
dummy_cube
X Wind (m s-1) | latitude | longitude |
---|---|---|
Shape | 4 | 2 |
Dimension coordinates | ||
latitude | x | - |
longitude | - | x |
This should now save & load without issue:
iris.save(dummy_cube, 'test.pp')
iris.load_cube('test.pp')
X Wind (m s-1) | latitude | longitude |
---|---|---|
Shape | 4 | 2 |
Dimension coordinates | ||
latitude | x | - |
longitude | - | x |