API#

Grid#

class xgcm.Grid(ds: xarray.core.dataset.Dataset, coords: Optional[Mapping[str, Mapping[str, str]]] = None, periodic: bool = True, fill_value: Optional[Union[float, Mapping[str, float]]] = None, default_shifts: Optional[Mapping[str, str]] = None, boundary: Optional[Union[str, Mapping[str, str]]] = None, face_connections=None, metrics: Optional[Mapping[Tuple[str], List[str]]] = None, autoparse_metadata: bool = True)[source]#

An object with multiple xgcm.Axis objects representing different independent axes.

__init__(ds: xarray.core.dataset.Dataset, coords: Optional[Mapping[str, Mapping[str, str]]] = None, periodic: bool = True, fill_value: Optional[Union[float, Mapping[str, float]]] = None, default_shifts: Optional[Mapping[str, str]] = None, boundary: Optional[Union[str, Mapping[str, str]]] = None, face_connections=None, metrics: Optional[Mapping[Tuple[str], List[str]]] = None, autoparse_metadata: bool = True)[source]#

Create a new Grid object from an input dataset.

Parameters
dsxarray.Dataset

Contains the relevant grid information. Coordinate attributes should conform to Comodo conventions [1].

coordsdict, optional

Specifies positions of dimension names along axes X, Y, Z, e.g {'X': {'center': 'XC', 'left: 'XG'}}. Each key should be an axis name (e.g., X, Y, or Z) and map to a dictionary which maps positions (center, left, right, outer, inner) to dimension names in the dataset (in the example above, XC is at the center position and XG at the left position along the X axis). If the values are not present in ds or are not dimensions, an error will be raised.

periodic{True, False, list}

Whether the grid is periodic (i.e. “wrap-around”). If a list is specified (e.g. ['X', 'Y']), the axis names in the list will be periodic and any other axes founds will be assumed non-periodic.

fill_value{float, dict}, optional

The value to use in boundary conditions with boundary=’fill’. Optionally a dict mapping axis name to seperate values for each axis can be passed.

default_shiftsdict

A dictionary of dictionaries specifying default grid position shifts (e.g. {'X': {'center': 'left', 'left': 'center'}})

boundary{None, ‘fill’, ‘extend’, ‘extrapolate’, dict}, optional

A flag indicating how to handle boundaries:

  • None: Do not apply any boundary conditions. Raise an error if boundary conditions are required for the operation.

  • ‘fill’: Set values outside the array boundary to fill_value (i.e. a Dirichlet boundary condition.)

  • ‘extend’: Set values outside the array to the nearest array value. (i.e. a limited form of Neumann boundary condition.)

  • ‘extrapolate’: Set values by extrapolating linearly from the two points nearest to the edge

Optionally a dict mapping axis name to seperate values for each axis can be passed.

face_connectionsdict

Grid topology

metricsdict, optional

Specification of grid metrics mapping axis names (X, Y, Z) to corresponding metric variable names in the dataset (e.g. {(‘X’,):[‘dx_t’], (‘X’, ‘Y’):[‘area_tracer’, ‘area_u’]} for the cell distance in the x-direction dx_t and the horizontal cell areas area_tracer and area_u, located at different grid positions).

References

1

Comodo Conventions https://web.archive.org/web/20160417032300/http://pycomodo.forge.imag.fr/norm.html

apply_as_grid_ufunc(func: Callable, *args: xarray.core.dataarray.DataArray, axis: Optional[Sequence[Sequence[str]]] = None, signature: Union[str, xgcm.grid_ufunc._GridUFuncSignature] = '', boundary_width: Optional[Mapping[str, Tuple[int, int]]] = None, boundary: Optional[Union[str, Mapping[str, str]]] = None, fill_value: Optional[Union[float, Mapping[str, float]]] = None, dask: Literal['forbidden', 'parallelized', 'allowed'] = 'forbidden', map_overlap: bool = False, **kwargs)[source]#

Apply a function to the given arguments in a grid-aware manner.

The relationship between xgcm axes on the input and output are specified by signature. Wraps xarray.apply_ufunc, but determines the core dimensions from the grid and signature passed.

Parameters
funccallable

Function to call like func(*args, **kwargs) on numpy-like unlabeled arrays (.data).

Passed directly on to xarray.apply_ufunc.

*argsxarray.DataArray

One or more xarray DataArray objects to apply the function to.

axisSequence[Sequence[str]], optional

Names of xgcm.Axes on which to act, for each array in args. Multiple axes can be passed as a sequence (e.g. ['X', 'Y']). Function will be executed over all Axes simultaneously, and each Axis must be present in the Grid.

signaturestring

Grid universal function signature. Specifies the xgcm.Axis names and positions for each input and output variable, e.g.,

"(X:center)->(X:left)" for diff_center_to_left(a).

boundary_widthDict[str: Tuple[int, int]

The widths of the boundaries at the edge of each array. Supplied in a mapping of the form {axis_name: (lower_width, upper_width)}.

boundary{None, ‘fill’, ‘extend’, ‘extrapolate’, dict}, optional

A flag indicating how to handle boundaries:

  • None: Do not apply any boundary conditions. Raise an error if boundary conditions are required for the operation.

  • ‘fill’: Set values outside the array boundary to fill_value (i.e. a Dirichlet boundary condition.)

  • ‘extend’: Set values outside the array to the nearest array value. (i.e. a limited form of Neumann boundary condition.)

  • ‘extrapolate’: Set values by extrapolating linearly from the two points nearest to the edge

Optionally a dict mapping axis name to separate values for each axis can be passed.

fill_value{float, dict}, optional

The value to use in boundary conditions with boundary=’fill’. Optionally a dict mapping axis name to separate values for each axis can be passed. Default is 0.

dask{“forbidden”, “allowed”, “parallelized”}, default: “forbidden”

How to handle applying to objects containing lazy data in the form of dask arrays. Passed directly on to xarray.apply_ufunc.

map_overlapbool, optional

Whether or not to automatically apply the function along chunked core dimensions using dask.array.map_overlap. Default is False. If True, will need to be accompanied by dask=’allowed’.

Returns
results

The result of the call to xarray.apply_ufunc, but including the coordinates given by the signature, which are read from the grid. Output is either a single object or a tuple of such objects.

average(da, axis, **kwargs)[source]#

Perform weighted mean reduction along specified axis or axes, accounting for grid metrics. (e.g. cell length, area, volume)

Parameters
axisstr, list of str

Name of the axis on which to act

**kwargs: dict

Additional arguments passed to xarray.DataArray.weighted.mean

Returns
da_ixarray.DataArray

The averaged data

cumint(da, axis, **kwargs)[source]#

Perform cumulative integral along specified axis or axes, accounting for grid metrics. (e.g. cell length, area, volume)

Parameters
axisstr or list or tuple

Name of the axis on which to act. Multiple axes can be passed as list or tuple (e.g. ['X', 'Y']). Functions will be executed over each axis in the given order.

tostr or dict, optional

The direction in which to shift the array (can be [‘center’,’left’,’right’,’inner’,’outer’]). If not specified, default will be used. Optionally a dict with separate values for each axis can be passed (see example)

boundaryNone or str or dict, optional

A flag indicating how to handle boundaries:

  • None: Do not apply any boundary conditions. Raise an error if boundary conditions are required for the operation.

  • ‘fill’: Set values outside the array boundary to fill_value (i.e. a Dirichlet boundary condition.)

  • ‘extend’: Set values outside the array to the nearest array value. (i.e. a limited form of Neumann boundary condition.)

Optionally a dict with separate values for each axis can be passed.

fill_value{float, dict}, optional

The value to use in the boundary condition with boundary=’fill’. Optionally a dict with separate values for each axis can be passed.

metric_weightedstr or tuple of str or dict, optional

Optionally use metrics to multiply/divide with appropriate metrics before/after the operation. E.g. if passing metric_weighted=[‘X’, ‘Y’], values will be weighted by horizontal area. If False (default), the points will be weighted equally. Optionally a dict with seperate values for each axis can be passed.

Returns
da_ixarray.DataArray

The cumulatively integrated data

cumsum(da: xarray.core.dataarray.DataArray, axis: Union[str, Iterable[str]], to=None, boundary=None, fill_value=None, metric_weighted=None, keep_coords: bool = False) xarray.core.dataarray.DataArray[source]#

Cumulatively sum a DataArray, transforming to the intermediate axis position.

Parameters
da: xarray.DataArray

Data to apply cumsum to.

axisstr or list or tuple

Name of the axis on which to act. Multiple axes can be passed as list or tuple (e.g. ['X', 'Y']). Functions will be executed over each axis in the given order.

tostr or dict, optional

The direction in which to shift the array (can be [‘center’,’left’,’right’,’inner’,’outer’]). If not specified, default will be used. Optionally a dict with seperate values for each axis can be passed (see example)

boundaryNone or str or dict, optional

A flag indicating how to handle boundaries:

  • None: Do not apply any boundary conditions. Raise an error if boundary conditions are required for the operation.

  • ‘fill’: Set values outside the array boundary to fill_value (i.e. a Dirichlet boundary condition.)

  • ‘extend’: Set values outside the array to the nearest array value. (i.e. a limited form of Neumann boundary condition.)

Optionally a dict with separate values for each axis can be passed (see example)

fill_value{float, dict}, optional

The value to use in the boundary condition with boundary=’fill’. Optionally a dict with seperate values for each axis can be passed (see example)

metric_weightedstr or tuple of str or dict, optional

Optionally use metrics to multiply/divide with appropriate metrics before/after the operation. E.g. if passing metric_weighted=[‘X’, ‘Y’], values will be weighted by horizontal area. If False (default), the points will be weighted equally. Optionally a dict with seperate values for each axis can be passed.

Returns
da_ixarray.DataArray

The cumsummed data

Examples

Each keyword argument can be provided as a per-axis dictionary. For instance, if we want to compute the cumulative sum of global 2D dataset in both X and Y axis, but the fill value at the boundary should be different for each axis, we can do this:

>>> grid.max(da, ["X", "Y"], fill_value={"X": 0, "Y": 100})
derivative(da, axis, **kwargs)[source]#

Take the centered-difference derivative along specified axis.

Parameters
axisstr or list or tuple

Name of the axis on which to act. Multiple axes can be passed as list or tuple (e.g. ['X', 'Y']). Functions will be executed over each axis in the given order.

tostr or dict, optional

The direction in which to shift the array (can be [‘center’,’left’,’right’,’inner’,’outer’]). If not specified, default will be used. Optionally a dict with seperate values for each axis can be passed (see example)

boundaryNone or str or dict, optional

A flag indicating how to handle boundaries:

  • None: Do not apply any boundary conditions. Raise an error if boundary conditions are required for the operation.

  • ‘fill’: Set values outside the array boundary to fill_value (i.e. a Dirichlet boundary condition.)

  • ‘extend’: Set values outside the array to the nearest array value. (i.e. a limited form of Neumann boundary condition.)

Optionally a dict with separate values for each axis can be passed (see example)

fill_value{float, dict}, optional

The value to use in the boundary condition with boundary=’fill’. Optionally a dict with seperate values for each axis can be passed (see example)

vector_partnerdict, optional

A single key (string), value (DataArray). Optionally a dict with seperate values for each axis can be passed (see example)

metric_weightedstr or tuple of str or dict, optional

Optionally use metrics to multiply/divide with appropriate metrics before/after the operation. E.g. if passing metric_weighted=[‘X’, ‘Y’], values will be weighted by horizontal area. If False (default), the points will be weighted equally. Optionally a dict with seperate values for each axis can be passed.

Returns
da_ixarray.DataArray

The differentiated data

diff(da, axis, **kwargs)[source]#

Difference neighboring points to the intermediate grid point.

Parameters
axisstr or list or tuple

Name of the axis on which to act. Multiple axes can be passed as list or tuple (e.g. ['X', 'Y']). Functions will be executed over each axis in the given order.

tostr or dict, optional

The direction in which to shift the array (can be [‘center’,’left’,’right’,’inner’,’outer’]). If not specified, default will be used. Optionally a dict with seperate values for each axis can be passed (see example)

boundaryNone or str or dict, optional

A flag indicating how to handle boundaries:

  • None: Do not apply any boundary conditions. Raise an error if boundary conditions are required for the operation.

  • ‘fill’: Set values outside the array boundary to fill_value (i.e. a Dirichlet boundary condition.)

  • ‘extend’: Set values outside the array to the nearest array value. (i.e. a limited form of Neumann boundary condition.)

Optionally a dict with separate values for each axis can be passed (see example)

fill_value{float, dict}, optional

The value to use in the boundary condition with boundary=’fill’. Optionally a dict with seperate values for each axis can be passed (see example)

vector_partnerdict, optional

A single key (string), value (DataArray). Optionally a dict with seperate values for each axis can be passed (see example)

metric_weightedstr or tuple of str or dict, optional

Optionally use metrics to multiply/divide with appropriate metrics before/after the operation. E.g. if passing metric_weighted=[‘X’, ‘Y’], values will be weighted by horizontal area. If False (default), the points will be weighted equally. Optionally a dict with seperate values for each axis can be passed.

Returns
da_ixarray.DataArray

The differenced data

Examples

Each keyword argument can be provided as a per-axis dictionary. For instance, if a global 2D dataset should be differenced on both X and Y axis, but the fill value at the boundary should be differenc for each axis, we can do this:

>>> grid.diff(da, ["X", "Y"], fill_value={"X": 0, "Y": 100})
diff_2d_vector(vector, **kwargs)[source]#

Difference a 2D vector to the intermediate grid point. This method is only necessary for complex grid topologies.

Parameters
vectordict

A dictionary with two entries. Keys are axis names, values are vector components along each axis.

%(neighbor_binary_func.parameters.no_f)s
Returns
vector_diffdict

A dictionary with two entries. Keys are axis names, values are differenced vector components along each axis

get_metric(array, axes)[source]#

Find the metric variable associated with a set of axes for a particular array.

Parameters
arrayxarray.DataArray

The array for which we are looking for a metric. Only its dimensions are considered.

axesiterable

A list of axes for which to find the metric.

Returns
metricxarray.DataArray

A metric which can broadcast against array

integrate(da, axis, **kwargs)[source]#

Perform finite volume integration along specified axis or axes, accounting for grid metrics. (e.g. cell length, area, volume)

Parameters
axisstr, list of str

Name of the axis on which to act

**kwargs: dict

Additional arguments passed to xarray.DataArray.sum

Returns
da_ixarray.DataArray

The integrated data

interp(da, axis, **kwargs)[source]#

Interpolate neighboring points to the intermediate grid point along this axis.

Parameters
axisstr or list or tuple

Name of the axis on which to act. Multiple axes can be passed as list or tuple (e.g. ['X', 'Y']). Functions will be executed over each axis in the given order.

tostr or dict, optional

The direction in which to shift the array (can be [‘center’,’left’,’right’,’inner’,’outer’]). If not specified, default will be used. Optionally a dict with seperate values for each axis can be passed (see example)

boundaryNone or str or dict, optional

A flag indicating how to handle boundaries:

  • None: Do not apply any boundary conditions. Raise an error if boundary conditions are required for the operation.

  • ‘fill’: Set values outside the array boundary to fill_value (i.e. a Dirichlet boundary condition.)

  • ‘extend’: Set values outside the array to the nearest array value. (i.e. a limited form of Neumann boundary condition.)

Optionally a dict with separate values for each axis can be passed (see example)

fill_value{float, dict}, optional

The value to use in the boundary condition with boundary=’fill’. Optionally a dict with seperate values for each axis can be passed (see example)

vector_partnerdict, optional

A single key (string), value (DataArray). Optionally a dict with seperate values for each axis can be passed (see example)

metric_weightedstr or tuple of str or dict, optional

Optionally use metrics to multiply/divide with appropriate metrics before/after the operation. E.g. if passing metric_weighted=[‘X’, ‘Y’], values will be weighted by horizontal area. If False (default), the points will be weighted equally. Optionally a dict with seperate values for each axis can be passed.

Returns
da_ixarray.DataArray

The interpolated data

Examples

Each keyword argument can be provided as a per-axis dictionary. For instance, if a global 2D dataset should be interpolated on both X and Y axis, but it is only periodic in the X axis, we can do this:

>>> grid.interp(da, ["X", "Y"], periodic={"X": True, "Y": False})
interp_2d_vector(vector, **kwargs)[source]#

Interpolate a 2D vector to the intermediate grid point. This method is only necessary for complex grid topologies.

Parameters
vectordict

A dictionary with two entries. Keys are axis names, values are vector components along each axis.

to{‘center’, ‘left’, ‘right’, ‘inner’, ‘outer’}

The direction in which to shift the array. If not specified, default will be used.

boundary{None, ‘fill’, ‘extend’}

A flag indicating how to handle boundaries:

  • None: Do not apply any boundary conditions. Raise an error if boundary conditions are required for the operation.

  • ‘fill’: Set values outside the array boundary to fill_value (i.e. a Dirichlet boundary condition.)

  • ‘extend’: Set values outside the array to the nearest array value. (i.e. a limited form of Neumann boundary condition.)

fill_valuefloat, optional

The value to use in the boundary condition with boundary=’fill’.

vector_partnerdict, optional

A single key (string), value (DataArray)

keep_coordsboolean, optional

Preserves compatible coordinates. False by default.

Returns
vector_interpdict

A dictionary with two entries. Keys are axis names, values are interpolated vector components along each axis

interp_like(array, like, boundary=None, fill_value=None)[source]#

Compares positions between two data arrays and interpolates array to the position of like if necessary

Parameters
arrayDataArray

DataArray to interpolate to the position of like

likeDataArray

DataArray with desired grid positions for source array

boundarystr or dict, optional,

boundary can either be one of {None, ‘fill’, ‘extend’, ‘extrapolate’}

  • None: Do not apply any boundary conditions. Raise an error if boundary conditions are required for the operation.

  • ‘fill’: Set values outside the array boundary to fill_value (i.e. a Dirichlet boundary condition.)

  • ‘extend’: Set values outside the array to the nearest array value. (i.e. a limited form of Neumann boundary condition where the difference at the boundary will be zero.)

  • ‘extrapolate’: Set values by extrapolating linearly from the two points nearest to the edge

This sets the default value. It can be overriden by specifying the boundary kwarg when calling specific methods.

fill_valuefloat, optional

The value to use in the boundary condition when boundary=’fill’.

Returns
arrayDataArray

Source data array with updated positions along axes matching with target array

max(da, axis, **kwargs)[source]#

Maximum of neighboring points on the intermediate grid point.

Parameters
axisstr or list or tuple

Name of the axis on which to act. Multiple axes can be passed as list or tuple (e.g. ['X', 'Y']). Functions will be executed over each axis in the given order.

tostr or dict, optional

The direction in which to shift the array (can be [‘center’,’left’,’right’,’inner’,’outer’]). If not specified, default will be used. Optionally a dict with seperate values for each axis can be passed (see example)

boundaryNone or str or dict, optional

A flag indicating how to handle boundaries:

  • None: Do not apply any boundary conditions. Raise an error if boundary conditions are required for the operation.

  • ‘fill’: Set values outside the array boundary to fill_value (i.e. a Dirichlet boundary condition.)

  • ‘extend’: Set values outside the array to the nearest array value. (i.e. a limited form of Neumann boundary condition.)

Optionally a dict with separate values for each axis can be passed (see example)

fill_value{float, dict}, optional

The value to use in the boundary condition with boundary=’fill’. Optionally a dict with seperate values for each axis can be passed (see example)

vector_partnerdict, optional

A single key (string), value (DataArray). Optionally a dict with seperate values for each axis can be passed (see example)

metric_weightedstr or tuple of str or dict, optional

Optionally use metrics to multiply/divide with appropriate metrics before/after the operation. E.g. if passing metric_weighted=[‘X’, ‘Y’], values will be weighted by horizontal area. If False (default), the points will be weighted equally. Optionally a dict with seperate values for each axis can be passed.

Returns
da_ixarray.DataArray

The maximum data

Examples

Each keyword argument can be provided as a per-axis dictionary. For instance, if we want to find the maximum of sourrounding grid cells for a global 2D dataset in both X and Y axis, but the fill value at the boundary should be different for each axis, we can do this:

>>> grid.max(da, ["X", "Y"], fill_value={"X": 0, "Y": 100})
min(da, axis, **kwargs)[source]#

Minimum of neighboring points on the intermediate grid point.

Parameters
axisstr or list or tuple

Name of the axis on which to act. Multiple axes can be passed as list or tuple (e.g. ['X', 'Y']). Functions will be executed over each axis in the given order.

tostr or dict, optional

The direction in which to shift the array (can be [‘center’,’left’,’right’,’inner’,’outer’]). If not specified, default will be used. Optionally a dict with seperate values for each axis can be passed (see example)

boundaryNone or str or dict, optional

A flag indicating how to handle boundaries:

  • None: Do not apply any boundary conditions. Raise an error if boundary conditions are required for the operation.

  • ‘fill’: Set values outside the array boundary to fill_value (i.e. a Dirichlet boundary condition.)

  • ‘extend’: Set values outside the array to the nearest array value. (i.e. a limited form of Neumann boundary condition.)

Optionally a dict with separate values for each axis can be passed (see example)

fill_value{float, dict}, optional

The value to use in the boundary condition with boundary=’fill’. Optionally a dict with seperate values for each axis can be passed (see example)

vector_partnerdict, optional

A single key (string), value (DataArray). Optionally a dict with seperate values for each axis can be passed (see example)

metric_weightedstr or tuple of str or dict, optional

Optionally use metrics to multiply/divide with appropriate metrics before/after the operation. E.g. if passing metric_weighted=[‘X’, ‘Y’], values will be weighted by horizontal area. If False (default), the points will be weighted equally. Optionally a dict with seperate values for each axis can be passed.

Returns
da_ixarray.DataArray

The mimimum data

Examples

Each keyword argument can be provided as a per-axis dictionary. For instance, if we want to find the minimum of sourrounding grid cells for a global 2D dataset in both X and Y axis, but the fill value at the boundary should be different for each axis, we can do this:

>>> grid.min(da, ["X", "Y"], fill_value={"X": 0, "Y": 100})
transform(da, axis, target, **kwargs)[source]#

Convert an array of data to new 1D-coordinates along axis. The method takes a multidimensional array of data da and transforms it onto another data_array target_data in the direction of the axis (for each 1-dimensional ‘column’).

target_data can be e.g. the existing coordinate along an axis, like depth. xgcm automatically detects the appropriate coordinate and then transforms the data from the input positions to the desired positions defined in target. This is the default behavior. The method can also be used for more complex cases like transforming a dataarray into new coordinates that are defined by e.g. a tracer field like temperature, density, etc.

Currently three methods are supported to carry out the transformation:

  • ‘linear’: Values are linear interpolated between 1D columns along axis of da and target_data. This method requires target_data to increase/decrease monotonically. target values are interpreted as new cell centers in this case. By default this method will return nan for values in target that are outside of the range of target_data, setting mask_edges=False results in the default np.interp behavior of repeated values.

  • ‘log’: Same as ‘linear’, but with values interpolated logarithmically between 1D columns. Operates by applying np.log to the target and target data values prior to linear interpolation.

  • ‘conservative’: Values are transformed while conserving the integral of da along each 1D column. This method can be used with non-monotonic values of target_data. Currently this will only work with extensive quantities (like heat, mass, transport) but not with intensive quantities (like temperature, density, velocity). N given target values are interpreted as cell-bounds and the returned array will have N-1 elements along the newly created coordinate, with coordinate values that are interpolated between target values.

Parameters
daxr.DataArray

Input data

axisstr

Name of the axis on which to act

target{np.array, xr.DataArray}

Target points for transformation. Depending on the method is interpreted as cell center (method=’linear’ and method=’log’) or cell bounds (method=’conservative). Values correspond to target_data or the existing coordinate along the axis (if target_data=None). The name of the resulting new coordinate is determined by the input type. When passed as numpy array the resulting dimension is named according to target_data, if provided as xr.Dataarray naming is inferred from the target input.

target_dataxr.DataArray, optional

Data to transform onto (e.g. a tracer like density or temperature). Defaults to None, which infers the appropriate coordinate along axis (e.g. the depth).

methodstr, optional

Method used to transform, by default “linear”

mask_edgesbool, optional

If activated, target values outside the range of target_data are masked with nan, by default True. Only applies to ‘linear’ and ‘log’ methods.

bypass_checksbool, optional

Only applies for method=’linear’ and method=’log’. Option to bypass logic to flip data if monotonically decreasing along the axis. This will improve performance if True, but the user needs to ensure that values are increasing along the axis.

suffixstr, optional

Customizable suffix to the name of the output array. This will be added to the original name of da. Defaults to _transformed.

Returns
xr.DataArray

The transformed data

Grid ufuncs#

xgcm.apply_as_grid_ufunc(func, *args[, ...])

Apply a function to the given arguments in a grid-aware manner.

xgcm.as_grid_ufunc([signature, boundary_width])

Decorator which turns a numpy ufunc into a "grid-aware ufunc".