Пример #1
0
    def partition(self,
                  wsp_darr,
                  wdir_darr,
                  dep_darr,
                  agefac=1.7,
                  wscut=0.3333,
                  hs_min=0.001,
                  nearest=False,
                  max_swells=5):
        """Partition wave spectra using WW3 watershed algorithm.

        Args:
            - wsp_darr (DataArray): wind speed (m/s).
            - wdir_darr (DataArray): Wind direction (degree).
            - dep_darr (DataArray): Water depth (m).
            - agefac (float): Age factor.
            - wscut (float): Wind speed cutoff.
            - hs_min (float): minimum Hs for assigning swell partition.
            - nearest (bool): if True, wsp, wdir and dep are allowed to be taken from the.
              nearest point if not matching positions in SpecArray (slower).
            - max_swells: maximum number of swells to extract

        Returns:
            - part_spec (SpecArray): partitioned spectra with one extra dimension
              representig partition number.

        Note:
            - All input DataArray objects must have same non-spectral
              dimensions as SpecArray.
        References:
            - Hanson, Jeffrey L., et al. "Pacific hindcast performance of three numerical 
              wave models." Journal of Atmospheric and Oceanic Technology 26.8 (2009): 1614-1633.
        
        TODO:
            - We currently loop through each spectrum to calculate the partitions which
              is slow. Ideally we should handle the problem in a multi-dimensional way.

        """
        # Assert spectral dims are present in spectra and non-spectral dims are present in winds and depths
        assert attrs.FREQNAME in self._obj.dims and attrs.DIRNAME in self._obj.dims, (
            'partition requires E(freq,dir) but freq|dir '
            'dimensions not in SpecArray dimensions (%s)' % (self._obj.dims))
        for darr in (wsp_darr, wdir_darr, dep_darr):
            # Conditional below aims at allowing wsp, wdir, dep to be DataArrays within the SpecArray. not working yet
            if isinstance(darr, str):
                darr = getattr(self, darr)
            assert set(darr.dims) == self._non_spec_dims, (
                '%s dimensions (%s) need matching non-spectral dimensions '
                'in SpecArray (%s) for consistent slicing' %
                (darr.name, set(darr.dims), self._non_spec_dims))

        from wavespectra.specpart import specpart

        # Initialise output - one SpecArray for each partition
        all_parts = [0 * self._obj]

        # Predefine these for speed
        dirs = self.dir.values
        freqs = self.freq.values
        ndir = len(dirs)
        nfreq = len(freqs)

        # Slice each possible 2D, freq-dir array out of the full data array
        slice_ids = {
            dim: range(self._obj[dim].size)
            for dim in self._non_spec_dims
        }
        for slice_dict in self._product(slice_ids):
            specarr = self._obj[slice_dict]
            if nearest:
                slice_dict_nearest = {
                    key: self._obj[key][val].values
                    for key, val in slice_dict.items()
                }
                wsp = float(
                    wsp_darr.sel(method='nearest', **slice_dict_nearest))
                wdir = float(
                    wdir_darr.sel(method='nearest', **slice_dict_nearest))
                dep = float(
                    dep_darr.sel(method='nearest', **slice_dict_nearest))
            else:
                wsp = float(wsp_darr[slice_dict])
                wdir = float(wdir_darr[slice_dict])
                dep = float(dep_darr[slice_dict])

            Up = agefac * wsp * np.cos(D2R * (dirs - wdir))
            windbool = np.tile(Up, (nfreq, 1)) > np.tile(
                self.celerity(dep, freqs)[:, _], (1, ndir))

            spectrum = specarr.values
            part_array = specpart.partition(spectrum)
            part_array_max = part_array.max()

            #TODO: join the two loops in a while loop
            # Assign new partition if multiple valleys and satisfying some conditions
            for part in range(1, part_array_max + 1):
                part_spec = np.where(part_array == part, spectrum,
                                     0.)  # Current partition

                imax, imin = self._inflection(part_spec, dfres=0.01, fmin=0.05)
                if len(imin) > 0:
                    part_spec[imin[0].squeeze():, :] = 0
                    newpart = part_spec > 0
                    if newpart.sum() > 20:
                        part_array_max += 1
                        part_array[newpart] = part_array_max

            # Group sea partitions and sort swells by hs
            swell_hs_parts = np.zeros(part_array_max + 1)  # +1 because of sea
            for part in range(1, part_array_max + 1):
                part_spec = np.where(part_array == part, spectrum,
                                     0.)  # Current partition
                W = part_spec[windbool].sum() / part_spec.sum()
                if W > wscut:
                    part_array[part_array == part] = 0
                    swell_hs_parts[part] = 0  # not really needed
                else:
                    swell_hs_parts[part] = hs(part_spec, freqs, dirs)
            sortedparts = np.flipud(swell_hs_parts[1:].argsort() + 1)
            num_swells = min(max_swells, sum(swell_hs_parts[1:] > hs_min))
            parts = np.concatenate(([0], sortedparts[:num_swells]))
            # Extend partitions list if any extra one has been detected
            for dummy in range(1 + num_swells - len(all_parts)):
                all_parts.append(0 * self._obj)

            for ind, part in enumerate(parts):
                all_parts[ind][slice_dict] = np.where(part_array == part,
                                                      spectrum, 0.)

        # Concatenate partitions along new axis
        part_coord = xr.DataArray(data=range(len(all_parts)),
                                  coords={'part': range(len(all_parts))},
                                  dims=('part', ),
                                  name='part',
                                  attrs=OrderedDict(
                                      (('standard_name',
                                        'spectral_partition_number'), ('units',
                                                                       ''))))
        return xr.concat(all_parts, dim=part_coord)
Пример #2
0
def nppart(spectrum,
           freq,
           dir,
           wspd,
           wdir,
           dpt,
           swells=3,
           agefac=1.7,
           wscut=0.3333):
    """Watershed partition on a numpy array.

    Args:
        - spectrum (2darray): Wave spectrum array with shape (nf, nd).
        - freq (1darray): Wave frequency array with shape (nf).
        - dir (1darray): Wave direction array with shape (nd).
        - wspd (float): Wind speed.
        - wdir (float): Wind direction.
        - dpt (float): Water depth.
        - swells (int): Number of swell partitions to compute.
        - agefac (float): Age factor.
        - wscut (float): Wind speed cutoff.

    Returns:
        - specpart (3darray): Wave spectrum partitions with shape (np, nf, nd).

    """
    part_array = specpart.partition(spectrum)

    Up = agefac * wspd * np.cos(D2R * (dir - wdir))
    windbool = np.tile(Up, (freq.size, 1)) > np.tile(
        celerity(freq, dpt)[:, np.newaxis], (1, dir.size))

    ipeak = 1  # values from specpart.partition start at 1
    part_array_max = part_array.max()
    # partitions_hs_swell = np.zeros(part_array_max + 1)  # zero is used for sea
    partitions_hs_swell = np.zeros(part_array_max + 1)  # zero is used for sea
    while ipeak <= part_array_max:
        part_spec = np.where(part_array == ipeak, spectrum, 0.0)

        # Assign new partition if multiple valleys and satisfying conditions
        __, imin = inflection(part_spec, freq, dfres=0.01, fmin=0.05)
        if len(imin) > 0:
            part_spec_new = part_spec.copy()
            part_spec_new[imin[0].squeeze():, :] = 0
            newpart = part_spec_new > 0
            if newpart.sum() > 20:
                part_spec[newpart] = 0
                part_array_max += 1
                part_array[newpart] = part_array_max
                partitions_hs_swell = np.append(partitions_hs_swell, 0)

        # Assign sea partition
        W = part_spec[windbool].sum() / part_spec.sum()
        if W > wscut:
            part_array[part_array == ipeak] = 0
        else:
            partitions_hs_swell[ipeak] = hs(part_spec, freq, dir)

        ipeak += 1

    sorted_swells = np.flipud(partitions_hs_swell[1:].argsort() + 1)
    parts = np.concatenate(([0], sorted_swells[:swells]))
    all_parts = []
    for part in parts:
        all_parts.append(np.where(part_array == part, spectrum, 0.0))

    # Extend partitions list if it is less than swells
    if len(all_parts) < swells + 1:
        nullspec = 0 * spectrum
        nmiss = (swells + 1) - len(all_parts)
        for i in range(nmiss):
            all_parts.append(nullspec)

    return np.array(all_parts)
Пример #3
0
    def partition(
        self,
        wsp_darr=None,
        wdir_darr=None,
        dep_darr=None,
        agefac=1.7,
        wscut=0.3333,
        hs_min=0.001,
        nearest=False,
        max_swells=5,
        fortran_code=True,
    ):
        """Partition wave spectra using WW3 watershed algorithm.

        Args:
            - wsp_darr (DataArray): wind speed (m/s).
            - wdir_darr (DataArray): Wind direction (degree).
            - dep_darr (DataArray): Water depth (m).
            - agefac (float): Age factor.
            - wscut (float): Wind speed cutoff.
            - hs_min (float): minimum Hs for assigning swell partition.
            - nearest (bool): if True, wsp, wdir and dep are allowed to be taken from the.
              nearest point if not matching positions in SpecArray (slower).
            - max_swells (int): maximum number of swells to extract
            - fortran_code (bool): use fortran partition code or (~30x slower) pure python

        Returns:
            - part_spec (SpecArray): partitioned spectra with one extra dimension
              representig partition number.

        Note:
            - All input DataArray objects must have same non-spectral
              dimensions as SpecArray.
        References:
            - Hanson, Jeffrey L., et al. "Pacific hindcast performance of three numerical 
              wave models." Journal of Atmospheric and Oceanic Technology 26.8 (2009): 1614-1633.

        TODO:
            - We currently loop through each spectrum to calculate the partitions which
              is slow. Ideally we should handle the problem in a multi-dimensional way.

        """
        # Assert spectral dims are present in spectra and non-spectral dims are present in winds and depths
        assert attrs.FREQNAME in self._obj.dims and attrs.DIRNAME in self._obj.dims, (
            "partition requires E(freq,dir) but freq|dir "
            "dimensions not in SpecArray dimensions (%s)" % (self._obj.dims))
        for darr in (wsp_darr, wdir_darr, dep_darr):
            # Conditional below aims at allowing wsp, wdir, dep to be DataArrays within the SpecArray. not working yet
            if isinstance(darr, str):
                darr = getattr(self, darr)
            assert set(darr.dims) == self._non_spec_dims, (
                "%s dimensions (%s) need matching non-spectral dimensions "
                "in SpecArray (%s) for consistent slicing" %
                (darr.name, set(darr.dims), self._non_spec_dims))

        if fortran_code:
            from wavespectra.specpart import specpart
        else:
            from wavespectra.core import specpartpy as specpart

        # Initialise output - one SpecArray for each partition
        all_parts = [0 * self._obj.load() for i in range(1 + max_swells)]

        # Predefine these for speed
        dirs = self.dir.values
        freqs = self.freq.values
        dfarr = self.dfarr.values
        ndir = len(dirs)
        nfreq = len(freqs)
        wsp_darr.load()
        wdir_darr.load()
        dep_darr.load()

        # Slice each possible 2D, freq-dir array out of the full data array
        slice_ids = {
            dim: range(self._obj[dim].size)
            for dim in self._non_spec_dims
        }
        for slice_dict in self._product(slice_ids):
            spectrum = self._obj[slice_dict].values
            if nearest:
                slice_dict_nearest = {
                    key: self._obj[key][val].values
                    for key, val in slice_dict.items()
                }
                wsp = float(
                    wsp_darr.sel(method="nearest", **slice_dict_nearest))
                wdir = float(
                    wdir_darr.sel(method="nearest", **slice_dict_nearest))
                dep = float(
                    dep_darr.sel(method="nearest", **slice_dict_nearest))
            else:
                wsp = float(wsp_darr[slice_dict])
                wdir = float(wdir_darr[slice_dict])
                dep = float(dep_darr[slice_dict])

            Up = agefac * wsp * np.cos(D2R * (dirs - wdir))
            windbool = np.tile(Up, (nfreq, 1)) > np.tile(
                self.celerity(dep, freqs)[:, _], (1, ndir))

            part_array = specpart.partition(spectrum)

            ipeak = 1  # values from specpart.partition start at 1
            part_array_max = part_array.max()
            partitions_hs_swell = np.zeros(
                part_array_max +
                1)  # +1 because zeroth index is used for windseas
            while ipeak <= part_array_max:
                part_spec = np.where(part_array == ipeak, spectrum,
                                     0.0)  # Current partition

                # Assign new partition if multiple valleys and satisfying some conditions
                imax, imin = self._inflection(part_spec, dfres=0.01, fmin=0.05)
                if len(imin) > 0:
                    # TODO: swap part_spec and part_spec_new and deal with more than one imin value ?
                    part_spec_new = part_spec.copy()
                    part_spec_new[imin[0].squeeze():, :] = 0
                    newpart = part_spec_new > 0
                    if newpart.sum() > 20:
                        part_spec[newpart] = 0
                        part_array_max += 1
                        part_array[newpart] = part_array_max
                        partitions_hs_swell = np.append(partitions_hs_swell, 0)

                # Group sea partitions and sort swells by hs
                W = np.dot((part_spec * windbool).sum(1), dfarr) / np.dot(
                    part_spec.sum(1), dfarr)
                if W > wscut:
                    part_array[part_array == ipeak] = 0  # mark as windsea
                else:
                    partitions_hs_swell[ipeak] = hs(part_spec, freqs, dirs)

                ipeak += 1

            num_swells = min(max_swells, sum(partitions_hs_swell > hs_min))

            sorted_swells = np.flipud(partitions_hs_swell[1:].argsort() + 1)
            parts = np.concatenate(([0], sorted_swells[:num_swells]))
            for ind, part in enumerate(parts):
                all_parts[ind][slice_dict] = np.where(part_array == part,
                                                      spectrum, 0.0)

        # Concatenate partitions along new axis
        part_coord = xr.DataArray(
            data=range(len(all_parts)),
            coords={"part": range(len(all_parts))},
            dims=("part", ),
            name="part",
            attrs=OrderedDict((("standard_name", "spectral_partition_number"),
                               ("units", ""))),
        )
        return xr.concat(all_parts, dim=part_coord)
Пример #4
0
    def partition(self,
                  wsp_darr,
                  wdir_darr,
                  dep_darr,
                  agefac=1.7,
                  wscut=0.3333,
                  hs_min=0.001,
                  nearest=False):
        """Partition wave spectra using WW3 watershed algorithm.

        Args:
            - wsp_darr (DataArray): wind speed (m/s).
            - wdir_darr (DataArray): Wind direction (degree).
            - dep_darr (DataArray): Water depth (m).
            - agefac (float): Age factor.
            - wscut (float): Wind speed cutoff.
            - hs_min (float): minimum Hs for assigning swell partition.
            - nearest (bool): if True, wsp, wdir and dep are allowed to be taken from the.
              nearest point if not matching positions in SpecArray (slower).

        Returns:
            - part_spec (SpecArray): partitioned spectra with one extra dimension
              representig partition number.

        Note:
            - All input DataArray objects must have same non-spectral
              dimensions as SpecArray.

        TODO:
            - We currently loop through each spectrum to calculate the partitions which
              is slow. Ideally we should handle the problem in a multi-dimensional way.

        """
        # Assert spectral dims are present in spectra and non-spectral dims are present in winds and depths
        assert attrs.FREQNAME in self._obj.dims and attrs.DIRNAME in self._obj.dims, (
            'partition requires E(freq,dir) but freq|dir '
            'dimensions not in SpecArray dimensions (%s)' % (self._obj.dims))
        for darr in (wsp_darr, wdir_darr, dep_darr):
            # Conditional below aims at allowing wsp, wdir, dep to be DataArrays within the SpecArray. not working yet
            if isinstance(darr, str):
                darr = getattr(self, darr)
            assert set(darr.dims) == self._non_spec_dims, (
                '%s dimensions (%s) need matching non-spectral dimensions '
                'in SpecArray (%s) for consistent slicing' %
                (darr.name, set(darr.dims), self._non_spec_dims))

        from wavespectra.specpart import specpart

        # Initialise output - one SpecArray for each partition
        all_parts = [0 * self._obj]

        # Predefine these for speed
        dirs = self.dir.values
        freqs = self.freq.values
        ndir = len(dirs)
        nfreq = len(freqs)

        # Slice each possible 2D, freq-dir array out of the full data array
        slice_ids = {
            dim: range(self._obj[dim].size)
            for dim in self._non_spec_dims
        }
        for slice_dict in self._product(slice_ids):
            specarr = self._obj[slice_dict]
            if nearest:
                slice_dict_nearest = {
                    key: self._obj[key][val].values
                    for key, val in slice_dict.items()
                }
                wsp = float(
                    wsp_darr.sel(method='nearest', **slice_dict_nearest))
                wdir = float(
                    wdir_darr.sel(method='nearest', **slice_dict_nearest))
                dep = float(
                    dep_darr.sel(method='nearest', **slice_dict_nearest))
            else:
                wsp = float(wsp_darr[slice_dict])
                wdir = float(wdir_darr[slice_dict])
                dep = float(dep_darr[slice_dict])

            spectrum = specarr.values
            part_array = specpart.partition(spectrum)
            nparts = part_array.max()

            # Assign new partition if multiple valleys and satisfying some conditions
            for part in range(1, nparts + 1):
                part_spec = np.where(part_array == part, spectrum,
                                     0.)  # Current partition only
                imax, imin = self._inflection(part_spec, dfres=0.01, fmin=0.05)
                if len(imin) > 0:
                    part_spec[imin[0].squeeze():, :] = 0
                    newpart = part_spec > 0
                    if newpart.sum() > 20:
                        nparts += 1
                        part_array[newpart] = nparts

            # Extend partitions list if any extra one has been detected (+1 because of sea)
            if len(all_parts) < nparts + 1:
                for new_part_number in set(range(nparts + 1)).difference(
                        range(len(all_parts))):
                    all_parts.append(0 * self._obj)

            # Assign sea and swells partitions based on wind and wave properties
            sea = 0 * spectrum
            swells = list()
            hs_swell = list()
            Up = agefac * wsp * np.cos(D2R * (dirs - wdir))
            for part in range(1, nparts + 1):
                part_spec = np.where(part_array == part, spectrum,
                                     0.)  # Current partition only
                W = part_spec[np.tile(Up, (nfreq, 1)) > \
                    np.tile(self.celerity(dep, freqs)[:,_], (1, ndir))].sum() / part_spec.sum()
                if W > wscut:
                    sea += part_spec
                else:
                    _hs = hs(part_spec, freqs, dirs)
                    if _hs > hs_min:
                        swells.append(part_spec)
                        hs_swell.append(_hs)
                        hs_swell = [
                            h + np.random.rand() * 1e-10 for h in hs_swell
                        ]  # If two or more partitions with same Hs
            if len(swells) > 1:
                swells = [
                    x for (y, x) in sorted(zip(hs_swell, swells), reverse=True)
                ]

            # Updating partition SpecArrays for current slice
            all_parts[0][slice_dict] = sea
            for ind, swell in enumerate(swells):
                all_parts[ind + 1][slice_dict] = swell

        # Concatenate partitions along new axis
        part_coord = xr.DataArray(data=range(len(all_parts)),
                                  coords={'part': range(len(all_parts))},
                                  dims=('part', ),
                                  name='part',
                                  attrs=OrderedDict(
                                      (('standard_name',
                                        'spectral_partition_number'), ('units',
                                                                       ''))))
        return xr.concat(all_parts, dim=part_coord)