def peak_direction(self): '''Compute peak wave direction Returns ------- theta : xarray.DataArray Peak wave direction at each point in time and location in the dataset ''' if self.has_dimension('direction', raise_error=True): coords = OrderedDict(self.coords) k = self._key_lookup('_energy') E = self.variables[k] dims = list(E.dims) # determine peak directions k = self._key_lookup('_direction') theta = coords.pop(k).values ix = E.argmax(dim=k).values peaks = theta[ix.flatten()].reshape(ix.shape) dims.remove(k) # determine units units = self.variables[k].attrs['units'] units = simplify(units) return xr.DataArray(peaks, coords=coords, dims=dims, attrs=dict(units=units))
def as_omnidirectional(self): '''Convert directional spectrum to an omnidirectional spectrum Integrate spectral energy over the directions. Returns ------- OceanWaves New OceanWaves object ''' if not self.has_dimension('direction'): return self # expand energy matrix k1 = self._key_lookup('_energy') k2 = self._key_lookup('_direction') energy = np.abs(np.trapz(self.variables[k1].values, self.coords[k2].values, axis=-1)) # determine units units = '(%s)*(%s)' % (self.variables[k1].attrs['units'], self.variables[k2].attrs['units']) # reinitialize object with new dimensions return self.reinitialize( direction=self.peak_direction(), spreading=self.directional_spreading(), energy=energy, energy_units=simplify(units), directional=False )
def peak_period(self): '''Compute peak wave period Returns ------- T : xarray.DataArray Peak wave period at each point in time and location in the dataset ''' if self.has_dimension('frequency', raise_error=True): coords = OrderedDict(self.coords) k = self._key_lookup('_energy') E = self.variables[k] dims = list(E.dims) # determine peak frequencies k = self._key_lookup('_frequency') f = coords.pop(k).values ix = E.argmax(dim=k).values peaks = 1. / f[ix.flatten()].reshape(ix.shape) dims.remove(k) # determine units units = '1/(%s)' % self.variables[k].attrs['units'] units = simplify(units) return xr.DataArray(peaks, coords=coords, dims=dims, attrs=dict(units=units))
def Hm0(self, f_min=0, f_max=np.inf): '''Compute significant wave height based on zeroth order moment Parameters ---------- f_min : float Minimum frequency to include in moment f_max : float Maximum frequency to include in moment Returns ------- H : xarray.DataArray Significant wave height at each point in time and location in the dataset ''' # compute moments m0 = self.moment(0, f_min=f_min, f_max=f_max) # compute wave height H = 4. * np.sqrt(m0) # determine units units = '(%s)^0.5' % m0.attrs['units'] H.attrs['units'] = simplify(units) return H
def directional_spreading(self): '''Estimate directional spreading Estimate directional spreading by assuming a gaussian distribution and computing the variance of the directional spreading. The directional spreading is assumed to be ``3000/var``. Notes ----- This estimate is inaccurate and should be improved based on the cosine model. ''' if self.has_dimension('direction', raise_error=True): coords = OrderedDict(self.coords) k = self._key_lookup('_energy') E = self.variables[k] dims = list(E.dims) # determine peak directions k = self._key_lookup('_direction') theta = coords.pop(k).values ix_direction = dims.index(k) dims.remove(k) # determine directional spreading E /= expand_and_repeat( np.trapz(E, theta, axis=ix_direction), repeat=len(theta), expand_dims=ix_direction ) m1 = np.trapz(theta * E, theta) m2 = np.trapz(theta**2. * E, theta) var = m2 - m1**2. spreading = np.round(3000./var / 5.) * 5. # determine units units = self.variables[k].attrs['units'] units = simplify(units) return xr.DataArray(spreading, coords=coords, dims=dims, attrs=dict(units=units))
def Tm02(self): '''Compute wave period based on second order moment Returns ------- T : xarray.DataArray Spectral wave period at each point in time and location in the dataset ''' # compute moments m0 = self.moment(0) m2 = self.moment(2) # compute wave period T = np.sqrt(m0/m2) # determine units units = '((%s)/(%s))^0.5' % (m0.attrs['units'], m2.attrs['units']) T.attrs['units'] = simplify(units) return T
def Tm01(self): '''Compute wave period based on first order moment Returns ------- T : xarray.DataArray Spectral wave period at each point in time and location in the dataset ''' # compute moments m0 = self.moment(0) m1 = self.moment(1) # compute wave period T = m0/m1 # determine units units = '(%s)/(%s)' % (m0.attrs['units'], m1.attrs['units']) T.attrs['units'] = simplify(units) return T
def as_directional(self, direction, direction_units='deg', theta_peak=None, s=None, normalize=True): '''Convert omnidirectional spectrum to a directional spectrum Spreads total wave energy over a given set of directions according to a spreading factor ``s``. See :func:`oceanwaves.spectral.directional_spreading` for options. Returns ------- OceanWaves New OceanWaves object ''' if self.has_dimension('direction'): return self direction = np.asarray(direction, dtype=np.float) # expand energy matrix k = self._key_lookup('_energy') energy = self.variables[k].values energy_units = self.variables[k].attrs['units'] ix_direction = energy.ndim # determine peak direction matrix if theta_peak is None: k = self._key_lookup('_direction') if k in self.variables.keys(): theta_peak = self.variables[k].values else: theta_peak = 0. if isinstance(theta_peak, (int, float)): theta_peak = theta_peak * np.ones(energy.shape) # determine spreading matrix if s is None: k = self._key_lookup('_spreading') if k in self.variables.keys(): s = self.variables[k].values else: s = 20. if isinstance(s, (int, float)): s = s * np.ones(energy.shape) # expand energy matrix energy = expand_and_repeat( energy, repeat=len(direction), expand_dims=ix_direction ) theta_mtx = expand_and_repeat( direction, shape=energy.shape, exist_dims=ix_direction ) thetap_mtx = expand_and_repeat( theta_peak, shape=energy.shape, expand_dims=ix_direction ) s_mtx = expand_and_repeat( s, shape=energy.shape, expand_dims=ix_direction ) # compute directional spreading spreading = directional_spreading( theta_mtx, units=direction_units, theta_peak=thetap_mtx, s=s_mtx, normalize=False ) # normalize shape if normalize: spreading /= trapz_and_repeat(spreading, theta_mtx, axis=ix_direction) # apply shape energy = np.multiply(energy, spreading) # determine units units = '(%s)/(%s)' % (energy_units, direction_units) # reinitialize object with new dimensions return self.reinitialize( direction=direction, direction_units=direction_units, energy=energy, energy_units=simplify(units) )
def as_spectral(self, frequency, frequency_units='Hz', Tp=None, gamma=3.3, sigma_low=.07, sigma_high=.09, shape='jonswap', method='yamaguchi', g=9.81, normalize=True): '''Convert wave energy to spectrum Spreads total wave energy over a given set of frequencies according to the JONSWAP spectrum shape. See :func:`oceanwaves.spectral.jonswap` for options. Returns ------- OceanWaves New OceanWaves object ''' if self.has_dimension('frequency'): return self frequency = np.asarray(frequency, dtype=np.float) frequency = frequency[frequency>0] k = self._key_lookup('_energy') energy = self.variables[k].values energy_units = self.variables[k].attrs['units'] ix_frequency = energy.ndim - int(self.has_dimension('direction')) # convert to energy if self.units == 'm': energy = energy**2. / 16. energy_units = '(%s)^2' % energy_units # determine peak period matrix if Tp is None: k = self._key_lookup('_frequency') if k in self.variables.keys(): Tp = 1./np.asarray(self.variables[k]) else: Tp = 4. if isinstance(Tp, (int, float)): Tp = Tp * np.ones(energy.shape) # expand energy matrices energy = expand_and_repeat( energy, repeat=len(frequency), expand_dims=ix_frequency ) f_mtx = expand_and_repeat( frequency, shape=energy.shape, exist_dims=ix_frequency ) Tp_mtx = expand_and_repeat( Tp, shape=energy.shape, expand_dims=ix_frequency ) # compute spectrum shape if shape.lower() == 'jonswap': spectrum = jonswap(f_mtx, Hm0=1., Tp=Tp_mtx, gamma=gamma, sigma_low=sigma_low, sigma_high=sigma_high, g=g, method=method, normalize=False) # normalize shape if normalize: spectrum /= trapz_and_repeat(spectrum, f_mtx, axis=ix_frequency) else: raise ValueError('Unknown spectrum shape: %s', shape) # apply shape energy = np.multiply(energy, spectrum) # determine units units = '(%s)/(%s)' % (energy_units, frequency_units) # reinitialize object with new dimensions return self.reinitialize( frequency=frequency, frequency_units=frequency_units, energy=energy, energy_units=simplify(units) )
def __init__(self, time=None, location=None, frequency=None, direction=None, energy=None, spreading=None, time_units='s', location_units='m', frequency_units='Hz', direction_units='deg', energy_units='m^2/Hz', spreading_units='deg', time_var='time', location_var='location', frequency_var='frequency', direction_var='direction', energy_var='energy', spreading_var='spreading', frequency_convention='absolute', direction_convention='nautical', spreading_convention='cosine', spectral=True, directional=True, attrs={}, crs=None, **kwargs): '''Initialize class Sets dimensions, converts coordinates and fills the dataset, if data is provided. Parameters ---------- time : iterable, optional Time coordinates, each item can be a datetime object or float location : iterable of 2-tuples, optional Location coordinates, each item is a 2-tuple with x- and y-coordinates frequency : iterable, optional Frequency cooridinates direction : iterable, optional Direction coordinates energy : matrix, optional Wave energy time_units : str, optional Units of time coordinates (default: s) location_units : str, optional Units of location coordinates (default: m) frequency_units : str, optional Units of frequency coordinates (default: Hz) direction_units : str, optional Units of direction coordinates (default: deg) energy_units : str, optional Units of wave energy (default: m^2/Hz) time_var : str, optional Name of time variable (default: time) location_var : str, optional Name of location variable (default: location) frequency_var : str, optional Name of frequency variable (default: frequency) direction_var : str, optional Name of direction variable (default: direction) energy_var : str, optional Name of wave energy variable (default: energy) frequency_convention : str, optional Convention of frequency definition (default: absolute) direction_convention : str, optional Convention of direction definition (default: nautical) attrs : dict-like, optional Global attributes crs : str, optional Proj4 specification of local coordinate reference system kwargs : dict, optional Additional options passed to the xarray.Dataset initialization method See Also -------- oceanwaves.OceanWaves.reinitialize ''' dims = [] coords = OrderedDict() data_vars = OrderedDict() # simplify dimensions time = np.asarray(time) location = np.asarray(location) frequency = np.asarray(frequency, dtype=np.float) direction = np.asarray(direction, dtype=np.float) spreading = np.asarray(spreading, dtype=np.float) energy = np.asarray(energy, dtype=np.float) # simplify units time_units = simplify(time_units) location_units = simplify(location_units) frequency_units = simplify(frequency_units) direction_units = simplify(direction_units) energy_units = simplify(energy_units) # determine object dimensions if self._isvalid(time): dims.append(time_var) coords[time_var] = xr.Variable( time_var, time, attrs=dict(units=time_units) ) if self._isvalid(location): dims.append(location_var) coords[location_var] = xr.Variable( location_var, np.arange(len(location)) ) x, y = list(zip(*location)) coords['%s_x' % location_var] = xr.Variable( location_var, np.asarray(x), attrs=dict(units=location_units) ) coords['%s_y' % location_var] = xr.Variable( location_var, np.asarray(y), attrs=dict(units=location_units) ) coords['%s_lat' % location_var] = xr.Variable( location_var, np.asarray(x) + np.nan, attrs=dict(units='degN') ) coords['%s_lon' % location_var] = xr.Variable( location_var, np.asarray(y) + np.nan, attrs=dict(units='degE') ) if self._isvalid(frequency, mask=frequency>0) and spectral: dims.append(frequency_var) coords[frequency_var] = xr.Variable( frequency_var, frequency[frequency>0], attrs=dict(units=frequency_units) ) if self._isvalid(direction) and directional: dims.append(direction_var) coords[direction_var] = xr.Variable( direction_var, direction, attrs=dict(units=direction_units) ) # determine object shape shp = tuple([len(c) for k, c in coords.items() if k in dims]) # initialize energy variable data_vars[energy_var] = xr.DataArray( np.nan + np.zeros(shp), dims=dims, coords=coords, attrs=dict(units=energy_units) ) # store parameterized frequencies if not spectral: if self._isvalid(frequency): data_vars[frequency_var] = xr.DataArray( frequency, dims=dims, coords=coords, attrs=dict(units=direction_units) ) # store parameterized directions if not directional: if self._isvalid(direction): data_vars[direction_var] = xr.DataArray( direction, dims=dims, coords=coords, attrs=dict(units=direction_units) ) if self._isvalid(spreading): data_vars[spreading_var] = xr.DataArray( spreading, dims=dims, coords=coords, attrs=dict(units=spreading_units) ) # collect global attributes attrs.update(dict( _init=kwargs.copy(), _crs=crs, _names=dict( time = time_var, location = location_var, frequency = frequency_var, direction = direction_var, spreading = spreading_var, energy = energy_var ), _units=dict( time = time_units, location = location_units, frequency = frequency_units, direction = direction_units, energy = energy_units ), _conventions=dict( frequency = frequency_convention, direction = direction_convention, spreading = spreading_convention ) )) # initialize empty object super(OceanWaves, self).__init__( data_vars=data_vars, coords=coords, attrs=attrs, **kwargs ) # set wave energy if self._isvalid(energy): self['_energy'] = dims, energy.squeeze() # convert coordinates self.convert_coordinates(crs)
def moment(self, n, f_min=0., f_max=np.inf): '''Compute nth order moment of wave spectrum Parameters ---------- n : int Order of moment f_min : float Minimum frequency to include in moment f_max : float Maximum frequency to include in moment Returns ------- m : xarray.DataArray nth order moment of the wave spectrum at each point in time and location in the dataset ''' if self.has_dimension('frequency', raise_error=True): coords = OrderedDict(self.coords) k = self._key_lookup('_energy') E = self.variables[k] dims = list(E.dims) # integrate frequencies k = self._key_lookup('_frequency') f = coords.pop(k).values ix_frequency = dims.index(k) f_mtx = expand_and_repeat( f, shape=E.values.shape, exist_dims=ix_frequency ) dims.remove(k) if f_min == 0. and f_max == np.inf: m = np.trapz(E.values * f_mtx**n, f_mtx, axis=ix_frequency) else: if n != 0: logger.warn('Computing %d-order moment using a frequency range; ' 'Are you sure what you are doing?', n) # integrate range of frequencies f_min = np.maximum(f_min, np.min(f)) f_max = np.minimum(f_max, np.max(f)) m = scipy.integrate.cumtrapz(E.values * f_mtx**n, f_mtx, axis=ix_frequency, initial=0) vals = [] if self.has_dimension('time'): k = self._key_lookup('_time') vals.append(coords[k].values.flatten().astype(np.float)) if self.has_dimension('location'): k = self._key_lookup('_location') vals.append(coords[k].values.flatten().astype(np.float)) vals.append(f.flatten()) if self.has_dimension('direction'): k = self._key_lookup('_direction') vals.append(coords[k].values.flatten().astype(np.float)) points = tuple(vals) points_min = vals.copy() points_min[ix_frequency] = [f_min] points_max = vals.copy() points_max[ix_frequency] = [f_max] xi_min = list(zip(*[x.flatten() for x in np.meshgrid(*points_min)])) xi_max = list(zip(*[x.flatten() for x in np.meshgrid(*points_max)])) m_min = scipy.interpolate.interpn(points, m, xi_min) m_max = scipy.interpolate.interpn(points, m, xi_max) m = (m_max - m_min).reshape([ len(x) for i, x in enumerate(vals) if i is not ix_freqency ]) # determine units k = self._key_lookup('_energy') E_units = self.variables[k].attrs['units'] k = self._key_lookup('_frequency') f_units = self.variables[k].attrs['units'] units = E_units + ('*((%s)^%d)' % (f_units, n+1)) units = simplify(units) return xr.DataArray(m, dims=dims, coords=coords, attrs=dict(units=units))