-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
implement Gradient #2398
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
implement Gradient #2398
Changes from 9 commits
59ff688
0adfc68
d665b3c
e0fa5fd
218e62d
888b924
a0ab4c2
c581513
d6be041
a083460
267694d
bf2f35e
2a71b62
b504da8
fb356c5
1694d3c
7a0b57f
e93b926
4c656e0
74acc81
8817306
4112cd9
1c2c88c
cbfecb4
2e8db19
c31539e
528bcab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1137,3 +1137,109 @@ def where(cond, x, y): | |
join='exact', | ||
dataset_join='exact', | ||
dask='allowed') | ||
|
||
|
||
def _gradient_once(variable, coord, edge_order): | ||
""" Compute the gradient along 1 dimension for Variable. | ||
variable, coord: Variable | ||
""" | ||
from .variable import Variable | ||
|
||
result_array = duck_array_ops.gradient( | ||
variable.data, coord.data, edge_order=edge_order, | ||
axis=variable.get_axis_num(coord.dims[0])) | ||
return Variable(variable.dims, result_array) | ||
|
||
|
||
def gradient(dataarray, coords, edge_order=1): | ||
""" Compute the gradient of the dataarray with the second order accurate | ||
central differences. | ||
|
||
Parameters | ||
---------- | ||
dataarray: xr.DataArray | ||
Target array | ||
coords: str or sequence of strs | ||
The coordinate to be used to compute the gradient. | ||
edge_order: 1 or 2. Default 1 | ||
N-th order accurate differences at the boundaries. | ||
|
||
Returns | ||
------- | ||
gradient: DataArray or a sequence of DataArrays | ||
|
||
See also | ||
-------- | ||
numpy.gradient: corresponding numpy function | ||
|
||
Examples | ||
-------- | ||
|
||
>>> da = xr.DataArray(np.arange(12).reshape(4, 3), dims=['x', 'y'], | ||
... coords={'x': [0, 0.1, 1.1, 1.2]}) | ||
>>> da | ||
<xarray.DataArray (x: 4, y: 3)> | ||
array([[ 0, 1, 2], | ||
[ 3, 4, 5], | ||
[ 6, 7, 8], | ||
[ 9, 10, 11]]) | ||
Coordinates: | ||
* x (x) float64 0.0 0.1 1.1 1.2 | ||
Dimensions without coordinates: y | ||
>>> | ||
>>> xr.gradient(da, 'x') | ||
<xarray.DataArray (x: 4, y: 3)> | ||
array([[30. , 30. , 30. ], | ||
[27.545455, 27.545455, 27.545455], | ||
[27.545455, 27.545455, 27.545455], | ||
[30. , 30. , 30. ]]) | ||
Coordinates: | ||
* x (x) float64 0.0 0.1 1.1 1.2 | ||
Dimensions without coordinates: y | ||
>>> | ||
>>> xr.gradient(da, ('x', 'y')) | ||
(<xarray.DataArray (x: 4, y: 3)> | ||
array([[30. , 30. , 30. ], | ||
[27.545455, 27.545455, 27.545455], | ||
[27.545455, 27.545455, 27.545455], | ||
[30. , 30. , 30. ]]) | ||
Coordinates: | ||
* x (x) float64 0.0 0.1 1.1 1.2 | ||
Dimensions without coordinates: y, <xarray.DataArray (x: 4, y: 3)> | ||
array([[1., 1., 1.], | ||
[1., 1., 1.], | ||
[1., 1., 1.], | ||
[1., 1., 1.]]) | ||
Coordinates: | ||
* x (x) float64 0.0 0.1 1.1 1.2 | ||
Dimensions without coordinates: y) | ||
""" | ||
from .dataarray import DataArray | ||
|
||
if not isinstance(dataarray, DataArray): | ||
raise TypeError( | ||
'Only DataArray is supported. Given {}.'.format(type(dataarray))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little confused here. You wrote an implementation for Dataset.differentiate, too, and here is a duplicate version of DataArray.differentiate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. Maybe the top level function is not necessary here and DataArray.differentiate and Dataset.differentiate would be sufficient. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 I don't think we need the separate function. |
||
|
||
return_sequence = True | ||
if not isinstance(coords, (tuple, list)): | ||
coords = (coords, ) | ||
return_sequence = False | ||
|
||
result = [] | ||
for coord in coords: | ||
if coord not in dataarray.coords and coord not in dataarray.dims: | ||
raise ValueError('Coordiante {} does not exist.'.format(coord)) | ||
|
||
coord_var = dataarray[coord].variable | ||
if coord_var.ndim != 1: | ||
raise ValueError( | ||
'Only 1d-coordinate is supported. {} is {} ' | ||
'dimensional.'.format(coord, dataarray[coord].ndim)) | ||
|
||
result.append(DataArray( | ||
_gradient_once(dataarray.variable, coord_var, edge_order), | ||
coords=dataarray.coords)) | ||
|
||
if return_sequence: | ||
return tuple(result) | ||
return result[0] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -104,3 +104,38 @@ def func(x, window, axis=-1): | |
index = (slice(None),) * axis + (slice(drop_size, | ||
drop_size + orig_shape[axis]), ) | ||
return out[index] | ||
|
||
|
||
def gradient(a, coord, axis, edge_order): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this should be implemented in the upstream... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean like this? Requires Dask 0.17.3+ though. So IDK if that works for you or not. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks @jakirkahm. I think this can be implemented using overlap as I do here. Is it in the scope of the dask development? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah sorry. Should have read more of the code. Yeah I think that is in scope. Just not currently supported yet. Could you please open an issue or perhaps PR over on the Dask repo? |
||
""" Apply gradient along one dimension """ | ||
if axis < 0: | ||
axis = a.ndim + axis | ||
|
||
for c in a.chunks[axis]: | ||
if c < edge_order + 1: | ||
raise ValueError('Chunk size must be larger than edge_order + 1. ' | ||
'Given {}. Rechunk to proceed.'.format(c)) | ||
|
||
depth = {d: 1 if d == axis else 0 for d in range(a.ndim)} | ||
# temporary pad zero at the boundary | ||
boundary = "none" | ||
ag = overlap(a, depth=depth, boundary=boundary) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. F841 local variable 'ag' is assigned to but never used |
||
|
||
n_chunk = len(a.chunks[axis]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. F841 local variable 'n_chunk' is assigned to but never used |
||
# adjust the coordinate position with overlap | ||
array_loc_stop = np.cumsum(np.array(a.chunks[axis])) + 1 | ||
array_loc_start = array_loc_stop - np.array(a.chunks[axis]) - 2 | ||
array_loc_stop[-1] -= 1 | ||
array_loc_start[0] = 0 | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. W293 blank line contains whitespace |
||
def func(x, block_id): | ||
block_loc = block_id[axis] | ||
c = coord[array_loc_start[block_loc]: array_loc_stop[block_loc]] | ||
grad = np.gradient(x, c, axis=axis, edge_order=edge_order) | ||
return grad | ||
|
||
return a.map_overlap( | ||
func, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E126 continuation line over-indented for hanging indent |
||
dtype=a.dtype, | ||
depth={j: 1 if j == axis else 0 for j in range(a.ndim)}, | ||
boundary="none") |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2267,6 +2267,54 @@ def rank(self, dim, pct=False, keep_attrs=False): | |
ds = self._to_temp_dataset().rank(dim, pct=pct, keep_attrs=keep_attrs) | ||
return self._from_temp_dataset(ds) | ||
|
||
def gradient(self, coord, edge_order=1): | ||
""" Compute the gradient with the second order accurate central | ||
differences. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and here |
||
|
||
Parameters | ||
---------- | ||
coords: str | ||
The coordinate to be used to compute the gradient. | ||
edge_order: 1 or 2. Default 1 | ||
N-th order accurate differences at the boundaries. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. needs |
||
Returns | ||
------- | ||
gradient: DataArray | ||
|
||
See also | ||
-------- | ||
numpy.gradient: corresponding numpy function | ||
xr.gradient: more numpy-like function for xarray object. | ||
|
||
Examples | ||
-------- | ||
|
||
>>> da = xr.DataArray(np.arange(12).reshape(4, 3), dims=['x', 'y'], | ||
... coords={'x': [0, 0.1, 1.1, 1.2]}) | ||
>>> da | ||
<xarray.DataArray (x: 4, y: 3)> | ||
array([[ 0, 1, 2], | ||
[ 3, 4, 5], | ||
[ 6, 7, 8], | ||
[ 9, 10, 11]]) | ||
Coordinates: | ||
* x (x) float64 0.0 0.1 1.1 1.2 | ||
Dimensions without coordinates: y | ||
>>> | ||
>>> da.gradient('x') | ||
<xarray.DataArray (x: 4, y: 3)> | ||
array([[30. , 30. , 30. ], | ||
[27.545455, 27.545455, 27.545455], | ||
[27.545455, 27.545455, 27.545455], | ||
[30. , 30. , 30. ]]) | ||
Coordinates: | ||
* x (x) float64 0.0 0.1 1.1 1.2 | ||
Dimensions without coordinates: y | ||
""" | ||
ds = self._to_temp_dataset().gradient(coord, edge_order) | ||
return self._from_temp_dataset(ds) | ||
|
||
|
||
# priority most be higher than Variable to properly work with binary ufuncs | ||
ops.inject_all_ops_and_reduce_methods(DataArray, priority=60) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,8 +13,8 @@ | |
import xarray as xr | ||
|
||
from . import ( | ||
alignment, duck_array_ops, formatting, groupby, indexing, ops, resample, | ||
rolling, utils) | ||
alignment, computation, duck_array_ops, formatting, groupby, indexing, ops, | ||
resample, rolling, utils) | ||
from .. import conventions | ||
from .alignment import align | ||
from .common import ( | ||
|
@@ -3645,6 +3645,44 @@ def rank(self, dim, pct=False, keep_attrs=False): | |
attrs = self.attrs if keep_attrs else None | ||
return self._replace_vars_and_dims(variables, coord_names, attrs=attrs) | ||
|
||
def gradient(self, coord, edge_order=1): | ||
""" Compute the gradient with the second order accurate central | ||
differences. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and here |
||
|
||
Parameters | ||
---------- | ||
coords: str | ||
The coordinate to be used to compute the gradient. | ||
edge_order: 1 or 2. Default 1 | ||
N-th order accurate differences at the boundaries. | ||
|
||
Returns | ||
------- | ||
gradient: Dataset | ||
|
||
See also | ||
-------- | ||
numpy.gradient: corresponding numpy function | ||
xr.gradient: more numpy-like function for xarray object. | ||
""" | ||
if coord not in self.variables and coord not in self.dims: | ||
raise ValueError('Coordinate {} does not exist.'.format(coord)) | ||
|
||
coord_var = self[coord].variable | ||
if coord_var.ndim != 1: | ||
raise ValueError('Coordinate {} must be 1 dimensional but {} is {}' | ||
' dimensional'.format(coord, coord_var.ndims)) | ||
|
||
dim = coord_var.dims[0] | ||
variables = OrderedDict() | ||
for k, v in self.variables.items(): | ||
if k in self.data_vars and dim in v.dims: | ||
variables[k] = computation._gradient_once( | ||
v, coord_var, edge_order) | ||
else: | ||
variables[k] = v | ||
return self._replace_vars_and_dims(variables) | ||
|
||
@property | ||
def real(self): | ||
return self._unary_op(lambda x: x.real, keep_attrs=True)(self) | ||
|
@@ -3658,7 +3696,7 @@ def filter_by_attrs(self, **kwargs): | |
|
||
Can pass in ``key=value`` or ``key=callable``. A Dataset is returned | ||
containing only the variables for which all the filter tests pass. | ||
These tests are either ``key=value`` for which the attribute ``key`` | ||
These tests are either ``key=value`` for which the attribute ``key`` | ||
has the exact value ``value`` or the callable passed into | ||
``key=callable`` returns True. The callable will be passed a single | ||
value, either the value of the attribute ``key`` or ``None`` if the | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Altough this API is similar to numpy's counterpart, I'm wondering whether we really need to support this. The return value is a tuple of DataArrays, which I feel inconsistent to other xarray functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I wrote my own wrapper for gradient of a DataArray, I returned a dataset with each variable being a gradient along one dimension, but even that isn't very elegant ¯_(ツ)_/¯
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be another option, but in order to store them to a dataset, we will need to define their names very heuristically...
Maybe we can drop this api as this does not speed up the computation and it's easy to do the same thing manually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. I don't think it's particularly useful to compute to compute independent gradients with respect to multiple axes at the same time, e.g.,
du/dx
,du/dy
, etc. If anything, I would be more excited about computing higher order derivatives, e.g.,\frac{d^2 u}{dx dy}