Skip to content

Add a "depth_average" function #199

Description

@mx-moth

This can either be an example, or added to the depth operations, as appropriate.

There are uses for computing an average value of a variable across a depth variable, weighting the values with the size of each depth layer. This is useful for tracking a tracer variable dispersing across a 2D domain, but the variable is 3D or 4D.

import numpy
import xarray
from emsarray.types import DataArrayOrName
from emsarray.utils import name_to_data_array


def depth_average(
    dataset: xarray.Dataset,
    data_array: DataArrayOrName,
    *,
    layer_sizes: xarray.DataArray | None = None
) -> xarray.DataArray:
    data_array = name_to_data_array(dataset, data_array)
    depth_variable = dataset.ems.get_depth_coordinate_for_data_array(data_array)
    depth_dimension = depth_variable.dims[0]

    if layer_sizes is None:
        if 'bounds' in depth_variable.attrs:
            depth_bounds = dataset[depth_variable.attrs['bounds']]
            assert depth_bounds.shape == (depth_variable.shape[0], 2)
            layer_sizes = bounds_to_size(depth_bounds)
        else:
            raise ValueError(
                f"Depth variable {depth_variable.name!r} does not have a bounds attribute. "
                "The layer_sizes parameter is required if the depth variable has no bounds.")

    total_depth = (numpy.isfinite(data_array) * layer_sizes).sum(depth_dimension)

    weighted_sum = (data_array * layer_sizes).sum(depth_dimension)
    weighted_average = weighted_sum / total_depth

    return weighted_average


def bounds_to_size(bounds):
    return bounds[:, 0] - bounds[:, 1]

This is complicated by many depth variable lacking proper bounds attributes. Some examples of how to estimate the bounds for this should be included. One untested option:

def estimate_bounds_from_centres(
    centres,
    *,
    clamp_lower: float | None = None,
    clamp_upper: float | None = None,
):
    if clamp_lower is None:
        clamp_lower = centres[0] - (centres[1] - centres[0])
    if clamp_upper is None:
        clamp_upper = centres[-1] + (centres[-1] - centres[-2])
    inner_bounds = centres[1:] - centres[:-1]

    return numpy.c_([
        [clamp_lower] + inner_bounds,
        inner_bounds + [clamp_upper],
    ])

For COMPAS all files, the Mesh2_layerfaces variable hold some of the useful information:

# Compute the thickness of each layer.
layerfaces = ds['Mesh2_layerfaces'].values
layerfaces[-1] = 0  # This is usually very high in the air and spoils the weighting
layer_sizes = xarray.DataArray(layerfaces[1:] - layerfaces[:-1], dims=('Mesh2_layers',))

Example use:

import emsarray
import xarray
from depth_average import depth_average

ds = emsarray.open_dataset("/home/hea211/example-datasets/setas_compas_all_2018-05.nc")
temp = ds['temp']

# Compute the thickness of each layer.
layerfaces = ds['Mesh2_layerfaces'].values
layerfaces[-1] = 0  # This is usually very high in the air and spoils the weighting
layer_sizes = xarray.DataArray(layerfaces[1:] - layerfaces[:-1], dims=('Mesh2_layers',))

# Find the depth averaged temperature
avg_temp = depth_average(ds, temp, layer_sizes=layer_sizes)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions