Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
duncanwp committed May 3, 2018
2 parents 8851a65 + b15f4ed commit 00072f0
Show file tree
Hide file tree
Showing 26 changed files with 449 additions and 2,072 deletions.
7 changes: 4 additions & 3 deletions docs/iris/src/sphinxext/gen_gallery.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import warnings
import matplotlib.image as image

from sphinx.util import status_iterator

template = '''\
{{% extends "layout.html" %}}
Expand Down Expand Up @@ -193,9 +194,9 @@ def gen_gallery(app, doctree):
with open(gallery_path, 'w') as fh:
fh.write(content)

for key in app.builder.status_iterator(thumbnails,
'generating thumbnails... ',
length=len(thumbnails)):
for key in status_iterator(thumbnails,
'generating thumbnails... ',
length=len(thumbnails)):
image.thumbnail(key, thumbnails[key], 0.3)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* All var names being written to NetCDF are now CF compliant. Non alpha-numeric characters are replaced with '_', and must always have a leading letter.
Ref: https://github.com/SciTools/iris/pull/2930
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Removed :mod:`iris.experimental.um`. Please use `mule <https://github.com/SciTools/mule>` instead.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
* The methods :meth:`iris.cube.Cube.convert_units` and
:meth:`iris.coords.Coord.convert_units` no longer forcibly realise the cube
data or coordinate points/bounds : The converted values are now lazy arrays
if the originals were.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Added :meth:`iris.analysis.trajectory.interpolate` that allows you interpolate to find values along a trajectory.
33 changes: 33 additions & 0 deletions lib/iris/_lazy_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,36 @@ def co_realise_cubes(*cubes):
results = _co_realise_lazy_arrays([cube.core_data() for cube in cubes])
for cube, result in zip(cubes, results):
cube.data = result


def lazy_elementwise(lazy_array, elementwise_op):
"""
Apply a (numpy-style) elementwise array operation to a lazy array.
Elementwise means that it performs a independent calculation at each point
of the input, producing a result array of the same shape.
Args:
* lazy_array:
The lazy array object to operate on.
* elementwise_op:
The elementwise operation, a function operating on numpy arrays.
.. note:
A single-point "dummy" call is made to the operation function, to
determine dtype of the result.
This return dtype must be stable in actual operation (!)
"""
# This is just a wrapper to provide an Iris-specific abstraction for a
# lazy operation in Dask (map_blocks).

# Explicitly determine the return type with a dummy call.
# This makes good practical sense for unit conversions, as a Unit.convert
# call may cast to float, or not, depending on unit equality : Thus, it's
# much safer to get udunits to decide that for us.
dtype = elementwise_op(np.zeros(1, lazy_array.dtype)).dtype

return da.map_blocks(elementwise_op, lazy_array, dtype=dtype)
69 changes: 68 additions & 1 deletion lib/iris/analysis/trajectory.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# (C) British Crown Copyright 2010 - 2017, Met Office
# (C) British Crown Copyright 2010 - 2018, Met Office
#
# This file is part of Iris.
#
Expand Down Expand Up @@ -132,6 +132,73 @@ def __repr__(self):
return 'Trajectory(%s, sample_count=%s)' % (self.waypoints,
self.sample_count)

def _get_interp_points(self):
"""
Translate `self.sampled_points` to the format expected by the
interpolator.
Returns:
`self.sampled points` in the format required by
`:func:`~iris.analysis.trajectory.interpolate`.
"""
points = {k: [point_dict[k] for point_dict in self.sampled_points]
for k in self.sampled_points[0].keys()}
return [(k, v) for k, v in points.items()]

def _src_cube_anon_dims(self, cube):
"""
A helper method to locate the index of anonymous dimensions on the
interpolation target, ``cube``.
Returns:
The index of any anonymous dimensions in ``cube``.
"""
named_dims = [cube.coord_dims(c)[0] for c in cube.dim_coords]
return list(set(range(cube.ndim)) - set(named_dims))

def interpolate(self, cube, method=None):
"""
Calls :func:`~iris.analysis.trajectory.interpolate` to interpolate
``cube`` on the defined trajectory.
Assumes that the coordinate names supplied in the waypoints
dictionaries match to coordinate names in `cube`, and that points are
supplied in the same coord_system as in `cube`, where appropriate (i.e.
for horizontal coordinate points).
Args:
* cube
The source Cube to interpolate.
Kwargs:
* method:
The interpolation method to use; "linear" (default) or "nearest".
Only nearest is available when specifying multi-dimensional
coordinates.
"""
sample_points = self._get_interp_points()
interpolated_cube = interpolate(cube, sample_points, method=method)
# Add an "index" coord to name the anonymous dimension produced by
# the interpolation, if present.
if len(interpolated_cube.dim_coords) < interpolated_cube.ndim:
# Add a new coord `index` to describe the new dimension created by
# interpolating.
index_coord = iris.coords.DimCoord(range(self.sample_count),
long_name='index')
# Make sure anonymous dims in `cube` do not mistakenly get labelled
# as the new `index` dimension created by interpolating.
src_anon_dims = self._src_cube_anon_dims(cube)
interp_anon_dims = self._src_cube_anon_dims(interpolated_cube)
anon_dim_index, = list(set(interp_anon_dims) - set(src_anon_dims))
# Add the new coord to the interpolated cube.
interpolated_cube.add_dim_coord(index_coord, anon_dim_index)
return interpolated_cube


def interpolate(cube, sample_points, method=None):
"""
Expand Down
34 changes: 30 additions & 4 deletions lib/iris/coords.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@

from iris._data_manager import DataManager
from iris._deprecation import warn_deprecated
from iris._lazy_data import as_concrete_data, is_lazy_data, multidim_lazy_stack
from iris._lazy_data import (as_concrete_data, is_lazy_data,
multidim_lazy_stack, lazy_elementwise)
import iris.aux_factory
import iris.exceptions
import iris.time
Expand Down Expand Up @@ -733,7 +734,13 @@ def __str__(self):
fmt = '{cls}({points}{bounds}' \
', standard_name={self.standard_name!r}' \
', calendar={self.units.calendar!r}{other_metadata})'
points = self._str_dates(self.points)
if self.units.is_long_time_interval():
# A time unit with a long time interval ("months" or "years")
# cannot be converted to a date using `num2date` so gracefully
# fall back to printing points as numbers, not datetimes.
points = self.points
else:
points = self._str_dates(self.points)
bounds = ''
if self.has_bounds():
bounds = ', bounds=' + self._str_dates(self.bounds)
Expand Down Expand Up @@ -909,9 +916,28 @@ def convert_units(self, unit):
raise iris.exceptions.UnitConversionError(
'Cannot convert from unknown units. '
'The "coord.units" attribute may be set directly.')
self.points = self.units.convert(self.points, unit)
if self.has_lazy_points() or self.has_lazy_bounds():
# Make fixed copies of old + new units for a delayed conversion.
old_unit = self.units
new_unit = unit

# Define a delayed conversion operation (i.e. a callback).
def pointwise_convert(values):
return old_unit.convert(values, new_unit)

if self.has_lazy_points():
new_points = lazy_elementwise(self.lazy_points(),
pointwise_convert)
else:
new_points = self.units.convert(self.points, unit)
self.points = new_points
if self.has_bounds():
self.bounds = self.units.convert(self.bounds, unit)
if self.has_lazy_bounds():
new_bounds = lazy_elementwise(self.lazy_bounds(),
pointwise_convert)
else:
new_bounds = self.units.convert(self.bounds, unit)
self.bounds = new_bounds
self.units = unit

def cells(self):
Expand Down
15 changes: 14 additions & 1 deletion lib/iris/cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import iris._concatenate
import iris._constraints
from iris._data_manager import DataManager
from iris._lazy_data import lazy_elementwise

import iris._merge
import iris.analysis
Expand Down Expand Up @@ -873,7 +874,19 @@ def convert_units(self, unit):
raise iris.exceptions.UnitConversionError(
'Cannot convert from unknown units. '
'The "cube.units" attribute may be set directly.')
self.data = self.units.convert(self.data, unit)
if self.has_lazy_data():
# Make fixed copies of old + new units for a delayed conversion.
old_unit = self.units
new_unit = unit

# Define a delayed conversion operation (i.e. a callback).
def pointwise_convert(values):
return old_unit.convert(values, new_unit)

new_data = lazy_elementwise(self.lazy_data(), pointwise_convert)
else:
new_data = self.units.convert(self.data, unit)
self.data = new_data
self.units = unit

def add_cell_method(self, cell_method):
Expand Down
Loading

0 comments on commit 00072f0

Please sign in to comment.