diff --git a/pywafo/.project b/pywafo/.project index c92b2cb..731ae23 100644 --- a/pywafo/.project +++ b/pywafo/.project @@ -1,6 +1,6 @@ - google_pywafo + pywafo @@ -10,6 +10,16 @@ + + org.eclipse.ui.externaltools.ExternalToolBuilder + auto,full,incremental, + + + LaunchConfigHandle + <project>/.externalToolBuilders/wafo_stats_tests.launch + + + org.python.pydev.pythonNature diff --git a/pywafo/.pydevproject b/pywafo/.pydevproject index 4884fb8..78946ac 100644 --- a/pywafo/.pydevproject +++ b/pywafo/.pydevproject @@ -3,8 +3,8 @@ -/google_pywafo/src +/pywafo/src -python 2.6 +python 2.7 Default diff --git a/pywafo/src/Wafo.egg-info/PKG-INFO b/pywafo/src/Wafo.egg-info/PKG-INFO index 5ada5a7..4bf4485 100644 --- a/pywafo/src/Wafo.egg-info/PKG-INFO +++ b/pywafo/src/Wafo.egg-info/PKG-INFO @@ -1,4 +1,4 @@ -Metadata-Version: 1.0 +Metadata-Version: 1.1 Name: wafo Version: 0.1.2 Summary: Statistical analysis and simulation of random waves and random loads diff --git a/pywafo/src/Wafo.egg-info/SOURCES.txt b/pywafo/src/Wafo.egg-info/SOURCES.txt index 365778e..3b3c382 100644 --- a/pywafo/src/Wafo.egg-info/SOURCES.txt +++ b/pywafo/src/Wafo.egg-info/SOURCES.txt @@ -4,17 +4,19 @@ gendocwafo.py manifest setup.py setup_old.py -test_all.py src/epydoc_wafo.prj src/Wafo.egg-info/PKG-INFO src/Wafo.egg-info/SOURCES.txt src/Wafo.egg-info/dependency_links.txt src/Wafo.egg-info/top_level.txt +src/wafo/MSO.py +src/wafo/MSPPT.py src/wafo/SpecData1D.mm src/wafo/__init__.py src/wafo/bitwise.py src/wafo/c_library.pyd src/wafo/c_library.so +src/wafo/containers.py src/wafo/cov2mod.pyd src/wafo/dctpack.py src/wafo/definitions.py @@ -28,6 +30,7 @@ src/wafo/info.py src/wafo/integrate.py src/wafo/interpolate.py src/wafo/kdetools.py +src/wafo/magic.py src/wafo/meshgrid.py src/wafo/misc.py src/wafo/mvn.pyd @@ -39,20 +42,26 @@ src/wafo/objects.py src/wafo/plotbackend.py src/wafo/polynomial.py src/wafo/polynomial_old.py -src/wafo/pychip.py +src/wafo/powerpoint.py +src/wafo/resize_problem.py src/wafo/rindmod.pyd src/wafo/rindmod.so src/wafo/sg_filter.py src/wafo/version.py src/wafo/wafodata.py +src/wafo/wtraits.py +src/wafo/wtraits2.py +src/wafo/wtraits3.py src/wafo.egg-info/SOURCES.txt src/wafo/covariance/__init__.py src/wafo/covariance/core.py src/wafo/data/__init__.py +src/wafo/data/__init__.pyc src/wafo/data/atlantic.dat src/wafo/data/gfaks89.dat src/wafo/data/gfaksr89.dat src/wafo/data/info.py +src/wafo/data/info.pyc src/wafo/data/info.~py src/wafo/data/japansea.dat src/wafo/data/northsea.dat @@ -276,30 +285,47 @@ src/wafo/source/test_f90/types.f90 src/wafo/source/test_f90/types.mod src/wafo/spectrum/__init__.py src/wafo/spectrum/core.py -src/wafo/spectrum/dispersion_relation.py src/wafo/spectrum/models.py -src/wafo/spectrum/test/test_dispersion_relation.py src/wafo/spectrum/test/test_models.py +src/wafo/spectrum/test/test_models.pyc src/wafo/spectrum/test/test_specdata1d.py +src/wafo/spectrum/test/test_specdata1d.pyc src/wafo/stats/__init__.py src/wafo/stats/core.py src/wafo/stats/distributions.py +src/wafo/stats/distributions_juli2010.py src/wafo/stats/estimation.py +src/wafo/stats/kde_test.py src/wafo/stats/misc.py +src/wafo/stats/six.py +src/wafo/stats/sklearn_test.py +src/wafo/stats/twolumps.py src/wafo/stats/tests/test_distributions.py src/wafo/stats/tests/test_estimation.py src/wafo/test/__init__.py +src/wafo/test/__init__.pyc src/wafo/test/test_gaussian.py +src/wafo/test/test_gaussian.pyc src/wafo/test/test_kdetools.py +src/wafo/test/test_kdetools.pyc src/wafo/test/test_misc.py +src/wafo/test/test_misc.pyc src/wafo/test/test_objects.py +src/wafo/test/test_objects.pyc src/wafo/transform/__init__.py src/wafo/transform/core.py src/wafo/transform/models.py src/wafo/transform/models.~py src/wafo/transform/test/__init__.py +src/wafo/transform/test/__init__.pyc src/wafo/transform/test/test_models.py +src/wafo/transform/test/test_models.pyc src/wafo/transform/test/test_trdata.py +src/wafo/transform/test/test_trdata.pyc src/wafo/wave_theory/__init__.py src/wafo/wave_theory/core.py -src/wafo/wave_theory/dispersion_relation.py \ No newline at end of file +src/wafo/wave_theory/dispersion_relation.py +src/wafo/wave_theory/test/__init__.py +src/wafo/wave_theory/test/__init__.pyc +src/wafo/wave_theory/test/test_dispersion_relation.py +src/wafo/wave_theory/test/test_dispersion_relation.pyc \ No newline at end of file diff --git a/pywafo/src/wafo/__init__.py b/pywafo/src/wafo/__init__.py index 66bc46a..18cd6cd 100644 --- a/pywafo/src/wafo/__init__.py +++ b/pywafo/src/wafo/__init__.py @@ -1,26 +1,27 @@ +from __future__ import division, print_function, absolute_import -from info import __doc__ -import misc -import data -import demos -import kdetools -import objects -import spectrum -import transform -import definitions -import polynomial -import stats -import interpolate -import dctpack +from .info import __doc__ +from . import misc +from . import data +from . import demos +from . import kdetools +from . import objects +from . import spectrum +from . import transform +from . import definitions +from . import polynomial +from . import stats +from . import interpolate +from . import dctpack try: - import fig + from . import fig except ImportError: - print 'fig import only supported on Windows' + print('fig import only supported on Windows') try: from wafo.version import version as __version__ except ImportError: - __version__='nobuilt' - + __version__ = 'nobuilt' + from numpy.testing import Tester test = Tester().test \ No newline at end of file diff --git a/pywafo/src/wafo/bitwise.py b/pywafo/src/wafo/bitwise.py index fd53dd9..9a73d9f 100644 --- a/pywafo/src/wafo/bitwise.py +++ b/pywafo/src/wafo/bitwise.py @@ -1,98 +1,104 @@ -''' -Module extending the bitoperator capabilites of numpy -''' - -from numpy import (bitwise_and, bitwise_or, #@UnresolvedImport - bitwise_not, binary_repr, #@UnresolvedImport @UnusedImport - bitwise_xor, where, arange) #@UnresolvedImport @UnusedImport -#import numpy as np -__all__ = ['bitwise_and', 'bitwise_or', 'bitwise_not', 'binary_repr', - 'bitwise_xor', 'getbit', 'setbit', 'getbits', 'setbits'] - -def getbit(i, bit): - """ - Get bit at specified position - - Parameters - ---------- - i : array-like of uints, longs - value to - bit : array-like of ints or longs - bit position between 0 and the number of bits in the uint class. - - Examples - -------- - >>> import numpy as np - >>> binary_repr(13) - '1101' - >>> getbit(13,np.arange(3,-1,-1)) - array([1, 1, 0, 1]) - >>> getbit(5, np.r_[0:4]) - array([1, 0, 1, 0]) - """ - return bitwise_and(i, 1 << bit) >> bit - -def getbits(i, numbits=8): - """ - Returns bits of i in a list - """ - return getbit(i, arange(0, numbits)) - -def setbit(i, bit, value=1): - """ - Set bit at specified position - - Parameters - ---------- - i : array-like of uints, longs - value to - bit : array-like of ints or longs - bit position between 0 and the number of bits in the uint class. - value : array-like of 0 or 1 - value to set the bit to. - - Examples - -------- - Set bit fifth bit in the five bit binary binary representation of 9 (01001) - yields 25 (11001) - >>> setbit(9,4) - array(25) - """ - val1 = 1 << bit - val0 = bitwise_not(val1) - return where((value==0) & (i==i) & (bit==bit), bitwise_and(i, val0), - bitwise_or(i, val1)) - -def setbits(bitlist): - """ - Set bits of val to values in bitlist - - Example - ------- - >>> setbits([1,1]) - 3 - >>> setbits([1,0]) - 1 - """ -# return bitlist[7]<<7 | bitlist[6]<<6 | bitlist[5]<<5 | bitlist[4]<<4 | \ -# bitlist[3]<<3 | bitlist[2]<<2 | bitlist[1]<<1 | bitlist[0] - val = 0 - for i, j in enumerate(bitlist): - val |= j << i - return val - -def test_docstrings(): - import doctest - doctest.testmod() -if __name__ == '__main__': - test_docstrings() - -# t = set(np.arange(8),1,1) -# t=get(0x84,np.arange(0,8)) -# t=getbyte(0x84) -# t=get(0x84,[0, 1, 2, 3, 4, 5, 6, 7]) -# t=get(0x20, 6) -# bit = [0 for i in range(8)] -# bit[7]=1 -# t = setbits(bit) -# print(hex(t)) +''' +Module extending the bitoperator capabilites of numpy +''' + +from numpy import (bitwise_and, bitwise_or, + bitwise_not, binary_repr, # @UnusedImport + bitwise_xor, where, arange) # @UnusedImport +__all__ = ['bitwise_and', 'bitwise_or', 'bitwise_not', 'binary_repr', + 'bitwise_xor', 'getbit', 'setbit', 'getbits', 'setbits'] + + +def getbit(i, bit): + """ + Get bit at specified position + + Parameters + ---------- + i : array-like of uints, longs + value to + bit : array-like of ints or longs + bit position between 0 and the number of bits in the uint class. + + Examples + -------- + >>> import numpy as np + >>> binary_repr(13) + '1101' + >>> getbit(13,np.arange(3,-1,-1)) + array([1, 1, 0, 1]) + >>> getbit(5, np.r_[0:4]) + array([1, 0, 1, 0]) + """ + return bitwise_and(i, 1 << bit) >> bit + + +def getbits(i, numbits=8): + """ + Returns bits of i in a list + """ + return getbit(i, arange(0, numbits)) + + +def setbit(i, bit, value=1): + """ + Set bit at specified position + + Parameters + ---------- + i : array-like of uints, longs + value to + bit : array-like of ints or longs + bit position between 0 and the number of bits in the uint class. + value : array-like of 0 or 1 + value to set the bit to. + + Examples + -------- + Set bit fifth bit in the five bit binary binary representation of 9 (01001) + yields 25 (11001) + >>> setbit(9,4) + array(25) + """ + val1 = 1 << bit + val0 = bitwise_not(val1) + return where((value == 0) & (i == i) & (bit == bit), bitwise_and(i, val0), + bitwise_or(i, val1)) + + +def setbits(bitlist): + """ + Set bits of val to values in bitlist + + Example + ------- + >>> setbits([1,1]) + 3 + >>> setbits([1,0]) + 1 + """ +# return bitlist[7]<<7 | bitlist[6]<<6 | bitlist[5]<<5 | bitlist[4]<<4 | \ +# bitlist[3]<<3 | bitlist[2]<<2 | bitlist[1]<<1 | bitlist[0] + val = 0 + for i, j in enumerate(bitlist): + val |= j << i + return val + + +def test_docstrings(): + import doctest + print('Testing docstrings in %s' % __file__) + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) + +if __name__ == '__main__': + test_docstrings() + +# t = set(np.arange(8),1,1) +# t=get(0x84,np.arange(0,8)) +# t=getbyte(0x84) +# t=get(0x84,[0, 1, 2, 3, 4, 5, 6, 7]) +# t=get(0x20, 6) +# bit = [0 for i in range(8)] +# bit[7]=1 +# t = setbits(bit) +# print(hex(t)) diff --git a/pywafo/src/wafo/containers.py b/pywafo/src/wafo/containers.py index ab55dd4..28a4df9 100644 --- a/pywafo/src/wafo/containers.py +++ b/pywafo/src/wafo/containers.py @@ -1,34 +1,42 @@ import warnings -from graphutil import cltext +from graphutil import cltext # @UnresolvedImport + from plotbackend import plotbackend from time import gmtime, strftime import numpy as np -from scipy.integrate.quadrature import cumtrapz #@UnresolvedImport +from scipy.integrate.quadrature import cumtrapz # @UnresolvedImport from scipy import interpolate from scipy import integrate __all__ = ['PlotData', 'AxisLabels'] + def empty_copy(obj): class Empty(obj.__class__): + def __init__(self): pass newcopy = Empty() newcopy.__class__ = obj.__class__ return newcopy + def _set_seed(iseed): - if iseed != None: + if iseed is not None: try: np.random.set_state(iseed) except: np.random.seed(iseed) + + def now(): ''' Return current date and time as a string ''' return strftime("%a, %d %b %Y %H:%M:%S", gmtime()) + class PlotData(object): + ''' Container class for data with interpolation and plotting methods @@ -48,7 +56,7 @@ class PlotData(object): copy : return a copy of object eval_points : interpolate data at given points and return the result plot : plot data on given axis and the object handles - + Example ------- >>> import numpy as np @@ -66,6 +74,7 @@ class PlotData(object): >>> h = d3.plot() ''' + def __init__(self, data=None, args=None, *args2, **kwds): self.data = data self.args = args @@ -76,20 +85,20 @@ class PlotData(object): self.plot_kwds_children = kwds.pop('plot_kwds_children', {}) self.plot_args = kwds.pop('plot_args', []) self.plot_kwds = kwds.pop('plot_kwds', {}) - + self.labels = AxisLabels(**kwds) if not self.plotter: self.setplotter(kwds.get('plotmethod', None)) - + def copy(self): newcopy = empty_copy(self) newcopy.__dict__.update(self.__dict__) return newcopy - + def eval_points(self, *points, **kwds): ''' Interpolate data at points - + Parameters ---------- points : ndarray of float, shape (..., ndim) @@ -98,9 +107,9 @@ class PlotData(object): method : {'linear', 'nearest', 'cubic'} Method of interpolation. One of - ``nearest``: return the value at the data point closest to - the point of interpolation. + the point of interpolation. - ``linear``: tesselate the input point set to n-dimensional - simplices, and interpolate linearly on each simplex. + simplices, and interpolate linearly on each simplex. - ``cubic`` (1-D): return the value detemined from a cubic spline. - ``cubic`` (2-D): return the value determined from a @@ -111,28 +120,30 @@ class PlotData(object): convex hull of the input points. If not provided, then the default is ``nan``. This option has no effect for the 'nearest' method. - + Examples -------- >>> import numpy as np >>> x = np.arange(-2, 2, 0.4) >>> xi = np.arange(-2, 2, 0.1) - - >>> d = PlotData(np.sin(x), x, xlab='x', ylab='sin', title='sinus', plot_args=['r.']) + + >>> d = PlotData(np.sin(x), x, xlab='x', ylab='sin', title='sinus', + ... plot_args=['r.']) >>> di = PlotData(d.eval_points(xi), xi) >>> hi = di.plot() >>> h = d.plot() - + See also -------- scipy.interpolate.griddata ''' options = dict(method='linear') options.update(**kwds) - if isinstance(self.args, (list, tuple)): # Multidimensional data + if isinstance(self.args, (list, tuple)): # Multidimensional data ndim = len(self.args) if ndim < 2: - msg = '''Unable to determine plotter-type, because len(self.args)<2. + msg = ''' + Unable to determine plotter-type, because len(self.args)<2. If the data is 1D, then self.args should be a vector! If the data is 2D, then length(self.args) should be 2. If the data is 3D, then length(self.args) should be 3. @@ -140,10 +151,12 @@ class PlotData(object): warnings.warn(msg) else: xi = np.meshgrid(*self.args) - return interpolate.griddata(xi, self.data.ravel(), points, **options) - else: #One dimensional data - return interpolate.griddata(self.args, self.data, points, **options) - + return interpolate.griddata( + xi, self.data.ravel(), points, **options) + else: # One dimensional data + return interpolate.griddata( + self.args, self.data, points, **options) + def integrate(self, a, b, **kwds): ''' >>> x = np.linspace(0,5,60) @@ -151,50 +164,58 @@ class PlotData(object): >>> d.dataCI = np.vstack((d.data*.9,d.data*1.1)).T >>> d.integrate(0,np.pi/2, return_ci=True) array([ 0.99940055, 0.85543644, 1.04553343]) - + ''' - method = kwds.pop('method','trapz') + method = kwds.pop('method', 'trapz') fun = getattr(integrate, method) - if isinstance(self.args, (list, tuple)): # Multidimensional data + if isinstance(self.args, (list, tuple)): # Multidimensional data raise NotImplementedError('integration for ndim>1 not implemented') #ndim = len(self.args) - #if ndim < 2: -# msg = '''Unable to determine plotter-type, because len(self.args)<2. + # if ndim < 2: +# msg = '''Unable to determine plotter-type, because +# len(self.args)<2. # If the data is 1D, then self.args should be a vector! # If the data is 2D, then length(self.args) should be 2. # If the data is 3D, then length(self.args) should be 3. # Unless you fix this, the plot methods will not work!''' # warnings.warn(msg) # else: -# return interpolate.griddata(self.args, self.data.ravel(), **kwds) - else: #One dimensional data +# return interpolate.griddata(self.args, self.data.ravel(), **kwds) + else: # One dimensional data return_ci = kwds.pop('return_ci', False) - x = self.args - ix = np.flatnonzero((a2') - else: #One dimensional data + else: # One dimensional data self.plotter = Plotter_1d(plotmethod) - - def show(self): - self.plotter.show() - + + def show(self, *args, **kwds): + self.plotter.show(*args, **kwds) + __call__ = plot interpolate = eval_points - + + class AxisLabels: + def __init__(self, title='', xlab='', ylab='', zlab='', **kwds): self.title = title self.xlab = xlab self.ylab = ylab - self.zlab = zlab + self.zlab = zlab + def __repr__(self): return self.__str__() + def __str__(self): - return '%s\n%s\n%s\n%s\n' % (self.title, self.xlab, self.ylab, self.zlab) + return '%s\n%s\n%s\n%s\n' % ( + self.title, self.xlab, self.ylab, self.zlab) + def copy(self): newcopy = empty_copy(self) newcopy.__dict__.update(self.__dict__) return newcopy - + def labelfig(self, axis=None): if axis is None: axis = plotbackend.gca() try: h = [] - for fun, txt in zip(('set_title', 'set_xlabel','set_ylabel', 'set_ylabel'), - (self.title,self.xlab,self.ylab, self.zlab)): + for fun, txt in zip( + ('set_title', 'set_xlabel', 'set_ylabel', 'set_ylabel'), + (self.title, self.xlab, self.ylab, self.zlab)): if txt: if fun.startswith('set_title'): title0 = axis.get_title() - txt = title0 +'\n' + txt + if title0.lower().strip() != txt.lower().strip(): + txt = title0 + '\n' + txt h.append(getattr(axis, fun)(txt)) return h except: pass + class Plotter_1d(object): + """ Parameters @@ -280,6 +313,7 @@ class Plotter_1d(object): step : stair-step plot scatter : scatter plot """ + def __init__(self, plotmethod='plot'): self.plotfun = None if plotmethod is None: @@ -290,12 +324,12 @@ class Plotter_1d(object): # self.plotfun = getattr(plotbackend, plotmethod) # except: # pass - - def show(self): - plotbackend.show() + + def show(self, *args, **kwds): + plotbackend.show(*args, **kwds) def plot(self, wdata, *args, **kwds): - axis = kwds.pop('axis',None) + axis = kwds.pop('axis', None) if axis is None: axis = plotbackend.gca() plotflag = kwds.pop('plotflag', False) @@ -314,79 +348,95 @@ class Plotter_1d(object): h1 = plotfun(*args1, **kwds) h2 = wdata.labels.labelfig(axis) return h1, h2 - + def _plot(self, axis, plotflag, wdata, *args, **kwds): - x = wdata.args + x = wdata.args data = transformdata(x, wdata.data, plotflag) dataCI = getattr(wdata, 'dataCI', ()) h1 = plot1d(axis, x, data, dataCI, plotflag, *args, **kwds) return h1 __call__ = plot - + + def plot1d(axis, args, data, dataCI, plotflag, *varargin, **kwds): - + plottype = np.mod(plotflag, 10) - if plottype == 0: # % No plotting + if plottype == 0: # % No plotting return [] - elif plottype == 1: + elif plottype == 1: H = axis.plot(args, data, *varargin, **kwds) - elif plottype == 2: + elif plottype == 2: H = axis.step(args, data, *varargin, **kwds) - elif plottype == 3: + elif plottype == 3: H = axis.stem(args, data, *varargin, **kwds) - elif plottype == 4: - H = axis.errorbar(args, data, yerr=[dataCI[:,0] - data, dataCI[:,1] - data], *varargin, **kwds) - elif plottype == 5: + elif plottype == 4: + H = axis.errorbar( + args, + data, + yerr=[ + dataCI[ + :, + 0] - data, + dataCI[ + :, + 1] - data], + *varargin, + **kwds) + elif plottype == 5: H = axis.bar(args, data, *varargin, **kwds) - elif plottype == 6: + elif plottype == 6: level = 0 if np.isfinite(level): - H = axis.fill_between(args, data, level, *varargin, **kwds); + H = axis.fill_between(args, data, level, *varargin, **kwds) else: - H = axis.fill_between(args, data, *varargin, **kwds); - elif plottype==7: + H = axis.fill_between(args, data, *varargin, **kwds) + elif plottype == 7: H = axis.plot(args, data, *varargin, **kwds) - H = axis.fill_between(args, dataCI[:,0], dataCI[:,1], alpha=0.2, color='r'); - + H = axis.fill_between( + args, dataCI[ + :, 0], dataCI[ + :, 1], alpha=0.2, color='r') + scale = plotscale(plotflag) logXscale = 'x' in scale - logYscale = 'y' in scale - logZscale = 'z' in scale - - if logXscale: + logYscale = 'y' in scale + logZscale = 'z' in scale + + if logXscale: axis.set(xscale='log') if logYscale: - axis.set(yscale='log') - if logZscale: + axis.set(yscale='log') + if logZscale: axis.set(zscale='log') - + transFlag = np.mod(plotflag // 10, 10) logScale = logXscale or logYscale or logZscale - if logScale or (transFlag == 5 and not logScale): + if logScale or (transFlag == 5 and not logScale): ax = list(axis.axis()) fmax1 = data.max() if transFlag == 5 and not logScale: ax[3] = 11 * np.log10(fmax1) ax[2] = ax[3] - 40 else: - ax[3] = 1.15 * fmax1; - ax[2] = ax[3] * 1e-4; - + ax[3] = 1.15 * fmax1 + ax[2] = ax[3] * 1e-4 + axis.axis(ax) - + if np.any(dataCI) and plottype < 3: axis.hold(True) - plot1d(axis, args, dataCI, (), plotflag, 'r--'); + plot1d(axis, args, dataCI, (), plotflag, 'r--') return H + def plotscale(plotflag): ''' Return plotscale from plotflag - + CALL scale = plotscale(plotflag) - + plotflag = integer defining plotscale. - Let scaleId = floor(plotflag/100). + Let scaleId = floor(plotflag/100). If scaleId < 8 then: 0 'linear' : Linear scale on all axes. 1 'xlog' : Log scale on x-axis. @@ -400,16 +450,16 @@ def plotscale(plotflag): if (mod(scaleId,10)>0) : Log scale on x-axis. if (mod(floor(scaleId/10),10)>0) : Log scale on y-axis. if (mod(floor(scaleId/100),10)>0) : Log scale on z-axis. - + scale = string defining plotscale valid options are: 'linear', 'xlog', 'ylog', 'xylog', 'zlog', 'xzlog', - 'yzlog', 'xyzlog' - + 'yzlog', 'xyzlog' + Example plotscale(100) % xlog plotscale(200) % xlog plotscale(1000) % ylog - + See also plotscale ''' scaleId = plotflag // 100 @@ -418,13 +468,22 @@ def plotscale(plotflag): logYscaleId = (np.mod(scaleId // 10, 10) > 0) * 2 logZscaleId = (np.mod(scaleId // 100, 10) > 0) * 4 scaleId = logYscaleId + logXscaleId + logZscaleId - - scales = ['linear', 'xlog', 'ylog', 'xylog', 'zlog', 'xzlog', 'yzlog', 'xyzlog'] - + + scales = [ + 'linear', + 'xlog', + 'ylog', + 'xylog', + 'zlog', + 'xzlog', + 'yzlog', + 'xyzlog'] + return scales[scaleId] + def transformdata(x, f, plotflag): - transFlag = np.mod(plotflag // 10, 10) + transFlag = np.mod(plotflag // 10, 10) if transFlag == 0: data = f elif transFlag == 1: @@ -438,11 +497,14 @@ def transformdata(x, f, plotflag): data = -np.log1p(-cumtrapz(f, x)) else: if any(f < 0): - raise ValueError('Invalid plotflag: Data or dataCI is negative, but must be positive') + raise ValueError('Invalid plotflag: Data or dataCI is ' + + 'negative, but must be positive') data = 10 * np.log10(f) return data + class Plotter_2d(Plotter_1d): + """ Parameters ---------- @@ -458,11 +520,12 @@ class Plotter_2d(Plotter_1d): if plotmethod is None: plotmethod = 'contour' super(Plotter_2d, self).__init__(plotmethod) - + def _plot(self, axis, plotflag, wdata, *args, **kwds): h1 = plot2d(axis, wdata, plotflag, *args, **kwds) return h1 - + + def plot2d(axis, wdata, plotflag, *args, **kwds): f = wdata if isinstance(wdata.args, (list, tuple)): @@ -471,72 +534,83 @@ def plot2d(axis, wdata, plotflag, *args, **kwds): args1 = tuple((wdata.args,)) + (wdata.data,) + args if plotflag in (1, 6, 7, 8, 9): isPL = False - if hasattr(f, 'clevels') and len(f.clevels) > 0: # check if contour levels is submitted + # check if contour levels is submitted + if hasattr(f, 'clevels') and len(f.clevels) > 0: CL = f.clevels - isPL = hasattr(f, 'plevels') and f.plevels is not None + isPL = hasattr(f, 'plevels') and f.plevels is not None if isPL: - PL = f.plevels # levels defines quantile levels? 0=no 1=yes + PL = f.plevels # levels defines quantile levels? 0=no 1=yes else: dmax = np.max(f.data) dmin = np.min(f.data) - CL = dmax - (dmax - dmin) * (1 - np.r_[0.01, 0.025, 0.05, 0.1, 0.2, 0.4, 0.5, 0.75]) + CL = dmax - (dmax - dmin) * \ + (1 - np.r_[0.01, 0.025, 0.05, 0.1, 0.2, 0.4, 0.5, 0.75]) clvec = np.sort(CL) - + if plotflag in [1, 8, 9]: - h = axis.contour(*args1, levels=CL, **kwds); - #else: + h = axis.contour(*args1, levels=CL, **kwds) + # else: # [cs hcs] = contour3(f.x{:},f.f,CL,sym); - + if plotflag in (1, 6): ncl = len(clvec) if ncl > 12: ncl = 12 - warnings.warn('Only the first 12 levels will be listed in table.') - + warnings.warn( + 'Only the first 12 levels will be listed in table.') + clvals = PL[:ncl] if isPL else clvec[:ncl] - unused_axcl = cltext(clvals, percent=isPL) # print contour level text + unused_axcl = cltext( + clvals, + percent=isPL) # print contour level text elif any(plotflag == [7, 9]): axis.clabel(h) else: axis.clabel(h) elif plotflag == 2: h = axis.mesh(*args1, **kwds) - elif plotflag == 3: - h = axis.surf(*args1, **kwds) #shading interp % flat, faceted % surfc - elif plotflag == 4: + elif plotflag == 3: + # shading interp % flat, faceted % surfc + h = axis.surf(*args1, **kwds) + elif plotflag == 4: h = axis.waterfall(*args1, **kwds) - elif plotflag == 5: - h = axis.pcolor(*args1, **kwds) #%shading interp % flat, faceted + elif plotflag == 5: + h = axis.pcolor(*args1, **kwds) # %shading interp % flat, faceted elif plotflag == 10: h = axis.contourf(*args1, **kwds) axis.clabel(h) plotbackend.colorbar(h) else: raise ValueError('unknown option for plotflag') - #if any(plotflag==(2:5)) + # if any(plotflag==(2:5)) # shading(shad); - #end + # end # pass - + + def test_plotdata(): plotbackend.ioff() x = np.arange(-2, 2, 0.4) xi = np.arange(-2, 2, 0.1) - - d = PlotData(np.sin(x), x, xlab='x', ylab='sin', title='sinus', plot_args=['r.']) + + d = PlotData(np.sin(x), x, xlab='x', ylab='sin', title='sinus', + plot_args=['r.']) di = PlotData(d.eval_points(xi, method='cubic'), xi) unused_hi = di.plot() unused_h = d.plot() d.show() + def test_docstrings(): import doctest - doctest.testmod() - + print('Testing docstrings in %s' % __file__) + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) + + def main(): pass if __name__ == '__main__': test_docstrings() - #test_plotdata() - #main() + # test_plotdata() + # main() diff --git a/pywafo/src/wafo/covariance/__init__.py b/pywafo/src/wafo/covariance/__init__.py index d142adc..cceda48 100644 --- a/pywafo/src/wafo/covariance/__init__.py +++ b/pywafo/src/wafo/covariance/__init__.py @@ -2,6 +2,6 @@ Covariance package in WAFO Toolbox. """ -from core import * #CovData1D +from core import * # CovData1D #import models -#import dispersion_relation \ No newline at end of file +import estimation diff --git a/pywafo/src/wafo/covariance/core.py b/pywafo/src/wafo/covariance/core.py index cd245ba..e381b53 100644 --- a/pywafo/src/wafo/covariance/core.py +++ b/pywafo/src/wafo/covariance/core.py @@ -1,837 +1,737 @@ -''' -CovData1D ---------- -data : Covariance function values. Size [ny nx nt], all singleton dim. removed. -args : Lag of first space dimension, length nx. -h : Water depth. -tr : Transformation function. -type : 'enc', 'rot' or 'none'. -v : Ship speed, if .type='enc' -phi : Rotation of coordinate system, e.g. direction of ship -norm : Normalization flag, Logical 1 if autocorrelation, 0 if covariance. -Rx, ... ,Rtttt : Obvious derivatives of .R. -note : Memorandum string. -date : Date and time of creation or change. -''' - -from __future__ import division -import warnings -#import numpy as np -from numpy import (zeros, sqrt, dot, inf, where, pi, nan, #@UnresolvedImport - atleast_1d, hstack, vstack, r_, linspace, flatnonzero, size, #@UnresolvedImport - isnan, finfo, diag, ceil, floor, random, pi) #@UnresolvedImport -from numpy.fft import fft #as fft -from numpy.random import randn -import scipy.interpolate as interpolate -from scipy.linalg import toeplitz, sqrtm, svd, cholesky, diagsvd, pinv -from scipy import sparse -from pylab import stineman_interp - -from wafo.wafodata import PlotData -from wafo.misc import sub_dict_select, nextpow2 #, JITImport -import wafo.spectrum as _wafospec -#_wafospec = JITImport('wafo.spectrum') - - -__all__ = ['CovData1D'] - -def _set_seed(iseed): - if iseed != None: - try: - random.set_state(iseed) - except: - random.seed(iseed) - - -#def rndnormnd(cov, mean=0.0, cases=1, method='svd'): -# ''' -# Random vectors from a multivariate Normal distribution -# -# Parameters -# ---------- -# mean, cov : array-like -# mean and covariance, respectively. -# cases : scalar integer -# number of sample vectors -# method : string -# defining squareroot method for covariance -# 'svd' : Singular value decomp. (stable, quite fast) (default) -# 'chol' : Cholesky decomposition (fast, but unstable) -# 'sqrtm' : sqrtm (stable and slow) -# -# Returns -# ------- -# r : matrix of random numbers from the multivariate normal -# distribution with the given mean and covariance matrix. -# -# The covariance must be a symmetric, semi-positive definite matrix with shape -# equal to the size of the mean. METHOD used for calculating the square root -# of COV is either svd, cholesky or sqrtm. (cholesky is fastest but least accurate.) -# When cholesky is chosen and S is not positive definite, the svd-method -# is used instead. -# -# Example -# ------- -# mu = [0, 5] -# S = [[1 0.45], [0.45 0.25]] -# r = rndnormnd(S, mu, 1) -# plot(r(:,1),r(:,2),'.') -# -# d = 40; rho = 2*rand(1,d)-1; -# mu = zeros(0,d); -# S = (rho.'*rho-diag(rho.^2))+eye(d); -# r = rndnormnd(S,mu,100,'genchol')'; -# -# See also -# -------- -# chol, svd, sqrtm, genchol -# np.random.multivariate_normal -# ''' -# sa = np.atleast_2d(cov) -# mu = np.atleast_1d(mean).ravel() -# m, n = sa.shape -# if m != n: -# raise ValueError('Covariance must be square') -# def svdfun(sa): -# u, s, vh = svd(sa, full_matrices=False) -# sqt = diagsvd(sqrt(s)) -# return dot(u, dot(sqt, vh)) -# -# sqrtfuns = dict(sqrtm=sqrtm, svd=svdfun, cholesky=cholesky) -# sqrtfun = sqrtfuns[method] -# std = sqrtfun(sa) -# return dot(std,random.randn(n, cases)) + mu[:,newaxis] - - -class CovData1D(PlotData): - """ Container class for 1D covariance data objects in WAFO - - Member variables - ---------------- - data : array_like - args : vector for 1D, list of vectors for 2D, 3D, ... - - type : string - spectrum type, one of 'freq', 'k1d', 'enc' (default 'freq') - lagtype : letter - lag type, one of: 'x', 'y' or 't' (default 't') - - - Examples - -------- - >>> import numpy as np - >>> import wafo.spectrum as sp - >>> Sj = sp.models.Jonswap(Hm0=3,Tp=7) - >>> w = np.linspace(0,4,256) - >>> S = sp.SpecData1D(Sj(w),w) #Make spectrum object from numerical values - - See also - -------- - PlotData - CovData - """ - - def __init__(self, *args, **kwds): - super(CovData1D, self).__init__(*args, **kwds) - - self.name = 'WAFO Covariance Object' - self.type = 'time' - self.lagtype = 't' - self.h = inf - self.tr = None - self.phi = 0. - self.v = 0. - self.norm = 0 - somekeys = ['phi', 'name', 'h', 'tr', 'lagtype', 'v', 'type', 'norm'] - - self.__dict__.update(sub_dict_select(kwds, somekeys)) - - self.setlabels() - def setlabels(self): - ''' Set automatic title, x-,y- and z- labels - - based on type, - ''' - - N = len(self.type) - if N == 0: - raise ValueError('Object does not appear to be initialized, it is empty!') - - labels = ['', 'ACF', ''] - - if self.lagtype.startswith('t'): - labels[0] = 'Lag [s]' - else: - labels[0] = 'Lag [m]' - - if self.norm: - title = 'Auto Correlation Function ' - labels[0] = labels[0].split('[')[0] - else: - title = 'Auto Covariance Function ' - - self.labels.title = title - self.labels.xlab = labels[0] - self.labels.ylab = labels[1] - self.labels.zlab = labels[2] - - - -## def copy(self): -## kwds = self.__dict__.copy() -## wdata = CovData1D(**kwds) -## return wdata - - def tospecdata(self, rate=None, method='fft', nugget=0.0, trunc=1e-5, fast=True): - ''' - Computes spectral density from the auto covariance function - - Parameters - ---------- - rate = scalar, int - 1,2,4,8...2^r, interpolation rate for f (default 1) - - method: string - interpolation method 'stineman', 'linear', 'cubic', 'fft' - - nugget = scalar, real - nugget effect to ensure that round off errors do not result in - negative spectral estimates. Good choice might be 10^-12. - - trunc : scalar, real - truncates all spectral values where S/max(S) < trunc - 0 <= trunc <1 This is to ensure that high frequency - noise is not added to the spectrum. (default 1e-5) - fast : bool - if True : zero-pad to obtain power of 2 length ACF (default) - otherwise no zero-padding of ACF, slower but more accurate. - - Returns - -------- - S = SpecData1D object - spectral density - - NB! This routine requires that the covariance is evenly spaced - starting from zero lag. Currently only capable of 1D matrices. - - Example: - >>> import wafo.spectrum.models as sm - >>> import numpy as np - >>> import scipy.signal as st - >>> import pylab - >>> L = 129 - >>> t = np.linspace(0,75,L) - >>> R = np.zeros(L) - >>> win = st.parzen(41) - >>> R[0:21] = win[20:41] - >>> R0 = CovData1D(R,t) - >>> S0 = R0.tospecdata() - - >>> Sj = sm.Jonswap() - >>> S = Sj.tospecdata() - >>> R2 = S.tocovdata() - >>> S1 = R2.tospecdata() - >>> abs(S1.data-S.data).max() - - >>> S1.plot('r-') - >>> S.plot('b:') - >>> pylab.show() - - >>> all(abs(S1.data-S.data)<1e-4) - - See also - -------- - spec2cov - datastructures - ''' - - dt = self.sampling_period() - # dt = time-step between data points. - - acf, unused_ti = atleast_1d(self.data, self.args) - - if self.lagtype in 't': - spectype = 'freq' - ftype = 'w' - else: - spectype = 'k1d' - ftype = 'k' - - if rate is None: - rate = 1 ##interpolation rate - else: - rate = 2 ** nextpow2(rate) ##make sure rate is a power of 2 - - - ## add a nugget effect to ensure that round off errors - ## do not result in negative spectral estimates - acf[0] = acf[0] + nugget - n = acf.size - # embedding a circulant vector and Fourier transform - - nfft = 2 ** nextpow2(2 * n - 2) if fast else 2 * n - 2 - - if method=='fft': - nfft *= rate - - nf = nfft / 2 ## number of frequencies - acf = r_[acf, zeros(nfft - 2 * n + 2), acf[n - 2:0:-1]] - - Rper = (fft(acf, nfft).real).clip(0) ## periodogram -# import pylab -# pylab.semilogy(Rper) -# pylab.show() - RperMax = Rper.max() - Rper = where(Rper < trunc * RperMax, 0, Rper) - - S = abs(Rper[0:(nf + 1)]) * dt / pi - w = linspace(0, pi / dt, nf + 1) - So = _wafospec.SpecData1D(S, w, type=spectype, freqtype=ftype) - So.tr = self.tr - So.h = self.h - So.norm = self.norm - - if method != 'fft' and rate > 1: - So.args = linspace(0, pi / dt, nf * rate) - if method == 'stineman': - So.data = stineman_interp(So.args, w, S) - else: - intfun = interpolate.interp1d(w, S, kind=method) - So.data = intfun(So.args) - So.data = So.data.clip(0) # clip negative values to 0 - return So - - def sampling_period(self): - ''' - Returns sampling interval - - Returns - --------- - dt : scalar - sampling interval, unit: - [s] if lagtype=='t' - [m] otherwise - ''' - dt1 = self.args[1] - self.args[0] - n = size(self.args) - 1 - t = self.args[-1] - self.args[0] - dt = t / n - if abs(dt - dt1) > 1e-10: - warnings.warn('Data is not uniformly sampled!') - return dt - - def sim(self, ns=None, cases=1, dt=None, iseed=None, derivative=False): - ''' - Simulates a Gaussian process and its derivative from ACF - - Parameters - ---------- - ns : scalar - number of simulated points. (default length(S)-1=n-1). - If ns>n-1 it is assummed that R(k)=0 for all k>n-1 - cases : scalar - number of replicates (default=1) - dt : scalar - step in grid (default dt is defined by the Nyquist freq) - iseed : int or state - starting state/seed number for the random number generator - (default none is set) - derivative : bool - if true : return derivative of simulated signal as well - otherwise - - Returns - ------- - xs = a cases+1 column matrix ( t,X1(t) X2(t) ...). - xsder = a cases+1 column matrix ( t,X1'(t) X2'(t) ...). - - Details - ------- - Performs a fast and exact simulation of stationary zero mean - Gaussian process through circulant embedding of the covariance matrix. - - If the ACF has a non-empty field .tr, then the transformation is - applied to the simulated data, the result is a simulation of a transformed - Gaussian process. - - Note: The simulation may give high frequency ripple when used with a - small dt. - - Example: - >>> import wafo.spectrum.models as sm - >>> Sj = sm.Jonswap() - >>> S = Sj.tospecdata() #Make spec - >>> R = S.tocovdata() - >>> x = R.sim(ns=1000,dt=0.2) - - See also - -------- - spec2sdat, gaus2dat - - Reference - ----------- - C.R Dietrich and G. N. Newsam (1997) - "Fast and exact simulation of stationary - Gaussian process through circulant embedding - of the Covariance matrix" - SIAM J. SCI. COMPT. Vol 18, No 4, pp. 1088-1107 - ''' - - # TODO fix it, it does not work - - # Add a nugget effect to ensure that round off errors - # do not result in negative spectral estimates - nugget = 0 # 10**-12 - - _set_seed(iseed) - - acf = self.data.ravel() - n = acf.size - - - I = acf.argmax() - if I != 0: - raise ValueError('ACF does not have a maximum at zero lag') - - acf.shape = (n, 1) - - dT = self.sampling_period() - - x = zeros((ns, cases + 1)) - - if derivative: - xder = x.copy() - - ## add a nugget effect to ensure that round off errors - ## do not result in negative spectral estimates - acf[0] = acf[0] + nugget - - ## Fast and exact simulation of simulation of stationary - ## Gaussian process throug circulant embedding of the - ## Covariance matrix - floatinfo = finfo(float) - if (abs(acf[-1]) > floatinfo.eps): ## assuming acf(n+1)==0 - m2 = 2 * n - 1 - nfft = 2 ** nextpow2(max(m2, 2 * ns)) - acf = r_[acf, zeros((nfft - m2, 1)), acf[-1:0:-1, :]] - #warnings,warn('I am now assuming that ACF(k)=0 for k>MAXLAG.') - else: # # ACF(n)==0 - m2 = 2 * n - 2 - nfft = 2 ** nextpow2(max(m2, 2 * ns)) - acf = r_[acf, zeros((nfft - m2, 1)), acf[n - 1:1:-1, :]] - - ##m2=2*n-2 - S = fft(acf, nfft, axis=0).real ## periodogram - - I = S.argmax() - k = flatnonzero(S < 0) - if k.size > 0: - #disp('Warning: Not able to construct a nonnegative circulant ') - #disp('vector from the ACF. Apply the parzen windowfunction ') - #disp('to the ACF in order to avoid this.') - #disp('The returned result is now only an approximation.') - - # truncating negative values to zero to ensure that - # that this noise is not added to the simulated timeseries - - S[k] = 0. - - ix = flatnonzero(k > 2 * I) - if ix.size > 0: -## # truncating all oscillating values above 2 times the peak -## # frequency to zero to ensure that -## # that high frequency noise is not added to -## # the simulated timeseries. - ix0 = k[ix[0]] - S[ix0:-ix0] = 0.0 - - - - trunc = 1e-5 - maxS = S[I] - k = flatnonzero(S[I:-I] < maxS * trunc) - if k.size > 0: - S[k + I] = 0. - ## truncating small values to zero to ensure that - ## that high frequency noise is not added to - ## the simulated timeseries - - cases1 = floor(cases / 2) - cases2 = ceil(cases / 2) -# Generate standard normal random numbers for the simulations - - #randn = np.random.randn - epsi = randn(nfft, cases2) + 1j * randn(nfft, cases2) - Ssqr = sqrt(S / (nfft)) # #sqrt(S(wn)*dw ) - ephat = epsi * Ssqr #[:,np.newaxis] - y = fft(ephat, nfft, axis=0) - x[:, 1:cases + 1] = hstack((y[2:ns + 2, 0:cases2].real, y[2:ns + 2, 0:cases1].imag)) - - x[:, 0] = linspace(0, (ns - 1) * dT, ns) ##(0:dT:(dT*(np-1)))' - - if derivative: - Ssqr = Ssqr * r_[0:(nfft / 2 + 1), -(nfft / 2 - 1):0] * 2 * pi / nfft / dT - ephat = epsi * Ssqr #[:,newaxis] - y = fft(ephat, nfft, axis=0) - xder[:, 1:(cases + 1)] = hstack((y[2:ns + 2, 0:cases2].imag - y[2:ns + 2, 0:cases1].real)) - xder[:, 0] = x[:, 0] - - if self.tr is not None: - print(' Transforming data.') - g = self.tr - if derivative: - for ix in range(cases): - tmp = g.gauss2dat(x[:, ix + 1], xder[:, ix + 1]) - x[:, ix + 1] = tmp[0] - xder[:, ix + 1] = tmp[1] - else: - for ix in range(cases): - x[:, ix + 1] = g.gauss2dat(x[:, ix + 1]) - - if derivative: - return x, xder - else: - return x - - def simcond(self, xo, cases=1, method='approx', inds=None): - """ - Simulate values conditionally on observed known values - - Parameters - ---------- - x : array-like - datavector including missing data. - (missing data must be NaN if inds is not given) - Assumption: The covariance of x is equal to self and have the - same sample period. - cases : scalar integer - number of cases, i.e., number of columns of sample (default=1) - method : string - defining method used in the conditional simulation. Options are: - 'approximate': Condition only on the closest points. Pros: quite fast - 'pseudo': Use pseudo inverse to calculate conditional covariance matrix - 'exact' : Exact simulation. Cons: Slow for large data sets, may not - return any result due to near singularity of the covariance matrix. - inds : integers - indices to spurious or missing data in x - - Returns - ------- - sample : ndarray - a random sample of the missing values conditioned on the observed data. - mu, sigma : ndarray - mean and standard deviation, respectively, of the missing values - conditioned on the observed data. - - - Notes - ----- - SIMCOND generates the missing values from x conditioned on the observed - values assuming x comes from a multivariate Gaussian distribution - with zero expectation and Auto Covariance function R. - - See also - -------- - CovData1D.sim - TimeSeries.reconstruct, - rndnormnd - - Reference - --------- - Brodtkorb, P, Myrhaug, D, and Rue, H (2001) - "Joint distribution of wave height and wave crest velocity from - reconstructed data with application to ringing" - Int. Journal of Offshore and Polar Engineering, Vol 11, No. 1, pp 23--32 - - Brodtkorb, P, Myrhaug, D, and Rue, H (1999) - "Joint distribution of wave height and wave crest velocity from - reconstructed data" - in Proceedings of 9th ISOPE Conference, Vol III, pp 66-73 - """ - # TODO: does not work yet. - - # secret methods: - # 'dec1-3': different decomposing algorithm's - # which is only correct for a variables - # having the Markov property - # Cons: 3 is not correct at all, but seems to give - # a reasonable result - # Pros: 1 is slow, 2 is quite fast and 3 is very fast - # Note: (mu1oStd is not given for method ='dec3') - compute_sigma = True - x = atleast_1d(xo).ravel() - acf = atleast_1d(self.data).ravel() - - N = len(x) - n = len(acf) - - i = acf.argmax() - if i != 0: - raise ValueError('This is not a valid ACF!!') - - - if not inds is None: - x[inds] = nan - inds = where(isnan(x))[0] #indices to the unknown observations - - Ns = len(inds) # # missing values - if Ns == 0: - warnings.warn('No missing data, unable to continue.') - return xo, zeros(Ns), zeros(Ns) - #end - if Ns == N:# simulated surface from the apriori distribution - txt = '''All data missing, - returning sample from the unconditional distribution.''' - warnings.warn(txt) - return self.sim(ns=N, cases=cases), zeros(Ns), zeros(Ns) - - indg = where(1 - isnan(x))[0] #indices to the known observations - - #initializing variables - mu1o = zeros(Ns, 1) - mu1o_std = mu1o - sample = zeros((Ns, cases)) - if method[0] == 'd': - # simulated surface from the apriori distribution - xs = self.sim(ns=N, cases=cases) - mu1os = zeros((Ns, cases)) - - if method.startswith('dec1'): - # only correct for variables having the Markov property - # but still seems to give a reasonable answer. Slow procedure. - Sigma = sptoeplitz(hstack((acf, zeros(N - n)))) - - #Soo=Sigma(~inds,~inds); # covariance between known observations - #S11=Sigma(inds,inds); # covariance between unknown observations - #S1o=Sigma(inds,~inds);# covariance between known and unknown observations - #tmp=S1o*pinv(full(Soo)); - #tmp=S1o/Soo; # this is time consuming if Soo large - tmp = 2 * Sigma[inds, indg] / (Sigma[indg, indg] + Sigma[indg, indg].T) - - if compute_sigma: - #standard deviation of the expected surface - #mu1o_std=sqrt(diag(S11-tmp*S1o')); - mu1o_std = sqrt(diag(Sigma[inds, inds] - tmp * Sigma[indg, inds])) - - - #expected surface conditioned on the known observations from x - mu1o = tmp * x[indg] - #expected surface conditioned on the known observations from xs - mu1os = tmp * (xs[indg, :]) - # sampled surface conditioned on the known observations - sample = mu1o + xs[inds, :] - mu1os - - elif method.startswith('dec2'): - # only correct for variables having the Markov property - # but still seems to give a reasonable answer - # approximating the expected surfaces conditioned on - # the known observations from x and xs by only using the closest points - Sigma = sptoeplitz(hstack((acf, zeros(n)))) - n2 = int(floor(n / 2)) - idx = r_[0:2 * n] + max(0, inds[0] - n2) # indices to the points used - tmpinds = zeros(N, dtype=bool) - tmpinds[inds] = True # temporary storage of indices to missing points - tinds = where(tmpinds[idx])[0] # indices to the points used - tindg = where(1 - tmpinds[idx])[0] - ns = len(tinds); # number of missing data in the interval - nprev = 0; # number of previously simulated points - xsinds = xs[inds, :] - while ns > 0: - tmp = 2 * Sigma[tinds, tindg] / (Sigma[tindg, tindg] + Sigma[tindg, tindg].T) - if compute_sigma: - #standard deviation of the expected surface - #mu1o_std=sqrt(diag(S11-tmp*S1o')); - ix = slice(nprev + 1, nprev + ns + 1) - mu1o_std[ix] = max(mu1o_std[ix], - sqrt(diag(Sigma[tinds, tinds] - tmp * Sigma[tindg, tinds]))) - #end - - #expected surface conditioned on the closest known observations - # from x and xs2 - mu1o[(nprev + 1):(nprev + ns + 1)] = tmp * x[idx[tindg]] - mu1os[(nprev + 1):(nprev + ns + 1), :] = tmp * xs[idx[tindg], :] - - if idx[-1] == N - 1:# - ns = 0 # no more points to simulate - else: - # updating by putting expected surface into x - x[idx[tinds]] = mu1o[(nprev + 1):(nprev + ns + 1)] - xs[idx[tinds]] = mu1os[(nprev + 1):(nprev + ns + 1)] - - nw = sum(tmpinds[idx[-n2:]])# # data which we want to simulate once - tmpinds[idx[:-n2]] = False # removing indices to data .. - # which has been simulated - nprev = nprev + ns - nw # update # points simulated so far - - if (nw == 0) and (nprev < Ns): - idx = r_[0:2 * n] + (inds[nprev + 1] - n2) # move to the next missing data - else: - idx = idx + n - #end - tmp = N - idx[-1] - if tmp < 0: # checking if tmp exceeds the limits - idx = idx + tmp - #end - # find new interval with missing data - tinds = where(tmpinds[idx])[0] - tindg = where(1 - tmpinds[idx])[0] - ns = len(tinds);# # missing data - #end - #end - # sampled surface conditioned on the known observations - sample = mu1o + (xsinds - mu1os) - elif method.startswith('dec3'): - # this is not correct for even for variables having the - # Markov property but still seems to give a reasonable answer - # a quasi approach approximating the expected surfaces conditioned on - # the known observations from x and xs with a spline - - mu1o = interp1(indg, x[indg], inds, 'spline') - mu1os = interp1(indg, xs[indg, :], inds, 'spline') - # sampled surface conditioned on the known observations - sample = mu1o + (xs[inds, :] - mu1os) - - elif method.startswith('exac') or method.startswith('pseu'): - # exact but slow. It also may not return any result - Sigma = sptoeplitz(hstack((acf, zeros(N - n)))) - #Soo=Sigma(~inds,~inds); # covariance between known observations - #S11=Sigma(inds,inds); # covariance between unknown observations - #S1o=Sigma(inds,~inds);# covariance between known and unknown observations - #tmp=S1o/Soo; # this is time consuming if Soo large - if method[0] == 'e': #exact - tmp = 2 * Sigma[inds, indg] / (Sigma[indg, indg] + Sigma[indg, indg].T); - else: # approximate the inverse with pseudo inverse - tmp = dot(Sigma[inds, indg], pinv(Sigma[indg, indg])) - #end - #expected surface conditioned on the known observations from x - mu1o = dot(tmp, x[indg]) - # Covariance conditioned on the known observations - Sigma1o = Sigma[inds, inds] - tmp * Sigma[indg, inds] - #sample conditioned on the known observations from x - sample = random.multivariate_normal(mu1o, Sigma1o, cases) - #rndnormnd(mu1o,Sigma1o,cases ) - - if compute_sigma: - #standard deviation of the expected surface - mu1o_std = sqrt(diag(Sigma1o)); - #end - - elif method.startswith('appr'): - # approximating by only condition on - # the closest points - # checking approximately how many lags we need in order to - # ensure conditional independence - # using that the inverse of the circulant covariance matrix has - # approximately the same bandstructure as the inverse of the - # covariance matrix - - Nsig = 2 * n; - - Sigma = sptoeplitz(hstack((acf, zeros(Nsig - n)))) - n2 = floor(Nsig / 4) - idx = r_[0:Nsig] + max(0, inds[0] - n2) # indices to the points used - tmpinds = zeros(N, dtype=bool) - tmpinds[inds] = True # temporary storage of indices to missing points - tinds = where(tmpinds[idx])[0] # indices to the points used - tindg = where(1 - tmpinds[idx])[0] - ns = len(tinds) # number of missing data in the interval - - nprev = 0 # number of previously simulated points - x2 = x - - while ns > 0: - #make sure MATLAB uses a symmetric matrix solver - tmp = 2 * Sigma[tinds, tindg] / (Sigma[tindg, tindg] + Sigma[tindg, tindg].T) - Sigma1o = Sigma[tinds, tinds] - tmp * Sigma[tindg, tinds] - if compute_sigma: - #standard deviation of the expected surface - #mu1o_std=sqrt(diag(S11-tmp*S1o')); - mu1o_std[(nprev + 1):(nprev + ns + 1)] = max(mu1o_std[(nprev + 1):(nprev + ns)] , - sqrt(diag(Sigma1o))) - #end - - #expected surface conditioned on the closest known observations from x - mu1o[(nprev + 1):(nprev + ns + 1)] = tmp * x2[idx[tindg]] - #sample conditioned on the known observations from x - sample[(nprev + 1):(nprev + ns + 1), :] = rndnormnd(tmp * x[idx[tindg]], Sigma1o, cases) - if idx[-1] == N - 1: - ns = 0 # no more points to simulate - else: - # updating - x2[idx[tinds]] = mu1o[(nprev + 1):(nprev + ns + 1)] #expected surface - x[idx[tinds]] = sample[(nprev + 1):(nprev + ns + 1)]#sampled surface - nw = sum(tmpinds[idx[-n2::]] == True)# # data we want to simulate once more - tmpinds[idx[:-n2]] = False # removing indices to data .. - # which has been simulated - nprev = nprev + ns - nw # update # points simulated so far - - if (nw == 0) and (nprev < Ns): - idx = r_[0:Nsig] + (inds[nprev + 1] - n2) # move to the next missing data - else: - idx = idx + n - #end - tmp = N - idx[-1] - if tmp < 0: # checking if tmp exceeds the limits - idx = idx + tmp - #end - # find new interval with missing data - tinds = where(tmpinds[idx])[0] - tindg = where(1 - tmpinds[idx])[0] - ns = len(tinds);# # missing data in the interval - #end - #end - #end - return sample -# plot(find(~inds),x(~inds),'.') -# hold on, -# ind=find(inds); -# plot(ind,mu1o ,'*') -# plot(ind,sample,'r+') -# #mu1o_std -# plot(ind,[mu1o-2*mu1o_std mu1o+2*mu1o_std ] ,'d') -# #plot(xs),plot(ind,mu1os,'r*') -# hold off -# legend('observed values','mu1o','sampled values','2 stdev') -# #axis([770 850 -1 1]) -# #axis([1300 1325 -1 1]) - -def sptoeplitz(x): - k = where(x.ravel())[0] - n = len(x) - if len(k) > 0.3 * n: - return toeplitz(x) - else: - spdiags = sparse.dia_matrix - data = x[k].reshape(-1, 1).repeat(n, axis= -1) - offsets = k - y = spdiags((data, offsets), shape=(n, n)) - if k[0] == 0: - offsets = k[1::] - data = data[1::, :] - return y + spdiags((data, -offsets), shape=(n, n)) - -def _test_covdata(): - import wafo.data - x = wafo.data.sea() - ts = wafo.objects.mat2timeseries(x) - rf = ts.tocovdata(lag=150) - rf.plot() - -def main(): - import wafo.spectrum.models as sm - import matplotlib - matplotlib.interactive(True) - Sj = sm.Jonswap() - S = Sj.tospecdata() #Make spec - S.plot() - R = S.tocovdata() - R.plot() - #x = R.sim(ns=1000,dt=0.2) - - -if __name__ == '__main__': - if True: #False : # - import doctest - doctest.testmod() - else: - main() +''' +CovData1D +--------- +data : Covariance function values. Size [ny nx nt], all singleton dim. removed. +args : Lag of first space dimension, length nx. +h : Water depth. +tr : Transformation function. +type : 'enc', 'rot' or 'none'. +v : Ship speed, if .type='enc' +phi : Rotation of coordinate system, e.g. direction of ship +norm : Normalization flag, Logical 1 if autocorrelation, 0 if covariance. +Rx, ... ,Rtttt : Obvious derivatives of .R. +note : Memorandum string. +date : Date and time of creation or change. +''' + +from __future__ import division +import warnings +import numpy as np +from numpy import (zeros, ones, sqrt, inf, where, nan, + atleast_1d, hstack, r_, linspace, flatnonzero, size, + isnan, finfo, diag, ceil, floor, random, pi) +from numpy.fft import fft +from numpy.random import randn +import scipy.interpolate as interpolate +from scipy.linalg import toeplitz, lstsq +from scipy import sparse +from pylab import stineman_interp + +from wafo.containers import PlotData +from wafo.misc import sub_dict_select, nextpow2 # , JITImport +import wafo.spectrum as _wafospec +from scipy.sparse.linalg.dsolve.linsolve import spsolve +from scipy.sparse.base import issparse +from scipy.signal.windows import parzen +#_wafospec = JITImport('wafo.spectrum') + +__all__ = ['CovData1D'] + + +def _set_seed(iseed): + if iseed != None: + try: + random.set_state(iseed) + except: + random.seed(iseed) + + +def rndnormnd(mean, cov, cases=1): + ''' + Random vectors from a multivariate Normal distribution + + Parameters + ---------- + mean, cov : array-like + mean and covariance, respectively. + cases : scalar integer + number of sample vectors + + Returns + ------- + r : matrix of random numbers from the multivariate normal + distribution with the given mean and covariance matrix. + + The covariance must be a symmetric, semi-positive definite matrix with + shape equal to the size of the mean. + + Example + ------- + >>> mu = [0, 5] + >>> S = [[1 0.45], [0.45 0.25]] + >>> r = rndnormnd(mu, S, 1) + + plot(r(:,1),r(:,2),'.') + + >>> d = 40 + >>> rho = 2 * np.random.rand(1,d)-1 + >>> mu = zeros(d) + >>> S = (np.dot(rho.T, rho)-diag(rho.ravel()**2))+np.eye(d) + >>> r = rndnormnd(mu, S, 100) + + See also + -------- + np.random.multivariate_normal + ''' + return np.random.multivariate_normal(mean, cov, cases) + + +class CovData1D(PlotData): + + """ Container class for 1D covariance data objects in WAFO + + Member variables + ---------------- + data : array_like + args : vector for 1D, list of vectors for 2D, 3D, ... + + type : string + spectrum type, one of 'freq', 'k1d', 'enc' (default 'freq') + lagtype : letter + lag type, one of: 'x', 'y' or 't' (default 't') + + + Examples + -------- + >>> import numpy as np + >>> import wafo.spectrum as sp + >>> Sj = sp.models.Jonswap(Hm0=3,Tp=7) + >>> w = np.linspace(0,4,256) + >>> S = sp.SpecData1D(Sj(w),w) #Make spectrum object from numerical values + + See also + -------- + PlotData + CovData + """ + + def __init__(self, *args, **kwds): + super(CovData1D, self).__init__(*args, **kwds) + + self.name = 'WAFO Covariance Object' + self.type = 'time' + self.lagtype = 't' + self.h = inf + self.tr = None + self.phi = 0. + self.v = 0. + self.norm = 0 + somekeys = ['phi', 'name', 'h', 'tr', 'lagtype', 'v', 'type', 'norm'] + + self.__dict__.update(sub_dict_select(kwds, somekeys)) + + self.setlabels() + + def setlabels(self): + ''' Set automatic title, x-,y- and z- labels + + based on type, + ''' + + N = len(self.type) + if N == 0: + raise ValueError( + 'Object does not appear to be initialized, it is empty!') + + labels = ['', 'ACF', ''] + + if self.lagtype.startswith('t'): + labels[0] = 'Lag [s]' + else: + labels[0] = 'Lag [m]' + + if self.norm: + title = 'Auto Correlation Function ' + labels[0] = labels[0].split('[')[0] + else: + title = 'Auto Covariance Function ' + + self.labels.title = title + self.labels.xlab = labels[0] + self.labels.ylab = labels[1] + self.labels.zlab = labels[2] + + def tospecdata(self, rate=None, method='fft', nugget=0.0, trunc=1e-5, + fast=True): + ''' + Computes spectral density from the auto covariance function + + Parameters + ---------- + rate = scalar, int + 1,2,4,8...2^r, interpolation rate for f (default 1) + method : string + interpolation method 'stineman', 'linear', 'cubic', 'fft' + nugget : scalar, real + nugget effect to ensure that round off errors do not result in + negative spectral estimates. Good choice might be 10^-12. + trunc : scalar, real + truncates all spectral values where S/max(S) < trunc + 0 <= trunc <1 This is to ensure that high frequency + noise is not added to the spectrum. (default 1e-5) + fast : bool + if True : zero-pad to obtain power of 2 length ACF (default) + otherwise no zero-padding of ACF, slower but more accurate. + + Returns + -------- + S : SpecData1D object + spectral density + + NB! This routine requires that the covariance is evenly spaced + starting from zero lag. Currently only capable of 1D matrices. + + Example: + >>> import wafo.spectrum.models as sm + >>> import numpy as np + >>> import scipy.signal as st + >>> import pylab + >>> L = 129 + >>> t = np.linspace(0,75,L) + >>> R = np.zeros(L) + >>> win = st.parzen(41) + >>> R[0:21] = win[20:41] + >>> R0 = CovData1D(R,t) + >>> S0 = R0.tospecdata() + + >>> Sj = sm.Jonswap() + >>> S = Sj.tospecdata() + >>> R2 = S.tocovdata() + >>> S1 = R2.tospecdata() + >>> abs(S1.data-S.data).max() + + >>> S1.plot('r-') + >>> S.plot('b:') + >>> pylab.show() + + >>> all(abs(S1.data-S.data)<1e-4) + + See also + -------- + spec2cov + datastructures + ''' + + dt = self.sampling_period() + # dt = time-step between data points. + + acf, unused_ti = atleast_1d(self.data, self.args) + + if self.lagtype in 't': + spectype = 'freq' + ftype = 'w' + else: + spectype = 'k1d' + ftype = 'k' + + if rate is None: + rate = 1 # interpolation rate + else: + rate = 2 ** nextpow2(rate) # make sure rate is a power of 2 + + # add a nugget effect to ensure that round off errors + # do not result in negative spectral estimates + acf[0] = acf[0] + nugget + n = acf.size + # embedding a circulant vector and Fourier transform + + nfft = 2 ** nextpow2(2 * n - 2) if fast else 2 * n - 2 + + if method == 'fft': + nfft *= rate + + nf = nfft / 2 # number of frequencies + acf = r_[acf, zeros(nfft - 2 * n + 2), acf[n - 2:0:-1]] + + Rper = (fft(acf, nfft).real).clip(0) # periodogram + RperMax = Rper.max() + Rper = where(Rper < trunc * RperMax, 0, Rper) + + S = abs(Rper[0:(nf + 1)]) * dt / pi + w = linspace(0, pi / dt, nf + 1) + So = _wafospec.SpecData1D(S, w, type=spectype, freqtype=ftype) + So.tr = self.tr + So.h = self.h + So.norm = self.norm + + if method != 'fft' and rate > 1: + So.args = linspace(0, pi / dt, nf * rate) + if method == 'stineman': + So.data = stineman_interp(So.args, w, S) + else: + intfun = interpolate.interp1d(w, S, kind=method) + So.data = intfun(So.args) + So.data = So.data.clip(0) # clip negative values to 0 + return So + + def sampling_period(self): + ''' + Returns sampling interval + + Returns + --------- + dt : scalar + sampling interval, unit: + [s] if lagtype=='t' + [m] otherwise + ''' + dt1 = self.args[1] - self.args[0] + n = size(self.args) - 1 + t = self.args[-1] - self.args[0] + dt = t / n + if abs(dt - dt1) > 1e-10: + warnings.warn('Data is not uniformly sampled!') + return dt + + def _is_valid_acf(self): + if self.data.argmax() != 0: + raise ValueError('ACF does not have a maximum at zero lag') + + def sim(self, ns=None, cases=1, dt=None, iseed=None, derivative=False): + ''' + Simulates a Gaussian process and its derivative from ACF + + Parameters + ---------- + ns : scalar + number of simulated points. (default length(S)-1=n-1). + If ns>n-1 it is assummed that R(k)=0 for all k>n-1 + cases : scalar + number of replicates (default=1) + dt : scalar + step in grid (default dt is defined by the Nyquist freq) + iseed : int or state + starting state/seed number for the random number generator + (default none is set) + derivative : bool + if true : return derivative of simulated signal as well + otherwise + + Returns + ------- + xs = a cases+1 column matrix ( t,X1(t) X2(t) ...). + xsder = a cases+1 column matrix ( t,X1'(t) X2'(t) ...). + + Details + ------- + Performs a fast and exact simulation of stationary zero mean + Gaussian process through circulant embedding of the covariance matrix. + + If the ACF has a non-empty field .tr, then the transformation is + applied to the simulated data, the result is a simulation of a + transformed Gaussian process. + + Note: The simulation may give high frequency ripple when used with a + small dt. + + Example: + >>> import wafo.spectrum.models as sm + >>> Sj = sm.Jonswap() + >>> S = Sj.tospecdata() #Make spec + >>> R = S.tocovdata() + >>> x = R.sim(ns=1000,dt=0.2) + + See also + -------- + spec2sdat, gaus2dat + + Reference + ----------- + C.R Dietrich and G. N. Newsam (1997) + "Fast and exact simulation of stationary + Gaussian process through circulant embedding + of the Covariance matrix" + SIAM J. SCI. COMPT. Vol 18, No 4, pp. 1088-1107 + ''' + + # TODO fix it, it does not work + + # Add a nugget effect to ensure that round off errors + # do not result in negative spectral estimates + nugget = 0 # 10**-12 + + _set_seed(iseed) + self._is_valid_acf() + acf = self.data.ravel() + n = acf.size + acf.shape = (n, 1) + + dT = self.sampling_period() + + x = zeros((ns, cases + 1)) + + if derivative: + xder = x.copy() + + # add a nugget effect to ensure that round off errors + # do not result in negative spectral estimates + acf[0] = acf[0] + nugget + + # Fast and exact simulation of simulation of stationary + # Gaussian process throug circulant embedding of the + # Covariance matrix + floatinfo = finfo(float) + if (abs(acf[-1]) > floatinfo.eps): # assuming acf(n+1)==0 + m2 = 2 * n - 1 + nfft = 2 ** nextpow2(max(m2, 2 * ns)) + acf = r_[acf, zeros((nfft - m2, 1)), acf[-1:0:-1, :]] + #warnings,warn('I am now assuming that ACF(k)=0 for k>MAXLAG.') + else: # ACF(n)==0 + m2 = 2 * n - 2 + nfft = 2 ** nextpow2(max(m2, 2 * ns)) + acf = r_[acf, zeros((nfft - m2, 1)), acf[n - 1:1:-1, :]] + + # m2=2*n-2 + S = fft(acf, nfft, axis=0).real # periodogram + + I = S.argmax() + k = flatnonzero(S < 0) + if k.size > 0: + #disp('Warning: Not able to construct a nonnegative circulant ') + #disp('vector from the ACF. Apply the parzen windowfunction ') + #disp('to the ACF in order to avoid this.') + #disp('The returned result is now only an approximation.') + + # truncating negative values to zero to ensure that + # that this noise is not added to the simulated timeseries + + S[k] = 0. + + ix = flatnonzero(k > 2 * I) + if ix.size > 0: +# truncating all oscillating values above 2 times the peak +# frequency to zero to ensure that +# that high frequency noise is not added to +# the simulated timeseries. + ix0 = k[ix[0]] + S[ix0:-ix0] = 0.0 + + trunc = 1e-5 + maxS = S[I] + k = flatnonzero(S[I:-I] < maxS * trunc) + if k.size > 0: + S[k + I] = 0. + # truncating small values to zero to ensure that + # that high frequency noise is not added to + # the simulated timeseries + + cases1 = int(cases / 2) + cases2 = int(ceil(cases / 2)) +# Generate standard normal random numbers for the simulations + + #randn = np.random.randn + epsi = randn(nfft, cases2) + 1j * randn(nfft, cases2) + Ssqr = sqrt(S / (nfft)) # sqrt(S(wn)*dw ) + ephat = epsi * Ssqr # [:,np.newaxis] + y = fft(ephat, nfft, axis=0) + x[:, 1:cases + 1] = hstack((y[2:ns + 2, 0:cases2].real, + y[2:ns + 2, 0:cases1].imag)) + + x[:, 0] = linspace(0, (ns - 1) * dT, ns) # (0:dT:(dT*(np-1)))' + + if derivative: + Ssqr = Ssqr * \ + r_[0:(nfft / 2 + 1), -(nfft / 2 - 1):0] * 2 * pi / nfft / dT + ephat = epsi * Ssqr # [:,newaxis] + y = fft(ephat, nfft, axis=0) + xder[:, 1:(cases + 1)] = hstack((y[2:ns + 2, 0:cases2].imag - + y[2:ns + 2, 0:cases1].real)) + xder[:, 0] = x[:, 0] + + if self.tr is not None: + print(' Transforming data.') + g = self.tr + if derivative: + for ix in range(cases): + tmp = g.gauss2dat(x[:, ix + 1], xder[:, ix + 1]) + x[:, ix + 1] = tmp[0] + xder[:, ix + 1] = tmp[1] + else: + for ix in range(cases): + x[:, ix + 1] = g.gauss2dat(x[:, ix + 1]) + + if derivative: + return x, xder + else: + return x + + def _get_lag_where_acf_is_almost_zero(self): + acf = self.data.ravel() + r0 = acf[0] + n = len(acf) + sigma = sqrt(r_[0, r0 ** 2, + r0 ** 2 + 2 * np.cumsum(acf[1:n - 1] ** 2)] / n) + k = flatnonzero(np.abs(acf) > 0.1 * sigma) + if k.size > 0: + lag = min(k.max() + 3, n) + return lag + return n + + def _get_acf(self, smooth=False): + self._is_valid_acf() + acf = atleast_1d(self.data).ravel() + n = self._get_lag_where_acf_is_almost_zero() + if smooth: + rwin = parzen(2 * n + 1) + return acf[:n] * rwin[n:2 * n] + else: + return acf[:n] + + def _split_cov(self, sigma, i_known, i_unknown): + ''' + Split covariance matrix between known/unknown observations + + Returns + ------- + Soo covariance between known observations + S11 = covariance between unknown observations + S1o = covariance between known and unknown obs + ''' + Soo, So1 = sigma[i_known][:, i_known], sigma[i_known][:, i_unknown] + S11 = sigma[i_unknown][:, i_unknown] + return Soo, So1, S11 + + def _update_window(self, idx, i_unknown, num_x, num_acf, + overlap, nw, num_restored): + Nsig = len(idx) + start_max = num_x - Nsig + if (nw == 0) and (num_restored < len(i_unknown)): + # move to the next missing data + start_ix = min(i_unknown[num_restored + 1] - overlap, start_max) + else: + start_ix = min(idx[0] + num_acf, start_max) + + return idx + start_ix - idx[0] + + def simcond(self, xo, method='approx', i_unknown=None): + """ + Simulate values conditionally on observed known values + + Parameters + ---------- + x : vector + timeseries including missing data. + (missing data must be NaN if i_unknown is not given) + Assumption: The covariance of x is equal to self and have the + same sample period. + method : string + defining method used in the conditional simulation. Options are: + 'approximate': Condition only on the closest points. Quite fast + 'exact' : Exact simulation. Slow for large data sets, may not + return any result due to near singularity of the covariance + matrix. + i_unknown : integers + indices to spurious or missing data in x + + Returns + ------- + sample : ndarray + a random sample of the missing values conditioned on the observed + data. + mu, sigma : ndarray + mean and standard deviation, respectively, of the missing values + conditioned on the observed data. + + Notes + ----- + SIMCOND generates the missing values from x conditioned on the observed + values assuming x comes from a multivariate Gaussian distribution + with zero expectation and Auto Covariance function R. + + See also + -------- + CovData1D.sim + TimeSeries.reconstruct, + rndnormnd + + Reference + --------- + Brodtkorb, P, Myrhaug, D, and Rue, H (2001) + "Joint distribution of wave height and wave crest velocity from + reconstructed data with application to ringing" + Int. Journal of Offshore and Polar Engineering, Vol 11, No. 1, + pp 23--32 + + Brodtkorb, P, Myrhaug, D, and Rue, H (1999) + "Joint distribution of wave height and wave crest velocity from + reconstructed data" + in Proceedings of 9th ISOPE Conference, Vol III, pp 66-73 + """ + x = atleast_1d(xo).ravel() + acf = self._get_acf() + + num_x = len(x) + num_acf = len(acf) + + if not i_unknown is None: + x[i_unknown] = nan + i_unknown = flatnonzero(isnan(x)) + num_unknown = len(i_unknown) + + mu1o = zeros((num_unknown,)) + mu1o_std = zeros((num_unknown,)) + sample = zeros((num_unknown,)) + if num_unknown == 0: + warnings.warn('No missing data, no point to continue.') + return sample, mu1o, mu1o_std + if num_unknown == num_x: + warnings.warn('All data missing, returning sample from' + + ' the apriori distribution.') + mu1o_std = ones(num_unknown) * sqrt(acf[0]) + return self.sim(ns=num_unknown, cases=1)[:, 1], mu1o, mu1o_std + + i_known = flatnonzero(1 - isnan(x)) + + if method.startswith('exac'): + # exact but slow. It also may not return any result + if num_acf > 0.3 * num_x: + Sigma = toeplitz(hstack((acf, zeros(num_x - num_acf)))) + else: + acf[0] = acf[0] * 1.00001 + Sigma = sptoeplitz(hstack((acf, zeros(num_x - num_acf)))) + Soo, So1, S11 = self._split_cov(Sigma, i_known, i_unknown) + + if issparse(Sigma): + So1 = So1.todense() + S11 = S11.todense() + S1o_Sooinv = spsolve(Soo + Soo.T, 2 * So1).T + else: + Sooinv_So1, _res, _rank, _s = lstsq(Soo + Soo.T, 2 * So1, + cond=1e-4) + S1o_Sooinv = Sooinv_So1.T + mu1o = S1o_Sooinv.dot(x[i_known]) + Sigma1o = S11 - S1o_Sooinv.dot(So1) + if (diag(Sigma1o) < 0).any(): + raise ValueError('Failed to converge to a solution') + + mu1o_std = sqrt(diag(Sigma1o)) + sample[:] = rndnormnd(mu1o, Sigma1o, cases=1).ravel() + + elif method.startswith('appr'): + # approximating by only condition on the closest points + + Nsig = min(2 * num_acf, num_x) + + Sigma = toeplitz(hstack((acf, zeros(Nsig - num_acf)))) + overlap = int(Nsig / 4) + # indices to the points used + idx = r_[0:Nsig] + max(0, min(i_unknown[0] - overlap, num_x - Nsig)) + mask_unknown = zeros(num_x, dtype=bool) + # temporary storage of indices to missing points + mask_unknown[i_unknown] = True + t_unknown = where(mask_unknown[idx])[0] + t_known = where(1 - mask_unknown[idx])[0] + ns = len(t_unknown) # number of missing data in the interval + + num_restored = 0 # number of previously simulated points + x2 = x.copy() + + while ns > 0: + Soo, So1, S11 = self._split_cov(Sigma, t_known, t_unknown) + if issparse(Soo): + So1 = So1.todense() + S11 = S11.todense() + S1o_Sooinv = spsolve(Soo + Soo.T, 2 * So1).T + else: + Sooinv_So1, _res, _rank, _s = lstsq(Soo + Soo.T, 2 * So1, + cond=1e-4) + S1o_Sooinv = Sooinv_So1.T + Sigma1o = S11 - S1o_Sooinv.dot(So1) + if (diag(Sigma1o) < 0).any(): + raise ValueError('Failed to converge to a solution') + + ix = slice((num_restored), (num_restored + ns)) + # standard deviation of the expected surface + mu1o_std[ix] = np.maximum(mu1o_std[ix], sqrt(diag(Sigma1o))) + + # expected surface conditioned on the closest known + # observations from x + mu1o[ix] = S1o_Sooinv.dot(x2[idx[t_known]]) + # sample conditioned on the known observations from x + mu1os = S1o_Sooinv.dot(x[idx[t_known]]) + sample[ix] = rndnormnd(mu1os, Sigma1o, cases=1) + if idx[-1] == num_x - 1: + ns = 0 # no more points to simulate + else: + x2[idx[t_unknown]] = mu1o[ix] # expected surface + x[idx[t_unknown]] = sample[ix] # sampled surface + # removing indices to data which has been simulated + mask_unknown[idx[:-overlap]] = False + # data we want to simulate once more + nw = sum(mask_unknown[idx[-overlap:]] == True) + num_restored += ns - nw # update # points simulated so far + + idx = self._update_window(idx, i_unknown, num_x, num_acf, + overlap, nw, num_restored) + + # find new interval with missing data + t_unknown = flatnonzero(mask_unknown[idx]) + t_known = flatnonzero(1 - mask_unknown[idx]) + ns = len(t_unknown) # # missing data in the interval + return sample, mu1o, mu1o_std + + +def sptoeplitz(x): + k = flatnonzero(x) + n = len(x) + spdiags = sparse.dia_matrix + data = x[k].reshape(-1, 1).repeat(n, axis=-1) + offsets = k + y = spdiags((data, offsets), shape=(n, n)) + if k[0] == 0: + offsets = k[1::] + data = data[1::, :] + t = y + spdiags((data, -offsets), shape=(n, n)) + return t.tocsr() + + +def _test_covdata(): + import wafo.data + x = wafo.data.sea() + ts = wafo.objects.mat2timeseries(x) + rf = ts.tocovdata(lag=150) + rf.plot() + + +def main(): + import wafo.spectrum.models as sm + import matplotlib + matplotlib.interactive(True) + Sj = sm.Jonswap() + S = Sj.tospecdata() # Make spec + S.plot() + R = S.tocovdata(rate=3) + R.plot() + x = R.sim(ns=1024 * 4) + inds = np.hstack((21 + np.arange(20), + 1000 + np.arange(20), + 1024 * 4 - 21 + np.arange(20))) + sample, mu1o, mu1o_std = R.simcond(x[:, 1], method='approx', i_unknown=inds) + + import matplotlib.pyplot as plt + #inds = np.atleast_2d(inds).reshape((-1,1)) + plt.plot(x[:, 1], 'k.', label='observed values') + plt.plot(inds, mu1o, '*', label='mu1o') + plt.plot(inds, sample.ravel(), 'r+', label='samples') + plt.plot(inds, mu1o - 2 * mu1o_std, 'r', + inds, mu1o + 2 * mu1o_std, 'r', label='2 stdev') + plt.legend() + plt.show('hold') + + +if __name__ == '__main__': + if False: # True: # + import doctest + doctest.testmod() + else: + main() diff --git a/pywafo/src/wafo/dctpack.py b/pywafo/src/wafo/dctpack.py index bfaa0b7..736c0cc 100644 --- a/pywafo/src/wafo/dctpack.py +++ b/pywafo/src/wafo/dctpack.py @@ -3,10 +3,11 @@ from scipy.fftpack import dct as _dct from scipy.fftpack import idct as _idct __all__ = ['dct', 'idct', 'dctn', 'idctn'] -def dct(x, type=2, n=None, axis=-1, norm='ortho'): #@ReservedAssignment + +def dct(x, type=2, n=None, axis=-1, norm='ortho'): # @ReservedAssignment ''' Return the Discrete Cosine Transform of arbitrary type sequence x. - + Parameters ---------- x : array_like @@ -19,90 +20,93 @@ def dct(x, type=2, n=None, axis=-1, norm='ortho'): #@ReservedAssignment Axis over which to compute the transform. norm : {None, 'ortho'}, optional Normalization mode (see Notes). Default is 'ortho'. - + Returns ------- y : ndarray of real The transformed input array. - + See Also -------- idct - + Notes ----- For a single dimension array ``x``, ``dct(x, norm='ortho')`` is equal to MATLAB ``dct(x)``. - + There are theoretically 8 types of the DCT, only the first 3 types are implemented in scipy. 'The' DCT generally refers to DCT type 2, and 'the' Inverse DCT generally refers to DCT type 3. - + type I ~~~~~~ There are several definitions of the DCT-I; we use the following (for ``norm=None``):: - + N-2 y[k] = x[0] + (-1)**k x[N-1] + 2 * sum x[n]*cos(pi*k*n/(N-1)) n=1 - + Only None is supported as normalization mode for DCT-I. Note also that the DCT-I is only supported for input size > 1 - + type II ~~~~~~~ There are several definitions of the DCT-II; we use the following (for ``norm=None``):: - - + + N-1 y[k] = 2* sum x[n]*cos(pi*k*(2n+1)/(2*N)), 0 <= k < N. n=0 - + If ``norm='ortho'``, ``y[k]`` is multiplied by a scaling factor `f`:: - + f = sqrt(1/(4*N)) if k = 0, f = sqrt(1/(2*N)) otherwise. - + Which makes the corresponding matrix of coefficients orthonormal (``OO' = Id``). - + type III ~~~~~~~~ - + There are several definitions, we use the following (for ``norm=None``):: - + N-1 y[k] = x[0] + 2 * sum x[n]*cos(pi*(k+0.5)*n/N), 0 <= k < N. n=1 - + or, for ``norm='ortho'`` and 0 <= k < N:: - + N-1 y[k] = x[0] / sqrt(N) + sqrt(1/N) * sum x[n]*cos(pi*(k+0.5)*n/N) n=1 - + The (unnormalized) DCT-III is the inverse of the (unnormalized) DCT-II, up to a factor `2N`. The orthonormalized DCT-III is exactly the inverse of the orthonormalized DCT-II. - + References ---------- - + http://en.wikipedia.org/wiki/Discrete_cosine_transform - + 'A Fast Cosine Transform in One and Two Dimensions', by J. Makhoul, `IEEE Transactions on acoustics, speech and signal processing` vol. 28(1), pp. 27-34, http://dx.doi.org/10.1109/TASSP.1980.1163351 (1980). ''' farr = np.asfarray if np.iscomplex(x).any(): - return _dct(farr(x.real), type, n, axis, norm) + 1j*_dct(farr(x.imag), type, n, axis, norm) + return _dct(farr(x.real), type, n, axis, norm) + \ + 1j * _dct(farr(x.imag), type, n, axis, norm) else: return _dct(farr(x), type, n, axis, norm) -def idct(x, type=2, n=None, axis=-1, norm='ortho'): #@ReservedAssignment + + +def idct(x, type=2, n=None, axis=-1, norm='ortho'): # @ReservedAssignment ''' Return the Inverse Discrete Cosine Transform of an arbitrary type sequence. @@ -118,136 +122,142 @@ def idct(x, type=2, n=None, axis=-1, norm='ortho'): #@ReservedAssignment Axis over which to compute the transform. norm : {None, 'ortho'}, optional Normalization mode (see Notes). Default is 'ortho'. - + Returns ------- y : ndarray of real The transformed input array. - + See Also -------- dct - + Notes ----- For a single dimension array `x`, ``idct(x, norm='ortho')`` is equal to matlab ``idct(x)``. - + 'The' IDCT is the IDCT of type 2, which is the same as DCT of type 3. - + IDCT of type 1 is the DCT of type 1, IDCT of type 2 is the DCT of type 3, and IDCT of type 3 is the DCT of type 2. For the definition of these types, see `dct`. ''' farr = np.asarray if np.iscomplex(x).any(): - return _idct(farr(x.real), type, n, axis, norm) + 1j*_idct(farr(x.imag), type, n, axis, norm) + return _idct(farr(x.real), type, n, axis, norm) + \ + 1j * _idct(farr(x.imag), type, n, axis, norm) else: return _idct(farr(x), type, n, axis, norm) -def dctn(x, type=2, axis=None, norm='ortho'): #@ReservedAssignment + + +def dctn(x, type=2, axis=None, norm='ortho'): # @ReservedAssignment ''' DCTN N-D discrete cosine transform. - + Y = DCTN(X) returns the discrete cosine transform of X. The array Y is the same size as X and contains the discrete cosine transform coefficients. This transform can be inverted using IDCTN. - + DCTN(X,axis) applies the DCTN operation across the dimension axis. - + Class Support ------------- Input array can be numeric or logical. The returned array is of class double. - + Reference --------- Narasimha M. et al, On the computation of the discrete cosine transform, IEEE Trans Comm, 26, 6, 1978, pp 934-936. - + Example ------- RGB = imread('autumn.tif'); I = rgb2gray(RGB); J = dctn(I); imshow(log(abs(J)),[]), colormap(jet), colorbar - + The commands below set values less than magnitude 10 in the DCT matrix to zero, then reconstruct the image using the inverse DCT. - + J(abs(J)<10) = 0; K = idctn(J); figure, imshow(I) figure, imshow(K,[0 255]) - + See also -------- idctn, dct, idct ''' - + y = np.atleast_1d(x) - shape0 = y.shape + shape0 = y.shape if axis is None: - y = y.squeeze() # Working across singleton dimensions is useless + y = y.squeeze() # Working across singleton dimensions is useless ndim = y.ndim - isvector = max(shape0)==y.size + isvector = max(shape0) == y.size if isvector: - if ndim==1: + if ndim == 1: y = np.atleast_2d(y) y = y.T - elif y.shape[0]==1: - if axis==0: + elif y.shape[0] == 1: + if axis == 0: return x - elif axis==1: - axis=0 + elif axis == 1: + axis = 0 y = y.T - elif axis==1: + elif axis == 1: return y - + if np.iscomplex(y).any(): - y = dctn(y.real, type, axis, norm) + 1j*dctn(y.imag, type, axis, norm) + y = dctn(y.real, type, axis, norm) + 1j * \ + dctn(y.imag, type, axis, norm) else: y = np.asfarray(y) for dim in range(ndim): y = y.transpose(np.roll(range(y.ndim), -1)) #y = shiftdim(y,1) - if axis is not None and dim!=axis: + if axis is not None and dim != axis: continue y = _dct(y, type, norm=norm) return y.reshape(shape0) - -def idctn(x, type=2, axis=None, norm='ortho'): #@ReservedAssignment + + +def idctn(x, type=2, axis=None, norm='ortho'): # @ReservedAssignment y = np.atleast_1d(x) - shape0 = y.shape + shape0 = y.shape if axis is None: - y = y.squeeze() # Working across singleton dimensions is useless + y = y.squeeze() # Working across singleton dimensions is useless ndim = y.ndim - isvector = max(shape0)==y.size + isvector = max(shape0) == y.size if isvector: - if ndim==1: + if ndim == 1: y = np.atleast_2d(y) y = y.T - elif y.shape[0]==1: - if axis==0: + elif y.shape[0] == 1: + if axis == 0: return x - elif axis==1: - axis=0 + elif axis == 1: + axis = 0 y = y.T - elif axis==1: + elif axis == 1: return y - + if np.iscomplex(y).any(): - y = idctn(y.real, type, axis, norm) + 1j*idctn(y.imag, type, axis, norm) + y = idctn(y.real, type, axis, norm) + 1j * \ + idctn(y.imag, type, axis, norm) else: y = np.asfarray(y) for dim in range(ndim): - y = y.transpose(np.roll(range(y.ndim), -1)) + y = y.transpose(np.roll(range(y.ndim), -1)) #y = shiftdim(y,1) - if axis is not None and dim!=axis: + if axis is not None and dim != axis: continue - y = _idct(y, type, norm=norm) + y = _idct(y, type, norm=norm) return y.reshape(shape0) - -#def dct(x, n=None): + +# def dct(x, n=None): # """ # Discrete Cosine Transform # @@ -297,7 +307,7 @@ def idctn(x, type=2, axis=None, norm='ortho'): #@ReservedAssignment # else: # return y # -#def idct(x, n=None): +# def idct(x, n=None): # """ # Inverse Discrete Cosine Transform # @@ -345,7 +355,8 @@ def idctn(x, type=2, axis=None, norm='ortho'): #@ReservedAssignment # y[..., ::2] = yp[..., :n / 2] # y[..., ::-2] = yp[..., n / 2::] # else: -# yp = ifft(np.hstack((xx, np.zeros_like(xx[..., 0]), np.conj(xx[..., :0:-1])))) +# yp = ifft(np.hstack((xx, np.zeros_like(xx[..., 0]), +# np.conj(xx[..., :0:-1])))) # y = yp[..., :n] # # if real_x: @@ -353,41 +364,41 @@ def idctn(x, type=2, axis=None, norm='ortho'): #@ReservedAssignment # else: # return y # -#def dctn(y, axis=None, w=None): +# def dctn(y, axis=None, w=None): # ''' # DCTN N-D discrete cosine transform. -# +# # Y = DCTN(X) returns the discrete cosine transform of X. The array Y is # the same size as X and contains the discrete cosine transform # coefficients. This transform can be inverted using IDCTN. -# +# # DCTN(X,axis) applies the DCTN operation across the dimension axis. -# +# # Class Support # ------------- # Input array can be numeric or logical. The returned array is of class # double. -# +# # Reference # --------- # Narasimha M. et al, On the computation of the discrete cosine # transform, IEEE Trans Comm, 26, 6, 1978, pp 934-936. -# +# # Example # ------- # RGB = imread('autumn.tif'); # I = rgb2gray(RGB); # J = dctn(I); # imshow(log(abs(J)),[]), colormap(jet), colorbar -# +# # The commands below set values less than magnitude 10 in the DCT matrix # to zero, then reconstruct the image using the inverse DCT. -# +# # J(abs(J)<10) = 0; # K = idctn(J); # figure, imshow(I) # figure, imshow(K,[0 255]) -# +# # See also # -------- # idctn, dct, idct @@ -395,24 +406,24 @@ def idctn(x, type=2, axis=None, norm='ortho'): #@ReservedAssignment # # y = np.atleast_1d(y) # shape0 = y.shape -# -# +# +# # if axis is None: -# y = y.squeeze() # Working across singleton dimensions is useless +# y = y.squeeze() # Working across singleton dimensions is useless # dimy = y.ndim # if dimy==1: # y = np.atleast_2d(y) # y = y.T -# # Some modifications are required if Y is a vector -## if isvector(y): -## if y.shape[0]==1: -## if axis==0: -## return y, None -## elif axis==1: -## axis=0 +# Some modifications are required if Y is a vector +# if isvector(y): +# if y.shape[0]==1: +# if axis==0: +# return y, None +# elif axis==1: +# axis=0 ## y = y.T -## elif axis==1: -## return y, None +# elif axis==1: +# return y, None # # if w is None: # w = [0,] * dimy @@ -420,19 +431,19 @@ def idctn(x, type=2, axis=None, norm='ortho'): #@ReservedAssignment # if axis is not None and dim!=axis: # continue # n = (dimy==1)*y.size + (dimy>1)*shape0[dim] -# #w{dim} = exp(1i*(0:n-1)'*pi/2/n); +# w{dim} = exp(1i*(0:n-1)'*pi/2/n); # w[dim] = np.exp(1j * np.arange(n) * np.pi / (2 * n)) -# -# # --- DCT algorithm --- +# +# --- DCT algorithm --- # if np.iscomplex(y).any(): # y = dctn(np.real(y),axis,w) + 1j*dctn(np.imag(y),axis,w) # else: # for dim in range(dimy): # y = shiftdim(y,1) # if axis is not None and dim!=axis: -# #y = shiftdim(y, 1) +# y = shiftdim(y, 1) # continue -# siz = y.shape +# siz = y.shape # n = siz[-1] # y = y[...,np.r_[0:n:2, 2*int(n//2)-1:0:-2]] # y = y.reshape((-1,n)) @@ -440,53 +451,53 @@ def idctn(x, type=2, axis=None, norm='ortho'): #@ReservedAssignment # y = (np.fft.ifft(y, n=n, axis=1) * w[dim]).real # y[:,0] = y[:,0]/np.sqrt(2) # y = y.reshape(siz) -# -# #end -# #end -# +# +# end +# end +# # return y.reshape(shape0), w # -#def idctn(y, axis=None, w=None): +# def idctn(y, axis=None, w=None): # ''' # IDCTN N-D inverse discrete cosine transform. # X = IDCTN(Y) inverts the N-D DCT transform, returning the original # array if Y was obtained using Y = DCTN(X). -# +# # IDCTN(X,DIM) applies the IDCTN operation across the dimension DIM. -# +# # Class Support # ------------- # Input array can be numeric or logical. The returned array is of class # double. -# +# # Reference # --------- # Narasimha M. et al, On the computation of the discrete cosine # transform, IEEE Trans Comm, 26, 6, 1978, pp 934-936. -# +# # Example # ------- # RGB = imread('autumn.tif'); # I = rgb2gray(RGB); # J = dctn(I); # imshow(log(abs(J)),[]), colormap(jet), colorbar -# +# # The commands below set values less than magnitude 10 in the DCT matrix # to zero, then reconstruct the image using the inverse DCT. -# +# # J(abs(J)<10) = 0; # K = idctn(J); # figure, imshow(I) # figure, imshow(K,[0 255]) -# -# See also +# +# See also # -------- -# dctn, idct, dct -# +# dctn, idct, dct +# # -- Damien Garcia -- 2009/04, revised 2009/11 # website: www.BiomeCardio.com -# +# # ---------- # [Y,W] = IDCTN(X,DIM,W) uses and returns the weights which are used by # the program. If IDCTN is required for several large arrays of same @@ -502,65 +513,64 @@ def idctn(x, type=2, axis=None, norm='ortho'): #@ReservedAssignment # # y = np.atleast_1d(y) # shape0 = y.shape -# +# # if axis is None: -# y = y.squeeze() # Working across singleton dimensions is useless -# +# y = y.squeeze() # Working across singleton dimensions is useless +# # dimy = y.ndim # if dimy==1: # y = np.atleast_2d(y) # y = y.T -# # Some modifications are required if Y is a vector -## if isvector(y): -## if y.shape[0]==1: -## if axis==0: -## return y, None -## elif axis==1: -## axis=0 +# Some modifications are required if Y is a vector +# if isvector(y): +# if y.shape[0]==1: +# if axis==0: +# return y, None +# elif axis==1: +# axis=0 ## y = y.T -## elif axis==1: -## return y, None -## -# -# +# elif axis==1: +# return y, None +## +# +# # if w is None: # w = [0,] * dimy # for dim in range(dimy): # if axis is not None and dim!=axis: # continue # n = (dimy==1)*y.size + (dimy>1)*shape0[dim] -# #w{dim} = exp(1i*(0:n-1)'*pi/2/n); +# w{dim} = exp(1i*(0:n-1)'*pi/2/n); # w[dim] = np.exp(1j * np.arange(n) * np.pi / (2 * n)) -# # --- IDCT algorithm --- +# --- IDCT algorithm --- # if np.iscomplex(y).any(): # y = np.complex(idctn(np.real(y),axis,w),idctn(np.imag(y),axis,w)) # else: # for dim in range(dimy): # y = shiftdim(y,1) # if axis is not None and dim!=axis: -# #y = shiftdim(y, 1) +# y = shiftdim(y, 1) # continue -# siz = y.shape +# siz = y.shape # n = siz[-1] -# +# # y = y.reshape((-1,n)) * w[dim] # y[:,0] = y[:,0]/np.sqrt(2) # y = (np.fft.ifft(y, n=n, axis=1)).real # y = y * np.sqrt(2*n) -# +# # I = np.empty(n,dtype=int) # I.put(np.r_[0:n:2],np.r_[0:int(n//2)+np.remainder(n,2)]) # I.put(np.r_[1:n:2],np.r_[n-1:int(n//2)-1:-1]) # y = y[:,I] -# +# # y = y.reshape(siz) -# -# +# +# # y = y.reshape(shape0); # return y, w - def no_leading_ones(x): first = 0 for i, xi in enumerate(x): @@ -568,43 +578,45 @@ def no_leading_ones(x): first = i break return x[first:] - -def shiftdim(x, n=None): + + +def shiftdim(x, n=None): ''' Shift dimensions - + Parameters ---------- x : array n : int - + Notes ----- - Shiftdim is handy for functions that intend to work along the first + Shiftdim is handy for functions that intend to work along the first non-singleton dimension of the array. - - If n is None returns the array with the same number of elements as X but + + If n is None returns the array with the same number of elements as X but with any leading singleton dimensions removed. - - When n is positive, shiftdim shifts the dimensions to the left and wraps - the n leading dimensions to the end. - - When n is negative, shiftdim shifts the dimensions to the right and pads - with singletons. - + + When n is positive, shiftdim shifts the dimensions to the left and wraps + the n leading dimensions to the end. + + When n is negative, shiftdim shifts the dimensions to the right and pads + with singletons. + See also -------- reshape, squeeze ''' - if n is None: - return x.reshape(no_leading_ones(x.shape)) - elif n>=0: - return x.transpose(np.roll(range(x.ndim), -n)) - else: - return x.reshape((1,)*-n+x.shape) + if n is None: + return x.reshape(no_leading_ones(x.shape)) + elif n >= 0: + return x.transpose(np.roll(range(x.ndim), -n)) + else: + return x.reshape((1,) * -n + x.shape) + def test_dctn(): - a = np.arange(12) #.reshape((3,-1)) + a = np.arange(12) # .reshape((3,-1)) print('a = ', a) print(' ') y = dct(a) @@ -614,7 +626,7 @@ def test_dctn(): print('x = idct(y)') print(x) print(' ') - + # y1 = dct1(a) # x1 = idct1(y) # print('y1 = dct1(a)') @@ -622,7 +634,7 @@ def test_dctn(): # print('x1 = idct1(y)') # print(x1) # print(' ') - + yn = dctn(a) xn = idctn(yn) print('yn = dctn(a)') @@ -630,20 +642,21 @@ def test_dctn(): print('xn = idctn(yn)') print(xn) print(' ') - + # yn1 = dctn1(a) # xn1 = idctn1(yn1) # print('yn1 = dctn1(a)') # print(yn1) # print('xn1 = idctn1(yn)') # print(xn1) - - - + + def test_docstrings(): import doctest - doctest.testmod() - + print('Testing docstrings in %s' % __file__) + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) + + if __name__ == '__main__': test_docstrings() - #test_dctn() \ No newline at end of file + # test_dctn() diff --git a/pywafo/src/wafo/definitions.py b/pywafo/src/wafo/definitions.py index 5e3d074..52e2993 100644 --- a/pywafo/src/wafo/definitions.py +++ b/pywafo/src/wafo/definitions.py @@ -1,296 +1,305 @@ -""" -WAFO defintions and numenclature - - crossings : - cycle_pairs : - turning_points : - wave_amplitudes : - wave_periods : - waves : - -Examples --------- -In order to view the documentation do the following in an ipython window: - ->>> import wafo.definitions as wd ->>> wd.crossings() - -or ->>> wd.crossings? - - -""" -def wave_amplitudes(): - r""" - Wave amplitudes and heights definitions and nomenclature - - Definition of wave amplitudes and wave heights - --------------------------------------------- - - <----- Direction of wave propagation - - - |..............c_..........| - | /| \ | - Hd | _/ | \ | Hu - M | / | \ | - / \ | M / Ac | \_ | c_ - F \ | / \m/ | \ | / \ - ------d----|---u------------------d---|---u----d------ level v - \ | /| \ | / \L - \_ | / | At \_|_/ - \|/..| t - t - - Parameters - ---------- - Ac : crest amplitude - At : trough amplitude - Hd : wave height as defined for down crossing waves - Hu : wave height as defined for up crossing waves - - See also - -------- - waves, crossings, turning_points - """ - print(wave_amplitudes.__doc__) - -def crossings(): - r""" - Level v crossing definitions and nomenclature - - Definition of level v crossings - ------------------------------- - M - . . M M - . . . . . . - F d . . L - -----------------------u-------d-------o----------------- level v - . . . . u - . m - m - - Let the letters 'm', 'M', 'F', 'L','d' and 'u' in the - figure above denote local minimum, maximum, first value, last - value, down- and up-crossing, respectively. The remaining - sampled values are indicated with a '.'. Values that are identical - with v, but do not cross the level is indicated with the letter 'o'. - We have a level up-crossing at index, k, if - - x(k) < v and v < x(k+1) - or if - x(k) == v and v < x(k+1) and x(r) < v for some di < r <= k-1 - - where di is the index to the previous downcrossing. - Similarly there is a level down-crossing at index, k, if - - x(k) > v and v > x(k+1) - or if - x(k) == v and v > x(k+1) and x(r) > v for some ui < r <= k-1 - - where ui is the index to the previous upcrossing. - - The first (F) value is a up crossing if x(1) = v and x(2) > v. - Similarly, it is a down crossing if x(1) = v and x(2) < v. - - See also - -------- - wave_periods, waves, turning_points, findcross, findtp - """ - print(crossings.__doc__) - -def cycle_pairs(): - r""" - Cycle pairs definitions and numenclature - - Definition of Max2min and min2Max cycle pair - -------------------------------------------- - A min2Max cycle pair (mM) is defined as the pair of a minimum - and the following Maximum. Similarly a Max2min cycle pair (Mm) - is defined as the pair of a Maximum and the following minimum. - (all turning points possibly rainflowfiltered before pairing into cycles.) - - See also - -------- - turning_points - """ - print(cycle_pairs.__doc__) - -def wave_periods(): - r""" - Wave periods (lengths) definitions and nomenclature - - Definition of wave periods (lengths) - ------------------------------------ - - - <----- Direction of wave propagation - - <-------Tu---------> - : : - <---Tc-----> : - : : : <------Tcc----> - M : c : : : : - / \ : M / \_ : : c_ c - F \ :/ \m/ \: :/ \ / \ - ------d--------u----------d-------u----d--------u---d-------- level v - \ / \ / :\_ _/: :\_ L - \_ / \_t_/ : \t_/ : : \m/ - \t/ : : : : - : : <---Tt---> : - <--------Ttt-------> : : - <-----Td-----> - Tu = Up crossing period - Td = Down crossing period - Tc = Crest period, i.e., period between up crossing and - the next down crossing - Tt = Trough period, i.e., period between down crossing and - the next up crossing - Ttt = Trough2trough period - Tcc = Crest2crest period - - - <----- Direction of wave propagation - - <--Tcf-> Tuc - : : <-Tcb-> <-> - M : c : : : : - / \ : M / \_ c_ : : c - F \ :/ \m/ \ / \___: :/ \ - ------d---------u----------d---------u-------d--------u---d------ level v - :\_ / \ __/: \_ _/ \_ L - : \_ / \_t_/ : \t_/ \m/ - : \t/ : : - : : : : - <-Ttf-> <-Ttb-> - - - Tcf = Crest front period, i.e., period between up crossing and crest - Tcb = Crest back period, i.e., period between crest and down crossing - Ttf = Trough front period, i.e., period between down crossing and trough - Ttb = Trough back period, i.e., period between trough and up crossing - Also note that Tcf and Ttf can also be abbreviated by their crossing - marker, e.g. Tuc (u2c) and Tdt (d2t), respectively. Similar applies - to all the other wave periods and wave lengths. - - (The nomenclature for wave length is similar, just substitute T and - period with L and length, respectively) - - <----- Direction of wave propagation - - <--TMm--> - <-TmM-> : : - M : : M : - / \ : M /:\_ : M_ M - F \ : / \m/ : \ : /: \ / \ - \ : / : \ : / : \ / \ - \ : / : \ : / : \_ _/ \_ L - \_ : / : \_m_/ : \m_/ \m/ - \m/ : : : : - <-----TMM-----> <----Tmm-----> - - - TmM = Period between minimum and the following Maximum - TMm = Period between Maximum and the following minimum - TMM = Period between Maximum and the following Maximum - Tmm = Period between minimum and the following minimum - - See also - -------- - waves, - wave_amplitudes, - crossings, - turning_points - """ - print(wave_periods.__doc__) -def turning_points(): - r""" - Turning points definitions and numenclature - - Definition of turningpoints - --------------------------- - <----- Direction of wave propagation - - M M - / \ .... M /:\_ M_ M - F \ | / \m/ : \ /: \ / \ - \ h | / : \ / : \ / \ - \ | / : \ / : \_ _/ \_ L - \_ | / : \_m_/ : \m_/ \m/ - \m/ : : : : - <------Mw-----> <-----mw-----> - - Local minimum or maximum are indicated with the - letters 'm' or 'M'. Turning points in this connection are all - local max (M) and min (m) and the last (L) value and the - first (F) value if the first local extremum is a max. - - (This choice is made in order to get the exact up-crossing intensity - from rfc by mm2lc(tp2mm(rfc)) ) - - - See also - -------- - waves, - crossings, - cycle_pairs - findtp - - """ - print(turning_points.__doc__) -def waves(): - r""" - Wave definitions and nomenclature - - Definition of trough and crest - ------------------------------ - A trough (t) is defined as the global minimum between a - level v down-crossing (d) and the next up-crossing (u) - and a crest (c) is defined as the global maximum between a - level v up-crossing and the following down-crossing. - - Definition of down- and up -crossing waves - ------------------------------------------ - A level v-down-crossing wave (dw) is a wave from a - down-crossing to the following down-crossing. - Similarly, a level v-up-crossing wave (uw) is a wave from an up-crossing - to the next up-crossing. - - Definition of trough and crest waves - ------------------------------------ - A trough-to-trough wave (tw) is a wave from a trough (t) to the - following trough. The crest-to-crest wave (cw) is defined similarly. - - - Definition of min2min and Max2Max wave - -------------------------------------- - A min2min wave (mw) is defined starting from a minimum (m) and - ending in the following minimum. - Similarly a Max2Max wave (Mw) is thus a wave from a maximum (M) - to the next maximum (all waves optionally rainflow filtered). - - <----- Direction of wave propagation - - - <------Mw-----> <----mw----> - M : : c : - / \ M : / \_ : c_ c - F \ / \m/ \ : /: \ /:\ - ------d--------u----------d-------u----d--------u---d------ level v - \ /: \ : /: : :\_ _/ : :\_ L - \_ / : \_t_/ : : : \t_/ : : \m/ - \t/ <-------uw---------> : <-----dw-----> - : : : : - <--------tw--------> <------cw-----> - - (F=first value and L=last value). - - See also - -------- - turning_points, - crossings, - wave_periods - findtc, - findcross - """ - print(waves.__doc__) \ No newline at end of file +""" +WAFO defintions and numenclature + + crossings : + cycle_pairs : + turning_points : + wave_amplitudes : + wave_periods : + waves : + +Examples +-------- +In order to view the documentation do the following in an ipython window: + +>>> import wafo.definitions as wd +>>> wd.crossings() + +or +>>> wd.crossings? + + +""" + + +def wave_amplitudes(): + r""" + Wave amplitudes and heights definitions and nomenclature + + Definition of wave amplitudes and wave heights + --------------------------------------------- + + <----- Direction of wave propagation + + + |..............c_..........| + | /| \ | + Hd | _/ | \ | Hu + M | / | \ | + / \ | M / Ac | \_ | c_ + F \ | / \m/ | \ | / \ + ------d----|---u------------------d---|---u----d------ level v + \ | /| \ | / \L + \_ | / | At \_|_/ + \|/..| t + t + + Parameters + ---------- + Ac : crest amplitude + At : trough amplitude + Hd : wave height as defined for down crossing waves + Hu : wave height as defined for up crossing waves + + See also + -------- + waves, crossings, turning_points + """ + print(wave_amplitudes.__doc__) + + +def crossings(): + r""" + Level v crossing definitions and nomenclature + + Definition of level v crossings + ------------------------------- + M + . . M M + . . . . . . + F d . . L + -----------------------u-------d-------o----------------- level v + . . . . u + . m + m + + Let the letters 'm', 'M', 'F', 'L','d' and 'u' in the + figure above denote local minimum, maximum, first value, last + value, down- and up-crossing, respectively. The remaining + sampled values are indicated with a '.'. Values that are identical + with v, but do not cross the level is indicated with the letter 'o'. + We have a level up-crossing at index, k, if + + x(k) < v and v < x(k+1) + or if + x(k) == v and v < x(k+1) and x(r) < v for some di < r <= k-1 + + where di is the index to the previous downcrossing. + Similarly there is a level down-crossing at index, k, if + + x(k) > v and v > x(k+1) + or if + x(k) == v and v > x(k+1) and x(r) > v for some ui < r <= k-1 + + where ui is the index to the previous upcrossing. + + The first (F) value is a up crossing if x(1) = v and x(2) > v. + Similarly, it is a down crossing if x(1) = v and x(2) < v. + + See also + -------- + wave_periods, waves, turning_points, findcross, findtp + """ + print(crossings.__doc__) + + +def cycle_pairs(): + r""" + Cycle pairs definitions and numenclature + + Definition of Max2min and min2Max cycle pair + -------------------------------------------- + A min2Max cycle pair (mM) is defined as the pair of a minimum + and the following Maximum. Similarly a Max2min cycle pair (Mm) + is defined as the pair of a Maximum and the following minimum. + (all turning points possibly rainflowfiltered before pairing into cycles.) + + See also + -------- + turning_points + """ + print(cycle_pairs.__doc__) + + +def wave_periods(): + r""" + Wave periods (lengths) definitions and nomenclature + + Definition of wave periods (lengths) + ------------------------------------ + + + <----- Direction of wave propagation + + <-------Tu---------> + : : + <---Tc-----> : + : : : <------Tcc----> + M : c : : : : + / \ : M / \_ : : c_ c + F \ :/ \m/ \: :/ \ / \ + ------d--------u----------d-------u----d--------u---d-------- level v + \ / \ / :\_ _/: :\_ L + \_ / \_t_/ : \t_/ : : \m/ + \t/ : : : : + : : <---Tt---> : + <--------Ttt-------> : : + <-----Td-----> + Tu = Up crossing period + Td = Down crossing period + Tc = Crest period, i.e., period between up crossing and + the next down crossing + Tt = Trough period, i.e., period between down crossing and + the next up crossing + Ttt = Trough2trough period + Tcc = Crest2crest period + + + <----- Direction of wave propagation + + <--Tcf-> Tuc + : : <-Tcb-> <-> + M : c : : : : + / \ : M / \_ c_ : : c + F \ :/ \m/ \ / \___: :/ \ + ------d---------u----------d---------u-------d--------u---d------ level v + :\_ / \ __/: \_ _/ \_ L + : \_ / \_t_/ : \t_/ \m/ + : \t/ : : + : : : : + <-Ttf-> <-Ttb-> + + + Tcf = Crest front period, i.e., period between up crossing and crest + Tcb = Crest back period, i.e., period between crest and down crossing + Ttf = Trough front period, i.e., period between down crossing and trough + Ttb = Trough back period, i.e., period between trough and up crossing + Also note that Tcf and Ttf can also be abbreviated by their crossing + marker, e.g. Tuc (u2c) and Tdt (d2t), respectively. Similar applies + to all the other wave periods and wave lengths. + + (The nomenclature for wave length is similar, just substitute T and + period with L and length, respectively) + + <----- Direction of wave propagation + + <--TMm--> + <-TmM-> : : + M : : M : + / \ : M /:\_ : M_ M + F \ : / \m/ : \ : /: \ / \ + \ : / : \ : / : \ / \ + \ : / : \ : / : \_ _/ \_ L + \_ : / : \_m_/ : \m_/ \m/ + \m/ : : : : + <-----TMM-----> <----Tmm-----> + + + TmM = Period between minimum and the following Maximum + TMm = Period between Maximum and the following minimum + TMM = Period between Maximum and the following Maximum + Tmm = Period between minimum and the following minimum + + See also + -------- + waves, + wave_amplitudes, + crossings, + turning_points + """ + print(wave_periods.__doc__) + + +def turning_points(): + r""" + Turning points definitions and numenclature + + Definition of turningpoints + --------------------------- + <----- Direction of wave propagation + + M M + / \ .... M /:\_ M_ M + F \ | / \m/ : \ /: \ / \ + \ h | / : \ / : \ / \ + \ | / : \ / : \_ _/ \_ L + \_ | / : \_m_/ : \m_/ \m/ + \m/ : : : : + <------Mw-----> <-----mw-----> + + Local minimum or maximum are indicated with the + letters 'm' or 'M'. Turning points in this connection are all + local max (M) and min (m) and the last (L) value and the + first (F) value if the first local extremum is a max. + + (This choice is made in order to get the exact up-crossing intensity + from rfc by mm2lc(tp2mm(rfc)) ) + + + See also + -------- + waves, + crossings, + cycle_pairs + findtp + + """ + print(turning_points.__doc__) + + +def waves(): + r""" + Wave definitions and nomenclature + + Definition of trough and crest + ------------------------------ + A trough (t) is defined as the global minimum between a + level v down-crossing (d) and the next up-crossing (u) + and a crest (c) is defined as the global maximum between a + level v up-crossing and the following down-crossing. + + Definition of down- and up -crossing waves + ------------------------------------------ + A level v-down-crossing wave (dw) is a wave from a + down-crossing to the following down-crossing. + Similarly, a level v-up-crossing wave (uw) is a wave from an up-crossing + to the next up-crossing. + + Definition of trough and crest waves + ------------------------------------ + A trough-to-trough wave (tw) is a wave from a trough (t) to the + following trough. The crest-to-crest wave (cw) is defined similarly. + + + Definition of min2min and Max2Max wave + -------------------------------------- + A min2min wave (mw) is defined starting from a minimum (m) and + ending in the following minimum. + Similarly a Max2Max wave (Mw) is thus a wave from a maximum (M) + to the next maximum (all waves optionally rainflow filtered). + + <----- Direction of wave propagation + + + <------Mw-----> <----mw----> + M : : c : + / \ M : / \_ : c_ c + F \ / \m/ \ : /: \ /:\ + ------d--------u----------d-------u----d--------u---d------ level v + \ /: \ : /: : :\_ _/ : :\_ L + \_ / : \_t_/ : : : \t_/ : : \m/ + \t/ <-------uw---------> : <-----dw-----> + : : : : + <--------tw--------> <------cw-----> + + (F=first value and L=last value). + + See also + -------- + turning_points, + crossings, + wave_periods + findtc, + findcross + """ + print(waves.__doc__) diff --git a/pywafo/src/wafo/demo_sg.py b/pywafo/src/wafo/demo_sg.py index 1f70688..ce15cb1 100644 --- a/pywafo/src/wafo/demo_sg.py +++ b/pywafo/src/wafo/demo_sg.py @@ -1,17 +1,18 @@ -from pylab import subplot, plot, title, savefig, figure, arange, sin, random #@UnresolvedImport +# @UnresolvedImport +from pylab import subplot, plot, title, savefig, figure, arange, sin, random from sg_filter import calc_coeff, smooth -figure(figsize=(7,12)) +figure(figsize=(7, 12)) # generate chirp signal tvec = arange(0, 6.28, .02) -signal = sin(tvec*(2.0+tvec)) +signal = sin(tvec * (2.0 + tvec)) # add noise to signal noise = random.normal(size=signal.shape) -signal += (2000.+.15 * noise) +signal += (2000. + .15 * noise) # plot signal subplot(311) @@ -21,7 +22,7 @@ title('signal') # smooth and plot signal subplot(312) coeff = calc_coeff(8, 4) -s_signal = smooth(signal, coeff) +s_signal = smooth(signal, coeff) plot(s_signal) title('smoothed signal') @@ -36,8 +37,3 @@ title('smoothed derivative of signal') # show plot savefig("savitzky.png") - - - - - diff --git a/pywafo/src/wafo/demos.py b/pywafo/src/wafo/demos.py index b2406c7..417dbdb 100644 --- a/pywafo/src/wafo/demos.py +++ b/pywafo/src/wafo/demos.py @@ -1,132 +1,138 @@ -''' -Created on 20. jan. 2011 - -@author: pab -''' -import numpy as np -from numpy import exp -from wafo.misc import meshgrid -__all__ = ['peaks', 'humps', 'magic'] - -def magic(n): - ''' - Return magic square for n of any orders > 2. - - A magic square has the property that the sum of every row and column, - as well as both diagonals, is the same number. - - Examples - -------- - >>> magic(3) - array([[8, 1, 6], - [3, 5, 7], - [4, 9, 2]]) - - >>> magic(4) - array([[16, 2, 3, 13], - [ 5, 11, 10, 8], - [ 9, 7, 6, 12], - [ 4, 14, 15, 1]]) - - >>> magic(6) - array([[35, 1, 6, 26, 19, 24], - [ 3, 32, 7, 21, 23, 25], - [31, 9, 2, 22, 27, 20], - [ 8, 28, 33, 17, 10, 15], - [30, 5, 34, 12, 14, 16], - [ 4, 36, 29, 13, 18, 11]]) - ''' - if (n<3): - raise ValueError('n must be greater than 2.') - - if np.mod(n,2)==1: # odd order - ix = np.arange(n) + 1 - J, I = np.meshgrid(ix, ix) - A = np.mod(I + J - (n + 3) / 2, n) - B = np.mod(I + 2 * J - 2, n) - M = n * A + B + 1 - elif np.mod(n,4)==0: # doubly even order - M = np.arange(1,n*n+1).reshape(n,n) - ix = np.mod(np.arange(n) + 1,4)//2 - J, I = np.meshgrid(ix, ix) - iz = np.flatnonzero(I==J) - M.put(iz, n*n+1-M.flat[iz]) - else: # singly even order - p = n//2 - M0 = magic(p) - - M = np.hstack((np.vstack((M0, M0+3*p*p)),np.vstack((M0+2*p*p, M0+p*p)))) - - if n>2: - k = (n-2)//4 - Jvec = np.hstack((np.arange(k), np.arange(n-k+1, n))) - for i in range(p): - for j in Jvec: - temp = M[i][j] - M[i][j]=M[i+p][j] - M[i+p][j] = temp - - i=k - j=0 - temp = M[i][j]; - M[i][j] = M[i+p][j] - M[i+p][j] = temp; - - j=i - temp=M[i+p][j] - M[i+p][j]=M[i][j] - M[i][j]=temp - - return M - -def peaks(x=None, y=None, n=51): - ''' - Return the "well" known MatLab (R) peaks function - evaluated in the [-3,3] x,y range - - Example - ------- - >>> import matplotlib.pyplot as plt - >>> x,y,z = peaks() - - h = plt.contourf(x,y,z) - - ''' - if x is None: - x = np.linspace(-3, 3, n) - if y is None: - y = np.linspace(-3, 3, n) - - [x1, y1] = meshgrid(x, y) - - z = (3 * (1 - x1) ** 2 * exp(-(x1 ** 2) - (y1 + 1) ** 2) - - 10 * (x1 / 5 - x1 ** 3 - y1 ** 5) * exp(-x1 ** 2 - y1 ** 2) - - 1. / 3 * exp(-(x1 + 1) ** 2 - y1 ** 2)) - - return x1, y1, z - -def humps(x=None): - ''' - Computes a function that has three roots, and some humps. - - Example - ------- - >>> import matplotlib.pyplot as plt - >>> x = np.linspace(0,1) - >>> y = humps(x) - - h = plt.plot(x,y) - ''' - if x is None: - y = np.linspace(0, 1) - else: - y = np.asarray(x) - - return 1.0 / ((y - 0.3) ** 2 + 0.01) + 1.0 / ((y - 0.9) ** 2 + 0.04) + 2 * y - 5.2 - -def test_docstrings(): - import doctest - doctest.testmod() - -if __name__ == '__main__': - test_docstrings() +''' +Created on 20. jan. 2011 + +@author: pab +''' +import numpy as np +from numpy import exp, meshgrid +__all__ = ['peaks', 'humps', 'magic'] + + +def magic(n): + ''' + Return magic square for n of any orders > 2. + + A magic square has the property that the sum of every row and column, + as well as both diagonals, is the same number. + + Examples + -------- + >>> magic(3) + array([[8, 1, 6], + [3, 5, 7], + [4, 9, 2]]) + + >>> magic(4) + array([[16, 2, 3, 13], + [ 5, 11, 10, 8], + [ 9, 7, 6, 12], + [ 4, 14, 15, 1]]) + + >>> magic(6) + array([[35, 1, 6, 26, 19, 24], + [ 3, 32, 7, 21, 23, 25], + [31, 9, 2, 22, 27, 20], + [ 8, 28, 33, 17, 10, 15], + [30, 5, 34, 12, 14, 16], + [ 4, 36, 29, 13, 18, 11]]) + ''' + if (n < 3): + raise ValueError('n must be greater than 2.') + + if np.mod(n, 2) == 1: # odd order + ix = np.arange(n) + 1 + J, I = np.meshgrid(ix, ix) + A = np.mod(I + J - (n + 3) / 2, n) + B = np.mod(I + 2 * J - 2, n) + M = n * A + B + 1 + elif np.mod(n, 4) == 0: # doubly even order + M = np.arange(1, n * n + 1).reshape(n, n) + ix = np.mod(np.arange(n) + 1, 4) // 2 + J, I = np.meshgrid(ix, ix) + iz = np.flatnonzero(I == J) + M.put(iz, n * n + 1 - M.flat[iz]) + else: # singly even order + p = n // 2 + M0 = magic(p) + + M = np.hstack((np.vstack((M0, M0 + 3 * p * p)), + np.vstack((M0 + 2 * p * p, M0 + p * p)))) + + if n > 2: + k = (n - 2) // 4 + Jvec = np.hstack((np.arange(k), np.arange(n - k + 1, n))) + for i in range(p): + for j in Jvec: + temp = M[i][j] + M[i][j] = M[i + p][j] + M[i + p][j] = temp + + i = k + j = 0 + temp = M[i][j] + M[i][j] = M[i + p][j] + M[i + p][j] = temp + + j = i + temp = M[i + p][j] + M[i + p][j] = M[i][j] + M[i][j] = temp + + return M + + +def peaks(x=None, y=None, n=51): + ''' + Return the "well" known MatLab (R) peaks function + evaluated in the [-3,3] x,y range + + Example + ------- + >>> import matplotlib.pyplot as plt + >>> x,y,z = peaks() + + h = plt.contourf(x,y,z) + + ''' + if x is None: + x = np.linspace(-3, 3, n) + if y is None: + y = np.linspace(-3, 3, n) + + [x1, y1] = meshgrid(x, y) + + z = (3 * (1 - x1) ** 2 * exp(-(x1 ** 2) - (y1 + 1) ** 2) + - 10 * (x1 / 5 - x1 ** 3 - y1 ** 5) * exp(-x1 ** 2 - y1 ** 2) + - 1. / 3 * exp(-(x1 + 1) ** 2 - y1 ** 2)) + + return x1, y1, z + + +def humps(x=None): + ''' + Computes a function that has three roots, and some humps. + + Example + ------- + >>> import matplotlib.pyplot as plt + >>> x = np.linspace(0,1) + >>> y = humps(x) + + h = plt.plot(x,y) + ''' + if x is None: + y = np.linspace(0, 1) + else: + y = np.asarray(x) + + return 1.0 / ((y - 0.3) ** 2 + 0.01) + 1.0 / ((y - 0.9) ** 2 + 0.04) + \ + 2 * y - 5.2 + + +def test_docstrings(): + import doctest + print('Testing docstrings in %s' % __file__) + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) + +if __name__ == '__main__': + test_docstrings() diff --git a/pywafo/src/wafo/graphutil.py b/pywafo/src/wafo/graphutil.py index 76b963f..e88abd5 100644 --- a/pywafo/src/wafo/graphutil.py +++ b/pywafo/src/wafo/graphutil.py @@ -1,282 +1,267 @@ -''' -Created on 20. jan. 2011 - -@author: pab - -license BSD -''' -from __future__ import division -import warnings -import numpy as np -from wafo.plotbackend import plotbackend -from matplotlib import mlab -__all__ = ['cltext', 'epcolor', 'tallibing', 'test_docstrings'] - -_TALLIBING_GID = 'TALLIBING' -_CLTEXT_GID = 'CLTEXT' - -def _matchfun(x, gidtxt): - if hasattr(x, 'get_gid'): - return x.get_gid() == gidtxt - return False - -def delete_text_object(gidtxt, figure=None, axis=None, verbose=False): - ''' - Delete all text objects matching the gidtxt if it exists - - Parameters - ---------- - gidtxt : string - - figure, axis : objects - current figure and current axis, respectively. - verbose : bool - If true print warnings when trying to delete non-existent objects - ''' - if figure is None: - figure = plotbackend.gcf() - if axis is None: - axis = figure.gca() - lmatchfun = lambda x : _matchfun(x, gidtxt) - objs = axis.findobj(lmatchfun) - for obj in objs: - try: - axis.texts.remove(obj) - except: - if verbose: - warnings.warn('Tried to delete a non-existing %s from axis' % gidtxt) - objs = figure.findobj(lmatchfun) - for obj in objs: - try: - figure.texts.remove(obj) - except: - if verbose: - warnings.warn('Tried to delete a non-existing %s from figure' % gidtxt) - -def cltext(levels, percent=False, n=4, xs=0.036, ys=0.94, zs=0, figure=None, axis=None): - ''' - Places contour level text in the current window - - Parameters - ---------- - levels : vector - contour levels or the corresponding percent which the - contour line encloses - percent : bool - False if levels are the actual contour levels (default) - True if levels are the corresponding percent which the - contour line encloses - n : integer - maximum N digits of precision (default 4) - figure, axis : objects - current figure and current axis, respectively. - default figure = plotbackend.gcf(), - axis = plotbackend.gca() - - Returns - ------- - h = handles to the text objects. - - - Notes - ----- - CLTEXT creates text objects in the current figure and prints - "Level curves at:" if percent is False and - "Level curves enclosing:" otherwise - and the contour levels or percent. - - The handles to the lines of text may also be found by - h = findobj(gcf,'gid','CLTEXT','type','text'); - h = findobj(gca,'gid','CLTEXT','type','text'); - To make the text objects follow the data in the axes set the units - for the text objects 'data' by - set(h,'unit','data') - - Examples - -------- - >>> import wafo.graphutil as wg - >>> import wafo.demos as wd - >>> import pylab as plt - >>> x,y,z = wd.peaks(); - >>> h = plt.contour(x,y,z) - >>> h = wg.cltext(h.levels) - >>> plt.show() - ''' - # TODO : Make it work like legend does (but without the box): include position options etc... - if figure is None: - figure = plotbackend.gcf() - if axis is None: - axis = figure.gca() - - clevels = np.atleast_1d(levels) - - - axpos = axis.get_position() - xint = axpos.intervalx - yint = axpos.intervaly - - xss = xint[0] + xs * (xint[1] - xint[0]) - yss = yint[0] + ys * (yint[1] - yint[0]) - - # delete cltext object if it exists - delete_text_object(_CLTEXT_GID, axis=axis) - - charHeight = 1.0 / 33.0 - delta_y = charHeight - - if percent: - titletxt = 'Level curves enclosing:'; - else: - titletxt = 'Level curves at:'; - - format_ = '%0.' + ('%d' % n) + 'g\n' - - cltxt = ''.join([format_ % level for level in clevels.tolist()]) - - titleProp = dict(gid=_CLTEXT_GID, horizontalalignment='left', - verticalalignment='center', fontweight='bold', axes=axis) # - - ha1 = figure.text(xss, yss, titletxt, **titleProp) - - yss -= delta_y; - txtProp = dict(gid=_CLTEXT_GID, horizontalalignment='left', - verticalalignment='top', axes=axis) - - ha2 = figure.text(xss, yss, cltxt, **txtProp) - plotbackend.draw_if_interactive() - return ha1, ha2 - -def tallibing(x, y, n, **kwds): - ''' - TALLIBING Display numbers on field-plot - - CALL h=tallibing(x,y,n,size,color) - - x,y = position matrices - n = the corresponding matrix of the values to be written - (non-integers are rounded) - size = font size (optional) (default=8) - color = color of text (optional) (default='white') - h = column-vector of handles to TEXT objects - - TALLIBING writes the numbers in a 2D array as text at the positions - given by the x and y coordinate matrices. - When plotting binned results, the number of datapoints in each - bin can be written on the bins in the plot. - - Example - ------- - >>> import wafo.graphutil as wg - >>> import wafo.demos as wd - >>> [x,y,z] = wd.peaks(n=20) - >>> h0 = wg.epcolor(x,y,z) - >>> h1 = wg.tallibing(x,y,z) - - pcolor(x,y,z); shading interp; - - See also - -------- - text - ''' - - axis = kwds.pop('axis',None) - if axis is None: - axis = plotbackend.gca() - - x, y, n = np.atleast_1d(x, y, n) - if mlab.isvector(x) or mlab.isvector(y): - x, y = np.meshgrid(x,y) - - x = x.ravel() - y = y.ravel() - n = n.ravel() - n = np.round(n) - - # delete tallibing object if it exists - delete_text_object(_TALLIBING_GID, axis=axis) - - txtProp = dict(gid=_TALLIBING_GID, size=8, color='w', horizontalalignment='center', - verticalalignment='center', fontweight='demi', axes=axis) - - txtProp.update(**kwds) - h = [] - for xi,yi, ni in zip(x,y,n): - if ni: - h.append(axis.text(xi, yi, str(ni), **txtProp)) - plotbackend.draw_if_interactive() - return h - -def epcolor(*args, **kwds): - ''' - Pseudocolor (checkerboard) plot with mid-bin positioning. - - h = epcolor(x,y,data) - - - [x,y]= the axes corresponding to the data-positions. Vectors or - matrices. If omitted, giving only data-matrix as inargument, the - matrix-indices are used as axes. - data = data-matrix - - EPCOLOR make a checkerboard plot where the data-point-positions are in - the middle of the bins instead of in the corners, and the last column - and row of data are used. - - - Example: - >>> import wafo.demos as wd - >>> import wafo.graphutil as wg - >>> x, y, z = wd.peaks(n=20) - >>> h = wg.epcolor(x,y,z) - - See also - -------- - pylab.pcolor - ''' - axis = kwds.pop('axis',None) - if axis is None: - axis = plotbackend.gca() - midbin = kwds.pop('midbin', True) - if not midbin: - ret = axis.pcolor(*args,**kwds) - plotbackend.draw_if_interactive() - return ret - - nargin = len(args) - data = np.atleast_2d(args[-1]).copy() - M, N = data.shape - if nargin==1: - x = np.arange(N) - y = np.arange(M) - elif nargin==3: - x, y = np.atleast_1d(*args[:-1]) - if min(x.shape)!=1: - x = x[0] - if min(y.shape)!=1: - y = y[:,0] - else: - raise ValueError('pcolor takes 3 or 1 inarguments! (x,y,data) or (data)') - - xx = _findbins(x) - yy = _findbins(y) - ret = axis.pcolor(xx, yy, data, **kwds) - plotbackend.draw_if_interactive() - return ret - - -def _findbins(x): - ''' Return points half way between all values of X _and_ outside the - endpoints. The outer limits have same distance from X's endpoints as - the limits just inside. - ''' - dx = np.diff(x) * 0.5 - dx = np.hstack((dx, dx[-1])) - return np.hstack((x[0] - dx[0], x + dx)) - - -def test_docstrings(): - import doctest - doctest.testmod() - -if __name__ == '__main__': - test_docstrings() \ No newline at end of file +''' +Created on 20. jan. 2011 + +@author: pab + +license BSD +''' +from __future__ import division +import warnings +import numpy as np +from wafo.plotbackend import plotbackend +from matplotlib import mlab +__all__ = ['cltext', 'tallibing', 'test_docstrings'] + +_TALLIBING_GID = 'TALLIBING' +_CLTEXT_GID = 'CLTEXT' + + +def _matchfun(x, gidtxt): + if hasattr(x, 'get_gid'): + return x.get_gid() == gidtxt + return False + + +def delete_text_object(gidtxt, figure=None, axis=None, verbose=False): + ''' + Delete all text objects matching the gidtxt if it exists + + Parameters + ---------- + gidtxt : string + + figure, axis : objects + current figure and current axis, respectively. + verbose : bool + If true print warnings when trying to delete non-existent objects + ''' + if figure is None: + figure = plotbackend.gcf() + if axis is None: + axis = figure.gca() + lmatchfun = lambda x: _matchfun(x, gidtxt) + objs = axis.findobj(lmatchfun) + for obj in objs: + try: + axis.texts.remove(obj) + except: + if verbose: + warnings.warn( + 'Tried to delete a non-existing %s from axis' % gidtxt) + objs = figure.findobj(lmatchfun) + for obj in objs: + try: + figure.texts.remove(obj) + except: + if verbose: + warnings.warn( + 'Tried to delete a non-existing %s from figure' % gidtxt) + + +def cltext(levels, percent=False, n=4, xs=0.036, ys=0.94, zs=0, figure=None, + axis=None): + ''' + Places contour level text in the current window + + Parameters + ---------- + levels : vector + contour levels or the corresponding percent which the + contour line encloses + percent : bool + False if levels are the actual contour levels (default) + True if levels are the corresponding percent which the + contour line encloses + n : integer + maximum N digits of precision (default 4) + figure, axis : objects + current figure and current axis, respectively. + default figure = plotbackend.gcf(), + axis = plotbackend.gca() + + Returns + ------- + h = handles to the text objects. + + + Notes + ----- + CLTEXT creates text objects in the current figure and prints + "Level curves at:" if percent is False and + "Level curves enclosing:" otherwise + and the contour levels or percent. + + The handles to the lines of text may also be found by + h = findobj(gcf,'gid','CLTEXT','type','text'); + h = findobj(gca,'gid','CLTEXT','type','text'); + To make the text objects follow the data in the axes set the units + for the text objects 'data' by + set(h,'unit','data') + + Examples + -------- + >>> import wafo.graphutil as wg + >>> import wafo.demos as wd + >>> import pylab as plt + >>> x,y,z = wd.peaks(); + >>> h = plt.contour(x,y,z) + >>> h = wg.cltext(h.levels) + >>> plt.show() + ''' + # TODO : Make it work like legend does (but without the box): include + # position options etc... + if figure is None: + figure = plotbackend.gcf() + if axis is None: + axis = figure.gca() + + clevels = np.atleast_1d(levels) + + axpos = axis.get_position() + xint = axpos.intervalx + yint = axpos.intervaly + + xss = xint[0] + xs * (xint[1] - xint[0]) + yss = yint[0] + ys * (yint[1] - yint[0]) + + # delete cltext object if it exists + delete_text_object(_CLTEXT_GID, axis=axis) + + charHeight = 1.0 / 33.0 + delta_y = charHeight + + if percent: + titletxt = 'Level curves enclosing:' + else: + titletxt = 'Level curves at:' + + format_ = '%0.' + ('%d' % n) + 'g\n' + + cltxt = ''.join([format_ % level for level in clevels.tolist()]) + + titleProp = dict(gid=_CLTEXT_GID, horizontalalignment='left', + verticalalignment='center', fontweight='bold', axes=axis) + + ha1 = figure.text(xss, yss, titletxt, **titleProp) + + yss -= delta_y + txtProp = dict(gid=_CLTEXT_GID, horizontalalignment='left', + verticalalignment='top', axes=axis) + + ha2 = figure.text(xss, yss, cltxt, **txtProp) + plotbackend.draw_if_interactive() + return ha1, ha2 + + +def tallibing(*args, **kwds): + ''' + TALLIBING Display numbers on field-plot + + CALL h=tallibing(x,y,n,size,color) + + Parameters + ---------- + x, y : array + position matrices + n : array + corresponding matrix of the values to be written + (non-integers are rounded) + mid_points : bool (default True) + data-point-positions are in the middle of bins instead of the corners + size : int, (default=8) + font size (optional) + color : str, (default='white') + color of text (optional) + + Returns + ------- + h : list + handles to TEXT objects + + TALLIBING writes the numbers in a 2D array as text at the positions + given by the x and y coordinate matrices. + When plotting binned results, the number of datapoints in each + bin can be written on the bins in the plot. + + Example + ------- + >>> import wafo.graphutil as wg + >>> import wafo.demos as wd + >>> [x,y,z] = wd.peaks(n=20) + >>> h0 = wg.pcolor(x,y,z) + >>> h1 = wg.tallibing(x,y,z) + + See also + -------- + text + ''' + + axis = kwds.pop('axis', None) + if axis is None: + axis = plotbackend.gca() + + x, y, n = _parse_data(*args, **kwds) + if mlab.isvector(x) or mlab.isvector(y): + x, y = np.meshgrid(x, y) + + n = np.round(n) + + # delete tallibing object if it exists + delete_text_object(_TALLIBING_GID, axis=axis) + + txtProp = dict(gid=_TALLIBING_GID, size=8, color='w', + horizontalalignment='center', + verticalalignment='center', fontweight='demi', axes=axis) + + txtProp.update(**kwds) + h = [] + for xi, yi, ni in zip(x.ravel(), y.ravel(), n.ravel()): + if ni: + h.append(axis.text(xi, yi, str(ni), **txtProp)) + plotbackend.draw_if_interactive() + return h + + +def _parse_data(*args, **kwds): + nargin = len(args) + data = np.atleast_2d(args[-1]).copy() + M, N = data.shape + if nargin == 1: + x = np.arange(N) + y = np.arange(M) + elif nargin == 3: + x, y = np.atleast_1d(*args[:-1]) + if min(x.shape) != 1: + x = x[0] + if min(y.shape) != 1: + y = y[:, 0] + else: + raise ValueError( + 'Requires 3 or 1 in arguments! (x,y,data) or (data)') + if kwds.pop('mid_point', True): + xx = _find_mid_points(x) + yy = _find_mid_points(y) + return xx, yy, data + return x, y, data + +pcolor = plotbackend.pcolor +pcolormesh = plotbackend.pcolormesh + + +def _find_mid_points(x): + ''' Return points half way between all values of X and outside the + endpoints. The outer limits have same distance from X's endpoints as + the limits just inside. + ''' + dx = np.diff(x) * 0.5 + dx = np.hstack((dx, dx[-1])) + return x + dx + + +def test_docstrings(): + import doctest + print('Testing docstrings in %s' % __file__) + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) + +if __name__ == '__main__': + test_docstrings() diff --git a/pywafo/src/wafo/info.py b/pywafo/src/wafo/info.py index 291fc8a..53d182a 100644 --- a/pywafo/src/wafo/info.py +++ b/pywafo/src/wafo/info.py @@ -1,9 +1,10 @@ """ WAFO ==== - WAFO is a toolbox Python routines for statistical analysis and simulation of random waves and random loads. - WAFO is freely redistributable software, see WAFO licence, cf. the GNU General Public License (GPL) and - contain tools for: + WAFO is a toolbox Python routines for statistical analysis and simulation of + random waves and random loads. + WAFO is freely redistributable software, see WAFO licence, cf. the + GNU General Public License (GPL) and contain tools for: Fatigue Analysis ---------------- @@ -22,42 +23,42 @@ Statistics -Kernel density estimation -Hidden markov models - WAFO consists of several subpackages and classes with short descriptions below. + WAFO consists of several subpackages and classes with short descriptions given + below. Classes: - TimeSeries - Data analysis of time series. Example: extraction of - turning points, estimation of spectrum and covariance function. + TimeSeries - Data analysis of time series. Example: extraction of + turning points, estimation of spectrum and covariance function. Estimation transformation used in transformed Gaussian model. - CovData - Computation of spectral functions, linear - and non-linear time series simulation. - SpecData - Computation of spectral moments and covariance functions, linear - and non-linear time series simulation. - Ex: common spectra implemented, directional spectra, + CovData - Computation of spectral functions, linear + and non-linear time series simulation. + SpecData - Computation of spectral moments and covariance functions, linear + and non-linear time series simulation. + Ex: common spectra implemented, directional spectra, bandwidth measures, exact distributions for wave characteristics. - - - CyclePairs - Cycle counting, discretization, and crossings, calculation of + + CyclePairs - Cycle counting, discretization, and crossings, calculation of damage. Simulation of discrete Markov chains, switching Markov - chains, harmonic oscillator. Ex: Rainflow cycles and matrix, - discretization of loads. Damage of a rainflow count or + chains, harmonic oscillator. Ex: Rainflow cycles and matrix, + discretization of loads. Damage of a rainflow count or matrix, damage matrix, S-N plot. - - + + Subpackages: TRANSFORM - Modelling with linear or transformed Gaussian waves. Ex: STATS - Statistical tools and extreme-value distributions. Ex: generation of random numbers, estimation of parameters, evaluation of pdf and cdf KDETOOLS - Kernel-density estimation. - MISC - Miscellaneous routines. - DOCS - Documentation of toolbox, definitions. An overview is given - in the routine wafomenu. + MISC - Miscellaneous routines. + DOCS - Documentation of toolbox, definitions. An overview is given + in the routine wafomenu. DATA - Measurements from marine applications. - + WAFO homepage: On the WAFO home page you will find: - The WAFO Tutorial - New versions of WAFO to download. - Reported bugs. - List of publications related to WAFO. -""" \ No newline at end of file +""" diff --git a/pywafo/src/wafo/integrate.py b/pywafo/src/wafo/integrate.py index 96758d2..5e31b77 100644 --- a/pywafo/src/wafo/integrate.py +++ b/pywafo/src/wafo/integrate.py @@ -797,15 +797,15 @@ def qrule(n, wfun=1, alpha=0, beta=0): number of base points wfun : integer defining the weight function, p(x). (default wfun = 1) - 1,11,21: p(x) = 1 a =-1, b = 1 Gauss-Legendre - 2,12 : p(x) = exp(-x^2) a =-inf, b = inf Hermite - 3,13 : p(x) = x^alpha*exp(-x) a = 0, b = inf Laguerre - 4,14 : p(x) = (x-a)^alpha*(b-x)^beta a =-1, b = 1 Jacobi - 5 : p(x) = 1/sqrt((x-a)*(b-x)), a =-1, b = 1 Chebyshev 1'st kind - 6 : p(x) = sqrt((x-a)*(b-x)), a =-1, b = 1 Chebyshev 2'nd kind - 7 : p(x) = sqrt((x-a)/(b-x)), a = 0, b = 1 - 8 : p(x) = 1/sqrt(b-x), a = 0, b = 1 - 9 : p(x) = sqrt(b-x), a = 0, b = 1 + 1,11,21: p(x) = 1 a =-1, b = 1 Gauss-Legendre + 2,12 : p(x) = exp(-x^2) a =-inf, b = inf Hermite + 3,13 : p(x) = x^alpha*exp(-x) a = 0, b = inf Laguerre + 4,14 : p(x) = (x-a)^alpha*(b-x)^beta a =-1, b = 1 Jacobi + 5 : p(x) = 1/sqrt((x-a)*(b-x)), a =-1, b = 1 Chebyshev 1'st kind + 6 : p(x) = sqrt((x-a)*(b-x)), a =-1, b = 1 Chebyshev 2'nd kind + 7 : p(x) = sqrt((x-a)/(b-x)), a = 0, b = 1 + 8 : p(x) = 1/sqrt(b-x), a = 0, b = 1 + 9 : p(x) = sqrt(b-x), a = 0, b = 1 Returns ------- @@ -1428,8 +1428,8 @@ def qdemo(f, a, b): formats = ['%4.0f, ', ] + ['%10.10f, ', ] * 6 formats[-1] = formats[-1].split(',')[0] data = np.vstack((neval, qt, et, qs, es, qb, eb)).T - print(' ftn Trapezoid Simpson''s Boole''s') - print('evals approx error approx error approx error') + print(' ftn Trapezoid Simpson''s Boole''s') # @IgnorePep8 + print('evals approx error approx error approx error') # @IgnorePep8 for k in xrange(kmax): tmp = data[k].tolist() @@ -1437,8 +1437,8 @@ def qdemo(f, a, b): # display results data = np.vstack((neval, qc, ec, qc2, ec2, qg, eg)).T - print(' ftn Clenshaw Chebychev Gauss-L') - print('evals approx error approx error approx error') + print(' ftn Clenshaw Chebychev Gauss-L') # @IgnorePep8 + print('evals approx error approx error approx error') # @IgnorePep8 for k in xrange(kmax): tmp = data[k].tolist() print(''.join(fi % t for fi, t in zip(formats, tmp))) @@ -1447,7 +1447,7 @@ def qdemo(f, a, b): plt.xlabel('number of function evaluations') plt.ylabel('error') plt.legend( - ('Trapezoid', 'Simpsons', 'Booles', 'Clenshaw', 'Chebychev', 'Gauss-L')) + ('Trapezoid', 'Simpsons', 'Booles', 'Clenshaw', 'Chebychev', 'Gauss-L')) # @IgnorePep8 # ec3' diff --git a/pywafo/src/wafo/interpolate.py b/pywafo/src/wafo/interpolate.py index 7ec6f68..568574d 100644 --- a/pywafo/src/wafo/interpolate.py +++ b/pywafo/src/wafo/interpolate.py @@ -12,9 +12,9 @@ from __future__ import division import numpy as np import scipy.signal -import scipy.special as spec -import scipy.sparse as sp +#import scipy.special as spec import scipy.sparse.linalg # @UnusedImport +import scipy.sparse as sparse from numpy.ma.core import ones, zeros, prod, sin from numpy import diff, pi, inf # @UnresolvedImport from numpy.lib.shape_base import vstack @@ -546,7 +546,7 @@ class SmoothSpline(PPform): else: dx1 = 1. / dx - D = sp.spdiags(var * ones(n), 0, n, n) # The variance + D = sparse.spdiags(var * ones(n), 0, n, n) # The variance u, p = self._compute_u(p, D, dydx, dx, dx1, n) dx1.shape = (n - 1, -1) @@ -590,10 +590,10 @@ class SmoothSpline(PPform): def _compute_u(self, p, D, dydx, dx, dx1, n): if p is None or p != 0: data = [dx[1:n - 1], 2 * (dx[:n - 2] + dx[1:n - 1]), dx[:n - 2]] - R = sp.spdiags(data, [-1, 0, 1], n - 2, n - 2) + R = sparse.spdiags(data, [-1, 0, 1], n - 2, n - 2) if p is None or p < 1: - Q = sp.spdiags( + Q = sparse.spdiags( [dx1[:n - 2], -(dx1[:n - 2] + dx1[1:n - 1]), dx1[1:n - 1]], [0, -1, -2], n, n - 2) QDQ = (Q.T * D * Q) @@ -612,8 +612,8 @@ class SmoothSpline(PPform): # Make sure it uses symmetric matrix solver ddydx = diff(dydx, axis=0) - sp.linalg.use_solver(useUmfpack=True) - u = 2 * sp.linalg.spsolve((QQ + QQ.T), ddydx) + #sp.linalg.use_solver(useUmfpack=True) + u = 2 * sparse.linalg.spsolve((QQ + QQ.T), ddydx) # @UndefinedVariable return u.reshape(n - 2, -1), p @@ -923,7 +923,7 @@ class StinemanInterp(object): ''' def __init__(self, x, y, yp=None, method='parabola', monotone=False): if yp is None: - yp = slopes(x, y, method, monotone) + yp = slopes(x, y, method, monotone=monotone) self.x = np.asarray(x, np.float_) self.y = np.asarray(y, np.float_) self.yp = np.asarray(yp, np.float_) @@ -1058,7 +1058,8 @@ class Pchip(PiecewisePolynomial): >>> h=plt.xlabel("X") >>> h=plt.ylabel("Y") - >>> h=plt.title("Comparing pypchip() vs. Scipy interp1d() vs. non-monotonic CHS") + >>> txt = "Comparing pypchip() vs. Scipy interp1d() vs. non-monotonic CHS" + >>> h=plt.title(txt) >>> legends = ["Data", "pypchip()", "interp1d","CHS", 'SI'] >>> h=plt.legend(legends, loc="upper left") >>> plt.show() @@ -1210,10 +1211,10 @@ def test_func(): _tck1, _u = interpolate.splprep([t, y], s=0) # @UndefinedVariable tck2 = interpolate.splrep(t, y, s=len(t), task=0) # @UndefinedVariable # interpolate.spl - tck = interpolate.splmake(t, y, order=3, kind='smoothest', conds=None) # @UndefinedVariable + tck = interpolate.splmake(t, y, order=3, kind='smoothest', conds=None) self = interpolate.ppform.fromspline(*tck2) # @UndefinedVariable plt.plot(t, self(t)) - plt.show() + plt.show('hold') pass @@ -1238,12 +1239,13 @@ def test_pp(): def test_docstrings(): import doctest - doctest.testmod() + print('Testing docstrings in %s' % __file__) + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) if __name__ == '__main__': - test_func() + #test_func() # test_doctstrings() # test_smoothing_spline() - # compare_methods() - #demo_monoticity() + #compare_methods() + demo_monoticity() diff --git a/pywafo/src/wafo/kdetools.py b/pywafo/src/wafo/kdetools.py index 1c358fc..248a79a 100644 --- a/pywafo/src/wafo/kdetools.py +++ b/pywafo/src/wafo/kdetools.py @@ -21,7 +21,7 @@ from scipy.ndimage.morphology import distance_transform_edt from numpy import pi, sqrt, atleast_2d, exp, newaxis # @UnresolvedImport from wafo.misc import meshgrid, nextpow2, tranproc # , trangood -from wafo.wafodata import PlotData +from wafo.containers import PlotData from wafo.dctpack import dct, dctn, idctn from wafo.plotbackend import plotbackend as plt try: @@ -3984,7 +3984,8 @@ def kreg_demo3(x, y, fun1, hs=None, fun='hisj', plotlog=False): eerr = np.abs((yiii - fiii)).std() + 0.5 * (df[:-1] * df[1:] < 0).sum() / n err = (fiii - fit).std() f = kreg( - xiii, output='plotobj', title='%s err=%1.3f,eerr=%1.3f, n=%d, hs=%1.3f, hs1=%1.3f, hs2=%1.3f' % + xiii, output='plotobj', + title='%s err=%1.3f,eerr=%1.3f, n=%d, hs=%1.3f, hs1=%1.3f, hs2=%1.3f' % (fun, err, eerr, n, hs, hs1, hs2), plotflag=1) #yi[yi==0] = 1.0/(c[c!=0].min()+4) @@ -4051,8 +4052,8 @@ def kreg_demo3(x, y, fun1, hs=None, fun='hisj', plotlog=False): # Wilson score den = 1 + (z0 ** 2. / ciii) xc = (pi1 + (z0 ** 2) / (2 * ciii)) / den - halfwidth = ( - z0 * sqrt((pi1 * (1 - pi1) / ciii) + (z0 ** 2 / (4 * (ciii ** 2))))) / den + halfwidth = (z0 * sqrt((pi1 * (1 - pi1) / ciii) + + (z0 ** 2 / (4 * (ciii ** 2))))) / den plo = (xc - halfwidth).clip(min=0) # wilson score pup = (xc + halfwidth).clip(max=1.0) # wilson score # pup = (pi + z0*np.sqrt(pi*(1-pi)/ciii)).clip(min=0,max=1) # dont use @@ -4061,14 +4062,18 @@ def kreg_demo3(x, y, fun1, hs=None, fun='hisj', plotlog=False): #mi = kreg.eval_grid(x) #sigma = (stineman_interp(x, xiii, pup)-stineman_interp(x, xiii, plo))/4 #aic = np.abs((y-mi)/sigma).std()+ 0.5*(df[:-1]*df[1:]<0).sum()/n - #aic = np.abs((yiii-fiii)/(pup-plo)).std()+ 0.5*(df[:-1]*df[1:]<0).sum() + ((yiii-pup).clip(min=0)-(yiii-plo).clip(max=0)).sum() + #aic = np.abs((yiii-fiii)/(pup-plo)).std() + \ + # 0.5*(df[:-1]*df[1:]<0).sum() + \ + # ((yiii-pup).clip(min=0)-(yiii-plo).clip(max=0)).sum() k = (df[:-1] * df[1:] < 0).sum() # numpeaks sigmai = (pup - plo) - aic = (((yiii - fiii) / sigmai) ** 2).sum() + 2 * k * (k + 1) / np.maximum(ni - k + 1, 1) + \ + aic = (((yiii - fiii) / sigmai) ** 2).sum() + \ + 2 * k * (k + 1) / np.maximum(ni - k + 1, 1) + \ np.abs((yiii - pup).clip(min=0) - (yiii - plo).clip(max=0)).sum() - #aic = (((yiii-fiii)/sigmai)**2).sum()+ 2*k*(k+1)/(ni-k+1) + np.abs((yiii-pup).clip(min=0)-(yiii-plo).clip(max=0)).sum() + #aic = (((yiii-fiii)/sigmai)**2).sum()+ 2*k*(k+1)/(ni-k+1) + \ + # np.abs((yiii-pup).clip(min=0)-(yiii-plo).clip(max=0)).sum() #aic = averr + ((yiii-pup).clip(min=0)-(yiii-plo).clip(max=0)).sum() @@ -4140,14 +4145,16 @@ def kreg_demo4(x, y, hs, hopt, alpha=0.05): yi = np.where(c == 0, 0, c0 / c) f.children = [PlotData( - [plo, pup], xiii, plotmethod='fill_between', plot_kwds=dict(alpha=0.2, color='r')), + [plo, pup], xiii, plotmethod='fill_between', + plot_kwds=dict(alpha=0.2, color='r')), PlotData(yi, xi, plotmethod='scatter', plot_kwds=dict(color='r', s=5))] yiii = interpolate.interp1d(xi, yi)(xiii) df = np.diff(fiii) k = (df[:-1] * df[1:] < 0).sum() # numpeaks sigmai = (pup - plo) - aicc = (((yiii - fiii) / sigmai) ** 2).sum() + 2 * k * (k + 1) / np.maximum(ni - k + 1, 1) + \ + aicc = (((yiii - fiii) / sigmai) ** 2).sum() + \ + 2 * k * (k + 1) / np.maximum(ni - k + 1, 1) + \ np.abs((yiii - pup).clip(min=0) - (yiii - plo).clip(max=0)).sum() f.aicc = aicc @@ -4168,7 +4175,7 @@ def check_kreg_demo3(): for fun in ['hste', ]: #@UnusedVariable - hsmax, hs1, hs2 = _get_regression_smooting(x, y, fun=fun) + hsmax, _hs1, _hs2 = _get_regression_smooting(x, y, fun=fun) for hi in np.linspace(hsmax * 0.25, hsmax, 9): plt.figure(k) k += 1 @@ -4197,7 +4204,7 @@ def check_kreg_demo4(): hopt = sqrt(hopt1 * hopt2) #hopt = _get_regression_smooting(x,y,fun='hos')[0] # , 'hisj', 'hns', 'hstt' @UnusedVariable - for j, fun in enumerate(['hste']): + for _j, fun in enumerate(['hste']): hsmax, _hs1, _hs2 = _get_regression_smooting(x, y, fun=fun) fmax = kreg_demo4(x, y, hsmax + 0.1, hopt) @@ -4295,18 +4302,18 @@ def _get_regression_smooting(x, y, fun='hste'): def empirical_bin_prb(x, y, hopt, color='r'): ''' Returns empirical binomial probabiltity - + Parameters ---------- x : ndarray position ve y : ndarray binomial response variable (zeros and ones) - + Returns ------- P(x) : PlotData object - empirical probability + empirical probability ''' xmin, xmax = x.min(), x.max() ni = max(2 * int((xmax - xmin) / hopt) + 3, 5) @@ -4320,10 +4327,12 @@ def empirical_bin_prb(x, y, hopt, color='r'): else: c0 = np.zeros(xi.shape) yi = np.where(c == 0, 0, c0 / c) - return PlotData(yi, xi, plotmethod='scatter', plot_kwds=dict(color=color, s=5)) + return PlotData(yi, xi, plotmethod='scatter', + plot_kwds=dict(color=color, s=5)) -def smoothed_bin_prb(x, y, hs, hopt, alpha=0.05, color='r', label='', bin_prb=None): +def smoothed_bin_prb(x, y, hs, hopt, alpha=0.05, color='r', label='', + bin_prb=None): ''' Parameters ---------- @@ -4379,14 +4388,16 @@ def smoothed_bin_prb(x, y, hs, hopt, alpha=0.05, color='r', label='', bin_prb=No if label: f.plot_kwds['label'] = label f.children = [PlotData( - [plo, pup], xiii, plotmethod='fill_between', plot_kwds=dict(alpha=0.2, color=color)), + [plo, pup], xiii, plotmethod='fill_between', + plot_kwds=dict(alpha=0.2, color=color)), bin_prb] yiii = interpolate.interp1d(xi, yi)(xiii) df = np.diff(fiii) k = (df[:-1] * df[1:] < 0).sum() # numpeaks sigmai = (pup - plo) - aicc = (((yiii - fiii) / sigmai) ** 2).sum() + 2 * k * (k + 1) / np.maximum(ni - k + 1, 1) + \ + aicc = (((yiii - fiii) / sigmai) ** 2).sum() + \ + 2 * k * (k + 1) / np.maximum(ni - k + 1, 1) + \ np.abs((yiii - pup).clip(min=0) - (yiii - plo).clip(max=0)).sum() f.aicc = aicc @@ -4400,7 +4411,7 @@ def smoothed_bin_prb(x, y, hs, hopt, alpha=0.05, color='r', label='', bin_prb=No def regressionbin(x, y, alpha=0.05, color='r', label=''): ''' Return kernel regression estimate for binomial data - + Parameters ---------- x : arraylike @@ -4408,17 +4419,15 @@ def regressionbin(x, y, alpha=0.05, color='r', label=''): y : arraylike of 0 and 1 ''' - # @UnusedVariable - hopt1, h1, h2 = _get_regression_smooting(x, y, fun='hos') - # @UnusedVariable - hopt2, h1, h2 = _get_regression_smooting(x, y, fun='hste') + + hopt1, _h1, _h2 = _get_regression_smooting(x, y, fun='hos') + hopt2, _h1, _h2 = _get_regression_smooting(x, y, fun='hste') hopt = sqrt(hopt1 * hopt2) fbest = smoothed_bin_prb(x, y, hopt2 + 0.1, hopt, alpha, color, label) bin_prb = fbest.children[-1] for fun in ['hste']: # , 'hisj', 'hns', 'hstt' - #@UnusedVariable - hsmax, hs1, hs2 = _get_regression_smooting(x, y, fun=fun) + hsmax, _hs1, _hs2 = _get_regression_smooting(x, y, fun=fun) for hi in np.linspace(hsmax * 0.1, hsmax, 55): f = smoothed_bin_prb(x, y, hi, hopt, alpha, color, label, bin_prb) if f.aicc <= fbest.aicc: @@ -4479,8 +4488,8 @@ def kde_gauss_demo(n=50): print(fmax / f2.data.max()) format_ = ''.join(('%g, ') * d) format_ = 'hs0=%s hs1=%s hs2=%s' % (format_, format_, format_) - print( - format_ % tuple(kde0.hs.tolist() + kde1.tkde.hs.tolist() + kde2.hs.tolist())) + print(format_ % tuple(kde0.hs.tolist() + + kde1.tkde.hs.tolist() + kde2.hs.tolist())) print('inc0 = %d, inc1 = %d, inc2 = %d' % (kde0.inc, kde1.inc, kde2.inc)) diff --git a/pywafo/src/wafo/meshgrid.py b/pywafo/src/wafo/meshgrid.py deleted file mode 100644 index a72fb8a..0000000 --- a/pywafo/src/wafo/meshgrid.py +++ /dev/null @@ -1,136 +0,0 @@ -import numpy as np - - -def meshgrid(*xi, **kwargs): - """ - Return coordinate matrices from one or more coordinate vectors. - - Make N-D coordinate arrays for vectorized evaluations of - N-D scalar/vector fields over N-D grids, given - one-dimensional coordinate arrays x1, x2,..., xn. - - Parameters - ---------- - x1, x2,..., xn : array_like - 1-D arrays representing the coordinates of a grid. - indexing : 'xy' or 'ij' (optional) - cartesian ('xy', default) or matrix ('ij') indexing of output - sparse : True or False (default) (optional) - If True a sparse grid is returned in order to conserve memory. - copy : True (default) or False (optional) - If False a view into the original arrays are returned in order to - conserve memory. Please note that sparse=False, copy=False will likely - return non-contiguous arrays. Furthermore, more than one element of a - broadcasted array may refer to a single memory location. If you - need to write to the arrays, make copies first. - - Returns - ------- - X1, X2,..., XN : ndarray - For vectors `x1`, `x2`,..., 'xn' with lengths ``Ni=len(xi)`` , - return ``(N1, N2, N3,...Nn)`` shaped arrays if indexing='ij' - or ``(N2, N1, N3,...Nn)`` shaped arrays if indexing='xy' - with the elements of `xi` repeated to fill the matrix along - the first dimension for `x1`, the second for `x2` and so on. - - Notes - ----- - This function supports both indexing conventions through the indexing - keyword argument. Giving the string 'ij' returns a meshgrid with matrix - indexing, while 'xy' returns a meshgrid with Cartesian indexing. The - difference is illustrated by the following code snippet: - - xv, yv = meshgrid(x, y, sparse=False, indexing='ij') - for i in range(nx): - for j in range(ny): - # treat xv[i,j], yv[i,j] - - xv, yv = meshgrid(x, y, sparse=False, indexing='xy') - for i in range(nx): - for j in range(ny): - # treat xv[j,i], yv[j,i] - - See Also - -------- - index_tricks.mgrid : Construct a multi-dimensional "meshgrid" - using indexing notation. - index_tricks.ogrid : Construct an open multi-dimensional "meshgrid" - using indexing notation. - - Examples - -------- - >>> nx, ny = (3, 2) - >>> x = np.linspace(0, 1, nx) - >>> y = np.linspace(0, 1, ny) - >>> xv, yv = meshgrid(x, y) - >>> xv - array([[ 0. , 0.5, 1. ], - [ 0. , 0.5, 1. ]]) - >>> yv - array([[ 0., 0., 0.], - [ 1., 1., 1.]]) - >>> xv, yv = meshgrid(x, y, sparse=True) # make sparse output arrays - >>> xv - array([[ 0. , 0.5, 1. ]]) - >>> yv - array([[ 0.], - [ 1.]]) - - `meshgrid` is very useful to evaluate functions on a grid. - - >>> x = np.arange(-5, 5, 0.1) - >>> y = np.arange(-5, 5, 0.1) - >>> xx, yy = meshgrid(x, y, sparse=True) - >>> z = np.sin(xx**2+yy**2)/(xx**2+yy**2) - - >>> import matplotlib.pyplot as plt - >>> h = plt.contourf(x,y,z) - """ - copy_ = kwargs.get('copy', True) - args = np.atleast_1d(*xi) - ndim = len(args) - - if not isinstance(args, list) or ndim < 2: - raise TypeError( - 'meshgrid() takes 2 or more arguments (%d given)' % int(ndim > 0)) - - sparse = kwargs.get('sparse', False) - indexing = kwargs.get('indexing', 'xy') - - s0 = (1,) * ndim - output = [x.reshape(s0[:i] + (-1,) + s0[i + 1::]) - for i, x in enumerate(args)] - - shape = [x.size for x in output] - - if indexing == 'xy': - # switch first and second axis - output[0].shape = (1, -1) + (1,) * (ndim - 2) - output[1].shape = (-1, 1) + (1,) * (ndim - 2) - shape[0], shape[1] = shape[1], shape[0] - - if sparse: - if copy_: - return [x.copy() for x in output] - else: - return output - else: - # Return the full N-D matrix (not only the 1-D vector) - if copy_: - mult_fact = np.ones(shape, dtype=int) - return [x * mult_fact for x in output] - else: - return np.broadcast_arrays(*output) - - -def ndgrid(*args, **kwargs): - """ - Same as calling meshgrid with indexing='ij' (see meshgrid for - documentation). - """ - kwargs['indexing'] = 'ij' - return meshgrid(*args, **kwargs) - -if __name__ == '__main__': - import doctest - doctest.testmod() diff --git a/pywafo/src/wafo/misc.py b/pywafo/src/wafo/misc.py index d4f6f55..d00bd8d 100644 --- a/pywafo/src/wafo/misc.py +++ b/pywafo/src/wafo/misc.py @@ -7,15 +7,16 @@ import sys import fractions import numpy as np from numpy import ( - meshgrid, + meshgrid, abs, amax, any, logical_and, arange, linspace, atleast_1d, - array, asarray, ceil, floor, frexp, hypot, - sqrt, arctan2, sin, cos, exp, log, mod, diff, empty_like, + asarray, ceil, floor, frexp, hypot, + sqrt, arctan2, sin, cos, exp, log, log1p, mod, diff, empty_like, finfo, inf, pi, interp, isnan, isscalar, zeros, ones, linalg, r_, sign, unique, hstack, vstack, nonzero, where, extract) from scipy.special import gammaln, gamma, psi from scipy.integrate import trapz, simps import warnings +from time import strftime, gmtime from plotbackend import plotbackend from collections import OrderedDict @@ -38,6 +39,334 @@ __all__ = [ 'trangood', 'tranproc', 'plot_histgrm', 'num2pistr', 'test_docstrings'] +def rotation_matrix(heading, pitch, roll): + ''' + + Examples + >>> import numpy as np + >>> rotation_matrix(heading=0, pitch=0, roll=0) + array([[ 1., 0., 0.], + [ 0., 1., 0.], + [ 0., 0., 1.]]) + + >>> np.all(np.abs(rotation_matrix(heading=180, pitch=0, roll=0)- + ... np.array([[ -1.000000e+00, -1.224647e-16, 0.000000e+00], + ... [ 1.224647e-16, -1.000000e+00, 0.000000e+00], + ... [ -0.000000e+00, 0.000000e+00, 1.000000e+00]]))<1e-7) + True + >>> np.all(np.abs(rotation_matrix(heading=0, pitch=180, roll=0)- + ... np.array([[ -1.000000e+00, 0.000000e+00, 1.224647e-16], + ... [ -0.000000e+00, 1.000000e+00, 0.000000e+00], + ... [ -1.224647e-16, -0.000000e+00, -1.000000e+00]]))<1e-7) + True + >>> np.all(np.abs(rotation_matrix(heading=0, pitch=0, roll=180)- + ... np.array([[ 1.000000e+00, 0.000000e+00, 0.000000e+00], + ... [ 0.000000e+00, -1.000000e+00, -1.224647e-16], + ... [ -0.000000e+00, 1.224647e-16, -1.000000e+00]]))<1e-7) + True + ''' + data = np.diag(np.ones(3)) # No transform if H=P=R=0 + if heading != 0 or pitch != 0 or roll != 0: + deg2rad = np.pi / 180 + H = heading * deg2rad + P = pitch * deg2rad + R = roll * deg2rad # Convert to radians + + data.put(0, cos(H) * cos(P)) + data.put(1, cos(H) * sin(P) * sin(R) - sin(H) * cos(R)) + data.put(2, cos(H) * sin(P) * cos(R) + sin(H) * sin(R)) + data.put(3, sin(H) * cos(P)) + data.put(4, sin(H) * sin(P) * sin(R) + cos(H) * cos(R)) + data.put(5, sin(H) * sin(P) * cos(R) - cos(H) * sin(R)) + data.put(6, -sin(P)) + data.put(7, cos(P) * sin(R)) + data.put(8, cos(P) * cos(R)) + return data + + +def rotate(x, y, z, heading=0, pitch=0, roll=0): + rot_param = rotation_matrix(heading, pitch, roll).ravel() + X = x * rot_param[0] + y * rot_param[1] + z * rot_param[2] + Y = x * rot_param[3] + y * rot_param[4] + z * rot_param[5] + Z = x * rot_param[6] + y * rot_param[7] + z * rot_param[8] + return X, Y, Z + + +def rotate_2d(x, y, angle_deg): + ''' + Rotate points in the xy cartesian plane counter clockwise + + Examples + -------- + >>> rotate_2d(x=1, y=0, angle_deg=0) + (1.0, 0.0) + >>> rotate_2d(x=1, y=0, angle_deg=90) + (6.123233995736766e-17, 1.0) + >>> rotate_2d(x=1, y=0, angle_deg=180) + (-1.0, 1.2246467991473532e-16) + >>> rotate_2d(x=1, y=0, angle_deg=360) + (1.0, -2.4492935982947064e-16) + ''' + angle_rad = angle_deg * pi / 180 + ch = cos(angle_rad) + sh = sin(angle_rad) + return ch * x - sh * y, sh * x + ch * y + + +def now(show_seconds=True): + ''' + Return current date and time as a string + ''' + if show_seconds: + return strftime("%a, %d %b %Y %H:%M:%S", gmtime()) + else: + return strftime("%a, %d %b %Y %H:%M", gmtime()) + + +def _assert(cond, txt=''): + if not cond: + raise ValueError(txt) + + +def spaceline(start_point, stop_point, num=10): + '''Return `num` evenly spaced points between the start and stop points. + + Parameters + ---------- + start_point : vector, size=3 + The starting point of the sequence. + stop_point : vector, size=3 + The end point of the sequence. + num : int, optional + Number of samples to generate. Default is 10. + + Returns + ------- + space_points : ndarray of shape n x 3 + There are `num` equally spaced points in the closed interval + ``[start, stop]``. + + See Also + -------- + linspace : similar to spaceline, but in 1D. + arange : Similiar to `linspace`, but uses a step size (instead of the + number of samples). + logspace : Samples uniformly distributed in log space. + + Example + ------- + >>> import wafo.misc as pm + >>> pm.spaceline((2,0,0), (3,0,0), num=5) + array([[ 2. , 0. , 0. ], + [ 2.25, 0. , 0. ], + [ 2.5 , 0. , 0. ], + [ 2.75, 0. , 0. ], + [ 3. , 0. , 0. ]]) + ''' + num = int(num) + e1, e2 = np.atleast_1d(start_point, stop_point) + e2m1 = e2 - e1 + length = np.sqrt((e2m1 ** 2).sum()) + #length = sqrt((E2[0]-E1(1))^2 + (E2(2)-E1(2))^2 + (E2(3)-E1(3))^2); + C = e2m1 / length + delta = length / float(num - 1) + return np.array([e1 + n * delta * C for n in range(num)]) + + +def narg_smallest(n, arr): + ''' Return the n smallest indicis to the arr + ''' + return np.array(arr).argsort()[:n] + + +def args_flat(*args): + ''' + Return x,y,z positions as a N x 3 ndarray + + Parameters + ---------- + pos : array-like, shape N x 3 + [x,y,z] positions + or + + x,y,z : array-like + [x,y,z] positions + + Returns + ------ + pos : ndarray, shape N x 3 + [x,y,z] positions + common_shape : None or tuple + common shape of x, y and z variables if given as triple input. + + Examples + -------- + >>> x = [1,2,3] + >>> pos, c_shape =args_flat(x,2,3) + >>> pos + array([[1, 2, 3], + [2, 2, 3], + [3, 2, 3]]) + >>> c_shape + (3,) + >>> pos1, c_shape1 = args_flat([1,2,3]) + >>> pos1 + array([[1, 2, 3]]) + >>> c_shape1 is None + True + >>> pos1, c_shape1 = args_flat(1,2,3) + >>> pos1 + array([[1, 2, 3]]) + >>> c_shape1 + () + >>> pos1, c_shape1 = args_flat([1],2,3) + >>> pos1 + array([[1, 2, 3]]) + >>> c_shape1 + (1,) + + ''' + nargin = len(args) + + if (nargin == 1): # pos + pos = np.atleast_2d(args[0]) + _assert((pos.shape[1] == 3) and (pos.ndim == 2), + 'POS array must be of shape N x 3!') + return pos, None + elif nargin == 3: + x, y, z = np.broadcast_arrays(*args[:3]) + c_shape = x.shape + return np.vstack((x.ravel(), y.ravel(), z.ravel())).T, c_shape + else: + raise ValueError('Number of arguments must be 1 or 3!') + + +def _check_and_adjust_shape(shape, nsub=None): + s = np.atleast_1d(shape) + ndim = len(s) + if ndim < 1: + raise ValueError('Shape vector must have at least 1 element.') + ndim = len(s) + if nsub is None: + nsub = ndim + if ndim <= nsub: # add trailing singleton dimensions + s = np.hstack([s, np.ones(nsub - ndim, dtype=int)]) + else: # Adjust for linear indexing on last element + s = np.hstack([s[:nsub - 1], np.prod(s[nsub - 1:])]) + return s + + +def _sub2index_factor(shape, order='C'): + '''Return multiplier needed for calculating linear index from subscripts. + ''' + step = 1 if order == 'F' else -1 # C order + return np.hstack([1, np.cumprod(shape[::step][:-1])])[::step] + + +def index2sub(shape, index, nsub=None, order='C'): + ''' + Returns Multiple subscripts from linear index. + + Parameters + ---------- + shape : array-like + shape of array + index : + linear index into array + nsub : int optional + Number of subscripts returned. default nsub=len(shape) + order : {'C','F'}, optional + The order of the linear index. + 'C' means C (row-major) order. + 'F' means Fortran (column-major) order. + By default, 'C' order is used. + + This function is used to determine the equivalent subscript values + corresponding to a given single index into an array. + + Example + ------- + >>> shape = (3,3,4) + >>> a = np.arange(np.prod(shape)).reshape(shape) + >>> order = 'C' + >>> a[1, 2, 3] + 23 + >>> i = sub2index(shape, 1, 2, 3, order=order) + >>> a.ravel(order)[i] + 23 + >>> index2sub(shape, i, order=order) + (array([1]), array([2]), array([3])) + + See also + -------- + sub2index + ''' + ndx = np.atleast_1d(index) + s = _check_and_adjust_shape(shape, nsub) + k = _sub2index_factor(s, order) + n = len(s) + step = -1 if order == 'F' else 1 # C order + subscripts = [0, ] * n + for i in range(n)[::step]: + vi = np.remainder(ndx, k[i]) + subscript = np.array((ndx - vi) / k[i], dtype=int) + subscripts[i] = subscript + ndx = vi + return tuple(subscripts) + + +def sub2index(shape, *subscripts, **kwds): + ''' + Returns linear index from multiple subscripts. + + Parameters + ---------- + shape : array-like + shape of array + *subscripts : + subscripts into array + order : {'C','F'}, optional + The order of the linear index. + 'C' means C (row-major) order. + 'F' means Fortran (column-major) order. + By default, 'C' order is used. + + This function is used to determine the equivalent single index + corresponding to a given set of subscript values into an array. + + Example + ------- + >>> shape = (3,3,4) + >>> a = np.arange(np.prod(shape)).reshape(shape) + >>> order = 'C' + >>> i = sub2index(shape, 1, 2, 3, order=order) + >>> a[1, 2, 3] + 23 + >>> a.ravel(order)[i] + 23 + >>> index2sub(shape, i, order=order) + (array([1]), array([2]), array([3])) + + See also + -------- + index2sub + ''' + nsub = len(subscripts) + s = _check_and_adjust_shape(shape, nsub) + k = _sub2index_factor(s, **kwds) + + ndx = 0 + s0 = np.shape(subscripts[0]) + for i, subscript in enumerate(subscripts): + np.testing.assert_equal(s0, np.shape(subscript), + 'The subscripts vectors must all be of the same shape.') + if (np.any(subscript < 0)) or (np.any(s[i] <= subscript)): + raise IndexError('Out of range subscript.') + + ndx = ndx + k[i] * subscript + return ndx + + def is_numlike(obj): 'return true if *obj* looks like a number' try: @@ -160,7 +489,7 @@ def parse_kwargs(options, **kwargs): def testfun(*args, **kwargs): opts = dict(opt1=1, opt2=2) if (len(args) == 1 and len(kwargs) == 0 and type(args[0]) is str and - args[0].startswith('default')): + args[0].startswith('default')): return opts opts = parse_kwargs(opts, **kwargs) return opts @@ -186,14 +515,14 @@ def detrendma(x, L): Examples -------- >>> import wafo.misc as wm - >>> import pylab as plb - >>> exp = plb.exp; cos = plb.cos; randn = plb.randn - >>> x = plb.linspace(0,1,200) + >>> import pylab as plt + >>> exp = plt.exp; cos = plt.cos; randn = plt.randn + >>> x = plt.linspace(0,1,200) >>> y = exp(x)+cos(5*2*pi*x)+1e-1*randn(x.size) >>> y0 = wm.detrendma(y,20); tr = y-y0 - >>> h = plb.plot(x, y, x, y0, 'r', x, exp(x), 'k', x, tr, 'm') + >>> h = plt.plot(x, y, x, y0, 'r', x, exp(x), 'k', x, tr, 'm') - >>> plb.close('all') + >>> plt.close('all') See also -------- @@ -247,22 +576,22 @@ def ecross(t, f, ind, v=0): Example ------- - >>> from matplotlib import pylab as plb + >>> from matplotlib import pylab as plt >>> import wafo.misc as wm - >>> ones = plb.ones - >>> t = plb.linspace(0,7*plb.pi,250) - >>> x = plb.sin(t) + >>> ones = np.ones + >>> t = np.linspace(0,7*np.pi,250) + >>> x = np.sin(t) >>> ind = wm.findcross(x,0.75) >>> ind array([ 9, 25, 80, 97, 151, 168, 223, 239]) >>> t0 = wm.ecross(t,x,ind,0.75) - >>> t0 - array([ 0.84910514, 2.2933879 , 7.13205663, 8.57630119, - 13.41484739, 14.85909194, 19.69776067, 21.14204343]) - >>> a = plb.plot(t, x, '.', t[ind], x[ind], 'r.', t, ones(t.shape)*0.75, - ... t0, ones(t0.shape)*0.75, 'g.') + >>> np.abs(t0 - np.array([0.84910514, 2.2933879 , 7.13205663, 8.57630119, + ... 13.41484739, 14.85909194, 19.69776067, 21.14204343]))<1e-7 + array([ True, True, True, True, True, True, True, True], dtype=bool) - >>> plb.close('all') + >>> a = plt.plot(t, x, '.', t[ind], x[ind], 'r.', t, ones(t.shape)*0.75, + ... t0, ones(t0.shape)*0.75, 'g.') + >>> plt.close('all') See also -------- @@ -337,23 +666,23 @@ def findcross(x, v=0.0, kind=None): Example ------- - >>> from matplotlib import pylab as plb + >>> from matplotlib import pylab as plt >>> import wafo.misc as wm - >>> ones = plb.ones + >>> ones = np.ones >>> findcross([0, 1, -1, 1],0) array([0, 1, 2]) >>> v = 0.75 - >>> t = plb.linspace(0,7*plb.pi,250) - >>> x = plb.sin(t) + >>> t = np.linspace(0,7*np.pi,250) + >>> x = np.sin(t) >>> ind = wm.findcross(x,v) # all crossings >>> ind array([ 9, 25, 80, 97, 151, 168, 223, 239]) - >>> t0 = plb.plot(t,x,'.',t[ind],x[ind],'r.', t, ones(t.shape)*v) + >>> t0 = plt.plot(t,x,'.',t[ind],x[ind],'r.', t, ones(t.shape)*v) >>> ind2 = wm.findcross(x,v,'u') >>> ind2 array([ 9, 80, 151, 223]) - >>> t0 = plb.plot(t[ind2],x[ind2],'o') - >>> plb.close('all') + >>> t0 = plt.plot(t[ind2],x[ind2],'o') + >>> plt.close('all') See also -------- @@ -411,13 +740,13 @@ def findextrema(x): Examples -------- >>> import numpy as np - >>> import pylab as pb + >>> import pylab as plt >>> import wafo.misc as wm >>> t = np.linspace(0,7*np.pi,250) >>> x = np.sin(t) >>> ind = wm.findextrema(x) - >>> a = pb.plot(t,x,'.',t[ind],x[ind],'r.') - >>> pb.close('all') + >>> a = plt.plot(t,x,'.',t[ind],x[ind],'r.') + >>> plt.close('all') See also -------- @@ -566,19 +895,19 @@ def findrfc(tp, h=0.0, method='clib'): Example: -------- - >>> import pylab as pb + >>> import matplotlib.pyplot as plt >>> import wafo.misc as wm - >>> t = pb.linspace(0,7*np.pi,250) - >>> x = pb.sin(t)+0.1*np.sin(50*t) + >>> t = np.linspace(0,7*np.pi,250) + >>> x = np.sin(t)+0.1*np.sin(50*t) >>> ind = wm.findextrema(x) >>> ti, tp = t[ind], x[ind] - >>> a = pb.plot(t,x,'.',ti,tp,'r.') + >>> a = plt.plot(t,x,'.',ti,tp,'r.') >>> ind1 = wm.findrfc(tp,0.3); ind1 array([ 0, 9, 32, 53, 74, 95, 116, 137]) >>> ind2 = wm.findrfc(tp,0.3, method=''); ind2 array([ 0, 9, 32, 53, 74, 95, 116, 137]) - >>> a = pb.plot(ti[ind1],tp[ind1]) - >>> pb.close('all') + >>> a = plt.plot(ti[ind1],tp[ind1]) + >>> plt.close('all') See also -------- @@ -699,18 +1028,21 @@ def mctp2rfc(fmM, fMm=None): ... [0.0000, 0, 0, 0, 0], ... [ 0, 0, 0, 0, 0]]) - >>> mctp2rfc(fmM) - array([[ 2.66998090e-02, 7.79970042e-03, 4.90607697e-07, - 0.00000000e+00, 0.00000000e+00], - [ 9.59962873e-03, 5.48500862e-01, 9.53995094e-02, - 0.00000000e+00, 0.00000000e+00], - [ 5.62297379e-07, 8.14994377e-02, 0.00000000e+00, - 0.00000000e+00, 0.00000000e+00], - [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, - 0.00000000e+00, 0.00000000e+00], - [ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, - 0.00000000e+00, 0.00000000e+00]]) - + >>> np.abs(mctp2rfc(fmM)-np.array([[2.669981e-02, 7.799700e-03, + ... 4.906077e-07, 0.000000e+00, 0.000000e+00], + ... [ 9.599629e-03, 5.485009e-01, 9.539951e-02, 0.000000e+00, + ... 0.000000e+00], + ... [ 5.622974e-07, 8.149944e-02, 0.000000e+00, 0.000000e+00, + ... 0.000000e+00], + ... [ 0.000000e+00, 0.000000e+00, 0.000000e+00, 0.000000e+00, + ... 0.000000e+00], + ... [ 0.000000e+00, 0.000000e+00, 0.000000e+00, 0.000000e+00, + ... 0.000000e+00]]))<1.e-7 + array([[ True, True, True, True, True], + [ True, True, True, True, True], + [ True, True, True, True, True], + [ True, True, True, True, True], + [ True, True, True, True, True]], dtype=bool) ''' if fMm is None: @@ -755,7 +1087,7 @@ def mctp2rfc(fmM, fMm=None): # end fx = 0.0 if (max(abs(e)) > 1e-6 and - max(abs(NN)) > 1e-6 * max(MA[0], mA[0])): + max(abs(NN)) > 1e-6 * max(MA[0], mA[0])): PMm = AA1.copy() for j in range(nA): norm = MA[j] @@ -772,7 +1104,7 @@ def mctp2rfc(fmM, fMm=None): fx = NN * (A / (1 - B * A) * e) else: rh = np.eye(A.shape[0]) - np.dot(B, A) - #least squares + # least squares fx = np.dot(NN, np.dot(A, linalg.solve(rh, e))) # end # end @@ -839,22 +1171,19 @@ def rfcfilter(x, h, method=0): >>> import wafo.misc as wm >>> x = wafo.data.sea() >>> y = wm.rfcfilter(x[:,1], h=0, method=1) + >>> np.all(np.abs(y[0:5]-np.array([-1.2004945 , 0.83950546, -0.09049454, + ... -0.02049454, -0.09049454]))<1e-7) + True >>> y.shape (2172,) - >>> y[0:5] - array([-1.2004945 , 0.83950546, -0.09049454, -0.02049454, -0.09049454]) - >>> y[-5::] - array([ 0.18950546, 0.15950546, 0.91950546, -0.51049454, -0.48049454]) # 2. This removes all rainflow cycles with range less than 0.5. >>> y1 = wm.rfcfilter(x[:,1], h=0.5) >>> y1.shape (863,) - >>> y1[0:5] - array([-1.2004945 , 0.83950546, -0.43049454, 0.34950546, -0.51049454]) - >>> y1[-5::] - array([-0.64049454, 0.65950546, -1.0004945 , 0.91950546, -0.51049454]) - + >>> np.all(np.abs(y1[0:5]-np.array([-1.2004945 , 0.83950546, -0.43049454, + ... 0.34950546, -0.51049454]))<1e-7) + True >>> ind = wm.findtp(x[:,1], h=0.5) >>> y2 = x[ind,1] >>> y2[0:5] @@ -899,7 +1228,7 @@ def rfcfilter(x, h, method=0): t1, y1 = (t0, y0) if z1 == 0 else (ti, yi) else: if (((z0 == +1) & cmpfun1(yi, fmi)) | - ((z0 == -1) & cmpfun2(yi, fpi))): + ((z0 == -1) & cmpfun2(yi, fpi))): z1 = -1 elif (((z0 == +1) & cmpfun2(fmi, yi)) | ((z0 == -1) & cmpfun1(fpi, yi))): @@ -962,26 +1291,23 @@ def findtp(x, h=0.0, kind=None): Example: -------- - >>> import wafo.data - >>> import pylab as plb + >>> import pylab as plt >>> import wafo.misc as wm - >>> x = wafo.data.sea() - >>> x1 = x[0:200,:] + >>> t = np.linspace(0,30,500).reshape((-1,1)) + >>> x = np.hstack((t, np.cos(t) + 0.3 * np.sin(5*t))) + >>> x1 = x[0:100,:] >>> itp = wm.findtp(x1[:,1],0,'Mw') >>> itph = wm.findtp(x1[:,1],0.3,'Mw') >>> tp = x1[itp,:] >>> tph = x1[itph,:] - >>> a = plb.plot(x1[:,0],x1[:,1], + >>> a = plt.plot(x1[:,0],x1[:,1], ... tp[:,0],tp[:,1],'ro', - ... tph[:,1],tph[:,1],'k.') - >>> plb.close('all') + ... tph[:,0],tph[:,1],'k.') + >>> plt.close('all') >>> itp - array([ 11, 21, 22, 24, 26, 28, 31, 39, 43, 45, 47, 51, 56, - 64, 70, 78, 82, 84, 89, 94, 101, 108, 119, 131, 141, 148, - 149, 150, 159, 173, 184, 190, 199]) + array([ 5, 18, 24, 38, 46, 57, 70, 76, 91, 98, 99]) >>> itph - array([ 11, 28, 31, 39, 47, 51, 56, 64, 70, 78, 89, 94, 101, - 108, 119, 131, 141, 148, 159, 173, 184, 190, 199]) + array([91]) See also --------- @@ -999,10 +1325,10 @@ def findtp(x, h=0.0, kind=None): if ind.size < 2: return None - #% In order to get the exact up-crossing intensity from rfc by - #% mm2lc(tp2mm(rfc)) we have to add the indices - #% to the last value (and also the first if the - #% sequence of turning points does not start with a minimum). + # In order to get the exact up-crossing intensity from rfc by + # mm2lc(tp2mm(rfc)) we have to add the indices to the last value + # (and also the first if the sequence of turning points does not start + # with a minimum). if kind == 'astm': # the Nieslony approach always put the first loading point as the first @@ -1068,14 +1394,15 @@ def findtc(x_in, v=None, kind=None): Example: -------- >>> import wafo.data - >>> import pylab as plb + >>> import pylab as plt >>> import wafo.misc as wm - >>> x = wafo.data.sea() + >>> t = np.linspace(0,30,500).reshape((-1,1)) + >>> x = np.hstack((t, np.cos(t))) >>> x1 = x[0:200,:] >>> itc, iv = wm.findtc(x1[:,1],0,'dw') >>> tc = x1[itc,:] - >>> a = plb.plot(x1[:,0],x1[:,1],tc[:,0],tc[:,1],'ro') - >>> plb.close('all') + >>> a = plt.plot(x1[:,0],x1[:,1],tc[:,0],tc[:,1],'ro') + >>> plt.close('all') See also -------- @@ -1095,38 +1422,35 @@ def findtc(x_in, v=None, kind=None): return zeros(0, dtype=np.int), zeros(0, dtype=np.int) # determine the number of trough2crest (or crest2trough) cycles - isodd = mod(n_c, 2) - if isodd: - n_tc = int((n_c - 1) / 2) - else: - n_tc = int((n_c - 2) / 2) + is_even = mod(n_c + 1, 2) + n_tc = int((n_c - 1 - is_even) / 2) - #% allocate variables before the loop increases the speed + # allocate variables before the loop increases the speed ind = zeros(n_c - 1, dtype=np.int) first_is_down_crossing = (x[v_ind[0]] > x[v_ind[0] + 1]) if first_is_down_crossing: for i in xrange(n_tc): - #% trough + # trough j = 2 * i ind[j] = x[v_ind[j] + 1:v_ind[j + 1] + 1].argmin() - #% crest + # crest ind[j + 1] = x[v_ind[j + 1] + 1:v_ind[j + 2] + 1].argmax() if (2 * n_tc + 1 < n_c) and (kind in (None, 'tw')): - #% trough + # trough ind[n_c - 2] = x[v_ind[n_c - 2] + 1:v_ind[n_c - 1]].argmin() - else: # %%%% the first is a up-crossing + else: # the first is a up-crossing for i in xrange(n_tc): - #% trough + # crest j = 2 * i ind[j] = x[v_ind[j] + 1:v_ind[j + 1] + 1].argmax() - #% crest + # trough ind[j + 1] = x[v_ind[j + 1] + 1:v_ind[j + 2] + 1].argmin() if (2 * n_tc + 1 < n_c) and (kind in (None, 'cw')): - #% trough + # crest ind[n_c - 2] = x[v_ind[n_c - 2] + 1:v_ind[n_c - 1]].argmax() return v_ind[:n_c - 1] + ind + 1, v_ind @@ -1174,7 +1498,8 @@ def findoutliers(x, zcrit=0.0, dcrit=None, ddcrit=None, verbose=False): >>> import numpy as np >>> import wafo >>> import wafo.misc as wm - >>> xx = wafo.data.sea() + >>> t = np.linspace(0,30,500).reshape((-1,1)) + >>> xx = np.hstack((t, np.cos(t))) >>> dt = np.diff(xx[:2,0]) >>> dcrit = 5*dt >>> ddcrit = 9.81/2*dt*dt @@ -1182,10 +1507,11 @@ def findoutliers(x, zcrit=0.0, dcrit=None, ddcrit=None, verbose=False): >>> [inds, indg] = wm.findoutliers(xx[:,1],zcrit,dcrit,ddcrit,verbose=True) Found 0 spurious positive jumps of Dx Found 0 spurious negative jumps of Dx - Found 37 spurious positive jumps of D^2x - Found 200 spurious negative jumps of D^2x - Found 244 consecutive equal values - Found the total of 1152 spurious points + Found 0 spurious positive jumps of D^2x + Found 0 spurious negative jumps of D^2x + Found 0 consecutive equal values + Found the total of 0 spurious points + #waveplot(xx,'-',xx(inds,:),1,1,1) @@ -1448,8 +1774,8 @@ def stirlerr(n): Example ------- >>> import wafo.misc as wm - >>> wm.stirlerr(2) - array([ 0.0413407]) + >>> np.abs(wm.stirlerr(2)- 0.0413407)<1e-7 + array([ True], dtype=bool) See also --------- @@ -1460,8 +1786,7 @@ def stirlerr(n): ----------- Catherine Loader (2000). Fast and Accurate Computation of Binomial Probabilities - - + ''' S0 = 0.083333333333333333333 # /* 1/12 */ @@ -1494,11 +1819,9 @@ def stirlerr(n): return y -#@ReservedAssignment - def getshipchar(value=None, property="max_deadweight", # @ReservedAssignment - **kwds): + **kwds): # @IgnorePep8 ''' Return ship characteristics from value of one ship-property @@ -1609,6 +1932,43 @@ def getshipchar(value=None, property="max_deadweight", # @ReservedAssignment return shipchar +def binomln(z, w): + ''' + Natural Logarithm of binomial coefficient. + + CALL binomln(z,w) + + BINOMLN computes the natural logarithm of the binomial + function for corresponding elements of Z and W. The arrays Z and + W must be real and nonnegative. Both arrays must be the same size, + or either can be scalar. BETALOGE is defined as: + + y = LOG(binom(Z,W)) = gammaln(Z)-gammaln(W)-gammaln(Z-W) + + and is obtained without computing BINOM(Z,W). Since the binom + function can range over very large or very small values, its + logarithm is sometimes more useful. + This implementation is more accurate than the log(BINOM(Z,W) implementation + for large arguments + + Example + ------- + + >>> abs(binomln(3,2)- 1.09861229)<1e-7 + array([ True], dtype=bool) + + See also + -------- + binom + ''' + # log(n!) = stirlerr(n) + log( sqrt(2*pi*n)*(n/exp(1))**n ) + # y = gammaln(z+1)-gammaln(w+1)-gammaln(z-w+1) + zpw = z - w + return (stirlerr(z + 1) - stirlerr(w + 1) - 0.5 * log(2 * pi) - + (w + 0.5) * log1p(w) + (z + 0.5) * log1p(z) - stirlerr(zpw + 1) - + (zpw + 0.5) * log1p(zpw) + 1) + + def betaloge(z, w): ''' Natural Logarithm of beta function. @@ -1631,8 +1991,8 @@ def betaloge(z, w): Example ------- >>> import wafo.misc as wm - >>> wm.betaloge(3,2) - array([-2.48490665]) + >>> abs(wm.betaloge(3,2)+2.48490665)<1e-7 + array([ True], dtype=bool) See also -------- @@ -1673,8 +2033,10 @@ def gravity(phi=45): >>> import wafo.misc as wm >>> import numpy as np >>> phi = np.linspace(0,45,5) - >>> wm.gravity(phi) - array([ 9.78049 , 9.78245014, 9.78803583, 9.79640552, 9.80629387]) + >>> np.abs(wm.gravity(phi)-np.array([ 9.78049 , 9.78245014, 9.78803583, + ... 9.79640552, 9.80629387]))<1.e-7 + array([ True, True, True, True, True], dtype=bool) + See also -------- @@ -1808,12 +2170,13 @@ def hyp2f1(a, b, c, z, rho=0.5): e7 = gammaln(c - b) e8 = gammaln(c - a - b) e9 = gammaln(a + b - c) - _cmab = c-a-b + _cmab = c - a - b #~(np.round(cmab) == cmab & cmab <= 0) if abs(z) <= rho: h = hyp2f1_taylor(a, b, c, z, 1e-15) elif abs(1 - z) <= rho: # % Require that |arg(1-z)| max(absEps,abs(relEps.*fsum(k1))))); # if any(k0),% compute more terms -# %nk=length(k0);%# of values we have to compute again +# %nk=length(k0);%# of values we have to compute again # E{2} = E{2}(k0); # E{3} = E{3}(k0); # else @@ -2272,7 +2637,7 @@ def hygfz(A, B, C, Z): # k0 = (find((abs(err(k1))) > max(absEps,abs(relEps.* ... # fsum(k1))))); # if any(k0),% compute more terms -# %nk=length(k0);%# of values we have to compute again +# %nk=length(k0);%# of values we have to compute again # else # converge='y'; # break; @@ -2282,7 +2647,7 @@ def hygfz(A, B, C, Z): # end # end # if ~strncmpi(converge,'y',1) -# disp(sprintf('#%d values did not converge',length(k1))) +# disp(sprintf('#%d values did not converge',length(k1))) # end # end # %ix @@ -2338,13 +2703,13 @@ def discretize(fun, a, b, tol=0.005, n=5, method='linear'): ------- >>> import wafo.misc as wm >>> import numpy as np - >>> import pylab as plb + >>> import pylab as plt >>> x,y = wm.discretize(np.cos, 0, np.pi) >>> xa,ya = wm.discretize(np.cos, 0, np.pi, method='adaptive') - >>> t = plb.plot(x, y, xa, ya, 'r.') - >>> plb.show() + >>> t = plt.plot(x, y, xa, ya, 'r.') - >>> plb.close('all') + plt.show() + >>> plt.close('all') ''' if method.startswith('a'): @@ -2437,6 +2802,7 @@ def polar2cart(theta, rho, z=None): return x, y else: return x, y, z +pol2cart = polar2cart def cart2polar(x, y, z=None): @@ -2445,9 +2811,9 @@ def cart2polar(x, y, z=None): Returns ------- theta : array-like - arctan2(y,x) + radial angle, arctan2(y,x) rho : array-like - sqrt(x**2+y**2) + radial distance, sqrt(x**2+y**2) See also -------- @@ -2458,6 +2824,7 @@ def cart2polar(x, y, z=None): return t, r else: return t, r, z +cart2pol = cart2polar def ndgrid(*args, **kwargs): @@ -2595,19 +2962,19 @@ def tranproc(x, f, x0, *xi): Example -------- Derivative of g and the transformed Gaussian model. - >>> import pylab as plb + >>> import pylab as plt >>> import wafo.misc as wm >>> import wafo.transform.models as wtm >>> tr = wtm.TrHermite() >>> x = linspace(-5,5,501) >>> g = tr(x) >>> gder = wm.tranproc(x, g, x, ones(g.shape[0])) - >>> h = plb.plot(x, g, x, gder[1]) + >>> h = plt.plot(x, g, x, gder[1]) - plb.plot(x,pdfnorm(g)*gder[1],x,pdfnorm(x)) - plb.legend('Transformed model','Gaussian model') + plt.plot(x,pdfnorm(g)*gder[1],x,pdfnorm(x)) + plt.legend('Transformed model','Gaussian model') - >>> plb.close('all') + >>> plt.close('all') See also -------- @@ -2638,7 +3005,8 @@ def tranproc(x, f, x0, *xi): hn = xo[1] - xo[0] if hn ** N < sqrt(_EPS): msg = ('Numerical problems may occur for the derivatives in ' + - 'tranproc.\nThe sampling of the transformation may be too small.') + 'tranproc.\n' + + 'The sampling of the transformation may be too small.') warnings.warn(msg) # Transform X with the derivatives of f. @@ -2773,13 +3141,15 @@ def plot_histgrm(data, bins=None, range=None, # @ReservedAssignment Example ------- - >>> import pylab as plb + >>> import pylab as plt >>> import wafo.misc as wm >>> import wafo.stats as ws >>> R = ws.weibull_min.rvs(2,loc=0,scale=2, size=100) >>> h0 = wm.plot_histgrm(R, 20, normed=True) - >>> x = linspace(-3,16,200) - >>> h1 = plb.plot(x,ws.weibull_min.pdf(x,2,0,2),'r') + >>> x = np.linspace(-3,16,200) + >>> h1 = plt.plot(x,ws.weibull_min.pdf(x,2,0,2),'r') + >>> plt.close('all') + See also -------- @@ -2791,7 +3161,6 @@ def plot_histgrm(data, bins=None, range=None, # @ReservedAssignment if bins is None: bins = np.ceil(4 * np.sqrt(np.sqrt(len(x)))) - #, new=True) bin_, limits = np.histogram( data, bins=bins, normed=normed, weights=weights) limits.shape = (-1, 1) @@ -2892,8 +3261,10 @@ def fourier(data, t=None, T=None, m=None, n=None, method='trapz'): >>> t = np.linspace(0,4*T) >>> x = np.sin(t) >>> a, b = wm.fourier(x, t, T=T, m=5) - >>> (np.round(a.ravel()), np.round(b.ravel())) - (array([ 0., -0., 0., -0., 0.]), array([ 0., 4., -0., -0., 0.])) + >>> np.abs(a.ravel())<1e-12 + array([ True, True, True, True, True], dtype=bool) + >>> np.abs(b.ravel()-np.array([ 0., 4., 0., 0., 0.]))<1e-12 + array([ True, True, True, True, True], dtype=bool) See also -------- @@ -2951,127 +3322,43 @@ def fourier(data, t=None, T=None, m=None, n=None, method='trapz'): return a, b -def _test_find_cross(): - t = findcross([0, 0, 1, -1, 1], 0) # @UnusedVariable - - -def _test_common_shape(): - - A = ones((4, 1)) - B = 2 - C = ones((1, 5)) * 5 - common_shape(A, B, C) - - common_shape(A, B, C, shape=(3, 4, 1)) - - A = ones((4, 1)) - B = 2 - C = ones((1, 5)) * 5 - common_shape(A, B, C, shape=(4, 5)) - - -def _test_meshgrid(): - x = array([-1, -0.5, 1, 4, 5], float) - y = array([0, -2, -5], float) - xv, yv = meshgrid(x, y, sparse=False) - print(xv) - print(yv) - xv, yv = meshgrid(x, y, sparse=True) # make sparse output arrays - print(xv) - print(yv) - print(meshgrid(0, 1, 5, sparse=True)) # just a 3D point - print(meshgrid([0, 1, 5], sparse=True)) # just a 3D point - xv, yv = meshgrid(y, y) - yv[0, 0] = 10 - print(xv) - print(yv) -# >>> xv -## array([[ 0. , 0.5, 1. ]]) -# >>> yv -# array([[ 0.], -# [ 1.]]) -# array([[-1. , -0.5, 1. , 4. , 5. ], -## [-1. , -0.5, 1. , 4. , 5. ], -# [-1. , -0.5, 1. , 4. , 5. ]]) -# -# array([[ 0., 0., 0., 0., 0.], -## [-2., -2., -2., -2., -2.], -# [-5., -5., -5., -5., -5.]]) - - -def _test_tranproc(): - import wafo.transform.models as wtm - tr = wtm.TrHermite() - x = linspace(-5, 5, 501) - g = tr(x) - _gder = tranproc(x, g, x, ones(g.size)) - pass - #>>> gder(:,1) = g(:,1) - #>>> plot(g(:,1),[g(:,2),gder(:,2)]) - #>>> plot(g(:,1),pdfnorm(g(:,2)).*gder(:,2),g(:,1),pdfnorm(g(:,1))) - #>>> legend('Transformed model','Gaussian model') - - -def _test_detrend(): - import pylab as plb - cos = plb.cos - randn = plb.randn - x = linspace(0, 1, 200) - y = exp(x) + cos(5 * 2 * pi * x) + 1e-1 * randn(x.size) - y0 = detrendma(y, 20) - tr = y - y0 - plb.plot(x, y, x, y0, 'r', x, exp(x), 'k', x, tr, 'm') - - -def _test_extrema(): - import pylab as pb - from pylab import plot - t = pb.linspace(0, 7 * pi, 250) - x = pb.sin(t) + 0.1 * sin(50 * t) - ind = findextrema(x) - ti, tp = t[ind], x[ind] - plot(t, x, '.', ti, tp, 'r.') - _ind1 = findrfc(tp, 0.3) - - -def _test_discretize(): - import pylab as plb - x, y = discretize(cos, 0, pi) - plb.plot(x, y) - plb.show() - plb.close('all') - - -def _test_stirlerr(): - x = linspace(1, 5, 6) - print stirlerr(x) - print stirlerr(1) - print getshipchar(1000) - print betaloge(3, 2) - - -def _test_parse_kwargs(): - opt = dict(arg1=1, arg2=3) - print opt - opt = parse_kwargs(opt, arg1=5) - print opt - opt2 = dict(arg3=15) - opt = parse_kwargs(opt, **opt2) - print opt - - opt0 = testfun('default') - print opt0 - opt0.update(opt1=100) - print opt0 - opt0 = parse_kwargs(opt0, opt2=200) - print opt0 - out1 = testfun(opt0['opt1'], **opt0) - print out1 +def real_main0(): + x = np.arange(10000) + y = np.arange(100).reshape(-1, 1) + np.broadcast_arrays(x, y, x, x, x, x, x, x, x, x) + + +def real_main(): + x = np.arange(100000) + y = np.arange(100).reshape(-1, 1) + common_shape(x, y, x, x, x, x, x, x, x, x) + + +def profile_main1(): + # This is the main function for profiling + # We've renamed our original main() above to real_main() + import cProfile + import pstats + prof = cProfile.Profile() + prof = prof.runctx("real_main()", globals(), locals()) + print "
"
+    stats = pstats.Stats(prof)
+    stats.sort_stats("time")  # Or cumulative
+    stats.print_stats(80)  # 80 = how many to print
+    # The rest is optional.
+    # stats.print_callees()
+    # stats.print_callers()
+    print "
" + + +main = profile_main1 def test_docstrings(): + # np.set_printoptions(precision=6) import doctest - doctest.testmod() + print('Testing docstrings in %s' % __file__) + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) def test_hyp2f1(): @@ -3083,9 +3370,9 @@ def test_hyp2f1(): # log(1+x)-log(1-x) = 2*x*F(.5,1,1.5,x^2) x = linspace(0., .7, 20) y = hyp2f1_taylor(-1, -4, 1, .9) - y2 = hygfz(-1, -4, 1, .9) - y3 = hygfz(5, -300, 10, 0.5) - y4 = hyp2f1_taylor(5, -300, 10, 0.5) + _y2 = hygfz(-1, -4, 1, .9) + _y3 = hygfz(5, -300, 10, 0.5) + _y4 = hyp2f1_taylor(5, -300, 10, 0.5) #y = hyp2f1(0.1, 0.2, 0.3, 0.5) #y = hyp2f1(1, 1.5, 3, -4 +3j) #y = hyp2f1(5, 7.5, 2.5, 5) @@ -3096,10 +3383,10 @@ def test_hyp2f1(): # plt = plotbackend plt.interactive(False) - plt.semilogy(x, np.abs(y- 1. / (1 - x)) + 1e-20, 'r') + plt.semilogy(x, np.abs(y - 1. / (1 - x)) + 1e-20, 'r') plt.show() if __name__ == "__main__": - #test_docstrings() - test_hyp2f1() \ No newline at end of file + test_docstrings() + #test_hyp2f1() diff --git a/pywafo/src/wafo/namedtuple.py b/pywafo/src/wafo/namedtuple.py index 9a634f3..ad070cd 100644 --- a/pywafo/src/wafo/namedtuple.py +++ b/pywafo/src/wafo/namedtuple.py @@ -1,132 +1,144 @@ -from operator import itemgetter as _itemgetter -from keyword import iskeyword as _iskeyword -import sys as _sys - -def namedtuple(typename, field_names, verbose=False): - """Returns a new subclass of tuple with named fields. - - >>> Point = namedtuple('Point', 'x y') - >>> Point.__doc__ # docstring for the new class - 'Point(x, y)' - >>> p = Point(11, y=22) # instantiate with positional args or keywords - >>> p[0] + p[1] # indexable like a plain tuple - 33 - >>> x, y = p # unpack like a regular tuple - >>> x, y - (11, 22) - >>> p.x + p.y # fields also accessable by name - 33 - >>> d = p._asdict() # convert to a dictionary - >>> d['x'] - 11 - >>> Point(**d) # convert from a dictionary - Point(x=11, y=22) - >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields - Point(x=100, y=22) - - """ - - # Parse and validate the field names. Validation serves two purposes, - # generating informative error messages and preventing template injection attacks. - if isinstance(field_names, basestring): - field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas - field_names = tuple(field_names) - for name in (typename,) + field_names: - if not min(c.isalnum() or c=='_' for c in name): - raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name) - if _iskeyword(name): - raise ValueError('Type names and field names cannot be a keyword: %r' % name) - if name[0].isdigit(): - raise ValueError('Type names and field names cannot start with a number: %r' % name) - seen_names = set() - for name in field_names: - if name.startswith('_'): - raise ValueError('Field names cannot start with an underscore: %r' % name) - if name in seen_names: - raise ValueError('Encountered duplicate field name: %r' % name) - seen_names.add(name) - - # Create and fill-in the class template - numfields = len(field_names) - argtxt = repr(field_names).replace("'", "")[1:-1] # tuple repr without parens or quotes - reprtxt = ', '.join('%s=%%r' % name for name in field_names) - dicttxt = ', '.join('%r: t[%d]' % (name, pos) for pos, name in enumerate(field_names)) - template = '''class %(typename)s(tuple): - '%(typename)s(%(argtxt)s)' \n - __slots__ = () \n - _fields = %(field_names)r \n - def __new__(cls, %(argtxt)s): - return tuple.__new__(cls, (%(argtxt)s)) \n - @classmethod - def _make(cls, iterable, new=tuple.__new__, len=len): - 'Make a new %(typename)s object from a sequence or iterable' - result = new(cls, iterable) - if len(result) != %(numfields)d: - raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result)) - return result \n - def __repr__(self): - return '%(typename)s(%(reprtxt)s)' %% self \n - def _asdict(t): - 'Return a new dict which maps field names to their values' - return {%(dicttxt)s} \n - def _replace(self, **kwds): - 'Return a new %(typename)s object replacing specified fields with new values' - result = self._make(map(kwds.pop, %(field_names)r, self)) - if kwds: - raise ValueError('Got unexpected field names: %%r' %% kwds.keys()) - return result \n\n''' % locals() - for i, name in enumerate(field_names): - template += ' %s = property(itemgetter(%d))\n' % (name, i) - if verbose: - print template - - # Execute the template string in a temporary namespace - namespace = dict(itemgetter=_itemgetter) - try: - exec template in namespace - except SyntaxError, e: - raise SyntaxError(e.message + ':\n' + template) - result = namespace[typename] - - # For pickling to work, the __module__ variable needs to be set to the frame - # where the named tuple is created. Bypass this step in enviroments where - # sys._getframe is not defined (Jython for example). - if hasattr(_sys, '_getframe'): - result.__module__ = _sys._getframe(1).f_globals['__name__'] - - return result - - - - - - -if __name__ == '__main__': - # verify that instances can be pickled - from cPickle import loads, dumps - Point = namedtuple('Point', 'x, y', True) - p = Point(x=10, y=20) - assert p == loads(dumps(p)) - - # test and demonstrate ability to override methods - class Point(namedtuple('Point', 'x y')): - @property - def hypot(self): - return (self.x ** 2 + self.y ** 2) ** 0.5 - def __str__(self): - return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot) - - for p in Point(3,4), Point(14,5), Point(9./7,6): - print p - - class Point(namedtuple('Point', 'x y')): - 'Point class with optimized _make() and _replace() without error-checking' - _make = classmethod(tuple.__new__) - def _replace(self, _map=map, **kwds): - return self._make(_map(kwds.get, ('x', 'y'), self)) - - print Point(11, 22)._replace(x=100) - - import doctest - TestResults = namedtuple('TestResults', 'failed attempted') - print TestResults(*doctest.testmod()) \ No newline at end of file +from operator import itemgetter as _itemgetter +from keyword import iskeyword as _iskeyword +import sys as _sys + + +def namedtuple(typename, field_names, verbose=False): + """Returns a new subclass of tuple with named fields. + + >>> Point = namedtuple('Point', 'x y') + >>> Point.__doc__ # docstring for the new class + 'Point(x, y)' + >>> p = Point(11, y=22) # instantiate with positional args or keywords + >>> p[0] + p[1] # indexable like a plain tuple + 33 + >>> x, y = p # unpack like a regular tuple + >>> x, y + (11, 22) + >>> p.x + p.y # fields also accessable by name + 33 + >>> d = p._asdict() # convert to a dictionary + >>> d['x'] + 11 + >>> Point(**d) # convert from a dictionary + Point(x=11, y=22) + >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields + Point(x=100, y=22) + + """ + + # Parse and validate the field names. Validation serves two purposes, + # generating informative error messages and preventing template injection + # attacks. + if isinstance(field_names, basestring): + # names separated by whitespace and/or commas + field_names = field_names.replace(',', ' ').split() + field_names = tuple(field_names) + for name in (typename,) + field_names: + if not min(c.isalnum() or c == '_' for c in name): + raise ValueError( + 'Type names and field names can only contain alphanumeric ' + + 'characters and underscores: %r' % name) + if _iskeyword(name): + raise ValueError( + 'Type names and field names cannot be a keyword: %r' % name) + if name[0].isdigit(): + raise ValueError('Type names and field names cannot start ' + + 'with a number: %r' % name) + seen_names = set() + for name in field_names: + if name.startswith('_'): + raise ValueError( + 'Field names cannot start with an underscore: %r' % name) + if name in seen_names: + raise ValueError('Encountered duplicate field name: %r' % name) + seen_names.add(name) + + # Create and fill-in the class template + numfields = len(field_names) + # tuple repr without parens or quotes + argtxt = repr(field_names).replace("'", "")[1:-1] + reprtxt = ', '.join('%s=%%r' % name for name in field_names) + dicttxt = ', '.join('%r: t[%d]' % (name, pos) + for pos, name in enumerate(field_names)) + template = '''class %(typename)s(tuple): + '%(typename)s(%(argtxt)s)' \n + __slots__ = () \n + _fields = %(field_names)r \n + def __new__(cls, %(argtxt)s): + return tuple.__new__(cls, (%(argtxt)s)) \n + @classmethod + def _make(cls, iterable, new=tuple.__new__, len=len): + 'Make a new %(typename)s object from a sequence or iterable' + result = new(cls, iterable) + if len(result) != %(numfields)d: + raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result)) + return result \n + def __repr__(self): + return '%(typename)s(%(reprtxt)s)' %% self \n + def _asdict(t): + 'Return a new dict which maps field names to their values' + return {%(dicttxt)s} \n + def _replace(self, **kwds): + 'Return a new %(typename)s object replacing specified fields with new values' + result = self._make(map(kwds.pop, %(field_names)r, self)) + if kwds: + raise ValueError('Got unexpected field names: %%r' %% kwds.keys()) + return result \n\n''' % locals() + for i, name in enumerate(field_names): + template += ' %s = property(itemgetter(%d))\n' % (name, i) + if verbose: + print template + + # Execute the template string in a temporary namespace + namespace = dict(itemgetter=_itemgetter) + try: + exec template in namespace + except SyntaxError, e: + raise SyntaxError(e.message + ':\n' + template) + result = namespace[typename] + + # For pickling to work, the __module__ variable needs to be set to the + # frame where the named tuple is created. Bypass this step in enviroments + # where sys._getframe is not defined (Jython for example). + if hasattr(_sys, '_getframe'): + result.__module__ = _sys._getframe(1).f_globals['__name__'] + + return result + + +if __name__ == '__main__': + # verify that instances can be pickled + from cPickle import loads, dumps + Point = namedtuple('Point', 'x, y', True) + p = Point(x=10, y=20) + assert p == loads(dumps(p)) + + # test and demonstrate ability to override methods + class Point(namedtuple('Point', 'x y')): + + @property + def hypot(self): + return (self.x ** 2 + self.y ** 2) ** 0.5 + + def __str__(self): + return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, + self.hypot) + + for p in Point(3, 4), Point(14, 5), Point(9. / 7, 6): + print p + + class Point(namedtuple('Point', 'x y')): + '''Point class with optimized _make() and _replace() + without error-checking + ''' + _make = classmethod(tuple.__new__) + + def _replace(self, _map=map, **kwds): + return self._make(_map(kwds.get, ('x', 'y'), self)) + + print Point(11, 22)._replace(x=100) + + import doctest + TestResults = namedtuple('TestResults', 'failed attempted') + print TestResults(*doctest.testmod()) diff --git a/pywafo/src/wafo/objects.py b/pywafo/src/wafo/objects.py index b3b73e5..0eb5780 100644 --- a/pywafo/src/wafo/objects.py +++ b/pywafo/src/wafo/objects.py @@ -9,46 +9,40 @@ # Copyright: (c) pab 2008 # Licence: -#!/usr/bin/env python +# !/usr/bin/env python from __future__ import division -from wafo.transform.core import TrData -from wafo.transform.models import TrHermite, TrOchi, TrLinear -from wafo.stats import edf, distributions -from wafo.misc import (nextpow2, findtp, findrfc, findtc, findcross, - ecross, JITImport, DotDict, gravity, findrfc_astm) -from wafodata import PlotData -from wafo.interpolate import SmoothSpline +from .transform.core import TrData +from .transform.estimation import TransformEstimator +from .stats import distributions +from .misc import (nextpow2, findtp, findrfc, findtc, findcross, + ecross, JITImport, DotDict, gravity, findrfc_astm) +from .interpolate import stineman_interp +from .containers import PlotData +from scipy.integrate import trapz +from scipy.signal import welch, lfilter +from scipy.signal.windows import get_window # @UnusedImport +from scipy import special from scipy.interpolate.interpolate import interp1d -from scipy.integrate.quadrature import cumtrapz # @UnresolvedImport -from scipy.special import ndtr as cdfnorm, ndtri as invnorm - +from scipy.special import ndtr as cdfnorm import warnings import numpy as np - -from numpy import (inf, pi, zeros, ones, sqrt, where, log, exp, cos, sin, arcsin, mod, interp, # @UnresolvedImport - #@UnresolvedImport - linspace, arange, sort, all, abs, vstack, hstack, atleast_1d, sign, expm1, - finfo, polyfit, r_, nonzero, cumsum, ravel, size, isnan, nan, ceil, diff, array) # @UnresolvedImport -from numpy.fft import fft +from numpy import (inf, pi, zeros, ones, sqrt, where, log, exp, cos, sin, + arcsin, mod, + linspace, arange, sort, all, abs, vstack, hstack, + atleast_1d, finfo, polyfit, r_, nonzero, + cumsum, ravel, size, isnan, ceil, diff, array) +from numpy.fft import fft # @UnusedImport from numpy.random import randn -from scipy.integrate import trapz -from wafo.interpolate import stineman_interp +import matplotlib from matplotlib.mlab import psd, detrend_mean -import scipy.signal - - from plotbackend import plotbackend -import matplotlib -from scipy.stats.stats import skew, kurtosis -from scipy.signal.windows import parzen -from scipy import special - floatinfo = finfo(float) matplotlib.interactive(True) _wafocov = JITImport('wafo.covariance') +_wafocov_estimation = JITImport('wafo.covariance.estimation') _wafospec = JITImport('wafo.spectrum') __all__ = ['TimeSeries', 'LevelCrossings', 'CyclePairs', 'TurningPoints', @@ -70,17 +64,17 @@ class LevelCrossings(PlotData): number of upcrossings or upcrossingintensity args : array-like crossing levels - + Examples -------- >>> import wafo.data >>> import wafo.objects as wo >>> x = wafo.data.sea() >>> ts = wo.mat2timeseries(x) - + >>> tp = ts.turning_points() >>> mm = tp.cycle_pairs() - + >>> lc = mm.level_crossings() >>> h2 = lc.plot() ''' @@ -99,7 +93,7 @@ class LevelCrossings(PlotData): # self.setplotter(plotmethod='step') icmax = self.data.argmax() - if self.data != None: + if self.data is not None: if self.sigma is None or self.mean is None: logcros = where(self.data == 0.0, inf, -log(self.data)) logcmin = logcros[icmax] @@ -107,7 +101,7 @@ class LevelCrossings(PlotData): logcros[0:icmax + 1] = 2 * logcros[ icmax] - logcros[0:icmax + 1] ncr = 10 - #least square fit + # least square fit p = polyfit(self.args[ncr:-ncr], logcros[ncr:-ncr], 1) if self.sigma is None: # estimated standard deviation of x @@ -119,14 +113,15 @@ class LevelCrossings(PlotData): y = cmax * exp(-x ** 2 / 2.0) self.children = [PlotData(y, self.args)] - def extrapolate(self, u_min=None, u_max=None, method='ml', dist='genpar', plotflag=0): - ''' + def extrapolate(self, u_min=None, u_max=None, method='ml', dist='genpar', + plotflag=0): + ''' Returns an extrapolated level crossing spectrum - + Parameters ----------- u_min, u_max : real scalars - extrapolate below u_min and above u_max. + extrapolate below u_min and above u_max. method : string describing the method of estimation. Options are: 'ml' : Maximum Likelihood method (default) @@ -135,66 +130,69 @@ class LevelCrossings(PlotData): defining distribution function. Options are: genpareto : Generalized Pareto distribution (GPD) expon : Exponential distribution (GPD with k=0) - rayleigh : truncated Rayleigh distribution - plotflag : scalar integer + rayleigh : truncated Rayleigh distribution + plotflag : scalar integer 1: Diagnostic plots. (default) 0: Don't plot diagnostic plots. - + Returns ------- lc : LevelCrossing object - with the estimated level crossing spectrum + with the estimated level crossing spectrum Est = Estimated parameters. [struct array] - - Extrapolates the level crossing spectrum (LC) for high and for low levels. - The tails of the LC is fitted to a survival function of a GPD. + + Extrapolates the level crossing spectrum (LC) for high and for low + levels. + The tails of the LC is fitted to a survival function of a GPD. H(x) = (1-k*x/s)^(1/k) (GPD) - The use of GPD is motivated by POT methods in extreme value theory. + The use of GPD is motivated by POT methods in extreme value theory. For k=0 the GPD is the exponential distribution H(x) = exp(-x/s), k=0 (expon) - The tails with the survival function of a truncated Rayleigh distribution. + The tails with the survival function of a truncated Rayleigh + distribution. H(x) = exp(-((x+x0).^2-x0^2)/s^2) (rayleigh) - where x0 is the distance from the truncation level to where the LC has its maximum. + where x0 is the distance from the truncation level to where the LC has + its maximum. The method 'gpd' uses the GPD. We recommend the use of 'gpd,ml'. - The method 'exp' uses the Exp. - The method 'ray' uses Ray, and should be used if the load is a Gaussian process. - + The method 'exp' uses the Exp. + The method 'ray' uses Ray, and should be used if the load is a + Gaussian process. + Example ------- >>> import wafo.data >>> import wafo.objects as wo >>> x = wafo.data.sea() >>> ts = wo.mat2timeseries(x) - + >>> tp = ts.turning_points() >>> mm = tp.cycle_pairs() >>> lc = mm.level_crossings() - + >>> s = x[:,1].std() >>> lc_gpd = lc.extrapolate(-2*s, 2*s) >>> lc_exp = lc.extrapolate(-2*s, 2*s, dist='expon') >>> lc_ray = lc.extrapolate(-2*s, 2*s, dist='rayleigh') - + lc.plot() lc_gpd.plot() lc_exp.plot() lc_ray.plot() - - - See also + + + See also -------- cmat2extralc, rfmextrapolate, lc2rfmextreme, extralc, fitgenpar - + References ---------- - Johannesson, P., and Thomas, J-.J. (2000): - Extrapolation of Rainflow Matrices. - Preprint 2000:82, Mathematical statistics, Chalmers, pp. 18. + Johannesson, P., and Thomas, J-.J. (2000): + Extrapolation of Rainflow Matrices. + Preprint 2000:82, Mathematical statistics, Chalmers, pp. 18. ''' i_max = self.data.argmax() c_max = self.data[i_max] - # Maximum of lc lc_max = self.args[i_max] if u_min is None or u_max is None: @@ -206,16 +204,14 @@ class LevelCrossings(PlotData): u_max = self.args[i.max()] lcf, lcx = self.data, self.args # Extrapolate LC for high levels - [lc_High, phat_high] = self._extrapolate( - lcx, lcf, u_max, u_max - lc_max, method, dist) -# -# Extrapolate LC for low levels - [lcEst1, phat_low] = self._extrapolate( - -lcx[::-1], lcf[::-1], -u_min, lc_max - u_min, method, dist) + [lc_High, phat_high] = self._extrapolate(lcx, lcf, u_max, + u_max - lc_max, method, dist) + # Extrapolate LC for low levels + [lcEst1, phat_low] = self._extrapolate(-lcx[::-1], lcf[::-1], -u_min, + lc_max - u_min, method, dist) lc_Low = lcEst1[::-1, :] # [-lcEst1[::-1, 0], lcEst1[::-1, 1::]] lc_Low[:, 0] *= -1 -# Est.Low = Est1; -# + if plotflag: plotbackend.semilogx(lcf, lcx, lc_High[:, 1], lc_High[:, 0], @@ -227,7 +223,6 @@ class LevelCrossings(PlotData): lc_out.phat_high = phat_high lc_out.phat_low = phat_low return lc_out - # def _extrapolate(self, lcx, lcf, u, offset, method, dist): # Extrapolate the level crossing spectra for high levels @@ -272,7 +267,7 @@ class LevelCrossings(PlotData): (r * sqrt(D[0]) * sin(ang), r * sqrt(D[1]) * cos(ang))) # plot(c0(1,:),c0(2,:)) - #* ones((1, len(c0))) # Transform to ellipse for (k,s) + # * ones((1, len(c0))) # Transform to ellipse for (k,s) c1 = np.dot(B, c0) + b[:, None] # plot(c1(1,:),c1(2,:)), hold on @@ -359,29 +354,29 @@ class LevelCrossings(PlotData): >>> ts = mat2timeseries(xs) >>> tp = ts.turning_points() >>> mm = tp.cycle_pairs() - >>> lc = mm.level_crossings() - + >>> lc = mm.level_crossings() + >>> xs2 = lc.sim(n,alpha) >>> ts2 = mat2timeseries(xs2) >>> Se = ts2.tospecdata(L=324) - + >>> alpha2 = Se.characteristic('alpha')[0] >>> np.round(alpha2*10) array([ 7.]) >>> np.abs(alpha-alpha2)<0.03 array([ True], dtype=bool) - + >>> h0 = S.plot('b') >>> h1 = Se.plot('r') - + >>> lc2 = ts2.turning_points().cycle_pairs().level_crossings() - + >>> import pylab as plt >>> h = plt.subplot(211) - >>> h2 = lc2.plot() + >>> h2 = lc2.plot() >>> h = plt.subplot(212) >>> h0 = lc.plot() - + """ # TODO: add a good example @@ -392,7 +387,7 @@ class LevelCrossings(PlotData): a2 = (tmp - rho_st) / (1 - tmp) y = vstack((a2 + rho_st, 1 - a2)).min(axis=0) maxidx = y.argmax() - #[maximum,maxidx]=max(y) + # [maximum,maxidx]=max(y) rho_st = rho_st[maxidx] a2 = a2[maxidx] @@ -401,13 +396,12 @@ class LevelCrossings(PlotData): r1 = -a1 / (1. + a2) r2 = (a1 ** 2 - a2 - a2 ** 2) / (1 + a2) sigma2 = r0 + a1 * r1 + a2 * r2 - #randn = np.random.randn + # randn = np.random.randn e = randn(ns) * sqrt(sigma2) e[:2] = 0.0 L0 = randn(1) L0 = hstack((L0, r1 * L0 + sqrt(1 - r2 ** 2) * randn(1))) - #%Simulate the process, starting in L0 - lfilter = scipy.signal.lfilter + # Simulate the process, starting in L0 z0 = lfilter([1, a1, a2], ones(1), L0) L, unused_zf = lfilter(ones(1), [1, a1, a2], e, axis=0, zi=z0) @@ -455,8 +449,8 @@ class LevelCrossings(PlotData): # # -# %Check the result without reference to getrfc: -## LCe = dat2lc(process) +# Check the result without reference to getrfc: +# LCe = dat2lc(process) # max(lc(:,2)) # max(LCe(:,2)) # @@ -464,7 +458,7 @@ class LevelCrossings(PlotData): # plot(lc(:,1),lc(:,2)/max(lc(:,2))) # hold on # plot(LCe(:,1),LCe(:,2)/max(LCe(:,2)),'-.') -## title('Relative crossing intensity') +# title('Relative crossing intensity') # # %% Plot made by the function funplot_4, JE 970707 # %param = [min(process(:,2)) max(process(:,2)) 100] @@ -482,7 +476,7 @@ class LevelCrossings(PlotData): Estimate transformation, g, from observed crossing intensity, version2. Assumption: a Gaussian process, Y, is related to the - non-Gaussian process, X, by Y = g(X). + non-Gaussian process, X, by Y = g(X). Parameters ---------- @@ -490,48 +484,51 @@ class LevelCrossings(PlotData): mean and standard deviation of the process **options : csm, gsm : real scalars - defines the smoothing of the crossing intensity and the transformation g. + defines the smoothing of the crossing intensity and the + transformation g. Valid values must be 0<=csm,gsm<=1. (default csm = 0.9 gsm=0.05) Smaller values gives smoother functions. - param : + param : vector which defines the region of variation of the data X. - (default [-5, 5, 513]). + (default [-5, 5, 513]). monitor : bool if true monitor development of estimation linextrap : bool - if true use a smoothing spline with a constraint on the ends to - ensure linear extrapolation outside the range of the data. (default) + if true use a smoothing spline with a constraint on the ends to + ensure linear extrapolation outside the range of data. (default) otherwise use a regular smoothing spline cvar, gvar : real scalars - Variances for the crossing intensity and the empirical transformation, g. (default 1) + Variances for the crossing intensity and the empirical + transformation, g. (default 1) ne : scalar integer - Number of extremes (maxima & minima) to remove from the estimation - of the transformation. This makes the estimation more robust against - outliers. (default 7) + Number of extremes (maxima & minima) to remove from the estimation + of the transformation. This makes the estimation more robust + against outliers. (default 7) ntr : scalar integer - Maximum length of empirical crossing intensity. The empirical - crossing intensity is interpolated linearly before smoothing if the - length exceeds ntr. A reasonable NTR (eg. 1000) will significantly - speed up the estimation for long time series without loosing any accuracy. - NTR should be chosen greater than PARAM(3). (default inf) - + Maximum length of empirical crossing intensity. The empirical + crossing intensity is interpolated linearly before smoothing if + the length exceeds ntr. A reasonable NTR (eg. 1000) will + significantly speed up the estimation for long time series without + loosing any accuracy. NTR should be chosen greater than PARAM(3). + (default inf) + Returns ------- - gs, ge : TrData objects - smoothed and empirical estimate of the transformation g. - - + gs, ge : TrData objects + smoothed and empirical estimate of the transformation g. + + Notes ----- The empirical crossing intensity is usually very irregular. More than one local maximum of the empirical crossing intensity may cause poor fit of the transformation. In such case one - should use a smaller value of GSM or set a larger variance for GVAR. - If X(t) is likely to cross levels higher than 5 standard deviations - then the vector param has to be modified. For example if X(t) is - unlikely to cross a level of 7 standard deviations one can use + should use a smaller value of GSM or set a larger variance for GVAR. + If X(t) is likely to cross levels higher than 5 standard deviations + then the vector param has to be modified. For example if X(t) is + unlikely to cross a level of 7 standard deviations one can use param = [-7 7 513]. - + Example ------- >>> import wafo.spectrum.models as sm @@ -540,15 +537,16 @@ class LevelCrossings(PlotData): >>> Hs = 7.0 >>> Sj = sm.Jonswap(Hm0=Hs) >>> S = Sj.tospecdata() #Make spectrum object from numerical values - >>> S.tr = tm.TrOchi(mean=0, skew=0.16, kurt=0, sigma=Hs/4, ysigma=Hs/4) + >>> S.tr = tm.TrOchi(mean=0, skew=0.16, kurt=0, + ... sigma=Hs/4, ysigma=Hs/4) >>> xs = S.sim(ns=2**16, iseed=10) >>> ts = mat2timeseries(xs) >>> tp = ts.turning_points() >>> mm = tp.cycle_pairs() - >>> lc = mm.level_crossings() - >>> g0, g0emp = lc.trdata(monitor=True) # Monitor the development + >>> lc = mm.level_crossings() + >>> g0, g0emp = lc.trdata(plotflag=1) >>> g1, g1emp = lc.trdata(gvar=0.5 ) # Equal weight on all points - >>> g2, g2emp = lc.trdata(gvar=[3.5, 0.5, 3.5]) # Less weight on the ends + >>> g2, g2emp = lc.trdata(gvar=[3.5, 0.5, 3.5]) # Less weight on ends >>> int(S.tr.dist2gauss()*100) 141 >>> int(g0emp.dist2gauss()*100) @@ -559,111 +557,28 @@ class LevelCrossings(PlotData): 162 >>> int(g2.dist2gauss()*100) 120 - + g0.plot() # Check the fit. - + See also troptset, dat2tr, trplot, findcross, smooth - + NB! the transformated data will be N(0,1) - + Reference - --------- + --------- Rychlik , I., Johannesson, P., and Leadbetter, M.R. (1997) - "Modelling and statistical analysis of ocean wavedata + "Modelling and statistical analysis of ocean wavedata using a transformed Gaussian process", - Marine structures, Design, Construction and Safety, + Marine structures, Design, Construction and Safety, Vol 10, pp 13--47 ''' - - if mean is None: - mean = self.mean - if sigma is None: - sigma = self.sigma - - opt = DotDict(chkder=True, plotflag=False, csm=0.9, gsm=.05, - param=(-5, 5, 513), delay=2, linextrap=True, ntr=10000, ne=7, gvar=1) - opt.update(options) - param = opt.param - Ne = opt.ne - - ncr = len(self.data) - if ncr > opt.ntr and opt.ntr > 0: - x0 = linspace(self.args[Ne], self.args[-1 - Ne], opt.ntr) - lc1, lc2 = x0, interp(x0, self.args, self.data) - Ne = 0 - Ner = opt.ne - ncr = opt.ntr - else: - Ner = 0 - lc1, lc2 = self.args, self.data - ng = len(atleast_1d(opt.gvar)) - if ng == 1: - gvar = opt.gvar * ones(ncr) - else: - gvar = interp1d(linspace(0, 1, ng), opt.gvar, kind='linear')( - linspace(0, 1, ncr)) - - uu = linspace(*param) - g1 = sigma * uu + mean - - if Ner > 0: # Compute correction factors - cor1 = trapz(lc2[0:Ner + 1], lc1[0:Ner + 1]) - cor2 = trapz(lc2[-Ner - 1::], lc1[-Ner - 1::]) - else: - cor1 = 0 - cor2 = 0 - - lc22 = hstack((0, cumtrapz(lc2, lc1) + cor1)) - - if self.intensity: - lc22 = (lc22 + 0.5 / ncr) / (lc22[-1] + cor2 + 1. / ncr) - else: - lc22 = (lc22 + 0.5) / (lc22[-1] + cor2 + 1) - - lc11 = (lc1 - mean) / sigma - - lc22 = invnorm(lc22) # - ymean - - g2 = TrData(lc22.copy(), lc1.copy(), mean=mean, sigma=sigma) - g2.setplotter('step') - # NB! the smooth function does not always extrapolate well outside the edges - # causing poor estimate of g - # We may alleviate this problem by: forcing the extrapolation - # to be linear outside the edges or choosing a lower value for csm2. - - inds = slice(Ne, ncr - Ne) # indices to points we are smoothing over - slc22 = SmoothSpline( - lc11[inds], lc22[inds], opt.gsm, opt.linextrap, gvar[inds])(uu) - - g = TrData(slc22.copy(), g1.copy(), mean=mean, sigma=sigma) - - if opt.chkder: - for ix in range(5): - dy = diff(g.data) - if any(dy <= 0): - warnings.warn( - ''' The empirical crossing spectrum is not sufficiently smoothed. - The estimated transfer function, g, is not a strictly increasing function. - ''') - eps = finfo(float).eps - dy[dy > 0] = eps - gvar = -(hstack((dy, 0)) + hstack((0, dy))) / 2 + eps - g.data = SmoothSpline( - g.args, g.data, 1, opt.linextrap, ix * gvar)(g.args) - else: - break - - if opt.plotflag > 0: - g.plot() - g2.plot() - - return g, g2 + estimate = TransformEstimator(**options) + return estimate._trdata_lc(self, mean, sigma) def test_levelcrossings_extrapolate(): import wafo.data - #import wafo.objects as wo x = wafo.data.sea() ts = mat2timeseries(x) @@ -676,7 +591,6 @@ def test_levelcrossings_extrapolate(): class CyclePairs(PlotData): - ''' Container class for Cycle Pairs data objects in WAFO @@ -691,7 +605,7 @@ class CyclePairs(PlotData): >>> import wafo.objects as wo >>> x = wafo.data.sea() >>> ts = wo.mat2timeseries(x) - + >>> tp = ts.turning_points() >>> mm = tp.cycle_pairs() >>> h1 = mm.plot(marker='x') @@ -719,14 +633,14 @@ class CyclePairs(PlotData): Parameters ---------- beta : array-like, size m - Beta-values, material parameter. + Beta-values, material parameter. K : scalar, optional K-value, material parameter. Returns ------- D : ndarray, size m - Damage. + Damage. Notes ----- @@ -755,6 +669,17 @@ class CyclePairs(PlotData): amp = abs(self.amplitudes()) return atleast_1d([K * np.sum(amp ** betai) for betai in beta]) + def get_minima_and_maxima(self): + index, = nonzero(self.args <= self.data) + if index.size == 0: + index, = nonzero(self.args >= self.data) + M = self.args[index] + m = self.data[index] + else: + m = self.args[index] + M = self.data[index] + return m, M + def level_crossings(self, kind='uM', intensity=False): """ Return level crossing spectrum from a cycle count. @@ -774,7 +699,6 @@ class CyclePairs(PlotData): lc : level crossing object with levels and number of upcrossings. - Calculates the number of upcrossings from a cycle pairs, e.g. min2Max cycles or rainflow cycles. @@ -803,20 +727,9 @@ class CyclePairs(PlotData): if ((defnr < 0) or (defnr > 3)): raise ValueError('kind must be one of (1,2,3,4).') - index, = nonzero(self.args <= self.data) - if index.size == 0: - index, = nonzero(self.args >= self.data) - M = self.args[index] - m = self.data[index] - else: - m = self.args[index] - M = self.data[index] + m, M = self.get_minima_and_maxima() -# if isempty(index) -# error('Error in input cc.') -# end ncc = len(m) - minima = vstack((m, ones(ncc), zeros(ncc), ones(ncc))) maxima = vstack((M, -ones(ncc), ones(ncc), zeros(ncc))) @@ -835,7 +748,6 @@ class CyclePairs(PlotData): ii += 1 extr[:, ii] = extremes[:, i] - #[xx nx]=max(extr(:,1)) nx = extr[0].argmax() + 1 levels = extr[0, 0:nx] if defnr == 2: # This are upcrossings + maxima @@ -851,7 +763,8 @@ class CyclePairs(PlotData): if intensity: dcount = dcount / self.time ylab = 'Intensity [count/sec]' - return LevelCrossings(dcount, levels, mean=self.mean, sigma=self.sigma, ylab=ylab, intensity=intensity) + return LevelCrossings(dcount, levels, mean=self.mean, sigma=self.sigma, + ylab=ylab, intensity=intensity) class TurningPoints(PlotData): @@ -863,14 +776,14 @@ class TurningPoints(PlotData): ---------------- data : array_like args : vector for 1D - + Examples -------- >>> import wafo.data >>> import wafo.objects as wo >>> x = wafo.data.sea() >>> ts = wo.mat2timeseries(x) - + >>> tp = ts.turning_points() >>> h1 = tp.plot(marker='x') ''' @@ -881,7 +794,6 @@ class TurningPoints(PlotData): self.mean = kwds.pop('mean', None) options = dict(title='Turning points') - # plot_args=['b.']) options.update(**kwds) super(TurningPoints, self).__init__(*args, **options) @@ -893,7 +805,7 @@ class TurningPoints(PlotData): self.data = ravel(self.data) def rainflow_filter(self, h=0.0, method='clib'): - ''' + ''' Return rainflow filtered turning points (tp). Parameters @@ -981,7 +893,6 @@ class TurningPoints(PlotData): iM = 1 # Extract min-max and max-min cycle pairs - #n = len(self.data) if kind.lower().startswith('min2max'): m = data[im:-1:2] M = data[im + 1::2] @@ -998,10 +909,10 @@ class TurningPoints(PlotData): def cycle_astm(self): """ Rainflow counted cycles according to Nieslony's ASTM implementation - + Parameters ---------- - + Returns ------- sig_rfc : array-like @@ -1009,21 +920,21 @@ class TurningPoints(PlotData): sig_rfc[:,0] Cycles amplitude sig_rfc[:,1] Cycles mean value sig_rfc[:,2] Cycle type, half (=0.5) or full (=1.0) - + References ---------- - Adam Nieslony, "Determination of fragments of multiaxial service loading - strongly influencing the fatigue of machine components", + Adam Nieslony, "Determination of fragments of multiaxial service + loading strongly influencing the fatigue of machine components", Mechanical Systems and Signal Processing 23, no. 8 (2009): 2712-2721. - + and is based on the following standard: ASTM E 1049-85 (Reapproved 1997), Standard practices for cycle counting - in fatigue analysis, in: Annual Book of ASTM Standards, + in fatigue analysis, in: Annual Book of ASTM Standards, vol. 03.01, ASTM, Philadelphia, 1999, pp. 710-718. - + Copyright (c) 1999-2002 by Adam Nieslony Ported to Python by David Verelst - + Example ------- >>> import wafo @@ -1048,7 +959,6 @@ def mat2timeseries(x): class TimeSeries(PlotData): - ''' Container class for 1D TimeSeries data objects in WAFO @@ -1071,17 +981,14 @@ class TimeSeries(PlotData): >>> ts = wo.mat2timeseries(x) >>> rf = ts.tocovdata(lag=150) >>> h = rf.plot() - + >>> S = ts.tospecdata() - The default L is set to 325 - >>> tp = ts.turning_points() >>> mm = tp.cycle_pairs() >>> h1 = mm.plot(marker='x') - + >>> lc = mm.level_crossings() >>> h2 = lc.plot() - ''' def __init__(self, *args, **kwds): @@ -1108,26 +1015,28 @@ class TimeSeries(PlotData): See also ''' - dt1 = self.args[1] - self.args[0] - n = size(self.args) - 1 - t = self.args[-1] - self.args[0] + t_vec = self.args + dt1 = t_vec[1] - t_vec[0] + n = len(t_vec) - 1 + t = t_vec[-1] - t_vec[0] dt = t / n if abs(dt - dt1) > 1e-10: warnings.warn('Data is not uniformly sampled!') return dt - def tocovdata(self, lag=None, flag='biased', norm=False, dt=None): - ''' + def tocovdata(self, lag=None, tr=None, detrend=detrend_mean, + window='boxcar', flag='biased', norm=False, dt=None): + ''' Return auto covariance function from data. Parameters ---------- lag : scalar, int maximum time-lag for which the ACF is estimated. (Default lag=n-1) - flag : string, 'biased' or 'unbiased' + flag : string, 'biased' or 'unbiased' If 'unbiased' scales the raw correlation by 1/(n-abs(k)), - where k is the index into the result, otherwise scales the raw - cross-correlation by 1/n. (default) + where k is the index into the result, otherwise scales the raw + cross-correlation by 1/n. (default) norm : bool True if normalize output to one dt : scalar @@ -1158,53 +1067,14 @@ class TimeSeries(PlotData): >>> acf = ts.tocovdata(150) >>> h = acf.plot() ''' - n = len(self.data) - if not lag: - lag = n - 1 - - x = self.data.flatten() - indnan = isnan(x) - if any(indnan): - x = x - x[1 - indnan].mean() # remove the mean pab 09.10.2000 - #indnan = find(indnan) - Ncens = n - sum(indnan) - x[indnan] = 0. # pab 09.10.2000 much faster for censored samples - else: - indnan = None - Ncens = n - x = x - x.mean() - - #fft = np.fft.fft - nfft = 2 ** nextpow2(n) - Rper = abs(fft(x, nfft)) ** 2 / Ncens # Raw periodogram - - R = np.real(fft(Rper)) / nfft # %ifft=fft/nfft since Rper is real! - lags = range(0, lag + 1) - if flag.startswith('unbiased'): - # unbiased result, i.e. divide by n-abs(lag) - R = R[lags] * Ncens / arange(Ncens, Ncens - lag, -1) - # else % biased result, i.e. divide by n - # r=r(1:L+1)*Ncens/Ncens - - c0 = R[0] - if norm: - R = R / c0 - r0 = R[0] - if dt is None: - dt = self.sampling_period() - t = linspace(0, lag * dt, lag + 1) - #cumsum = np.cumsum - acf = _wafocov.CovData1D(R[lags], t) - acf.sigma = sqrt( - r_[0, r0 ** 2, r0 ** 2 + 2 * cumsum(R[1:] ** 2)] / Ncens) - acf.children = [ - PlotData(-2. * acf.sigma[lags], t), PlotData(2. * acf.sigma[lags], t)] - acf.plot_args_children = ['r:'] - acf.norm = norm - return acf - - def _specdata(self, L=None, tr=None, method='cov', detrend=detrend_mean, window=parzen, noverlap=0, pad_to=None): - """ + estimate_cov = _wafocov_estimation.CovarianceEstimator( + lag=lag, tr=tr, detrend=detrend, window=window, flag=flag, + norm=norm, dt=dt) + return estimate_cov(self) + + def _specdata(self, L=None, tr=None, method='cov', detrend=detrend_mean, + window='parzen', noverlap=0, pad_to=None): + """ Obsolete: Delete? Return power spectral density by Welches average periodogram method. @@ -1239,7 +1109,6 @@ class TimeSeries(PlotData): Procedures, John Wiley & Sons """ dt = self.sampling_period() - #fs = 1. / (2 * dt) yy = self.data.ravel() if tr is None else tr.dat2gauss( self.data.ravel()) yy = detrend(yy) if hasattr(detrend, '__call__') else yy @@ -1250,285 +1119,170 @@ class TimeSeries(PlotData): w = fact * f return _wafospec.SpecData1D(S / fact, w) - def tospecdata(self, L=None, tr=None, method='cov', detrend=detrend_mean, window=parzen, noverlap=0, ftype='w', alpha=None): + def _get_bandwidth_and_dof(self, wname, n, L, dt): + '''Returns bandwidth (rad/sec) and degrees of freedom + used in chi^2 distribution + ''' + Be = v = None + if isinstance(wname, tuple): + wname = wname[0] + if wname == 'parzen': + v = int(3.71 * n / L) + Be = 2 * pi * 1.33 / (L * dt) + elif wname == 'hanning': + v = int(2.67 * n / L) + Be = 2 * pi / (L * dt) + elif wname == 'bartlett': + v = int(3 * n / L) + Be = 2 * pi * 1.33 / (L * dt) + return Be, v + + def tospecdata(self, L=None, tr=None, method='cov', detrend=detrend_mean, + window='parzen', noverlap=0, ftype='w', alpha=None): ''' Estimate one-sided spectral density from data. - + Parameters ---------- L : scalar integer - maximum lag size of the window function. As L decreases the estimate - becomes smoother and Bw increases. If we want to resolve peaks in - S which is Bf (Hz or rad/sec) apart then Bw < Bf. If no value is given the - lag size is set to be the lag where the auto correlation is less than - 2 standard deviations. (maximum 300) + maximum lag size of the window function. As L decreases the + estimate becomes smoother and Bw increases. If we want to resolve + peaks in S which is Bf (Hz or rad/sec) apart then Bw < Bf. If no + value is given the lag size is set to be the lag where the auto + correlation is less than 2 standard deviations. (maximum 300) tr : transformation object - the transformation assuming that x is a sample of a transformed - Gaussian process. If g is None then x is a sample of a Gaussian process (Default) + the transformation assuming that x is a sample of a transformed + Gaussian process. If g is None then x is a sample of a Gaussian + process (Default) method : string defining estimation method. Options are - 'cov' : Frequency smoothing using a parzen window function + 'cov' : Frequency smoothing using the window function on the estimated autocovariance function. (default) - 'psd' : Welch's averaged periodogram method with no overlapping batches + 'psd' : Welch's averaged periodogram method with no overlapping + batches detrend : function defining detrending performed on the signal before estimation. - (default detrend_mean) + (default detrend_mean) window : vector of length NFFT or function To create window vectors see numpy.blackman, numpy.hamming, numpy.bartlett, scipy.signal, scipy.signal.get_window etc. noverlap : scalar int gives the length of the overlap between segments. ftype : character - defining frequency type: 'w' or 'f' (default 'w') - + defining frequency type: 'w' or 'f' (default 'w') + Returns --------- spec : SpecData1D object - - + Example ------- x = load('sea.dat'); S = dat2spec(x); specplot(S) - + See also -------- dat2tr, dat2cov - - + References: ----------- Georg Lindgren and Holger Rootzen (1986) "Stationara stokastiska processer", pp 173--176. - + Gareth Janacek and Louise Swift (1993) "TIME SERIES forecasting, simulation, applications", pp 75--76 and 261--268 - + Emanuel Parzen (1962), "Stochastic Processes", HOLDEN-DAY, pp 66--103 ''' - #% Initialize constants - #%~~~~~~~~~~~~~~~~~~~~~ nugget = 1e-12 - rate = 2 - # % interpolationrate for frequency - - wdef = 1 - # % 1=parzen window 2=hanning window, 3= bartlett window - + rate = 2 # interpolationrate for frequency dt = self.sampling_period() - #yy = self.data if tr is None else tr.dat2gauss(self.data) - yy = self.data.ravel() if tr is None else tr.dat2gauss( - self.data.ravel()) + + yy = self.data.ravel() + if not (tr is None): + yy = tr.dat2gauss(yy) yy = detrend(yy) if hasattr(detrend, '__call__') else yy n = len(yy) - L = min(L, n) + L = min(L, n - 1) - max_L = min(300, n) - # % maximum lag if L is undetermined estimate_L = L is None - if estimate_L: - L = min(n - 2, int(4. / 3 * max_L + 0.5)) - if method == 'cov' or estimate_L: tsy = TimeSeries(yy, self.args) - R = tsy.tocovdata() - if estimate_L: - # finding where ACF is less than 2 st. deviations. - # a better L value - L = max_L + 2 - \ - (np.abs(R.data[max_L::-1]) > 2 * R.sigma[ - max_L::-1]).argmax() - # modify L so that hanning and Parzen give appr. the same - # result - if wdef == 1: - L = min(int(4 * L / 3), n - 2) - print('The default L is set to %d' % L) - try: - win = window(2 * L - 1) - wname = window.__name__ - if wname == 'parzen': - # degrees of freedom used in chi^2 distribution - v = int(3.71 * n / L) - Be = 2 * pi * 1.33 / (L * dt) # % bandwidth (rad/sec) - elif wname == 'hanning': - # degrees of freedom used in chi^2 distribution - v = int(2.67 * n / L) - Be = 2 * pi / (L * dt) - # % bandwidth (rad/sec) - elif wname == 'bartlett': - # degrees of freedom used in chi^2 distribution - v = int(3 * n / L) - Be = 2 * pi * 1.33 / (L * dt) - # bandwidth (rad/sec) - except: - wname = None - win = window - v = None - Be = None - + R = tsy.tocovdata(lag=L, window=window) + L = len(R.data) - 1 + if method == 'cov': + # add a nugget effect to ensure that round off errors + # do not result in negative spectral estimates + spec = R.tospecdata(rate=rate, nugget=nugget) if method == 'psd': nfft = 2 ** nextpow2(L) pad_to = rate * nfft # Interpolate the spectrum with rate - S, f = psd( - yy, Fs=1. / dt, NFFT=nfft, detrend=detrend, window=window(nfft), - noverlap=noverlap, pad_to=pad_to, scale_by_freq=True) + f, S = welch(yy, fs=1.0 / dt, window=window, nperseg=nfft, + noverlap=noverlap, nfft=pad_to, detrend=detrend, + return_onesided=True, scaling='density', axis=-1) +# S, f = psd(yy, Fs=1. / dt, NFFT=nfft, detrend=detrend, +# window=win, noverlap=noverlap, pad_to=pad_to, +# scale_by_freq=True) fact = 2.0 * pi w = fact * f spec = _wafospec.SpecData1D(S / fact, w) - else: # cov method - # add a nugget effect to ensure that round off errors - # do not result in negative spectral estimates - - R.data[:L] = R.data[:L] * win[L - 1::] - R.data[L] = 0.0 - R.data = R.data[:L + 1] - R.args = R.args[:L + 1] - # R.plot() - # R.show() - spec = R.tospecdata(rate=rate, nugget=nugget) + else: + raise ValueError('Unknown method (%s)' % method) + Be, v = self._get_bandwidth_and_dof(window, n, L, dt) spec.Bw = Be if ftype == 'f': spec.Bw = Be / (2 * pi) # bandwidth in Hz if alpha is not None: - #% Confidence interval constants - spec.CI = [ - v / _invchi2(1 - alpha / 2, v), v / _invchi2(alpha / 2, v)] + # Confidence interval constants + spec.CI = [v / _invchi2(1 - alpha / 2, v), + v / _invchi2(alpha / 2, v)] spec.tr = tr spec.L = L spec.norm = False spec.note = 'method=%s' % method -# S = createspec('freq',ftype); -# S.tr = g; -# S.note = ['dat2spec(',inputname(1),'), Method = ' method ]; -# S.norm = 0; % not normalized -# S.L = L; -# S.S = zeros(nf+1,m-1); return spec - def _trdata_cdf(self, **options): - ''' - Estimate transformation, g, from observed marginal CDF. - Assumption: a Gaussian process, Y, is related to the - non-Gaussian process, X, by Y = g(X). - Parameters - ---------- - options = options structure defining how the smoothing is done. - (See troptset for default values) - Returns - ------- - tr, tr_emp = smoothed and empirical estimate of the transformation g. - - The empirical CDF is usually very irregular. More than one local - maximum of the empirical CDF may cause poor fit of the transformation. - In such case one should use a smaller value of GSM or set a larger - variance for GVAR. If X(t) is likely to cross levels higher than 5 - standard deviations then the vector param has to be modified. For - example if X(t) is unlikely to cross a level of 7 standard deviations - one can use param = [-7 7 513]. - - ''' - - mean = self.data.mean() - sigma = self.data.std() - cdf = edf(self.data.ravel()) - - opt = DotDict( - chkder=True, plotflag=False, gsm=0.05, param=[-5, 5, 513], - delay=2, linextrap=True, ntr=1000, ne=7, gvar=1) - opt.update(options) - Ne = opt.ne - nd = len(cdf.data) - if nd > opt.ntr and opt.ntr > 0: - x0 = linspace(cdf.args[Ne], cdf.args[nd - 1 - Ne], opt.ntr) - cdf.data = interp(x0, cdf.args, cdf.data) - cdf.args = x0 - Ne = 0 - uu = linspace(*opt.param) - - ncr = len(cdf.data) - ng = len(np.atleast_1d(opt.gvar)) - if ng == 1: - gvar = opt.gvar * ones(ncr) - else: - opt.gvar = np.atleast_1d(opt.gvar) - gvar = interp( - linspace(0, 1, ncr), linspace(0, 1, ng), opt.gvar.ravel()) - - ind = np.flatnonzero(diff(cdf.args) > 0) # remove equal points - nd = len(ind) - ind1 = ind[Ne:nd - Ne] - tmp = invnorm(cdf.data[ind]) - - x = sigma * uu + mean - pp_tr = SmoothSpline( - cdf.args[ind1], tmp[Ne:nd - Ne], p=opt.gsm, lin_extrap=opt.linextrap, var=gvar[ind1]) - # g(:,2) = smooth(Fx(ind1,1),tmp(Ne+1:end-Ne),opt.gsm,g(:,1),def,gvar); - tr = TrData(pp_tr(x), x, mean=mean, sigma=sigma) - tr_emp = TrData(tmp, cdf.args[ind], mean=mean, sigma=sigma) - tr_emp.setplotter('step') - - if opt.chkder: - for ix in xrange(5): - dy = diff(tr.data) - if (dy <= 0).any(): - dy[dy > 0] = floatinfo.eps - gvar = - \ - (np.hstack((dy, 0)) + np.hstack((0, dy))) / \ - 2 + floatinfo.eps - pp_tr = SmoothSpline(cdf.args[ind1], tmp[ - Ne:nd - Ne], p=1, lin_extrap=opt.linextrap, var=ix * gvar) - tr = TrData(pp_tr(x), x, mean=mean, sigma=sigma) - else: - break - else: - msg = '''The empirical distribution is not sufficiently smoothed. - The estimated transfer function, g, is not - a strictly increasing function.''' - warnings.warn(msg) - - if opt.plotflag > 0: - tr.plot() - tr_emp.plot() - return tr, tr_emp - def trdata(self, method='nonlinear', **options): ''' Estimate transformation, g, from data. - + Parameters - ---------- - method : string - 'nonlinear' : transform based on smoothed crossing intensity (default) - 'mnonlinear': transform based on smoothed marginal distribution - 'hermite' : transform based on cubic Hermite polynomial - 'ochi' : transform based on exponential function + ---------- + method : string defining transform based on: + 'nonlinear' : smoothed crossing intensity (default) + 'mnonlinear': smoothed marginal distribution + 'hermite' : cubic Hermite polynomial + 'ochi' : exponential function 'linear' : identity. - + options : keyword with the following fields: - csm,gsm - defines the smoothing of the logarithm of crossing intensity - and the transformation g, respectively. Valid values must - be 0<=csm,gsm<=1. (default csm=0.9, gsm=0.05) - Smaller values gives smoother functions. - param - vector which defines the region of variation of the data x. - (default see lc2tr). - plotflag - 0 no plotting (Default) - 1 plots empirical and smoothed g(u) and the theoretical for - a Gaussian model. - 2 monitor the development of the estimation - linextrap - 0 use a regular smoothing spline - 1 use a smoothing spline with a constraint on the ends to - ensure linear extrapolation outside the range of the data. - (default) - gvar - Variances for the empirical transformation, g. (default 1) - ne - Number of extremes (maxima & minima) to remove from the + csm, gsm : real scalars + defines the smoothing of the logarithm of crossing intensity and + the transformation g, respectively. Valid values must be + 0<=csm,gsm<=1. (default csm=0.9, gsm=0.05) + Smaller values gives smoother functions. + param : vector (default see lc2tr) + which defines the region of variation of the data x. + plotflag : int + 0 no plotting (Default) + 1 plots empirical and smoothed g(u) and the theoretical for a + Gaussian model. + 2 monitor the development of the estimation + linextrap: int + 0 use a regular smoothing spline + 1 use a smoothing spline with a constraint on the ends to ensure + linear extrapolation outside the range of the data. (default) + gvar: real scalar + Variances for the empirical transformation, g. (default 1) + ne - Number of extremes (maxima & minima) to remove from the estimation of the transformation. This makes the estimation more robust against outliers. (default 7) ntr - Maximum length of empirical crossing intensity or CDF. @@ -1538,28 +1292,28 @@ class TimeSeries(PlotData): estimation for long time series without loosing any accuracy. NTR should be chosen greater than PARAM(3). (default 1000) - + Returns ------- tr, tr_emp : TrData objects - with the smoothed and empirical transformation, respectively. - - - TRDATA estimates the transformation in a transformed Gaussian model. + with the smoothed and empirical transformation, respectively. + + + TRDATA estimates the transformation in a transformed Gaussian model. Assumption: a Gaussian process, Y, is related to the - non-Gaussian process, X, by Y = g(X). - + non-Gaussian process, X, by Y = g(X). + The empirical crossing intensity is usually very irregular. - More than one local maximum of the empirical crossing intensity - may cause poor fit of the transformation. In such case one - should use a smaller value of CSM. In order to check the effect - of smoothing it is recomended to also plot g and g2 in the same plot or - plot the smoothed g against an interpolated version of g (when CSM=GSM=1). - If x is likely to cross levels higher than 5 standard deviations - then the vector param has to be modified. For example if x is - unlikely to cross a level of 7 standard deviations one can use + More than one local maximum of the empirical crossing intensity may + cause poor fit of the transformation. In such case one should use a + smaller value of CSM. In order to check the effect of smoothing it is + recomended to also plot g and g2 in the same plot or plot the smoothed + g against an interpolated version of g (when CSM=GSM=1). + If x is likely to cross levels higher than 5 standard deviations + then the vector param has to be modified. For example if x is + unlikely to cross a level of 7 standard deviations one can use PARAM=[-7 7 513]. - + Example ------- >>> import wafo.spectrum.models as sm @@ -1568,12 +1322,13 @@ class TimeSeries(PlotData): >>> Hs = 7.0 >>> Sj = sm.Jonswap(Hm0=Hs) >>> S = Sj.tospecdata() #Make spectrum object from numerical values - >>> S.tr = tm.TrOchi(mean=0, skew=0.16, kurt=0, sigma=Hs/4, ysigma=Hs/4) + >>> S.tr = tm.TrOchi(mean=0, skew=0.16, kurt=0, + ... sigma=Hs/4, ysigma=Hs/4) >>> xs = S.sim(ns=2**16, iseed=10) >>> ts = mat2timeseries(xs) - >>> g0, g0emp = ts.trdata(monitor=True) # Monitor the development - >>> g1, g1emp = ts.trdata(method='m', gvar=0.5 ) # Equal weight on all points - >>> g2, g2emp = ts.trdata(method='n', gvar=[3.5, 0.5, 3.5]) # Less weight on the ends + >>> g0, g0emp = ts.trdata(plotflag=1) + >>> g1, g1emp = ts.trdata(method='m', gvar=0.5 ) + >>> g2, g2emp = ts.trdata(method='n', gvar=[3.5, 0.5, 3.5]) >>> int(S.tr.dist2gauss()*100) 141 >>> int(g0emp.dist2gauss()*100) @@ -1584,60 +1339,30 @@ class TimeSeries(PlotData): 66 >>> int(g2.dist2gauss()*100) 84 - + See also -------- - LevelCrossings.trdata + LevelCrossings.trdata wafo.transform.models - + References ---------- Rychlik, I. , Johannesson, P and Leadbetter, M. R. (1997) - "Modelling and statistical analysis of ocean wavedata using + "Modelling and statistical analysis of ocean wavedata using transformed Gaussian process." - Marine structures, Design, Construction and Safety, Vol. 10, No. 1, pp 13--47 - - + Marine structures, Design, Construction and Safety, Vol. 10, No. 1, + pp 13--47 + Brodtkorb, P, Myrhaug, D, and Rue, H (1999) "Joint distribution of wave height and crest velocity from reconstructed data" - in Proceedings of 9th ISOPE Conference, Vol III, pp 66-73 + in Proceedings of 9th ISOPE Conference, Vol III, pp 66-73 ''' - - # opt = troptset('plotflag','off','csm',.95,'gsm',.05,.... - # 'param',[-5 5 513],'delay',2,'linextrap','on','ne',7,... - # 'cvar',1,'gvar',1,'multip',0); - opt = DotDict(chkder=True, plotflag=False, csm=.95, gsm=.05, - param=[-5, 5, 513], delay=2, ntr=1000, linextrap=True, ne=7, cvar=1, gvar=1, - multip=False, crossdef='uM') - opt.update(**options) - - ma = self.data.mean() - sa = self.data.std() - - if method.startswith('lin'): - return TrLinear(mean=ma, sigma=sa) - - if method[0] == 'n': - tp = self.turning_points() - mM = tp.cycle_pairs() - lc = mM.level_crossings(opt.crossdef) - return lc.trdata(mean=ma, sigma=sa, **opt) - elif method[0] == 'm': - return self._trdata_cdf(**opt) - elif method[0] == 'h': - ga1 = skew(self.data) - ga2 = kurtosis(self.data, fisher=True) # kurt(xx(n+1:end))-3; - up = min(4 * (4 * ga1 / 3) ** 2, 13) - lo = (ga1 ** 2) * 3 / 2 - kurt1 = min(up, max(ga2, lo)) + 3 - return TrHermite(mean=ma, var=sa ** 2, skew=ga1, kurt=kurt1) - elif method[0] == 'o': - ga1 = skew(self.data) - return TrOchi(mean=ma, var=sa ** 2, skew=ga1) + estimate = TransformEstimator(method=method, **options) + return estimate.trdata(self) def turning_points(self, h=0.0, wavetype=None): - ''' + ''' Return turning points (tp) from data, optionally rainflowfiltered. Parameters @@ -1647,7 +1372,6 @@ class TimeSeries(PlotData): if h<=0, then tp is a sequence of turning points (default) if h>0, then all rainflow cycles with height smaller than h are removed. - wavetype : string defines the type of wave. Possible options are 'astm' 'mw' 'Mw' or 'none'. @@ -1691,7 +1415,7 @@ class TimeSeries(PlotData): return TurningPoints(self.data[ind], t, mean=mean, sigma=sigma) def trough_crest(self, v=None, wavetype=None): - """ + """ Return trough and crest turning points Parameters @@ -1728,19 +1452,19 @@ class TimeSeries(PlotData): ---------- rate : scalar integer interpolation rate. Interpolates with spline if greater than one. - + Returns ------- parameters : dict wave parameters such as Ac, At : Crest and trough amplitude, respectively Tcf, Tcb : Crest front and crest (rear) back period, respectively - Hu, Hd : zero-up-crossing and zero-downcrossing wave height, respectively. - Tu, Td : zero-up-crossing and zero-downcrossing wave period, respectively. - - The definition of g, Ac,At, Tcf, etc. are given in gravity and - wafo.definitions. - + Hu, Hd : zero-up- and down-crossing wave height, respectively. + Tu, Td : zero-up- and down-crossing wave period, respectively. + + The definition of g, Ac,At, Tcf, etc. are given in gravity and + wafo.definitions. + Example ------- >>> import wafo.data as wd @@ -1758,14 +1482,14 @@ class TimeSeries(PlotData): ('Td', array([ 3.84377468, 6.35707656])) ('Tcf', array([ 0.42656819, 0.57361617])) ('Tcb', array([ 0.93355982, 1.04063638])) - + >>> import pylab as plt >>> h = plt.plot(wp['Td'],wp['Hd'],'.') >>> h = plt.xlabel('Td [s]') >>> h = plt.ylabel('Hd [m]') - - - See also + + + See also -------- wafo.definitions ''' @@ -1776,7 +1500,6 @@ class TimeSeries(PlotData): n = len(self.args) ti = linspace(t0, tn, int(rate * n)) xi = interp1d(self.args, self.data.ravel(), kind='cubic')(ti) - else: ti, xi = self.args, self.data.ravel() @@ -1794,8 +1517,7 @@ class TimeSeries(PlotData): Tcf = tc_t[1::2] - tu[:-1] Tcf[(Tcf == 0)] = dT # avoiding division by zero Tcb = td[1:] - tc_t[1::2] - Tcb[(Tcb == 0)] = dT - # % avoiding division by zero + Tcb[(Tcb == 0)] = dT # avoiding division by zero return dict(Ac=Ac, At=At, Hu=Hu, Hd=Hd, Tu=Tu, Td=Td, Tcf=Tcf, Tcb=Tcb) def wave_height_steepness(self, method=1, rate=1, g=None): @@ -1806,10 +1528,10 @@ class TimeSeries(PlotData): ---------- rate : scalar integer interpolation rate. Interpolates with spline if greater than one. - - method : scalar integer + + method : scalar integer (default 1) 0 max(Vcf, Vcb) and corresponding wave height Hd or Hu in H - 1 crest front (rise) speed (Vcf) in S and wave height Hd in H. (default) + 1 crest front (rise) speed (Vcf) in S and wave height Hd in H. -1 crest back (fall) speed (Vcb) in S and waveheight Hu in H. 2 crest front steepness in S and the wave height Hd in H. -2 crest back steepness in S and the wave height Hu in H. @@ -1820,7 +1542,7 @@ class TimeSeries(PlotData): Returns ------- S, H = Steepness and the corresponding wave height according to method - + The parameters are calculated as follows: Crest front speed (velocity) = Vcf = Ac/Tcf @@ -1829,10 +1551,10 @@ class TimeSeries(PlotData): Crest back steepness = 2*pi*Ac./Tu/Tcb/g Total wave steepness (zero-downcrossing wave) = 2*pi*Hd./Td.^2/g Total wave steepness (zero-upcrossing wave) = 2*pi*Hu./Tu.^2/g - - The definition of g, Ac,At, Tcf, etc. are given in gravity and - wafo.definitions. - + + The definition of g, Ac,At, Tcf, etc. are given in gravity and + wafo.definitions. + Example ------- >>> import wafo.data as wd @@ -1849,14 +1571,13 @@ class TimeSeries(PlotData): (array([ 0.60835634, 0.60930197]), array([ 0.42, 0.78])) (array([ 0.10140867, 0.06141156]), array([ 0.42, 0.78])) (array([ 0.01821413, 0.01236672]), array([ 0.42, 0.78])) - + >>> import pylab as plt >>> h = plt.plot(S,H,'.') >>> h = plt.xlabel('S') >>> h = plt.ylabel('Hd [m]') - - - See also + + See also -------- wafo.definitions ''' @@ -1908,7 +1629,7 @@ class TimeSeries(PlotData): # Zero-upcrossing wave height [m] H = Ac + At[1:] # Hu S = Ac / Tcb - #crest front steepness in S and the wave height Hd in H. + # crest front steepness in S and the wave height Hd in H. elif method == 2: H = Ac + At[:-1] # Hd Td = diff(ecross(ti, xi, z_ind[::2], v=0)) @@ -1935,7 +1656,7 @@ class TimeSeries(PlotData): return S, H def wave_periods(self, vh=None, pdef='d2d', wdef=None, index=None, rate=1): - """ + """ Return sequence of wave periods/lengths from data. Parameters @@ -2049,13 +1770,14 @@ class TimeSeries(PlotData): index = findtc(x, vh, wdef)[0] elif pdef in ('d2t', 't2u', 'u2c', 'c2d', 'all'): index, v_ind = findtc(x, vh, wdef) - #% sorting crossings and tp in sequence + # sorting crossings and tp in sequence index = sort(r_[index, v_ind]) else: raise ValueError('Unknown pdef option!') if (x[index[0]] > x[index[1]]): # % if first is down-crossing or max - if pdef in ('d2t', 'M2m', 'c2t', 'd2u', 'M2M', 'c2c', 'd2d', 'all'): + if pdef in ('d2t', 'M2m', 'c2t', 'd2u', 'M2M', 'c2c', 'd2d', + 'all'): start = 1 elif pdef in ('t2u', 'm2M', 't2c', 'u2d', 'm2m', 't2t', 'u2u'): start = 2 @@ -2085,17 +1807,17 @@ class TimeSeries(PlotData): else: step = 2 - #% determine the distance between min2min, t2t etc.. + # determine the distance between min2min, t2t etc.. if pdef in ('m2m', 't2t', 'u2u', 'M2M', 'c2c', 'd2d'): dist = 2 else: dist = 1 nn = len(index) - #% New call: (pab 28.06.2001) + # New call: (pab 28.06.2001) if pdef[0] in ('u', 'd'): t0 = ecross(ti, x, index[start:(nn - dist):step], vh) - else: # % min, Max, trough, crest or all crossings wanted + else: # min, Max, trough, crest or all crossings wanted t0 = x[index[start:(nn - dist):step]] if pdef[2] in ('u', 'd'): @@ -2106,45 +1828,330 @@ class TimeSeries(PlotData): T = t1 - t0 return T, index - def reconstruct(self): - # TODO: finish reconstruct - pass + def reconstruct(self, inds=None, Nsim=20, L=None, def_='nonlinear', + **options): + ''' + function [y,g,g2,test,tobs,mu1o, mu1oStd] = reconstruct(x,) + RECONSTRUCT reconstruct the spurious/missing points of timeseries + + CALL: [y,g,g2,test,tobs,mu1o,mu1oStd]= + reconstruct(x,inds,Nsim,L,def,options) + + Returns + ------- + y = reconstructed signal + g,g2 = smoothed and empirical transformation, respectively + test, tobs = test observator int(g(u)-u)^2 du and + int(g_new(u)-g_old(u))^2 du, + respectively, where int limits is given by param in lc2tr. + Test is a measure of departure from the Gaussian model for + the data. Tobs is a measure of the convergence of the + estimation of g. + mu1o = expected surface elevation of the Gaussian model process. + mu1o_std = standarddeviation of mu1o. + + Parameters + ---------- + x : 2 column timeseries + first column sampling times [sec] + second column surface elevation [m] + inds : integer array + indices to spurious points of x + Nsim = the maximum # of iterations before we stop + + L = lag size of the Parzen window function. + If no value is given the lag size is set to + be the lag where the auto correlation is less than + 2 standard deviations. (maximum 200) + def : + 'nonlinear' : transform from smoothed crossing intensity (default) + 'mnonlinear': transform from smoothed marginal distribution + 'linear' : identity. + options = options structure defining how the estimation of g is + done, see troptset. + + In order to reconstruct the data a transformed Gaussian random process + is used for modelling and simulation of the missing/removed data + conditioned on the other known observations. + + Estimates of standarddeviations of y is obtained by a call to tranproc + Std = tranproc(mu1o+/-mu1oStd,fliplr(g)); + + See also + -------- + troptset, findoutliers, cov2csdat, dat2cov, dat2tr, detrendma + + Reference + --------- + Brodtkorb, P, Myrhaug, D, and Rue, H (2001) + "Joint distribution of wave height and wave crest velocity from + reconstructed data with application to ringing" + Int. Journal of Offshore and Polar Engineering, Vol 11, No. 1, + pp 23--32 + + Brodtkorb, P, Myrhaug, D, and Rue, H (1999) + "Joint distribution of wave height and wave crest velocity from + reconstructed data + in Proceedings of 9th ISOPE Conference, Vol III, pp 66-73 + ''' + + opt = DotDict(chkder=True, plotflag=False, csm=0.9, gsm=.05, + param=(-5, 5, 513), delay=2, linextrap=True, ntr=10000, + ne=7, gvar=1) + opt.update(options) + + xn = self.data.copy().ravel() + n = len(xn) + + if n < 2: + raise ValueError('The vector must have more than 2 elements!') + + param = opt.param + plotflags = dict(none=0, off=0, final=1, iter=2) + plotflag = plotflags.get(opt.plotflag, opt.plotflag) + + olddef = def_ + method = 'approx' + ptime = opt.delay # pause for ptime sec if plotflag=2 + + expect1 = 1 # first reconstruction by expectation? 1=yes 0=no + expect = 1 # reconstruct by expectation? 1=yes 0=no + tol = 0.001 # absolute tolerance of e(g_new-g_old) + + cmvmax = 100; # if number of consecutive missing values (cmv) are longer they + # are not used in estimation of g, due to the fact that the + # conditional expectation approaches zero as the length to + # the closest known points increases, see below in the for loop + dT = self.sampling_period() + + Lm = np.minimum([n, 200, int(200/dT)]) # Lagmax 200 seconds + if L is not None: + Lm = max(L, Lm) + Lma = 1500 # size of the moving average window used + # for detrending the reconstructed signal + if not inds is None: + xn[inds] = np.nan + + inds = isnan(xn) + if not inds.any(): + raise ValueError('No spurious data given') + + endpos = np.diff(inds) + strtpos = np.flatnonzero(endpos>0) + endpos = np.flatnonzero(endpos<0) + + indg = np.flatnonzero(1-inds) # indices to good points + inds = np.flatnonzero(inds) # indices to spurious points + + indNaN = [] # indices to points omitted in the covariance estimation + indr = np.arange(n) # indices to point used in the estimation of g + + # Finding more than cmvmax consecutive spurios points. + # They will not be used in the estimation of g and are thus removed + # from indr. + + if strtpos.size>0 and (endpos.size==0 or endpos[-1] < strtpos[-1]): + if (n - strtpos[-1]) > cmvmax: + indNaN = indr[strtpos[-1]+1:n] + indr = indr[:strtpos[-1]+1] + strtpos = strtpos[:-1] + + if endpos.size>0 and (strtpos.size==0 or endpos[0] < strtpos[0]): + if endpos[0] > cmvmax: + indNaN = np.hstack((indNaN, indr[:endpos[0]])) + indr = indr[endpos[0]:] + + strtpos = strtpos-endpos[0] + endpos = endpos-endpos[0] + endpos = endpos[1:] + + for ix in range(len(strtpos)-1,-1,-1): + if (endpos[ix]-strtpos[ix]>cmvmax): + indNaN = np.hstack((indNaN, indr[strtpos[ix]+1:endpos[ix]])) + del indr[strtpos[ix]+1:endpos[ix]] # remove this when estimating the transform + + if len(indr)<0.1*n: + raise ValueError('Not possible to reconstruct signal') + + if indNaN.any(): + indNaN = np.sort(indNaN) + + # initial reconstruction attempt + # xn(indg,2)=detrendma(xn(indg,2),1500); +# [g, test, cmax, irr, g2] = dat2tr(xn(indg,:),def,opt); +# xnt=xn; +# xnt(indg,:)=dat2gaus(xn(indg,:),g); +# xnt(inds,2)=NaN; +# rwin=findrwin(xnt,Lm,L); +# disp(['First reconstruction attempt, e(g-u)=', num2str(test)] ) +# [samp ,mu1o, mu1oStd] = cov2csdat(xnt(:,2),rwin,1,method,inds); # old simcgauss +# if expect1,# reconstruction by expectation +# xnt(inds,2) =mu1o; +# else +# xnt(inds,2) =samp; +# end +# xn=gaus2dat(xnt,g); +# xn(:,2)=detrendma(xn(:,2),Lma); # detrends the signal with a moving +# # average of size Lma +# g_old=g; +# +# bias = mean(xn(:,2)); +# xn(:,2)=xn(:,2)-bias; # bias correction + +# if plotflag==2 +# clf +# mind=1:min(1500,n); +# waveplot(xn(mind,:),x(inds(mind),:), 6,1) +# subplot(111) +# pause(ptime) +# end +# +# test0=0; +# for ix=1:Nsim, +# # if 0,#ix==2, +# # rwin=findrwin(xn,Lm,L); +# # xs=cov2sdat(rwin,[n 100 dT]); +# # [g0 test0 cmax irr g2] = dat2tr(xs,def,opt); +# # [test0 ind0]=sort(test0); +# # end +# +# if 1, #test>test0(end-5), +# # 95# sure the data comes from a non-Gaussian process +# def = olddef; #Non Gaussian process +# else +# def = 'linear'; # Gaussian process +# end +# # used for isope article +# # indr =[1:27000 30000:39000]; +# # Too many consecutive missing values will influence the estimation of +# # g. By default do not use consecutive missing values if there are more +# # than cmvmax. +# +# [g test cmax irr g2] = dat2tr(xn(indr,:),def,opt); +# if plotflag==2, +# pause(ptime) +# end +# +# #tobs=sqrt((param(2)-param(1))/(param(3)-1)*sum((g_old(:,2)-g(:,2)).^2)) +# # new call +# tobs=sqrt((param(2)-param(1))/(param(3)-1).... +# *sum((g(:,2)-interp1(g_old(:,1)-bias, g_old(:,2),g(:,1),'spline')).^2)); +# +# if ix>1 +# if tol>tobs2 && tol>tobs, +# break, #estimation of g converged break out of for loop +# end +# end +# +# tobs2=tobs; +# +# xnt=dat2gaus(xn,g); +# if ~isempty(indNaN), xnt(indNaN,2)=NaN; end +# rwin=findrwin(xnt,Lm,L); +# disp(['Simulation nr: ', int2str(ix), ' of ' num2str(Nsim),' e(g-g_old)=', num2str(tobs), ', e(g-u)=', num2str(test)]) +# [samp ,mu1o, mu1oStd] = cov2csdat(xnt(:,2),rwin,1,method,inds); +# +# if expect, +# xnt(inds,2) =mu1o; +# else +# xnt(inds,2) =samp; +# end +# +# xn=gaus2dat(xnt,g); +# if ixtest0(end-5) +# xnt=dat2gaus(xn,g); +# [samp ,mu1o, mu1oStd] = cov2csdat(xnt(:,2),rwin,1,method,inds); +# xnt(inds,2) =samp; +# xn=gaus2dat(xnt,g); +# bias=mean(xn(:,2)); +# xn(:,2) = (xn(:,2)-bias); # bias correction +# g(:,1)=g(:,1)-bias; +# g2(:,1)=g2(:,1)-bias; +# gn=trangood(g); +# +# #mu1o=mu1o-tranproc(bias,gn); +# muUStd=tranproc(mu1o+2*mu1oStd,fliplr(gn));# +# muLStd=tranproc(mu1o-2*mu1oStd,fliplr(gn));# +# else +# muLStd=mu1o-2*mu1oStd; +# muUStd=mu1o+2*mu1oStd; +# end +# +# if plotflag==2 && length(xn)<10000, +# waveplot(xn,[xn(inds,1) muLStd ;xn(inds,1) muUStd ], 6,round(n/3000),[]) +# legend('reconstructed','2 stdev') +# #axis([770 850 -1 1]) +# #axis([1300 1325 -1 1]) +# end +# y=xn; +# toc +# +# return +# +# function r=findrwin(xnt,Lm,L) +# r=dat2cov(xnt,Lm);#computes ACF +# #finding where ACF is less than 2 st. deviations . +# # in order to find a better L value +# if nargin<3||isempty(L) +# L=find(abs(r.R)>2*r.stdev)+1; +# if isempty(L), # pab added this check 09.10.2000 +# L = Lm; +# else +# L = min([floor(4/3*L(end)) Lm]); +# end +# end +# win=parzen(2*L-1); +# r.R(1:L)=win(L:2*L-1).*r.R(1:L); +# r.R(L+1:end)=0; +# return def plot_wave(self, sym1='k.', ts=None, sym2='k+', nfig=None, nsub=None, sigma=None, vfact=3): - ''' + ''' Plots the surface elevation of timeseries. - + Parameters ---------- sym1, sym2 : string - plot symbol and color for data and ts, respectively + plot symbol and color for data and ts, respectively (see PLOT) (default 'k.' and 'k+') ts : TimeSeries or TurningPoints object to overplot data. default zero-separated troughs and crests. nsub : scalar integer - Number of subplots in each figure. By default nsub is such that - there are about 20 mean down crossing waves in each subplot. - If nfig is not given and nsub is larger than 6 then nsub is + Number of subplots in each figure. By default nsub is such that + there are about 20 mean down crossing waves in each subplot. + If nfig is not given and nsub is larger than 6 then nsub is changed to nsub=min(6,ceil(nsub/nfig)) nfig : scalar integer - Number of figures. By default nfig=ceil(Nsub/6). + Number of figures. By default nfig=ceil(Nsub/6). sigma : real scalar - standard deviation of data. + standard deviation of data. vfact : real scalar how large in stdev the vertical scale should be (default 3) - - + + Example - ------- + ------- Plot x1 with red lines and mark troughs and crests with blue circles. >>> import wafo >>> x = wafo.data.sea() >>> ts150 = wafo.objects.mat2timeseries(x[:150,:]) - >>> h = ts150.plot_wave('r-', sym2='bo') - + >>> h = ts150.plot_wave('r-', sym2='bo') + See also - -------- + -------- findtc, plot ''' @@ -2222,7 +2229,7 @@ class TimeSeries(PlotData): def plot_sp_wave(self, wave_idx_, *args, **kwds): """ Plot specified wave(s) from timeseries - + Parameters ---------- wave_idx : integer vector @@ -2258,7 +2265,7 @@ class TimeSeries(PlotData): Nwp[Nsub - 1] = wave_idx[-1] - wave_idx[dw[-1]] + 1 wave_idx[dw[-1] + 1:] = -2 for ix in range(Nsub - 2, 1, -2): - # # of waves pr subplot + # of waves pr subplot Nwp[ix] = wave_idx[dw[ix] - 1] - wave_idx[dw[ix - 1]] + 1 wave_idx[dw[ix - 1] + 1:dw[ix]] = -2 @@ -2289,565 +2296,18 @@ class TimeSeries(PlotData): plotbackend.ylabel('Wave %d' % wave_idx[ix]) else: plotbackend.ylabel( - 'Wave %d - %d' % (wave_idx[ix], wave_idx[ix] + Nwp[ix] - 1)) + 'Wave %d - %d' % (wave_idx[ix], + wave_idx[ix] + Nwp[ix] - 1)) plotbackend.xlabel('Time [sec]') # wafostamp return figs -# def hyperbolic_ratio(a, b, sa, sb): -# ''' -# Return ratio of hyperbolic functions -# to allow extreme variations of arguments. -# -# Parameters -# ---------- -# a, b : array-like -# arguments vectors of the same size -# sa, sb : scalar integers -# defining the hyperbolic function used, i.e., f(x,1)=cosh(x), f(x,-1)=sinh(x) -# -# Returns -# ------- -# r : ndarray -# f(a,sa)/f(b,sb), ratio of hyperbolic functions of same -# size as a and b -# Examples -# -------- -# >>> x = [-2,0,2] -# >>> hyperbolic_ratio(x,1,1,1) # gives r=cosh(x)/cosh(1) -# array([ 2.438107 , 0.64805427, 2.438107 ]) -# >>> hyperbolic_ratio(x,1,1,-1) # gives r=cosh(x)/sinh(1) -# array([ 3.20132052, 0.85091813, 3.20132052]) -# >>> hyperbolic_ratio(x,1,-1,1) # gives r=sinh(x)/cosh(1) -# array([-2.35040239, 0. , 2.35040239]) -# >>> hyperbolic_ratio(x,1,-1,-1) # gives r=sinh(x)/sinh(1) -# array([-3.08616127, 0. , 3.08616127]) -# >>> hyperbolic_ratio(1,x,1,1) # gives r=cosh(1)/cosh(x) -# array([ 0.41015427, 1.54308063, 0.41015427]) -# >>> hyperbolic_ratio(1,x,1,-1) # gives r=cosh(1)/sinh(x) -# array([-0.42545906, inf, 0.42545906]) -# >>> hyperbolic_ratio(1,x,-1,1) # gives r=sinh(1)/cosh(x) -# array([ 0.3123711 , 1.17520119, 0.3123711 ]) -# >>> hyperbolic_ratio(1,x,-1,-1) # gives r=sinh(1)/sinh(x) -# array([-0.32402714, inf, 0.32402714]) -# -# See also -# -------- -# tran -# ''' -# ak, bk, sak, sbk = np.atleast_1d(a, b, sign(sa), sign(sb)) -# old call -# return exp(ak-bk)*(1+sak*exp(-2*ak))/(1+sbk*exp(-2*bk)) -# TODO: Does not always handle division by zero correctly -# -# signRatio = np.where(sak * ak < 0, sak, 1) -# signRatio = np.where(sbk * bk < 0, sbk * signRatio, signRatio) -# -# bk = np.abs(bk) -# ak = np.abs(ak) -# -# num = np.where(sak < 0, expm1(-2 * ak), 1 + exp(-2 * ak)) -# den = np.where(sbk < 0, expm1(-2 * bk), 1 + exp(-2 * bk)) -# iden = np.ones(den.shape) * inf -# ind = np.flatnonzero(den != 0) -# iden.flat[ind] = 1.0 / den[ind] -# val = np.where(num == den, 1, num * iden) -# return signRatio * exp(ak - bk) * val #((sak+exp(-2*ak))/(sbk+exp(-2*bk))) -# -# def sensor_typeid(*sensortypes): -# ''' Return ID for sensortype name -# -# Parameter -# --------- -# sensortypes : list of strings defining the sensortype -# -# Returns -# ------- -# sensorids : list of integers defining the sensortype -# -# Valid senor-ids and -types for time series are as follows: -# 0, 'n' : Surface elevation (n=Eta) -# 1, 'n_t' : Vertical surface velocity -# 2, 'n_tt' : Vertical surface acceleration -# 3, 'n_x' : Surface slope in x-direction -# 4, 'n_y' : Surface slope in y-direction -# 5, 'n_xx' : Surface curvature in x-direction -# 6, 'n_yy' : Surface curvature in y-direction -# 7, 'n_xy' : Surface curvature in xy-direction -# 8, 'P' : Pressure fluctuation about static MWL pressure -# 9, 'U' : Water particle velocity in x-direction -# 10, 'V' : Water particle velocity in y-direction -# 11, 'W' : Water particle velocity in z-direction -# 12, 'U_t' : Water particle acceleration in x-direction -# 13, 'V_t' : Water particle acceleration in y-direction -# 14, 'W_t' : Water particle acceleration in z-direction -# 15, 'X_p' : Water particle displacement in x-direction from its mean position -# 16, 'Y_p' : Water particle displacement in y-direction from its mean position -# 17, 'Z_p' : Water particle displacement in z-direction from its mean position -# -# Example: -# >>> sensor_typeid('W','v') -# [11, 10] -# >>> sensor_typeid('rubbish') -# [nan] -# -# See also -# -------- -# sensor_type -# ''' -# -# sensorid_table = dict(n=0, n_t=1, n_tt=2, n_x=3, n_y=4, n_xx=5, -# n_yy=6, n_xy=7, p=8, u=9, v=10, w=11, u_t=12, -# v_t=13, w_t=14, x_p=15, y_p=16, z_p=17) -# try: -# return [sensorid_table.get(name.lower(), nan) for name in sensortypes] -# except: -# raise ValueError('Input must be a string!') -# -# -# -# def sensor_type(*sensorids): -# ''' -# Return sensortype name -# -# Parameter -# --------- -# sensorids : vector or list of integers defining the sensortype -# -# Returns -# ------- -# sensornames : tuple of strings defining the sensortype -# Valid senor-ids and -types for time series are as follows: -# 0, 'n' : Surface elevation (n=Eta) -# 1, 'n_t' : Vertical surface velocity -# 2, 'n_tt' : Vertical surface acceleration -# 3, 'n_x' : Surface slope in x-direction -# 4, 'n_y' : Surface slope in y-direction -# 5, 'n_xx' : Surface curvature in x-direction -# 6, 'n_yy' : Surface curvature in y-direction -# 7, 'n_xy' : Surface curvature in xy-direction -# 8, 'P' : Pressure fluctuation about static MWL pressure -# 9, 'U' : Water particle velocity in x-direction -# 10, 'V' : Water particle velocity in y-direction -# 11, 'W' : Water particle velocity in z-direction -# 12, 'U_t' : Water particle acceleration in x-direction -# 13, 'V_t' : Water particle acceleration in y-direction -# 14, 'W_t' : Water particle acceleration in z-direction -# 15, 'X_p' : Water particle displacement in x-direction from its mean position -# 16, 'Y_p' : Water particle displacement in y-direction from its mean position -# 17, 'Z_p' : Water particle displacement in z-direction from its mean position -# -# Example: -# >>> sensor_type(range(3)) -# ('n', 'n_t', 'n_tt') -# -# See also -# -------- -# sensor_typeid, tran -# ''' -# valid_names = ('n', 'n_t', 'n_tt', 'n_x', 'n_y', 'n_xx', 'n_yy', 'n_xy', -# 'p', 'u', 'v', 'w', 'u_t', 'v_t', 'w_t', 'x_p', 'y_p', 'z_p', -# nan) -# ids = atleast_1d(*sensorids) -# if isinstance(ids, list): -# ids = hstack(ids) -# n = len(valid_names) - 1 -# ids = where(((ids < 0) | (n < ids)), n , ids) -# return tuple(valid_names[i] for i in ids) -# -# class TransferFunction(object): -# ''' -# Class for computing transfer functions based on linear wave theory -# of the system with input surface elevation, -# eta(x0,y0,t) = exp(i*(kx*x0+ky*y0-w*t)), -# and output Y determined by sensortype and position of sensor. -# -# Member methods -# -------------- -# tran(w, theta, kw) -# -# Hw = a function of frequency only (not direction) size 1 x Nf -# Gwt = a function of frequency and direction size Nt x Nf -# w = vector of angular frequencies in Rad/sec. Length Nf -# theta = vector of directions in radians Length Nt (default 0) -# ( theta = 0 -> positive x axis theta = pi/2 -> positive y axis) -# Member variables -# ---------------- -# pos : [x,y,z] -# vector giving coordinate position relative to [x0 y0 z0] (default [0,0,0]) -# sensortype = string -# defining the sensortype or transfer function in output. -# 0, 'n' : Surface elevation (n=Eta) (default) -# 1, 'n_t' : Vertical surface velocity -# 2, 'n_tt' : Vertical surface acceleration -# 3, 'n_x' : Surface slope in x-direction -# 4, 'n_y' : Surface slope in y-direction -# 5, 'n_xx' : Surface curvature in x-direction -# 6, 'n_yy' : Surface curvature in y-direction -# 7, 'n_xy' : Surface curvature in xy-direction -# 8, 'P' : Pressure fluctuation about static MWL pressure -# 9, 'U' : Water particle velocity in x-direction -# 10, 'V' : Water particle velocity in y-direction -# 11, 'W' : Water particle velocity in z-direction -# 12, 'U_t' : Water particle acceleration in x-direction -# 13, 'V_t' : Water particle acceleration in y-direction -# 14, 'W_t' : Water particle acceleration in z-direction -# 15, 'X_p' : Water particle displacement in x-direction from its mean position -# 16, 'Y_p' : Water particle displacement in y-direction from its mean position -# 17, 'Z_p' : Water particle displacement in z-direction from its mean position -# h : real scalar -# water depth (default inf) -# g : real scalar -# acceleration of gravity (default 9.81 m/s**2) -# rho : real scalar -# water density (default 1028 kg/m**3) -# bet : 1 or -1 -# 1, theta given in terms of directions toward which waves travel (default) -# -1, theta given in terms of directions from which waves come -# igam : 1,2 or 3 -# 1, if z is measured positive upward from mean water level (default) -# 2, if z is measured positive downward from mean water level -# 3, if z is measured positive upward from sea floor -# thetax, thetay : real scalars -# angle in degrees clockwise from true north to positive x-axis and -# positive y-axis, respectively. (default theatx=90, thetay=0) -# -# Example -# ------- -# >>> import pylab as plt -# >>> N=50; f0=0.1; th0=0; h=50; w0 = 2*pi*f0 -# >>> t = np.linspace(0,15,N) -# >>> eta0 = np.exp(-1j*w0*t) -# >>> stypes = ['n', 'n_x', 'n_y']; -# >>> tf = TransferFunction(pos=(0, 0, 0), h=50) -# >>> vals = [] -# >>> fh = plt.plot(t, eta0.real, 'r.') -# >>> plt.hold(True) -# >>> for i,stype in enumerate(stypes): -# ... tf.sensortype = stype -# ... Hw, Gwt = tf.tran(w0,th0) -# ... vals.append((Hw*Gwt*eta0).real.ravel()) -# ... vals[i] -# ... fh = plt.plot(t, vals[i]) -# >>> plt.show() -# -# -# See also -# -------- -# dat2dspec, sensor_type, sensor_typeid -# -# Reference -# --------- -# Young I.R. (1994) -# "On the measurement of directional spectra", -# Applied Ocean Research, Vol 16, pp 283-294 -# ''' -# def __init__(self, pos=(0, 0, 0), sensortype='n', h=inf, g=9.81, rho=1028, -# bet=1, igam=1, thetax=90, thetay=0): -# self.pos = pos -# self.sensortype = sensortype if isinstance(sensortype, str) else sensor_type(sensortype) -# self.h = h -# self.g = g -# self.rho = rho -# self.bet = bet -# self.igam = igam -# self.thetax = thetax -# self.thetay = thetay -# self._tran_dict = dict(n=self._n, n_t=self._n_t, n_tt=self._n_tt, -# n_x=self._n_x, n_y=self._n_y, n_xx=self._n_xx, -# n_yy=self._n_yy, n_xy=self._n_xy, -# P=self._p, p=self._p, -# U=self._u, u=self._u, -# V=self._v, v=self._v, -# W=self._w, w=self._w, -# U_t=self._u_t, u_t=self._u_t, -# V_t=self._v_t, v_t=self._v_t, -# W_t=self._w_t, w_t=self._w_t, -# X_p=self._x_p, x_p=self._x_p, -# Y_p=self._y_p, y_p=self._y_p, -# Z_p=self._z_p, z_p=self._z_p) -# -# def tran(self, w, theta=0, kw=None): -# ''' -# Return transfer functions based on linear wave theory -# of the system with input surface elevation, -# eta(x0,y0,t) = exp(i*(kx*x0+ky*y0-w*t)), -# and output, -# Y = Hw*Gwt*eta, determined by sensortype and position of sensor. -# -# Parameters -# ---------- -# w : array-like -# vector of angular frequencies in Rad/sec. Length Nf -# theta : array-like -# vector of directions in radians Length Nt (default 0) -# ( theta = 0 -> positive x axis theta = pi/2 -> positive y axis) -# kw : array-like -# vector of wave numbers corresponding to angular frequencies, w. Length Nf -# (default calculated with w2k) -# -# Returns -# ------- -# Hw = transfer function of frequency only (not direction) size 1 x Nf -# Gwt = transfer function of frequency and direction size Nt x Nf -# -# The complete transfer function Hwt = Hw*Gwt is a function of -# w (columns) and theta (rows) size Nt x Nf -# ''' -# if kw is None: -# kw, unusedkw2 = w2k(w, 0, self.h) #wave number as function of angular frequency -# -# w, theta, kw = np.atleast_1d(w, theta, kw) -# make sure they have the correct orientation -# theta.shape = (-1, 1) -# kw.shape = (-1,) -# w.shape = (-1,) -# -# tran_fun = self._tran_dict[self.sensortype] -# Hw, Gwt = tran_fun(w, theta, kw) -# -# New call to avoid singularities. pab 07.11.2000 -# Set Hw to 0 for expressions w*hyperbolic_ratio(z*k,h*k,1,-1)= 0*inf -# ind = np.flatnonzero(1 - np.isfinite(Hw)) -# Hw.flat[ind] = 0 -# -# sgn = np.sign(Hw); -# k0 = np.flatnonzero(sgn < 0) -# if len(k0): # make sure Hw>=0 ie. transfer negative signs to Gwt -# Gwt[:, k0] = -Gwt[:, k0] -# Hw[:, k0] = -Hw[:, k0] -# -# if self.igam == 2: -# pab 09 Oct.2002: bug fix -# Changing igam by 2 should affect the directional result in the same way that changing eta by -eta! -# Gwt = -Gwt -# return Hw, Gwt -# __call__ = tran -# ---Private member methods -# def _get_ee_cthxy(self, theta, kw): -# convert from angle in degrees to radians -# bet = self.bet -# thxr = self.thetax * pi / 180 -# thyr = self.thetay * pi / 180 -# -# cthx = bet * cos(theta - thxr + pi / 2) -# cthy = cos(theta-thyr-pi/2) -# cthy = bet * sin(theta - thyr) -# -# Compute location complex exponential -# x, y, unused_z = list(self.pos) -# ee = exp((1j * (x * cthx + y * cthy)) * kw) # exp(i*k(w)*(x*cos(theta)+y*sin(theta)) size Nt X Nf -# return ee, cthx, cthy -# -# def _get_zk(self, kw): -# h = self.h -# z = self.pos[2] -# if self.igam == 1: -# zk = kw * (h + z) # z measured positive upward from mean water level (default) -# elif self.igam == 2: -# zk = kw * (h - z) # z measured positive downward from mean water level -# else: -# zk = kw * z # z measured positive upward from sea floor -# return zk -# -# --- Surface elevation --- -# def _n(self, w, theta, kw): -# '''n = Eta = wave profile -# ''' -# ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) -# return np.ones_like(w), ee -# -# ---- Vertical surface velocity and acceleration----- -# def _n_t(self, w, theta, kw): -# ''' n_t = Eta_t ''' -# ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) -# return w, -1j * ee; -# def _n_tt(self, w, theta, kw): -# '''n_tt = Eta_tt''' -# ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) -# return w ** 2, -ee -# -# --- Surface slopes --- -# def _n_x(self, w, theta, kw): -# ''' n_x = Eta_x = x-slope''' -# ee, cthx, unused_cthy = self._get_ee_cthxy(theta, kw) -# return kw, 1j * cthx * ee -# def _n_y(self, w, theta, kw): -# ''' n_y = Eta_y = y-slope''' -# ee, unused_cthx, cthy = self._get_ee_cthxy(theta, kw) -# return kw, 1j * cthy * ee -# -# --- Surface curvatures --- -# def _n_xx(self, w, theta, kw): -# ''' n_xx = Eta_xx = Surface curvature (x-dir)''' -# ee, cthx, unused_cthy = self._get_ee_cthxy(theta, kw) -# return kw ** 2, -(cthx ** 2) * ee -# def _n_yy(self, w, theta, kw): -# ''' n_yy = Eta_yy = Surface curvature (y-dir)''' -# ee, unused_cthx, cthy = self._get_ee_cthxy(theta, kw) -# return kw ** 2, -cthy ** 2 * ee -# def _n_xy(self, w, theta, kw): -# ''' n_xy = Eta_xy = Surface curvature (xy-dir)''' -# ee, cthx, cthy = self._get_ee_cthxy(theta, kw) -# return kw ** 2, -cthx * cthy * ee -# -# --- Pressure--- -# def _p(self, w, theta, kw): -# ''' pressure fluctuations''' -# ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) -# hk = kw * self.h -# zk = self._get_zk(kw) -# return self.rho * self.g * hyperbolic_ratio(zk, hk, 1, 1), ee #hyperbolic_ratio = cosh(zk)/cosh(hk) -# -# ---- Water particle velocities --- -# def _u(self, w, theta, kw): -# ''' U = x-velocity''' -# ee, cthx, unused_cthy = self._get_ee_cthxy(theta, kw) -# hk = kw * self.h -# zk = self._get_zk(kw) -# return w * hyperbolic_ratio(zk, hk, 1, -1), cthx * ee# w*cosh(zk)/sinh(hk), cos(theta)*ee -# def _v(self, w, theta, kw): -# '''V = y-velocity''' -# ee, unused_cthx, cthy = self._get_ee_cthxy(theta, kw) -# hk = kw * self.h -# zk = self._get_zk(kw) -# return w * hyperbolic_ratio(zk, hk, 1, -1), cthy * ee # w*cosh(zk)/sinh(hk), sin(theta)*ee -# def _w(self, w, theta, kw): -# ''' W = z-velocity''' -# ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) -# hk = kw * self.h -# zk = self._get_zk(kw) -# return w * hyperbolic_ratio(zk, hk, -1, -1), -1j * ee # w*sinh(zk)/sinh(hk), -? -# -# ---- Water particle acceleration --- -# def _u_t(self, w, theta, kw): -# ''' U_t = x-acceleration''' -# ee, cthx, unused_cthy = self._get_ee_cthxy(theta, kw) -# hk = kw * self.h -# zk = self._get_zk(kw) -# return (w ** 2) * hyperbolic_ratio(zk, hk, 1, -1), -1j * cthx * ee # w^2*cosh(zk)/sinh(hk), ? -# -# def _v_t(self, w, theta, kw): -# ''' V_t = y-acceleration''' -# ee, unused_cthx, cthy = self._get_ee_cthxy(theta, kw) -# hk = kw * self.h -# zk = self._get_zk(kw) -# return (w ** 2) * hyperbolic_ratio(zk, hk, 1, -1), -1j * cthy * ee # w^2*cosh(zk)/sinh(hk), ? -# def _w_t(self, w, theta, kw): -# ''' W_t = z-acceleration''' -# ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) -# hk = kw * self.h -# zk = self._get_zk(kw) -# return (w ** 2) * hyperbolic_ratio(zk, hk, -1, -1), -ee # w*sinh(zk)/sinh(hk), ? -# -# ---- Water particle displacement --- -# def _x_p(self, w, theta, kw): -# ''' X_p = x-displacement''' -# ee, cthx, unused_cthy = self._get_ee_cthxy(theta, kw) -# hk = kw * self.h -# zk = self._get_zk(kw) -# return hyperbolic_ratio(zk, hk, 1, -1), 1j * cthx * ee # cosh(zk)./sinh(hk), ? -# def _y_p(self, w, theta, kw): -# ''' Y_p = y-displacement''' -# ee, unused_cthx, cthy = self._get_ee_cthxy(theta, kw) -# hk = kw * self.h -# zk = self._get_zk(kw) -# return hyperbolic_ratio(zk, hk, 1, -1), 1j * cthy * ee # cosh(zk)./sinh(hk), ? -# def _z_p(self, w, theta, kw): -# ''' Z_p = z-displacement''' -# ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) -# hk = kw * self.h -# zk = self._get_zk(kw) -# return hyperbolic_ratio(zk, hk, -1, -1), ee # sinh(zk)./sinh(hk), ee -# -# def wave_pressure(z, Hm0, h=10000, g=9.81, rho=1028): -# ''' -# Calculate pressure amplitude due to water waves. -# -# Parameters -# ---------- -# z : array-like -# depth where pressure is calculated [m] -# Hm0 : array-like -# significant wave height (same as the average of the 1/3'rd highest -# waves in a seastate. [m] -# h : real scalar -# waterdepth (default 10000 [m]) -# g : real scalar -# acceleration of gravity (default 9.81 m/s**2) -# rho : real scalar -# water density (default 1028 kg/m**3) -# -# -# Returns -# ------- -# p : ndarray -# pressure amplitude due to water waves at water depth z. [Pa] -# -# PRESSURE calculate pressure amplitude due to water waves according to -# linear theory. -# -# Example -# ----- -# >>> import pylab as plt -# >>> z = -np.linspace(10,20) -# >>> fh = plt.plot(z, wave_pressure(z, Hm0=1, h=20)) -# >>> plt.show() -# -# See also -# -------- -# w2k -# -# -# u = psweep.Fn*sqrt(mgf.length*9.81) -# z = -10; h = inf; -# Hm0 = 1.5;Tp = 4*sqrt(Hm0); -# S = jonswap([],[Hm0,Tp]); -# Hw = tran(S.w,0,[0 0 -z],'P',h) -# Sm = S; -# Sm.S = Hw.'.*S.S; -# x1 = spec2sdat(Sm,1000); -# pwave = pressure(z,Hm0,h) -# -# plot(psweep.x{1}/u, psweep.f) -# hold on -# plot(x1(1:100,1)-30,x1(1:100,2),'r') -# ''' -# -# -# Assume seastate with jonswap spectrum: -# -# Tp = 4 * np.sqrt(Hm0) -# gam = jonswap_peakfact(Hm0, Tp) -# Tm02 = Tp / (1.30301 - 0.01698 * gam + 0.12102 / gam) -# w = 2 * np.pi / Tm02 -# kw, unused_kw2 = w2k(w, 0, h) -# -# hk = kw * h -# zk1 = kw * z -# zk = hk + zk1 # z measured positive upward from mean water level (default) -# zk = hk-zk1; % z measured positive downward from mean water level -# zk1 = -zk1; -# zk = zk1; % z measured positive upward from sea floor -# -# cosh(zk)/cosh(hk) approx exp(zk) for large h -# hyperbolic_ratio(zk,hk,1,1) = cosh(zk)/cosh(hk) -# pr = np.where(np.pi < hk, np.exp(zk1), hyperbolic_ratio(zk, hk, 1, 1)) -# pr = hyperbolic_ratio(zk, hk, 1, 1) -# pressure = (rho * g * Hm0 / 2) * pr -# -## pos = [np.zeros_like(z),np.zeros_like(z),z] -## tf = TransferFunction(pos=pos, sensortype='p', h=h, rho=rho, g=g) -## Hw, Gwt = tf.tran(w,0) -## pressure2 = np.abs(Hw) * Hm0 / 2 -# -# return pressure - def main(): import wafo ts = wafo.objects.mat2timeseries(wafo.data.sea()) + S = ts.tospecdata(method='psd') tp = ts.turning_points() mm = tp.cycle_pairs() lc = mm.level_crossings() @@ -2887,14 +2347,10 @@ def main(): def test_docstrings(): import doctest - doctest.testmod() + print('Testing docstrings in %s' % __file__) + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) if __name__ == '__main__': test_docstrings() + #main() # test_levelcrossings_extrapolate() -# if True: #False : # - -# import doctest -# doctest.testmod() -# else: -# main() diff --git a/pywafo/src/wafo/plotbackend.py b/pywafo/src/wafo/plotbackend.py index 08e806f..afc3849 100644 --- a/pywafo/src/wafo/plotbackend.py +++ b/pywafo/src/wafo/plotbackend.py @@ -7,7 +7,7 @@ if False: try: from scitools import easyviz as plotbackend if verbose: - print('wafo.wafodata: plotbackend is set to scitools.easyviz') + print('wafo: plotbackend is set to scitools.easyviz') except: warnings.warn('wafo: Unable to load scitools.easyviz as plotbackend') plotbackend = None @@ -16,7 +16,7 @@ else: from matplotlib import pyplot as plotbackend plotbackend.interactive(True) if verbose: - print('wafo.wafodata: plotbackend is set to matplotlib.pyplot') + print('wafo: plotbackend is set to matplotlib.pyplot') except: warnings.warn('wafo: Unable to load matplotlib.pyplot as plotbackend') plotbackend = None \ No newline at end of file diff --git a/pywafo/src/wafo/polynomial.py b/pywafo/src/wafo/polynomial.py index b483d5f..39d327c 100644 --- a/pywafo/src/wafo/polynomial.py +++ b/pywafo/src/wafo/polynomial.py @@ -1,12 +1,13 @@ """ Extended functions to operate on polynomials """ -#------------------------------------------------------------------------------- +#------------------------------------------------------------------------- # Name: polynomial # Purpose: Functions to operate on polynomials. # # Author: pab -# polyXXX functions are based on functions found in the matlab toolbox polyutil written by +# polyXXX functions are based on functions found in the matlab toolbox polyutil +# written by # Author: Peter J. Acklam # E-mail: pjacklam@online.no # WWW URL: http://home.online.no/~pjacklam @@ -14,23 +15,24 @@ # Created: 30.12.2008 # Copyright: (c) pab 2008 # Licence: LGPL -#------------------------------------------------------------------------------- +#------------------------------------------------------------------------- #!/usr/bin/env python from plotbackend import plotbackend as plt import numpy as np from numpy.fft import fft, ifft -from numpy import (zeros, ones, zeros_like, array, asarray, newaxis, arange, #@UnresolvedImport - logical_or, any, pi, cos, round, diff, all, r_, exp, atleast_1d, # hstack,#@UnresolvedImport - where, extract, dot, linalg, sign, concatenate, floor, isreal, conj, remainder, #@UnresolvedImport - linspace) #@UnresolvedImport -from numpy.lib.polynomial import * #@UnusedWildImport -from scipy.misc.common import pade +from numpy import (zeros, ones, zeros_like, array, asarray, newaxis, arange, + logical_or, any, pi, cos, round, diff, all, exp, + where, extract, linalg, sign, concatenate, floor, isreal, + conj, remainder, linspace, atleast_1d, hstack, sum) +from numpy.lib.polynomial import * # @UnusedWildImport +from scipy.misc.common import pade # @UnresolvedImport __all__ = np.lib.polynomial.__all__ -__all__ = __all__ + ['pade', 'padefit', 'polyreloc', 'polyrescl', 'polytrim', 'poly2hstr', 'poly2str', - 'polyshift', 'polyishift', 'map_from_intervall', 'map_to_intervall', - 'cheb2poly', 'chebextr', 'chebroot', 'chebpoly', 'chebfit', 'chebval', - 'chebder', 'chebint', 'Cheb1d', 'dct', 'idct'] +__all__ = __all__ + ['pade', 'padefit', 'polyreloc', 'polyrescl', 'polytrim', + 'poly2hstr', 'poly2str', 'polyshift', 'polyishift', + 'map_from_intervall', 'map_to_intervall', 'cheb2poly', + 'chebextr', 'chebroot', 'chebpoly', 'chebfit', 'chebval', + 'chebder', 'chebint', 'Cheb1d', 'dct', 'idct'] def polyint(p, m=1, k=None): @@ -85,9 +87,10 @@ def polyint(p, m=1, k=None): 0.0 >>> np.polyder(P, 2)(0) 0.0 - >>> P = np.polyint(p, 3, k=[6,5,3]) - >>> P - poly1d([ 0.01666667, 0.04166667, 0.16666667, 3. , 5. , 3. ]) + >>> P = np.polyint(p, 3, k=[6, 5, 3]) + >>> P.coefficients.tolist() + [0.016666666666666666, 0.041666666666666664, 0.16666666666666666, 3.0, + 5.0, 3.0] Note that 3 = 6 / 2!, and that the constants are given in the order of integrations. Constant of the highest-order polynomial term comes first: @@ -102,22 +105,22 @@ def polyint(p, m=1, k=None): """ m = int(m) if m < 0: - raise ValueError, "Order of integral must be positive (see polyder)" + raise ValueError("Order of integral must be positive (see polyder)") if k is None: k = zeros(m, float) k = atleast_1d(k) if len(k) == 1 and m > 1: k = k[0] * ones(m, float) if len(k) < m: - raise ValueError, \ - "k must be a scalar or a rank-1 array of length 1 or >m." + raise ValueError( + "k must be a scalar or a rank-1 array of length 1 or >m.") truepoly = isinstance(p, poly1d) p = asarray(p) if m == 0: if truepoly: return poly1d(p) return p - else: + else: ix = arange(len(p), 0, -1) if p.ndim > 1: ix = ix[..., newaxis] @@ -126,12 +129,13 @@ def polyint(p, m=1, k=None): else: k0 = [k[0]] y = np.concatenate((p.__truediv__(ix), k0), axis=0) - + val = polyint(y, m - 1, k=k[1:]) if truepoly: return poly1d(val) return val + def polyder(p, m=1): """ Return the derivative of the specified order of a polynomial. @@ -186,7 +190,7 @@ def polyder(p, m=1): """ m = int(m) if m < 0: - raise ValueError, "Order of derivative must be positive (see polyint)" + raise ValueError("Order of derivative must be positive (see polyint)") truepoly = isinstance(p, poly1d) p = asarray(p) if m == 0: @@ -203,194 +207,248 @@ def polyder(p, m=1): if truepoly: return poly1d(val) return val - -def unfinished_polydeg(x,y): + + +def polydeg(x, y): ''' - Return optimal degree for polynomial fitting. - N = POLYDEG(X,Y) finds the optimal degree for polynomial fitting + Return optimal degree for polynomial fitting + + + N = POLYDEG(X,Y) finds the optimal degree for polynomial fitting, according to the Akaike's information criterion. - + Assuming that you want to find the degree N of a polynomial that fits the data Y(X) best in a least-squares sense, the Akaike's information criterion is defined by: - 2*(N+1) + n*(log(2*pi*RSS/n)+1) + 2*(N + 1) + n * (log(2 * pi * RSS / n) + 1) where n is the number of points and RSS is the residual sum of squares. - The optimal degree N is defined here as that which minimizes AIC. - + The optimal degree N is defined here as that which minimizes AIC: + http://en.wikipedia.org/wiki/Akaike_Information_Criterion + Notes: ----- If the number of data is small, POLYDEG may tend to return: N = (number of points)-1. - + ORTHOFIT is more appropriate than POLYFIT for polynomial fitting with relatively high degrees. - - Examples: - -------- - load census - n = polydeg(cdate,pop) - - x = linspace(0,10,300); - y = sin(x.^3/100).^2 + 0.05*randn(size(x)); - n = polydeg(x,y) + + Example: + ------- + >>> x = np.linspace(0,10,300) + >>> y = np.sin(x ** 3 / 100) ** 2 + 0.05 * np.random.randn(x.size) + >>> n = polydeg(x,y) + >>> n + 21 + ys = orthofit(x,y,n); - plot(x,y,'.',x,ys,'k') - - Damien Garcia, 02/2008, revised 01/2010 - - See also POLYFIT, ORTHOFIT. + plt.plot(x, y, '.', x, ys, 'k') + + See also + -------- + polyfit, orthofit ''' x, y = np.atleast_1d(x, y) - + x = x.ravel() + y = y.ravel() N = len(x) - - - ## Search the optimal degree minimizing the Akaike's information criterion - # --- + + # Search the optimal degree minimizing the Akaike's information criterion # y(x) are fitted in a least-squares sense using a polynomial of degree n # developed in a series of orthogonal polynomials. - - - p = y.mean() - ys = np.ones((N,))*p - AIC = 2+N*(np.log(2*pi*((ys-y)**2).sum()/N)+1)+ 4/(N-2) #correction for small sample sizes - - p = zeros((2,2)) - p[1,0] = x.mean() - PL = np.ones((2,N)) - PL[1] = x-p[1,0] - + ys = np.ones((N,)) * y.mean() + # correction for small sample sizes + AIC = 2 + N * \ + (np.log(2 * pi * ((ys - y) ** 2).sum() / N) + 1) + 4 / (N - 2) + n = 1 nit = 0 - + # While-loop is stopped when a minimum is detected. 3 more steps are # required to take AIC noise into account and to ensure that this minimum # is a (likely) global minimum. - - while nit<3: - if n>0: - p[0,n] = sum(x*PL[:,n]**2)/sum(PL[:,n]**2) - p[1,n] = sum(x*PL[:,n-1]*PL[:,n])/sum(PL[:,n-1]**2) - PL[:,n] = (x-p[0,n+1])*PL[:,n]-p[1,n+1]*PL[:,n-1] - #end - - tmp = sum(y*PL)/sum(PL**2) - ys = sum(PL*tmp,axis=-1) - + + while nit < 3: + p = orthofit(x, y, n) + ys = orthoval(p, x) # -- Akaike's Information Criterion - aic = 2*(n+1)+N*(np.log(2*pi*sum((ys-y.ravel()**2)/N)+1)) + 2*(n+1)*(n+2)/(N-n-2) - - - if aic>=AIC: + aic = 2 * (n + 1) * (1 + (n + 2) / (N - n - 2)) + \ + N * (np.log(2 * pi * sum((ys - y) ** 2) / N) + 1) + + if aic >= AIC: nit += 1 else: nit = 0 AIC = aic - - n = n+1 - - if n>=N: + + n = n + 1 + + if n >= N: break - n = n-nit-1 - + n = n - nit - 1 return n - -def unfinished_orthofit(x,y,n): + + +def orthoval(p, x): ''' - ORTHOFIT Fit polynomial to data. - YS = ORTHOFIT(X,Y,N) smooths/fits data Y(X) in a least-squares sense - using a polynomial of degree N and returns the smoothed data YS. - - [YS,YI] = ORTHOFIT(X,Y,N,XI) also returns the values YI of the fitting - polynomial at the points of a different XI array. - - YI = ORTHOFIT(X,Y,N,XI) returns only the values YI of the fitting - polynomial at the points of the XI array. - - [YS,P] = ORTHOFIT(X,Y,N) returns the polynomial coefficients P for use - with POLYVAL. - + Evaluation of orthogonal polynomial + + Parameters + ---------- + p : array_like + 2D array of polynomial coefficients (including coefficients equal + to zero) from highest degree to the constant term. + x : array_like + A number or a 1D array of numbers "at" which to evaluate `p`. + + Returns + ------- + values : ndarray + + See Also + -------- + orthofit + ''' + p = np.atleast_2d(p) + n = p.shape[1] - 1 + xi = np.atleast_1d(x) + shape0 = xi.shape + if n == 0: + return np.ones(shape0) * p[0] + xi = xi.ravel() + xn = np.ones((n + 1, len(xi))) + xn[1] = xi - p[1, 1] + for i in range(2, n + 1): + xn[i, :] = (xi - p[1, i]) * xn[i - 1, :] - p[2, i] * xn[i - 2, :] + ys = np.dot(p[0], xn) + return ys.reshape(shape0) + + +def ortho2poly(p): + """ + Converts orthogonal polynomial to ordinary polynomial coefficients + + Parameters + ---------- + p : array-like + orthogonal polynomial coefficients + + Returns + ------- + p : ndarray + ordinary polynomial coefficients + + It is not advised to do this for p.shape[1]>10 due to numerical + cancellations. + + See also + -------- + orthoval + orthofit + + Examples + -------- + >>> import numpy as np + >>> x = np.array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0]) + >>> y = np.array([0.0, 0.8, 0.9, 0.1, -0.8, -1.0]) + >>> p = orthofit(x, y, 3) + >>> p + array([[ 0. , -0.30285714, -0.16071429, 0.08703704], + [ 0. , 2.5 , 2.5 , 2.5 ], + [ 0. , 0. , 2.91666667, 2.13333333]]) + >>> ortho2poly(p) + array([ 0.08703704, -0.81349206, 1.69312169, -0.03968254]) + >>> np.polyfit(x, y, 3) + array([ 0.08703704, -0.81349206, 1.69312169, -0.03968254]) + + References + ---------- + """ + p = np.atleast_2d(p) + n = p.shape[1] - 1 + if n == 0: + return p[0] + x = [1, ] * (n + 1) + x[1] = np.array([1, - p[1, 1]]) + for i in range(2, n + 1): + x[i] = polyadd(polymul([1, - p[1, i]], x[i - 1]), - p[2, i] * x[i - 2]) + for i in range(n + 1): + x[i] *= p[0, i] + return reduce(polyadd, x) + + +def orthofit(x, y, n): + ''' + Fit orthogonal polynomial to data. + + Parameters + --------- + x, y : arrays + data Y(X) to fit to a polynomial. + n : integer + degree of fitted polynomial. + + Returns + ------- + p : array + orthogonal polynomial + Notes: ----- - ORTHOFIT smooths/fits data using a polynomial of degree N developed in + Orthofit smooths/fits data using a polynomial of degree N developed in a sequence of orthogonal polynomials. ORTHOFIT is more appropriate than - POLYFIT for polynomial fitting and smoothing since this method does not + polyfit for polynomial fitting and smoothing since this method does not involve any matrix linear system but a simple recursive procedure. Degrees much higher than 30 could be used with orthogonal polynomials, whereas badly conditioned matrices may appear with a classical polynomial fitting of degree typically higher than 10. - + To avoid using unnecessarily high degrees, you may let the function POLYDEG choose it for you. POLYDEG finds an optimal polynomial degree - according to the Akaike's information criterion (available here). - + according to the Akaike's information criterion. + Example: ------- - x = linspace(0,10,300); - y = sin(x.^3/100).^2 + 0.05*randn(size(x)); - ys = orthofit(x,y,25); - plot(x,y,'.',x,ys,'k') - try POLYFIT for comparison... - - Automatic degree determination with POLYDEG - n = polydeg(x,y); - ys = orthofit(x,y,n); - plot(x,y,'.',x,ys,'k') - - Reference: Methodes de calcul numerique 2. JP Nougier. Hermes Science + >>> x = np.linspace(0,10,300); + >>> y = np.sin(x**3/100)**2 + 0.05*np.random.randn(x.size) + >>> p = orthofit(x, y, 25) + >>> ys = orthoval(p, x) + + plot(x, y,'.',x, ys, 'k') + + See also + -------- + polydeg, polyfit, polyval + + Reference: + --------- + Methodes de calcul numerique 2. JP Nougier. Hermes Science Publications, 2001. Section 4.7 pp 116-121 - - Damien Garcia, 09/2007, revised 01/2010 - - See also POLYDEG, POLYFIT, POLYVAL. ''' - x, y = np.atleast_1d(x,y) - # Particular case: n=0 - if n==0: - p = y.mean() - ys = np.ones(y.shape)*p - return p, ys - - - # Reshape + x, y = np.atleast_1d(x, y) x = x.ravel() - # siz0 = y.shape y = y.ravel() - - # Coefficients of the orthogonal polynomials - p = np.zeros((3,n+1)) - p[1,1] = x.mean() - + # Particular case: n=0 + if n == 0: + return y.mean() + + # p = Coefficients of the orthogonal polynomials + p = np.zeros((3, n + 1)) + p[1, 1] = x.mean() + N = len(x) - PL = np.ones((N,n+1)) - PL[:,1] = x-p[1,1] - - for i in range(2,n+1): - p[1,i] = sum(x*PL[:,i-1]**2)/sum(PL[:,i-1]**2) - p[2,i] = sum(x*PL[:,i-2]*PL[:,i-1])/sum(PL[:,i-2]**2) - PL[:,i] = (x-p[1,i])*PL[:,i-1]-p[2,i]*PL[:,i-2] - #end - p[0,:] = sum(PL*y)/sum(PL**2); - - # ys = smoothed y - #ys = sum(PL*p(0,:) axis=1) - #ys.shape = siz0 - - - # Coefficients of the polynomial in its final form - - yi = np.zeros((n+1,n+1)) - yi[0,n] = 1 - yi[1,n-1:n+1] = 1 -p[1,1] - for i in range(2, n+1): - yi[i,:] = np.hstack((yi[i-1,1:], 0))-p[1,i]*yi[i-1,:]-p[2,i]*yi[i-2,:]; - - p = sum(p[0,:]*yi, axis=0) + PL = np.ones((n + 1, N)) + PL[1] = x - p[1, 1] + + for i in range(2, n + 1): + p[1, i] = np.dot(x, PL[i - 1] ** 2) / sum(PL[i - 1] ** 2) + p[2, i] = np.dot(x, PL[i - 2] * PL[i - 1]) / sum(PL[i - 2] ** 2) + PL[i] = (x - p[1, i]) * PL[i - 1] - p[2, i] * PL[i - 2] + p[0, :] = np.dot(PL, y) / sum(PL ** 2, axis=1) return p - + # ys = np.dot(p[0, :], PL) # smoothed y + + def polyreloc(p, x, y=0.0): """ Relocate polynomial @@ -403,8 +461,8 @@ def polyreloc(p, x, y=0.0): Parameters ---------- p : array-like, poly1d - vector or matrix of column vectors of polynomial coefficients to relocate. - (Polynomial coefficients are in decreasing order.) + vector or matrix of column vectors of polynomial coefficients to + relocate. (Polynomial coefficients are in decreasing order.) x : scalar distance to relocate P along x-axis y : scalar @@ -449,6 +507,7 @@ def polyreloc(p, x, y=0.0): r = poly1d(r) return r + def polyrescl(p, x, y=1.0): """ Rescale polynomial. @@ -456,8 +515,8 @@ def polyrescl(p, x, y=1.0): Parameters ---------- p : array-like, poly1d - vector or matrix of column vectors of polynomial coefficients to rescale. - (Polynomial coefficients are in decreasing order.) + vector or matrix of column vectors of polynomial coefficients to + rescale. (Polynomial coefficients are in decreasing order.) x,y : scalars defining the factors to rescale the polynomial `p` in x-direction and y-direction, respectively. @@ -490,7 +549,7 @@ def polyrescl(p, x, y=1.0): r = atleast_1d(p) n = r.shape[0] - xscale = (float(x) ** arange(1 - n , 1)) + xscale = (float(x) ** arange(1 - n, 1)) if r.ndim == 1: q = y * r * xscale else: @@ -499,6 +558,7 @@ def polyrescl(p, x, y=1.0): q = poly1d(q) return q + def polytrim(p): """ Trim polynomial by stripping off leading zeros. @@ -539,6 +599,7 @@ def polytrim(p): r = r[is_not_lead_zeros, :] return r + def poly2hstr(p, variable='x'): """ Return polynomial as a Horner represented string. @@ -568,9 +629,9 @@ def poly2hstr(p, variable='x'): var = variable coefs = polytrim(atleast_1d(p)) - order = len(coefs) - 1 # Order of polynomial. + order = len(coefs) - 1 # Order of polynomial. s = '' # Initialize output string. - ix = 1; + ix = 1 for expon in range(order, -1, -1): coef = coefs[order - expon] #% There is no point in adding a zero term (except if it's the only @@ -581,14 +642,16 @@ def poly2hstr(p, variable='x'): #% Append exponent if necessary. if ix > 1: exponstr = '%.0f' % ix - s = '%s**%s' % (s, exponstr); + s = '%s**%s' % (s, exponstr) ix = 1 #% Is it the first term? isfirst = s == '' # We need the coefficient only if it is different from 1 or -1 or # when it is the constant term. - needcoef = ((abs(coef) != 1) | (expon == 0) & isfirst) | 1 - isfirst + needcoef = ( + (abs(coef) != 1) | ( + expon == 0) & isfirst) | 1 - isfirst # We need the variable except in the constant term. needvar = (expon != 0) @@ -596,13 +659,12 @@ def poly2hstr(p, variable='x'): #% Add sign, but we don't need a leading plus-sign. if isfirst: if coef < 0: - s = '-' # % Unary minus. + s = '-' # % Unary minus. else: if coef < 0: - s = '%s - ' % s # % Binary minus (subtraction). + s = '%s - ' % s # % Binary minus (subtraction). else: - s = '%s + ' % s # % Binary plus (addition). - + s = '%s + ' % s # % Binary plus (addition). #% Append the coefficient if it is different from one or when it is #% the constant term. @@ -624,6 +686,7 @@ def poly2hstr(p, variable='x'): s = '0' return s + def poly2str(p, variable='x'): """ Return polynomial as a string. @@ -698,7 +761,8 @@ def poly2str(p, variable='x'): thestr = newstr return thestr -def polyshift(py, a= -1, b=1): + +def polyshift(py, a=-1, b=1): """ Polynomial coefficient shift @@ -739,7 +803,8 @@ def polyshift(py, a= -1, b=1): L = b - a return polyishift(py, -(2. + b + a) / L, (2. - b - a) / L) -def polyishift(px, a= -1, b=1): + +def polyishift(px, a=-1, b=1): """ Inverse polynomial coefficient shift @@ -782,15 +847,18 @@ def polyishift(px, a= -1, b=1): xloc = -float(a + b) / L return polyreloc(polyrescl(px, xscale), xloc) -def map_from_interval(x, a, b) : + +def map_from_interval(x, a, b): """F(x), where F: [a,b] -> [-1,1].""" return (x - (b + a) / 2.0) * (2.0 / (b - a)) -def map_to_interval(x, a, b) : + +def map_to_interval(x, a, b): """F(x), where F: [-1,1] -> [a,b].""" return (x * (b - a) + (b + a)) / 2.0 -def poly2cheb(p, a= -1, b=1): + +def poly2cheb(p, a=-1, b=1): """ Convert polynomial coefficients into Chebyshev coefficients @@ -842,7 +910,8 @@ def poly2cheb(p, a= -1, b=1): n = len(f.coeffs) return chebfit(f, n, a, b) -def cheb2poly(ck, a= -1, b=1): + +def cheb2poly(ck, a=-1, b=1): """ Converts Chebyshev coefficients to polynomial coefficients @@ -885,24 +954,25 @@ def cheb2poly(ck, a= -1, b=1): b_Nmi = zeros(1) b_Nmip1 = zeros(1) - y = r_[2 / (b - a), -(a + b) / (b - a)] + y = np.r_[2 / (b - a), -(a + b) / (b - a)] y2 = 2. * y # Clenshaw recurence for ix in xrange(n - 1): tmp = b_Nmi - b_Nmi = polymul(y2, b_Nmi) # polynomial multiplication + b_Nmi = polymul(y2, b_Nmi) # polynomial multiplication nb = len(b_Nmip1) b_Nmip1[-1] = b_Nmip1[-1] - ck[ix] b_Nmi[-nb::] = b_Nmi[-nb::] - b_Nmip1 b_Nmip1 = tmp - p = polymul(y, b_Nmi) # polynomial multiplication + p = polymul(y, b_Nmi) # polynomial multiplication nb = len(b_Nmip1) b_Nmip1[-1] = b_Nmip1[-1] - ck[n - 1] p[-nb::] = p[-nb::] - b_Nmip1 return polytrim(p) + def chebextr(n): """ Return roots of derivative of Chebychev polynomial of the first kind. @@ -933,7 +1003,8 @@ def chebextr(n): http://en.wikipedia.org/wiki/Chebyshev_nodes http://en.wikipedia.org/wiki/Chebyshev_polynomials """ - return - cos((pi * arange(n + 1)) / n); + return - cos((pi * arange(n + 1)) / n) + def chebroot(n, kind=1): """ @@ -972,7 +1043,7 @@ def chebroot(n, kind=1): """ if kind not in (1, 2): raise ValueError('kind must be 1 or 2') - return - cos(pi * (arange(n) + 0.5 * kind) / (n + kind - 1)); + return - cos(pi * (arange(n) + 0.5 * kind) / (n + kind - 1)) def chebpoly(n, x=None, kind=1): @@ -980,10 +1051,10 @@ def chebpoly(n, x=None, kind=1): Return Chebyshev polynomial of the first or second kind. These polynomials are orthogonal on the interval [-1,1], with - respect to the weight function w(x) = (1-x^2)^(-1/2+kind-1). + respect to the weight function w(x) = (1-x**2)**(-1/2+kind-1). - chebpoly(n) returns the coefficients of the Chebychev polynomial of degree N. - chebpoly(n,x) returns the Chebychev polynomial of degree N evaluated in X. + chebpoly(n) returns coefficients of the Chebychev polynomial of degree N. + chebpoly(n,x) returns the Chebychev polynomial of degree N evaluated at X. Parameters ---------- @@ -1024,14 +1095,15 @@ def chebpoly(n, x=None, kind=1): p = ones(1) else: p = round(pow(2, n - 2 + kind) * poly(chebroot(n, kind=kind))) - p[1::2] = 0; + p[1::2] = 0 return p - else: # Evaluate polynomial in chebychev form + else: # Evaluate polynomial in chebychev form ck = zeros(n + 1) ck[0] = 1. return _chebval(atleast_1d(x), ck, kind=kind) -def chebfit(fun, n=10, a= -1, b=1, trace=False): + +def chebfit(fun, n=10, a=-1, b=1, trace=False): """ Computes the Chebyshevs coefficients @@ -1092,7 +1164,7 @@ def chebfit(fun, n=10, a= -1, b=1, trace=False): if hasattr(fun, '__call__'): x = map_to_interval(chebroot(n), a, b) - f = fun(x); + f = fun(x) if trace: plt.plot(x, f, '+') else: @@ -1108,6 +1180,7 @@ def chebfit(fun, n=10, a= -1, b=1, trace=False): ck[0] = ck[0] / 2. return ck[::-1] + def dct(x, n=None): """ Discrete Cosine Transform @@ -1130,7 +1203,7 @@ def dct(x, n=None): http://en.wikipedia.org/wiki/Discrete_cosine_transform http://users.ece.utexas.edu/~bevans/courses/ee381k/lectures/ """ - + x = atleast_1d(x) if n is None: @@ -1158,6 +1231,7 @@ def dct(x, n=None): else: return y + def idct(x, n=None): """ Inverse Discrete Cosine Transform @@ -1184,7 +1258,6 @@ def idct(x, n=None): http://users.ece.utexas.edu/~bevans/courses/ee381k/lectures/ """ - x = atleast_1d(x) if n is None: @@ -1214,11 +1287,12 @@ def idct(x, n=None): else: return y + def _chebval(x, ck, kind=1): """ Evaluate polynomial in Chebyshev form. - A polynomial of degree N in Chebyshev form is a polynomial p(x) of the form: + A polynomial of degree N in Chebyshev form is a polynomial p(x): N p(x) = sum ck*Tk(x) @@ -1237,7 +1311,7 @@ def _chebval(x, ck, kind=1): http://mathworld.wolfram.com/ClenshawRecurrenceFormula.html """ n = len(ck) - b_Nmi = zeros(x.shape) # b_(N-i) + b_Nmi = zeros(x.shape) # b_(N-i) b_Nmip1 = b_Nmi.copy() # b_(N-i+1) x2 = 2 * x # Clenshaw reccurence @@ -1248,11 +1322,11 @@ def _chebval(x, ck, kind=1): return kind * x * b_Nmi - b_Nmip1 + ck[n - 1] -def chebval(x, ck, a= -1, b=1, kind=1, fill=None): +def chebval(x, ck, a=-1, b=1, kind=1, fill=None): """ Evaluate polynomial in Chebyshev form at X - A polynomial of degree N in Chebyshev form is a polynomial p(x) of the form: + A polynomial of degree N in Chebyshev form is a polynomial p(x) of the form N p(x) = sum ck*Tk(x) @@ -1265,7 +1339,8 @@ def chebval(x, ck, a= -1, b=1, kind=1, fill=None): x : array-like points to evaluate ck : array-like - polynomial coefficients in Chebyshev form ordered from highest degree to zero + polynomial coefficients in Chebyshev form ordered from highest degree + to zero a,b : real, scalars, optional limits for polynomial (Default -1,1) kind: 1 or 2, optional @@ -1311,7 +1386,7 @@ def chebval(x, ck, a= -1, b=1, kind=1, fill=None): return f -def chebder(ck, a= -1, b=1): +def chebder(ck, a=-1, b=1): """ Differentiate Chebyshev polynomial @@ -1360,9 +1435,10 @@ def chebder(ck, a= -1, b=1): for j in xrange(2, n): cder[j] = cder[j - 2] + 2 * (n - j) * ck[j] - return cder * 2. / (b - a) # Normalize to the interval b-a. + return cder * 2. / (b - a) # Normalize to the interval b-a. -def chebint(ck, a= -1, b=1): + +def chebint(ck, a=-1, b=1): """ Integrate Chebyshev polynomial @@ -1410,8 +1486,8 @@ def chebint(ck, a= -1, b=1): # p(x) = sum cn*Tn(x) # n=0 -# int p(x) dx = sum cn * int(Tn(x)dx) = 0.5*sum cn *{Tn+1(x)/(n+1) - Tn-1(x)/(n-1)} -# = 0.5 sum (cn-1-cn+1)*Tn/n n>0 +# int p(x) dx = sum cn * int(Tn(x)dx) = +# 0.5*sum cn *{Tn+1(x)/(n+1) - Tn-1(x)/(n-1)} = 0.5 sum (cn-1-cn+1)*Tn/n n>0 n = len(ck) @@ -1419,39 +1495,41 @@ def chebint(ck, a= -1, b=1): con = 0.25 * (b - a) dif1 = diff(ck[-1::-2]) - ix1 = r_[1:n - 1:2] + ix1 = np.r_[1:n - 1:2] cint[ix1] = -(con * dif1) / ix1 if n > 3: dif2 = diff(ck[-2::-2]) - ix2 = r_[2:n - 1:2] + ix2 = np.r_[2:n - 1:2] cint[ix2] = -(con * dif2) / ix2 cint = cint[::-1] - #% cint(n) is a special case + # cint(n) is a special case cint[-1] = (con * ck[n - 2]) / (n - 1) - cint[0] = 2 * np.sum((-1) ** r_[0:n - 1] * cint[-2::-1]) # Set integration constant + # Set integration constant + cint[0] = 2 * np.sum((-1) ** np.r_[0:n - 1] * cint[-2::-1]) return cint + class Cheb1d(object): coeffs = None order = None a = None b = None kind = None - def __init__(self, ck, a= -1, b=1, kind=1): + + def __init__(self, ck, a=-1, b=1, kind=1): if isinstance(ck, Cheb1d): for key in ck.__dict__.keys(): self.__dict__[key] = ck.__dict__[key] return cki = trim_zeros(atleast_1d(ck), 'b') if len(cki.shape) > 1: - raise ValueError, "Polynomial must be 1d only." + raise ValueError("Polynomial must be 1d only.") self.__dict__['coeffs'] = cki self.__dict__['order'] = len(cki) - 1 self.__dict__['a'] = a self.__dict__['b'] = b self.__dict__['kind'] = kind - def __call__(self, x): return chebval(x, self.coeffs, self.a, self.b, self.kind) @@ -1471,6 +1549,7 @@ class Cheb1d(object): def __str__(self): pass + def __neg__(self): new = Cheb1d(self) new.coeffs = -self.coeffs @@ -1479,7 +1558,6 @@ class Cheb1d(object): def __pos__(self): return self - def __add__(self, other): other = Cheb1d(other) new = Cheb1d(self) @@ -1504,13 +1582,14 @@ class Cheb1d(object): def __eq__(self, other): other = Cheb1d(other) return (all(self.coeffs == other.coeffs) and (self.a == other.a) - and (self.b == other.b) and (self.kind == other.kind)) + and (self.b == other.b) and (self.kind == other.kind)) def __ne__(self, other): - return any(self.coeffs != other.coeffs) or (self.a != other.a) or (self.b != other.b) or (self.kind != other.kind) + return any(self.coeffs != other.coeffs) or (self.a != other.a) or ( + self.b != other.b) or (self.kind != other.kind) def __setattr__(self, key, val): - raise ValueError, "Attributes cannot be changed this way." + raise ValueError("Attributes cannot be changed this way.") def __getattr__(self, key): if key in ['c', 'coef', 'coefficients']: @@ -1527,7 +1606,10 @@ class Cheb1d(object): try: return self.__dict__[key] except KeyError: - raise AttributeError("'%s' has no attribute '%s'" % (self.__class__, key)) + raise AttributeError( + "'%s' has no attribute '%s'" % + (self.__class__, key)) + def __getitem__(self, val): if val > self.order: return 0 @@ -1538,7 +1620,7 @@ class Cheb1d(object): def __setitem__(self, key, val): #ind = self.order - key if key < 0: - raise ValueError, "Does not support negative powers." + raise ValueError("Does not support negative powers.") if key > self.order: zr = zeros(key - self.order, self.coeffs.dtype) self.__dict__['coeffs'] = concatenate((self.coeffs, zr)) @@ -1579,6 +1661,7 @@ class Cheb1d(object): der.coeffs = chebder(self.coeffs, self.a, self.b) return der + def padefit(c, m=None): """ Rational polynomial fitting from polynomial coefficients @@ -1616,12 +1699,12 @@ def padefit(c, m=None): Pade approximation to exp(x) >>> import scipy.special as sp >>> import matplotlib.pyplot as plt - >>> c = poly1d(1./sp.gamma(np.r_[6+1:0:-1])) #polynomial coeff exponential function + >>> c = poly1d(1./sp.gamma(np.r_[6+1:0:-1])) >>> [p, q] = padefit(c) >>> p; q poly1d([ 0.00277778, 0.03333333, 0.2 , 0.66666667, 1. ]) poly1d([ 0.03333333, -0.33333333, 1. ]) - + >>> x = np.linspace(0,4); >>> h = plt.plot(x,c(x),x,p(x)/q(x),'g-', x,np.exp(x),'r.') >>> plt.close() @@ -1636,13 +1719,15 @@ def padefit(c, m=None): c = asarray(c) return pade(c[::-1], m) + def test_pade(): cof = array(([1.0, 1.0, 1.0 / 2, 1. / 6, 1. / 24])) p, q = pade(cof, 2) t = arange(0, 2, 0.1) assert(all(abs(p(t) / q(t) - exp(t)) < 0.3)) -def padefitlsq(fun, m, k, a= -1, b=1, trace=False, x=None, end_points=True): + +def padefitlsq(fun, m, k, a=-1, b=1, trace=False, x=None, end_points=True): """ Rational polynomial fitting. A minimax solution by least squares. @@ -1686,7 +1771,7 @@ def padefitlsq(fun, m, k, a= -1, b=1, trace=False, x=None, end_points=True): >>> c1; c2 poly1d([ 0.01443847, 0.128842 , 0.55284547, 0.99999962]) poly1d([-0.0049658 , 0.07610473, -0.44716929, 1. ]) - + >>> x = np.linspace(0,4) >>> h = plt.plot(x, polyval(c1,x)/polyval(c2,x),'g') >>> h = plt.plot(x, np.exp(x), 'r') @@ -1708,7 +1793,8 @@ def padefitlsq(fun, m, k, a= -1, b=1, trace=False, x=None, end_points=True): smallest_devmax = BIG ncof = m + k + 1 - npt = NFAC * ncof # % Number of points where function is evaluated, i.e. fineness of mesh + # % Number of points where function is evaluated, i.e. fineness of mesh + npt = NFAC * ncof if x is None: if end_points: @@ -1716,18 +1802,20 @@ def padefitlsq(fun, m, k, a= -1, b=1, trace=False, x=None, end_points=True): # the Chebychev polynomial of the first kind of degree NPT-1. x = map_to_interval(chebextr(npt - 1), a, b) else: - # Use the roots of the Chebychev polynomial of the first kind of degree NPT. - # Note this is useful if there are singularities close to the endpoints. + # Use the roots of the Chebychev polynomial of the first kind of + # degree NPT. Note this is useful if there are singularities close + # to the endpoints. x = map_to_interval(chebroot(npt, kind=1), a, b) - if hasattr(fun, '__call__'): fs = fun(x) else: fs = fun n = len(fs) if n < npt: - warnings.warn('Check the result! Number of function values should be at least: %d' % npt) + warnings.warn( + 'Check the result! ' + + 'Number of function values should be at least: %d' % npt) if trace: plt.plot(x, fs, '+') @@ -1738,7 +1826,7 @@ def padefitlsq(fun, m, k, a= -1, b=1, trace=False, x=None, end_points=True): u = zeros((npt, ncof)) for ix in xrange(MAXIT): - #% Set up design matrix for least squares fit. + # Set up design matrix for least squares fit. pow1 = wt bb = pow1 * (fs + abs(mad) * sign(ee)) @@ -1751,19 +1839,20 @@ def padefitlsq(fun, m, k, a= -1, b=1, trace=False, x=None, end_points=True): pow1 = pow1 * x u[:, jx] = pow1 - [u1, w, v] = linalg.svd(u, full_matrices=False) - cof = where(w == 0, 0.0, dot(bb, u1) / w) - cof = dot(cof, v) + cof = where(w == 0, 0.0, np.dot(bb, u1) / w) + cof = np.dot(cof, v) - #% Tabulate the deviations and revise the weights - ee = polyval(cof[m::-1], x) / polyval(cof[ncof:m:-1].tolist() + [1, ], x) - fs + # Tabulate the deviations and revise the weights + ee = polyval(cof[m::-1], x) / \ + polyval(cof[ncof:m:-1].tolist() + [1, ], x) - fs wt = np.abs(ee) devmax = max(wt) - mad = wt.mean() #% mean absolute deviation + mad = wt.mean() # % mean absolute deviation - if (devmax <= smallest_devmax): #% Save only the best coefficients found + # Save only the best coefficients found + if (devmax <= smallest_devmax): smallest_devmax = devmax c1 = cof[m::-1] c2 = cof[ncof:m:-1].tolist() + [1, ] @@ -1771,14 +1860,9 @@ def padefitlsq(fun, m, k, a= -1, b=1, trace=False, x=None, end_points=True): if trace: print('Iteration=%d, max error=%g' % (ix, devmax)) plt.plot(x, fs, x, ee + fs) - #c1=c1(:) - #c2=c2(:) return poly1d(c1), poly1d(c2) - - - def main(): [c1, c2] = padefitlsq(exp, 3, 3, 0, 2) @@ -1788,51 +1872,78 @@ def main(): plt.plot(x, exp(x), 'r') import scipy.special as sp - + p = [[1, 1, 1], [2, 2, 2]] pi = polyint(p, 1) _pr = polyreloc(p, 2) _pd = polyder(p) _st = poly2str(p) - c = poly1d(1. / sp.gamma(np.r_[6 + 1:0:-1])) #polynomial coeff exponential function + c = poly1d( + 1. / + sp.gamma( + np.r_[ + 6 + + 1:0:- + 1])) # polynomial coeff exponential function [p, q] = padefit(c) - x = linspace(0, 4); + x = linspace(0, 4) plt.plot(x, c(x), x, p(x) / q(x), 'g-', x, exp(x), 'r.') plt.close() x = arange(4) dx = dct(x) _idx = idct(dx) - - a = 0; - b = 2; - ck = chebfit(exp, 6, a, b); + + a = 0 + b = 2 + ck = chebfit(exp, 6, a, b) _t = chebval(0, ck, a, b) - x = linspace(0, 2, 6); + x = linspace(0, 2, 6) plt.plot(x, exp(x), 'r', x, chebval(x, ck, a, b), 'g.') - #x1 = chebroot(9).'*(b-a)/2+(b+a)/2 ; - #ck1 =chebfit([x1 exp(x1)],9,a,b); - #plot(x,exp(x),'r'), hold on - #plot(x,chebval(x,ck1,a,b),'g'), hold off - + # x1 = chebroot(9).'*(b-a)/2+(b+a)/2 ; + # ck1 =chebfit([x1 exp(x1)],9,a,b); + # plot(x,exp(x),'r'), hold on + # plot(x,chebval(x,ck1,a,b),'g'), hold off _t = poly2hstr([1, 1, 2]) py = [1, 0] - px = polyshift(py, 0, 5); - _t1 = polyval(px, [0, 2.5, 5]) #% This is the same as the line below - _t2 = polyval(py, [-1, 0, 1 ]) + px = polyshift(py, 0, 5) + _t1 = polyval(px, [0, 2.5, 5]) # % This is the same as the line below + _t2 = polyval(py, [-1, 0, 1]) px = [1, 0] - py = polyishift(px, 0, 5); - t1 = polyval(px, [0, 2.5, 5]) #% This is the same as the line below - t2 = polyval(py, [-1, 0, 1 ]) + py = polyishift(px, 0, 5) + t1 = polyval(px, [0, 2.5, 5]) # % This is the same as the line below + t2 = polyval(py, [-1, 0, 1]) print(t1, t2) + +def test_polydeg(): + x = np.linspace(0, 10, 300) + y = np.sin(x ** 3 / 100) ** 2 + 0.05 * np.random.randn(x.size) + n = polydeg(x, y) + #n = 2 + p = orthofit(x, y, n) + xi = linspace(x.min(), x.max()) + ys0 = orthoval(p, x) + ys = orthoval(p, xi) + + ys2 = orthoval(p, xi) + plt.plot(x, y, '.', x, ys0, 'k', xi, ys, 'r', xi, ys2, 'r.') + p0 = ortho2poly(p) + p1 = polyfit(x, ys0, n) + plt.plot(xi, polyval(p0, xi), 'g-.', xi, polyval(p1, xi), 'go') + plt.show('hold') + + def test_docstrings(): import doctest - doctest.testmod() + print('Testing docstrings in %s' % __file__) + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) + - if __name__ == '__main__': - test_docstrings() -# main() - + if False: # True: # + main() + else: + test_docstrings() + #test_polydeg() diff --git a/pywafo/src/wafo/powerpoint.py b/pywafo/src/wafo/powerpoint.py index 6f47388..d39b487 100644 --- a/pywafo/src/wafo/powerpoint.py +++ b/pywafo/src/wafo/powerpoint.py @@ -1,303 +1,320 @@ -''' -Created on 15. des. 2009 - -@author: pab -''' -#import os -#import sys -#import win32com -#from win32com.client.selecttlb import EnumTlbs -#typelib_mso = None -#typelib_msppt = None -#for typelib in EnumTlbs(): -# d = typelib.desc.split(' ') -# if d[0] == 'Microsoft' and d[1] == 'Office' and d[3] == 'Object' and d[4] == 'Library': -# typelib_mso = typelib -# if d[0] == 'Microsoft' and d[1] == 'PowerPoint' and d[3] == 'Object' and d[4] == 'Library': -# typelib_msppt = typelib -#if hasattr(sys, 'frozen'): # If we're an .exe file -# win32com.__gen_path__ = os.path.dirname(sys.executable) -## win32com.__gen_path__ = os.environ['TEMP'] -#if win32com.client.gencache.is_readonly: -# win32com.client.gencache.is_readonly = False -# win32com.client.gencache.Rebuild() -#MSPPT = win32com.client.gencache.EnsureModule(typelib_msppt.clsid, typelib_msppt.lcid, -# int(typelib_msppt.major), int(typelib_msppt.minor)) -#MSO = win32com.client.gencache.EnsureModule(typelib_mso.clsid, typelib_mso.lcid, -# int(typelib_mso.major), int(typelib_mso.minor)) -import os -import warnings -import win32com.client -import MSO -import MSPPT -from PIL import Image #@UnresolvedImport - -g = globals() -for c in dir(MSO.constants): - g[c] = getattr(MSO.constants, c) -for c in dir(MSPPT.constants): - g[c] = getattr(MSPPT.constants, c) - -class Powerpoint(object): - def __init__(self, file_name=''): - - self.application = win32com.client.Dispatch("Powerpoint.Application") - #self.application.Visible = True - self._visible = self.application.Visible - if file_name: - self.presentation = self.application.Presentations.Open(file_name) - else: - self.presentation = self.application.Presentations.Add() - self.num_slides = 0 - # default picture width and height - self.default_width = 500 - self.default_height = 400 - self.title_font = 'Arial' #'Boopee' - self.title_size = 36 - self.text_font = 'Arial' #'Boopee' - self.text_size = 20 - self.footer = '' - - def set_footer(self): - ''' - Set Footer in SlideMaster and NotesMaster - ''' - if self.footer: - if self.presentation.HasTitleMaster: - TMHF = self.presentation.TitleMaster.HeadersFooters - TMHF.Footer.Text = self.footer - TMHF.Footer.Visible = True - - SMHF = self.presentation.SlideMaster.HeadersFooters - SMHF.Footer.Text = self.footer - SMHF.Footer.Visible = True - SMHF.SlideNumber.Visible= True - NMHF = self.presentation.NotesMaster.HeadersFooters - NMHF.Footer.Text = self.footer - NMHF.SlideNumber.Visible= True - for slide in self.presentation.Slides: - shapes = slide.Shapes - for shape in shapes: - if shape.Name=='Footer': - footer = shape - break - else: - footer = shapes.AddTextbox(msoTextOrientationHorizontal, Left=0, Top=510, Width=720, Height=28.875) #@UndefinedVariable - footer.Name = 'Footer' - footer.TextFrame.TextRange.Text = self.footer - - - def add_title_slide(self, title, subtitle=''): - self.num_slides +=1 - slide = self.presentation.Slides.Add(self.num_slides, MSPPT.constants.ppLayoutTitle) - - unused_title_id, unused_textbox_id = 1, 2 - for id_, title1 in enumerate([title, subtitle]): - titlerange = slide.Shapes(id_+1).TextFrame.TextRange - titlerange.Text = title1 - titlerange.Font.Name = self.title_font - titlerange.Font.Size = self.title_size-id_*12 if self.title_size>22 else self.title_size - - def add_slide(self, title='', texts='', notes='', image_file='', - maxlevel=None, left=220, width=-1, height=-1): - self.num_slides +=1 - slide = self.presentation.Slides.Add(self.num_slides, MSPPT.constants.ppLayoutText) - - self.add2slide(slide, title, texts, notes, image_file, maxlevel, left, width, height) - return slide - - def add2slide(self, slide, title='', texts='', notes='', image_file='', - maxlevel=None, left=220, width=-1, height=-1, keep_aspect=True): - title_id, textbox_id = 1, 2 - if title: - titlerange = slide.Shapes(title_id).TextFrame.TextRange - titlerange.Font.Name = self.title_font - titlerange.Text = title - titlerange.Font.Size = self.title_size - - if texts != '' and texts != ['']: - #textrange = slide.Shapes(textbox_id).TextFrame.TextRange - self._add_text(slide, textbox_id, texts, maxlevel) - - if image_file != '' and image_file != ['']: - if keep_aspect: - im = Image.open(image_file) - t_w, t_h = im.size - if height<=0 and width<=0: - if t_w*self.default_height < t_h*self.default_width: - height = self.default_height - else: - width = self.default_width - if height<=0 and width: - height = t_h * width / t_w - elif height and width <=0: - width = t_w * height / t_h - - slide.Shapes.AddPicture(FileName=image_file, LinkToFile=False, - SaveWithDocument=True, - Left=left, Top=110, - Width=width, Height=height) #400) - if notes != '' and notes != ['']: - notespage = slide.NotesPage #.Shapes(2).TextFrame.TextRange - self._add_text(notespage, 2, notes) - return slide - - def _add_text(self, page, id, txt, maxlevel=None): #@ReservedAssignment - page.Shapes(id).TextFrame.TextRange.Font.Name = self.text_font - - if isinstance(txt, dict): - self._add_text_from_dict(page, id, txt, 1, maxlevel) - elif isinstance(txt, (list, tuple)): - self._add_text_from_list(page, id, txt, maxlevel) - else: - unused_tr = page.Shapes(id).TextFrame.TextRange.InsertAfter(txt) - unused_temp = page.Shapes(id).TextFrame.TextRange.InsertAfter('\r') - - page.Shapes(id).TextFrame.TextRange.Font.Size = self.text_size - - def _add_text_from_dict(self, page, id, txt_dict, level, maxlevel=None): #@ReservedAssignment - if maxlevel is None or level<=maxlevel: - for name, subdict in txt_dict.iteritems(): - tr = page.Shapes(id).TextFrame.TextRange.InsertAfter(name) - unused_temp = page.Shapes(id).TextFrame.TextRange.InsertAfter('\r') - tr.IndentLevel = level - self._add_text_from_dict(page, id, subdict, min(level+1,5), maxlevel) - - def _add_text_from_list(self, page, id, txt_list, maxlevel=None): #@ReservedAssignment - for txt in txt_list: - level = 1 - while isinstance(txt, (list, tuple)): - txt = txt[0] - level += 1 - if maxlevel is None or level<=maxlevel: - tr = page.Shapes(id).TextFrame.TextRange.InsertAfter(txt) - unused_temp = page.Shapes(id).TextFrame.TextRange.InsertAfter('\r') - tr.IndentLevel = level - - - def save(self, fullfile=''): - if fullfile: - self.presentation.SaveAs(FileName=fullfile) - else: - self.presentation.Save() - - - def quit(self): #@ReservedAssignment - if self._visible: - self.presentation.Close() - else: - self.application.Quit() - - def quit_only_if_hidden(self): - if not self._visible: - self.application.Quit() - -def test_powerpoint(): - # Make powerpoint - - ppt = Powerpoint() - #time. - ppt.footer='This is the footer' - ppt.add_title_slide('Title', 'Per A.') - ppt.add_slide(title='alsfkasldk', texts='asdflaf', notes='asdfas') - ppt.set_footer() - -def make_ppt(): - application = win32com.client.Dispatch("Powerpoint.Application") - application.Visible = True - presentation = application.Presentations.Add() - slide1 = presentation.Slides.Add(1, MSPPT.constants.ppLayoutText) - - -# title = slide1.Shapes.AddTextBox(Type=msoTextOrientationHorizontal,Left=50, Top=10, Width=620, Height=70) -# title.TextFrame.TextRange.Text = 'Overskrift' - - - title_id, textbox_id = 1,2 - slide1.Shapes(title_id).TextFrame.TextRange.Text = 'Overskrift' - #slide1.Shapes(title_id).TextFrame.Width = 190 - - - slide1.Shapes(textbox_id).TextFrame.TextRange.InsertAfter('Test') - unused_tr = slide1.Shapes(textbox_id).TextFrame.TextRange.InsertAfter('\r') - slide1.Shapes(textbox_id).TextFrame.TextRange.IndentLevel = 1 - tr = slide1.Shapes(textbox_id).TextFrame.TextRange.InsertAfter('tests') - unused_tr0 = slide1.Shapes(textbox_id).TextFrame.TextRange.InsertAfter('\r') - tr.IndentLevel=2 - tr1 = slide1.Shapes(textbox_id).TextFrame.TextRange.InsertAfter('test3') - tr1.IndentLevel=3 - #slide1.Shapes(textbox_id).TextFrame.TextRange.Text = 'Test \r test2' - -# textbox = slide1.Shapes.AddTextBox(Type=msoTextOrientationHorizontal,Left=30, Top=100, Width=190, Height=400) -# textbox.TextFrame.TextRange.Text = 'Test \r test2' - #picbox = slide1.Shapes(picb_id) - - filename = r'c:\temp\data1_report1_and_2_Tr120_1.png' - slide1.Shapes.AddPicture(FileName=filename, LinkToFile=False, - SaveWithDocument=True, - Left=220, Top=100, Width=500, Height=420) - - slide1.NotesPage.Shapes(2).TextFrame.TextRange.Text = 'test' - - - -# for shape in slide1.Shapes: -# shape.TextFrame.TextRange.Text = 'Test \r test2' - #slide1.Shapes.Titles.TextFrames.TestRange.Text -# shape = slide1.Shapes.AddShape(msoShapeRectangle, 300, 100, 400, 400) -# shape.TextFrame.TextRange.Text = 'Test \n test2' -# shape.TextFrame.TextRange.Font.Size = 12 - - # -# app = wx.PySimpleApp() -# dialog = wx.FileDialog(None, 'Choose image file', defaultDir=os.getcwd(), -# wildcard='*.*', -# style=wx.OPEN | wx.CHANGE_DIR | wx.MULTIPLE) -# -# if dialog.ShowModal() == wx.ID_OK: -# files_or_paths = dialog.GetPaths() -# for filename in files_or_paths: -# slide1.Shapes.AddPicture(FileName=filename, LinkToFile=False, -# SaveWithDocument=True, -# Left=100, Top=100, Width=200, Height=200) -# dialog.Destroy() - #presentation.Save() - #application.Quit() -def rename_ppt(): - root = r'C:/pab/tsm_opeval/analysis_tsmps_aco_v2008b/plots' -# root = r'C:/pab/tsm_opeval/analysis_tsmps_mag_v2008b/plots' -# root = r'C:/pab/tsm_opeval/analysis_tsmps_mag_v2010a/plots' -# root = r'C:/pab/tsm_opeval/analysis_tsmps_aco_v2010a/plots' - #filename = r'mag_sweep_best_tsmps_ship_eff0-10.ppt' - filenames = os.listdir(root) - prefix = 'TSMPSv2008b_' - #prefix = 'TSMPSv2010a_' - for filename in filenames: - if filename.endswith('.ppt'): - try: - ppt = Powerpoint(os.path.join(root,filename)) - ppt.footer = prefix + filename - ppt.set_footer() - ppt.save(os.path.join(root, ppt.footer)) - except: - warnings.warn('Unable to load %s' % filename) -def load_file_into_ppt(): - root = r'C:/pab/tsm_opeval/analysis_tsmps_aco_v2008b/plots' -# root = r'C:/pab/tsm_opeval/analysis_tsmps_mag_v2008b/plots' -# root = r'C:/pab/tsm_opeval/analysis_tsmps_mag_v2010a/plots' -# root = r'C:/pab/tsm_opeval/analysis_tsmps_aco_v2010a/plots' - #filename = r'mag_sweep_best_tsmps_ship_eff0-10.ppt' - filenames = os.listdir(root) - prefix = 'TSMPSv2008b_' - #prefix = 'TSMPSv2010a_' - for filename in filenames: - if filename.startswith(prefix) and filename.endswith('.ppt'): - try: - unused_ppt = Powerpoint(os.path.join(root,filename)) - except: - warnings.warn('Unable to load %s' % filename) -if __name__ == '__main__': - #make_ppt() - #test_powerpoint() - #load_file_into_ppt() - rename_ppt() \ No newline at end of file +''' +Created on 15. des. 2009 + +@author: pab +''' +#import os +#import sys +#import win32com +#from win32com.client.selecttlb import EnumTlbs +#typelib_mso = None +#typelib_msppt = None +# for typelib in EnumTlbs(): +# d = typelib.desc.split(' ') +# if d[0] == 'Microsoft' and d[1] == 'Office' and d[3] == 'Object' \ +# and d[4] == 'Library': +# typelib_mso = typelib +# if d[0] == 'Microsoft' and d[1] == 'PowerPoint' and d[3] == 'Object' \ +# and d[4] == 'Library': +# typelib_msppt = typelib +# if hasattr(sys, 'frozen'): # If we're an .exe file +# win32com.__gen_path__ = os.path.dirname(sys.executable) +## win32com.__gen_path__ = os.environ['TEMP'] +# if win32com.client.gencache.is_readonly: +# win32com.client.gencache.is_readonly = False +# win32com.client.gencache.Rebuild() +# MSPPT = win32com.client.gencache.EnsureModule(typelib_msppt.clsid, +# typelib_msppt.lcid, +# int(typelib_msppt.major), +# int(typelib_msppt.minor)) +# MSO = win32com.client.gencache.EnsureModule(typelib_mso.clsid, +# typelib_mso.lcid, +# int(typelib_mso.major), int(typelib_mso.minor)) +import os +import warnings +import win32com.client +import MSO +import MSPPT +from PIL import Image # @UnresolvedImport + +g = globals() +for c in dir(MSO.constants): + g[c] = getattr(MSO.constants, c) +for c in dir(MSPPT.constants): + g[c] = getattr(MSPPT.constants, c) + + +class Powerpoint(object): + + def __init__(self, file_name=''): + + self.application = win32com.client.Dispatch("Powerpoint.Application") + #self.application.Visible = True + self._visible = self.application.Visible + if file_name: + self.presentation = self.application.Presentations.Open(file_name) + else: + self.presentation = self.application.Presentations.Add() + self.num_slides = 0 + # default picture width and height + self.default_width = 500 + self.default_height = 400 + self.title_font = 'Arial' # 'Boopee' + self.title_size = 36 + self.text_font = 'Arial' # 'Boopee' + self.text_size = 20 + self.footer = '' + + def set_footer(self): + ''' + Set Footer in SlideMaster and NotesMaster + ''' + if self.footer: + if self.presentation.HasTitleMaster: + TMHF = self.presentation.TitleMaster.HeadersFooters + TMHF.Footer.Text = self.footer + TMHF.Footer.Visible = True + + SMHF = self.presentation.SlideMaster.HeadersFooters + SMHF.Footer.Text = self.footer + SMHF.Footer.Visible = True + SMHF.SlideNumber.Visible = True + NMHF = self.presentation.NotesMaster.HeadersFooters + NMHF.Footer.Text = self.footer + NMHF.SlideNumber.Visible = True + for slide in self.presentation.Slides: + shapes = slide.Shapes + for shape in shapes: + if shape.Name == 'Footer': + footer = shape + break + else: + footer = shapes.AddTextbox( + msoTextOrientationHorizontal, # @UndefinedVariable + Left=0, Top=510, Width=720, Height=28.875) + footer.Name = 'Footer' + footer.TextFrame.TextRange.Text = self.footer + + def add_title_slide(self, title, subtitle=''): + self.num_slides += 1 + slide = self.presentation.Slides.Add( + self.num_slides, MSPPT.constants.ppLayoutTitle) + + unused_title_id, unused_textbox_id = 1, 2 + for id_, title1 in enumerate([title, subtitle]): + titlerange = slide.Shapes(id_ + 1).TextFrame.TextRange + titlerange.Text = title1 + titlerange.Font.Name = self.title_font + titlerange.Font.Size = self.title_size - id_ * \ + 12 if self.title_size > 22 else self.title_size + + def add_slide(self, title='', texts='', notes='', image_file='', + maxlevel=None, left=220, width=-1, height=-1): + self.num_slides += 1 + slide = self.presentation.Slides.Add( + self.num_slides, MSPPT.constants.ppLayoutText) + + self.add2slide(slide, title, texts, notes, image_file, maxlevel, left, + width, height) + return slide + + def add2slide(self, slide, title='', texts='', notes='', image_file='', + maxlevel=None, left=220, width=-1, height=-1, + keep_aspect=True): + title_id, textbox_id = 1, 2 + if title: + titlerange = slide.Shapes(title_id).TextFrame.TextRange + titlerange.Font.Name = self.title_font + titlerange.Text = title + titlerange.Font.Size = self.title_size + + if texts != '' and texts != ['']: + #textrange = slide.Shapes(textbox_id).TextFrame.TextRange + self._add_text(slide, textbox_id, texts, maxlevel) + + if image_file != '' and image_file != ['']: + if keep_aspect: + im = Image.open(image_file) + t_w, t_h = im.size + if height <= 0 and width <= 0: + if t_w * self.default_height < t_h * self.default_width: + height = self.default_height + else: + width = self.default_width + if height <= 0 and width: + height = t_h * width / t_w + elif height and width <= 0: + width = t_w * height / t_h + + slide.Shapes.AddPicture(FileName=image_file, LinkToFile=False, + SaveWithDocument=True, + Left=left, Top=110, + Width=width, Height=height) # 400) + if notes != '' and notes != ['']: + notespage = slide.NotesPage # .Shapes(2).TextFrame.TextRange + self._add_text(notespage, 2, notes) + return slide + + def _add_text(self, page, id, txt, maxlevel=None): # @ReservedAssignment + page.Shapes(id).TextFrame.TextRange.Font.Name = self.text_font + + if isinstance(txt, dict): + self._add_text_from_dict(page, id, txt, 1, maxlevel) + elif isinstance(txt, (list, tuple)): + self._add_text_from_list(page, id, txt, maxlevel) + else: + unused_tr = page.Shapes(id).TextFrame.TextRange.InsertAfter(txt) + unused_temp = page.Shapes(id).TextFrame.TextRange.InsertAfter('\r') + + page.Shapes(id).TextFrame.TextRange.Font.Size = self.text_size + + def _add_text_from_dict(self, page, id, txt_dict, # @ReservedAssignment + level, maxlevel=None): + if maxlevel is None or level <= maxlevel: + for name, subdict in txt_dict.iteritems(): + tr = page.Shapes(id).TextFrame.TextRange.InsertAfter(name) + unused_temp = page.Shapes( + id).TextFrame.TextRange.InsertAfter('\r') + tr.IndentLevel = level + self._add_text_from_dict( + page, id, subdict, min(level + 1, 5), maxlevel) + + def _add_text_from_list(self, page, id, # @ReservedAssignment + txt_list, maxlevel=None): + for txt in txt_list: + level = 1 + while isinstance(txt, (list, tuple)): + txt = txt[0] + level += 1 + if maxlevel is None or level <= maxlevel: + tr = page.Shapes(id).TextFrame.TextRange.InsertAfter(txt) + unused_temp = page.Shapes( + id).TextFrame.TextRange.InsertAfter('\r') + tr.IndentLevel = level + + def save(self, fullfile=''): + if fullfile: + self.presentation.SaveAs(FileName=fullfile) + else: + self.presentation.Save() + + def quit(self): # @ReservedAssignment + if self._visible: + self.presentation.Close() + else: + self.application.Quit() + + def quit_only_if_hidden(self): + if not self._visible: + self.application.Quit() + + +def test_powerpoint(): + # Make powerpoint + + ppt = Powerpoint() + # time. + ppt.footer = 'This is the footer' + ppt.add_title_slide('Title', 'Per A.') + ppt.add_slide(title='alsfkasldk', texts='asdflaf', notes='asdfas') + ppt.set_footer() + + +def make_ppt(): + application = win32com.client.Dispatch("Powerpoint.Application") + application.Visible = True + presentation = application.Presentations.Add() + slide1 = presentation.Slides.Add(1, MSPPT.constants.ppLayoutText) + +# title = slide1.Shapes.AddTextBox(Type=msoTextOrientationHorizontal, +# Left=50, Top=10, Width=620, Height=70) +# title.TextFrame.TextRange.Text = 'Overskrift' + title_id, textbox_id = 1, 2 + slide1.Shapes(title_id).TextFrame.TextRange.Text = 'Overskrift' + #slide1.Shapes(title_id).TextFrame.Width = 190 + + slide1.Shapes(textbox_id).TextFrame.TextRange.InsertAfter('Test') + unused_tr = slide1.Shapes(textbox_id).TextFrame.TextRange.InsertAfter('\r') + slide1.Shapes(textbox_id).TextFrame.TextRange.IndentLevel = 1 + tr = slide1.Shapes(textbox_id).TextFrame.TextRange.InsertAfter('tests') + unused_tr0 = slide1.Shapes( + textbox_id).TextFrame.TextRange.InsertAfter('\r') + tr.IndentLevel = 2 + tr1 = slide1.Shapes(textbox_id).TextFrame.TextRange.InsertAfter('test3') + tr1.IndentLevel = 3 + #slide1.Shapes(textbox_id).TextFrame.TextRange.Text = 'Test \r test2' + +# textbox = slide1.Shapes.AddTextBox(Type=msoTextOrientationHorizontal, +# Left=30, Top=100, Width=190, Height=400) +# textbox.TextFrame.TextRange.Text = 'Test \r test2' + #picbox = slide1.Shapes(picb_id) + + filename = r'c:\temp\data1_report1_and_2_Tr120_1.png' + slide1.Shapes.AddPicture(FileName=filename, LinkToFile=False, + SaveWithDocument=True, + Left=220, Top=100, Width=500, Height=420) + + slide1.NotesPage.Shapes(2).TextFrame.TextRange.Text = 'test' + + +# for shape in slide1.Shapes: +# shape.TextFrame.TextRange.Text = 'Test \r test2' + # slide1.Shapes.Titles.TextFrames.TestRange.Text +# shape = slide1.Shapes.AddShape(msoShapeRectangle, 300, 100, 400, 400) +# shape.TextFrame.TextRange.Text = 'Test \n test2' +# shape.TextFrame.TextRange.Font.Size = 12 + # +# app = wx.PySimpleApp() +# dialog = wx.FileDialog(None, 'Choose image file', defaultDir=os.getcwd(), +# wildcard='*.*', +# style=wx.OPEN | wx.CHANGE_DIR | wx.MULTIPLE) +# +# if dialog.ShowModal() == wx.ID_OK: +# files_or_paths = dialog.GetPaths() +# for filename in files_or_paths: +# slide1.Shapes.AddPicture(FileName=filename, LinkToFile=False, +# SaveWithDocument=True, +# Left=100, Top=100, Width=200, Height=200) +# dialog.Destroy() + # presentation.Save() + # application.Quit() +def rename_ppt(): + root = r'C:/pab/tsm_opeval/analysis_tsmps_aco_v2008b/plots' +# root = r'C:/pab/tsm_opeval/analysis_tsmps_mag_v2008b/plots' +# root = r'C:/pab/tsm_opeval/analysis_tsmps_mag_v2010a/plots' +# root = r'C:/pab/tsm_opeval/analysis_tsmps_aco_v2010a/plots' + #filename = r'mag_sweep_best_tsmps_ship_eff0-10.ppt' + filenames = os.listdir(root) + prefix = 'TSMPSv2008b_' + #prefix = 'TSMPSv2010a_' + for filename in filenames: + if filename.endswith('.ppt'): + try: + ppt = Powerpoint(os.path.join(root, filename)) + ppt.footer = prefix + filename + ppt.set_footer() + ppt.save(os.path.join(root, ppt.footer)) + except: + warnings.warn('Unable to load %s' % filename) + + +def load_file_into_ppt(): + root = r'C:/pab/tsm_opeval/analysis_tsmps_aco_v2008b/plots' +# root = r'C:/pab/tsm_opeval/analysis_tsmps_mag_v2008b/plots' +# root = r'C:/pab/tsm_opeval/analysis_tsmps_mag_v2010a/plots' +# root = r'C:/pab/tsm_opeval/analysis_tsmps_aco_v2010a/plots' + #filename = r'mag_sweep_best_tsmps_ship_eff0-10.ppt' + filenames = os.listdir(root) + prefix = 'TSMPSv2008b_' + #prefix = 'TSMPSv2010a_' + for filename in filenames: + if filename.startswith(prefix) and filename.endswith('.ppt'): + try: + unused_ppt = Powerpoint(os.path.join(root, filename)) + except: + warnings.warn('Unable to load %s' % filename) +if __name__ == '__main__': + # make_ppt() + # test_powerpoint() + # load_file_into_ppt() + rename_ppt() diff --git a/pywafo/src/wafo/sg_filter.py b/pywafo/src/wafo/sg_filter.py index 6b6e3e7..d539c73 100644 --- a/pywafo/src/wafo/sg_filter.py +++ b/pywafo/src/wafo/sg_filter.py @@ -1,428 +1,938 @@ -import numpy as np -#from math import pow -#from numpy import zeros,dot -from numpy import abs, size, convolve, linalg, concatenate #@UnresolvedImport - -__all__ = ['calc_coeff', 'smooth', 'smooth_last'] - - -def calc_coeff(n, degree, diff_order=0): - """ calculates filter coefficients for symmetric savitzky-golay filter. - see: http://www.nrbook.com/a/bookcpdf/c14-8.pdf - - n means that 2*n+1 values contribute to the - smoother. - - degree is degree of fitting polynomial - - diff_order is degree of implicit differentiation. - 0 means that filter results in smoothing of function - 1 means that filter results in smoothing the first - derivative of function. - and so on ... - - """ - order_range = np.arange(degree + 1) - k_range = np.arange(-n, n + 1, dtype=float).reshape(-1, 1) - b = np.mat(k_range ** order_range) - #b = np.mat([[float(k)**i for i in order_range] for k in range(-n,n+1)]) - coeff = linalg.pinv(b).A[diff_order] - return coeff - -def smooth_last(signal, coeff, k=0): - n = size(coeff - 1) // 2 - y = np.squeeze(signal) - if y.ndim > 1: - coeff.shape = (-1, 1) - first_vals = y[0] - abs(y[n:0:-1] - y[0]) - last_vals = y[-1] + abs(y[-2:-n - 2:-1] - y[-1]) - y = concatenate((first_vals, y, last_vals)) - return (y[-2 * n - 1 - k:-k] * coeff).sum(axis=0) - - -def smooth(signal, coeff, pad=True): - - """ applies coefficients calculated by calc_coeff() - to signal """ - - n = size(coeff - 1) // 2 - y = np.squeeze(signal) - if pad: - first_vals = y[0] - abs(y[n:0:-1] - y[0]) - last_vals = y[-1] + abs(y[-2:-n - 2:-1] - y[-1]) - y = concatenate((first_vals, y, last_vals)) - n *= 2 - d = y.ndim - if d > 1: - y1 = y.reshape(y.shape[0], -1) - res = [] - for i in range(y1.shape[1]): - res.append(convolve(y1[:, i], coeff)[n:-n]) - res = np.asarray(res).T - else: - res = convolve(y, coeff)[n:-n] - return res - -class SavitzkyGolay(object): - r"""Smooth (and optionally differentiate) data with a Savitzky-Golay filter. - - The Savitzky-Golay filter removes high frequency noise from data. - It has the advantage of preserving the original shape and - features of the signal better than other types of filtering - approaches, such as moving averages techniques. - - Parameters - ---------- - n : int - the size of the smoothing window is 2*n+1. - degree : int - the order of the polynomial used in the filtering. - Must be less than `window_size` - 1, i.e, less than 2*n. - diff_order : int - the order of the derivative to compute (default = 0 means only smoothing) - 0 means that filter results in smoothing of function - 1 means that filter results in smoothing the first derivative of function. - and so on ... - - Notes - ----- - The Savitzky-Golay is a type of low-pass filter, particularly - suited for smoothing noisy data. The main idea behind this - approach is to make for each point a least-square fit with a - polynomial of high order over a odd-sized window centered at - the point. - - Examples - -------- - >>> t = np.linspace(-4, 4, 500) - >>> y = np.exp( -t**2 ) + np.random.normal(0, 0.05, t.shape) - >>> ysg = SavitzkyGolay(n=15, degree=4).smooth(y) - >>> import matplotlib.pyplot as plt - >>> hy = plt.plot(t, y, label='Noisy signal') - >>> h = plt.plot(t, np.exp(-t**2), 'k', lw=1.5, label='Original signal') - >>> h = plt.plot(t, ysg, 'r', label='Filtered signal') - >>> h = plt.legend() - >>> plt.show() - - References - ---------- - .. [1] A. Savitzky, M. J. E. Golay, Smoothing and Differentiation of - Data by Simplified Least Squares Procedures. Analytical - Chemistry, 1964, 36 (8), pp 1627-1639. - .. [2] Numerical Recipes 3rd Edition: The Art of Scientific Computing - W.H. Press, S.A. Teukolsky, W.T. Vetterling, B.P. Flannery - Cambridge University Press ISBN-13: 9780521880688 - """ - def __init__(self, n, degree=1, diff_order=0): - self.n = n - self.degree = degree - self.diff_order = diff_order - self.calc_coeff() - - def calc_coeff(self): - """ calculates filter coefficients for symmetric savitzky-golay filter. - """ - n = self.n - order_range = np.arange(self.degree + 1) - k_range = np.arange(-n, n + 1, dtype=float).reshape(-1, 1) - b = np.mat(k_range ** order_range) - #b = np.mat([[float(k)**i for i in order_range] for k in range(-n,n+1)]) - self._coeff = linalg.pinv(b).A[self.diff_order] - def smooth_last(self, signal, k=0): - coeff = self._coeff - n = size(coeff - 1) // 2 - y = np.squeeze(signal) - if y.ndim > 1: - coeff.shape = (-1, 1) - first_vals = y[0] - abs(y[n:0:-1] - y[0]) - last_vals = y[-1] + abs(y[-2:-n - 2:-1] - y[-1]) - y = concatenate((first_vals, y, last_vals)) - return (y[-2 * n - 1 - k:-k] * coeff).sum(axis=0) - - - def smooth(self, signal, pad=True): - """ - Returns smoothed signal (or it's n-th derivative). - - Parameters - ---------- - y : array_like, shape (N,) - the values of the time history of the signal. - pad : bool - pad first and last values to lessen the end effects. - - Returns - ------- - ys : ndarray, shape (N) - the smoothed signal (or it's n-th derivative). - """ - coeff = self._coeff - n = size(coeff - 1) // 2 - y = np.squeeze(signal) - if pad: - first_vals = y[0] - abs(y[n:0:-1] - y[0]) - last_vals = y[-1] + abs(y[-2:-n - 2:-1] - y[-1]) - y = concatenate((first_vals, y, last_vals)) - n *= 2 - d = y.ndim - if d > 1: - y1 = y.reshape(y.shape[0], -1) - res = [] - for i in range(y1.shape[1]): - res.append(convolve(y1[:, i], coeff)[n:-n]) - res = np.asarray(res).T - else: - res = convolve(y, coeff)[n:-n] - return res - -class Kalman(object): - ''' - Kalman filter object - updates a system state vector estimate based upon an - observation, using a discrete Kalman filter. - - The Kalman filter is "optimal" under a variety of - circumstances. An excellent paper on Kalman filtering at - the introductory level, without detailing the mathematical - underpinnings, is: - - "An Introduction to the Kalman Filter" - Greg Welch and Gary Bishop, University of North Carolina - http://www.cs.unc.edu/~welch/kalman/kalmanIntro.html - - PURPOSE: - The purpose of each iteration of a Kalman filter is to update - the estimate of the state vector of a system (and the covariance - of that vector) based upon the information in a new observation. - The version of the Kalman filter in this function assumes that - observations occur at fixed discrete time intervals. Also, this - function assumes a linear system, meaning that the time evolution - of the state vector can be calculated by means of a state transition - matrix. - - USAGE: - filt = Kalman(R, x, P, A, B=0, u=0, Q, H) - x = filt(z) - - filt is a "system" object containing various fields used as input - and output. The state estimate "x" and its covariance "P" are - updated by the function. The other fields describe the mechanics - of the system and are left unchanged. A calling routine may change - these other fields as needed if state dynamics are time-dependent; - otherwise, they should be left alone after initial values are set. - The exceptions are the observation vector "z" and the input control - (or forcing function) "u." If there is an input function, then - "u" should be set to some nonzero value by the calling routine. - - System dynamics - --------------- - - The system evolves according to the following difference equations, - where quantities are further defined below: - - x = Ax + Bu + w meaning the state vector x evolves during one time - step by premultiplying by the "state transition - matrix" A. There is optionally (if nonzero) an input - vector u which affects the state linearly, and this - linear effect on the state is represented by - premultiplying by the "input matrix" B. There is also - gaussian process noise w. - z = Hx + v meaning the observation vector z is a linear function - of the state vector, and this linear relationship is - represented by premultiplication by "observation - matrix" H. There is also gaussian measurement - noise v. - where w ~ N(0,Q) meaning w is gaussian noise with covariance Q - v ~ N(0,R) meaning v is gaussian noise with covariance R - - VECTOR VARIABLES: - - s.x = state vector estimate. In the input struct, this is the - "a priori" state estimate (prior to the addition of the - information from the new observation). In the output struct, - this is the "a posteriori" state estimate (after the new - measurement information is included). - s.z = observation vector - s.u = input control vector, optional (defaults to zero). - - MATRIX VARIABLES: - - s.A = state transition matrix (defaults to identity). - s.P = covariance of the state vector estimate. In the input struct, - this is "a priori," and in the output it is "a posteriori." - (required unless autoinitializing as described below). - s.B = input matrix, optional (defaults to zero). - s.Q = process noise covariance (defaults to zero). - s.R = measurement noise covariance (required). - s.H = observation matrix (defaults to identity). - - NORMAL OPERATION: - - (1) define all state definition fields: A,B,H,Q,R - (2) define intial state estimate: x,P - (3) obtain observation and control vectors: z,u - (4) call the filter to obtain updated state estimate: x,P - (5) return to step (3) and repeat - - INITIALIZATION: - - If an initial state estimate is unavailable, it can be obtained - from the first observation as follows, provided that there are the - same number of observable variables as state variables. This "auto- - intitialization" is done automatically if s.x is absent or NaN. - - x = inv(H)*z - P = inv(H)*R*inv(H') - - This is mathematically equivalent to setting the initial state estimate - covariance to infinity. - - Example (Automobile Voltimeter): - ------- - # Define the system as a constant of 12 volts: - >>> V0 = 12 - >>> h = 1 # voltimeter measure the voltage itself - >>> q = 1e-5 # variance of process noise s the car operates - >>> r = 0.1**2 # variance of measurement error - >>> b = 0 # no system input - >>> u = 0 # no system input - >>> filt = Kalman(R=r, A=1, Q=q, H=h, B=b, u=u) - - # Generate random voltages and watch the filter operate. - >>> n = 50 - >>> truth = np.random.randn(n)*np.sqrt(q) + V0 - >>> z = truth + np.random.randn(n)*np.sqrt(r) # measurement - >>> x = np.zeros(n) - - >>> for i, zi in enumerate(z): - ... x[i] = filt(zi) # perform a Kalman filter iteration - - >>> import matplotlib.pyplot as plt - >>> hz = plt.plot(z,'r.', label='observations') - >>> hx = plt.plot(x,'b-', label='Kalman output') # a-posteriori state estimates: - >>> ht = plt.plot(truth,'g-', label='true voltage') - >>> h = plt.legend() - >>> h = plt.title('Automobile Voltimeter Example') - - ''' - - def __init__(self, R, x=None, P=None, A=None, B=0, u=0, Q=None, H=None): - self.R = R - self.x = x - self.P = P - self.u = u - self.A = A - self.B = B - self.Q = Q - self.H = H - self.reset() - - def reset(self): - self._filter = self._filter_first - - def _filter_first(self, z): - - self._filter = self._filter_main - - auto_init = self.x is None - if auto_init: - n = np.size(z) - else: - n = np.size(self.x) - if self.A is None: - self.A = np.eye(n, n) - self.A = np.atleast_2d(self.A) - if self.Q is None: - self.Q = np.zeros((n, n)) - self.Q = np.atleast_2d(self.Q) - if self.H is None: - self.H = np.eye(n, n) - self.H = np.atleast_2d(self.H) -# if np.diff(np.shape(self.H)): -# raise ValueError('Observation matrix must be square and invertible for state autointialization.') - HI = np.linalg.inv(self.H) - if self.P is None: - self.P = np.dot(np.dot(HI, self.R), HI.T) - self.P = np.atleast_2d(self.P) - if auto_init: - #initialize state estimate from first observation - self.x = np.dot(HI, z) - return self.x - else: - return self._filter_main(z) - - - def _filter_main(self, z): - ''' This is the code which implements the discrete Kalman filter: - ''' - A = self.A - H = self.H - P = self.P - - # Prediction for state vector and covariance: - x = np.dot(A, self.x) + np.dot(self.B, self.u) - P = np.dot(np.dot(A, P), A.T) + self.Q - - # Compute Kalman gain factor: - PHT = np.dot(P, H.T) - K = np.dot(PHT, np.linalg.inv(np.dot(H, PHT) + self.R)) - - # Correction based on observation: - self.x = x + np.dot(K, z - np.dot(H, x)) - self.P = P - np.dot(K, np.dot(H, P)) - - # Note that the desired result, which is an improved estimate - # of the system state vector x and its covariance P, was obtained - # in only five lines of code, once the system was defined. (That's - # how simple the discrete Kalman filter is to use.) Later, - # we'll discuss how to deal with nonlinear systems. - - - return self.x - def __call__(self, z): - return self._filter(z) - -def test_kalman(): - V0 = 12 - h = np.atleast_2d(1) # voltimeter measure the voltage itself - q = 1e-9 # variance of process noise as the car operates - r = 0.05 ** 2 # variance of measurement error - b = 0 # no system input - u = 0 # no system input - filt = Kalman(R=r, A=1, Q=q, H=h, B=b, u=u) - - # Generate random voltages and watch the filter operate. - n = 50 - truth = np.random.randn(n) * np.sqrt(q) + V0 - z = truth + np.random.randn(n) * np.sqrt(r) # measurement - x = np.zeros(n) - - for i, zi in enumerate(z): - x[i] = filt(zi) # perform a Kalman filter iteration - - import matplotlib.pyplot as plt - _hz = plt.plot(z, 'r.', label='observations') - _hx = plt.plot(x, 'b-', label='Kalman output') # a-posteriori state estimates: - _ht = plt.plot(truth, 'g-', label='true voltage') - plt.legend() - plt.title('Automobile Voltimeter Example') - plt.show() - - -def test_smooth(): - import matplotlib.pyplot as plt - t = np.linspace(-4, 4, 500) - y = np.exp(-t ** 2) + np.random.normal(0, 0.05, t.shape) - coeff = calc_coeff(num_points=3, degree=2, diff_order=0) - ysg = smooth(y, coeff, pad=True) - - plt.plot(t, y, t, ysg, '--') - plt.show() - -def test_docstrings(): - import doctest - doctest.testmod() -if __name__ == '__main__': - test_docstrings() - #test_kalman() - #test_smooth() - +import numpy as np +#from math import pow +#from numpy import zeros,dot +from numpy import abs, size, convolve, linalg, concatenate # @UnresolvedImport +from scipy.sparse import spdiags +from scipy.sparse.linalg import spsolve, expm +from scipy.signal import medfilt + +__all__ = ['calc_coeff', 'smooth', 'smooth_last', + 'SavitzkyGolay', 'Kalman', 'HodrickPrescott'] + + +def calc_coeff(n, degree, diff_order=0): + """ calculates filter coefficients for symmetric savitzky-golay filter. + see: http://www.nrbook.com/a/bookcpdf/c14-8.pdf + + n means that 2*n+1 values contribute to the smoother. + + degree is degree of fitting polynomial + + diff_order is degree of implicit differentiation. + 0 means that filter results in smoothing of function + 1 means that filter results in smoothing the first + derivative of function. + and so on ... + + """ + order_range = np.arange(degree + 1) + k_range = np.arange(-n, n + 1, dtype=float).reshape(-1, 1) + b = np.mat(k_range ** order_range) + #b = np.mat([[float(k)**i for i in order_range] for k in range(-n,n+1)]) + coeff = linalg.pinv(b).A[diff_order] + return coeff + + +def smooth_last(signal, coeff, k=0): + n = size(coeff - 1) // 2 + y = np.squeeze(signal) + if y.ndim > 1: + coeff.shape = (-1, 1) + first_vals = y[0] - abs(y[n:0:-1] - y[0]) + last_vals = y[-1] + abs(y[-2:-n - 2:-1] - y[-1]) + y = concatenate((first_vals, y, last_vals)) + return (y[-2 * n - 1 - k:-k] * coeff).sum(axis=0) + + +def smooth(signal, coeff, pad=True): + """applies coefficients calculated by calc_coeff() to signal.""" + + n = size(coeff - 1) // 2 + y = np.squeeze(signal) + if n == 0: + return y + if pad: + first_vals = y[0] - abs(y[n:0:-1] - y[0]) + last_vals = y[-1] + abs(y[-2:-n - 2:-1] - y[-1]) + y = concatenate((first_vals, y, last_vals)) + n *= 2 + d = y.ndim + if d > 1: + y1 = y.reshape(y.shape[0], -1) + res = [] + for i in range(y1.shape[1]): + res.append(convolve(y1[:, i], coeff)[n:-n]) + res = np.asarray(res).T + else: + res = convolve(y, coeff)[n:-n] + return res + + +class SavitzkyGolay(object): + r"""Smooth and optionally differentiate data with a Savitzky-Golay filter. + + The Savitzky-Golay filter removes high frequency noise from data. + It has the advantage of preserving the original shape and + features of the signal better than other types of filtering + approaches, such as moving averages techniques. + + Parameters + ---------- + n : int + the size of the smoothing window is 2*n+1. + degree : int + the order of the polynomial used in the filtering. + Must be less than `window_size` - 1, i.e, less than 2*n. + diff_order : int + order of the derivative to compute (default = 0 means only smoothing) + 0 means that filter results in smoothing of function + 1 means that filter results in smoothing the first derivative of the + function and so on ... + + Notes + ----- + The Savitzky-Golay is a type of low-pass filter, particularly + suited for smoothing noisy data. The main idea behind this + approach is to make for each point a least-square fit with a + polynomial of high order over a odd-sized window centered at + the point. + + Examples + -------- + >>> t = np.linspace(-4, 4, 500) + >>> y = np.exp( -t**2 ) + np.random.normal(0, 0.05, t.shape) + >>> ysg = SavitzkyGolay(n=20, degree=2).smooth(y) + >>> import matplotlib.pyplot as plt + >>> h = plt.plot(t, y, label='Noisy signal') + >>> h1 = plt.plot(t, np.exp(-t**2), 'k', lw=1.5, label='Original signal') + >>> h2 = plt.plot(t, ysg, 'r', label='Filtered signal') + >>> h3 = plt.legend() + >>> h4 = plt.title('Savitzky-Golay') + plt.show() + + References + ---------- + .. [1] A. Savitzky, M. J. E. Golay, Smoothing and Differentiation of + Data by Simplified Least Squares Procedures. Analytical + Chemistry, 1964, 36 (8), pp 1627-1639. + .. [2] Numerical Recipes 3rd Edition: The Art of Scientific Computing + W.H. Press, S.A. Teukolsky, W.T. Vetterling, B.P. Flannery + Cambridge University Press ISBN-13: 9780521880688 + """ + + def __init__(self, n, degree=1, diff_order=0): + self.n = n + self.degree = degree + self.diff_order = diff_order + self.calc_coeff() + + def calc_coeff(self): + """ calculates filter coefficients for symmetric savitzky-golay filter. + """ + n = self.n + order_range = np.arange(self.degree + 1) + k_range = np.arange(-n, n + 1, dtype=float).reshape(-1, 1) + b = np.mat(k_range ** order_range) + #b =np.mat([[float(k)**i for i in order_range] for k in range(-n,n+1)]) + self._coeff = linalg.pinv(b).A[self.diff_order] + + def smooth_last(self, signal, k=0): + coeff = self._coeff + n = size(coeff - 1) // 2 + y = np.squeeze(signal) + if n == 0: + return y + if y.ndim > 1: + coeff.shape = (-1, 1) + first_vals = y[0] - abs(y[n:0:-1] - y[0]) + last_vals = y[-1] + abs(y[-2:-n - 2:-1] - y[-1]) + y = concatenate((first_vals, y, last_vals)) + return (y[-2 * n - 1 - k:-k] * coeff).sum(axis=0) + + def __call__(self, signal): + return self.smooth(signal) + + def smooth(self, signal, pad=True): + """ + Returns smoothed signal (or it's n-th derivative). + + Parameters + ---------- + y : array_like, shape (N,) + the values of the time history of the signal. + pad : bool + pad first and last values to lessen the end effects. + + Returns + ------- + ys : ndarray, shape (N) + the smoothed signal (or it's n-th derivative). + """ + coeff = self._coeff + n = size(coeff - 1) // 2 + y = np.squeeze(signal) + if n == 0: + return y + if pad: + first_vals = y[0] - abs(y[n:0:-1] - y[0]) + last_vals = y[-1] + abs(y[-2:-n - 2:-1] - y[-1]) + y = concatenate((first_vals, y, last_vals)) + n *= 2 + d = y.ndim + if d > 1: + y1 = y.reshape(y.shape[0], -1) + res = [] + for i in range(y1.shape[1]): + res.append(convolve(y1[:, i], coeff)[n:-n]) + res = np.asarray(res).T + else: + res = convolve(y, coeff)[n:-n] + return res + + +class HodrickPrescott(object): + + '''Smooth data with a Hodrick-Prescott filter. + + The Hodrick-Prescott filter removes high frequency noise from data. + It has the advantage of preserving the original shape and + features of the signal better than other types of filtering + approaches, such as moving averages techniques. + + Parameter + --------- + w : real scalar + smooting parameter. Larger w means more smoothing. Values usually + in the [100, 20000] interval. As w approach infinity H-P will approach + a line. + + Examples + -------- + >>> t = np.linspace(-4, 4, 500) + >>> y = np.exp( -t**2 ) + np.random.normal(0, 0.05, t.shape) + >>> ysg = HodrickPrescott(w=10000)(y) + >>> import matplotlib.pyplot as plt + >>> h = plt.plot(t, y, label='Noisy signal') + >>> h1 = plt.plot(t, np.exp(-t**2), 'k', lw=1.5, label='Original signal') + >>> h2 = plt.plot(t, ysg, 'r', label='Filtered signal') + >>> h3 = plt.legend() + >>> h4 = plt.title('Hodrick-Prescott') + >>> plt.show() + + References + ---------- + .. [1] E. T. Whittaker, On a new method of graduation. In proceedings of + the Edinburgh Mathematical association., 1923, 78, pp 88-89. + .. [2] R. Hodrick and E. Prescott, Postwar U.S. business cycles: an + empirical investigation, + Journal of money, credit and banking, 1997, 29 (1), pp 1-16. + .. [3] Kim Hyeongwoo, Hodrick-Prescott filter, + 2004, www.auburn.edu/~hzk0001/hpfilter.pdf + ''' + + def __init__(self, w=100): + self.w = w + + def _get_matrix(self, n): + w = self.w + diag_matrix = np.repeat( + np.atleast_2d([w, -4 * w, 6 * w + 1, -4 * w, w]).T, n, axis=1) + A = spdiags(diag_matrix, np.arange(-2, 2 + 1), n, n).tocsr() + A[0, 0] = A[-1, -1] = 1 + w + A[1, 1] = A[-2, -2] = 1 + 5 * w + A[0, 1] = A[1, 0] = A[-2, -1] = A[-1, -2] = -2 * w + return A + + def __call__(self, x): + x = np.atleast_1d(x).flatten() + n = len(x) + if n < 4: + return x.copy() + + A = self._get_matrix(n) + return spsolve(A, x) + + +class Kalman(object): + + ''' + Kalman filter object - updates a system state vector estimate based upon an + observation, using a discrete Kalman filter. + + The Kalman filter is "optimal" under a variety of + circumstances. An excellent paper on Kalman filtering at + the introductory level, without detailing the mathematical + underpinnings, is: + + "An Introduction to the Kalman Filter" + Greg Welch and Gary Bishop, University of North Carolina + http://www.cs.unc.edu/~welch/kalman/kalmanIntro.html + + PURPOSE: + The purpose of each iteration of a Kalman filter is to update + the estimate of the state vector of a system (and the covariance + of that vector) based upon the information in a new observation. + The version of the Kalman filter in this function assumes that + observations occur at fixed discrete time intervals. Also, this + function assumes a linear system, meaning that the time evolution + of the state vector can be calculated by means of a state transition + matrix. + + USAGE: + filt = Kalman(R, x, P, A, B=0, Q, H) + x = filt(z, u=0) + + filt is a "system" object containing various fields used as input + and output. The state estimate "x" and its covariance "P" are + updated by the function. The other fields describe the mechanics + of the system and are left unchanged. A calling routine may change + these other fields as needed if state dynamics are time-dependent; + otherwise, they should be left alone after initial values are set. + The exceptions are the observation vector "z" and the input control + (or forcing function) "u." If there is an input function, then + "u" should be set to some nonzero value by the calling routine. + + System dynamics + --------------- + + The system evolves according to the following difference equations, + where quantities are further defined below: + + x = Ax + Bu + w meaning the state vector x evolves during one time + step by premultiplying by the "state transition + matrix" A. There is optionally (if nonzero) an input + vector u which affects the state linearly, and this + linear effect on the state is represented by + premultiplying by the "input matrix" B. There is also + gaussian process noise w. + z = Hx + v meaning the observation vector z is a linear function + of the state vector, and this linear relationship is + represented by premultiplication by "observation + matrix" H. There is also gaussian measurement + noise v. + where w ~ N(0,Q) meaning w is gaussian noise with covariance Q + v ~ N(0,R) meaning v is gaussian noise with covariance R + + VECTOR VARIABLES: + + s.x = state vector estimate. In the input struct, this is the + "a priori" state estimate (prior to the addition of the + information from the new observation). In the output struct, + this is the "a posteriori" state estimate (after the new + measurement information is included). + z = observation vector + u = input control vector, optional (defaults to zero). + + MATRIX VARIABLES: + + s.A = state transition matrix (defaults to identity). + s.P = covariance of the state vector estimate. In the input struct, + this is "a priori," and in the output it is "a posteriori." + (required unless autoinitializing as described below). + s.B = input matrix, optional (defaults to zero). + s.Q = process noise covariance (defaults to zero). + s.R = measurement noise covariance (required). + s.H = observation matrix (defaults to identity). + + NORMAL OPERATION: + + (1) define all state definition fields: A,B,H,Q,R + (2) define intial state estimate: x,P + (3) obtain observation and control vectors: z,u + (4) call the filter to obtain updated state estimate: x,P + (5) return to step (3) and repeat + + INITIALIZATION: + + If an initial state estimate is unavailable, it can be obtained + from the first observation as follows, provided that there are the + same number of observable variables as state variables. This "auto- + intitialization" is done automatically if s.x is absent or NaN. + + x = inv(H)*z + P = inv(H)*R*inv(H') + + This is mathematically equivalent to setting the initial state estimate + covariance to infinity. + + Example (Automobile Voltimeter): + ------- + # Define the system as a constant of 12 volts: + >>> V0 = 12 + >>> h = 1 # voltimeter measure the voltage itself + >>> q = 1e-5 # variance of process noise s the car operates + >>> r = 0.1**2 # variance of measurement error + >>> b = 0 # no system input + >>> u = 0 # no system input + >>> filt = Kalman(R=r, A=1, Q=q, H=h, B=b) + + # Generate random voltages and watch the filter operate. + >>> n = 50 + >>> truth = np.random.randn(n)*np.sqrt(q) + V0 + >>> z = truth + np.random.randn(n)*np.sqrt(r) # measurement + >>> x = np.zeros(n) + + >>> for i, zi in enumerate(z): + ... x[i] = filt(zi, u) # perform a Kalman filter iteration + + >>> import matplotlib.pyplot as plt + >>> hz = plt.plot(z,'r.', label='observations') + + # a-posteriori state estimates: + >>> hx = plt.plot(x,'b-', label='Kalman output') + >>> ht = plt.plot(truth,'g-', label='true voltage') + >>> h = plt.legend() + >>> h1 = plt.title('Automobile Voltimeter Example') + >>> plt.show() + + ''' + + def __init__(self, R, x=None, P=None, A=None, B=0, Q=None, H=None): + self.R = R # Estimated error in measurements. + self.x = x # Initial state estimate. + self.P = P # Initial covariance estimate. + self.A = A # State transition matrix. + self.B = B # Control matrix. + self.Q = Q # Estimated error in process. + self.H = H # Observation matrix. + self.reset() + + def reset(self): + self._filter = self._filter_first + + def _filter_first(self, z, u): + + self._filter = self._filter_main + + auto_init = self.x is None + if auto_init: + n = np.size(z) + else: + n = np.size(self.x) + if self.A is None: + self.A = np.eye(n) + self.A = np.atleast_2d(self.A) + if self.Q is None: + self.Q = np.zeros((n, n)) + self.Q = np.atleast_2d(self.Q) + if self.H is None: + self.H = np.eye(n) + self.H = np.atleast_2d(self.H) + try: + HI = np.linalg.inv(self.H) + except: + HI = np.eye(n) + if self.P is None: + self.P = np.dot(np.dot(HI, self.R), HI.T) + + self.P = np.atleast_2d(self.P) + if auto_init: + # initialize state estimate from first observation + self.x = np.dot(HI, z) + return self.x + else: + return self._filter_main(z, u) + + def _predict_state(self, x, u): + return np.dot(self.A, x) + np.dot(self.B, u) + + def _predict_covariance(self, P): + A = self.A + return np.dot(np.dot(A, P), A.T) + self.Q + + def _compute_gain(self, P): + """Kalman gain factor.""" + H = self.H + PHT = np.dot(P, H.T) + innovation_covariance = np.dot(H, PHT) + self.R + #return np.linalg.solve(PHT, innovation_covariance) + return np.dot(PHT, np.linalg.inv(innovation_covariance)) + + def _update_state_from_observation(self, x, z, K): + innovation = z - np.dot(self.H, x) + return x + np.dot(K, innovation) + + def _update_covariance(self, P, K): + return P - np.dot(K, np.dot(self.H, P)) + return np.dot(np.eye(len(P)) - K * self.H, P) + + def _filter_main(self, z, u): + ''' This is the code which implements the discrete Kalman filter: + ''' + P = self._predict_covariance(self.P) + x = self._predict_state(self.x, u) + + K = self._compute_gain(P) + + self.P = self._update_covariance(P, K) + self.x = self._update_state_from_observation(x, z, K) + + return self.x + + def __call__(self, z, u=0): + return self._filter(z, u) + + +def test_kalman(): + V0 = 12 + h = np.atleast_2d(1) # voltimeter measure the voltage itself + q = 1e-9 # variance of process noise as the car operates + r = 0.05 ** 2 # variance of measurement error + b = 0 # no system input + u = 0 # no system input + filt = Kalman(R=r, A=1, Q=q, H=h, B=b) + + # Generate random voltages and watch the filter operate. + n = 50 + truth = np.random.randn(n) * np.sqrt(q) + V0 + z = truth + np.random.randn(n) * np.sqrt(r) # measurement + x = np.zeros(n) + + for i, zi in enumerate(z): + x[i] = filt(zi, u) # perform a Kalman filter iteration + + import matplotlib.pyplot as plt + _hz = plt.plot(z, 'r.', label='observations') + # a-posteriori state estimates: + _hx = plt.plot(x, 'b-', label='Kalman output') + _ht = plt.plot(truth, 'g-', label='true voltage') + plt.legend() + plt.title('Automobile Voltimeter Example') + plt.show('hold') + + +def lti_disc(F, L=None, Q=None, dt=1): + ''' + LTI_DISC Discretize LTI ODE with Gaussian Noise + + Syntax: + [A,Q] = lti_disc(F,L,Qc,dt) + + In: + F - NxN Feedback matrix + L - NxL Noise effect matrix (optional, default identity) + Qc - LxL Diagonal Spectral Density (optional, default zeros) + dt - Time Step (optional, default 1) + + Out: + A - Transition matrix + Q - Discrete Process Covariance + + Description: + Discretize LTI ODE with Gaussian Noise. The original + ODE model is in form + + dx/dt = F x + L w, w ~ N(0,Qc) + + Result of discretization is the model + + x[k] = A x[k-1] + q, q ~ N(0,Q) + + Which can be used for integrating the model + exactly over time steps, which are multiples + of dt. + ''' + n = np.shape(F)[0] + if L is None: + L = np.eye(n) + + if Q is None: + Q = np.zeros((n, n)) + # Closed form integration of transition matrix + A = expm(F * dt) + + # Closed form integration of covariance + # by matrix fraction decomposition + + Phi = np.vstack((np.hstack((F, np.dot(np.dot(L, Q), L.T))), + np.hstack((np.zeros((n, n)), -F.T)))) + AB = np.dot(expm(Phi * dt), np.vstack((np.zeros((n, n)), np.eye(n)))) + #Q = AB[:n, :] / AB[n:(2 * n), :] + Q = np.linalg.solve(AB[n:(2 * n), :].T, AB[:n, :].T) + return A, Q + + +def test_kalman_sine(): + '''Kalman Filter demonstration with sine signal.''' + sd = 1. + dt = 0.1 + w = 1 + T = np.arange(0, 30 + dt / 2, dt) + n = len(T) + X = np.sin(w * T) + Y = X + sd * np.random.randn(n) + + ''' Initialize KF to values + x = 0 + dx/dt = 0 + with great uncertainty in derivative + ''' + M = np.zeros((2, 1)) + P = np.diag([0.1, 2]) + R = sd ** 2 + H = np.atleast_2d([1, 0]) + q = 0.1 + F = np.atleast_2d([[0, 1], + [0, 0]]) + A, Q = lti_disc(F, L=None, Q=np.diag([0, q]), dt=dt) + + # Track and animate + m = M.shape[0] + MM = np.zeros((m, n)) + PP = np.zeros((m, m, n)) + '''In this demonstration we estimate a stationary sine signal from noisy + measurements by using the classical Kalman filter.' + ''' + filt = Kalman(R=R, x=M, P=P, A=A, Q=Q, H=H, B=0) + + # Generate random voltages and watch the filter operate. + #n = 50 + #truth = np.random.randn(n) * np.sqrt(q) + V0 + #z = truth + np.random.randn(n) * np.sqrt(r) # measurement + truth = X + z = Y + x = np.zeros((n, m)) + + for i, zi in enumerate(z): + x[i] = filt(zi, u=0).ravel() + + import matplotlib.pyplot as plt + _hz = plt.plot(z, 'r.', label='observations') + # a-posteriori state estimates: + _hx = plt.plot(x[:, 0], 'b-', label='Kalman output') + _ht = plt.plot(truth, 'g-', label='true voltage') + plt.legend() + plt.title('Automobile Voltimeter Example') + plt.show() + +# for k in range(m): +# [M,P] = kf_predict(M,P,A,Q); +# [M,P] = kf_update(M,P,Y(k),H,R); +# +# MM(:,k) = M; +# PP(:,:,k) = P; +# +# % +# % Animate +# % +# if rem(k,10)==1 +# plot(T,X,'b--',... +# T,Y,'ro',... +# T(k),M(1),'k*',... +# T(1:k),MM(1,1:k),'k-'); +# legend('Real signal','Measurements','Latest estimate','Filtered estimate') +# title('Estimating a noisy sine signal with Kalman filter.'); +# drawnow; +# +# pause; +# end +# end +# +# clc; +# disp('In this demonstration we estimate a stationary sine signal from noisy measurements by using the classical Kalman filter.'); +# disp(' '); +# disp('The filtering results are now displayed sequantially for 10 time step at a time.'); +# disp(' '); +# disp('') +# pause; +# % +# % Apply Kalman smoother +# % +# SM = rts_smooth(MM,PP,A,Q); +# plot(T,X,'b--',... +# T,MM(1,:),'k-',... +# T,SM(1,:),'r-'); +# legend('Real signal','Filtered estimate','Smoothed estimate') +# title('Filtered and smoothed estimate of the original signal'); +# +# clc; +# disp('The filtered and smoothed estimates of the signal are now displayed.') +# disp(' '); +# disp('RMS errors:'); +# % +# % Errors +# % +# fprintf('KF = %.3f\nRTS = %.3f\n',... +# sqrt(mean((MM(1,:)-X(1,:)).^2)),... +# sqrt(mean((SM(1,:)-X(1,:)).^2))); + + +class HampelFilter(object): + ''' + Hampel Filter. + + HAMPEL(X,Y,DX,T,varargin) returns the Hampel filtered values of the + elements in Y. It was developed to detect outliers in a time series, + but it can also be used as an alternative to the standard median + filter. + + X,Y are row or column vectors with an equal number of elements. + The elements in Y should be Gaussian distributed. + + Parameters + ---------- + dx : positive scalar (default 3 * median(diff(X)) + which defines the half width of the filter window. Dx should be + dimensionally equivalent to the values in X. + t : positive scalar (default 3) + which defines the threshold value used in the equation + |Y - Y0| > T * S0. + adaptive: real scalar + if greater than 0 it uses an experimental adaptive Hampel filter. + If none it uses a standard Hampel filter + fulloutput: bool + if True also the vectors: outliers, Y0,LB,UB,ADX, which corresponds to + the mask of the replaced values, nominal data, lower and upper bounds + on the Hampel filter and the relative half size of the local window, + respectively. outliers.sum() gives the number of outliers detected. + + Examples + --------- + Hampel filter removal of outliers + >>> import numpy as np + >>> randint = np.random.randint + >>> Y = 5000 + np.random.randn(1000) + >>> outliers = randint(0,1000, size=(10,)) + >>> Y[outliers] = Y[outliers] + randint(1000, size=(10,)) + >>> YY, res = HampelFilter(fulloutput=True)(Y) + >>> YY1, res1 = HampelFilter(dx=1, t=3, adaptive=0.1, fulloutput=True)(Y) + >>> YY2, res2 = HampelFilter(dx=3, t=0, fulloutput=True)(Y) # Y0 = median + + X = np.arange(len(YY)) + plt.plot(X, Y, 'b.') # Original Data + plt.plot(X, YY, 'r') # Hampel Filtered Data + plt.plot(X, res['Y0'], 'b--') # Nominal Data + plt.plot(X, res['LB'], 'r--') # Lower Bounds on Hampel Filter + plt.plot(X, res['UB'], 'r--') # Upper Bounds on Hampel Filter + i = res['outliers'] + plt.plot(X[i], Y[i], 'ks') # Identified Outliers + plt.show('hold') + + References + ---------- + Chapters 1.4.2, 3.2.2 and 4.3.4 in Mining Imperfect Data: Dealing with + Contamination and Incomplete Records by Ronald K. Pearson. + + Acknowledgements + I would like to thank Ronald K. Pearson for the introduction to moving + window filters. Please visit his blog at: + http://exploringdatablog.blogspot.com/2012/01/moving-window-filters-and + -pracma.html + ''' + def __init__(self, dx=None, t=3, adaptive=None, fulloutput=False): + self.dx = dx + self.t = t + self.adaptive = adaptive + self.fulloutput = fulloutput + + def __call__(self, y, x=None): + Y = np.atleast_1d(y).ravel() + if x is None: + x = range(len(Y)) + X = np.atleast_1d(x).ravel() + + dx = self.dx + if dx is None: + dx = 3 * np.median(np.diff(X)) + if not np.isscalar(dx): + raise ValueError('DX must be a scalar.') + elif dx < 0: + raise ValueError('DX must be larger than zero.') + + YY = Y + S0 = np.nan * np.zeros(YY.shape) + Y0 = np.nan * np.zeros(YY.shape) + ADX = dx * np.ones(Y.shape) + + def localwindow(X, Y, DX, i): + mask = (X[i] - DX <= X) & (X <= X[i] + DX) + Y0 = np.median(Y[mask]) + # Calculate Local Scale of Natural Variation + S0 = 1.4826 * np.median(np.abs(Y[mask] - Y0)) + return Y0, S0 + + def smgauss(X, V, DX): + Xj = X + Xk = np.atleast_2d(X).T + Wjk = np.exp(-((Xj - Xk) / (2 * DX)) ** 2) + G = np.dot(Wjk, V) / np.sum(Wjk, axis=0) + return G + + if len(X) > 1: + if self.adaptive is None: + for i in range(len(Y)): + Y0[i], S0[i] = localwindow(X, Y, dx, i) + else: # 'adaptive' + + Y0Tmp = np.nan * np.zeros(YY.shape) + S0Tmp = np.nan * np.zeros(YY.shape) + DXTmp = np.arange(1, len(S0) + 1) * dx + # Integer variation of Window Half Size + + # Calculate Initial Guess of Optimal Parameters Y0, S0, ADX + for i in range(len(Y)): + j = 0 + S0Rel = np.inf + while S0Rel > self.adaptive: + Y0Tmp[j], S0Tmp[j] = localwindow(X, Y, DXTmp[j], i) + if j > 0: + S0Rel = abs((S0Tmp[j - 1] - S0Tmp[j]) / + (S0Tmp[j - 1] + S0Tmp[j]) / 2) + j += 1 + Y0[i] = Y0Tmp[j - 2] + S0[i] = S0Tmp[j - 2] + ADX[i] = DXTmp[j - 2] / dx + + # Gaussian smoothing of relevant parameters + DX = 2 * np.median(np.diff(X)) + ADX = smgauss(X, ADX, DX) + S0 = smgauss(X, S0, DX) + Y0 = smgauss(X, Y0, DX) + + T = self.t + ## Prepare Output + self.UB = Y0 + T * S0 + self.LB = Y0 - T * S0 + outliers = np.abs(Y - Y0) > T * S0 # possible outliers + YY[outliers] = Y0[outliers] + self.outliers = outliers + self.num_outliers = outliers.sum() + self.ADX = ADX + self.Y0 = Y0 + if self.fulloutput: + return YY, dict(outliers=outliers, Y0=Y0, + LB=self.LB, UB=self.UB, ADX=ADX) + return YY + + +def test_hampel(): + import matplotlib.pyplot as plt + randint = np.random.randint + Y = 5000 + np.random.randn(1000) + outliers = randint(0, 1000, size=(10,)) + Y[outliers] = Y[outliers] + randint(1000, size=(10,)) + YY, res = HampelFilter(dx=3, t=3, fulloutput=True)(Y) + YY1, res1 = HampelFilter(dx=1, t=3, adaptive=0.1, fulloutput=True)(Y) + YY2, res2 = HampelFilter(dx=3, t=0, fulloutput=True)(Y) # median + plt.figure(1) + plot_hampel(Y, YY, res) + plt.title('Standard HampelFilter') + plt.figure(2) + plot_hampel(Y, YY1, res1) + plt.title('Adaptive HampelFilter') + plt.figure(3) + plot_hampel(Y, YY2, res2) + plt.title('Median filter') + plt.show('hold') + + +def plot_hampel(Y, YY, res): + import matplotlib.pyplot as plt + X = np.arange(len(YY)) + plt.plot(X, Y, 'b.') # Original Data + plt.plot(X, YY, 'r') # Hampel Filtered Data + plt.plot(X, res['Y0'], 'b--') # Nominal Data + plt.plot(X, res['LB'], 'r--') # Lower Bounds on Hampel Filter + plt.plot(X, res['UB'], 'r--') # Upper Bounds on Hampel Filter + i = res['outliers'] + plt.plot(X[i], Y[i], 'ks') # Identified Outliers + #plt.show('hold') + + +def test_tide_filter(): +# import statsmodels.api as sa + import wafo.spectrum.models as sm + sd = 10 + Sj = sm.Jonswap(Hm0=4.* sd) + S = Sj.tospecdata() + + q = (0.1 * sd) ** 2 # variance of process noise s the car operates + r = (100 * sd) ** 2 # variance of measurement error + b = 0 # no system input + u = 0 # no system input + + from scipy.signal import butter, lfilter, filtfilt, lfilter_zi + freq_tide = 1. / (12 * 60 * 60) + freq_wave = 1. / 10 + freq_filt = freq_wave / 10 + dt = 1. + freq = 1. / dt + fn = (freq / 2) + + P = 10* np.diag([1, 0.01]) + R = r + H = np.atleast_2d([1, 0]) + + F = np.atleast_2d([[0, 1], + [0, 0]]) + A, Q = lti_disc(F, L=None, Q=np.diag([0, q]), dt=dt) + + t = np.arange(0, 60 * 12, 1. / freq) + w = 2 * np.pi * freq # 1 Hz + tide = 100 * np.sin(freq_tide * w * t + 2 * np.pi / 4) + 100 + y = tide + S.sim(len(t), dt=1. / freq)[:, 1].ravel() +# lowess = sa.nonparametric.lowess +# y2 = lowess(y, t, frac=0.5)[:,1] + + filt = Kalman(R=R, x=np.array([[tide[0]], [0]]), P=P, A=A, Q=Q, H=H, B=b) + filt2 = Kalman(R=R, x=np.array([[tide[0]], [0]]), P=P, A=A, Q=Q, H=H, B=b) + #y = tide + 0.5 * np.sin(freq_wave * w * t) + # Butterworth filter + b, a = butter(9, (freq_filt / fn), btype='low') + #y2 = [lowess(y[max(i-60,0):i + 1], t[max(i-60,0):i + 1], frac=.3)[-1,1] for i in range(len(y))] + #y2 = [lfilter(b, a, y[:i + 1])[i] for i in range(len(y))] + #y3 = filtfilt(b, a, y[:16]).tolist() + [filtfilt(b, a, y[:i + 1])[i] for i in range(16, len(y))] + #y0 = medfilt(y, 41) + zi = lfilter_zi(b, a) + #y2 = lfilter(b, a, y)#, zi=y[0]*zi) # standard filter + y3 = filtfilt(b, a, y) # filter with phase shift correction + y4 =[] + y5 = [] + for i, j in enumerate(y): + tmp = filt(j, u=u).ravel() + tmp = filt2(tmp[0], u=u).ravel() +# if i==0: +# print(filt.x) +# print(filt2.x) + y4.append(tmp[0]) + y5.append(tmp[1]) + y0 = medfilt(y4, 41) + print(filt.P) + # plot + import matplotlib.pyplot as plt + plt.plot(t, y, 'r.-', linewidth=2, label='raw data') + #plt.plot(t, y2, 'b.-', linewidth=2, label='lowess @ %g Hz' % freq_filt) + #plt.plot(t, y2, 'b.-', linewidth=2, label='filter @ %g Hz' % freq_filt) + plt.plot(t, y3, 'g.-', linewidth=2, label='filtfilt @ %g Hz' % freq_filt) + plt.plot(t, y4, 'k.-', linewidth=2, label='kalman') + #plt.plot(t, y5, 'k.', linewidth=2, label='kalman2') + plt.plot(t, tide, 'y-', linewidth=2, label='True tide') + plt.legend(frameon=False, fontsize=14) + plt.xlabel("Time [s]") + plt.ylabel("Amplitude") + plt.show('hold') + + +def test_smooth(): + import matplotlib.pyplot as plt + t = np.linspace(-4, 4, 500) + y = np.exp(-t ** 2) + np.random.normal(0, 0.05, t.shape) + coeff = calc_coeff(n=0, degree=0, diff_order=0) + ysg = smooth(y, coeff, pad=True) + + plt.plot(t, y, t, ysg, '--') + plt.show() + + +def test_docstrings(): + import doctest + print('Testing docstrings in %s' % __file__) + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) + +if __name__ == '__main__': + #test_kalman_sine() + test_tide_filter() + #test_docstrings() + #test_hampel() + #test_kalman() + # test_smooth() diff --git a/pywafo/src/wafo/spectrum/__init__.py b/pywafo/src/wafo/spectrum/__init__.py index 307e615..5fed0cd 100644 --- a/pywafo/src/wafo/spectrum/__init__.py +++ b/pywafo/src/wafo/spectrum/__init__.py @@ -5,7 +5,7 @@ Spectrum package in WAFO Toolbox. """ -from core import * +from core import * #SpecData1D, SpecData2D, cltext import models from wafo.wave_theory import dispersion_relation diff --git a/pywafo/src/wafo/spectrum/core.py b/pywafo/src/wafo/spectrum/core.py index 8ef0a1d..bb5e004 100644 --- a/pywafo/src/wafo/spectrum/core.py +++ b/pywafo/src/wafo/spectrum/core.py @@ -1,3652 +1,4337 @@ -from __future__ import division -from wafo.misc import meshgrid, gravity, cart2polar, polar2cart -from wafo.objects import TimeSeries #mat2timeseries, -import warnings - -import numpy as np -from numpy import (pi, inf, zeros, ones, where, nonzero, #@UnresolvedImport - flatnonzero, ceil, sqrt, exp, log, arctan2, log10, #@UnresolvedImport - tanh, cosh, sinh, random, atleast_1d, #@UnresolvedImport - minimum, diff, isnan, any, r_, conj, mod, #@UnresolvedImport - hstack, vstack, interp, ravel, finfo, linspace, #@UnresolvedImport - arange, array, nan, newaxis, sign) #, fliplr, maximum) #@UnresolvedImport -from numpy.fft import fft -from scipy.integrate import simps, trapz -from scipy.special import erf -from scipy.linalg import toeplitz -import scipy.interpolate as interpolate -from wafo.interpolate import stineman_interp - -from wafo.wave_theory.dispersion_relation import w2k #, k2w -from wafo.wafodata import PlotData, now -from wafo.misc import sub_dict_select, nextpow2, discretize, JITImport, findpeaks #, tranproc -from wafo.graphutil import cltext -from wafo.kdetools import qlevels -from wafo import wafodata -try: - from wafo.gaussian import Rind -except ImportError: - Rind = None -try: - from wafo import c_library -except ImportError: - warnings.warn('Compile the c_library.pyd again!') - c_library = None -try: - from wafo import cov2mod -except ImportError: - warnings.warn('Compile the cov2mod.pyd again!') - cov2mod = None - - -#from wafo.transform import TrData -from wafo.transform.models import TrLinear -from wafo.plotbackend import plotbackend - - -# Trick to avoid error due to circular import - -_WAFOCOV = JITImport('wafo.covariance') - - -__all__ = ['SpecData1D', 'SpecData2D', 'plotspec'] - -def _set_seed(iseed): - '''Set seed of random generator''' - if iseed != None: - try: - random.set_state(iseed) - except: - random.seed(iseed) - -def qtf(w, h=inf, g=9.81): - """ - Return Quadratic Transfer Function - - Parameters - ------------ - w : array-like - angular frequencies - h : scalar - water depth - g : scalar - acceleration of gravity - - Returns - ------- - h_s = sum frequency effects - h_d = difference frequency effects - h_dii = diagonal of h_d - """ - w = atleast_1d(w) - num_w = w.size - - k_w = w2k(w, theta=0, h=h, g=g)[0] - - k_1, k_2 = meshgrid(k_w, k_w) - - if h == inf: # go here for faster calculations - h_s = 0.25 * (abs(k_1) + abs(k_2)) - h_d = -0.25 * abs(abs(k_1) - abs(k_2)) - h_dii = zeros(num_w) - return h_s, h_d , h_dii - - [w_1, w_2] = meshgrid(w, w) - - - - w12 = (w_1 * w_2) - w1p2 = (w_1 + w_2) - w1m2 = (w_1 - w_2) - k12 = (k_1 * k_2) - k1p2 = (k_1 + k_2) - k1m2 = abs(k_1 - k_2) - - if 0: # Langley - p_1 = (-2 * w1p2 * (k12 * g ** 2. - w12 ** 2.) + - w_1 * (w_2 ** 4. - g ** 2 * k_2 ** 2) + - w_2 * (w_1 ** 4 - g * 2. * k_1 ** 2)) / (4. * w12) - p_2 = w1p2 ** 2. * cosh((k1p2) * h) - g * (k1p2) * sinh((k1p2) * h) - - h_s = (-p_1 / p_2 * w1p2 * cosh((k1p2) * h) / g - - (k12 * g ** 2 - w12 ** 2.) / (4 * g * w12) + - (w_1 ** 2 + w_2 ** 2) / (4 * g)) - - p_3 = (-2 * w1m2 * (k12 * g ** 2 + w12 ** 2) - - w_1 * (w_2 ** 4 - g ** 2 * k_2 ** 2) + - w_2 * (w_1 ** 4 - g ** 2 * k_1 ** 2)) / (4. * w12) - p_4 = w1m2 ** 2. * cosh(k1m2 * h) - g * (k1m2) * sinh((k1m2) * h) - - - h_d = (-p_3 / p_4 * (w1m2) * cosh((k1m2) * h) / g - - (k12 * g ** 2 + w12 ** 2) / (4 * g * w12) + - (w_1 ** 2. + w_2 ** 2.) / (4. * g)) - - else: # # Marthinsen & Winterstein - tmp1 = 0.5 * g * k12 / w12 - tmp2 = 0.25 / g * (w_1 ** 2. + w_2 ** 2. + w12) - h_s = (tmp1 - tmp2 + 0.25 * g * (w_1 * k_2 ** 2. + w_2 * k_1 ** 2) / - (w12 * (w1p2))) / (1. - g * (k1p2) / (w1p2) ** 2. * - tanh((k1p2) * h)) + tmp2 - 0.5 * tmp1 ## OK - - tmp2 = 0.25 / g * (w_1 ** 2 + w_2 ** 2 - w12) # #OK - h_d = (tmp1 - tmp2 - 0.25 * g * (w_1 * k_2 ** 2 - w_2 * k_1 ** 2) / - (w12 * (w1m2))) / (1. - g * (k1m2) / (w1m2) ** 2. * - tanh((k1m2) * h)) + tmp2 - 0.5 * tmp1 # # OK - - - ##tmp1 = 0.5*g*k_w./(w.*sqrt(g*h)) - ##tmp2 = 0.25*w.^2/g - -# Wave group velocity - c_g = 0.5 * g * (tanh(k_w * h) + k_w * h * (1.0 - tanh(k_w * h) ** 2)) / w - h_dii = (0.5 * (0.5 * g * (k_w / w) ** 2. - 0.5 * w ** 2 / g + - g * k_w / (w * c_g)) - / (1. - g * h / c_g ** 2.) - 0.5 * k_w / sinh(2 * k_w * h))# # OK - h_d.flat[0::num_w + 1] = h_dii - - ##k = find(w_1==w_2) - ##h_d(k) = h_dii - - #% The NaN's occur due to division by zero. => Set the isnans to zero - - h_dii = where(isnan(h_dii), 0, h_dii) - h_d = where(isnan(h_d), 0, h_d) - h_s = where(isnan(h_s), 0, h_s) - - return h_s, h_d , h_dii - -def plotspec(specdata, linetype='b-', flag=1): - pass -# ''' -# PLOTSPEC Plot a spectral density -# -# Parameters -# ---------- -# S : SpecData1D or SpecData2D object -# defining spectral density. -# linetype : string -# defining color and linetype, see plot for possibilities -# flag : scalar integer -# defining the type of plot -# 1D: -# 1 plots the density, S, (default) -# 2 plot 10log10(S) -# 3 plots both the above plots -# 2D: -# Directional spectra: S(w,theta), S(f,theta) -# 1 polar plot S (default) -# 2 plots spectral density and the directional -# spreading, int S(w,theta) dw or int S(f,theta) df -# 3 plots spectral density and the directional -# spreading, int S(w,theta)/S(w) dw or int S(f,theta)/S(f) df -# 4 mesh of S -# 5 mesh of S in polar coordinates -# 6 contour plot of S -# 7 filled contour plot of S -# Wavenumber spectra: S(k1,k2) -# 1 contour plot of S (default) -# 2 filled contour plot of S -# -# Example -# ------- -# >>> import numpy as np -# >>> import wafo.spectrum as ws -# >>> Sj = ws.models.Jonswap(Hm0=3, Tp=7) -# >>> S = Sj.tospecdata() -# >>> ws.plotspec(S,flag=1) -# -# S = demospec('dir'); S2 = mkdspec(jonswap,spreading); -# plotspec(S,2), hold on -# plotspec(S,3,'g') % Same as previous fig. due to frequency independent spreading -# plotspec(S2,2,'r') % Not the same as previous figs. due to frequency dependent spreading -# plotspec(S2,3,'m') -# % transform from angular frequency and radians to frequency and degrees -# Sf = ttspec(S,'f','d'); clf -# plotspec(Sf,2), -# -# See also dat2spec, createspec, simpson -# ''' -# -# # label the contour levels -# txtFlag = 0; -# LegendOn = 1; -# -# -# ftype = specdata.freqtype #options are 'f' and 'w' and 'k' -# data = specdata.data -# if data.ndim == 2: -# freq = specdata.args[1] -# theta = specdata.args[0] -# else: -# freq = specdata.args -# #if isinstance(specdata.args, (list, tuple)): -# -# if ftype == 'w': -# xlbl_txt = 'Frequency [rad/s]'; -# ylbl1_txt = 'S(w) [m^2 s / rad]'; -# ylbl3_txt = 'Directional Spectrum'; -# zlbl_txt = 'S(w,\theta) [m^2 s / rad^2]'; -# funit = ' [rad/s]'; -# #Sunit = ' [m^2 s / rad]'; -# elif ftype == 'f': -# xlbl_txt = 'Frequency [Hz]'; -# ylbl1_txt = 'S(f) [m^2 s]'; -# ylbl3_txt = 'Directional Spectrum'; -# zlbl_txt = 'S(f,\theta) [m^2 s / rad]'; -# funit = ' [Hz]'; -# #Sunit = ' [m^2 s ]'; -# elif ftype == 'k': -# xlbl_txt = 'Wave number [rad/m]'; -# ylbl1_txt = 'S(k) [m^3/ rad]'; -# funit = ' [rad/m]'; -# #Sunit = ' [m^3 / rad]'; -# ylbl4_txt = 'Wave Number Spectrum'; -# -# else: -# raise ValueError('Frequency type unknown') -# -# -# if hasattr(specdata, 'norm') and specdata.norm : -# #Sunit=[]; -# funit = []; -# ylbl1_txt = 'Normalized Spectral density'; -# ylbl3_txt = 'Normalized Directional Spectrum'; -# ylbl4_txt = 'Normalized Wave Number Spectrum'; -# if ftype == 'k': -# xlbl_txt = 'Normalized Wave number'; -# else: -# xlbl_txt = 'Normalized Frequency'; -# -# ylbl2_txt = 'Power spectrum (dB)'; -# -# phi = specdata.phi -# -# spectype = specdata.type.lower() -# stype = spectype[-3::] -# if stype in ('enc', 'req', 'k1d') : #1D plot -# Fn = freq[-1] # Nyquist frequency -# indm = findpeaks(data, n=4) -# maxS = data.max() -## if isfield(S,'CI') && ~isempty(S.CI), -## maxS = maxS*S.CI(2); -## txtCI = [num2str(100*S.p), '% CI']; -## end -# -# Fp = freq[indm]# %peak frequency/wave number -# -# if len(indm) == 1: -# txt = [('fp = %0.2g' % Fp) + funit] -# else: -# txt = [] -# for i, fp in enumerate(Fp.tolist()): -# txt.append(('fp%d = %0.2g' % (i, fp)) + funit) -# -# txt = ''.join(txt) -# if (flag == 3): -# plotbackend.subplot(2, 1, 1) -# if (flag == 1) or (flag == 3):#% Plot in normal scale -# plotbackend.plot(np.vstack([Fp, Fp]), np.vstack([zeros(len(indm)), data.take(indm)]), ':', label=txt) -# plotbackend.plot(freq, data, linetype) -# specdata.labels.labelfig() -## if isfield(S,'CI'), -## plot(freq,S.S*S.CI(1), 'r:' ) -## plot(freq,S.S*S.CI(2), 'r:' ) -# -# a = plotbackend.axis() -# -# a1 = Fn -# if (Fp > 0): -# a1 = max(min(Fn, 10 * max(Fp)), a[1]); -# -# plotbackend.axis([0, a1 , 0, max(1.01 * maxS, a[3])]) -# #plotbackend.title('Spectral density') -# #plotbackend.xlabel(xlbl_txt) -# #plotbackend.ylabel(ylbl1_txt) -# -# -# if (flag == 3): -# plotbackend.subplot(2, 1, 2) -# -# if (flag == 2) or (flag == 3) : # Plot in logaritmic scale -# ind = np.flatnonzero(data > 0) -# -# plotbackend.plot(np.vstack([Fp, Fp]), -# np.vstack((min(10 * log10(data.take(ind) / maxS)).repeat(len(Fp)), -# 10 * log10(data.take(indm) / maxS))), ':',label=txt) -## hold on -## if isfield(S,'CI'), -## plot(freq(ind),10*log10(S.S(ind)*S.CI(1)/maxS), 'r:' ) -## plot(freq(ind),10*log10(S.S(ind)*S.CI(2)/maxS), 'r:' ) -## end -# plotbackend.plot(freq[ind], 10 * log10(data[ind] / maxS), linetype) -# -# a = plotbackend.axis() -# -# a1 = Fn -# if (Fp > 0): -# a1 = max(min(Fn, 10 * max(Fp)), a[1]); -# -# plotbackend.axis([0, a1 , -20, max(1.01 * 10 * log10(1), a[3])]) -# -# specdata.labels.labelfig() -# #plotbackend.title('Spectral density') -# #plotbackend.xlabel(xlbl_txt) -# plotbackend.ylabel(ylbl2_txt) -# -# if LegendOn: -# plotbackend.legend() -# if isfield(S,'CI'), -# legend(txt{:},txtCI,1) -# else -# legend(txt{:},1) -# end -# end -# case {'k2d'} -# if plotflag==1, -# [c, h] = contour(freq,S.k2,S.S,'b'); -# z_level = clevels(c); -# -# -# if txtFlag==1 -# textstart_x=0.05; textstart_y=0.94; -# cltext1(z_level,textstart_x,textstart_y); -# else -# cltext(z_level,0) -# end -# else -# [c,h] = contourf(freq,S.k2,S.S); -# %clabel(c,h), colorbar(c,h) -# fcolorbar(c) % alternative -# end -# rotate(h,[0 0 1],-phi*180/pi) -# -# -# -# xlabel(xlbl_txt) -# ylabel(xlbl_txt) -# title(ylbl4_txt) -# %return -# km=max([-freq(1) freq(end) S.k2(1) -S.k2(end)]); -# axis([-km km -km km]) -# hold on -# plot([0 0],[ -km km],':') -# plot([-km km],[0 0],':') -# axis('square') -# -# -# %cltext(z_level); -# %axis('square') -# if ~ih, hold off,end -# case {'dir'} -# thmin = S.theta(1)-phi;thmax=S.theta(end)-phi; -# if plotflag==1 % polar plot -# if 0, % alternative but then z_level must be chosen beforehand -# h = polar([0 2*pi],[0 freq(end)]); -# delete(h);hold on -# [X,Y]=meshgrid(S.theta,freq); -# [X,Y]=polar2cart(X,Y); -# contour(X,Y,S.S',lintype) -# else -# if (abs(thmax-thmin)<3*pi), % angle given in radians -# theta = S.theta; -# else -# theta = S.theta*pi/180; % convert to radians -# phi = phi*pi/180; -# end -# c = contours(theta,freq,S.S');%,Nlevel); % calculate levels -# if isempty(c) -# c = contours(theta,freq,S.S);%,Nlevel); % calculate levels -# end -# [z_level c] = clevels(c); % find contour levels -# h = polar(c(1,:),c(2,:),lintype); -# rotate(h,[0 0 1],-phi*180/pi) -# end -# title(ylbl3_txt) -# % label the contour levels -# -# if txtFlag==1 -# textstart_x = -0.1; textstart_y=1.00; -# cltext1(z_level,textstart_x,textstart_y); -# else -# cltext(z_level,0) -# end -# -# elseif (plotflag==2) || (plotflag==3), -# %ih = ishold; -# -# subplot(211) -# -# if ih, hold on, end -# -# Sf = spec2spec(S,'freq'); % frequency spectrum -# plotspec(Sf,1,lintype) -# -# subplot(212) -# -# Dtf = S.S; -# [Nt,Nf] = size(S.S); -# Sf = Sf.S(:).'; -# ind = find(Sf); -# -# if plotflag==3, %Directional distribution D(theta,freq)) -# Dtf(:,ind) = Dtf(:,ind)./Sf(ones(Nt,1),ind); -# end -# Dtheta = simpson(freq,Dtf,2); %Directional spreading, D(theta) -# Dtheta = Dtheta/simpson(S.theta,Dtheta); % make sure int D(theta)dtheta = 1 -# [y,ind] = max(Dtheta); -# Wdir = S.theta(ind)-phi; % main wave direction -# txtwdir = ['\theta_p=' num2pistr(Wdir,3)]; % convert to text string -# -# plot([1 1]*S.theta(ind)-phi,[0 Dtheta(ind)],':'), hold on -# if LegendOn -# lh=legend(txtwdir,0); -# end -# plot(S.theta-phi,Dtheta,lintype) -# -# fixthetalabels(thmin,thmax,'x',2) % fix xticklabel and xlabel for theta -# ylabel('D(\theta)') -# title('Spreading function') -# if ~ih, hold off, end -# %legend(lh) % refresh current legend -# elseif plotflag==4 % mesh -# mesh(freq,S.theta-phi,S.S) -# xlabel(xlbl_txt); -# fixthetalabels(thmin,thmax,'y',3) % fix yticklabel and ylabel for theta -# zlabel(zlbl_txt) -# title(ylbl3_txt) -# elseif plotflag==5 % mesh -# %h=polar([0 2*pi],[0 freq(end)]); -# %delete(h);hold on -# [X,Y]=meshgrid(S.theta-phi,freq); -# [X,Y]=polar2cart(X,Y); -# mesh(X,Y,S.S') -# % display the unit circle beneath the surface -# hold on, mesh(X,Y,zeros(size(S.S'))),hold off -# zlabel(zlbl_txt) -# title(ylbl3_txt) -# set(gca,'xticklabel','','yticklabel','') -# lighting phong -# %lighting gouraud -# %light -# elseif (plotflag==6) || (plotflag==7), -# theta = S.theta-phi; -# [c, h] = contour(freq,theta,S.S); %,Nlevel); % calculate levels -# fixthetalabels(thmin,thmax,'y',2) % fix yticklabel and ylabel for theta -# if plotflag==7, -# hold on -# [c,h] = contourf(freq,theta,S.S); %,Nlevel); % calculate levels -# %hold on -# end -# -# title(ylbl3_txt) -# xlabel(xlbl_txt); -# if 0, -# [z_level] = clevels(c); % find contour levels -# % label the contour levels -# if txtFlag==1 -# textstart_x = 0.06; textstart_y=0.94; -# cltext1(z_level,textstart_x,textstart_y) % a local variant of cltext -# else -# cltext(z_level) -# end -# else -# colormap('jet') -# -# if plotflag==7, -# fcolorbar(c) -# else -# %clabel(c,h), -# hcb = colorbar; -# end -# grid on -# end -# else -# error('Unknown plot option') -# end -# otherwise, error('unknown spectral type') -# end -# -# if ~ih, hold off, end -# -# % The following two commands install point-and-click editing of -# % all the text objects (title, xlabel, ylabel) of the current figure: -# -# %set(findall(gcf,'type','text'),'buttondownfcn','edtext') -# %set(gcf,'windowbuttondownfcn','edtext(''hide'')') -# -# return -# - - -# -# function fixthetalabels(thmin,thmax,xy,dim) -# %FIXTHETALABELS pretty prints the ticklabels and x or y labels for theta -# % -# % CALL fixthetalabels(thmin,thmax,xy,dim) -# % -# % thmin, thmax = minimum and maximum value for theta (wave direction) -# % xy = 'x' if theta is plotted on the x-axis -# % 'y' if theta is plotted on the y-axis -# % dim = specifies the dimension of the plot (ie number of axes shown 2 or 3) -# % If abs(thmax-thmin)<3*pi it is assumed that theta is given in radians -# % otherwise degrees -# -# ind = [('x' == xy) ('y' == xy) ]; -# yx = 'yx'; -# yx = yx(ind); -# if nargin<4||isempty(dim), -# dim=2; -# end -# %drawnow -# %pause -# -# if abs(thmax-thmin)<3*pi, %Radians given. Want xticks given as fractions of pi -# %Trick to update the axis -# if xy=='x' -# if dim<3, -# axis([thmin,thmax 0 inf ]) -# else -# axis([thmin,thmax 0 inf 0 inf]) -# end -# else -# if dim<3, -# axis([0 inf thmin,thmax ]) -# else -# axis([0 inf thmin,thmax 0 inf]) -# end -# end -# -# set(gca,[xy 'tick'],pi*(thmin/pi:0.25:thmax/pi)); -# set(gca,[xy 'ticklabel'],[]); -# x = get(gca,[xy 'tick']); -# y = get(gca,[yx 'tick']); -# y1 = y(1); -# dy = y(2)-y1; -# yN = y(end)+dy; -# ylim = [y1 yN]; -# dy1 = diff(ylim)/40; -# %ylim=get(gca,[yx 'lim'])%,ylim=ylim(2); -# -# if xy=='x' -# for j=1:length(x) -# xtxt = num2pistr(x(j)); -# figtext(x(j),y1-dy1,xtxt,'data','data','center','top'); -# end -# % ax = [thmin thmax 0 inf]; -# ax = [thmin thmax ylim]; -# if dim<3, -# figtext(mean(x),y1-7*dy1,'Wave directions (rad)','data','data','center','top') -# else -# ax = [ax 0 inf]; -# xlabel('Wave directions (rad)') -# end -# else -# %ax = [0 inf thmin thmax]; -# ax = [ylim thmin thmax]; -# -# if dim<3, -# for j=1:length(x) -# xtxt = num2pistr(x(j)); -# figtext(y1-dy1/2,x(j),xtxt,'data','data','right'); -# end -# set(gca,'DefaultTextRotation',90) -# %ylabel('Wave directions (rad)') -# figtext(y1-3*dy1,mean(x),'Wave directions (rad)','data','data','center','bottom') -# set(gca,'DefaultTextRotation',0) -# else -# for j=1:length(x) -# xtxt = num2pistr(x(j)); -# figtext(y1-3*dy1,x(j),xtxt,'data','data','right'); -# end -# ax = [ax 0 inf]; -# ylabel('Wave directions (rad)') -# end -# end -# %xtxt = num2pistr(x(j)); -# %for j=2:length(x) -# % xtxt = strvcat(xtxt,num2pistr(x(j))); -# %end -# %set(gca,[xy 'ticklabel'],xtxt) -# else % Degrees given -# set(gca,[xy 'tick'],thmin:45:thmax) -# if xy=='x' -# ax=[thmin thmax 0 inf]; -# if dim>=3, ax=[ax 0 inf]; end -# xlabel('Wave directions (deg)') -# else -# ax=[0 inf thmin thmax ]; -# if dim>=3, ax=[ax 0 inf]; end -# ylabel('Wave directions (deg)') -# end -# end -# axis(ax) -# return -# - - - - -class SpecData1D(PlotData): - """ - Container class for 1D spectrum data objects in WAFO - - Member variables - ---------------- - data : array-like - One sided Spectrum values, size nf - args : array-like - freguency/wave-number-lag values of freqtype, size nf - type : String - spectrum type, one of 'freq', 'k1d', 'enc' (default 'freq') - freqtype : letter - frequency type, one of: 'f', 'w' or 'k' (default 'w') - tr : Transformation function (default (none)). - h : real scalar - Water depth (default inf). - v : real scalar - Ship speed, if type = 'enc'. - norm : bool - Normalization flag, True if S is normalized, False if not - date : string - Date and time of creation or change. - - Examples - -------- - >>> import numpy as np - >>> import wafo.spectrum.models as sm - >>> Sj = sm.Jonswap(Hm0=3) - >>> w = np.linspace(0,4,256) - >>> S1 = Sj.tospecdata(w) #Make spectrum object from numerical values - >>> S = sm.SpecData1D(Sj(w),w) # Alternatively do it manually - - See also - -------- - PlotData - CovData - """ - - def __init__(self, *args, **kwds): - self.name_ = kwds.pop('name', 'WAFO Spectrum Object') - self.type = kwds.pop('type', 'freq') - self.freqtype = kwds.pop('freqtype', 'w') - self.angletype = '' - self.h = kwds.pop('h', inf) - self.tr = kwds.pop('tr', None) #TrLinear() - self.phi = kwds.pop('phi', 0.0) - self.v = kwds.pop('v', 0.0) - self.norm = kwds.pop('norm', False) - super(SpecData1D, self).__init__(*args, **kwds) - - self.setlabels() - - def tocov_matrix(self, nr=0, nt=None, dt=None): - ''' - Computes covariance function and its derivatives, alternative version - - Parameters - ---------- - nr : scalar integer - number of derivatives in output, nr<=4 (default 0) - nt : scalar integer - number in time grid, i.e., number of time-lags. - (default rate*(n_f-1)) where rate = round(1/(2*f(end)*dt)) or - rate = round(pi/(w(n_f)*dt)) depending on S. - dt : real scalar - time spacing for acfmat - - Returns - ------- - acfmat : [R0, R1,...Rnr], shape Nt+1 x Nr+1 - matrix with autocovariance and its derivatives, i.e., Ri (i=1:nr) - are column vectors with the 1'st to nr'th derivatives of R0. - - NB! This routine requires that the spectrum grid is equidistant - starting from zero frequency. - - Example - ------- - >>> import wafo.spectrum.models as sm - >>> Sj = sm.Jonswap() - >>> S = Sj.tospecdata() - >>> acfmat = S.tocov_matrix(nr=3, nt=256, dt=0.1) - >>> np.round(acfmat[:2,:],3) - array([[ 3.061, 0. , -1.677, 0. ], - [ 3.052, -0.167, -1.668, 0.187]]) - - See also - -------- - cov, - resample, - objects - ''' - - ftype = self.freqtype # %options are 'f' and 'w' and 'k' - freq = self.args - n_f = len(freq) - dt_old = self.sampling_period() - if dt is None: - dt = dt_old - rate = 1 - else: - rate = max(round(dt_old * 1. / dt), 1.) - - - if nt is None: - nt = rate * (n_f - 1) - else: #%check if Nt is ok - nt = minimum(nt, rate * (n_f - 1)) - - - checkdt = 1.2 * min(diff(freq)) / 2. / pi - if ftype in 'k': - lagtype = 'x' - else: - lagtype = 't' - if ftype in 'f': - checkdt = checkdt * 2 * pi - msg1 = 'Step dt = %g in computation of the density is too small.' % dt - msg2 = 'Step dt = %g is small, and may cause numerical inaccuracies.' % dt - - if (checkdt < 2. ** -16 / dt): - print(msg1) - print('The computed covariance (by FFT(2^K)) may differ from the') - print('theoretical. Solution:') - raise ValueError('use larger dt or sparser grid for spectrum.') - - - # Calculating covariances - #~~~~~~~~~~~~~~~~~~~~~~~~ - spec = self.copy() - spec.resample(dt) - - acf = spec.tocovdata(nr, nt, rate=1) - acfmat = zeros((nt + 1, nr + 1), dtype=float) - acfmat[:, 0] = acf.data[0:nt + 1] - fieldname = 'R' + lagtype * nr - for i in range(1, nr + 1): - fname = fieldname[:i + 1] - r_i = getattr(acf, fname) - acfmat[:, i] = r_i[0:nt + 1] - - eps0 = 0.0001 - if nt + 1 >= 5: - cc2 = acfmat[0, 0] - acfmat[4, 0] * (acfmat[4, 0] / acfmat[0, 0]) - if (cc2 < eps0): - warnings.warn(msg1) - cc1 = acfmat[0, 0] - acfmat[1, 0] * (acfmat[1, 0] / acfmat[0, 0]) - if (cc1 < eps0): - warnings.warn(msg2) - return acfmat - - def tocovdata(self, nr=0, nt=None, rate=None): - ''' - Computes covariance function and its derivatives - - Parameters - ---------- - nr : number of derivatives in output, nr<=4 (default = 0). - nt : number in time grid, i.e., number of time-lags - (default rate*(length(S.data)-1)). - rate = 1,2,4,8...2**r, interpolation rate for R - (default = 1, no interpolation) - - Returns - ------- - R : CovData1D - auto covariance function - - The input 'rate' gives together with the spectrum - the time-grid-spacing: dt=pi/(S.w[-1]*rate), S.w[-1] is the Nyquist freq. - This results in the time-grid: 0:dt:Nt*dt. - - What output is achieved with different S and choices of Nt,Nx and Ny: - 1) S.type='freq' or 'dir', Nt set, Nx,Ny not set: then result R(time) (one-dim) - 2) S.type='k1d' or 'k2d', Nt set, Nx,Ny not set: then result R(x) (one-dim) - 3) Any type, Nt and Nx set =>R(x,time); Nt and Ny set =>R(y,time) - 4) Any type, Nt, Nx and Ny set => R(x,y,time) - 5) Any type, Nt not set, Nx and/or Ny set => Nt set to default, goto 3) or 4) - - NB! This routine requires that the spectrum grid is equidistant - starting from zero frequency. - NB! If you are using a model spectrum, spec, with sharp edges - to calculate covariances then you should probably round off the sharp - edges like this: - - Example: - >>> import wafo.spectrum.models as sm - >>> Sj = sm.Jonswap() - >>> S = Sj.tospecdata() - >>> S.data[0:40] = 0.0 - >>> S.data[100:-1] = 0.0 - >>> Nt = len(S.data)-1 - >>> acf = S.tocovdata(nr=0, nt=Nt) - >>> S1 = acf.tospecdata() - >>> h = S.plot('r') - >>> h1 = S1.plot('b:') - - R = spec2cov(spec,0,Nt) - win = parzen(2*Nt+1) - R.data = R.data.*win(Nt+1:end) - S1 = cov2spec(acf) - R2 = spec2cov(S1) - figure(1) - plotspec(S),hold on, plotspec(S1,'r') - figure(2) - covplot(R), hold on, covplot(R2,[],[],'r') - figure(3) - semilogy(abs(R2.data-R.data)), hold on, - semilogy(abs(S1.data-S.data)+1e-7,'r') - - See also - -------- - cov2spec - ''' - - freq = self.args - n_f = len(freq) - - if freq[0] > 0: - txt = '''Spectrum does not start at zero frequency/wave number. - Correct it with resample, for example.''' - raise ValueError(txt) - d_w = abs(diff(freq, n=2, axis=0)) - if any(d_w > 1.0e-8): - txt = '''Not equidistant frequencies/wave numbers in spectrum. - Correct it with resample, for example.''' - raise ValueError(txt) - - - if rate is None: - rate = 1 # %interpolation rate - elif rate > 16: - rate = 16 - else: # make sure rate is a power of 2 - rate = 2 ** nextpow2(rate) - - if nt is None: - nt = rate * (n_f - 1) - else: #check if Nt is ok - nt = minimum(nt, rate * (n_f - 1)) - - spec = self.copy() - - if self.freqtype in 'k': - lagtype = 'x' - else: - lagtype = 't' - - d_t = spec.sampling_period() - #normalize spec so that sum(specn)/(n_f-1)=acf(0)=var(X) - specn = spec.data * freq[-1] - if spec.freqtype in 'f': - w = freq * 2 * pi - else: - w = freq - - nfft = rate * 2 ** nextpow2(2 * n_f - 2) - - # periodogram - rper = r_[specn, zeros(nfft - (2 * n_f) + 2), conj(specn[n_f - 2:0:-1])] - time = r_[0:nt + 1] * d_t * (2 * n_f - 2) / nfft - - r = fft(rper, nfft).real / (2 * n_f - 2) - acf = _WAFOCOV.CovData1D(r[0:nt + 1], time, lagtype=lagtype) - acf.tr = spec.tr - acf.h = spec.h - acf.norm = spec.norm - - if nr > 0: - w = r_[w , zeros(nfft - 2 * n_f + 2) , -w[n_f - 2:0:-1] ] - fieldname = 'R' + lagtype[0] * nr - for i in range(1, nr + 1): - rper = -1j * w * rper - d_acf = fft(rper, nfft).real / (2 * n_f - 2) - setattr(acf, fieldname[0:i + 1], d_acf[0:nt + 1]) - return acf - - def to_linspec(self, ns=None, dt=None, cases=20, iseed=None, - fn_limit=sqrt(2), gravity=9.81): - ''' - Split the linear and non-linear component from the Spectrum - according to 2nd order wave theory - - Returns - ------- - SL, SN : SpecData1D objects - with linear and non-linear components only, respectively. - - Parameters - ---------- - ns : scalar integer - giving ns load points. (default length(S)-1=n-1). - If np>n-1 it is assummed that S(k)=0 for all k>n-1 - cases : scalar integer - number of cases (default=20) - dt : real scalar - step in grid (default dt is defined by the Nyquist freq) - iseed : scalar integer - starting seed number for the random number generator - (default none is set) - fnLimit : real scalar - normalized upper frequency limit of spectrum for 2'nd order - components. The frequency is normalized with - sqrt(gravity*tanh(kbar*water_depth)/Amax)/(2*pi) - (default sqrt(2), i.e., Convergence criterion). - Generally this should be the same as used in the final - non-linear simulation (see example below). - - SPEC2LINSPEC separates the linear and non-linear component of the - spectrum according to 2nd order wave theory. This is useful when - simulating non-linear waves because: - If the spectrum does not decay rapidly enough towards zero, the - contribution from the 2nd order wave components at the upper tail can - be very large and unphysical. Another option to ensure convergence of - the perturbation series in the simulation, is to truncate the upper tail - of the spectrum at FNLIMIT in the calculation of the 2nd order wave - components, i.e., in the calculation of sum and difference frequency effects. - - Example: - -------- - np = 10000; - iseed = 1; - pflag = 2; - S = jonswap(10); - fnLimit = inf; - [SL,SN] = spec2linspec(S,np,[],[],fnLimit); - x0 = spec2nlsdat(SL,8*np,[],iseed,[],fnLimit); - x1 = spec2nlsdat(S,8*np,[],iseed,[],fnLimit); - x2 = spec2nlsdat(S,8*np,[],iseed,[],sqrt(2)); - Se0 = dat2spec(x0); - Se1 = dat2spec(x1); - Se2 = dat2spec(x2); - clf - plotspec(SL,'r',pflag), % Linear components - hold on - plotspec(S,'b',pflag) % target spectrum for simulated data - plotspec(Se0,'m',pflag), % approx. same as S - plotspec(Se1,'g',pflag) % unphysical spectrum - plotspec(Se2,'k',pflag) % approx. same as S - axis([0 10 -80 0]) - hold off - - See also - -------- - spec2nlsdat - - References - ---------- - P. A. Brodtkorb (2004), - The probability of Occurrence of dangerous Wave Situations at Sea. - Dr.Ing thesis, Norwegian University of Science and Technolgy, NTNU, - Trondheim, Norway. - - Nestegaard, A and Stokka T (1995) - A Third Order Random Wave model. - In proc.ISOPE conf., Vol III, pp 136-142. - - R. S Langley (1987) - A statistical analysis of non-linear random waves. - Ocean Engng, Vol 14, pp 389-407 - - Marthinsen, T. and Winterstein, S.R (1992) - 'On the skewness of random surface waves' - In proc. ISOPE Conf., San Francisco, 14-19 june. - ''' - - # by pab 13.08.2002 - - # TODO % Replace inputs with options structure - # TODO % Can be improved further. - - method = 'apstochastic' - trace = 1 #% trace the convergence - max_sim = 30 - tolerance = 5e-4 - - L = 200 #%maximum lag size of the window function used in - #%spectral estimate - #ftype = self.freqtype #options are 'f' and 'w' and 'k' -# switch ftype -# case 'f', -# ftype = 'w'; -# S = ttspec(S,ftype); -# end - Hm0 = self.characteristic('Hm0') - Tm02 = self.characteristic('Tm02') - - - if not iseed is None: - _set_seed(iseed) #% set the the seed - - n = len(self.data) - if ns is None: - ns = max(n - 1, 5000) - if dt is None: - S = self.interp(dt) # interpolate spectrum - else: - S = self.copy() - - ns = ns + mod(ns, 2) # make sure np is even - - water_depth = abs(self.h); - kbar = w2k(2 * pi / Tm02, 0, water_depth)[0] - - # Expected maximum amplitude for 1000 waves seastate - num_waves = 10000 - Amax = sqrt(2 * log(num_waves)) * Hm0 / 4 - - fLimitLo = sqrt(gravity * tanh(kbar * water_depth) * Amax / water_depth ** 3); - - - freq = S.args - eps = finfo(float).eps - freq[-1] = freq[-1] - sqrt(eps) - Hw2 = 0 - - SL = S - - indZero = nonzero(freq < fLimitLo)[0] - if len(indZero): - SL.data[indZero] = 0 - - maxS = max(S.data); - #Fs = 2*freq(end)+eps; % sampling frequency - - for ix in xrange(max_sim): - #[x2, x1] = spec2nlsdat(SL, [np, cases], [], iseed, method, fnLimit) - [x2, x1] = self.sim_nl(ns=np, cases=cases, dt=None, iseed=iseed, method=method, - fnlimit=fn_limit) - #%x2(:,2:end) = x2(:,2:end) -x1(:,2:end); - S2 = dat2spec(x2, L) - S1 = dat2spec(x1, L) - #%[tf21,fi] = tfe(x2(:,2),x1(:,2),1024,Fs,[],512); - #%Hw11 = interp1q(fi,tf21.*conj(tf21),freq); - if True: - Hw1 = exp(interp1q(S2.args, log(abs(S1.data / S2.data)), freq)) - else: - # Geometric mean - Hw1 = exp((interp1q(S2.args, log(abs(S1.data / S2.data)), freq) + log(Hw2)) / 2) - #end - #Hw1 = (interp1q( S2.w,abs(S1.S./S2.S),freq)+Hw2)/2; - #plot(freq, abs(Hw11-Hw1),'g') - #title('diff') - #pause - #clf - - #d1 = interp1q( S2.w,S2.S,freq);; - - SL.data = (Hw1 * S.data) - - if len(indZero): - SL.data[indZero] = 0 - #end - k = nonzero(SL.data < 0)[0] - if len(k): # Make sure that the current guess is larger than zero - #%k - #Hw1(k) - Hw1[k] = min(S1.data[k] * 0.9, S.data[k]) - SL.data[k] = max(Hw1[k] * S.data[k], eps) - #end - Hw12 = Hw1 - Hw2 - maxHw12 = max(abs(Hw12)) - if trace == 1: - plotbackend.figure(1), - plotbackend.semilogy(freq, Hw1, 'r') - plotbackend.title('Hw') - plotbackend.figure(2), - plotbackend.semilogy(freq, abs(Hw12), 'r') - plotbackend.title('Hw-HwOld') - - #pause(3) - plotbackend.figure(1), - plotbackend.semilogy(freq, Hw1, 'b') - plotbackend.title('Hw') - plotbackend.figure(2), - plotbackend.semilogy(freq, abs(Hw12), 'b') - plotbackend.title('Hw-HwOld') - #figtile - #end - - print('Iteration : %d, Hw12 : %g Hw12/maxS : %g' % (ix, maxHw12, (maxHw12 / maxS))) - if (maxHw12 < maxS * tolerance) and (Hw1[-1] < Hw2[-1]) : - break - #end - Hw2 = Hw1 - #end - - #%Hw1(end) - #%maxS*1e-3 - #%if Hw1(end)*S.>maxS*1e-3, - #% warning('The Nyquist frequency of the spectrum may be too low') - #%end - - SL.date = now() #datestr(now) - #if nargout>1 - SN = SL.copy() - SN.data = S.data - SL.data - SN.note = SN.note + ' non-linear component (spec2linspec)' - #end - SL.note = SL.note + ' linear component (spec2linspec)' - - return SL, SN - - def to_mm_pdf(self, paramt=None, paramu=None, utc=None, nit=2, EPS=5e-5, - EPSS=1e-6, C=4.5, EPS0=1e-5, IAC=1, ISQ=0, verbose=False): - ''' - nit = order of numerical integration: 0,1,2,3,4,5. - paramu = parameter vector defining discretization of min/max values. - t = grid of time points between maximum and minimum (to - integrate out). interval between maximum and the following - minimum, - The variable ISQ marks which type of conditioning will be used ISQ=0 - means random time where the probability is minimum, ISQ=1 is the time - where the variance of the residual process is minimal(ISQ=1 is faster). - - NIT, IAC are described in CROSSPACK paper, EPS0 is the accuracy constant - used in choosing the number of nodes in numerical integrations - (XX1, H1 vectors). The nodes and weights and other parameters are - read in the subroutine INITINTEG from files Z.DAT, H.DAT and ACCUR.DAT. - - - NIT=0, IAC=1 then one uses RIND0 - subroutine, all other cases - goes through RIND1, ...,RIND5. NIT=0, here means explicite formula - approximation for XIND=E[Y^+1{ HH>> import numpy as np - >>> import wafo.spectrum.models as sm - >>> Sj = sm.Jonswap(Hm0=3) - >>> w = np.linspace(0,4,256) - >>> S1 = Sj.tospecdata(w) #Make spectrum object from numerical values - >>> S = sm.SpecData1D(Sj(w),w) # Alternatively do it manually - mm = S.to_mm_pdf() - mm.plot() - mm.plot(plotflag=1) - ''' - - S = self.copy() - S.normalize() - m, unused_mtxt = self.moment(nr=4, even=True) - A = sqrt(m[0] / m[1]) - - if paramt is None: - distanceBetweenExtremes = 5 * pi * sqrt(m[1] / m[2]) #(2.5 * mean distance between extremes) - paramt = [0, distanceBetweenExtremes, 43] - - if paramu is None: - paramu = [-5 * sqrt(m[0]), 5 * sqrt(m[0]), 41] - - if self.tr is None: - g = TrLinear(var=m[0]) - else: - g = self.tr - - if utc is None: - utc = g.gauss2dat(0) # most frequent crossed level - - # transform reference level into Gaussian level - u = g.dat2gauss(utc) - if verbose: - print('The level u for Gaussian process = %g' % u) - - - unused_t0, tn, Nt = paramt - t = linspace(0, tn / A, Nt) # normalized times - - #Transform amplitudes to Gaussian levels: - h = linspace(*paramu); - dt = t[1] - t[0] - nr = 4 - R = S.tocov_matrix(nr, Nt - 1, dt) - - #ulev = linspace(*paramu) - #vlev = linspace(*paramu) - - trdata = g.trdata() - Tg = trdata.args - Xg = trdata.data - - cov2mod.initinteg(EPS, EPSS, EPS0, C, IAC, ISQ) - uvdens = cov2mod.cov2mmpdfreg(t, R, h, h, Tg, Xg, nit) - uvdens = np.rot90(uvdens, -2) - - dh = h[1] - h[0] - uvdens *= dh * dh - - mmpdf = PlotData(uvdens, args=(h, h), xlab='max [m]', ylab='min [m]', - title='Joint density of maximum and minimum') - try: - pl = [10, 30, 50, 70, 90, 95, 99, 99.9] - mmpdf.cl = qlevels(uvdens, pl, h, h) - mmpdf.pl = pl - except: - pass - return mmpdf - - - def to_t_pdf(self, u=None, kind='Tc', paramt=None, **options): - ''' - Density of crest/trough- period or length, version 2. - - Parameters - ---------- - u : real scalar - reference level (default the most frequently crossed level). - kind : string, 'Tc', Tt', 'Lc' or 'Lt' - 'Tc', gives half wave period, Tc (default). - 'Tt', gives half wave period, Tt - 'Lc' and 'Lt' ditto for wave length. - paramt : [t0, tn, nt] - where t0, tn and nt is the first value, last value and the number - of points, respectively, for which the density will be computed. - paramt= [5, 5, 51] implies that the density is computed only for - T=5 and using 51 equidistant points in the interval [0,5]. - options : optional parameters - controlling the performance of the integration. See Rind for details. - - Notes - ----- - SPEC2TPDF2 calculates pdf of halfperiods Tc, Tt, Lc or Lt - in a stationary Gaussian transform process X(t), - where Y(t) = g(X(t)) (Y zero-mean Gaussian with spectrum given in S). - The transformation, g, can be estimated using LC2TR, - DAT2TR, HERMITETR or OCHITR. - - Example - ------- - The density of Tc is computed by: - >>> import pylab as plb - >>> from wafo.spectrum import models as sm - >>> w = np.linspace(0,3,100) - >>> Sj = sm.Jonswap() - >>> S = Sj.tospecdata() - >>> f = S.to_t_pdf(pdef='Tc', paramt=(0, 10, 51), speed=7) - >>> h = f.plot() - - estimated error bounds - >>> h2 = plb.plot(f.args, f.data+f.err, 'r', f.args, f.data-f.err, 'r') - - >>> plb.close('all') - - See also - -------- - Rind, spec2cov2, specnorm, dat2tr, dat2gaus, - definitions.wave_periods, - definitions.waves - - ''' - - opts = dict(speed=9) - opts.update(options) - if kind[0] in ('l', 'L'): - if self.type != 'k1d': - raise ValueError('Must be spectrum of type: k1d') - elif kind[0] in ('t', 'T'): - if self.type != 'freq': - raise ValueError('Must be spectrum of type: freq') - else: - raise ValueError('pdef must be Tc,Tt or Lc, Lt') -# if strncmpi('l',kind,1) -# spec=spec2spec(spec,'k1d') -# elseif strncmpi('t',kind,1) -# spec=spec2spec(spec,'freq') -# else -# error('Unknown kind') -# end - kind2defnr = dict(tc=1, lc=1, tt= -1, lt= -1) - defnr = kind2defnr[kind.lower()] - - S = self.copy() - S.normalize() - m, unused_mtxt = self.moment(nr=2, even=True) - A = sqrt(m[0] / m[1]) - - - if self.tr is None: - g = TrLinear(var=m[0]) - else: - g = self.tr - - - if u is None: - u = g.gauss2dat(0) #% most frequently crossed level - - # transform reference level into Gaussian level - un = g.dat2gauss(u) - - #disp(['The level u for Gaussian process = ', num2str(u)]) - - if paramt is None: - #% z2 = u^2/2 - z = -sign(defnr) * un / sqrt(2) - expectedMaxPeriod = 2 * ceil(2 * pi * A * exp(z) * (0.5 + erf(z) / 2)) - paramt = [0, expectedMaxPeriod, 51] - - t0 = paramt[0] - tn = paramt[1] - Ntime = paramt[2] - t = linspace(0, tn / A, Ntime) #normalized times - Nstart = max(round(t0 / tn * (Ntime - 1)), 1) #% index to starting point to evaluate - - dt = t[1] - t[0] - nr = 2 - R = S.tocov_matrix(nr, Ntime - 1, dt) - #R = spec2cov2(S,nr,Ntime-1,dt) - - - xc = vstack((un, un)) - indI = -ones(4, dtype=int) - Nd = 2 - Nc = 2 - XdInf = 100.e0 * sqrt(-R[0, 2]) - XtInf = 100.e0 * sqrt(R[0, 0]) - - B_up = hstack([un + XtInf, XdInf, 0]) - B_lo = hstack([un, 0, -XdInf]) - #%INFIN = [1 1 0] - #BIG = zeros((Ntime+2,Ntime+2)) - ex = zeros(Ntime + 2, dtype=float) - #%CC = 2*pi*sqrt(-R(1,1)/R(1,3))*exp(un^2/(2*R(1,1))) - #% XcScale = log(CC) - opts['xcscale'] = log(2 * pi * sqrt(-R[0, 0] / R[0, 2])) + (un ** 2 / (2 * R[0, 0])) - - f = zeros(Ntime, dtype=float) - err = zeros(Ntime, dtype=float) - - rind = Rind(**opts) - #h11 = fwaitbar(0,[],sprintf('Please wait ...(start at: %s)',datestr(now))) - for pt in xrange(Nstart, Ntime): - Nt = pt - Nd + 1 - Ntd = Nt + Nd - Ntdc = Ntd + Nc - indI[1] = Nt - 1 - indI[2] = Nt - indI[3] = Ntd - 1 - - #% positive wave period - BIG = self._covinput_t_pdf(pt, R) - - tmp = rind(BIG, ex[:Ntdc], B_lo, B_up, indI, xc, Nt) - f[pt], err[pt] = tmp[:2] - #fwaitbar(pt/Ntime,h11,sprintf('%s Ready: %d of %d',datestr(now),pt,Ntime)) - #end - #close(h11) - - - titledict = dict(tc='Density of Tc', tt='Density of Tt', lc='Density of Lc', lt='Density of Lt') - Htxt = titledict.get(kind.lower()) - - if kind[0].lower() == 'l': - xtxt = 'wave length [m]' - else: - xtxt = 'period [s]' - - Htxt = '%s_{v =%2.5g}' % (Htxt, u) - pdf = PlotData(f / A, t * A, title=Htxt, xlab=xtxt) - pdf.err = err / A - pdf.u = u - pdf.options = opts - return pdf - - - def _covinput_t_pdf(self, pt, R): - """ - Return covariance matrix for Tc or Tt period problems - - Parameters - ---------- - pt : scalar integer - time - R : array-like, shape Ntime x 3 - [R0,R1,R2] column vectors with autocovariance and its derivatives, - i.e., Ri (i=1:2) are vectors with the 1'st and 2'nd derivatives of R0. - - The order of the variables in the covariance matrix are organized as follows: - For pt>1: - ||X(t2)..X(ts),..X(tn-1)|| X'(t1) X'(tn)|| X(t1) X(tn) || - = [Xt Xd Xc] - - where - - Xt = time points in the indicator function - Xd = derivatives - Xc=variables to condition on - - Computations of all covariances follows simple rules: - Cov(X(t),X(s))=r(t,s), - then Cov(X'(t),X(s))=dr(t,s)/dt. Now for stationary X(t) we have - a function r(tau) such that Cov(X(t),X(s))=r(s-t) (or r(t-s) will give - the same result). - - Consequently - Cov(X'(t),X(s)) = -r'(s-t) = -sign(s-t)*r'(|s-t|) - Cov(X'(t),X'(s)) = -r''(s-t) = -r''(|s-t|) - Cov(X''(t),X'(s)) = r'''(s-t) = sign(s-t)*r'''(|s-t|) - Cov(X''(t),X(s)) = r''(s-t) = r''(|s-t|) - Cov(X''(t),X''(s)) = r''''(s-t) = r''''(|s-t|) - - """ - # cov(Xd) - Sdd = -toeplitz(R[[0, pt], 2]) - # cov(Xc) - Scc = toeplitz(R[[0, pt], 0]) - # cov(Xc,Xd) - Scd = array([[0, R[pt, 1]], [ -R[pt, 1], 0]]) - - if pt > 1 : - #%cov(Xt) - Stt = toeplitz(R[:pt - 1, 0]) # Cov(X(tn),X(ts)) = r(ts-tn) = r(|ts-tn|) - #%cov(Xc,Xt) - Sct = R[1:pt, 0] # Cov(X(tn),X(ts)) = r(ts-tn) = r(|ts-tn|) - Sct = vstack((Sct, Sct[::-1])) - #%Cov(Xd,Xt) - Sdt = -R[1:pt, 1] # Cov(X'(t1),X(ts)) = -r'(ts-t1) = r(|s-t|) - Sdt = vstack((Sdt, -Sdt[::-1])) - #N = pt + 3 - big = vstack((hstack((Stt, Sdt.T, Sct.T)), - hstack((Sdt, Sdd, Scd.T)), - hstack((Sct, Scd, Scc)))) - else: - #N = 4 - big = vstack((hstack((Sdd, Scd.T)), - hstack((Scd, Scc)))) - return big - - def to_mmt_pdf(self, paramt=None,paramu=None,utc=None,kind='mm',verbose=False,**options): - ''' Returns joint density of Maximum, minimum and period. - - Parameters - ---------- - u = reference level (default the most frequently crossed level). - kind : string - defining density returned - 'Mm' : maximum and the following minimum. (M,m) (default) - 'rfc' : maximum and the rainflow minimum height. - 'AcAt' : (crest,trough) heights. - 'vMm' : level v separated Maximum and minimum (M,m)_v - 'MmTMm' : maximum, minimum and period between (M,m,TMm) - 'vMmTMm': level v separated Maximum, minimum and period - between (M,m,TMm)_v - 'MmTMd' : level v separated Maximum, minimum and the period - from Max to level v-down-crossing (M,m,TMd)_v. - 'MmTdm' : level v separated Maximum, minimum and the period from - level v-down-crossing to min. (M,m,Tdm)_v - NB! All 'T' above can be replaced by 'L' to get wave length - instead. - paramt : [0 tn Nt] - defines discretization of half period: tn is the longest period - considered while Nt is the number of points, i.e. (Nt-1)/tn is the - sampling frequnecy. paramt= [0 10 51] implies that the halfperiods - are considered at 51 linearly spaced points in the interval [0,10], - i.e. sampling frequency is 5 Hz. - paramu : [u v N] - defines discretization of maxima and minima ranges: u is the lowest - minimum considered, v the highest maximum and N is the number of - levels (u,v) included. - options : - rind-options structure containing optional parameters controlling - the performance of the integration. See rindoptset for details. - [] = default values are used. - - Returns - ------- - f = pdf (density structure) of crests (trough) heights - - TO_MMT_PDF calculates densities of wave characteristics in a - stationary Gaussian transform process X(t) where - Y(t) = g(X(t)) (Y zero-mean Gaussian with spectrum given in input spec). - The tr. g can be estimated using lc2tr, dat2tr, hermitetr or ochitr. - - Examples - -------- - The joint density of zero separated Max2min cycles in time (a); - in space (b); AcAt in time for nonlinear sea model (c): - - Hm0=7;Tp=11; - S = jonswap(4*pi/Tp,[Hm0 Tp]); - Sk = spec2spec(S,'k1d'); - L0 = spec2mom(S,1); - paramu = [sqrt(L0)*[-4 4] 41]; - ft = spec2mmtpdf(S,0,'vmm',[],paramu); pdfplot(ft) % a) - fs = spec2mmtpdf(Sk,0,'vmm'); figure, pdfplot(fs) % b) - [sk, ku, me]=spec2skew(S); - g = hermitetr([],[sqrt(L0) sk ku me]); - Snorm=S; Snorm.S=S.S/L0; Snorm.tr=g; - ftg=spec2mmtpdf(Snorm,0,'AcAt',[],paramu); pdfplot(ftg) % c) - - See also - -------- - rindoptset, dat2tr, datastructures, wavedef, perioddef - - References - --------- - Podgorski et al. (2000) - "Exact distributions for apparent waves in irregular seas" - Ocean Engineering, Vol 27, no 1, pp979-1016. - - P. A. Brodtkorb (2004), - Numerical evaluation of multinormal expectations - In Lund university report series - and in the Dr.Ing thesis: - The probability of Occurrence of dangerous Wave Situations at Sea. - Dr.Ing thesis, Norwegian University of Science and Technolgy, NTNU, - Trondheim, Norway. - - Per A. Brodtkorb (2006) - "Evaluating Nearly Singular Multinormal Expectations with Application to - Wave Distributions", - Methodology And Computing In Applied Probability, Volume 8, Number 1, pp. 65-91(27) - ''' - - opts = dict(speed=4, nit=2, method=0) - opts.update(**options) - - ftype = self.freqtype - kind2defnr = dict( ac=-2,at=-2, - rfc=-1, - mm=0, - mmtmm=1, mmlmm=1, - vmm=2, - vmmtmm=3, vmmlmm=3, - mmtmd=4, vmmtmd=4, mmlmd=4,vmmlmd=4, - mmtdm=5, vmmtdm=5, mmldm=5, vmmldm=5) - defnr = kind2defnr.get(kind, 0) - in_space = (ftype=='k') # distribution in space or time - if defnr>=3 or defnr==1: - in_space = (kind[-2].upper()=='L') - - if in_space: - #spec = spec2spec(spec,'k1d') ; - ptxt = 'space'; - else: - #spec = spec2spec(spec,'freq'); - ptxt='time'; - - S = self.copy() - S.normalize() - m, unused_mtxt = self.moment(nr=4, even=True) - A = sqrt(m[0] / m[1]) - - if paramt is None: - distanceBetweenExtremes = 5 * pi * sqrt(m[1] / m[2]) #(2.5 * mean distance between extremes) - paramt = [0, distanceBetweenExtremes, 43] - - if paramu is None: - paramu = [-5 * sqrt(m[0]), 5 * sqrt(m[0]), 41] - - if self.tr is None: - g = TrLinear(var=m[0]) - else: - g = self.tr - - if utc is None: - utc = g.gauss2dat(0) # most frequent crossed level - - # transform reference level into Gaussian level - u = g.dat2gauss(utc) - if verbose: - print('The level u for Gaussian process = %g' % u) - - - t0, tn, Nt = paramt - t = linspace(0, tn / A, Nt) # normalized times - - Nstart = 1 + round(t0/tn*(Nt-1)) # the starting point to evaluate - - - Nx = paramu[2] - if (defnr>1): - paramu[0] = max(0,paramu[0]) - if (paramu[1]<0): - raise ValueError('Discretization levels must be larger than zero') - - #Transform amplitudes to Gaussian levels: - #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - h = linspace(*paramu) - - - if defnr>1: # level v separated Max2min densities - hg = np.hstack((utc+h,utc-h)) - hg, der = g.dat2gauss(utc+h, ones(Nx)) - hg1, der1 = g.dat2gauss(utc-h, ones(Nx)) - der, der1 = np.abs(der), np.abs(der1) - hg = np.hstack((hg,hg1)) - else: # Max2min densities - hg, der = np.abs(g.dat2gauss(h, ones(Nx))) - der = der1 = np.abs(der) - - dt = t[1] - t[0] - nr = 4 - R = S.tocov_matrix(nr, Nt - 1, dt) - - #NB!!! the spec2XXpdf.exe programmes is very sensitive to how you interpolate - # the covariances, especially where the process is very dependent - # and the covariance matrix is nearly singular. (i.e. for small t - # and high levels of u if Tc and low levels of u if Tt) - # The best is to interpolate the spectrum linearly so that S.S>=0 - # This makes sure that the covariance matrix is positive - # semi-definitt, since the circulant spectrum are the eigenvalues of - # the circulant covariance matrix. - - - callFortran = 0; # %options.method<0; - #if callFortran, % call fortran - #ftmp = cov2mmtpdfexe(R,dt,u,defnr,Nstart,hg,options); - #err = repmat(nan,size(ftmp)); - #else - [ftmp,err,terr,options] = cov2mmtpdf(R,dt,u,defnr,Nstart,hg,options) - - #end - note = '' - if hasattr(self,'note'): - note = note + self.note - tmp = 'L' if in_space else 'T' - if Nx>2: - titledict = {'-2':'Joint density of (Ac,At) in %s' % ptxt, - '-1':'Joint density of (M,m_{rfc}) in %s' % ptxt, - '0':'Joint density of (M,m) in %s' % ptxt, - '1':'Joint density of (M,m,%sMm) in %s' % (tmp, ptxt), - '2':'Joint density of (M,m)_{v=%2.5g} in %s' % (utc, ptxt), - '3':'Joint density of (M,m,%sMm)_{v=%2.5g} in %s' % (tmp,utc, ptxt), - '4':'Joint density of (M,m,%sMd)_{v=%2.5g} in %s' % (tmp,utc, ptxt), - '5':'Joint density of (M,m,%sdm)_{v=%2.5g} in %s' % (tmp,utc, ptxt)} - title = titledict[defnr] - labx = 'Max [m]' - laby = 'min [m]'; - args = (h,h) - else: - note = note + 'Density is not scaled to unity' - if defnr in (-2,-1,0,1): - title = 'Density of (%sMm, M = %2.5g, m = %2.5g)' % (tmp,h[1],h[0]) - elif defnr in (2,3): - title = 'Density of (%sMm, M = %2.5g, m = %2.5g)_{v=%2.5g}' % (tmp,h[1],-h[1],utc) - elif defnr==4: - title = 'Density of (%sMd, %sMm, M = %2.5g, m = %2.5g)_{v=%2.5g}' % (tmp,tmp,h[1],-h[1],utc) - elif defnr==5: - title = 'Density of (%sdm, %sMm, M = %2.5g, m = %2.5g)_{v=%2.5g}' % (tmp,tmp,h[1],-h[1],utc) - - - - f = PlotData(); -# f.options = options; -# if defnr>1 or defnr==-2: -# f.u = utc # save level u -# -# if Nx>2 % amplitude distributions wanted -# f.x{2} = h; -# f.labx{2} = 'min [m]'; -# -# -# if defnr>2 || defnr==1 -# der0 = der1[:,None] * der[None,:] -# ftmp = np.reshape(ftmp,Nx,Nx,Nt) * der0[:,:, None] / A -# err = np.reshape(err,Nx,Nx,Nt) * der0[:,:, None] / A -# -# f.x{3} = t(:)*A -# labz = 'wave length [m]' if in_space else 'period [sec]' -# -# else -# der0 = der[:,None] * der[None,:] -# ftmp = np.reshape(ftmp,Nx,Nx) * der0 -# err = np.reshape(err,Nx,Nx) * der0 -# -# if (defnr==-1): -# ftmp0 = fliplr(mctp2rfc(fliplr(ftmp))); -# err = abs(ftmp0-fliplr(mctp2rfc(fliplr(ftmp+err)))); -# ftmp = ftmp0; -# elif (defnr==-2): -# ftmp0=fliplr(mctp2tc(fliplr(ftmp),utc,paramu))*sqrt(L4*L0)/L2; -# err =abs(ftmp0-fliplr(mctp2tc(fliplr(ftmp+err),utc,paramu))*sqrt(L4*L0)/L2); -# index1=find(f.x{1}>0); -# index2=find(f.x{2}<0); -# ftmp=flipud(ftmp0(index2,index1)); -# err =flipud(err(index2,index1)); -# f.x{1} = f.x{1}(index1); -# f.x{2} = abs(flipud(f.x{2}(index2))); -# end -# end -# f.f = ftmp; -# f.err = err; -# else % Only time or wave length distributions wanted -# f.f = ftmp/A; -# f.err = err/A; -# f.x{1}=A*t'; -# if strcmpi(def(1),'t') -# f.labx{1} = 'period [sec]'; -# else -# f.labx{1} = 'wave length [m]'; -# end -# if defnr>3, -# f.f = reshape(f.f,[Nt, Nt]); -# f.err = reshape(f.err,[Nt, Nt]); -# f.x{2}= A*t'; -# if strcmpi(def(1),'t') -# f.labx{2} = 'period [sec]'; -# else -# f.labx{2} = 'wave length [m]'; -# end -# end -# end -# -# -# try -# [f.cl,f.pl]=qlevels(f.f,[10 30 50 70 90 95 99 99.9],f.x{1},f.x{2}); -# catch -# warning('WAFO:SPEC2MMTPDF','Singularity likely in pdf') -# end -# %pdfplot(f) -# -# %Test of spec2mmtpdf -# % cd f:\matlab\matlab\wafo\source\sp2thpdfalan -# % addpath f:\matlab\matlab\wafo ,initwafo, addpath f:\matlab\matlab\graphutil -# % Hm0=7;Tp=11; S = jonswap(4*pi/Tp,[Hm0 Tp]); -# % ft = spec2mmtpdf(S,0,'vMmTMm',[0.3,.4,11],[0 .00005 2]); - - return f #% main - -# function dens = cov2mmtpdfexe(R,dt,u,defnr,Nstart,hg,options) -# % Write parameters to file -# %~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Nx = max(1,length(hg)); -# if (defnr>1) -# Nx = Nx/2; %level v separated max2min densities wanted -# end -# Ntime = size(R,1); -# -# filenames = {'h.in','reflev.in'}; -# cleanup(filenames{:}) -# -# fid = fopen('h.in','wt'); -# fprintf(fid,'%12.10f\n',hg); -# fclose(fid); -# -# %XSPLT = options.xsplit; -# nit = options.nit; -# speed = options.speed; -# seed = options.seed; -# SCIS = abs(options.method); % method<=0 -# -# disp('writing data') -# fid=fopen('reflev.in','wt'); -# fprintf(fid,'%2.0f \n',Ntime); -# fprintf(fid,'%2.0f \n',Nstart); -# fprintf(fid,'%2.0f \n',nit); -# fprintf(fid,'%2.0f \n',speed); -# fprintf(fid,'%2.0f \n',SCIS); -# fprintf(fid,'%2.0f \n',seed); % select a random seed for rind -# fprintf(fid,'%2.0f \n',Nx); -# fprintf(fid,'%12.10E \n',dt); -# fprintf(fid,'%12.10E \n',u); -# fprintf(fid,'%2.0f \n',defnr); % def -# fclose(fid); -# -# filenames2 = writecov(R); -# -# disp(' Starting Fortran executable.') -# -# dos([ wafoexepath 'cov2mmtpdf.exe']); %compiled cov2mmtpdf.f with rind70.f -# -# dens = load('dens.out'); -# -# cleanup(filenames{:},filenames2{:}) -# -# %% Clean up -# -# return - def to_specnorm(self): - S = self.copy() - S.normalize() - return S - - def sim(self, ns=None, cases=1, dt=None, iseed=None, method='random', derivative=False): - ''' Simulates a Gaussian process and its derivative from spectrum - - Parameters - ---------- - ns : scalar - number of simulated points. (default length(spec)-1=n-1). - If ns>n-1 it is assummed that acf(k)=0 for all k>n-1 - cases : scalar - number of replicates (default=1) - dt : scalar - step in grid (default dt is defined by the Nyquist freq) - iseed : int or state - starting state/seed number for the random number generator - (default none is set) - method : string - if 'exact' : simulation using cov2sdat - if 'random' : random phase and amplitude simulation (default) - derivative : bool - if true : return derivative of simulated signal as well - otherwise - - Returns - ------- - xs = a cases+1 column matrix ( t,X1(t) X2(t) ...). - xsder = a cases+1 column matrix ( t,X1'(t) X2'(t) ...). - - Details - ------- - Performs a fast and exact simulation of stationary zero mean - Gaussian process through circulant embedding of the covariance matrix - or by summation of sinus functions with random amplitudes and random - phase angle. - - If the spectrum has a non-empty field .tr, then the transformation is - applied to the simulated data, the result is a simulation of a transformed - Gaussian process. - - Note: The method 'exact' simulation may give high frequency ripple when - used with a small dt. In this case the method 'random' works better. - - Example: - >>> import wafo.spectrum.models as sm - >>> Sj = sm.Jonswap();S = Sj.tospecdata() - >>> ns =100; dt = .2 - >>> x1 = S.sim(ns,dt=dt) - - >>> import numpy as np - >>> import scipy.stats as st - >>> x2 = S.sim(20000,20) - >>> truth1 = [0,np.sqrt(S.moment(1)[0]),0., 0.] - >>> funs = [np.mean,np.std,st.skew,st.kurtosis] - >>> for fun,trueval in zip(funs,truth1): - ... res = fun(x2[:,1::],axis=0) - ... m = res.mean() - ... sa = res.std() - ... #trueval, m, sa - ... np.abs(m-trueval) T) - - # Trick to avoid adding high frequency noise to the spectrum - if i.size > 0: - acf.data[i[0]::] = 0.0 - - return acf.sim(ns=ns, cases=cases, iseed=iseed, derivative=derivative) - - _set_seed(iseed) - - ns = ns + mod(ns, 2) # make sure it is even - - f_i = freq[1:-1] - s_i = spec.data[1:-1] - if ftype in ('w', 'k'): - fact = 2. * pi - s_i = s_i * fact - f_i = f_i / fact - - x = zeros((ns, cases + 1)) - - d_f = 1 / (ns * d_t) - - - # interpolate for freq. [1:(N/2)-1]*d_f and create 2-sided, uncentered spectra - f = arange(1, ns / 2.) * d_f - - f_u = hstack((0., f_i, d_f * ns / 2.)) - s_u = hstack((0., abs(s_i) / 2., 0.)) - - - s_i = interp(f, f_u, s_u) - s_u = hstack((0., s_i, 0, s_i[(ns / 2) - 2::-1])) - del(s_i, f_u) - - # Generate standard normal random numbers for the simulations - randn = random.randn - z_r = randn((ns / 2) + 1, cases) - z_i = vstack((zeros((1, cases)), randn((ns / 2) - 1, cases), zeros((1, cases)))) - - amp = zeros((ns, cases), dtype=complex) - amp[0:(ns / 2 + 1), :] = z_r - 1j * z_i - del(z_r, z_i) - amp[(ns / 2 + 1):ns, :] = amp[ns / 2 - 1:0:-1, :].conj() - amp[0, :] = amp[0, :]*sqrt(2.) - amp[(ns / 2), :] = amp[(ns / 2), :]*sqrt(2.) - - - # Make simulated time series - T = (ns - 1) * d_t - Ssqr = sqrt(s_u * d_f / 2.) - - # stochastic amplitude - amp = amp * Ssqr[:, newaxis] - - - # Deterministic amplitude - #amp = sqrt[1]*Ssqr(:,ones(1,cases)).*exp(sqrt(-1)*atan2(imag(amp),real(amp))) - del(s_u, Ssqr) - - - x[:, 1::] = fft(amp, axis=0).real - x[:, 0] = linspace(0, T, ns) #' %(0:d_t:(np-1)*d_t).' - - - if derivative: - xder = zeros(ns, cases + 1) - w = 2. * pi * hstack((0, f, 0., -f[-1::-1])) - amp = -1j * amp * w[:, newaxis] - xder[:, 1:(cases + 1)] = fft(amp, axis=0).real - xder[:, 0] = x[:, 0] - - if spec.tr is not None: - #print(' Transforming data.') - g = spec.tr - if derivative: - for i in range(cases): - x[:, i + 1], xder[:, i + 1] = g.gauss2dat(x[:, i + 1], xder[:, i + 1]) - else: - for i in range(cases): - x[:, i + 1] = g.gauss2dat(x[:, i + 1]) - - - if derivative: - return x, xder - else: - return x - -# function [x2,x,svec,dvec,amp]=spec2nlsdat(spec,np,dt,iseed,method,truncationLimit) - def sim_nl(self, ns=None, cases=1, dt=None, iseed=None, method='random', - fnlimit=1.4142, reltol=1e-3, g=9.81, verbose=False): - """ - Simulates a Randomized 2nd order non-linear wave X(t) - - Parameters - ---------- - ns : scalar - number of simulated points. (default length(spec)-1=n-1). - If ns>n-1 it is assummed that R(k)=0 for all k>n-1 - cases : scalar - number of replicates (default=1) - dt : scalar - step in grid (default dt is defined by the Nyquist freq) - iseed : int or state - starting state/seed number for the random number generator - (default none is set) - method : string - 'apStochastic' : Random amplitude and phase (default) - 'aDeterministic' : Deterministic amplitude and random phase - 'apDeterministic' : Deterministic amplitude and phase - fnlimit : scalar - normalized upper frequency limit of spectrum for 2'nd order - components. The frequency is normalized with - sqrt(gravity*tanh(kbar*water_depth)/amp_max)/(2*pi) - (default sqrt(2), i.e., Convergence criterion [1]_). - Other possible values are: - sqrt(1/2) : No bump in trough criterion - sqrt(pi/7) : Wave steepness criterion - reltol : scalar - relative tolerance defining where to truncate spectrum for the - sum and difference frequency effects - - - Returns - ------- - xs2 = a cases+1 column matrix ( t,X1(t) X2(t) ...). - xs1 = a cases+1 column matrix ( t,X1'(t) X2'(t) ...). - - Details - ------- - Performs a Fast simulation of Randomized 2nd order non-linear - waves by summation of sinus functions with random amplitudes and - phase angles. The extent to which the simulated result are applicable - to real seastates are dependent on the validity of the assumptions: - - 1. Seastate is unidirectional - 2. Surface elevation is adequately represented by 2nd order random - wave theory - 3. The first order component of the surface elevation is a Gaussian - random process. - - If the spectrum does not decay rapidly enough towards zero, the - contribution from the 2nd order wave components at the upper tail can - be very large and unphysical. To ensure convergence of the perturbation - series, the upper tail of the spectrum is truncated at FNLIMIT in the - calculation of the 2nd order wave components, i.e., in the calculation - of sum and difference frequency effects. This may also be combined with - the elimination of second order effects from the spectrum, i.e., extract - the linear components from the spectrum. One way to do this is to use - SPEC2LINSPEC. - - Example - -------- - >>> import wafo.spectrum.models as sm - >>> Sj = sm.Jonswap();S = Sj.tospecdata() - >>> ns =100; dt = .2 - >>> x1 = S.sim_nl(ns,dt=dt) - - >>> import numpy as np - >>> import scipy.stats as st - >>> x2, x1 = S.sim_nl(ns=20000,cases=20) - >>> truth1 = [0,np.sqrt(S.moment(1)[0][0])] + S.stats_nl(moments='sk') - >>> truth1[-1] = truth1[-1]-3 - >>> np.round(truth1, 3) - array([ 0. , 1.75 , 0.187, 0.062]) - - >>> funs = [np.mean,np.std,st.skew,st.kurtosis] - >>> for fun,trueval in zip(funs,truth1): - ... res = fun(x2[:,1::], axis=0) - ... m = res.mean() - ... sa = res.std() - ... #trueval, m, sa - ... np.abs(m-trueval)>> x = [] - >>> for i in range(20): - ... x2, x1 = S.sim_nl(ns=20000,cases=1) - ... x.append(x2[:,1::]) - >>> x2 = np.hstack(x) - >>> truth1 = [0,np.sqrt(S.moment(1)[0][0])] + S.stats_nl(moments='sk') - >>> truth1[-1] = truth1[-1]-3 - >>> np.round(truth1,3) - array([ 0. , 1.75 , 0.187, 0.062]) - - >>> funs = [np.mean,np.std,st.skew,st.kurtosis] - >>> for fun,trueval in zip(funs,truth1): - ... res = fun(x2, axis=0) - ... m = res.mean() - ... sa = res.std() - ... #trueval, m, sa - ... np.abs(m-trueval) s_max * reltol).argmax() - nmax = flatnonzero(s_i > 0).max() - s_u = hstack((0., s_i, 0, s_i[(ns / 2) - 2::-1])) - del(s_i, f_u) - - # Generate standard normal random numbers for the simulations - randn = random.randn - z_r = randn((ns / 2) + 1, cases) - z_i = vstack((zeros((1, cases)), - randn((ns / 2) - 1, cases), - zeros((1, cases)))) - - amp = zeros((ns, cases), dtype=complex) - amp[0:(ns / 2 + 1), :] = z_r - 1j * z_i - del(z_r, z_i) - amp[(ns / 2 + 1):ns, :] = amp[ns / 2 - 1:0:-1, :].conj() - amp[0, :] = amp[0, :]*sqrt(2.) - amp[(ns / 2), :] = amp[(ns / 2), :]*sqrt(2.) - - - # Make simulated time series - - T = (ns - 1) * d_t - Ssqr = sqrt(s_u * df / 2.) - - - if method.startswith('apd') : # apdeterministic - # Deterministic amplitude and phase - amp[1:(ns / 2), :] = amp[1, 0] - amp[(ns / 2 + 1):ns, :] = amp[1, 0].conj() - amp = sqrt(2) * Ssqr[:, newaxis] * exp(1J * arctan2(amp.imag, amp.real)) - elif method.startswith('ade'): # adeterministic - # Deterministic amplitude and random phase - amp = sqrt(2) * Ssqr[:, newaxis] * exp(1J * arctan2(amp.imag, amp.real)) - else: - # stochastic amplitude - amp = amp * Ssqr[:, newaxis] - # Deterministic amplitude - #amp = sqrt(2)*Ssqr(:,ones(1,cases)).*exp(sqrt(-1)*atan2(imag(amp),real(amp))) - del(s_u, Ssqr) - - - x[:, 1::] = fft(amp, axis=0).real - x[:, 0] = linspace(0, T, ns) #' %(0:d_t:(np-1)*d_t).' - - - - x2 = x.copy() - - # If the spectrum does not decay rapidly enough towards zero, the - # contribution from the wave components at the upper tail can be very - # large and unphysical. - # To ensure convergence of the perturbation series, the upper tail of - # the spectrum is truncated in the calculation of sum and difference - # frequency effects. - # Find the critical wave frequency to ensure convergence. - - num_waves = 1000. # Typical number of waves in 3 hour seastate - kbar = w2k(2. * pi / Tm02, 0., water_depth)[0] - amp_max = sqrt(2 * log(num_waves)) * Hm0 / 4 #% Expected maximum amplitude for 1000 waves seastate - - f_limit_up = fnlimit * sqrt(g * tanh(kbar * water_depth) / amp_max) / (2 * pi) - f_limit_lo = sqrt(g * tanh(kbar * water_depth) * amp_max / water_depth) / (2 * pi * water_depth) - - nmax = min(flatnonzero(f <= f_limit_up).max(), nmax) + 1 - nmin = max(flatnonzero(f_limit_lo <= f).min(), nmin) + 1 - - #if isempty(nmax),nmax = np/2end - #if isempty(nmin),nmin = 2end % Must always be greater than 1 - f_limit_up = df * nmax - f_limit_lo = df * nmin - if verbose: - print('2nd order frequency Limits = %g,%g' % (f_limit_lo, f_limit_up)) - -## if nargout>3, -## %compute the sum and frequency effects separately -## [svec, dvec] = disufq((amp.'),w,kw,min(h,10^30),g,nmin,nmax) -## svec = svec.' -## dvec = dvec.' -## -## x2s = fft(svec) % 2'nd order sum frequency component -## x2d = fft(dvec) % 2'nd order difference frequency component -## -## % 1'st order + 2'nd order component. -## x2(:,2:end) =x(:,2:end)+ real(x2s(1:np,:))+real(x2d(1:np,:)) -## else - if False: - # TODO: disufq does not work for cases>1 - amp = np.array(amp.T).ravel() - rvec, ivec = c_library.disufq(amp.real, amp.imag, w, kw, water_depth, - g, nmin, nmax, cases, ns) - svec = rvec + 1J * ivec - else: - amp = amp.T - svec = [] - for i in range(cases): - rvec, ivec = c_library.disufq(amp[i].real, amp[i].imag, w, kw, water_depth, - g, nmin, nmax, 1, ns) - svec.append(rvec + 1J * ivec) - svec = np.hstack(svec) - svec.shape = (cases, ns) - x2o = fft(svec, axis=1).T # 2'nd order component - - - # 1'st order + 2'nd order component. - x2[:, 1::] = x[:, 1::] + x2o[0:ns, :].real - - return x2, x - - - - def stats_nl(self, h=None, moments='sk', method='approximate', g=9.81): - """ - Statistics of 2'nd order waves to the leading order. - - Parameters - ---------- - h : scalar - water depth (default self.h) - moments : string (default='sk') - composed of letters ['mvsk'] specifying which moments to compute: - 'm' = mean, - 'v' = variance, - 's' = skewness, - 'k' = (Pearson's) kurtosis. - method : string - 'approximate' method due to Marthinsen & Winterstein (default) - 'eigenvalue' method due to Kac and Siegert - - Skewness = kurtosis-3 = 0 for a Gaussian process. - The mean, sigma, skewness and kurtosis are determined as follows: - method == 'approximate': due to Marthinsen and Winterstein - mean = 2 * int Hd(w1,w1)*S(w1) dw1 - sigma = sqrt(int S(w1) dw1) - skew = 6 * int int [Hs(w1,w2)+Hd(w1,w2)]*S(w1)*S(w2) dw1*dw2/m0^(3/2) - kurt = (4*skew/3)^2 - - where Hs = sum frequency effects and Hd = difference frequency effects - - method == 'eigenvalue' - - mean = sum(E) - sigma = sqrt(sum(C^2)+2*sum(E^2)) - skew = sum((6*C^2+8*E^2).*E)/sigma^3 - kurt = 3+48*sum((C^2+E^2).*E^2)/sigma^4 - - where - h1 = sqrt(S*dw/2) - C = (ctranspose(V)*[h1;h1]) - and E and V is the eigenvalues and eigenvectors, respectively, of the 2'order - transfer matrix. S is the spectrum and dw is the frequency spacing of S. - - Example: - -------- - #Simulate a Transformed Gaussian process: - >>> import wafo.spectrum.models as sm - >>> import wafo.transform.models as wtm - >>> Hs = 7. - >>> Sj = sm.Jonswap(Hm0=Hs, Tp=11) - >>> S = Sj.tospecdata() - >>> me, va, sk, ku = S.stats_nl(moments='mvsk') - >>> g = wtm.TrHermite(mean=me, sigma=Hs/4, skew=sk, kurt=ku, ysigma=Hs/4) - >>> ys = S.sim(15000) # Simulated in the Gaussian world - >>> xs = g.gauss2dat(ys[:,1]) # Transformed to the real world - - - See also - --------- - transform.TrHermite - transform.TrOchi - objects.LevelCrossings.trdata - objects.TimeSeries.trdata - - References: - ----------- - Langley, RS (1987) - 'A statistical analysis of nonlinear random waves' - Ocean Engineering, Vol 14, No 5, pp 389-407 - - Marthinsen, T. and Winterstein, S.R (1992) - 'On the skewness of random surface waves' - In proceedings of the 2nd ISOPE Conference, San Francisco, 14-19 june. - - Winterstein, S.R, Ude, T.C. and Kleiven, G. (1994) - 'Springing and slow drift responses: - predicted extremes and fatigue vs. simulation' - In Proc. 7th International behaviour of Offshore structures, (BOSS) - Vol. 3, pp.1-15 - """ - - #% default options - if h is None: - h = self.h - - #S = ttspec(S,'w') - w = ravel(self.args) - S = ravel(self.data) - if self.freqtype in ['f', 'w']: - #vari = 't' - if self.freqtype == 'f': - w = 2. * pi * w - S = S / (2. * pi) - #m0 = self.moment(nr=0) - m0 = simps(S, w) - sa = sqrt(m0) - #Nw = w.size - - Hs, Hd, Hdii = qtf(w, h, g) - - #%return - #%skew=6/sqrt(m0)^3*simpson(S.w,simpson(S.w,(Hs+Hd).*S1(:,ones(1,Nw))).*S1.') - - Hspd = trapz(trapz((Hs + Hd) * S[newaxis, :], w) * S, w) - output = [] - if method[0] == 'a': # %approx : Marthinsen, T. and Winterstein, S.R (1992) method - if 'm' in moments: - output.append(2. * trapz(Hdii * S, w)) - if 'v' in moments: - output.append(m0) - skew = 6. / sa ** 3 * Hspd - if 's' in moments: - output.append(skew) - if 'k' in moments: - output.append((4. * skew / 3.) ** 2. + 3.) - else: - raise ValueError('Unknown option!') - -## elif method[0]== 'q': #, #% quasi method -## Fn = self.nyquist_freq() -## dw = Fn/Nw -## tmp1 =sqrt(S[:,newaxis]*S[newaxis,:])*dw -## Hd = Hd*tmp1 -## Hs = Hs*tmp1 -## k = 6 -## stop = 0 -## while !stop: -## E = eigs([Hd,Hs;Hs,Hd],[],k) -## %stop = (length(find(abs(E)<1e-4))>0 | k>1200) -## %stop = (any(abs(E(:))<1e-4) | k>1200) -## stop = (any(abs(E(:))<1e-4) | k>=min(2*Nw,1200)) -## k = min(2*k,2*Nw) -## #end -## -## -## m02=2*sum(E.^2) % variance of 2'nd order contribution -## -## %Hstd = 16*trapz(S.w,(Hdii.*S1).^2) -## %Hstd = trapz(S.w,trapz(S.w,((Hs+Hd)+ 2*Hs.*Hd).*S1(:,ones(1,Nw))).*S1.') -## ma = 2*trapz(S.w,Hdii.*S1) -## %m02 = Hstd-ma^2% variance of second order part -## sa = sqrt(m0+m02) -## skew = 6/sa^3*Hspd -## kurt = (4*skew/3).^2+3 -## elif method[0]== 'e': #, % Kac and Siegert eigenvalue analysis -## Fn = self.nyquist_freq() -## dw = Fn/Nw -## tmp1 =sqrt(S[:,newaxis]*S[newaxis,:])*dw -## Hd = Hd*tmp1 -## Hs = Hs*tmp1 -## k = 6 -## stop = 0 -## -## -## while (not stop): -## [V,D] = eigs([Hd,HsHs,Hd],[],k) -## E = diag(D) -## %stop = (length(find(abs(E)<1e-4))>0 | k>=min(2*Nw,1200)) -## stop = (any(abs(E(:))<1e-4) | k>=min(2*Nw,1200)) -## k = min(2*k,2*Nw) -## #end -## -## -## h1 = sqrt(S*dw/2) -## C = (ctranspose(V)*[h1;h1]) -## -## E2 = E.^2 -## C2 = C.^2 -## -## ma = sum(E) % mean -## sa = sqrt(sum(C2)+2*sum(E2)) % standard deviation -## skew = sum((6*C2+8*E2).*E)/sa^3 % skewness -## kurt = 3+48*sum((C2+E2).*E2)/sa^4 % kurtosis - return output - def testgaussian(self, ns, test0=None, cases=100, method='nonlinear', verbose=False, **opt): - ''' - TESTGAUSSIAN Test if a stochastic process is Gaussian. - - CALL: test1 = testgaussian(S,[ns,Ns],test0,def,options); - - test1, - test0 = simulated and observed value of e(g)=int (g(u)-u)^2 du, - respectively, where int limits is given by OPTIONS.PARAM. - - S = spectral density structure - ns = # of points simulated - cases = # of independent simulations (default 100) - - def = 'nonlinear' : transform based on smoothed crossing intensity (default) - 'mnonlinear': transform based on smoothed marginal distribution - options = options structure defining how the estimation of the - transformation is done. (default troptset('dat2tr')) - - TESTGAUSSIAN simulates e(g(u)-u) = int (g(u)-u)^2 du for Gaussian processes - given the spectral density, S. The result is plotted if test0 is given. - This is useful for testing if the process X(t) is Gaussian. - If 95% of TEST1 is less than TEST0 then X(t) is not Gaussian at a 5% level. - - Example: - ------- - >>> import wafo.spectrum.models as sm - >>> import wafo.transform.models as wtm - >>> import wafo.objects as wo - >>> Hs = 7 - >>> Sj = sm.Jonswap(Hm0=Hs) - >>> S0 = Sj.tospecdata() - >>> ns =100; dt = .2 - >>> x1 = S0.sim(ns, dt=dt) - - >>> S = S0.copy() - >>> me, va, sk, ku = S.stats_nl(moments='mvsk') - >>> S.tr = wtm.TrHermite(mean=me, sigma=Hs/4, skew=sk, kurt=ku, ysigma=Hs/4) - >>> ys = wo.mat2timeseries(S.sim(ns=2**13)) - >>> g0, gemp = ys.trdata() - >>> t0 = g0.dist2gauss() - >>> t1 = S0.testgaussian(ns=2**13, t0=t0, cases=50) - >>> sum(t1>t0)<5 - True - - See also cov2sdat, dat2tr, troptset - ''' -# Tested on: Matlab 5.3, 5.2, 5.1 -# History: -# revised pab -# -changed name from mctrtest to testgaussianity -# revised pab jan2005 -# changed order of input so that chapter1.m works -# revised pab Feb2004 -# -changed h1line -# revised pab 29.12.2000 -# - added options and def to input due to changed calling syntax for dat2tr. -# - updated the help header -# revised by pab 12.11.99 -# fixed a bug, string input to dat2tr 'nonlinear' -# revised by pab 12.10.99 -# updated help header -# revised by pab 11.08.99 -# changed name from mctest to mctrtest -# by pab 11.11.98 - - maxsize = 200000 # must divide the computations due to limited memory -# if nargin<5||isempty(opt): -# opt = troptset('dat2tr'); -# -# opt = troptset(opt,'multip',1) - - plotflag = False if test0 is None else True - if cases > 50: - print(' ... be patient this may take a while') - - - rep = int(ns * cases / maxsize) + 1 - - Nstep = int(cases / rep) - - acf = self.tocovdata() - #R = spec2cov(S); - test1 = [] - for ix in range(rep): - xs = acf.sim(ns=ns, cases=Nstep) - for iy in range(1, xs.shape[-1]): - ts = TimeSeries(xs[:, iy], xs[:, 0].ravel()) - g, tmp = ts.trdata(method, **opt) - test1.append(g.dist2gauss()) - if verbose: - print('finished %d of %d ' % (ix + 1, rep)) - - if rep > 1: - xs = acf.sim(ns=ns, cases=np.remainder(cases, rep)) - for iy in range(1, xs.shape[-1]): - ts = TimeSeries(xs[:, iy], xs[:, 0].ravel()) - g, tmp = ts.trdata(method, **opt) - test1.append(g.dist2gauss()) - - if plotflag: - plotbackend.plot(test1, 'o') - plotbackend.plot([1, cases], [test0, test0], '--') - - plotbackend.ylabel('e(g(u)-u)') - plotbackend.xlabel('Simulation number') - return test1 - - def moment(self, nr=2, even=True, j=0): - ''' Calculates spectral moments from spectrum - - Parameters - ---------- - nr : int - order of moments (recomended maximum 4) - even : bool - False for all moments, - True for only even orders - j : int - 0 or 1 - - Returns - ------- - m : list of moments - mtext : list of strings describing the elements of m, see below - - Details - ------- - Calculates spectral moments of up to order NR by use of - Simpson-integration. - - / / - mj_t^i = | w^i S(w)^(j+1) dw, or mj_x^i = | k^i S(k)^(j+1) dk - / / - - where k=w^2/gravity, i=0,1,...,NR - - The strings in output mtext have the same position in the list - as the corresponding numerical value has in output m - Notation in mtext: 'm0' is the variance, - 'm0x' is the first-order moment in x, - 'm0xx' is the second-order moment in x, - 'm0t' is the first-order moment in t, - etc. - For the calculation of moments see Baxevani et al. - - Example: - >>> import numpy as np - >>> import wafo.spectrum.models as sm - >>> Sj = sm.Jonswap(Hm0=3, Tp=7) - >>> w = np.linspace(0,4,256) - >>> S = SpecData1D(Sj(w),w) #Make spectrum object from numerical values - >>> S.moment() - ([0.5616342024616453, 0.7309966918203602], ['m0', 'm0tt']) - - References - ---------- - Baxevani A. et al. (2001) - Velocities for Random Surfaces - ''' - one_dim_spectra = ['freq', 'enc', 'k1d'] - if self.type not in one_dim_spectra: - raise ValueError('Unknown spectrum type!') - - f = ravel(self.args) - S = ravel(self.data) - if self.freqtype in ['f', 'w']: - vari = 't' - if self.freqtype == 'f': - f = 2. * pi * f - S = S / (2. * pi) - else: - vari = 'x' - S1 = abs(S) ** (j + 1.) - m = [simps(S1, x=f)] - mtxt = 'm%d' % j - mtext = [mtxt] - step = mod(even, 2) + 1 - df = f ** step - for i in range(step, nr + 1, step): - S1 = S1 * df - m.append(simps(S1, x=f)) - mtext.append(mtxt + vari * i) - return m, mtext - - def nyquist_freq(self): - """ - Return Nyquist frequency - - Example - ------- - >>> import wafo.spectrum.models as sm - >>> Sj = sm.Jonswap(Hm0=5) - >>> S = Sj.tospecdata() #Make spectrum ob - >>> S.nyquist_freq() - 3.0 - """ - return self.args[-1] - - def sampling_period(self): - ''' Returns sampling interval from Nyquist frequency of spectrum - - Returns - --------- - dT : scalar - sampling interval, unit: - [m] if wave number spectrum, - [s] otherwise - - Let wm be maximum frequency/wave number in spectrum, - then dT=pi/wm if angular frequency, dT=1/(2*wm) if natural frequency (Hz) - - Example - ------- - >>> import wafo.spectrum.models as sm - >>> Sj = sm.Jonswap(Hm0=5) - >>> S = Sj.tospecdata() #Make spectrum ob - >>> S.sampling_period() - 1.0471975511965976 - - See also - ''' - - if self.freqtype in 'f': - wmdt = 0.5 # Nyquist to sampling interval factor - else: # ftype == w og ftype == k - wmdt = pi - - wm = self.args[-1] #Nyquist frequency - dt = wmdt / wm #sampling interval = 1/Fs - return dt - - def resample(self, dt=None, Nmin=0, Nmax=2 ** 13 + 1, method='stineman'): - ''' - Interpolate and zero-padd spectrum to change Nyquist freq. - - Parameters - ---------- - dt : real scalar - wanted sampling interval (default as given by S, see spec2dt) - unit: [s] if frequency-spectrum, [m] if wave number spectrum - Nmin, Nmax : scalar integers - minimum and maximum number of frequencies, respectively. - method : string - interpolation method (options are 'linear', 'cubic' or 'stineman') - - To be used before simulation (e.g. spec2sdat) or evaluation of covariance - function (spec2cov) to directly get wanted sampling interval. - The input spectrum is interpolated and padded with zeros to reach - the right max-frequency, w(end)=pi/dt, f(end)=1/(2*dt), or k(end)=pi/dt. - The objective is that output frequency grid should be at least as dense - as the input grid, have equidistant spacing and length equal to - 2^k+1 (>=Nmin). If the max frequency is changed, the number of points - in the spectrum is maximized to 2^13+1. - - Note: Also zero-padding down to zero freq, if S does not start there. - If empty input dt, this is the only effect. - - See also - -------- - spec2cov, spec2sdat, covinterp, spec2dt - ''' - - ftype = self.freqtype - w = self.args.ravel() - n = w.size - - #%doInterpolate = 0 - # Nyquist to sampling interval factor - Cnf2dt = 0.5 if ftype == 'f' else pi #% ftype == w og ftype == k - - - wnOld = w[-1] # Old Nyquist frequency - dTold = Cnf2dt / wnOld # sampling interval=1/Fs - - #dTold = self.sampling_period() - - if dt is None: - dt = dTold - - # Find how many points that is needed - nfft = 2 ** nextpow2(max(n - 1, Nmin - 1)) - dttest = dTold * (n - 1) / nfft - - while (dttest > dt) and (nfft < Nmax - 1): - nfft = nfft * 2 - dttest = dTold * (n - 1) / nfft - - nfft = nfft + 1 - - wnNew = Cnf2dt / dt #% New Nyquist frequency - dWn = wnNew - wnOld - doInterpolate = dWn > 0 or w[1] > 0 or (nfft != n) or dt != dTold or any(abs(diff(w, axis=0)) > 1.0e-8) - - if doInterpolate > 0: - S1 = self.data - - dw = min(diff(w)) - - if dWn > 0: - #% add a zero just above old max-freq, and a zero at new max-freq - #% to get correct interpolation there - Nz = 1 + (dWn > dw) # % Number of zeros to add - if Nz == 2: - w = hstack((w, wnOld + dw, wnNew)) - else: - w = hstack((w, wnNew)) - - S1 = hstack((S1, zeros(Nz))) - - if w[0] > 0: - #% add a zero at freq 0, and, if there is space, a zero just below min-freq - Nz = 1 + (w[0] > dw) #% Number of zeros to add - if Nz == 2: - w = hstack((0, w[0] - dw, w)) - else: - w = hstack((0, w)) - - S1 = hstack((zeros(Nz), S1)) - - #% Do a final check on spacing in order to check that the gridding is - #% sufficiently dense: - #np1 = S1.size - dwMin = finfo(float).max - #%wnc = min(wnNew,wnOld-1e-5) - wnc = wnNew - #specfun = lambda xi : stineman_interp(xi, w, S1) - specfun = interpolate.interp1d(w, S1, kind='cubic') - x, unused_y = discretize(specfun, 0, wnc) - dwMin = minimum(min(diff(x)), dwMin) - - newNfft = 2 ** nextpow2(ceil(wnNew / dwMin)) + 1 - if newNfft > nfft: - #if (nfft <= 2 ** 15 + 1) and (newNfft > 2 ** 15 + 1): - # warnings.warn('Spectrum matrix is very large (>33k). Memory problems may occur.') - - nfft = newNfft - self.args = linspace(0, wnNew, nfft) - if method == 'stineman': - self.data = stineman_interp(self.args, w, S1) - else: - intfun = interpolate.interp1d(w, S1, kind=method) - self.data = intfun(self.args) - self.data = self.data.clip(0) # clip negative values to 0 - - def normalize(self, gravity=9.81): - ''' - Normalize a spectral density such that m0=m2=1 - - Paramter - -------- - gravity=9.81 - - Notes - ----- - Normalization performed such that - INT S(freq) dfreq = 1 INT freq^2 S(freq) dfreq = 1 - where integration limits are given by freq and S(freq) is the - spectral density; freq can be frequency or wave number. - The normalization is defined by - A=sqrt(m0/m2); B=1/A/m0; freq'=freq*A; S(freq')=S(freq)*B - - If S is a directional spectrum then a normalized gravity (.g) is added - to Sn, such that mxx normalizes to 1, as well as m0 and mtt. - (See spec2mom for notation of moments) - - If S is complex-valued cross spectral density which has to be - normalized, then m0, m2 (suitable spectral moments) should be given. - - Example: - ------- - >>> import wafo.spectrum.models as sm - >>> Sj = sm.Jonswap(Hm0=5) - >>> S = Sj.tospecdata() #Make spectrum ob - >>> S.moment(2) - ([1.5614600345079888, 0.95567089481941048], ['m0', 'm0tt']) - >>> Sn = S.copy(); Sn.normalize() - - Now the moments should be one - >>> Sn.moment(2) - ([1.0000000000000004, 0.99999999999999967], ['m0', 'm0tt']) - ''' - mom, unused_mtext = self.moment(nr=4, even=True) - m0 = mom[0] - m2 = mom[1] - m4 = mom[2] - - SM0 = sqrt(m0) - SM2 = sqrt(m2) - A = SM0 / SM2 - B = SM2 / (SM0 * m0) - - if self.freqtype == 'f': - self.args = self.args * A / 2 / pi - self.data = self.data * B * 2 * pi - elif self.freqtype == 'w' : - self.args = self.args * A - self.data = self.data * B - m02 = m4 / gravity ** 2 - m20 = m02 - self.g = gravity * sqrt(m0 * m20) / m2 - self.A = A - self.norm = True - self.date = now() - - def bandwidth(self, factors=0): - ''' - Return some spectral bandwidth and irregularity factors - - Parameters - ----------- - factors : array-like - Input vector 'factors' correspondence: - 0 alpha=m2/sqrt(m0*m4) (irregularity factor) - 1 eps2 = sqrt(m0*m2/m1^2-1) (narrowness factor) - 2 eps4 = sqrt(1-m2^2/(m0*m4))=sqrt(1-alpha^2) (broadness factor) - 3 Qp=(2/m0^2)int_0^inf f*S(f)^2 df (peakedness factor) - - Returns - -------- - bw : arraylike - vector of bandwidth factors - Order of output is the same as order in 'factors' - - Example: - >>> import numpy as np - >>> import wafo.spectrum.models as sm - >>> Sj = sm.Jonswap(Hm0=3, Tp=7) - >>> w = np.linspace(0,4,256) - >>> S = SpecData1D(Sj(w),w) #Make spectrum object from numerical values - >>> S.bandwidth([0,'eps2',2,3]) - array([ 0.73062845, 0.34476034, 0.68277527, 2.90817052]) - ''' - m, unused_mtxt = self.moment(nr=4, even=False) - - fact_dict = dict(alpha=0, eps2=1, eps4=3, qp=3, Qp=3) - fun = lambda fact: fact_dict.get(fact, fact) - fact = atleast_1d(map(fun, list(factors))) - - #fact = atleast_1d(fact) - alpha = m[2] / sqrt(m[0] * m[4]) - eps2 = sqrt(m[0] * m[2] / m[1] ** 2. - 1.) - eps4 = sqrt(1. - m[2] ** 2. / m[0] / m[4]) - f = self.args - S = self.data - Qp = 2 / m[0] ** 2. * simps(f * S ** 2, x=f) - bw = array([alpha, eps2, eps4, Qp]) - return bw[fact] - - def characteristic(self, fact='Hm0', T=1200, g=9.81): - """ - Returns spectral characteristics and their covariance - - Parameters - ---------- - fact : vector with factor integers or a string or a list of strings - defining spectral characteristic, see description below. - T : scalar - recording time (sec) (default 1200 sec = 20 min) - g : scalar - acceleration of gravity [m/s^2] - - Returns - ------- - ch : vector - of spectral characteristics - R : matrix - of the corresponding covariances given T - chtext : a list of strings - describing the elements of ch, see example. - - - Description - ------------ - If input spectrum is of wave number type, output are factors for - corresponding 'k1D', else output are factors for 'freq'. - Input vector 'factors' correspondence: - 1 Hm0 = 4*sqrt(m0) Significant wave height - 2 Tm01 = 2*pi*m0/m1 Mean wave period - 3 Tm02 = 2*pi*sqrt(m0/m2) Mean zero-crossing period - 4 Tm24 = 2*pi*sqrt(m2/m4) Mean period between maxima - 5 Tm_10 = 2*pi*m_1/m0 Energy period - 6 Tp = 2*pi/{w | max(S(w))} Peak period - 7 Ss = 2*pi*Hm0/(g*Tm02^2) Significant wave steepness - 8 Sp = 2*pi*Hm0/(g*Tp^2) Average wave steepness - 9 Ka = abs(int S(w)*exp(i*w*Tm02) dw ) /m0 Groupiness parameter - 10 Rs = (S(0.092)+S(0.12)+S(0.15)/(3*max(S(w))) Quality control parameter - 11 Tp1 = 2*pi*int S(w)^4 dw Peak Period (robust estimate for Tp) - ------------------ - int w*S(w)^4 dw - - 12 alpha = m2/sqrt(m0*m4) Irregularity factor - 13 eps2 = sqrt(m0*m2/m1^2-1) Narrowness factor - 14 eps4 = sqrt(1-m2^2/(m0*m4))=sqrt(1-alpha^2) Broadness factor - 15 Qp = (2/m0^2)int_0^inf w*S(w)^2 dw Peakedness factor - - Order of output is same as order in 'factors' - The covariances are computed with a Taylor expansion technique - and is currently only available for factors 1, 2, and 3. Variances - are also available for factors 4,5,7,12,13,14 and 15 - - Quality control: - ---------------- - Critical value for quality control parameter Rs is Rscrit = 0.02 - for surface displacement records and Rscrit=0.0001 for records of - surface acceleration or slope. If Rs > Rscrit then probably there - are something wrong with the lower frequency part of S. - - Ss may be used as an indicator of major malfunction, by checking that - it is in the range of 1/20 to 1/16 which is the usual range for - locally generated wind seas. - - Examples: - --------- - >>> import wafo.spectrum.models as sm - >>> Sj = sm.Jonswap(Hm0=5) - >>> S = Sj.tospecdata() #Make spectrum ob - >>> S.characteristic(1) - (array([ 8.59007646]), array([[ 0.03040216]]), ['Tm01']) - - >>> [ch, R, txt] = S.characteristic([1,2,3]) # fact a vector of integers - >>> S.characteristic('Ss') # fact a string - (array([ 0.04963112]), array([[ 2.63624782e-06]]), ['Ss']) - - >>> S.characteristic(['Hm0','Tm02']) # fact a list of strings - (array([ 4.99833578, 8.03139757]), array([[ 0.05292989, 0.02511371], - [ 0.02511371, 0.0274645 ]]), ['Hm0', 'Tm02']) - - See also - --------- - bandwidth, - moment - - References - ---------- - Krogstad, H.E., Wolf, J., Thompson, S.P., and Wyatt, L.R. (1999) - 'Methods for intercomparison of wave measurements' - Coastal Enginering, Vol. 37, pp. 235--257 - - Krogstad, H.E. (1982) - 'On the covariance of the periodogram' - Journal of time series analysis, Vol. 3, No. 3, pp. 195--207 - - Tucker, M.J. (1993) - 'Recommended standard for wave data sampling and near-real-time processing' - Ocean Engineering, Vol.20, No.5, pp. 459--474 - - Young, I.R. (1999) - "Wind generated ocean waves" - Elsevier Ocean Engineering Book Series, Vol. 2, pp 239 - """ - - #% TODO % Need more checking on computing the variances for Tm24,alpha, eps2 and eps4 - #% TODO % Covariances between Tm24,alpha, eps2 and eps4 variables are also needed - - tfact = dict(Hm0=0, Tm01=1, Tm02=2, Tm24=3, Tm_10=4, Tp=5, Ss=6, Sp=7, Ka=8, - Rs=9, Tp1=10, Alpha=11, Eps2=12, Eps4=13, Qp=14) - tfact1 = ('Hm0', 'Tm01', 'Tm02', 'Tm24', 'Tm_10', 'Tp', 'Ss', 'Sp', 'Ka', - 'Rs', 'Tp1', 'Alpha', 'Eps2', 'Eps4', 'Qp') - - if isinstance(fact, str): - fact = list((fact,)) - if isinstance(fact, (list, tuple)): - nfact = [] - for k in fact: - if isinstance(k, str): - nfact.append(tfact.get(k.capitalize(), 15)) - else: - nfact.append(k) - else: - nfact = fact - - nfact = atleast_1d(nfact) - - if any((nfact > 14) | (nfact < 0)): - raise ValueError('Factor outside range (0,...,14)') - - #vari = self.freqtype - - f = self.args.ravel() - S1 = self.data.ravel() - m, unused_mtxt = self.moment(nr=4, even=False) - - #% moments corresponding to freq in Hz - for k in range(1, 5): - m[k] = m[k] / (2 * pi) ** k - - #pi = np.pi - ind = flatnonzero(f > 0) - m.append(simps(S1[ind] / f[ind], f[ind]) * 2. * pi) # % = m_1 - m_10 = simps(S1[ind] ** 2 / f[ind], f[ind]) * (2 * pi) ** 2 / T # % = COV(m_1,m0|T=t0) - m_11 = simps(S1[ind] ** 2. / f[ind] ** 2, f[ind]) * (2 * pi) ** 3 / T #% = COV(m_1,m_1|T=t0) - - #sqrt = np.sqrt - #% Hm0 Tm01 Tm02 Tm24 Tm_10 - Hm0 = 4. * sqrt(m[0]) - Tm01 = m[0] / m[1] - Tm02 = sqrt(m[0] / m[2]) - Tm24 = sqrt(m[2] / m[4]) - Tm_10 = m[5] / m[0] - - Tm12 = m[1] / m[2] - - ind = S1.argmax() - maxS = S1[ind] - #[maxS ind] = max(S1) - Tp = 2. * pi / f[ind] # % peak period /length - Ss = 2. * pi * Hm0 / g / Tm02 ** 2 # % Significant wave steepness - Sp = 2. * pi * Hm0 / g / Tp ** 2 # % Average wave steepness - Ka = abs(simps(S1 * exp(1J * f * Tm02), f)) / m[0] #% groupiness factor - - #% Quality control parameter - #% critical value is approximately 0.02 for surface displacement records - #% If Rs>0.02 then there are something wrong with the lower frequency part - #% of S. - Rs = np.sum(interp(r_[0.0146, 0.0195, 0.0244] * 2 * pi, f, S1)) / 3. / maxS - Tp2 = 2 * pi * simps(S1 ** 4, f) / simps(f * S1 ** 4, f) - - - alpha1 = Tm24 / Tm02 # % m(3)/sqrt(m(1)*m(5)) - eps2 = sqrt(Tm01 / Tm12 - 1.)# % sqrt(m(1)*m(3)/m(2)^2-1) - eps4 = sqrt(1. - alpha1 ** 2) # % sqrt(1-m(3)^2/m(1)/m(5)) - Qp = 2. / m[0] ** 2 * simps(f * S1 ** 2, f) - - ch = r_[Hm0, Tm01, Tm02, Tm24, Tm_10, Tp, Ss, Sp, Ka, Rs, Tp2, alpha1, eps2, eps4, Qp] - - #% Select the appropriate values - ch = ch[nfact] - chtxt = [tfact1[i] for i in nfact] - - #if nargout>1, - #% covariance between the moments: - #%COV(mi,mj |T=t0) = int f^(i+j)*S(f)^2 df/T - mij, unused_mijtxt = self.moment(nr=8, even=False, j=1) - for ix, tmp in enumerate(mij): - mij[ix] = tmp / T / ((2. * pi) ** (ix - 1.0)) - - - #% and the corresponding variances for - #%{'hm0', 'tm01', 'tm02', 'tm24', 'tm_10','tp','ss', 'sp', 'ka', 'rs', 'tp1','alpha','eps2','eps4','qp'} - R = r_[4 * mij[0] / m[0], - mij[0] / m[1] ** 2. - 2. * m[0] * mij[1] / m[1] ** 3. + m[0] ** 2. * mij[2] / m[1] ** 4., - 0.25 * (mij[0] / (m[0] * m[2]) - 2. * mij[2] / m[2] ** 2 + m[0] * mij[4] / m[2] ** 3), - 0.25 * (mij[4] / (m[2] * m[4]) - 2 * mij[6] / m[4] ** 2 + m[2] * mij[8] / m[4] ** 3) , - m_11 / m[0] ** 2 + (m[5] / m[0] ** 2) ** 2 * mij[0] - 2 * m[5] / m[0] ** 3 * m_10, - nan, - (8 * pi / g) ** 2 * (m[2] ** 2 / (4 * m[0] ** 3) * mij[0] + mij[4] / m[0] - m[2] / m[0] ** 2 * mij[2]), - nan * ones(4), - m[2] ** 2 * mij[0] / (4 * m[0] ** 3 * m[4]) + mij[4] / (m[0] * m[4]) + mij[8] * m[2] ** 2 / (4 * m[0] * m[4] ** 3) - - m[2] * mij[2] / (m[0] ** 2 * m[4]) + m[2] ** 2 * mij[4] / (2 * m[0] ** 2 * m[4] ** 2) - m[2] * mij[6] / m[0] / m[4] ** 2, - (m[2] ** 2 * mij[0] / 4 + (m[0] * m[2] / m[1]) ** 2 * mij[2] + m[0] ** 2 * mij[4] / 4 - m[2] ** 2 * m[0] * mij[1] / m[1] + - m[0] * m[2] * mij[2] / 2 - m[0] ** 2 * m[2] / m[1] * mij[3]) / eps2 ** 2 / m[1] ** 4, - (m[2] ** 2 * mij[0] / (4 * m[0] ** 2) + mij[4] + m[2] ** 2 * mij[8] / (4 * m[4] ** 2) - m[2] * mij[2] / m[0] + - m[2] ** 2 * mij[4] / (2 * m[0] * m[4]) - m[2] * mij[6] / m[4]) * m[2] ** 2 / (m[0] * m[4] * eps4) ** 2, - nan] - - #% and covariances by a taylor expansion technique: - #% Cov(Hm0,Tm01) Cov(Hm0,Tm02) Cov(Tm01,Tm02) - S0 = r_[ 2. / (sqrt(m[0]) * m[1]) * (mij[0] - m[0] * mij[1] / m[1]), - 1. / sqrt(m[2]) * (mij[0] / m[0] - mij[2] / m[2]), - 1. / (2 * m[1]) * sqrt(m[0] / m[2]) * (mij[0] / m[0] - mij[2] / m[2] - mij[1] / m[1] + m[0] * mij[3] / (m[1] * m[2]))] - - R1 = ones((15, 15)) - R1[:, :] = nan - for ix, Ri in enumerate(R): - R1[ix, ix] = Ri - - - - R1[0, 2:4] = S0[:2] - R1[1, 2] = S0[2] - for ix in [0, 1]: #%make lower triangular equal to upper triangular part - R1[ix + 1:, ix] = R1[ix, ix + 1:] - - - R = R[nfact] - R1 = R1[nfact, :][:, nfact] - - - #% Needs further checking: - #% Var(Tm24)= 0.25*(mij[4]/(m[2]*m[4])-2*mij[6]/m[4]**2+m[2]*mij[8]/m[4]**3) ... - return ch, R1, chtxt - - def setlabels(self): - ''' Set automatic title, x-,y- and z- labels on SPECDATA object - - based on type, angletype, freqtype - ''' - - N = len(self.type) - if N == 0: - raise ValueError('Object does not appear to be initialized, it is empty!') - - labels = ['', '', ''] - if self.type.endswith('dir'): - title = 'Directional Spectrum' - if self.freqtype.startswith('w'): - labels[0] = 'Frequency [rad/s]' - labels[2] = r'S($\omega$,$\theta$) $[m^2 s / rad^2]$' - else: - labels[0] = 'Frequency [Hz]' - labels[2] = r'S(f,$\theta$) $[m^2 s / rad]$' - - if self.angletype.startswith('r'): - labels[1] = 'Wave directions [rad]' - elif self.angletype.startswith('d'): - labels[1] = 'Wave directions [deg]' - elif self.type.endswith('freq'): - title = 'Spectral density' - if self.freqtype.startswith('w'): - labels[0] = 'Frequency [rad/s]' - labels[1] = r'S($\omega$) $[m^2 s/ rad]$' - else: - labels[0] = 'Frequency [Hz]' - labels[1] = r'S(f) $[m^2 s]$' - else: - title = 'Wave Number Spectrum' - labels[0] = 'Wave number [rad/m]' - if self.type.endswith('k1d'): - labels[1] = r'S(k) $[m^3/ rad]$' - elif self.type.endswith('k2d'): - labels[1] = labels[0] - labels[2] = r'S(k1,k2) $[m^4/ rad^2]$' - else: - raise ValueError('Object does not appear to be initialized, it is empty!') - if self.norm != 0: - title = 'Normalized ' + title - labels[0] = 'Normalized ' + labels[0].split('[')[0] - if not self.type.endswith('dir'): - labels[1] = labels[1].split('[')[0] - labels[2] = labels[2].split('[')[0] - - self.labels.title = title - self.labels.xlab = labels[0] - self.labels.ylab = labels[1] - self.labels.zlab = labels[2] - -class SpecData2D(PlotData): - """ Container class for 2D spectrum data objects in WAFO - - Member variables - ---------------- - data : array_like - args : vector for 1D, list of vectors for 2D, 3D, ... - - type : string - spectrum type (default 'freq') - freqtype : letter - frequency type (default 'w') - angletype : string - angle type of directional spectrum (default 'radians') - - Examples - -------- - >>> import numpy as np - >>> import wafo.spectrum.models as sm - >>> Sj = sm.Jonswap(Hm0=3, Tp=7) - >>> w = np.linspace(0,4,256) - >>> S = SpecData1D(Sj(w),w) #Make spectrum object from numerical values - - See also - -------- - PlotData - CovData - """ - - def __init__(self, *args, **kwds): - - - super(SpecData2D, self).__init__(*args, **kwds) - - self.name = 'WAFO Spectrum Object' - self.type = 'dir' - self.freqtype = 'w' - self.angletype = '' - self.h = inf - self.tr = None - self.phi = 0. - self.v = 0. - self.norm = 0 - somekeys = ['angletype', 'phi', 'name', 'h', 'tr', 'freqtype', 'v', 'type', 'norm'] - - self.__dict__.update(sub_dict_select(kwds, somekeys)) - - if self.type.endswith('dir') and self.angletype == '': - self.angletype = 'radians' - - self.setlabels() - - def toacf(self): - pass - def tospecdata(self, type=None): - pass - def sim(self): - pass - def sim_nl(self): - pass - def rotate(self, phi=0, rotateGrid=False, method='linear'): - ''' - Rotate spectrum clockwise around the origin. - - Parameters - ----------- - phi = rotation angle (default 0) - rotateGrid = 1 if rotate grid of Snew physically (thus Snew.phi=0). - 0 if rotate so that only Snew.phi is changed - (the grid is not physically rotated) (default) - method = interpolation method to use when ROTATEGRID==1, - (default 'linear') - - Rotates the spectrum clockwise around the origin. - This equals a anti-clockwise rotation of the cordinate system (x,y). - The spectrum can be of any of the two-dimensional types. - For spectrum in polar representation: - newtheta = theta-phi, but circulant such that -pi>> import wafo.spectrum.models as sm - >>> D = sm.Spreading() - >>> SD = D.tospecdata2d(sm.Jonswap().tospecdata(),nt=101) - >>> m,mtext = SD.moment(nr=2,vari='xyt') - >>> np.round(m,3),mtext - (array([ 3.061, 0.132, -0. , 2.13 , 0.011, 0.008, 1.677, -0. , - 0.109, 0.109]), ['m0', 'mx', 'my', 'mt', 'mxx', 'myy', 'mtt', 'mxy', 'mxt', 'myt']) - - References - ---------- - Baxevani A. et al. (2001) - Velocities for Random Surfaces - ''' - - - two_dim_spectra = ['dir', 'encdir', 'k2d'] - if self.type not in two_dim_spectra: - raise ValueError('Unknown 2D spectrum type!') - - if vari == None and nr <= 1: - vari = 'x' - elif vari == None: - vari = 'xt' - else: #% secure the mutual order ('xyt') - vari = ''.join(sorted(vari.lower())) - Nv = len(vari) - - if vari[0] == 't' and Nv > 1: - vari = vari[1::] + vari[0] - - Nv = len(vari) - - if not self.type.endswith('dir'): - S1 = self.tospecdata(self.type[:-2] + 'dir') - else: - S1 = self - w = ravel(S1.args[0]) - theta = S1.args[1] - S1.phi - S = S1.data - Sw = simps(S, x=theta, axis=0) - m = [simps(Sw, x=w)] - mtext = ['m0'] - - if nr > 0: - vec = [] - g = np.atleast_1d(S1.__dict__.get('g', gravity())) - kx = w ** 2 / g[0] # maybe different normalization in x and y => diff. g - ky = w ** 2 / g[-1] - - nw = w.size - - if 'x' in vari: - ct = np.cos(theta[:, None]) - Sc = simps(S * ct, x=theta, axis=0) - vec.append(kx * Sc) - mtext.append('mx') - if 'y' in vari: - st = np.sin(theta[:, None]) - Ss = simps(S * st, x=theta, axis=0) - vec.append(ky * Ss) - mtext.append('my') - if 't' in vari: - vec.append(w * Sw) - mtext.append('mt') - - if nr > 1: - if 'x' in vari: - Sc2 = simps(S * ct ** 2, x=theta, axis=0) - vec.append(kx ** 2 * Sc2) - mtext.append('mxx') - if 'y' in vari: - Ss2 = simps(S * st ** 2, x=theta, axis=0) - vec.append(ky ** 2 * Ss2) - mtext.append('myy') - if 't' in vari: - vec.append(w ** 2 * Sw) - mtext.append('mtt') - if 'x' in vari and 'y' in vari: - Scs = simps(S * ct * st, x=theta, axis=0) - vec.append(kx * ky * Scs) - mtext.append('mxy') - if 'x' in vari and 't' in vari: - vec.append(kx * w * Sc) - mtext.append('mxt') - if 'y' in vari and 't' in vari: - vec.append(ky * w * Sc) - mtext.append('myt') - - if nr > 3: - if 'x' in vari: - Sc3 = simps(S * ct ** 3, x=theta, axis=0) - Sc4 = simps(S * ct ** 4, x=theta, axis=0) - vec.append(kx ** 4 * Sc4) - mtext.append('mxxxx') - if 'y' in vari: - Ss3 = simps(S * st ** 3, x=theta, axis=0) - Ss4 = simps(S * st ** 4, x=theta, axis=0) - vec.append(ky ** 4 * Ss4) - mtext.append('myyyy') - if 't' in vari: - vec.append(w ** 4 * Sw) - mtext.append('mtttt') - - if 'x' in vari and 'y' in vari: - Sc2s = simps(S * ct ** 2 * st, x=theta, axis=0) - Sc3s = simps(S * ct ** 3 * st, x=theta, axis=0) - Scs2 = simps(S * ct * st ** 2, x=theta, axis=0) - Scs3 = simps(S * ct * st ** 3, x=theta, axis=0) - Sc2s2 = simps(S * ct ** 2 * st ** 2, x=theta, axis=0) - vec.extend((kx ** 3 * ky * Sc3s, kx ** 2 * ky ** 2 * Sc2s2, kx * ky ** 3 * Scs3)) - mtext.extend(('mxxxy', 'mxxyy', 'mxyyy')) - if 'x' in vari and 't' in vari: - vec.extend((kx ** 3 * w * Sc3, kx ** 2 * w ** 2 * Sc2, kx * w ** 3 * Sc)) - mtext.extend(('mxxxt', 'mxxtt', 'mxttt')) - if 'y' in vari and 't' in vari: - vec.extend((ky ** 3 * w * Ss3, ky ** 2 * w ** 2 * Ss2, ky * w ** 3 * Ss)) - mtext.extend(('myyyt', 'myytt', 'myttt')) - if 'x' in vari and 'y' in vari and 't' in vari: - vec.extend((kx ** 2 * ky * w * Sc2s, kx * ky ** 2 * w * Scs2, kx * ky * w ** 2 * Scs)) - mtext.extend(('mxxyt', 'mxyyt', 'mxytt')) - #end % if nr>1 - m.extend([simps(vals, x=w) for vals in vec]) - return np.asarray(m), mtext - - - - def interp(self): - pass - def normalize(self): - pass - def bandwidth(self): - pass - def setlabels(self): - ''' Set automatic title, x-,y- and z- labels on SPECDATA object - - based on type, angletype, freqtype - ''' - - N = len(self.type) - if N == 0: - raise ValueError('Object does not appear to be initialized, it is empty!') - - labels = ['', '', ''] - if self.type.endswith('dir'): - title = 'Directional Spectrum' - if self.freqtype.startswith('w'): - labels[0] = 'Frequency [rad/s]' - labels[2] = r'$S(w,\theta) [m**2 s / rad**2]$' - else: - labels[0] = 'Frequency [Hz]' - labels[2] = r'$S(f,\theta) [m**2 s / rad]$' - - if self.angletype.startswith('r'): - labels[1] = 'Wave directions [rad]' - elif self.angletype.startswith('d'): - labels[1] = 'Wave directions [deg]' - elif self.type.endswith('freq'): - title = 'Spectral density' - if self.freqtype.startswith('w'): - labels[0] = 'Frequency [rad/s]' - labels[1] = 'S(w) [m**2 s/ rad]' - else: - labels[0] = 'Frequency [Hz]' - labels[1] = 'S(f) [m**2 s]' - else: - title = 'Wave Number Spectrum' - labels[0] = 'Wave number [rad/m]' - if self.type.endswith('k1d'): - labels[1] = 'S(k) [m**3/ rad]' - elif self.type.endswith('k2d'): - labels[1] = labels[0] - labels[2] = 'S(k1,k2) [m**4/ rad**2]' - else: - raise ValueError('Object does not appear to be initialized, it is empty!') - if self.norm != 0: - title = 'Normalized ' + title - labels[0] = 'Normalized ' + labels[0].split('[')[0] - if not self.type.endswith('dir'): - labels[1] = labels[1].split('[')[0] - labels[2] = labels[2].split('[')[0] - - self.labels.title = title - self.labels.xlab = labels[0] - self.labels.ylab = labels[1] - self.labels.zlab = labels[2] - -def _test_specdata(): - import wafo.spectrum.models as sm - Sj = sm.Jonswap() - S = Sj.tospecdata() - me, va, sk, ku = S.stats_nl(moments='mvsk') - -def main(): - import matplotlib - matplotlib.interactive(True) - from wafo.spectrum import models as sm - - w = linspace(0, 3, 100) - Sj = sm.Jonswap() - S = Sj.tospecdata() - - f = S.to_t_pdf(pdef='Tc', paramt=(0, 10, 51), speed=7) - f.err - f.plot() - f.show() - #pdfplot(f) - #hold on, - #plot(f.x{:}, f.f+f.err,'r',f.x{:}, f.f-f.err) estimated error bounds - #hold off - #S = SpecData1D(Sj(w),w) - R = S.tocovdata(nr=1) - S1 = S.copy() - Si = R.tospecdata() - ns = 5000 - dt = .2 - x1 = S.sim_nl(ns=ns, dt=dt) - x2 = TimeSeries(x1[:, 1], x1[:, 0]) - R = x2.tocovdata(lag=100) - R.plot() - - S.plot('ro') - t = S.moment() - t1 = S.bandwidth([0, 1, 2, 3]) - S1 = S.copy() - S1.resample(dt=0.3, method='cubic') - S1.plot('k+') - x = S1.sim(ns=100) - import pylab - pylab.clf() - pylab.plot(x[:, 0], x[:, 1]) - pylab.show() - - pylab.close('all') - print('done') - -def test_mm_pdf(): - - import wafo.spectrum.models as sm - Sj = sm.Jonswap(Hm0=7, Tp=11) - w = np.linspace(0, 4, 256) - S1 = Sj.tospecdata(w) #Make spectrum object from numerical values - S = sm.SpecData1D(Sj(w), w) # Alternatively do it manually - mm = S.to_mm_pdf() - -def test_docstrings(): - import doctest - doctest.testmod() - - -if __name__ == '__main__': - test_docstrings() - #test_mm_pdf() - #main() +from __future__ import division +from wafo.misc import meshgrid, gravity, cart2polar, polar2cart +from wafo.objects import TimeSeries # mat2timeseries, +import warnings +import os +import numpy as np +from numpy import (pi, inf, zeros, ones, where, nonzero, + flatnonzero, ceil, sqrt, exp, log, arctan2, + tanh, cosh, sinh, random, atleast_1d, + minimum, diff, isnan, any, r_, conj, mod, + hstack, vstack, interp, ravel, finfo, linspace, + arange, array, nan, newaxis, sign) +from numpy.fft import fft +from scipy.integrate import simps, trapz +from scipy.special import erf +from scipy.linalg import toeplitz +import scipy.interpolate as interpolate +from wafo.interpolate import stineman_interp + +from wafo.wave_theory.dispersion_relation import w2k # , k2w +from wafo.containers import PlotData, now +# , tranproc +from wafo.misc import sub_dict_select, nextpow2, discretize, JITImport +# from wafo.graphutil import cltext +from wafo.kdetools import qlevels + +try: + from wafo.gaussian import Rind +except ImportError: + Rind = None +try: + from wafo import c_library +except ImportError: + warnings.warn('Compile the c_library.pyd again!') + c_library = None +try: + from wafo import cov2mod +except ImportError: + warnings.warn('Compile the cov2mod.pyd again!') + cov2mod = None + + +# from wafo.transform import TrData +from wafo.transform.models import TrLinear +from wafo.plotbackend import plotbackend + + +# Trick to avoid error due to circular import + +_WAFOCOV = JITImport('wafo.covariance') + + +__all__ = ['SpecData1D', 'SpecData2D', 'plotspec'] + + +def _set_seed(iseed): + '''Set seed of random generator''' + if iseed != None: + try: + random.set_state(iseed) + except: + random.seed(iseed) + + +def qtf(w, h=inf, g=9.81): + """ + Return Quadratic Transfer Function + + Parameters + ------------ + w : array-like + angular frequencies + h : scalar + water depth + g : scalar + acceleration of gravity + + Returns + ------- + h_s = sum frequency effects + h_d = difference frequency effects + h_dii = diagonal of h_d + """ + w = atleast_1d(w) + num_w = w.size + + k_w = w2k(w, theta=0, h=h, g=g)[0] + + k_1, k_2 = meshgrid(k_w, k_w) + + if h == inf: # go here for faster calculations + h_s = 0.25 * (abs(k_1) + abs(k_2)) + h_d = -0.25 * abs(abs(k_1) - abs(k_2)) + h_dii = zeros(num_w) + return h_s, h_d, h_dii + + [w_1, w_2] = meshgrid(w, w) + + w12 = (w_1 * w_2) + w1p2 = (w_1 + w_2) + w1m2 = (w_1 - w_2) + k12 = (k_1 * k_2) + k1p2 = (k_1 + k_2) + k1m2 = abs(k_1 - k_2) + + if 0: # Langley + p_1 = (-2 * w1p2 * (k12 * g ** 2. - w12 ** 2.) + + w_1 * (w_2 ** 4. - g ** 2 * k_2 ** 2) + + w_2 * (w_1 ** 4 - g * 2. * k_1 ** 2)) / (4. * w12) + p_2 = w1p2 ** 2. * cosh((k1p2) * h) - g * (k1p2) * sinh((k1p2) * h) + + h_s = (-p_1 / p_2 * w1p2 * cosh((k1p2) * h) / g - + (k12 * g ** 2 - w12 ** 2.) / (4 * g * w12) + + (w_1 ** 2 + w_2 ** 2) / (4 * g)) + + p_3 = (-2 * w1m2 * (k12 * g ** 2 + w12 ** 2) - + w_1 * (w_2 ** 4 - g ** 2 * k_2 ** 2) + + w_2 * (w_1 ** 4 - g ** 2 * k_1 ** 2)) / (4. * w12) + p_4 = w1m2 ** 2. * cosh(k1m2 * h) - g * (k1m2) * sinh((k1m2) * h) + + h_d = (-p_3 / p_4 * (w1m2) * cosh((k1m2) * h) / g - + (k12 * g ** 2 + w12 ** 2) / (4 * g * w12) + + (w_1 ** 2. + w_2 ** 2.) / (4. * g)) + + else: # Marthinsen & Winterstein + tmp1 = 0.5 * g * k12 / w12 + tmp2 = 0.25 / g * (w_1 ** 2. + w_2 ** 2. + w12) + h_s = (tmp1 - tmp2 + 0.25 * g * (w_1 * k_2 ** 2. + w_2 * k_1 ** 2) / + (w12 * (w1p2))) / (1. - g * (k1p2) / (w1p2) ** 2. * + tanh((k1p2) * h)) + tmp2 - 0.5 * tmp1 # OK + + tmp2 = 0.25 / g * (w_1 ** 2 + w_2 ** 2 - w12) # OK + h_d = (tmp1 - tmp2 - 0.25 * g * (w_1 * k_2 ** 2 - w_2 * k_1 ** 2) / + (w12 * (w1m2))) / (1. - g * (k1m2) / (w1m2) ** 2. * + tanh((k1m2) * h)) + tmp2 - 0.5 * tmp1 # OK + + # tmp1 = 0.5*g*k_w./(w.*sqrt(g*h)) + # tmp2 = 0.25*w.^2/g +# Wave group velocity + c_g = 0.5 * g * (tanh(k_w * h) + k_w * h * (1.0 - tanh(k_w * h) ** 2)) / w + h_dii = (0.5 * (0.5 * g * (k_w / w) ** 2. - 0.5 * w ** 2 / g + + g * k_w / (w * c_g)) + / (1. - g * h / c_g ** 2.) - 0.5 * k_w / sinh(2 * k_w * h)) # OK + h_d.flat[0::num_w + 1] = h_dii + + # k = find(w_1==w_2) + # h_d(k) = h_dii + + #% The NaN's occur due to division by zero. => Set the isnans to zero + + h_dii = where(isnan(h_dii), 0, h_dii) + h_d = where(isnan(h_d), 0, h_d) + h_s = where(isnan(h_s), 0, h_s) + + return h_s, h_d, h_dii + + +def plotspec(specdata, linetype='b-', flag=1): + pass +# ''' +# PLOTSPEC Plot a spectral density +# +# Parameters +# ---------- +# S : SpecData1D or SpecData2D object +# defining spectral density. +# linetype : string +# defining color and linetype, see plot for possibilities +# flag : scalar integer +# defining the type of plot +# 1D: +# 1 plots the density, S, (default) +# 2 plot 10log10(S) +# 3 plots both the above plots +# 2D: +# Directional spectra: S(w,theta), S(f,theta) +# 1 polar plot S (default) +# 2 plots spectral density and the directional +# spreading, int S(w,theta) dw or int S(f,theta) df +# 3 plots spectral density and the directional +# spreading, int S(w,theta)/S(w) dw or int S(f,theta)/S(f) df +# 4 mesh of S +# 5 mesh of S in polar coordinates +# 6 contour plot of S +# 7 filled contour plot of S +# Wavenumber spectra: S(k1,k2) +# 1 contour plot of S (default) +# 2 filled contour plot of S +# +# Example +# ------- +# >>> import numpy as np +# >>> import wafo.spectrum as ws +# >>> Sj = ws.models.Jonswap(Hm0=3, Tp=7) +# >>> S = Sj.tospecdata() +# >>> ws.plotspec(S,flag=1) +# +# S = demospec('dir'); S2 = mkdspec(jonswap,spreading); +# plotspec(S,2), hold on +# plotspec(S,3,'g') % Same as previous fig. due to frequency independent spreading +# plotspec(S2,2,'r') % Not the same as previous figs. due to frequency dependent spreading +# plotspec(S2,3,'m') +# % transform from angular frequency and radians to frequency and degrees +# Sf = ttspec(S,'f','d'); clf +# plotspec(Sf,2), +# +# See also dat2spec, createspec, simpson +# ''' +# +# label the contour levels +# txtFlag = 0 +# LegendOn = 1 +# +# +# ftype = specdata.freqtype #options are 'f' and 'w' and 'k' +# data = specdata.data +# if data.ndim == 2: +# freq = specdata.args[1] +# theta = specdata.args[0] +# else: +# freq = specdata.args +# if isinstance(specdata.args, (list, tuple)): +# +# if ftype == 'w': +# xlbl_txt = 'Frequency [rad/s]' +# ylbl1_txt = 'S(w) [m^2 s / rad]' +# ylbl3_txt = 'Directional Spectrum' +# zlbl_txt = 'S(w,\theta) [m^2 s / rad^2]' +# funit = ' [rad/s]' +# Sunit = ' [m^2 s / rad]' +# elif ftype == 'f': +# xlbl_txt = 'Frequency [Hz]' +# ylbl1_txt = 'S(f) [m^2 s]' +# ylbl3_txt = 'Directional Spectrum' +# zlbl_txt = 'S(f,\theta) [m^2 s / rad]' +# funit = ' [Hz]' +# Sunit = ' [m^2 s ]' +# elif ftype == 'k': +# xlbl_txt = 'Wave number [rad/m]' +# ylbl1_txt = 'S(k) [m^3/ rad]' +# funit = ' [rad/m]' +# Sunit = ' [m^3 / rad]' +# ylbl4_txt = 'Wave Number Spectrum' +# +# else: +# raise ValueError('Frequency type unknown') +# +# +# if hasattr(specdata, 'norm') and specdata.norm : +# Sunit=[] +# funit = [] +# ylbl1_txt = 'Normalized Spectral density' +# ylbl3_txt = 'Normalized Directional Spectrum' +# ylbl4_txt = 'Normalized Wave Number Spectrum' +# if ftype == 'k': +# xlbl_txt = 'Normalized Wave number' +# else: +# xlbl_txt = 'Normalized Frequency' +# +# ylbl2_txt = 'Power spectrum (dB)' +# +# phi = specdata.phi +# +# spectype = specdata.type.lower() +# stype = spectype[-3::] +# if stype in ('enc', 'req', 'k1d') : #1D plot +# Fn = freq[-1] # Nyquist frequency +# indm = findpeaks(data, n=4) +# maxS = data.max() +# if isfield(S,'CI') && ~isempty(S.CI), +# maxS = maxS*S.CI(2) +# txtCI = [num2str(100*S.p), '% CI'] +# end +# +# Fp = freq[indm]# %peak frequency/wave number +# +# if len(indm) == 1: +# txt = [('fp = %0.2g' % Fp) + funit] +# else: +# txt = [] +# for i, fp in enumerate(Fp.tolist()): +# txt.append(('fp%d = %0.2g' % (i, fp)) + funit) +# +# txt = ''.join(txt) +# if (flag == 3): +# plotbackend.subplot(2, 1, 1) +# if (flag == 1) or (flag == 3):#% Plot in normal scale +# plotbackend.plot(np.vstack([Fp, Fp]), +# np.vstack([zeros(len(indm)), data.take(indm)]), +# ':', label=txt) +# plotbackend.plot(freq, data, linetype) +# specdata.labels.labelfig() +# if isfield(S,'CI'), +# plot(freq,S.S*S.CI(1), 'r:' ) +# plot(freq,S.S*S.CI(2), 'r:' ) +# +# a = plotbackend.axis() +# +# a1 = Fn +# if (Fp > 0): +# a1 = max(min(Fn, 10 * max(Fp)), a[1]) +# +# plotbackend.axis([0, a1 , 0, max(1.01 * maxS, a[3])]) +# plotbackend.title('Spectral density') +# plotbackend.xlabel(xlbl_txt) +# plotbackend.ylabel(ylbl1_txt) +# +# +# if (flag == 3): +# plotbackend.subplot(2, 1, 2) +# +# if (flag == 2) or (flag == 3) : # Plot in logaritmic scale +# ind = np.flatnonzero(data > 0) +# +# plotbackend.plot(np.vstack([Fp, Fp]), +# np.vstack((min(10 * log10(data.take(ind) / maxS)).repeat(len(Fp)), +# 10 * log10(data.take(indm) / maxS))), ':',label=txt) +# hold on +# if isfield(S,'CI'), +# plot(freq(ind),10*log10(S.S(ind)*S.CI(1)/maxS), 'r:' ) +# plot(freq(ind),10*log10(S.S(ind)*S.CI(2)/maxS), 'r:' ) +# end +# plotbackend.plot(freq[ind], 10 * log10(data[ind] / maxS), linetype) +# +# a = plotbackend.axis() +# +# a1 = Fn +# if (Fp > 0): +# a1 = max(min(Fn, 10 * max(Fp)), a[1]) +# +# plotbackend.axis([0, a1 , -20, max(1.01 * 10 * log10(1), a[3])]) +# +# specdata.labels.labelfig() +# plotbackend.title('Spectral density') +# plotbackend.xlabel(xlbl_txt) +# plotbackend.ylabel(ylbl2_txt) +# +# if LegendOn: +# plotbackend.legend() +# if isfield(S,'CI'), +# legend(txt{:},txtCI,1) +# else +# legend(txt{:},1) +# end +# end +# case {'k2d'} +# if plotflag==1, +# [c, h] = contour(freq,S.k2,S.S,'b') +# z_level = clevels(c) +# +# +# if txtFlag==1 +# textstart_x=0.05; textstart_y=0.94 +# cltext1(z_level,textstart_x,textstart_y) +# else +# cltext(z_level,0) +# end +# else +# [c,h] = contourf(freq,S.k2,S.S) +# %clabel(c,h), colorbar(c,h) +# fcolorbar(c) % alternative +# end +# rotate(h,[0 0 1],-phi*180/pi) +# +# +# +# xlabel(xlbl_txt) +# ylabel(xlbl_txt) +# title(ylbl4_txt) +# %return +# km=max([-freq(1) freq(end) S.k2(1) -S.k2(end)]) +# axis([-km km -km km]) +# hold on +# plot([0 0],[ -km km],':') +# plot([-km km],[0 0],':') +# axis('square') +# +# +# %cltext(z_level) +# %axis('square') +# if ~ih, hold off,end +# case {'dir'} +# thmin = S.theta(1)-phi;thmax=S.theta(end)-phi +# if plotflag==1 % polar plot +# if 0, % alternative but then z_level must be chosen beforehand +# h = polar([0 2*pi],[0 freq(end)]) +# delete(h);hold on +# [X,Y]=meshgrid(S.theta,freq) +# [X,Y]=polar2cart(X,Y) +# contour(X,Y,S.S',lintype) +# else +# if (abs(thmax-thmin)<3*pi), % angle given in radians +# theta = S.theta +# else +# theta = S.theta*pi/180 % convert to radians +# phi = phi*pi/180 +# end +# c = contours(theta,freq,S.S')%,Nlevel) % calculate levels +# if isempty(c) +# c = contours(theta,freq,S.S)%,Nlevel); % calculate levels +# end +# [z_level c] = clevels(c); % find contour levels +# h = polar(c(1,:),c(2,:),lintype); +# rotate(h,[0 0 1],-phi*180/pi) +# end +# title(ylbl3_txt) +# % label the contour levels +# +# if txtFlag==1 +# textstart_x = -0.1; textstart_y=1.00; +# cltext1(z_level,textstart_x,textstart_y); +# else +# cltext(z_level,0) +# end +# +# elseif (plotflag==2) || (plotflag==3), +# %ih = ishold; +# +# subplot(211) +# +# if ih, hold on, end +# +# Sf = spec2spec(S,'freq'); % frequency spectrum +# plotspec(Sf,1,lintype) +# +# subplot(212) +# +# Dtf = S.S; +# [Nt,Nf] = size(S.S); +# Sf = Sf.S(:).'; +# ind = find(Sf); +# +# if plotflag==3, %Directional distribution D(theta,freq)) +# Dtf(:,ind) = Dtf(:,ind)./Sf(ones(Nt,1),ind); +# end +# Dtheta = simpson(freq,Dtf,2); %Directional spreading, D(theta) +# Dtheta = Dtheta/simpson(S.theta,Dtheta); % make sure int D(theta)dtheta = 1 +# [y,ind] = max(Dtheta); +# Wdir = S.theta(ind)-phi; % main wave direction +# txtwdir = ['\theta_p=' num2pistr(Wdir,3)]; % convert to text string +# +# plot([1 1]*S.theta(ind)-phi,[0 Dtheta(ind)],':'), hold on +# if LegendOn +# lh=legend(txtwdir,0); +# end +# plot(S.theta-phi,Dtheta,lintype) +# +# fixthetalabels(thmin,thmax,'x',2) % fix xticklabel and xlabel for theta +# ylabel('D(\theta)') +# title('Spreading function') +# if ~ih, hold off, end +# %legend(lh) % refresh current legend +# elseif plotflag==4 % mesh +# mesh(freq,S.theta-phi,S.S) +# xlabel(xlbl_txt); +# fixthetalabels(thmin,thmax,'y',3) % fix yticklabel and ylabel for theta +# zlabel(zlbl_txt) +# title(ylbl3_txt) +# elseif plotflag==5 % mesh +# %h=polar([0 2*pi],[0 freq(end)]); +# %delete(h);hold on +# [X,Y]=meshgrid(S.theta-phi,freq); +# [X,Y]=polar2cart(X,Y); +# mesh(X,Y,S.S') +# % display the unit circle beneath the surface +# hold on, mesh(X,Y,zeros(size(S.S'))),hold off +# zlabel(zlbl_txt) +# title(ylbl3_txt) +# set(gca,'xticklabel','','yticklabel','') +# lighting phong +# %lighting gouraud +# %light +# elseif (plotflag==6) || (plotflag==7), +# theta = S.theta-phi; +# [c, h] = contour(freq,theta,S.S); %,Nlevel); % calculate levels +# fixthetalabels(thmin,thmax,'y',2) % fix yticklabel and ylabel for theta +# if plotflag==7, +# hold on +# [c,h] = contourf(freq,theta,S.S); %,Nlevel); % calculate levels +# %hold on +# end +# +# title(ylbl3_txt) +# xlabel(xlbl_txt); +# if 0, +# [z_level] = clevels(c); % find contour levels +# % label the contour levels +# if txtFlag==1 +# textstart_x = 0.06; textstart_y=0.94; +# cltext1(z_level,textstart_x,textstart_y) % a local variant of cltext +# else +# cltext(z_level) +# end +# else +# colormap('jet') +# +# if plotflag==7, +# fcolorbar(c) +# else +# %clabel(c,h), +# hcb = colorbar; +# end +# grid on +# end +# else +# error('Unknown plot option') +# end +# otherwise, error('unknown spectral type') +# end +# +# if ~ih, hold off, end +# +# % The following two commands install point-and-click editing of +# % all the text objects (title, xlabel, ylabel) of the current figure: +# +# %set(findall(gcf,'type','text'),'buttondownfcn','edtext') +# %set(gcf,'windowbuttondownfcn','edtext(''hide'')') +# +# return +# + + +# +# function fixthetalabels(thmin,thmax,xy,dim) +# %FIXTHETALABELS pretty prints the ticklabels and x or y labels for theta +# % +# % CALL fixthetalabels(thmin,thmax,xy,dim) +# % +# % thmin, thmax = minimum and maximum value for theta (wave direction) +# % xy = 'x' if theta is plotted on the x-axis +# % 'y' if theta is plotted on the y-axis +# % dim = specifies the dimension of the plot (ie number of axes shown 2 or 3) +# % If abs(thmax-thmin)<3*pi it is assumed that theta is given in radians +# % otherwise degrees +# +# ind = [('x' == xy) ('y' == xy) ]; +# yx = 'yx'; +# yx = yx(ind); +# if nargin<4||isempty(dim), +# dim=2; +# end +# %drawnow +# %pause +# +# if abs(thmax-thmin)<3*pi, %Radians given. Want xticks given as fractions of pi +# %Trick to update the axis +# if xy=='x' +# if dim<3, +# axis([thmin,thmax 0 inf ]) +# else +# axis([thmin,thmax 0 inf 0 inf]) +# end +# else +# if dim<3, +# axis([0 inf thmin,thmax ]) +# else +# axis([0 inf thmin,thmax 0 inf]) +# end +# end +# +# set(gca,[xy 'tick'],pi*(thmin/pi:0.25:thmax/pi)); +# set(gca,[xy 'ticklabel'],[]); +# x = get(gca,[xy 'tick']); +# y = get(gca,[yx 'tick']); +# y1 = y(1); +# dy = y(2)-y1; +# yN = y(end)+dy; +# ylim = [y1 yN]; +# dy1 = diff(ylim)/40; +# %ylim=get(gca,[yx 'lim'])%,ylim=ylim(2); +# +# if xy=='x' +# for j=1:length(x) +# xtxt = num2pistr(x(j)); +# figtext(x(j),y1-dy1,xtxt,'data','data','center','top'); +# end +# % ax = [thmin thmax 0 inf]; +# ax = [thmin thmax ylim]; +# if dim<3, +# figtext(mean(x),y1-7*dy1,'Wave directions (rad)','data','data','center','top') +# else +# ax = [ax 0 inf]; +# xlabel('Wave directions (rad)') +# end +# else +# %ax = [0 inf thmin thmax]; +# ax = [ylim thmin thmax]; +# +# if dim<3, +# for j=1:length(x) +# xtxt = num2pistr(x(j)); +# figtext(y1-dy1/2,x(j),xtxt,'data','data','right'); +# end +# set(gca,'DefaultTextRotation',90) +# %ylabel('Wave directions (rad)') +# figtext(y1-3*dy1,mean(x),'Wave directions (rad)','data','data','center','bottom') +# set(gca,'DefaultTextRotation',0) +# else +# for j=1:length(x) +# xtxt = num2pistr(x(j)); +# figtext(y1-3*dy1,x(j),xtxt,'data','data','right'); +# end +# ax = [ax 0 inf]; +# ylabel('Wave directions (rad)') +# end +# end +# %xtxt = num2pistr(x(j)); +# %for j=2:length(x) +# % xtxt = strvcat(xtxt,num2pistr(x(j))); +# %end +# %set(gca,[xy 'ticklabel'],xtxt) +# else % Degrees given +# set(gca,[xy 'tick'],thmin:45:thmax) +# if xy=='x' +# ax=[thmin thmax 0 inf]; +# if dim>=3, ax=[ax 0 inf]; end +# xlabel('Wave directions (deg)') +# else +# ax=[0 inf thmin thmax ]; +# if dim>=3, ax=[ax 0 inf]; end +# ylabel('Wave directions (deg)') +# end +# end +# axis(ax) +# return +# + + +class SpecData1D(PlotData): + + """ + Container class for 1D spectrum data objects in WAFO + + Member variables + ---------------- + data : array-like + One sided Spectrum values, size nf + args : array-like + freguency/wave-number-lag values of freqtype, size nf + type : String + spectrum type, one of 'freq', 'k1d', 'enc' (default 'freq') + freqtype : letter + frequency type, one of: 'f', 'w' or 'k' (default 'w') + tr : Transformation function (default (none)). + h : real scalar + Water depth (default inf). + v : real scalar + Ship speed, if type = 'enc'. + norm : bool + Normalization flag, True if S is normalized, False if not + date : string + Date and time of creation or change. + + Examples + -------- + >>> import numpy as np + >>> import wafo.spectrum.models as sm + >>> Sj = sm.Jonswap(Hm0=3) + >>> w = np.linspace(0,4,256) + >>> S1 = Sj.tospecdata(w) #Make spectrum object from numerical values + >>> S = sm.SpecData1D(Sj(w),w) # Alternatively do it manually + + See also + -------- + PlotData + CovData + """ + + def __init__(self, *args, **kwds): + self.name_ = kwds.pop('name', 'WAFO Spectrum Object') + self.type = kwds.pop('type', 'freq') + self.freqtype = kwds.pop('freqtype', 'w') + self.angletype = '' + self.h = kwds.pop('h', inf) + self.tr = kwds.pop('tr', None) # TrLinear() + self.phi = kwds.pop('phi', 0.0) + self.v = kwds.pop('v', 0.0) + self.norm = kwds.pop('norm', False) + super(SpecData1D, self).__init__(*args, **kwds) + + self.setlabels() + + def tocov_matrix(self, nr=0, nt=None, dt=None): + ''' + Computes covariance function and its derivatives, alternative version + + Parameters + ---------- + nr : scalar integer + number of derivatives in output, nr<=4 (default 0) + nt : scalar integer + number in time grid, i.e., number of time-lags. + (default rate*(n_f-1)) where rate = round(1/(2*f(end)*dt)) or + rate = round(pi/(w(n_f)*dt)) depending on S. + dt : real scalar + time spacing for acfmat + + Returns + ------- + acfmat : [R0, R1,...Rnr], shape Nt+1 x Nr+1 + matrix with autocovariance and its derivatives, i.e., Ri (i=1:nr) + are column vectors with the 1'st to nr'th derivatives of R0. + + NB! This routine requires that the spectrum grid is equidistant + starting from zero frequency. + + Example + ------- + >>> import wafo.spectrum.models as sm + >>> Sj = sm.Jonswap() + >>> S = Sj.tospecdata() + >>> acfmat = S.tocov_matrix(nr=3, nt=256, dt=0.1) + >>> np.round(acfmat[:2,:],3) + array([[ 3.061, 0. , -1.677, 0. ], + [ 3.052, -0.167, -1.668, 0.187]]) + + See also + -------- + cov, + resample, + objects + ''' + + ftype = self.freqtype # %options are 'f' and 'w' and 'k' + freq = self.args + n_f = len(freq) + dt_old = self.sampling_period() + if dt is None: + dt = dt_old + rate = 1 + else: + rate = max(round(dt_old * 1. / dt), 1.) + + if nt is None: + nt = rate * (n_f - 1) + else: # %check if Nt is ok + nt = minimum(nt, rate * (n_f - 1)) + + checkdt = 1.2 * min(diff(freq)) / 2. / pi + if ftype in 'k': + lagtype = 'x' + else: + lagtype = 't' + if ftype in 'f': + checkdt = checkdt * 2 * pi + msg1 = 'Step dt = %g in computation of the density is too small.' % dt + msg2 = 'Step dt = %g is small, and may cause numerical inaccuracies.' % dt + + if (checkdt < 2. ** -16 / dt): + print(msg1) + print('The computed covariance (by FFT(2^K)) may differ from the') + print('theoretical. Solution:') + raise ValueError('use larger dt or sparser grid for spectrum.') + + # Calculating covariances + #~~~~~~~~~~~~~~~~~~~~~~~~ + spec = self.copy() + spec.resample(dt) + + acf = spec.tocovdata(nr, nt, rate=1) + acfmat = zeros((nt + 1, nr + 1), dtype=float) + acfmat[:, 0] = acf.data[0:nt + 1] + fieldname = 'R' + lagtype * nr + for i in range(1, nr + 1): + fname = fieldname[:i + 1] + r_i = getattr(acf, fname) + acfmat[:, i] = r_i[0:nt + 1] + + eps0 = 0.0001 + if nt + 1 >= 5: + cc2 = acfmat[0, 0] - acfmat[4, 0] * (acfmat[4, 0] / acfmat[0, 0]) + if (cc2 < eps0): + warnings.warn(msg1) + cc1 = acfmat[0, 0] - acfmat[1, 0] * (acfmat[1, 0] / acfmat[0, 0]) + if (cc1 < eps0): + warnings.warn(msg2) + return acfmat + + def tocovdata(self, nr=0, nt=None, rate=None): + ''' + Computes covariance function and its derivatives + + Parameters + ---------- + nr : number of derivatives in output, nr<=4 (default = 0). + nt : number in time grid, i.e., number of time-lags + (default rate*(length(S.data)-1)). + rate = 1,2,4,8...2**r, interpolation rate for R + (default = 1, no interpolation) + + Returns + ------- + R : CovData1D + auto covariance function + + The input 'rate' with the spectrum gives the time-grid-spacing: + dt=pi/(S.w[-1]*rate), + S.w[-1] is the Nyquist freq. + This results in the time-grid: 0:dt:Nt*dt. + + What output is achieved with different S and choices of Nt, Nx and Ny: + 1) S.type='freq' or 'dir', Nt set, Nx,Ny not set => R(time) (one-dim) + 2) S.type='k1d' or 'k2d', Nt set, Nx,Ny not set: => R(x) (one-dim) + 3) Any type, Nt and Nx set => R(x,time); Nt and Ny set => R(y,time) + 4) Any type, Nt, Nx and Ny set => R(x,y,time) + 5) Any type, Nt not set, Nx and/or Ny set + => Nt set to default, goto 3) or 4) + + NB! This routine requires that the spectrum grid is equidistant + starting from zero frequency. + NB! If you are using a model spectrum, spec, with sharp edges + to calculate covariances then you should probably round off the sharp + edges like this: + + Example: + >>> import wafo.spectrum.models as sm + >>> Sj = sm.Jonswap() + >>> S = Sj.tospecdata() + >>> S.data[0:40] = 0.0 + >>> S.data[100:-1] = 0.0 + >>> Nt = len(S.data)-1 + >>> acf = S.tocovdata(nr=0, nt=Nt) + >>> S1 = acf.tospecdata() + >>> h = S.plot('r') + >>> h1 = S1.plot('b:') + + R = spec2cov(spec,0,Nt) + win = parzen(2*Nt+1) + R.data = R.data.*win(Nt+1:end) + S1 = cov2spec(acf) + R2 = spec2cov(S1) + figure(1) + plotspec(S),hold on, plotspec(S1,'r') + figure(2) + covplot(R), hold on, covplot(R2,[],[],'r') + figure(3) + semilogy(abs(R2.data-R.data)), hold on, + semilogy(abs(S1.data-S.data)+1e-7,'r') + + See also + -------- + cov2spec + ''' + + freq = self.args + n_f = len(freq) + + if freq[0] > 0: + txt = '''Spectrum does not start at zero frequency/wave number. + Correct it with resample, for example.''' + raise ValueError(txt) + d_w = abs(diff(freq, n=2, axis=0)) + if any(d_w > 1.0e-8): + txt = '''Not equidistant frequencies/wave numbers in spectrum. + Correct it with resample, for example.''' + raise ValueError(txt) + + if rate is None: + rate = 1 # %interpolation rate + elif rate > 16: + rate = 16 + else: # make sure rate is a power of 2 + rate = 2 ** nextpow2(rate) + + if nt is None: + nt = rate * (n_f - 1) + else: # check if Nt is ok + nt = minimum(nt, rate * (n_f - 1)) + + spec = self.copy() + + if self.freqtype in 'k': + lagtype = 'x' + else: + lagtype = 't' + + d_t = spec.sampling_period() + # normalize spec so that sum(specn)/(n_f-1)=acf(0)=var(X) + specn = spec.data * freq[-1] + if spec.freqtype in 'f': + w = freq * 2 * pi + else: + w = freq + + nfft = rate * 2 ** nextpow2(2 * n_f - 2) + + # periodogram + rper = r_[ + specn, zeros(nfft - (2 * n_f) + 2), conj(specn[n_f - 2:0:-1])] + time = r_[0:nt + 1] * d_t * (2 * n_f - 2) / nfft + + r = fft(rper, nfft).real / (2 * n_f - 2) + acf = _WAFOCOV.CovData1D(r[0:nt + 1], time, lagtype=lagtype) + acf.tr = spec.tr + acf.h = spec.h + acf.norm = spec.norm + + if nr > 0: + w = r_[w, zeros(nfft - 2 * n_f + 2), -w[n_f - 2:0:-1]] + fieldname = 'R' + lagtype[0] * nr + for i in range(1, nr + 1): + rper = -1j * w * rper + d_acf = fft(rper, nfft).real / (2 * n_f - 2) + setattr(acf, fieldname[0:i + 1], d_acf[0:nt + 1]) + return acf + + def to_linspec(self, ns=None, dt=None, cases=20, iseed=None, + fn_limit=sqrt(2), gravity=9.81): + ''' + Split the linear and non-linear component from the Spectrum + according to 2nd order wave theory + + Returns + ------- + SL, SN : SpecData1D objects + with linear and non-linear components only, respectively. + + Parameters + ---------- + ns : scalar integer + giving ns load points. (default length(S)-1=n-1). + If np>n-1 it is assummed that S(k)=0 for all k>n-1 + cases : scalar integer + number of cases (default=20) + dt : real scalar + step in grid (default dt is defined by the Nyquist freq) + iseed : scalar integer + starting seed number for the random number generator + (default none is set) + fnLimit : real scalar + normalized upper frequency limit of spectrum for 2'nd order + components. The frequency is normalized with + sqrt(gravity*tanh(kbar*water_depth)/Amax)/(2*pi) + (default sqrt(2), i.e., Convergence criterion). + Generally this should be the same as used in the final + non-linear simulation (see example below). + + SPEC2LINSPEC separates the linear and non-linear component of the + spectrum according to 2nd order wave theory. This is useful when + simulating non-linear waves because: + If the spectrum does not decay rapidly enough towards zero, the + contribution from the 2nd order wave components at the upper tail can + be very large and unphysical. Another option to ensure convergence of + the perturbation series in the simulation, is to truncate the upper + tail of the spectrum at FNLIMIT in the calculation of the 2nd order + wave components, i.e., in the calculation of sum and difference + frequency effects. + + Example: + -------- + np = 10000 + iseed = 1 + pflag = 2 + S = jonswap(10) + fnLimit = inf + [SL,SN] = spec2linspec(S,np,[],[],fnLimit) + x0 = spec2nlsdat(SL,8*np,[],iseed,[],fnLimit) + x1 = spec2nlsdat(S,8*np,[],iseed,[],fnLimit) + x2 = spec2nlsdat(S,8*np,[],iseed,[],sqrt(2)) + Se0 = dat2spec(x0) + Se1 = dat2spec(x1) + Se2 = dat2spec(x2) + clf + plotspec(SL,'r',pflag), % Linear components + hold on + plotspec(S,'b',pflag) % target spectrum for simulated data + plotspec(Se0,'m',pflag), % approx. same as S + plotspec(Se1,'g',pflag) % unphysical spectrum + plotspec(Se2,'k',pflag) % approx. same as S + axis([0 10 -80 0]) + hold off + + See also + -------- + spec2nlsdat + + References + ---------- + P. A. Brodtkorb (2004), + The probability of Occurrence of dangerous Wave Situations at Sea. + Dr.Ing thesis, Norwegian University of Science and Technolgy, NTNU, + Trondheim, Norway. + + Nestegaard, A and Stokka T (1995) + A Third Order Random Wave model. + In proc.ISOPE conf., Vol III, pp 136-142. + + R. S Langley (1987) + A statistical analysis of non-linear random waves. + Ocean Engng, Vol 14, pp 389-407 + + Marthinsen, T. and Winterstein, S.R (1992) + 'On the skewness of random surface waves' + In proc. ISOPE Conf., San Francisco, 14-19 june. + ''' + + # by pab 13.08.2002 + + # TODO % Replace inputs with options structure + # TODO % Can be improved further. + + method = 'apstochastic' + trace = 1 # % trace the convergence + max_sim = 30 + tolerance = 5e-4 + + L = 200 # %maximum lag size of the window function used in + #%spectral estimate + # ftype = self.freqtype #options are 'f' and 'w' and 'k' +# switch ftype +# case 'f', +# ftype = 'w' +# S = ttspec(S,ftype) +# end + Hm0 = self.characteristic('Hm0') + Tm02 = self.characteristic('Tm02') + + if not iseed is None: + _set_seed(iseed) # % set the the seed + + n = len(self.data) + if ns is None: + ns = max(n - 1, 5000) + if dt is None: + S = self.interp(dt) # interpolate spectrum + else: + S = self.copy() + + ns = ns + mod(ns, 2) # make sure np is even + + water_depth = abs(self.h) + kbar = w2k(2 * pi / Tm02, 0, water_depth)[0] + + # Expected maximum amplitude for 10000 waves seastate + num_waves = 10000 # Typical number of waves in 30 hour seastate + Amax = sqrt(2 * log(num_waves)) * Hm0 / 4 + + fLimitLo = sqrt( + gravity * tanh(kbar * water_depth) * Amax / water_depth ** 3) + + freq = S.args + eps = finfo(float).eps + freq[-1] = freq[-1] - sqrt(eps) + Hw2 = 0 + + SL = S + + indZero = nonzero(freq < fLimitLo)[0] + if len(indZero): + SL.data[indZero] = 0 + + maxS = max(S.data) + # Fs = 2*freq(end)+eps % sampling frequency + + for ix in xrange(max_sim): + x2, x1 = self.sim_nl(ns=np, cases=cases, dt=None, iseed=iseed, + method=method, + fnlimit=fn_limit) + #%x2(:,2:end) = x2(:,2:end) -x1(:,2:end) + # TODO: Finish spec.to_linspec +# S2 = dat2spec(x2, L) +# S1 = dat2spec(x1, L) +# %[tf21,fi] = tfe(x2(:,2),x1(:,2),1024,Fs,[],512) +# %Hw11 = interp1q(fi,tf21.*conj(tf21),freq) +# if True: +# Hw1 = exp(interp1q(S2.args, log(abs(S1.data / S2.data)), freq)) +# else: +# Geometric mean +# Hw1 = exp( +# (interp1q(S2.args, log(abs(S1.data / S2.data)), +# freq) + log(Hw2)) / 2) + # end + # Hw1 = (interp1q( S2.w,abs(S1.S./S2.S),freq)+Hw2)/2 + # plot(freq, abs(Hw11-Hw1),'g') + # title('diff') + # pause + # clf + + # d1 = interp1q( S2.w,S2.S,freq) + + SL.data = (Hw1 * S.data) + + if len(indZero): + SL.data[indZero] = 0 + # end + k = nonzero(SL.data < 0)[0] + if len(k): # Make sure that the current guess is larger than zero + #%k + # Hw1(k) + Hw1[k] = min(S1.data[k] * 0.9, S.data[k]) + SL.data[k] = max(Hw1[k] * S.data[k], eps) + # end + Hw12 = Hw1 - Hw2 + maxHw12 = max(abs(Hw12)) + if trace == 1: + plotbackend.figure(1), + plotbackend.semilogy(freq, Hw1, 'r') + plotbackend.title('Hw') + plotbackend.figure(2), + plotbackend.semilogy(freq, abs(Hw12), 'r') + plotbackend.title('Hw-HwOld') + + # pause(3) + plotbackend.figure(1), + plotbackend.semilogy(freq, Hw1, 'b') + plotbackend.title('Hw') + plotbackend.figure(2), + plotbackend.semilogy(freq, abs(Hw12), 'b') + plotbackend.title('Hw-HwOld') + # figtile + # end + + print('Iteration : %d, Hw12 : %g Hw12/maxS : %g' % + (ix, maxHw12, (maxHw12 / maxS))) + if (maxHw12 < maxS * tolerance) and (Hw1[-1] < Hw2[-1]): + break + # end + Hw2 = Hw1 + # end + + #%Hw1(end) + #%maxS*1e-3 + #%if Hw1(end)*S.>maxS*1e-3, + #% warning('The Nyquist frequency of the spectrum may be too low') + #%end + + SL.date = now() # datestr(now) + # if nargout>1 + SN = SL.copy() + SN.data = S.data - SL.data + SN.note = SN.note + ' non-linear component (spec2linspec)' + # end + SL.note = SL.note + ' linear component (spec2linspec)' + + return SL, SN + + def to_mm_pdf(self, paramt=None, paramu=None, utc=None, nit=2, EPS=5e-5, + EPSS=1e-6, C=4.5, EPS0=1e-5, IAC=1, ISQ=0, verbose=False): + ''' + nit = order of numerical integration: 0,1,2,3,4,5. + paramu = parameter vector defining discretization of min/max values. + t = grid of time points between maximum and minimum (to + integrate out). interval between maximum and the following + minimum, + The variable ISQ marks which type of conditioning will be used ISQ=0 + means random time where the probability is minimum, ISQ=1 is the time + where the variance of the residual process is minimal(ISQ=1 is faster). + + NIT, IAC are described in CROSSPACK paper, EPS0 is the accuracy + constant used in choosing the number of nodes in numerical integrations + (XX1, H1 vectors). The nodes and weights and other parameters are + read in the subroutine INITINTEG from files Z.DAT, H.DAT and ACCUR.DAT. + + + NIT=0, IAC=1 then one uses RIND0 - subroutine, all other cases + goes through RIND1, ...,RIND5. NIT=0, here means explicite formula + approximation for XIND=E[Y^+1{ HH>> import numpy as np + >>> import wafo.spectrum.models as sm + >>> Sj = sm.Jonswap(Hm0=3) + >>> w = np.linspace(0,4,256) + >>> S1 = Sj.tospecdata(w) #Make spectrum object from numerical values + >>> S = sm.SpecData1D(Sj(w),w) # Alternatively do it manually + mm = S.to_mm_pdf() + mm.plot() + mm.plot(plotflag=1) + ''' + + S = self.copy() + S.normalize() + m, unused_mtxt = self.moment(nr=4, even=True) + A = sqrt(m[0] / m[1]) + + if paramt is None: + # (2.5 * mean distance between extremes) + distanceBetweenExtremes = 5 * pi * sqrt(m[1] / m[2]) + paramt = [0, distanceBetweenExtremes, 43] + + if paramu is None: + paramu = [-5 * sqrt(m[0]), 5 * sqrt(m[0]), 41] + + if self.tr is None: + g = TrLinear(var=m[0]) + else: + g = self.tr + + if utc is None: + utc = g.gauss2dat(0) # most frequent crossed level + + # transform reference level into Gaussian level + u = g.dat2gauss(utc) + if verbose: + print('The level u for Gaussian process = %g' % u) + + unused_t0, tn, Nt = paramt + t = linspace(0, tn / A, Nt) # normalized times + + # Transform amplitudes to Gaussian levels: + h = linspace(*paramu) + dt = t[1] - t[0] + nr = 4 + R = S.tocov_matrix(nr, Nt - 1, dt) + + # ulev = linspace(*paramu) + # vlev = linspace(*paramu) + + trdata = g.trdata() + Tg = trdata.args + Xg = trdata.data + + cov2mod.initinteg(EPS, EPSS, EPS0, C, IAC, ISQ) + uvdens = cov2mod.cov2mmpdfreg(t, R, h, h, Tg, Xg, nit) + uvdens = np.rot90(uvdens, -2) + + dh = h[1] - h[0] + uvdens *= dh * dh + + mmpdf = PlotData(uvdens, args=(h, h), xlab='max [m]', ylab='min [m]', + title='Joint density of maximum and minimum') + try: + pl = [10, 30, 50, 70, 90, 95, 99, 99.9] + mmpdf.cl = qlevels(uvdens, pl, h, h) + mmpdf.pl = pl + except: + pass + return mmpdf + + def to_t_pdf(self, u=None, kind='Tc', paramt=None, **options): + ''' + Density of crest/trough- period or length, version 2. + + Parameters + ---------- + u : real scalar + reference level (default the most frequently crossed level). + kind : string, 'Tc', Tt', 'Lc' or 'Lt' + 'Tc', gives half wave period, Tc (default). + 'Tt', gives half wave period, Tt + 'Lc' and 'Lt' ditto for wave length. + paramt : [t0, tn, nt] + where t0, tn and nt is the first value, last value and the number + of points, respectively, for which the density will be computed. + paramt= [5, 5, 51] implies that the density is computed only for + T=5 and using 51 equidistant points in the interval [0,5]. + options : optional parameters + controlling the performance of the integration. + See Rind for details. + + Notes + ----- + SPEC2TPDF2 calculates pdf of halfperiods Tc, Tt, Lc or Lt + in a stationary Gaussian transform process X(t), + where Y(t) = g(X(t)) (Y zero-mean Gaussian with spectrum given in S). + The transformation, g, can be estimated using LC2TR, + DAT2TR, HERMITETR or OCHITR. + + Example + ------- + The density of Tc is computed by: + >>> import pylab as plb + >>> from wafo.spectrum import models as sm + >>> w = np.linspace(0,3,100) + >>> Sj = sm.Jonswap() + >>> S = Sj.tospecdata() + >>> f = S.to_t_pdf(pdef='Tc', paramt=(0, 10, 51), speed=7) + >>> h = f.plot() + + estimated error bounds + >>> h2 = plb.plot(f.args, f.data+f.err, 'r', f.args, f.data-f.err, 'r') + + >>> plb.close('all') + + See also + -------- + Rind, spec2cov2, specnorm, dat2tr, dat2gaus, + definitions.wave_periods, + definitions.waves + + ''' + + opts = dict(speed=9) + opts.update(options) + if kind[0] in ('l', 'L'): + if self.type != 'k1d': + raise ValueError('Must be spectrum of type: k1d') + elif kind[0] in ('t', 'T'): + if self.type != 'freq': + raise ValueError('Must be spectrum of type: freq') + else: + raise ValueError('pdef must be Tc,Tt or Lc, Lt') +# if strncmpi('l',kind,1) +# spec=spec2spec(spec,'k1d') +# elseif strncmpi('t',kind,1) +# spec=spec2spec(spec,'freq') +# else +# error('Unknown kind') +# end + kind2defnr = dict(tc=1, lc=1, tt=-1, lt=-1) + defnr = kind2defnr[kind.lower()] + + S = self.copy() + S.normalize() + m, unused_mtxt = self.moment(nr=2, even=True) + A = sqrt(m[0] / m[1]) + + if self.tr is None: + g = TrLinear(var=m[0]) + else: + g = self.tr + + if u is None: + u = g.gauss2dat(0) # % most frequently crossed level + + # transform reference level into Gaussian level + un = g.dat2gauss(u) + + # disp(['The level u for Gaussian process = ', num2str(u)]) + + if paramt is None: + # z2 = u^2/2 + z = -sign(defnr) * un / sqrt(2) + expectedMaxPeriod = 2 * \ + ceil(2 * pi * A * exp(z) * (0.5 + erf(z) / 2)) + paramt = [0, expectedMaxPeriod, 51] + + t0 = paramt[0] + tn = paramt[1] + Ntime = paramt[2] + t = linspace(0, tn / A, Ntime) # normalized times + # index to starting point to evaluate + Nstart = max(round(t0 / tn * (Ntime - 1)), 1) + + dt = t[1] - t[0] + nr = 2 + R = S.tocov_matrix(nr, Ntime - 1, dt) + # R = spec2cov2(S,nr,Ntime-1,dt) + + xc = vstack((un, un)) + indI = -ones(4, dtype=int) + Nd = 2 + Nc = 2 + XdInf = 100.e0 * sqrt(-R[0, 2]) + XtInf = 100.e0 * sqrt(R[0, 0]) + + B_up = hstack([un + XtInf, XdInf, 0]) + B_lo = hstack([un, 0, -XdInf]) + #%INFIN = [1 1 0] + # BIG = zeros((Ntime+2,Ntime+2)) + ex = zeros(Ntime + 2, dtype=float) + #%CC = 2*pi*sqrt(-R(1,1)/R(1,3))*exp(un^2/(2*R(1,1))) + #% XcScale = log(CC) + opts['xcscale'] = log( + 2 * pi * sqrt(-R[0, 0] / R[0, 2])) + (un ** 2 / (2 * R[0, 0])) + + f = zeros(Ntime, dtype=float) + err = zeros(Ntime, dtype=float) + + rind = Rind(**opts) + # h11 = fwaitbar(0,[],sprintf('Please wait ...(start at: %s)', + # datestr(now))) + for pt in xrange(Nstart, Ntime): + Nt = pt - Nd + 1 + Ntd = Nt + Nd + Ntdc = Ntd + Nc + indI[1] = Nt - 1 + indI[2] = Nt + indI[3] = Ntd - 1 + + #% positive wave period + BIG = self._covinput_t_pdf(pt, R) + + tmp = rind(BIG, ex[:Ntdc], B_lo, B_up, indI, xc, Nt) + f[pt], err[pt] = tmp[:2] + # fwaitbar(pt/Ntime,h11,sprintf('%s Ready: %d of %d', + # datestr(now),pt,Ntime)) + # end + # close(h11) + + titledict = dict( + tc='Density of Tc', tt='Density of Tt', lc='Density of Lc', + lt='Density of Lt') + Htxt = titledict.get(kind.lower()) + + if kind[0].lower() == 'l': + xtxt = 'wave length [m]' + else: + xtxt = 'period [s]' + + Htxt = '%s_{v =%2.5g}' % (Htxt, u) + pdf = PlotData(f / A, t * A, title=Htxt, xlab=xtxt) + pdf.err = err / A + pdf.u = u + pdf.options = opts + return pdf + + def _covinput_t_pdf(self, pt, R): + """ + Return covariance matrix for Tc or Tt period problems + + Parameters + ---------- + pt : scalar integer + time + R : array-like, shape Ntime x 3 + [R0,R1,R2] column vectors with autocovariance and its derivatives, + i.e., R1 and R2 are vectors with the 1'st and 2'nd derivatives of + R0, respectively. + + The order of the variables in the covariance matrix are organized as + follows: + For pt>1: + ||X(t2)..X(ts),..X(tn-1)|| X'(t1) X'(tn)|| X(t1) X(tn) || + = [Xt Xd Xc] + + where + + Xt = time points in the indicator function + Xd = derivatives + Xc=variables to condition on + + Computations of all covariances follows simple rules: + Cov(X(t),X(s))=r(t,s), + then Cov(X'(t),X(s))=dr(t,s)/dt. Now for stationary X(t) we have + a function r(tau) such that Cov(X(t),X(s))=r(s-t) (or r(t-s) will give + the same result). + + Consequently + Cov(X'(t),X(s)) = -r'(s-t) = -sign(s-t)*r'(|s-t|) + Cov(X'(t),X'(s)) = -r''(s-t) = -r''(|s-t|) + Cov(X''(t),X'(s)) = r'''(s-t) = sign(s-t)*r'''(|s-t|) + Cov(X''(t),X(s)) = r''(s-t) = r''(|s-t|) + Cov(X''(t),X''(s)) = r''''(s-t) = r''''(|s-t|) + + """ + # cov(Xd) + Sdd = -toeplitz(R[[0, pt], 2]) + # cov(Xc) + Scc = toeplitz(R[[0, pt], 0]) + # cov(Xc,Xd) + Scd = array([[0, R[pt, 1]], [-R[pt, 1], 0]]) + + if pt > 1: + #%cov(Xt) + # Cov(X(tn),X(ts)) = r(ts-tn) = r(|ts-tn|) + Stt = toeplitz(R[:pt - 1, 0]) + #%cov(Xc,Xt) + # Cov(X(tn),X(ts)) = r(ts-tn) = r(|ts-tn|) + Sct = R[1:pt, 0] + Sct = vstack((Sct, Sct[::-1])) + #%Cov(Xd,Xt) + # Cov(X'(t1),X(ts)) = -r'(ts-t1) = r(|s-t|) + Sdt = -R[1:pt, 1] + Sdt = vstack((Sdt, -Sdt[::-1])) + # N = pt + 3 + big = vstack((hstack((Stt, Sdt.T, Sct.T)), + hstack((Sdt, Sdd, Scd.T)), + hstack((Sct, Scd, Scc)))) + else: + # N = 4 + big = vstack((hstack((Sdd, Scd.T)), + hstack((Scd, Scc)))) + return big + + def to_mmt_pdf(self, paramt=None, paramu=None, utc=None, kind='mm', + verbose=False, **options): + ''' Returns joint density of Maximum, minimum and period. + + Parameters + ---------- + u = reference level (default the most frequently crossed level). + kind : string + defining density returned + 'Mm' : maximum and the following minimum. (M,m) (default) + 'rfc' : maximum and the rainflow minimum height. + 'AcAt' : (crest,trough) heights. + 'vMm' : level v separated Maximum and minimum (M,m)_v + 'MmTMm' : maximum, minimum and period between (M,m,TMm) + 'vMmTMm': level v separated Maximum, minimum and period + between (M,m,TMm)_v + 'MmTMd' : level v separated Maximum, minimum and the period + from Max to level v-down-crossing (M,m,TMd)_v. + 'MmTdm' : level v separated Maximum, minimum and the period from + level v-down-crossing to min. (M,m,Tdm)_v + NB! All 'T' above can be replaced by 'L' to get wave length + instead. + paramt : [0 tn Nt] + defines discretization of half period: tn is the longest period + considered while Nt is the number of points, i.e. (Nt-1)/tn is the + sampling frequnecy. paramt= [0 10 51] implies that the halfperiods + are considered at 51 linearly spaced points in the interval [0,10], + i.e. sampling frequency is 5 Hz. + paramu : [u, v, N] + defines discretization of maxima and minima ranges: u is the + lowest minimum considered, v the highest maximum and N is the + number of levels (u,v) included. + options : + rind-options structure containing optional parameters controlling + the performance of the integration. See rindoptset for details. + [] = default values are used. + + Returns + ------- + f = pdf (density structure) of crests (trough) heights + + TO_MMT_PDF calculates densities of wave characteristics in a + stationary Gaussian transform process X(t) where + Y(t) = g(X(t)) (Y zero-mean Gaussian with spectrum given in input spec) + The tr.g can be estimated using lc2tr, dat2tr, hermitetr or ochitr. + + Examples + -------- + The joint density of zero separated Max2min cycles in time (a); + in space (b); AcAt in time for nonlinear sea model (c): + + Hm0=7;Tp=11 + S = jonswap(4*pi/Tp,[Hm0 Tp]) + Sk = spec2spec(S,'k1d') + L0 = spec2mom(S,1) + paramu = [sqrt(L0)*[-4 4] 41] + ft = spec2mmtpdf(S,0,'vmm',[],paramu); pdfplot(ft) % a) + fs = spec2mmtpdf(Sk,0,'vmm'); figure, pdfplot(fs) % b) + [sk, ku, me]=spec2skew(S) + g = hermitetr([],[sqrt(L0) sk ku me]) + Snorm=S; Snorm.S=S.S/L0; Snorm.tr=g + ftg=spec2mmtpdf(Snorm,0,'AcAt',[],paramu); pdfplot(ftg) % c) + + See also + -------- + rindoptset, dat2tr, datastructures, wavedef, perioddef + + References + --------- + Podgorski et al. (2000) + "Exact distributions for apparent waves in irregular seas" + Ocean Engineering, Vol 27, no 1, pp979-1016. + + P. A. Brodtkorb (2004), + Numerical evaluation of multinormal expectations + In Lund university report series + and in the Dr.Ing thesis: + The probability of Occurrence of dangerous Wave Situations at Sea. + Dr.Ing thesis, Norwegian University of Science and Technolgy, NTNU, + Trondheim, Norway. + + Per A. Brodtkorb (2006) + "Evaluating Nearly Singular Multinormal Expectations with Application + to Wave Distributions", + Methodology And Computing In Applied Probability, Volume 8, Number 1, + pp. 65-91(27) + ''' + + opts = dict(speed=4, nit=2, method=0) + opts.update(**options) + + ftype = self.freqtype + kind2defnr = dict(ac=-2, at=-2, + rfc=-1, + mm=0, + mmtmm=1, mmlmm=1, + vmm=2, + vmmtmm=3, vmmlmm=3, + mmtmd=4, vmmtmd=4, mmlmd=4, vmmlmd=4, + mmtdm=5, vmmtdm=5, mmldm=5, vmmldm=5) + defnr = kind2defnr.get(kind, 0) + in_space = (ftype == 'k') # distribution in space or time + if defnr >= 3 or defnr == 1: + in_space = (kind[-2].upper() == 'L') + + if in_space: + # spec = spec2spec(spec,'k1d') + ptxt = 'space' + else: + # spec = spec2spec(spec,'freq') + ptxt = 'time' + + S = self.copy() + S.normalize() + m, unused_mtxt = self.moment(nr=4, even=True) + A = sqrt(m[0] / m[1]) + + if paramt is None: + # (2.5 * mean distance between extremes) + distanceBetweenExtremes = 5 * pi * sqrt(m[1] / m[2]) + paramt = [0, distanceBetweenExtremes, 43] + + if paramu is None: + paramu = [-5 * sqrt(m[0]), 5 * sqrt(m[0]), 41] + + if self.tr is None: + g = TrLinear(var=m[0]) + else: + g = self.tr + + if utc is None: + utc = g.gauss2dat(0) # most frequent crossed level + + # transform reference level into Gaussian level + u = g.dat2gauss(utc) + if verbose: + print('The level u for Gaussian process = %g' % u) + + t0, tn, Nt = paramt + t = linspace(0, tn / A, Nt) # normalized times + + # the starting point to evaluate + Nstart = 1 + round(t0 / tn * (Nt - 1)) + + Nx = paramu[2] + if (defnr > 1): + paramu[0] = max(0, paramu[0]) + if (paramu[1] < 0): + raise ValueError( + 'Discretization levels must be larger than zero') + + # Transform amplitudes to Gaussian levels: + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + h = linspace(*paramu) + + if defnr > 1: # level v separated Max2min densities + hg = np.hstack((utc + h, utc - h)) + hg, der = g.dat2gauss(utc + h, ones(Nx)) + hg1, der1 = g.dat2gauss(utc - h, ones(Nx)) + der, der1 = np.abs(der), np.abs(der1) + hg = np.hstack((hg, hg1)) + else: # Max2min densities + hg, der = np.abs(g.dat2gauss(h, ones(Nx))) + der = der1 = np.abs(der) + + dt = t[1] - t[0] + nr = 4 + R = S.tocov_matrix(nr, Nt - 1, dt) + + # NB!!! the spec2XXpdf.exe programmes are very sensitive to how you + # interpolate the covariances, especially where the process is very + # dependent and the covariance matrix is nearly singular. (i.e. for + # small t and high levels of u if Tc and low levels of u if Tt) + # The best is to interpolate the spectrum linearly so that S.S>=0 + # This makes sure that the covariance matrix is positive + # semi-definitt, since the circulant spectrum are the eigenvalues of + # the circulant covariance matrix. + + callFortran = 0 + # %options.method<0 + # if callFortran, % call fortran + # ftmp = cov2mmtpdfexe(R,dt,u,defnr,Nstart,hg,options) + # err = repmat(nan,size(ftmp)) + # else + [ftmp, err, terr, options] = self._cov2mmtpdf( + R, dt, u, defnr, Nstart, hg, options) + + # end + note = '' + if hasattr(self, 'note'): + note = note + self.note + tmp = 'L' if in_space else 'T' + if Nx > 2: + titledict = { + '-2': 'Joint density of (Ac,At) in %s' % ptxt, + '-1': 'Joint density of (M,m_{rfc}) in %s' % ptxt, + '0': 'Joint density of (M,m) in %s' % ptxt, + '1': 'Joint density of (M,m,%sMm) in %s' % (tmp, ptxt), + '2': 'Joint density of (M,m)_{v=%2.5g} in %s' % (utc, ptxt), + '3': 'Joint density of (M,m,%sMm)_{v=%2.5g} in %s' % + (tmp, utc, ptxt), + '4': 'Joint density of (M,m,%sMd)_{v=%2.5g} in %s' % + (tmp, utc, ptxt), + '5': 'Joint density of (M,m,%sdm)_{v=%2.5g} in %s' % + (tmp, utc, ptxt)} + title = titledict[defnr] + labx = 'Max [m]' + laby = 'min [m]' + args = (h, h) + else: + note = note + 'Density is not scaled to unity' + if defnr in (-2, -1, 0, 1): + title = 'Density of (%sMm, M = %2.5g, m = %2.5g)' % ( + tmp, h[1], h[0]) + elif defnr in (2, 3): + title = 'Density of (%sMm, M = %2.5g, m = %2.5g)_{v=%2.5g}' % ( + tmp, h[1], -h[1], utc) + elif defnr == 4: + title = 'Density of (%sMd, %sMm, M = %2.5g, m = %2.5g)_{v=%2.5g}' % ( + tmp, tmp, h[1], -h[1], utc) + elif defnr == 5: + title = 'Density of (%sdm, %sMm, M = %2.5g, m = %2.5g)_{v=%2.5g}' % ( + tmp, tmp, h[1], -h[1], utc) + + f = PlotData() +# f.options = options +# if defnr>1 or defnr==-2: +# f.u = utc # save level u +# +# if Nx>2 % amplitude distributions wanted +# f.x{2} = h +# f.labx{2} = 'min [m]' +# +# +# if defnr>2 || defnr==1 +# der0 = der1[:,None] * der[None,:] +# ftmp = np.reshape(ftmp,Nx,Nx,Nt) * der0[:,:, None] / A +# err = np.reshape(err,Nx,Nx,Nt) * der0[:,:, None] / A +# +# f.x{3} = t(:)*A +# labz = 'wave length [m]' if in_space else 'period [sec]' +# +# else +# der0 = der[:,None] * der[None,:] +# ftmp = np.reshape(ftmp,Nx,Nx) * der0 +# err = np.reshape(err,Nx,Nx) * der0 +# +# if (defnr==-1): +# ftmp0 = fliplr(mctp2rfc(fliplr(ftmp))) +# err = abs(ftmp0-fliplr(mctp2rfc(fliplr(ftmp+err)))) +# ftmp = ftmp0 +# elif (defnr==-2): +# ftmp0=fliplr(mctp2tc(fliplr(ftmp),utc,paramu))*sqrt(L4*L0)/L2 +# err =abs(ftmp0-fliplr(mctp2tc(fliplr(ftmp+err),utc,paramu))*sqrt(L4*L0)/L2) +# index1=find(f.x{1}>0) +# index2=find(f.x{2}<0) +# ftmp=flipud(ftmp0(index2,index1)) +# err =flipud(err(index2,index1)) +# f.x{1} = f.x{1}(index1) +# f.x{2} = abs(flipud(f.x{2}(index2))) +# end +# end +# f.f = ftmp +# f.err = err +# else % Only time or wave length distributions wanted +# f.f = ftmp/A +# f.err = err/A +# f.x{1}=A*t' +# if strcmpi(def(1),'t') +# f.labx{1} = 'period [sec]' +# else +# f.labx{1} = 'wave length [m]' +# end +# if defnr>3, +# f.f = reshape(f.f,[Nt, Nt]) +# f.err = reshape(f.err,[Nt, Nt]) +# f.x{2}= A*t' +# if strcmpi(def(1),'t') +# f.labx{2} = 'period [sec]' +# else +# f.labx{2} = 'wave length [m]' +# end +# end +# end +# +# +# try +# [f.cl,f.pl]=qlevels(f.f,[10 30 50 70 90 95 99 99.9],f.x{1},f.x{2}) +# catch +# warning('WAFO:SPEC2MMTPDF','Singularity likely in pdf') +# end +# %pdfplot(f) +# +# %Test of spec2mmtpdf +# % cd f:\matlab\matlab\wafo\source\sp2thpdfalan +# % addpath f:\matlab\matlab\wafo ,initwafo, addpath f:\matlab\matlab\graphutil +# % Hm0=7;Tp=11; S = jonswap(4*pi/Tp,[Hm0 Tp]) +# % ft = spec2mmtpdf(S,0,'vMmTMm',[0.3,.4,11],[0 .00005 2]) + + return f # % main + + def _cov2mmtpdf(self, R, dt, u, def_nr, Nstart, hg, options): + ''' + COV2MMTPDF Joint density of Maximum, minimum and period. + + CALL [pdf, err, options] = cov2mmtpdf(R,dt,u,def,Nstart,hg,options) + + pdf = calculated pdf size Nx x Ntime + err = error estimate + terr = truncation error + options = requested and actual rindoptions used in integration. + R = [R0,R1,R2,R3,R4] column vectors with autocovariance and its + derivatives, i.e., Ri (i=1:4) are vectors with the 1'st to + 4'th derivatives of R0. size Ntime x Nr+1 + dt = time spacing between covariance samples, i.e., + between R0(1),R0(2). + u = crossing level + def = integer defining pdf calculated: + 0 : maximum and the following minimum. (M,m) (default) + 1 : level v separated Maximum and minimum (M,m)_v + 2 : maximum, minimum and period between (M,m,TMm) + 3 : level v separated Maximum, minimum and period + between (M,m,TMm)_v + 4 : level v separated Maximum, minimum and the period + from Max to level v-down-crossing (M,m,TMd)_v. + 5 : level v separated Maximum, minimum and the period from + level v-down-crossing to min. (M,m,Tdm)_v + Nstart = index to where to start calculation, i.e., t0 = t(Nstart) + hg = vector of amplitudes length Nx or 0 + options = rind options structure defining the integration parameters + + COV2MMTPDF computes joint density of the maximum and the following + minimum or level u separated maxima and minima + period/wavelength + + For DEF = 0,1 : (Maxima, Minima and period/wavelength) + = 2,3 : (Level v separated Maxima and Minima and + period/wavelength between them) + + If Nx==1 then the conditional density for period/wavelength between + Maxima and Minima given the Max and Min is returned + Y = + X'(t2)..X'(ts)..X'(tn-1)|| X''(t1) X''(tn)|| X'(t1) X'(tn) X(t1) X(tn) + = [ Xt Xd Xc ] + + Nt = tn-2, Nd = 2, Nc = 4 + Xt = contains Nt time points in the indicator function + Xd = " Nd derivatives in Jacobian + Xc = " Nc variables to condition on + + There are 3 (NI=4) regions with constant barriers: + (indI[0]=0); for i in (indI[0],indI[1]] Y[i]<0. + (indI[1]=Nt); for i in (indI[1]+1,indI[2]], Y[i]<0 (deriv. X''(t1)) + (indI[2]=Nt+1); for i\in (indI[2]+1,indI[3]], Y[i]>0 (deriv. X''(tn)) + + For DEF = 4,5 (Level v separated Maxima and Minima and + period/wavelength from Max to crossing) + + If Nx==1 then the conditional joint density for period/wavelength + between Maxima, Minima and Max to level v crossing given the Max and + the min is returned + + Y= + X'(t2)..X'(ts)..X'(tn-1)||X''(t1) X''(tn) X'(ts)|| X'(t1) X'(tn) X(t1) X(tn) X(ts) + = [ Xt Xd Xc ] + + Nt = tn-2, Nd = 3, Nc = 5 + + Xt= contains Nt time points in the indicator function + Xd= " Nd derivatives + Xc= " Nc variables to condition on + + There are 4 (NI=5) regions with constant barriers: + (indI(1)=0); for i\in (indI(1),indI(2)] Y(i)<0. + (indI(2)=Nt) ; for i\in (indI(2)+1,indI(3)], Y(i)<0 (deriv. X''(t1)) + (indI(3)=Nt+1); for i\in (indI(3)+1,indI(4)], Y(i)>0 (deriv. X''(tn)) + (indI(4)=Nt+2); for i\in (indI(4)+1,indI(5)], Y(i)<0 (deriv. X'(ts)) + ''' + R0, R1, R2, R3, R4, R5 = R[:, :5].T + + Ntime = len(R0) + Nx0 = max(1, len(hg)) + Nx1 = Nx0 + # Nx0 = Nx1 #just plain Mm + if def_nr > 1: + Nx1 = Nx0 // 2 + # Nx0 = 2*Nx1 # level v separated max2min densities wanted + # print('def = %d' % def_nr)) + + # The bound 'infinity' is set to 100*sigma + XdInf = 100.0 * sqrt(R4[0]) + XtInf = 100.0 * sqrt(-R2[0]) + Nc = 4 + NI = 4 + Nd = 2 + # Mb = 1 + # Nj = 0 + + Nstart = max(2, Nstart) + symmetry = 0 + isOdd = np.mod(Nx1, 2) + if def_nr <= 1: # just plain Mm + Nx = Nx1 * (Nx1 - 1) / 2 + IJ = (Nx1 + isOdd) / 2 + if (hg[0] + hg[Nx1 - 1] == 0 and (hg[IJ - 1] == 0 or hg[IJ - 1] + hg[IJ] == 0)): + symmetry = 0 + print(' Integration region symmetric') + # May save Nx1-isOdd integrations in each time step + # This is not implemented yet. + # Nx = Nx1*(Nx1-1)/2-Nx1+isOdd + + # CC = normalizing constant = 1/ expected number of zero-up-crossings of X' + # CC = 2*pi*sqrt(-R2[0]/R4[0]) + # XcScale = log(CC) + XcScale = log(2 * pi * sqrt(-R2[0] / R4[0])) + else: + # level u separated Mm + Nx = (Nx1 - 1) * (Nx1 - 1) + if (abs(u) <= _EPS and (hg[0] + hg[Nx1] == 0) and + (hg[Nx1 - 1] + hg[2 * Nx1 - 1] == 0)): + symmetry = 0 + print(' Integration region symmetric') + # Not implemented for DEF <= 3 + # IF (DEF.LE.3) Nx = (Nx1-1)*(Nx1-2)/2 + + if def_nr > 3: + Nstart = max(Nstart, 3) + Nc = 5 + NI = 5 + Nd = 3 + # CC= normalizing constant= 1/ expected number of u-up-crossings of X + # CC = 2*pi*sqrt(-R0(1)/R2(1))*exp(0.5D0*u*u/R0(1)) + XcScale = log(2 * pi * sqrt(-R0[0] / R2[0])) + 0.5 * u * u / R0[0] + + options['xcscale'] = XcScale + opt0 = struct2cell(options) + # opt0 = opt0(1:10) + # seed = [] + # opt0 = {SCIS,XcScale,ABSEPS,RELEPS,COVEPS,MAXPTS,MINPTS,seed,NIT1} + + if (Nx > 1): + # (M,m) or (M,m)v distribution wanted + if ((def_nr == 0 or def_nr == 2)): + asize = [Nx1, Nx1] + else: + # (M,m,TMm), (M,m,TMm)v (M,m,TMd)v or (M,M,Tdm)v distributions wanted + asize = [Nx1, Nx1, Ntime] + elif (def_nr > 3): + # Conditional distribution for (TMd,TMm)v or (Tdm,TMm)v given (M,m) + # wanted + asize = [1, Ntime, Ntime] + else: + # Conditional distribution for (TMm) or (TMm)v given (M,m) wanted + asize = [1, 1, Ntime] + # Initialization + pdf = zeros(asize) + err = zeros(asize) + terr = zeros(asize) + + BIG = zeros(Ntime + Nc + 1, Ntime + Nc + 1) + ex = zeros(1, Ntime + Nc + 1) + # fxind = zeros(Nx,1) + xc = zeros(Nc, Nx) + + indI = zeros(1, NI) + a_up = zeros(1, NI - 1) + a_lo = zeros(1, NI - 1) + + # INFIN = INTEGER, array of integration limits flags: size 1 x Nb (in) + # if INFIN(I) < 0, Ith limits are (-infinity, infinity) + # if INFIN(I) = 0, Ith limits are (-infinity, Hup(I)] + # if INFIN(I) = 1, Ith limits are [Hlo(I), infinity) + # if INFIN(I) = 2, Ith limits are [Hlo(I), Hup(I)]. + # INFIN = repmat(0,1,NI-1) + # INFIN(3) = 1 + a_up[0, 2] = +XdInf + a_lo[0, :2] = [-XtInf, -XdInf] + if (def_nr > 3): + a_lo[0, 3] = -XtInf + + IJ = 0 + if (def_nr <= 1): # Max2min and period/wavelength + for I in range(1, Nx1): + J = IJ + I + xc[2, IJ:J] = hg[I] + xc[3, IJ:J] = hg[:I].T + IJ = J + else: + # Level u separated Max2min + xc[Nc, :] = u + # Hg(1) = Hg(Nx1+1)= u => start do loop at I=2 since by definition + # we must have: minimum u + xc[3, IJ:J] = hg[Nx1 + 2: 2 * Nx1].T # Min < u + IJ = J + if (def_nr <= 3): + # h11 = fwaitbar(0,[],sprintf('Please wait ...(start at: + # %s)',datestr(now))) + + for Ntd in range(Nstart, Ntime): + # Ntd=tn + Ntdc = Ntd + Nc + Nt = Ntd - Nd + indI[1] = Nt + indI[2] = Nt + 1 + indI[3] = Ntd + # positive wave period + BIG[:Ntdc, :Ntdc] = covinput( + BIG[:Ntdc, :Ntdc], R0, R1, R2, R3, R4, Ntd, 0) + [fxind, err0, terr0] = rind(BIG[:Ntdc, :Ntdc], ex[:Ntdc], + a_lo, a_up, indI, xc, Nt, *opt0) + # fxind = CC*rind(BIG(1:Ntdc,1:Ntdc),ex(1:Ntdc),xc,Nt,NIT1, + # speed1,indI,a_lo,a_up) + if (Nx < 2): + # Density of TMm given the Max and the Min. Note that the + # density is not scaled to unity + pdf[0, 0, Ntd] = fxind[0] + err[0, 0, Ntd] = err0[0] ** 2 + terr[0, 0, Ntd] = terr0[0] + # GOTO 100 + else: + IJ = 0 + # joint density of (Ac,At),(M,m_rfc) or (M,m). + if def_nr in [-2, -1, 0]: + for i in range(1, Nx1): + J = IJ + i + pdf[:i, i, 0] = pdf[:i, i, 0] + \ + fxind[IJ:J].T * dt # *CC + err[:i, i, 0] = err[:i, i, 0] + \ + (err0[IJ + 1:J].T * dt) ** 2 + terr[:i, i, 0] = terr[ + :i, i, 0] + (terr0[IJ:J].T * dt) + IJ = J + elif def_nr == 1: # joint density of (M,m,TMm) + for i in range(1, Nx1): + J = IJ + i + pdf[:i, i, Ntd] = fxind[IJ:J].T # %*CC + err[:i, i, Ntd] = (err0[IJ:J].T) ** 2 # %*CC + terr[:i, i, Ntd] = (terr0[IJ:J].T) # %*CC + IJ = J + # end %do + # joint density of level v separated (M,m)v + elif def_nr == 2: + for i in range(1, Nx1): + J = IJ + Nx1 + pdf[1:Nx1, i, 0] = pdf[1:Nx1, i, 0] + \ + fxind[IJ:J].T * dt # %*CC + err[1:Nx1, i, 0] = err[1:Nx1, i, 0] + \ + (err0[IJ:J].T * dt) ** 2 + terr[1:Nx1, i, 0] = terr[ + 1:Nx1, i, 0] + (terr0[IJ:J].T * dt) + IJ = J + # end %do + elif def_nr == 3: + # joint density of level v separated (M,m,TMm)v + for i in range(1, Nx1): + J = IJ + Nx1 + pdf[1:Nx1, i, Ntd] = pdf[ + 1:Nx1, i, Ntd] + fxind[IJ:J].T # %*CC + err[1:Nx1, i, Ntd] = err[ + 1:Nx1, i, Ntd] + (err0[IJ:J].T) ** 2 + terr[1:Nx1, i, Ntd] = terr[ + 1:Nx1, i, Ntd] + (terr0[IJ:J].T) + IJ = J + # end %do + # end % SELECT + # end %ENDIF + # waitTxt = sprintf('%s Ready: %d of %d',datestr(now),Ntd,Ntime) + # fwaitbar(Ntd/Ntime,h11,waitTxt) + + # end %do + # close(h11) + err = sqrt(err) + #% goto 800 + else: # def_nr>3 + #%200 continue + # waitTxt = sprintf('Please wait ...(start at: %s)',datestr(now)) + # h11 = fwaitbar(0,[],waitTxt) + tnold = -1 + for tn in range(Nstart, Ntime): + Ntd = tn + 1 + Ntdc = Ntd + Nc + Nt = Ntd - Nd + indI[1] = Nt + indI[2] = Nt + 1 + indI[3] = Nt + 2 + indI[4] = Ntd + + if not symmetry: # IF (SYMMETRY) GOTO 300 + for ts in range(1, tn - 1): # = 2:tn-1: + # positive wave period + BIG[:Ntdc, :Ntdc] = covinput(BIG[:Ntdc, :Ntdc], + R0, R1, R2, R3, R4, + tn, ts, tnold) + [fxind, err0, terr0] = rind(BIG[:Ntdc, :Ntdc], ex[:Ntdc], + a_lo, a_up, indI, xc, Nt, *opt0) + + # tnold = tn + if def_nr in [3, 4]: + if (Nx == 1): + # Joint density (TMd,TMm) given the Max and the min. + # Note the density is not scaled to unity + pdf[0, ts, tn] = fxind[0] # *CC + err[0, ts, tn] = err0[0] ** 2 # *CC + terr[0, ts, tn] = terr0[0] # *CC + else: + # 4, gives level u separated Max2min and wave period + # from Max to the crossing of level u + # (M,m,TMd). + IJ = 0 + for i in range(1, Nx1): + J = IJ + Nx1 + pdf[1:Nx1, i, ts] = pdf[ + 1:Nx1, i, ts] + fxind[IJ:J].T * dt + err[1:Nx1, i, ts] = err[ + 1:Nx1, i, ts] + (err0[IJ:J].T * dt) ** 2 + terr[1:Nx1, i, ts] = terr[ + 1:Nx1, i, ts] + (terr0[IJ:J].T * dt) + IJ = J + # end %do + # end + elif def_nr == 5: + if (Nx == 1): + #% Joint density (Tdm,TMm) given the Max and the min. + #% Note the density is not scaled to unity + pdf[0, tn - ts, tn] = fxind[0] # %*CC + err[0, tn - ts, tn] = err0[0] ** 2 + terr[0, tn - ts, tn] = terr0[0] + else: + #% 5, gives level u separated Max2min and wave period from + #% the crossing of level u to the min (M,m,Tdm). + + IJ = 0 + for i in range(1, Nx1): # = 2:Nx1 + J = IJ + Nx1 + # %*CC + pdf[1:Nx1, i, tn - ts] = pdf[1:Nx1, + i, tn - ts] + fxind[IJ:J].T * dt + err[1:Nx1, i, tn - ts] = err[1:Nx1, i, + tn - ts] + (err0[IJ:J].T * dt) ** 2 + terr[ + 1:Nx1, i, tn - ts] = terr[1:Nx1, i, tn - ts] + (terr0[IJ:J].T * dt) + IJ = J + # end %do + # end + # end % SELECT + # end% enddo + else: # % exploit symmetry + #%300 Symmetry + for ts in range(1, Ntd // 2): # = 2:floor(Ntd//2) + #% Using the symmetry since U = 0 and the transformation is + #% linear. + #% positive wave period + BIG[:Ntdc, :Ntdc] = covinput(BIG[:Ntdc, :Ntdc], + R0, R1, R2, R3, R4, + tn, ts, tnold) + [fxind, err0, terr0] = rind(BIG[:Ntdc, :Ntdc], ex[:Ntdc], + a_lo, a_up, indI, xc, Nt, *opt0) + + #[fxind,err0] = rind(BIG(1:Ntdc,1:Ntdc),ex,a_lo,a_up,indI, xc,Nt,opt0{:}) + #%tnold = tn + if (Nx == 1): # % THEN + #% Joint density of (TMd,TMm),(Tdm,TMm) given the max and + #% the min. + #% Note that the density is not scaled to unity + pdf[0, ts, tn] = fxind[0] # %*CC + err[0, ts, tn] = err0[0] ** 2 + err[0, ts, tn] = terr0(1) + if (ts < tn - ts): # %THEN + pdf[0, tn - ts, tn] = fxind[0] # *CC + err[0, tn - ts, tn] = err0[0] ** 2 + terr[0, tn - ts, tn] = terr0[0] + # end + #%GOTO 350 + else: + IJ = 0 + if def_nr == 4: + #% 4, gives level u separated Max2min and wave period from + #% Max to the crossing of level u (M,m,TMd). + for i in range(1, Nx1): + J = IJ + Nx1 + pdf[1:Nx1, i, ts] = pdf[ + 1:Nx1, i, ts] + fxind[IJ:J] * dt # %*CC + err[1:Nx1, i, ts] = err[ + 1:Nx1, i, ts] + (err0[IJ:J] * dt) ** 2 + terr[1:Nx1, i, ts] = terr[ + 1:Nx1, i, ts] + (terr0[IJ:J] * dt) + if (ts < tn - ts): + #% exploiting the symmetry + # %*CC + pdf[i, 1:Nx1, tn - ts] = pdf[i, + 1:Nx1, tn - ts] + fxind[IJ:J] * dt + err[i, 1:Nx1, tn - ts] = err[i, 1:Nx1, + tn - ts] + (err0[IJ:J] * dt) ** 2 + terr[ + i, 1:Nx1, tn - ts] = terr[i, 1:Nx1, tn - ts] + (terr0[IJ:J] * dt) + # end + IJ = J + # end %do + elif def_nr == 5: + #% 5, gives level u separated Max2min and wave period + #% from the crossing of level u to min (M,m,Tdm). + for i in range(1, Nx1): # = 2:Nx1, + J = IJ + Nx1 + pdf[1:Nx1, i, tn - ts] = pdf[1:Nx1, + i, tn - ts] + fxind[IJ:J] * dt + err[1:Nx1, i, tn - ts] = err[1:Nx1, i, + tn - ts] + (err0[IJ:J] * dt) ** 2 + terr[ + 1:Nx1, i, tn - ts] = terr[1:Nx1, i, tn - ts] + (terr0[IJ:J] * dt) + if (ts < tn - ts + 1): + # exploiting the symmetry + pdf[i, 1:Nx1, ts] = pdf[ + i, 1:Nx1, ts] + fxind[IJ:J] * dt + err[i, 1:Nx1, ts] = err[ + i, 1:Nx1, ts] + (err0[IJ:J] * dt) ** 2 + terr[i, 1:Nx1, ts] = terr[ + i, 1:Nx1, ts] + (terr0[IJ:J] * dt) + # end %ENDIF + IJ = J + # end %do + # end %END SELECT + # end + #%350 + # end %do + # end + # waitTxt = sprintf('%s Ready: %d of %d',datestr(now),tn,Ntime) + # fwaitbar(tn/Ntime,h11,waitTxt) + # 400 print *,'Ready: ',tn,' of ',Ntime + # end %do + # close(h11) + err = sqrt(err) + # end % if + + #%Nx1,size(pdf) def Ntime + if (Nx > 1): # % THEN + IJ = 1 + if (def_nr > 2 or def_nr == 1): + IJ = Ntime + # end + pdf = pdf[:Nx1, :Nx1, :IJ] + err = err[:Nx1, :Nx1, :IJ] + terr = terr[:Nx1, :Nx1, :IJ] + else: + IJ = 1 + if (def_nr > 3): + IJ = Ntime + # end + pdf = np.squeeze(pdf[0, :IJ, :Ntime]) + err = np.squeeze(err[0, :IJ, :Ntime]) + terr = np.squeeze(terr[0, :IJ, :Ntime]) + # end + return pdf, err, terr, options + + def _covinput_mmt_pdf(self, BIG, R, tn, ts, tnold=-1): + """ + COVINPUT Sets up the covariance matrix + + CALL BIG = covinput(BIG, R0,R1,R2,R3,R4,tn,ts) + + BIG = covariance matrix for X = [Xt,Xd,Xc] in spec2mmtpdf problems. + + The order of the variables in the covariance matrix are organized as + follows: + for ts <= 1: + X'(t2)..X'(ts),...,X'(tn-1) X''(t1),X''(tn) X'(t1),X'(tn),X(t1),X(tn) + = [ Xt | Xd | Xc ] + + for ts > =2: + X'(t2)..X'(ts),...,X'(tn-1) X''(t1),X''(tn) X'(ts) X'(t1),X'(tn),X(t1),X(tn) X(ts) + = [ Xt | Xd | Xc ] + + where + + Xt = time points in the indicator function + Xd = derivatives + Xc = variables to condition on + + Computations of all covariances follows simple rules: Cov(X(t),X(s)) = r(t,s), + then Cov(X'(t),X(s))=dr(t,s)/dt. Now for stationary X(t) we have + a function r(tau) such that Cov(X(t),X(s))=r(s-t) (or r(t-s) will give the same result). + + Consequently Cov(X'(t),X(s)) = -r'(s-t) = -sign(s-t)*r'(|s-t|) + Cov(X'(t),X'(s)) = -r''(s-t) = -r''(|s-t|) + Cov(X''(t),X'(s)) = r'''(s-t) = sign(s-t)*r'''(|s-t|) + Cov(X''(t),X(s)) = r''(s-t) = r''(|s-t|) + Cov(X''(t),X''(s)) = r''''(s-t) = r''''(|s-t|) + """ + R0, R1, R2, R3, R4 = R.T + if (ts > 1): + shft = 1 + N = tn + 5 + shft + # Cov(Xt,Xc) + # for + i = np.arange(tn - 2) # 1:tn-2 + #%j = abs(i+1-ts) + #%BIG(i,N) = -sign(R1(j+1),R1(j+1)*dble(ts-i-1)) %cov(X'(ti+1),X(ts)) + j = i + 1 - ts + tau = abs(j) + #%BIG(i,N) = abs(R1(tau)).*sign(R1(tau).*j.') + BIG[i, N] = R1[tau] * sign(j) + #%end do + #%Cov(Xc) + BIG[N, N] = R0[0] # cov(X(ts),X(ts)) + BIG[tn + shft + 1, N] = -R1[ts] # cov(X'(t1),X(ts)) + BIG[tn + shft + 2, N] = R1[tn - ts] # cov(X'(tn),X(ts)) + BIG[tn + shft + 3, N] = R0[ts] # cov(X(t1),X(ts)) + BIG[tn + shft + 4, N] = R0[tn - ts] # cov(X(tn),X(ts)) + # Cov(Xd,Xc) + BIG[tn - 1, N] = R2[ts] # %cov(X''(t1),X(ts)) + BIG[tn, N] = R2[tn - ts] # %cov(X''(tn),X(ts)) + + #%ADD a level u crossing at ts + + #%Cov(Xt,Xd) + #%for + i = np.arange(tn - 2) # 1:tn-2 + j = abs(i + 1 - ts) + BIG[i, tn + shft] = -R2[j] # %cov(X'(ti+1),X'(ts)) + #%end do + #%Cov(Xd) + BIG[tn + shft, tn + shft] = -R2[0] # %cov(X'(ts),X'(ts)) + BIG[tn - 1, tn + shft] = R3[ts] # %cov(X''(t1),X'(ts)) + BIG[tn, tn + shft] = -R3[tn - ts] # %cov(X''(tn),X'(ts)) + + #%Cov(Xd,Xc) + BIG[tn + shft, N] = 0.0 # %cov(X'(ts),X(ts)) + # % cov(X'(ts),X'(t1)) + BIG[tn + shft, tn + shft + 1] = -R2[ts] + # % cov(X'(ts),X'(tn)) + BIG[tn + shft, tn + shft + 2] = -R2[tn - ts] + BIG[tn + shft, tn + shft + 3] = R1[ts] # % cov(X'(ts),X(t1)) + # % cov(X'(ts),X(tn)) + BIG[tn + shft, tn + shft + 4] = -R1[tn - ts] + + if (tnold == tn): + #% A previous call to covinput with tn==tnold has been made + #% need only to update row and column N and tn+1 of big: + return BIG + #% % make lower triangular part equal to upper and then return + #% for j=1:tn+shft + #% BIG(N,j) = BIG(j,N) + #% BIG(tn+shft,j) = BIG(j,tn+shft) + #% end + #% for j=tn+shft+1:N-1 + #% BIG(N,j) = BIG(j,N) + #% BIG(j,tn+shft) = BIG(tn+shft,j) + #% end + #% return + # end %if + # %tnold = tn + else: + # N = tn+4 + shft = 0 + # end %if + + if (tn > 2): + #%for i=1:tn-2 + #%cov(Xt) + #% for j=i:tn-2 + #% BIG(i,j) = -R2(j-i+1) % cov(X'(ti+1),X'(tj+1)) + #% end %do + + # % cov(Xt) = % cov(X'(ti+1),X'(tj+1)) + BIG[:tn - 2, :tn - 2] = toeplitz(-R2[:tn - 2]) + + # cov(Xt,Xc) + BIG[:tn - 2, tn + shft] = -R2[1:tn - 1] # cov(X'(ti+1),X'(t1)) + # cov(X'(ti+1),X'(tn)) + BIG[:tn - 2, tn + shft + 1] = -R2[tn - 2:0:-1] + BIG[:tn - 2, tn + shft + 2] = R1[1:tn - 1] # cov(X'(ti+1),X(t1)) + # cov(X'(ti+1),X(tn)) + BIG[:tn - 2, tn + shft + 3] = -R1[tn - 2:0:-1] + + #%Cov(Xt,Xd) + BIG[:tn - 2, tn - 2] = R3[1:tn - 1] # cov(X'(ti+1),X''(t1)) + BIG[:tn - 2, tn - 1] = -R3[tn - 2:0:-1] # cov(X'(ti+1),X''(tn)) + #%end %do + # end + #%cov(Xd) + BIG[tn - 2, tn - 2] = R4[0] + BIG[tn - 2, tn - 1] = R4[tn - 1] # cov(X''(t1),X''(tn)) + BIG[tn - 1, tn - 1] = R4[0] + + #%cov(Xc) + BIG[tn + shft + 2, tn + shft + 2] = R0[0] # cov(X(t1),X(t1)) + # cov(X(t1),X(tn)) + BIG[tn + shft + 2, tn + shft + 3] = R0[tn - 1] + BIG[tn + shft + 1, tn + shft + 2] = 0.0 # cov(X(t1),X'(t1)) + # cov(X(t1),X'(tn)) + BIG[tn + shft + 1, tn + shft + 2] = R1[tn - 1] + BIG[tn + shft + 3, tn + shft + 3] = R0[0] # cov(X(tn),X(tn)) + BIG[tn + shft, tn + shft + 3] = -R1[tn - 1] # cov(X(tn),X'(t1)) + BIG[tn + shft + 1, tn + shft + 3] = 0.0 # cov(X(tn),X'(tn)) + BIG[tn + shft, tn + shft] = -R2[0] # cov(X'(t1),X'(t1)) + BIG[tn + shft, tn + shft + 1] = -R2[tn - 1] # cov(X'(t1),X'(tn)) + BIG[tn + shft + 1, tn + shft + 1] = -R2[0] # cov(X'(tn),X'(tn)) + # Xc=X(t1),X(tn),X'(t1),X'(tn) + # Xd=X''(t1),X''(tn) + # cov(Xd,Xc) + BIG[tn - 2, tn + shft + 2] = R2[0] # cov(X''(t1),X(t1)) + BIG[tn - 2, tn + shft + 3] = R2[tn - 1] # cov(X''(t1),X(tn)) + BIG[tn - 2, tn + shft] = 0.0 # cov(X''(t1),X'(t1)) + BIG[tn - 2, tn + shft + 1] = R3[tn - 1] # cov(X''(t1),X'(tn)) + BIG[tn - 1, tn + shft + 2] = R2[tn - 1] # cov(X''(tn),X(t1)) + BIG[tn - 1, tn + shft + 3] = R2[0] # cov(X''(tn),X(tn)) + BIG[tn - 1, tn + shft] = -R3[tn - 1] # cov(X''(tn),X'(t1)) + BIG[tn - 1, tn + shft + 1] = 0.0 # cov(X''(tn),X'(tn)) + + # make lower triangular part equal to upper + # for j=1:N-1 + # for i=j+1:N + # BIG(i,j) = BIG(j,i) + # end #do + # end #do + lp = np.flatnonzero(np.tril(ones(BIG.shape))) # indices to lower triangular part + BIGT = BIG.T + BIG[lp] = BIGT[lp] + return BIG + # END SUBROUTINE COV_INPUT + + def _cov2mmtpdfexe(self, R, dt, u, defnr, Nstart, hg, options): + # Write parameters to file + Nx = max(1, len(hg)) + if defnr > 1: + Nx = Nx // 2 # level v separated max2min densities wanted + + Ntime = R.shape[0] + + filenames = ['h.in', 'reflev.in'] + self._cleanup(*filenames) + + with open('h.in', 'wt') as f: + f.write('%12.10f\n', hg) + + # XSPLT = options.xsplit + nit = options.nit + speed = options.speed + seed = options.seed + SCIS = abs(options.method) # method<=0 + + with open('reflev.in', 'wt') as fid: + fid.write('%2.0f \n', Ntime) + fid.write('%2.0f \n', Nstart) + fid.write('%2.0f \n', nit) + fid.write('%2.0f \n', speed) + fid.write('%2.0f \n', SCIS) + fid.write('%2.0f \n', seed) + fid.write('%2.0f \n', Nx) + fid.write('%12.10E \n', dt) + fid.write('%12.10E \n', u) + fid.write('%2.0f \n', defnr) + + filenames2 = self._writecov(R) + + print(' Starting Fortran executable.') + #compiled cov2mmtpdf.f with rind70.f + #dos([ wafoexepath 'cov2mmtpdf.exe']) + + dens = 1 #load('dens.out') + + self._cleanup(*filenames) + self._cleanup(*filenames2) + + return dens + + def _cleanup(self, *files): + '''Removes files from harddisk if they exist''' + for f in files: + if os.path.exists(f): + os.remove(f) + + def to_specnorm(self): + S = self.copy() + S.normalize() + return S + + def sim(self, ns=None, cases=1, dt=None, iseed=None, method='random', + derivative=False): + ''' Simulates a Gaussian process and its derivative from spectrum + + Parameters + ---------- + ns : scalar + number of simulated points. (default length(spec)-1=n-1). + If ns>n-1 it is assummed that acf(k)=0 for all k>n-1 + cases : scalar + number of replicates (default=1) + dt : scalar + step in grid (default dt is defined by the Nyquist freq) + iseed : int or state + starting state/seed number for the random number generator + (default none is set) + method : string + if 'exact' : simulation using cov2sdat + if 'random' : random phase and amplitude simulation (default) + derivative : bool + if true : return derivative of simulated signal as well + otherwise + + Returns + ------- + xs = a cases+1 column matrix ( t,X1(t) X2(t) ...). + xsder = a cases+1 column matrix ( t,X1'(t) X2'(t) ...). + + Details + ------- + Performs a fast and exact simulation of stationary zero mean + Gaussian process through circulant embedding of the covariance matrix + or by summation of sinus functions with random amplitudes and random + phase angle. + + If the spectrum has a non-empty field .tr, then the transformation is + applied to the simulated data, the result is a simulation of a + transformed Gaussian process. + + Note: The method 'exact' simulation may give high frequency ripple when + used with a small dt. In this case the method 'random' works better. + + Example: + >>> import wafo.spectrum.models as sm + >>> Sj = sm.Jonswap();S = Sj.tospecdata() + >>> ns =100; dt = .2 + >>> x1 = S.sim(ns,dt=dt) + + >>> import numpy as np + >>> import scipy.stats as st + >>> x2 = S.sim(20000,20) + >>> truth1 = [0,np.sqrt(S.moment(1)[0]),0., 0.] + >>> funs = [np.mean,np.std,st.skew,st.kurtosis] + >>> for fun,trueval in zip(funs,truth1): + ... res = fun(x2[:,1::],axis=0) + ... m = res.mean() + ... sa = res.std() + ... #trueval, m, sa + ... np.abs(m-trueval) T) + + # Trick to avoid adding high frequency noise to the spectrum + if i.size > 0: + acf.data[i[0]::] = 0.0 + + return acf.sim(ns=ns, cases=cases, iseed=iseed, + derivative=derivative) + + _set_seed(iseed) + + ns = ns + mod(ns, 2) # make sure it is even + + f_i = freq[1:-1] + s_i = spec.data[1:-1] + if ftype in ('w', 'k'): + fact = 2. * pi + s_i = s_i * fact + f_i = f_i / fact + + x = zeros((ns, cases + 1)) + + d_f = 1 / (ns * d_t) + + # interpolate for freq. [1:(N/2)-1]*d_f and create 2-sided, uncentered + # spectra + f = arange(1, ns / 2.) * d_f + + f_u = hstack((0., f_i, d_f * ns / 2.)) + s_u = hstack((0., abs(s_i) / 2., 0.)) + + s_i = interp(f, f_u, s_u) + s_u = hstack((0., s_i, 0, s_i[(ns / 2) - 2::-1])) + del(s_i, f_u) + + # Generate standard normal random numbers for the simulations + randn = random.randn + z_r = randn((ns / 2) + 1, cases) + z_i = vstack( + (zeros((1, cases)), randn((ns / 2) - 1, cases), zeros((1, cases)))) + + amp = zeros((ns, cases), dtype=complex) + amp[0:(ns / 2 + 1), :] = z_r - 1j * z_i + del(z_r, z_i) + amp[(ns / 2 + 1):ns, :] = amp[ns / 2 - 1:0:-1, :].conj() + amp[0, :] = amp[0, :] * sqrt(2.) + amp[(ns / 2), :] = amp[(ns / 2), :] * sqrt(2.) + + # Make simulated time series + T = (ns - 1) * d_t + Ssqr = sqrt(s_u * d_f / 2.) + + # stochastic amplitude + amp = amp * Ssqr[:, newaxis] + + # Deterministic amplitude + # amp = + # sqrt[1]*Ssqr(:,ones(1,cases)) * \ + # exp(sqrt(-1)*atan2(imag(amp),real(amp))) + del(s_u, Ssqr) + + x[:, 1::] = fft(amp, axis=0).real + x[:, 0] = linspace(0, T, ns) # ' %(0:d_t:(np-1)*d_t).' + + if derivative: + xder = zeros(ns, cases + 1) + w = 2. * pi * hstack((0, f, 0., -f[-1::-1])) + amp = -1j * amp * w[:, newaxis] + xder[:, 1:(cases + 1)] = fft(amp, axis=0).real + xder[:, 0] = x[:, 0] + + if spec.tr is not None: + # print(' Transforming data.') + g = spec.tr + if derivative: + for i in range(cases): + x[:, i + 1], xder[:, i + 1] = g.gauss2dat(x[:, i + 1], + xder[:, i + 1]) + else: + for i in range(cases): + x[:, i + 1] = g.gauss2dat(x[:, i + 1]) + + if derivative: + return x, xder + else: + return x + +# function [x2,x,svec,dvec,amp]=spec2nlsdat(spec,np,dt,iseed,method, +# truncationLimit) + def sim_nl(self, ns=None, cases=1, dt=None, iseed=None, method='random', + fnlimit=1.4142, reltol=1e-3, g=9.81, verbose=False): + """ + Simulates a Randomized 2nd order non-linear wave X(t) + + Parameters + ---------- + ns : scalar + number of simulated points. (default length(spec)-1=n-1). + If ns>n-1 it is assummed that R(k)=0 for all k>n-1 + cases : scalar + number of replicates (default=1) + dt : scalar + step in grid (default dt is defined by the Nyquist freq) + iseed : int or state + starting state/seed number for the random number generator + (default none is set) + method : string + 'apStochastic' : Random amplitude and phase (default) + 'aDeterministic' : Deterministic amplitude and random phase + 'apDeterministic' : Deterministic amplitude and phase + fnlimit : scalar + normalized upper frequency limit of spectrum for 2'nd order + components. The frequency is normalized with + sqrt(gravity*tanh(kbar*water_depth)/amp_max)/(2*pi) + (default sqrt(2), i.e., Convergence criterion [1]_). + Other possible values are: + sqrt(1/2) : No bump in trough criterion + sqrt(pi/7) : Wave steepness criterion + reltol : scalar + relative tolerance defining where to truncate spectrum for the + sum and difference frequency effects + + + Returns + ------- + xs2 = a cases+1 column matrix ( t,X1(t) X2(t) ...). + xs1 = a cases+1 column matrix ( t,X1'(t) X2'(t) ...). + + Details + ------- + Performs a Fast simulation of Randomized 2nd order non-linear + waves by summation of sinus functions with random amplitudes and + phase angles. The extent to which the simulated result are applicable + to real seastates are dependent on the validity of the assumptions: + + 1. Seastate is unidirectional + 2. Surface elevation is adequately represented by 2nd order random + wave theory + 3. The first order component of the surface elevation is a Gaussian + random process. + + If the spectrum does not decay rapidly enough towards zero, the + contribution from the 2nd order wave components at the upper tail can + be very large and unphysical. To ensure convergence of the perturbation + series, the upper tail of the spectrum is truncated at FNLIMIT in the + calculation of the 2nd order wave components, i.e., in the calculation + of sum and difference frequency effects. This may also be combined with + the elimination of second order effects from the spectrum, i.e., + extract the linear components from the spectrum. One way to do this is + to use SPEC2LINSPEC. + + Example + -------- + >>> import wafo.spectrum.models as sm + >>> Sj = sm.Jonswap();S = Sj.tospecdata() + >>> ns =100; dt = .2 + >>> x1 = S.sim_nl(ns,dt=dt) + + >>> import numpy as np + >>> import scipy.stats as st + >>> x2, x1 = S.sim_nl(ns=20000,cases=20) + >>> truth1 = [0,np.sqrt(S.moment(1)[0][0])] + S.stats_nl(moments='sk') + >>> truth1[-1] = truth1[-1]-3 + >>> np.round(truth1, 3) + array([ 0. , 1.75 , 0.187, 0.062]) + + >>> funs = [np.mean,np.std,st.skew,st.kurtosis] + >>> for fun,trueval in zip(funs,truth1): + ... res = fun(x2[:,1::], axis=0) + ... m = res.mean() + ... sa = res.std() + ... #trueval, m, sa + ... np.abs(m-trueval)>> x = [] + >>> for i in range(20): + ... x2, x1 = S.sim_nl(ns=20000,cases=1) + ... x.append(x2[:,1::]) + >>> x2 = np.hstack(x) + >>> truth1 = [0,np.sqrt(S.moment(1)[0][0])] + S.stats_nl(moments='sk') + >>> truth1[-1] = truth1[-1]-3 + >>> np.round(truth1,3) + array([ 0. , 1.75 , 0.187, 0.062]) + + >>> funs = [np.mean,np.std,st.skew,st.kurtosis] + >>> for fun,trueval in zip(funs,truth1): + ... res = fun(x2, axis=0) + ... m = res.mean() + ... sa = res.std() + ... #trueval, m, sa + ... np.abs(m-trueval) s_max * reltol).argmax() + nmax = flatnonzero(s_i > 0).max() + s_u = hstack((0., s_i, 0, s_i[(ns / 2) - 2::-1])) + del(s_i, f_u) + + # Generate standard normal random numbers for the simulations + randn = random.randn + z_r = randn((ns / 2) + 1, cases) + z_i = vstack((zeros((1, cases)), + randn((ns / 2) - 1, cases), + zeros((1, cases)))) + + amp = zeros((ns, cases), dtype=complex) + amp[0:(ns / 2 + 1), :] = z_r - 1j * z_i + del(z_r, z_i) + amp[(ns / 2 + 1):ns, :] = amp[ns / 2 - 1:0:-1, :].conj() + amp[0, :] = amp[0, :] * sqrt(2.) + amp[(ns / 2), :] = amp[(ns / 2), :] * sqrt(2.) + + # Make simulated time series + T = (ns - 1) * d_t + Ssqr = sqrt(s_u * df / 2.) + + if method.startswith('apd'): # apdeterministic + # Deterministic amplitude and phase + amp[1:(ns / 2), :] = amp[1, 0] + amp[(ns / 2 + 1):ns, :] = amp[1, 0].conj() + amp = sqrt(2) * Ssqr[:, newaxis] * \ + exp(1J * arctan2(amp.imag, amp.real)) + elif method.startswith('ade'): # adeterministic + # Deterministic amplitude and random phase + amp = sqrt(2) * Ssqr[:, newaxis] * \ + exp(1J * arctan2(amp.imag, amp.real)) + else: + # stochastic amplitude + amp = amp * Ssqr[:, newaxis] + # Deterministic amplitude + # amp = + # sqrt(2)*Ssqr(:,ones(1,cases))* \ + # exp(sqrt(-1)*atan2(imag(amp),real(amp))) + del(s_u, Ssqr) + + x[:, 1::] = fft(amp, axis=0).real + x[:, 0] = linspace(0, T, ns) # ' %(0:d_t:(np-1)*d_t).' + + x2 = x.copy() + + # If the spectrum does not decay rapidly enough towards zero, the + # contribution from the wave components at the upper tail can be very + # large and unphysical. + # To ensure convergence of the perturbation series, the upper tail of + # the spectrum is truncated in the calculation of sum and difference + # frequency effects. + # Find the critical wave frequency to ensure convergence. + + num_waves = 1000. # Typical number of waves in 3 hour seastate + kbar = w2k(2. * pi / Tm02, 0., water_depth)[0] + # Expected maximum amplitude for 1000 waves seastate + amp_max = sqrt(2 * log(num_waves)) * Hm0 / 4 + + f_limit_up = fnlimit * \ + sqrt(g * tanh(kbar * water_depth) / amp_max) / (2 * pi) + f_limit_lo = sqrt(g * tanh(kbar * water_depth) * + amp_max / water_depth) / (2 * pi * water_depth) + + nmax = min(flatnonzero(f <= f_limit_up).max(), nmax) + 1 + nmin = max(flatnonzero(f_limit_lo <= f).min(), nmin) + 1 + + # if isempty(nmax),nmax = np/2end + # if isempty(nmin),nmin = 2end % Must always be greater than 1 + f_limit_up = df * nmax + f_limit_lo = df * nmin + if verbose: + print('2nd order frequency Limits = %g,%g' % + (f_limit_lo, f_limit_up)) + +# if nargout>3, +# %compute the sum and frequency effects separately +# [svec, dvec] = disufq((amp.'),w,kw,min(h,10^30),g,nmin,nmax) +# svec = svec.' +# dvec = dvec.' +## +# x2s = fft(svec) % 2'nd order sum frequency component +# x2d = fft(dvec) % 2'nd order difference frequency component +## +# % 1'st order + 2'nd order component. +# x2(:,2:end) =x(:,2:end)+ real(x2s(1:np,:))+real(x2d(1:np,:)) +# else + if False: + # TODO: disufq does not work for cases>1 + amp = np.array(amp.T).ravel() + rvec, ivec = c_library.disufq(amp.real, amp.imag, w, kw, + water_depth, + g, nmin, nmax, cases, ns) + svec = rvec + 1J * ivec + else: + amp = amp.T + svec = [] + for i in range(cases): + rvec, ivec = c_library.disufq(amp[i].real, amp[i].imag, w, kw, + water_depth, + g, nmin, nmax, 1, ns) + svec.append(rvec + 1J * ivec) + svec = np.hstack(svec) + svec.shape = (cases, ns) + x2o = fft(svec, axis=1).T # 2'nd order component + + # 1'st order + 2'nd order component. + x2[:, 1::] = x[:, 1::] + x2o[0:ns, :].real + + return x2, x + + def stats_nl(self, h=None, moments='sk', method='approximate', g=9.81): + """ + Statistics of 2'nd order waves to the leading order. + + Parameters + ---------- + h : scalar + water depth (default self.h) + moments : string (default='sk') + composed of letters ['mvsk'] specifying which moments to compute: + 'm' = mean, + 'v' = variance, + 's' = skewness, + 'k' = (Pearson's) kurtosis. + method : string + 'approximate' method due to Marthinsen & Winterstein (default) + 'eigenvalue' method due to Kac and Siegert + + Skewness = kurtosis-3 = 0 for a Gaussian process. + The mean, sigma, skewness and kurtosis are determined as follows: + method == 'approximate': due to Marthinsen and Winterstein + mean = 2 * int Hd(w1,w1)*S(w1) dw1 + sigma = sqrt(int S(w1) dw1) + skew = 6 * int int [Hs(w1,w2)+Hd(w1,w2)]*S(w1)*S(w2) dw1*dw2/m0^(3/2) + kurt = (4*skew/3)^2 + + where Hs = sum frequency effects and Hd = difference frequency effects + + method == 'eigenvalue' + + mean = sum(E) + sigma = sqrt(sum(C^2)+2*sum(E^2)) + skew = sum((6*C^2+8*E^2).*E)/sigma^3 + kurt = 3+48*sum((C^2+E^2).*E^2)/sigma^4 + + where + h1 = sqrt(S*dw/2) + C = (ctranspose(V)*[h1;h1]) + and E and V is the eigenvalues and eigenvectors, respectively, of the + 2'order transfer matrix. + S is the spectrum and dw is the frequency spacing of S. + + Example: + -------- + # Simulate a Transformed Gaussian process: + >>> import wafo.spectrum.models as sm + >>> import wafo.transform.models as wtm + >>> Hs = 7. + >>> Sj = sm.Jonswap(Hm0=Hs, Tp=11) + >>> S = Sj.tospecdata() + >>> me, va, sk, ku = S.stats_nl(moments='mvsk') + >>> g = wtm.TrHermite(mean=me, sigma=Hs/4, skew=sk, kurt=ku, + ... ysigma=Hs/4) + >>> ys = S.sim(15000) # Simulated in the Gaussian world + >>> xs = g.gauss2dat(ys[:,1]) # Transformed to the real world + + + See also + --------- + transform.TrHermite + transform.TrOchi + objects.LevelCrossings.trdata + objects.TimeSeries.trdata + + References: + ----------- + Langley, RS (1987) + 'A statistical analysis of nonlinear random waves' + Ocean Engineering, Vol 14, No 5, pp 389-407 + + Marthinsen, T. and Winterstein, S.R (1992) + 'On the skewness of random surface waves' + In proceedings of the 2nd ISOPE Conference, San Francisco, 14-19 june. + + Winterstein, S.R, Ude, T.C. and Kleiven, G. (1994) + 'Springing and slow drift responses: + predicted extremes and fatigue vs. simulation' + In Proc. 7th International behaviour of Offshore structures, (BOSS) + Vol. 3, pp.1-15 + """ + + #% default options + if h is None: + h = self.h + + # S = ttspec(S,'w') + w = ravel(self.args) + S = ravel(self.data) + if self.freqtype in ['f', 'w']: + # vari = 't' + if self.freqtype == 'f': + w = 2. * pi * w + S = S / (2. * pi) + # m0 = self.moment(nr=0) + m0 = simps(S, w) + sa = sqrt(m0) + # Nw = w.size + + Hs, Hd, Hdii = qtf(w, h, g) + + # return + # skew=6/sqrt(m0)^3*simpson(S.w, + # simpson(S.w,(Hs+Hd).*S1(:,ones(1,Nw))).*S1.') + + Hspd = trapz(trapz((Hs + Hd) * S[newaxis, :], w) * S, w) + output = [] + # %approx : Marthinsen, T. and Winterstein, S.R (1992) method + if method[0] == 'a': + if 'm' in moments: + output.append(2. * trapz(Hdii * S, w)) + if 'v' in moments: + output.append(m0) + skew = 6. / sa ** 3 * Hspd + if 's' in moments: + output.append(skew) + if 'k' in moments: + output.append((4. * skew / 3.) ** 2. + 3.) + else: + raise ValueError('Unknown option!') + +# elif method[0]== 'q': #, #% quasi method +# Fn = self.nyquist_freq() +# dw = Fn/Nw +# tmp1 =sqrt(S[:,newaxis]*S[newaxis,:])*dw +# Hd = Hd*tmp1 +# Hs = Hs*tmp1 +# k = 6 +# stop = 0 +# while !stop: +# E = eigs([Hd,Hs;Hs,Hd],[],k) +# %stop = (length(find(abs(E)<1e-4))>0 | k>1200) +# %stop = (any(abs(E(:))<1e-4) | k>1200) +# stop = (any(abs(E(:))<1e-4) | k>=min(2*Nw,1200)) +# k = min(2*k,2*Nw) +# end +## +## +# m02=2*sum(E.^2) % variance of 2'nd order contribution +## +# %Hstd = 16*trapz(S.w,(Hdii.*S1).^2) +# %Hstd = trapz(S.w,trapz(S.w,((Hs+Hd)+ 2*Hs.*Hd).*S1(:,ones(1,Nw))).*S1.') +# ma = 2*trapz(S.w,Hdii.*S1) +# %m02 = Hstd-ma^2% variance of second order part +# sa = sqrt(m0+m02) +# skew = 6/sa^3*Hspd +# kurt = (4*skew/3).^2+3 +# elif method[0]== 'e': #, % Kac and Siegert eigenvalue analysis +# Fn = self.nyquist_freq() +# dw = Fn/Nw +# tmp1 =sqrt(S[:,newaxis]*S[newaxis,:])*dw +# Hd = Hd*tmp1 +# Hs = Hs*tmp1 +# k = 6 +# stop = 0 +## +## +# while (not stop): +# [V,D] = eigs([Hd,HsHs,Hd],[],k) +# E = diag(D) +# %stop = (length(find(abs(E)<1e-4))>0 | k>=min(2*Nw,1200)) +# stop = (any(abs(E(:))<1e-4) | k>=min(2*Nw,1200)) +# k = min(2*k,2*Nw) +# end +## +## +# h1 = sqrt(S*dw/2) +# C = (ctranspose(V)*[h1;h1]) +## +# E2 = E.^2 +# C2 = C.^2 +## +# ma = sum(E) % mean +# sa = sqrt(sum(C2)+2*sum(E2)) % standard deviation +# skew = sum((6*C2+8*E2).*E)/sa^3 % skewness +# kurt = 3+48*sum((C2+E2).*E2)/sa^4 % kurtosis + return output + + def testgaussian(self, ns, test0=None, cases=100, method='nonlinear', + verbose=False, **opt): + ''' + TESTGAUSSIAN Test if a stochastic process is Gaussian. + + CALL: test1 = testgaussian(S,[ns,Ns],test0,def,options) + + Returns + ------- + test1 : array, + simulated values of e(g)=int (g(u)-u)^2 du, where int limits is + given by OPTIONS.PARAM. + + Parameters + ---------- + ns : int + # of points simulated + test0 : real scalar + observed value of e(g)=int (g(u)-u)^2 du, + cases : int + # of independent simulations (default 100) + method : string + defines method of estimation of the transform + nonlinear': from smoothed crossing intensity (default) + 'mnonlinear': from smoothed marginal distribution + options = options structure defining how the estimation of the + transformation is done. (default troptset('dat2tr')) + + TESTGAUSSIAN simulates e(g(u)-u) = int (g(u)-u)^2 du for Gaussian + processes given the spectral density, S. The result is plotted if + test0 is given. This is useful for testing if the process X(t) is + Gaussian. If 95% of TEST1 is less than TEST0 then X(t) is not Gaussian + at a 5% level. + + Example: + ------- + >>> import wafo.spectrum.models as sm + >>> import wafo.transform.models as wtm + >>> import wafo.objects as wo + >>> Hs = 7 + >>> Sj = sm.Jonswap(Hm0=Hs) + >>> S0 = Sj.tospecdata() + >>> ns =100; dt = .2 + >>> x1 = S0.sim(ns, dt=dt) + + >>> S = S0.copy() + >>> me, va, sk, ku = S.stats_nl(moments='mvsk') + >>> S.tr = wtm.TrHermite(mean=me, sigma=Hs/4, skew=sk, kurt=ku, + ... ysigma=Hs/4) + >>> ys = wo.mat2timeseries(S.sim(ns=2**13)) + >>> g0, gemp = ys.trdata() + >>> t0 = g0.dist2gauss() + >>> t1 = S0.testgaussian(ns=2**13, t0=t0, cases=50) + >>> sum(t1 > t0) < 5 + True + + See also + -------- + cov2sdat, dat2tr, troptset + ''' + + maxsize = 200000 # must divide the computations due to limited memory +# if nargin<5||isempty(opt): +# opt = troptset('dat2tr') +# opt = troptset(opt,'multip',1) + + plotflag = False if test0 is None else True + if cases > 50: + print(' ... be patient this may take a while') + + rep = int(ns * cases / maxsize) + 1 + Nstep = int(cases / rep) + + acf = self.tocovdata() + test1 = [] + for ix in range(rep): + xs = acf.sim(ns=ns, cases=Nstep) + for iy in range(1, xs.shape[-1]): + ts = TimeSeries(xs[:, iy], xs[:, 0].ravel()) + g, _tmp = ts.trdata(method, **opt) + test1.append(g.dist2gauss()) + if verbose: + print('finished %d of %d ' % (ix + 1, rep)) + + if rep > 1: + xs = acf.sim(ns=ns, cases=np.remainder(cases, rep)) + for iy in range(1, xs.shape[-1]): + ts = TimeSeries(xs[:, iy], xs[:, 0].ravel()) + g, _tmp = ts.trdata(method, **opt) + test1.append(g.dist2gauss()) + + if plotflag: + plotbackend.plot(test1, 'o') + plotbackend.plot([1, cases], [test0, test0], '--') + + plotbackend.ylabel('e(g(u)-u)') + plotbackend.xlabel('Simulation number') + return test1 + + def moment(self, nr=2, even=True, j=0): + ''' Calculates spectral moments from spectrum + + Parameters + ---------- + nr : int + order of moments (recomended maximum 4) + even : bool + False for all moments, + True for only even orders + j : int + 0 or 1 + + Returns + ------- + m : list of moments + mtext : list of strings describing the elements of m, see below + + Details + ------- + Calculates spectral moments of up to order NR by use of + Simpson-integration. + + / / + mj_t^i = | w^i S(w)^(j+1) dw, or mj_x^i = | k^i S(k)^(j+1) dk + / / + + where k=w^2/gravity, i=0,1,...,NR + + The strings in output mtext have the same position in the list + as the corresponding numerical value has in output m + Notation in mtext: 'm0' is the variance, + 'm0x' is the first-order moment in x, + 'm0xx' is the second-order moment in x, + 'm0t' is the first-order moment in t, + etc. + For the calculation of moments see Baxevani et al. + + Example: + >>> import numpy as np + >>> import wafo.spectrum.models as sm + >>> Sj = sm.Jonswap(Hm0=3, Tp=7) + >>> w = np.linspace(0,4,256) + >>> S = SpecData1D(Sj(w),w) #Make spectrum object from numerical values + >>> S.moment() + ([0.5616342024616453, 0.7309966918203602], ['m0', 'm0tt']) + + References + ---------- + Baxevani A. et al. (2001) + Velocities for Random Surfaces + ''' + one_dim_spectra = ['freq', 'enc', 'k1d'] + if self.type not in one_dim_spectra: + raise ValueError('Unknown spectrum type!') + + f = ravel(self.args) + S = ravel(self.data) + if self.freqtype in ['f', 'w']: + vari = 't' + if self.freqtype == 'f': + f = 2. * pi * f + S = S / (2. * pi) + else: + vari = 'x' + S1 = abs(S) ** (j + 1.) + m = [simps(S1, x=f)] + mtxt = 'm%d' % j + mtext = [mtxt] + step = mod(even, 2) + 1 + df = f ** step + for i in range(step, nr + 1, step): + S1 = S1 * df + m.append(simps(S1, x=f)) + mtext.append(mtxt + vari * i) + return m, mtext + + def nyquist_freq(self): + """ + Return Nyquist frequency + + Example + ------- + >>> import wafo.spectrum.models as sm + >>> Sj = sm.Jonswap(Hm0=5) + >>> S = Sj.tospecdata() #Make spectrum ob + >>> S.nyquist_freq() + 3.0 + """ + return self.args[-1] + + def sampling_period(self): + ''' Returns sampling interval from Nyquist frequency of spectrum + + Returns + --------- + dT : scalar + sampling interval, unit: + [m] if wave number spectrum, + [s] otherwise + + Let wm be maximum frequency/wave number in spectrum, then + dT=pi/wm + if angular frequency, + dT=1/(2*wm) + if natural frequency (Hz) + + Example + ------- + >>> import wafo.spectrum.models as sm + >>> Sj = sm.Jonswap(Hm0=5) + >>> S = Sj.tospecdata() #Make spectrum ob + >>> S.sampling_period() + 1.0471975511965976 + + See also + ''' + + if self.freqtype == 'f': + wmdt = 0.5 # Nyquist to sampling interval factor + else: # ftype == w og ftype == k + wmdt = pi + + wm = self.args[-1] # Nyquist frequency + dt = wmdt / wm # sampling interval = 1/Fs + return dt + + def resample(self, dt=None, Nmin=0, Nmax=2 ** 13 + 1, method='stineman'): + ''' + Interpolate and zero-padd spectrum to change Nyquist freq. + + Parameters + ---------- + dt : real scalar + wanted sampling interval (default as given by S, see spec2dt) + unit: [s] if frequency-spectrum, [m] if wave number spectrum + Nmin, Nmax : scalar integers + minimum and maximum number of frequencies, respectively. + method : string + interpolation method (options are 'linear', 'cubic' or 'stineman') + + To be used before simulation (e.g. spec2sdat) or evaluation of + covariance function (spec2cov) to get the wanted sampling interval. + The input spectrum is interpolated and padded with zeros to reach + the right max-frequency, w[-1]=pi/dt, f(end)=1/(2*dt), or k[-1]=pi/dt. + The objective is that output frequency grid should be at least as dense + as the input grid, have equidistant spacing and length equal to + 2^k+1 (>=Nmin). If the max frequency is changed, the number of points + in the spectrum is maximized to 2^13+1. + + Note: Also zero-padding down to zero freq, if S does not start there. + If empty input dt, this is the only effect. + + See also + -------- + spec2cov, spec2sdat, covinterp, spec2dt + ''' + + ftype = self.freqtype + w = self.args.ravel() + n = w.size + + #%doInterpolate = 0 + # Nyquist to sampling interval factor + Cnf2dt = 0.5 if ftype == 'f' else pi # % ftype == w og ftype == k + + wnOld = w[-1] # Old Nyquist frequency + dTold = Cnf2dt / wnOld # sampling interval=1/Fs + # dTold = self.sampling_period() + + if dt is None: + dt = dTold + + # Find how many points that is needed + nfft = 2 ** nextpow2(max(n - 1, Nmin - 1)) + dttest = dTold * (n - 1) / nfft + + while (dttest > dt) and (nfft < Nmax - 1): + nfft = nfft * 2 + dttest = dTold * (n - 1) / nfft + + nfft = nfft + 1 + + wnNew = Cnf2dt / dt # % New Nyquist frequency + dWn = wnNew - wnOld + doInterpolate = dWn > 0 or w[1] > 0 or ( + nfft != n) or dt != dTold or any(abs(diff(w, axis=0)) > 1.0e-8) + + if doInterpolate > 0: + S1 = self.data + + dw = min(diff(w)) + + if dWn > 0: + # add a zero just above old max-freq, and a zero at new + # max-freq to get correct interpolation there + Nz = 1 + (dWn > dw) # % Number of zeros to add + if Nz == 2: + w = hstack((w, wnOld + dw, wnNew)) + else: + w = hstack((w, wnNew)) + + S1 = hstack((S1, zeros(Nz))) + + if w[0] > 0: + # add a zero at freq 0, and, if there is space, a zero just + # below min-freq + Nz = 1 + (w[0] > dw) # % Number of zeros to add + if Nz == 2: + w = hstack((0, w[0] - dw, w)) + else: + w = hstack((0, w)) + + S1 = hstack((zeros(Nz), S1)) + + # Do a final check on spacing in order to check that the gridding + # is sufficiently dense: + # np1 = S1.size + dwMin = finfo(float).max + # wnc = min(wnNew,wnOld-1e-5) + wnc = wnNew + # specfun = lambda xi : stineman_interp(xi, w, S1) + specfun = interpolate.interp1d(w, S1, kind='cubic') + x, unused_y = discretize(specfun, 0, wnc) + dwMin = minimum(min(diff(x)), dwMin) + + newNfft = 2 ** nextpow2(ceil(wnNew / dwMin)) + 1 + if newNfft > nfft: + # if (nfft <= 2 ** 15 + 1) and (newNfft > 2 ** 15 + 1): + # warnings.warn('Spectrum matrix is very large (>33k). ' + + # 'Memory problems may occur.') + nfft = newNfft + self.args = linspace(0, wnNew, nfft) + if method == 'stineman': + self.data = stineman_interp(self.args, w, S1) + else: + intfun = interpolate.interp1d(w, S1, kind=method) + self.data = intfun(self.args) + self.data = self.data.clip(0) # clip negative values to 0 + + def normalize(self, gravity=9.81): + ''' + Normalize a spectral density such that m0=m2=1 + + Paramter + -------- + gravity=9.81 + + Notes + ----- + Normalization performed such that + INT S(freq) dfreq = 1 INT freq^2 S(freq) dfreq = 1 + where integration limits are given by freq and S(freq) is the + spectral density; freq can be frequency or wave number. + The normalization is defined by + A=sqrt(m0/m2); B=1/A/m0; freq'=freq*A; S(freq')=S(freq)*B + + If S is a directional spectrum then a normalized gravity (.g) is added + to Sn, such that mxx normalizes to 1, as well as m0 and mtt. + (See spec2mom for notation of moments) + + If S is complex-valued cross spectral density which has to be + normalized, then m0, m2 (suitable spectral moments) should be given. + + Example + ------- + >>> import wafo.spectrum.models as sm + >>> Sj = sm.Jonswap(Hm0=5) + >>> S = Sj.tospecdata() #Make spectrum ob + >>> S.moment(2) + ([1.5614600345079888, 0.95567089481941048], ['m0', 'm0tt']) + >>> Sn = S.copy(); Sn.normalize() + + Now the moments should be one + >>> Sn.moment(2) + ([1.0000000000000004, 0.99999999999999967], ['m0', 'm0tt']) + ''' + mom, unused_mtext = self.moment(nr=4, even=True) + m0 = mom[0] + m2 = mom[1] + m4 = mom[2] + + SM0 = sqrt(m0) + SM2 = sqrt(m2) + A = SM0 / SM2 + B = SM2 / (SM0 * m0) + + if self.freqtype == 'f': + self.args = self.args * A / 2 / pi + self.data = self.data * B * 2 * pi + elif self.freqtype == 'w': + self.args = self.args * A + self.data = self.data * B + m02 = m4 / gravity ** 2 + m20 = m02 + self.g = gravity * sqrt(m0 * m20) / m2 + self.A = A + self.norm = True + self.date = now() + + def bandwidth(self, factors=0): + ''' + Return some spectral bandwidth and irregularity factors + + Parameters + ----------- + factors : array-like + Input vector 'factors' correspondence: + 0 alpha=m2/sqrt(m0*m4) (irregularity factor) + 1 eps2 = sqrt(m0*m2/m1^2-1) (narrowness factor) + 2 eps4 = sqrt(1-m2^2/(m0*m4))=sqrt(1-alpha^2) (broadness factor) + 3 Qp=(2/m0^2)int_0^inf f*S(f)^2 df (peakedness factor) + + Returns + -------- + bw : arraylike + vector of bandwidth factors + Order of output is the same as order in 'factors' + + Example: + >>> import numpy as np + >>> import wafo.spectrum.models as sm + >>> Sj = sm.Jonswap(Hm0=3, Tp=7) + >>> w = np.linspace(0,4,256) + >>> S = SpecData1D(Sj(w),w) #Make spectrum object from numerical values + >>> S.bandwidth([0,'eps2',2,3]) + array([ 0.73062845, 0.34476034, 0.68277527, 2.90817052]) + ''' + m, unused_mtxt = self.moment(nr=4, even=False) + + fact_dict = dict(alpha=0, eps2=1, eps4=3, qp=3, Qp=3) + fun = lambda fact: fact_dict.get(fact, fact) + fact = atleast_1d(map(fun, list(factors))) + + # fact = atleast_1d(fact) + alpha = m[2] / sqrt(m[0] * m[4]) + eps2 = sqrt(m[0] * m[2] / m[1] ** 2. - 1.) + eps4 = sqrt(1. - m[2] ** 2. / m[0] / m[4]) + f = self.args + S = self.data + Qp = 2 / m[0] ** 2. * simps(f * S ** 2, x=f) + bw = array([alpha, eps2, eps4, Qp]) + return bw[fact] + + def characteristic(self, fact='Hm0', T=1200, g=9.81): + """ + Returns spectral characteristics and their covariance + + Parameters + ---------- + fact : vector with factor integers or a string or a list of strings + defining spectral characteristic, see description below. + T : scalar + recording time (sec) (default 1200 sec = 20 min) + g : scalar + acceleration of gravity [m/s^2] + + Returns + ------- + ch : vector + of spectral characteristics + R : matrix + of the corresponding covariances given T + chtext : a list of strings + describing the elements of ch, see example. + + + Description + ------------ + If input spectrum is of wave number type, output are factors for + corresponding 'k1D', else output are factors for 'freq'. + Input vector 'factors' correspondence: + 1 Hm0 = 4*sqrt(m0) Significant wave height + 2 Tm01 = 2*pi*m0/m1 Mean wave period + 3 Tm02 = 2*pi*sqrt(m0/m2) Mean zero-crossing period + 4 Tm24 = 2*pi*sqrt(m2/m4) Mean period between maxima + 5 Tm_10 = 2*pi*m_1/m0 Energy period + 6 Tp = 2*pi/{w | max(S(w))} Peak period + 7 Ss = 2*pi*Hm0/(g*Tm02^2) Significant wave steepness + 8 Sp = 2*pi*Hm0/(g*Tp^2) Average wave steepness + 9 Ka = abs(int S(w)*exp(i*w*Tm02) dw ) /m0 Groupiness parameter + 10 Rs = (S(0.092)+S(0.12)+S(0.15)/(3*max(S(w))) + Quality control parameter + 11 Tp1 = 2*pi*int S(w)^4 dw Peak Period + ------------------ (robust estimate for Tp) + int w*S(w)^4 dw + + 12 alpha = m2/sqrt(m0*m4) Irregularity factor + 13 eps2 = sqrt(m0*m2/m1^2-1) Narrowness factor + 14 eps4 = sqrt(1-m2^2/(m0*m4))=sqrt(1-alpha^2) Broadness factor + 15 Qp = (2/m0^2)int_0^inf w*S(w)^2 dw Peakedness factor + + Order of output is same as order in 'factors' + The covariances are computed with a Taylor expansion technique + and is currently only available for factors 1, 2, and 3. Variances + are also available for factors 4,5,7,12,13,14 and 15 + + Quality control: + ---------------- + Critical value for quality control parameter Rs is Rscrit = 0.02 + for surface displacement records and Rscrit=0.0001 for records of + surface acceleration or slope. If Rs > Rscrit then probably there + are something wrong with the lower frequency part of S. + + Ss may be used as an indicator of major malfunction, by checking that + it is in the range of 1/20 to 1/16 which is the usual range for + locally generated wind seas. + + Examples: + --------- + >>> import wafo.spectrum.models as sm + >>> Sj = sm.Jonswap(Hm0=5) + >>> S = Sj.tospecdata() #Make spectrum ob + >>> S.characteristic(1) + (array([ 8.59007646]), array([[ 0.03040216]]), ['Tm01']) + + >>> [ch, R, txt] = S.characteristic([1,2,3]) # fact vector of integers + >>> S.characteristic('Ss') # fact a string + (array([ 0.04963112]), array([[ 2.63624782e-06]]), ['Ss']) + + >>> S.characteristic(['Hm0','Tm02']) # fact a list of strings + (array([ 4.99833578, 8.03139757]), array([[ 0.05292989, 0.02511371], + [ 0.02511371, 0.0274645 ]]), ['Hm0', 'Tm02']) + + See also + --------- + bandwidth, + moment + + References + ---------- + Krogstad, H.E., Wolf, J., Thompson, S.P., and Wyatt, L.R. (1999) + 'Methods for intercomparison of wave measurements' + Coastal Enginering, Vol. 37, pp. 235--257 + + Krogstad, H.E. (1982) + 'On the covariance of the periodogram' + Journal of time series analysis, Vol. 3, No. 3, pp. 195--207 + + Tucker, M.J. (1993) + 'Recommended standard for wave data sampling and near-real-time + processing' + Ocean Engineering, Vol.20, No.5, pp. 459--474 + + Young, I.R. (1999) + "Wind generated ocean waves" + Elsevier Ocean Engineering Book Series, Vol. 2, pp 239 + """ + + # TODO: Need more checking on computing the variances for Tm24,alpha, + # eps2 and eps4 + # TODO: Covariances between Tm24,alpha, eps2 and eps4 variables are + # also needed + + tfact = dict(Hm0=0, Tm01=1, Tm02=2, Tm24=3, Tm_10=4, Tp=5, Ss=6, Sp=7, + Ka=8, Rs=9, Tp1=10, Alpha=11, Eps2=12, Eps4=13, Qp=14) + tfact1 = ('Hm0', 'Tm01', 'Tm02', 'Tm24', 'Tm_10', 'Tp', 'Ss', 'Sp', + 'Ka', 'Rs', 'Tp1', 'Alpha', 'Eps2', 'Eps4', 'Qp') + + if isinstance(fact, str): + fact = list((fact,)) + if isinstance(fact, (list, tuple)): + nfact = [] + for k in fact: + if isinstance(k, str): + nfact.append(tfact.get(k.capitalize(), 15)) + else: + nfact.append(k) + else: + nfact = fact + + nfact = atleast_1d(nfact) + + if any((nfact > 14) | (nfact < 0)): + raise ValueError('Factor outside range (0,...,14)') + + # vari = self.freqtype + f = self.args.ravel() + S1 = self.data.ravel() + m, unused_mtxt = self.moment(nr=4, even=False) + + # moments corresponding to freq in Hz + for k in range(1, 5): + m[k] = m[k] / (2 * pi) ** k + + # pi = np.pi + ind = flatnonzero(f > 0) + m.append(simps(S1[ind] / f[ind], f[ind]) * 2. * pi) # = m_1 + m_10 = simps(S1[ind] ** 2 / f[ind], f[ind]) * \ + (2 * pi) ** 2 / T # = COV(m_1,m0|T=t0) + m_11 = simps(S1[ind] ** 2. / f[ind] ** 2, f[ind]) * \ + (2 * pi) ** 3 / T # = COV(m_1,m_1|T=t0) + + # sqrt = np.sqrt + # Hm0 Tm01 Tm02 Tm24 Tm_10 + Hm0 = 4. * sqrt(m[0]) + Tm01 = m[0] / m[1] + Tm02 = sqrt(m[0] / m[2]) + Tm24 = sqrt(m[2] / m[4]) + Tm_10 = m[5] / m[0] + + Tm12 = m[1] / m[2] + + ind = S1.argmax() + maxS = S1[ind] + # [maxS ind] = max(S1) + Tp = 2. * pi / f[ind] # peak period /length + Ss = 2. * pi * Hm0 / g / Tm02 ** 2 # Significant wave steepness + Sp = 2. * pi * Hm0 / g / Tp ** 2 # Average wave steepness + # groupiness factor + Ka = abs(simps(S1 * exp(1J * f * Tm02), f)) / m[0] + + # Quality control parameter + # critical value is approximately 0.02 for surface displacement records + # If Rs>0.02 then there are something wrong with the lower frequency + # part of S. + Rs = np.sum( + interp(r_[0.0146, 0.0195, 0.0244] * 2 * pi, f, S1)) / 3. / maxS + Tp2 = 2 * pi * simps(S1 ** 4, f) / simps(f * S1 ** 4, f) + + alpha1 = Tm24 / Tm02 # m(3)/sqrt(m(1)*m(5)) + eps2 = sqrt(Tm01 / Tm12 - 1.) # sqrt(m(1)*m(3)/m(2)^2-1) + eps4 = sqrt(1. - alpha1 ** 2) # sqrt(1-m(3)^2/m(1)/m(5)) + Qp = 2. / m[0] ** 2 * simps(f * S1 ** 2, f) + + ch = r_[Hm0, Tm01, Tm02, Tm24, Tm_10, Tp, Ss, + Sp, Ka, Rs, Tp2, alpha1, eps2, eps4, Qp] + + # Select the appropriate values + ch = ch[nfact] + chtxt = [tfact1[i] for i in nfact] + + # if nargout>1, + # covariance between the moments: + # COV(mi,mj |T=t0) = int f^(i+j)*S(f)^2 df/T + mij, unused_mijtxt = self.moment(nr=8, even=False, j=1) + for ix, tmp in enumerate(mij): + mij[ix] = tmp / T / ((2. * pi) ** (ix - 1.0)) + + # and the corresponding variances for + # {'hm0', 'tm01', 'tm02', 'tm24', 'tm_10','tp','ss', 'sp', 'ka', 'rs', + # 'tp1','alpha','eps2','eps4','qp'} + R = r_[4 * mij[0] / m[0], + mij[0] / m[1] ** 2. - 2. * m[0] * mij[1] / + m[1] ** 3. + m[0] ** 2. * mij[2] / m[1] ** 4., + 0.25 * (mij[0] / (m[0] * m[2]) - 2. * mij[2] / m[2] + ** 2 + m[0] * mij[4] / m[2] ** 3), + 0.25 * (mij[4] / (m[2] * m[4]) - 2 * mij[6] / m[4] + ** 2 + m[2] * mij[8] / m[4] ** 3), + m_11 / m[0] ** 2 + (m[5] / m[0] ** 2) ** 2 * + mij[0] - 2 * m[5] / m[0] ** 3 * m_10, + nan, + (8 * pi / g) ** 2 * (m[2] ** 2 / (4 * m[0] ** 3) * + mij[0] + mij[4] / m[0] - m[2] / m[0] ** 2 * mij[2]), + nan * ones(4), + m[2] ** 2 * mij[0] / (4 * m[0] ** 3 * m[4]) + mij[4] / + (m[0] * m[4]) + mij[8] * m[2] ** 2 / (4 * m[0] * m[4] ** 3) - + m[2] * mij[2] / (m[0] ** 2 * m[4]) + m[2] ** 2 * mij[4] / + (2 * m[0] ** 2 * m[4] ** 2) - m[2] * mij[6] / m[0] / m[4] ** 2, + (m[2] ** 2 * mij[0] / 4 + (m[0] * m[2] / m[1]) ** 2 * mij[2] + + m[0] ** 2 * mij[4] / 4 - m[2] ** 2 * m[0] * mij[1] / m[1] + + m[0] * m[2] * mij[2] / 2 - m[0] ** 2 * m[2] / m[1] * mij[3]) / + eps2 ** 2 / m[1] ** 4, + (m[2] ** 2 * mij[0] / (4 * m[0] ** 2) + mij[4] + m[2] ** 2 * + mij[8] / (4 * m[4] ** 2) - m[2] * mij[2] / m[0] + m[2] ** 2 * + mij[4] / (2 * m[0] * m[4]) - m[2] * mij[6] / m[4]) * m[2] ** 2 + / (m[0] * m[4] * eps4) ** 2, + nan] + + # and covariances by a taylor expansion technique: + # Cov(Hm0,Tm01) Cov(Hm0,Tm02) Cov(Tm01,Tm02) + S0 = r_[2. / (sqrt(m[0]) * m[1]) * (mij[0] - m[0] * mij[1] / m[1]), + 1. / sqrt(m[2]) * (mij[0] / m[0] - mij[2] / m[2]), + 1. / (2 * m[1]) * sqrt(m[0] / m[2]) * (mij[0] / m[0] - mij[2] / + m[2] - mij[1] / m[1] + m[0] * mij[3] / (m[1] * m[2]))] + + R1 = ones((15, 15)) + R1[:, :] = nan + for ix, Ri in enumerate(R): + R1[ix, ix] = Ri + + R1[0, 2:4] = S0[:2] + R1[1, 2] = S0[2] + # make lower triangular equal to upper triangular part + for ix in [0, 1]: + R1[ix + 1:, ix] = R1[ix, ix + 1:] + + R = R[nfact] + R1 = R1[nfact, :][:, nfact] + + # Needs further checking: + # Var(Tm24)= 0.25*(mij[4]/(m[2]*m[4])- + # 2*mij[6]/m[4]**2+m[2]*mij[8]/m[4]**3) + return ch, R1, chtxt + + def setlabels(self): + ''' Set automatic title, x-,y- and z- labels on SPECDATA object + + based on type, angletype, freqtype + ''' + + N = len(self.type) + if N == 0: + raise ValueError( + 'Object does not appear to be initialized, it is empty!') + + labels = ['', '', ''] + if self.type.endswith('dir'): + title = 'Directional Spectrum' + if self.freqtype.startswith('w'): + labels[0] = 'Frequency [rad/s]' + labels[2] = r'S($\omega$,$\theta$) $[m^2 s / rad^2]$' + else: + labels[0] = 'Frequency [Hz]' + labels[2] = r'S(f,$\theta$) $[m^2 s / rad]$' + + if self.angletype.startswith('r'): + labels[1] = 'Wave directions [rad]' + elif self.angletype.startswith('d'): + labels[1] = 'Wave directions [deg]' + elif self.type.endswith('freq'): + title = 'Spectral density' + if self.freqtype.startswith('w'): + labels[0] = 'Frequency [rad/s]' + labels[1] = r'S($\omega$) $[m^2 s/ rad]$' + else: + labels[0] = 'Frequency [Hz]' + labels[1] = r'S(f) $[m^2 s]$' + else: + title = 'Wave Number Spectrum' + labels[0] = 'Wave number [rad/m]' + if self.type.endswith('k1d'): + labels[1] = r'S(k) $[m^3/ rad]$' + elif self.type.endswith('k2d'): + labels[1] = labels[0] + labels[2] = r'S(k1,k2) $[m^4/ rad^2]$' + else: + raise ValueError( + 'Object does not appear to be initialized, it is empty!') + if self.norm != 0: + title = 'Normalized ' + title + labels[0] = 'Normalized ' + labels[0].split('[')[0] + if not self.type.endswith('dir'): + labels[1] = labels[1].split('[')[0] + labels[2] = labels[2].split('[')[0] + + self.labels.title = title + self.labels.xlab = labels[0] + self.labels.ylab = labels[1] + self.labels.zlab = labels[2] + + +class SpecData2D(PlotData): + + """ Container class for 2D spectrum data objects in WAFO + + Member variables + ---------------- + data : array_like + args : vector for 1D, list of vectors for 2D, 3D, ... + + type : string + spectrum type (default 'freq') + freqtype : letter + frequency type (default 'w') + angletype : string + angle type of directional spectrum (default 'radians') + + Examples + -------- + >>> import numpy as np + >>> import wafo.spectrum.models as sm + >>> Sj = sm.Jonswap(Hm0=3, Tp=7) + >>> w = np.linspace(0,4,256) + >>> S = SpecData1D(Sj(w),w) #Make spectrum object from numerical values + + See also + -------- + PlotData + CovData + """ + + def __init__(self, *args, **kwds): + + super(SpecData2D, self).__init__(*args, **kwds) + + self.name = 'WAFO Spectrum Object' + self.type = 'dir' + self.freqtype = 'w' + self.angletype = '' + self.h = inf + self.tr = None + self.phi = 0. + self.v = 0. + self.norm = 0 + somekeys = ['angletype', 'phi', 'name', 'h', + 'tr', 'freqtype', 'v', 'type', 'norm'] + + self.__dict__.update(sub_dict_select(kwds, somekeys)) + + if self.type.endswith('dir') and self.angletype == '': + self.angletype = 'radians' + + self.setlabels() + + def toacf(self): + pass + + def tospecdata(self, type=None): # @ReservedAssignment + pass + + def sim(self): + pass + + def sim_nl(self): + pass + + def rotate(self, phi=0, rotateGrid=False, method='linear'): + ''' + Rotate spectrum clockwise around the origin. + + Parameters + ---------- + phi : real scalar + rotation angle (default 0) + rotateGrid : bool + True if rotate grid of Snew physically (thus Snew.phi=0). + False if rotate so that only Snew.phi is changed + (the grid is not physically rotated) (default) + method : string + interpolation method to use when ROTATEGRID==1, (default 'linear') + + Rotates the spectrum clockwise around the origin. + This equals a anti-clockwise rotation of the cordinate system (x,y). + The spectrum can be of any of the two-dimensional types. + For spectrum in polar representation: + newtheta = theta-phi, but circulant such that -pi>> import wafo.spectrum.models as sm + >>> D = sm.Spreading() + >>> SD = D.tospecdata2d(sm.Jonswap().tospecdata(),nt=101) + >>> m,mtext = SD.moment(nr=2,vari='xyt') + >>> np.round(m,3),mtext + (array([ 3.061, 0.132, -0. , 2.13 , 0.011, 0.008, 1.677, -0., + 0.109, 0.109]), + ['m0', 'mx', 'my', 'mt', 'mxx', 'myy', 'mtt', 'mxy', 'mxt', + 'myt']) + + References + ---------- + Baxevani A. et al. (2001) + Velocities for Random Surfaces + ''' + + two_dim_spectra = ['dir', 'encdir', 'k2d'] + if self.type not in two_dim_spectra: + raise ValueError('Unknown 2D spectrum type!') + + if vari == None and nr <= 1: + vari = 'x' + elif vari == None: + vari = 'xt' + else: # % secure the mutual order ('xyt') + vari = ''.join(sorted(vari.lower())) + Nv = len(vari) + + if vari[0] == 't' and Nv > 1: + vari = vari[1::] + vari[0] + + Nv = len(vari) + + if not self.type.endswith('dir'): + S1 = self.tospecdata(self.type[:-2] + 'dir') + else: + S1 = self + w = ravel(S1.args[0]) + theta = S1.args[1] - S1.phi + S = S1.data + Sw = simps(S, x=theta, axis=0) + m = [simps(Sw, x=w)] + mtext = ['m0'] + + if nr > 0: + vec = [] + g = np.atleast_1d(S1.__dict__.get('g', gravity())) + # maybe different normalization in x and y => diff. g + kx = w ** 2 / g[0] + ky = w ** 2 / g[-1] + + # nw = w.size + + if 'x' in vari: + ct = np.cos(theta[:, None]) + Sc = simps(S * ct, x=theta, axis=0) + vec.append(kx * Sc) + mtext.append('mx') + if 'y' in vari: + st = np.sin(theta[:, None]) + Ss = simps(S * st, x=theta, axis=0) + vec.append(ky * Ss) + mtext.append('my') + if 't' in vari: + vec.append(w * Sw) + mtext.append('mt') + + if nr > 1: + if 'x' in vari: + Sc2 = simps(S * ct ** 2, x=theta, axis=0) + vec.append(kx ** 2 * Sc2) + mtext.append('mxx') + if 'y' in vari: + Ss2 = simps(S * st ** 2, x=theta, axis=0) + vec.append(ky ** 2 * Ss2) + mtext.append('myy') + if 't' in vari: + vec.append(w ** 2 * Sw) + mtext.append('mtt') + if 'x' in vari and 'y' in vari: + Scs = simps(S * ct * st, x=theta, axis=0) + vec.append(kx * ky * Scs) + mtext.append('mxy') + if 'x' in vari and 't' in vari: + vec.append(kx * w * Sc) + mtext.append('mxt') + if 'y' in vari and 't' in vari: + vec.append(ky * w * Sc) + mtext.append('myt') + + if nr > 3: + if 'x' in vari: + Sc3 = simps(S * ct ** 3, x=theta, axis=0) + Sc4 = simps(S * ct ** 4, x=theta, axis=0) + vec.append(kx ** 4 * Sc4) + mtext.append('mxxxx') + if 'y' in vari: + Ss3 = simps(S * st ** 3, x=theta, axis=0) + Ss4 = simps(S * st ** 4, x=theta, axis=0) + vec.append(ky ** 4 * Ss4) + mtext.append('myyyy') + if 't' in vari: + vec.append(w ** 4 * Sw) + mtext.append('mtttt') + + if 'x' in vari and 'y' in vari: + Sc2s = simps(S * ct ** 2 * st, x=theta, axis=0) + Sc3s = simps(S * ct ** 3 * st, x=theta, axis=0) + Scs2 = simps(S * ct * st ** 2, x=theta, axis=0) + Scs3 = simps(S * ct * st ** 3, x=theta, axis=0) + Sc2s2 = simps(S * ct ** 2 * st ** 2, x=theta, axis=0) + vec.extend((kx ** 3 * ky * Sc3s, + kx ** 2 * ky ** 2 * Sc2s2, + kx * ky ** 3 * Scs3)) + mtext.extend(('mxxxy', 'mxxyy', 'mxyyy')) + if 'x' in vari and 't' in vari: + vec.extend((kx ** 3 * w * Sc3, + kx ** 2 * w ** 2 * Sc2, kx * w ** 3 * Sc)) + mtext.extend(('mxxxt', 'mxxtt', 'mxttt')) + if 'y' in vari and 't' in vari: + vec.extend((ky ** 3 * w * Ss3, ky ** 2 * w ** 2 * Ss2, + ky * w ** 3 * Ss)) + mtext.extend(('myyyt', 'myytt', 'myttt')) + if 'x' in vari and 'y' in vari and 't' in vari: + vec.extend((kx ** 2 * ky * w * Sc2s, + kx * ky ** 2 * w * Scs2, + kx * ky * w ** 2 * Scs)) + mtext.extend(('mxxyt', 'mxyyt', 'mxytt')) + # end % if nr>1 + m.extend([simps(vals, x=w) for vals in vec]) + return np.asarray(m), mtext + + def interp(self): + pass + + def normalize(self): + pass + + def bandwidth(self): + pass + + def setlabels(self): + ''' Set automatic title, x-,y- and z- labels on SPECDATA object + + based on type, angletype, freqtype + ''' + + N = len(self.type) + if N == 0: + raise ValueError( + 'Object does not appear to be initialized, it is empty!') + + labels = ['', '', ''] + if self.type.endswith('dir'): + title = 'Directional Spectrum' + if self.freqtype.startswith('w'): + labels[0] = 'Frequency [rad/s]' + labels[2] = r'$S(w,\theta) [m**2 s / rad**2]$' + else: + labels[0] = 'Frequency [Hz]' + labels[2] = r'$S(f,\theta) [m**2 s / rad]$' + + if self.angletype.startswith('r'): + labels[1] = 'Wave directions [rad]' + elif self.angletype.startswith('d'): + labels[1] = 'Wave directions [deg]' + elif self.type.endswith('freq'): + title = 'Spectral density' + if self.freqtype.startswith('w'): + labels[0] = 'Frequency [rad/s]' + labels[1] = 'S(w) [m**2 s/ rad]' + else: + labels[0] = 'Frequency [Hz]' + labels[1] = 'S(f) [m**2 s]' + else: + title = 'Wave Number Spectrum' + labels[0] = 'Wave number [rad/m]' + if self.type.endswith('k1d'): + labels[1] = 'S(k) [m**3/ rad]' + elif self.type.endswith('k2d'): + labels[1] = labels[0] + labels[2] = 'S(k1,k2) [m**4/ rad**2]' + else: + raise ValueError( + 'Object does not appear to be initialized, it is empty!') + if self.norm != 0: + title = 'Normalized ' + title + labels[0] = 'Normalized ' + labels[0].split('[')[0] + if not self.type.endswith('dir'): + labels[1] = labels[1].split('[')[0] + labels[2] = labels[2].split('[')[0] + + self.labels.title = title + self.labels.xlab = labels[0] + self.labels.ylab = labels[1] + self.labels.zlab = labels[2] + + +def _test_specdata(): + import wafo.spectrum.models as sm + Sj = sm.Jonswap() + S = Sj.tospecdata() + me, va, sk, ku = S.stats_nl(moments='mvsk') + + +def main(): + import matplotlib + matplotlib.interactive(True) + from wafo.spectrum import models as sm + + w = linspace(0, 3, 100) + Sj = sm.Jonswap() + S = Sj.tospecdata() + + f = S.to_t_pdf(pdef='Tc', paramt=(0, 10, 51), speed=7) + f.err + f.plot() + f.show() + # pdfplot(f) + # hold on, + # plot(f.x{:}, f.f+f.err,'r',f.x{:}, f.f-f.err) estimated error bounds + # hold off + # S = SpecData1D(Sj(w),w) + R = S.tocovdata(nr=1) + S1 = S.copy() + Si = R.tospecdata() + ns = 5000 + dt = .2 + x1 = S.sim_nl(ns=ns, dt=dt) + x2 = TimeSeries(x1[:, 1], x1[:, 0]) + R = x2.tocovdata(lag=100) + R.plot() + + S.plot('ro') + t = S.moment() + t1 = S.bandwidth([0, 1, 2, 3]) + S1 = S.copy() + S1.resample(dt=0.3, method='cubic') + S1.plot('k+') + x = S1.sim(ns=100) + import pylab + pylab.clf() + pylab.plot(x[:, 0], x[:, 1]) + pylab.show() + + pylab.close('all') + print('done') + + +def test_mm_pdf(): + + import wafo.spectrum.models as sm + Sj = sm.Jonswap(Hm0=7, Tp=11) + w = np.linspace(0, 4, 256) + S1 = Sj.tospecdata(w) # Make spectrum object from numerical values + S = sm.SpecData1D(Sj(w), w) # Alternatively do it manually + mm = S.to_mm_pdf() + + +def test_docstrings(): + import doctest + doctest.testmod() + + +if __name__ == '__main__': + test_docstrings() + # test_mm_pdf() + # main() diff --git a/pywafo/src/wafo/spectrum/models.py b/pywafo/src/wafo/spectrum/models.py index edb6212..03d2bc0 100644 --- a/pywafo/src/wafo/spectrum/models.py +++ b/pywafo/src/wafo/spectrum/models.py @@ -1,2076 +1,2111 @@ -""" -Models module -------------- - -Dispersion relation -------------------- -k2w - Translates from wave number to frequency -w2k - Translates from frequency to wave number - -Model spectra -------------- -Bretschneider - Bretschneider spectral density. -Jonswap - JONSWAP spectral density -McCormick - McCormick spectral density. -OchiHubble - OchiHubble bimodal spectral density model. -Tmaspec - JONSWAP spectral density for finite water depth -Torsethaugen - Torsethaugen double peaked (swell + wind) spectrum model -Wallop - Wallop spectral density. -demospec - Loads a precreated spectrum of chosen type -jonswap_peakfact - Jonswap peakedness factor Gamma given Hm0 and Tp -jonswap_seastate - jonswap seastate from windspeed and fetch - -Directional spreading functions -------------------------------- -Spreading - Directional spreading function. - -""" - - -#------------------------------------------------------------------------------- -# Name: models -# Purpose: Interface to various spectrum models -# -# Author: pab -# -# Created: 29.08.2008 -# Copyright: (c) pab 2008 -# Licence: -#------------------------------------------------------------------------------- -#!/usr/bin/env python -from __future__ import division - -import warnings -from scipy.interpolate import interp1d -import scipy.optimize as optimize -import scipy.integrate as integrate -import scipy.special as sp -from scipy.fftpack import fft -#from scipy.misc import ppimport -import numpy as np -from numpy import (inf, atleast_1d, newaxis, any, minimum, maximum, array, #@UnresolvedImport - asarray, exp, log, sqrt, where, pi, arange, linspace, sin, cos, abs, sinh, #@UnresolvedImport - isfinite, mod, expm1, tanh, cosh, finfo, ones, ones_like, isnan, #@UnresolvedImport - zeros_like, flatnonzero, sinc, hstack, vstack, real, flipud, clip) #@UnresolvedImport -from wafo.wave_theory.dispersion_relation import w2k, k2w #@UnusedImport -from wafo.spectrum import SpecData1D, SpecData2D -sech = lambda x: 1.0 / cosh(x) - -eps = finfo(float).eps - - -__all__ = ['Bretschneider', 'Jonswap', 'Torsethaugen', 'Wallop', 'McCormick', 'OchiHubble', - 'Tmaspec', 'jonswap_peakfact', 'jonswap_seastate', 'spreading', - 'w2k', 'k2w', 'phi1'] - -# Model spectra - -def _gengamspec(wn, N=5, M=4): - ''' Return Generalized gamma spectrum in dimensionless form - - Parameters - ---------- - wn : arraylike - normalized frequencies, w/wp. - N : scalar - defining the decay of the high frequency part. - M : scalar - defining the spectral width around the peak. - - Returns - ------- - S : arraylike - spectral values, same size as wn. - - The generalized gamma spectrum in non- - dimensional form is defined as: - - S = G0.*wn.**(-N).*exp(-B*wn.**(-M)) for wn > 0 - = 0 otherwise - where - B = N/M - C = (N-1)/M - G0 = B**C*M/gamma(C), Normalizing factor related to Bretschneider form - - Note that N = 5, M = 4 corresponds to a normalized - Bretschneider spectrum. - - Examples - -------- - >>> import wafo.spectrum.models as wsm - >>> import numpy as np - >>> wn = np.linspace(0,4,5) - >>> wsm._gengamspec(wn, N=6, M=2) - array([ 0. , 1.16765216, 0.17309961, 0.02305179, 0.00474686]) - - See also - -------- - Bretschneider - Jonswap, - Torsethaugen - - - References - ---------- - Torsethaugen, K. (2004) - "Simplified Double Peak Spectral Model for Ocean Waves" - In Proc. 14th ISOPE - ''' - w = atleast_1d(wn) - S = zeros_like(w) - - ##for w>0 # avoid division by zero - k = flatnonzero(w > 0.0) - if k.size > 0: - B = N / M - C = (N - 1.0) / M - - # # A = Normalizing factor related to Bretschneider form - # A = B**C*M/gamma(C) - # S(k) = A*wn(k)**(-N)*exp(-B*wn(k)**(-M)) - logwn = log(w.take(k)) - logA = (C * log(B) + log(M) - sp.gammaln(C)) - S.put(k, exp(logA - N * logwn - B * exp(-M * logwn))) - return S - -class ModelSpectrum(object): - def __init__(self, Hm0=7.0, Tp=11.0, **kwds): - self.Hm0 = Hm0 - self.Tp = Tp - self.type = 'ModelSpectrum' - def tospecdata(self, w=None, wc=None, nw=257): - ''' - Return SpecData1D object from ModelSpectrum - - Parameter - --------- - w : arraylike - vector of angular frequencies used in the discretization of spectrum - wc : scalar - cut off frequency (default 33/Tp) - nw : int - number of frequencies - - Returns - ------- - S : SpecData1D object - member attributes of model spectrum are copied to S.workspace - ''' - - if w is None: - if wc is None: - wc = 33. / self.Tp - w = linspace(0, wc, nw) - S = SpecData1D(self.__call__(w), w) - try: - h = self.h - S.h = h - except: - pass - S.labels.title = self.type + ' ' + S.labels.title - S.workspace = self.__dict__.copy() - return S - - def chk_seastate(self): - ''' Check if seastate is valid - ''' - - if self.Hm0 < 0: - raise ValueError('Hm0 can not be negative!') - - if self.Tp <= 0: - raise ValueError('Tp must be positve!') - - if self.Hm0 == 0.0: - warnings.warn('Hm0 is zero!') - - self._chk_extra_param() - - def _chk_extra_param(self): - pass - -class Bretschneider(ModelSpectrum): - ''' - Bretschneider spectral density model - - Member variables - ---------------- - Hm0 : significant wave height (default 7 (m)) - Tp : peak period (default 11 (sec)) - N : scalar defining decay of high frequency part. (default 5) - M : scalar defining spectral width around the peak. (default 4) - - Parameters - ---------- - w : array-like - angular frequencies [rad/s] - - - The Bretschneider spectrum is defined as - - S(w) = A * G0 * wn**(-N)*exp(-N/(M*wn**M)) - where - G0 = Normalizing factor related to Bretschneider form - A = (Hm0/4)**2 / wp (Normalization factor) - wn = w/wp - wp = 2*pi/Tp, angular peak frequency - - This spectrum is a suitable model for fully developed sea, - i.e. a sea state where the wind has been blowing long enough over a - sufficiently open stretch of water, so that the high-frequency waves have - reached an equilibrium. In the part of the spectrum where the frequency is - greater than the peak frequency (w>wp), the energy distribution is - proportional to w**-5. - The spectrum is identical with ITTC (International Towing Tank - Conference), ISSC (International Ship and Offshore Structures Congress) - and Pierson-Moskowitz, wave spectrum given Hm0 and Tm01. It is also identical - with JONSWAP when the peakedness factor, gamma, is one. - For this spectrum, the following relations exist between the mean - period Tm01 = 2*pi*m0/m1, the peak period Tp and the mean - zero-upcrossing period Tz: - - Tm01 = 1.086*Tz, Tp = 1.408*Tz and Tp=1.2965*Tm01 - - Examples - -------- - >>> import wafo.spectrum.models as wsm - >>> S = wsm.Bretschneider(Hm0=6.5,Tp=10) - >>> S((0,1,2,3)) - array([ 0. , 1.69350993, 0.06352698, 0.00844783]) - - See also - -------- - Jonswap, - Torsethaugen - ''' - def __init__(self, Hm0=7.0, Tp=11.0, N=5, M=4, chk_seastate=True, **kwds): - self.type = 'Bretschneider' - self.Hm0 = Hm0 - self.Tp = Tp - self.N = N - self.M = M - if chk_seastate: - self.chk_seastate() - - - def __call__(self, wi): - ''' Return Bretschnieder spectrum - ''' - w = atleast_1d(wi) - if self.Hm0 > 0: - wp = 2 * pi / self.Tp - wn = w / wp - S = (self.Hm0 / 4.0) ** 2 / wp * _gengamspec(wn, self.N, self.M) - else: - S = zeros_like(w) - return S - -def jonswap_peakfact(Hm0, Tp): - ''' Jonswap peakedness factor, gamma, given Hm0 and Tp - - Parameters - ---------- - Hm0 : significant wave height [m]. - Tp : peak period [s] - - Returns - ------- - gamma : Peakedness parameter of the JONSWAP spectrum - - Details - ------- - A standard value for GAMMA is 3.3. However, a more correct approach is - to relate GAMMA to Hm0 and Tp: - D = 0.036-0.0056*Tp/sqrt(Hm0) - gamma = exp(3.484*(1-0.1975*D*Tp**4/(Hm0**2))) - This parameterization is based on qualitative considerations of deep water - wave data from the North Sea, see Torsethaugen et. al. (1984) - Here GAMMA is limited to 1..7. - - NOTE: The size of GAMMA is the common shape of Hm0 and Tp. - - Examples - -------- - >>> import wafo.spectrum.models as wsm - >>> import pylab as plb - >>> Tp,Hs = plb.meshgrid(range(4,8),range(2,6)) - >>> gam = wsm.jonswap_peakfact(Hs,Tp) - - >>> Hm0 = plb.linspace(1,20) - >>> Tp = Hm0 - >>> [T,H] = plb.meshgrid(Tp,Hm0) - >>> gam = wsm.jonswap_peakfact(H,T) - >>> v = plb.arange(0,8) - >>> h = plb.contourf(Tp,Hm0,gam,v);h=plb.colorbar() - - >>> Hm0 = plb.arange(1,11) - >>> Tp = plb.linspace(2,16) - >>> T,H = plb.meshgrid(Tp,Hm0) - >>> gam = wsm.jonswap_peakfact(H,T) - >>> h = plb.plot(Tp,gam.T) - >>> h = plb.xlabel('Tp [s]') - >>> h = plb.ylabel('Peakedness parameter') - - >>> plb.close('all') - - See also - -------- - jonswap - ''' - Hm0, Tp = atleast_1d(Hm0, Tp) - - x = Tp / sqrt(Hm0) - - gam = ones_like(x) - - k1 = flatnonzero(x <= 5.14285714285714) - if k1.size > 0: # #limiting gamma to [1 7] - xk = x.take(k1) - D = 0.036 - 0.0056 * xk # # approx 5.061*Hm0**2/Tp**4*(1-0.287*log(gam)) - gam.put(k1, minimum(exp(3.484 * (1.0 - 0.1975 * D * xk ** 4.0)), 7.0)) # # gamma - - return gam - - -def jonswap_seastate(u10, fetch=150000., method='lewis', g=9.81, output='dict'): - ''' - Return Jonswap seastate from windspeed and fetch - - Parameters - ---------- - U10 : real scalar - windspeed at 10 m above mean water surface [m/s] - fetch : real scalar - fetch [m] - method : 'hasselman73' seastate according to Hasselman et. al. 1973 - 'hasselman76' seastate according to Hasselman et. al. 1976 - 'lewis' seastate according to Lewis and Allos 1990 - g : real scalar - accelaration of gravity [m/s**2] - output : 'dict' or 'list' - - Returns - ------- - seastate: dict where - Hm0 : significant wave height [m] - Tp : peak period [s] - gamma : jonswap peak enhancement factor. - sigmaA, - sigmaB : jonswap spectral width parameters. - Ag : jonswap alpha, normalization factor. - - Example - -------- - >>> import wafo.spectrum.models as wsm - >>> fetch = 10000; u10 = 10 - >>> ss = wsm.jonswap_seastate(u10, fetch, output='dict') - >>> for key in sorted(ss.keys()): key, ss[key] - ('Ag', 0.016257903375341734) - ('Hm0', 0.51083679198275533) - ('Tp', 2.7727680999585265) - ('gamma', 2.4824142635861119) - ('sigmaA', 0.07531733139517202) - ('sigmaB', 0.09191208451225134) - >>> S = wsm.Jonswap(**ss) - >>> S.Hm0 - 0.51083679198275533 - - # Alternatively - >>> ss1 = wsm.jonswap_seastate(u10, fetch, output='list') - >>> S1 = wsm.Jonswap(*ss1) - >>> S1.Hm0 - 0.51083679198275533 - - See also - -------- - Jonswap - - - References - ---------- - Lewis, A. W. and Allos, R.N. (1990) - JONSWAP's parameters: sorting out the inconscistencies. - Ocean Engng, Vol 17, No 4, pp 409-415 - - Hasselmann et al. (1973) - Measurements of Wind-Wave Growth and Swell Decay during the Joint - North Sea Project (JONSWAP). - Ergansungsheft, Reihe A(8), Nr. 12, Deutschen Hydrografischen Zeitschrift. - - Hasselmann et al. (1976) - A parametric wave prediction model. - J. phys. oceanogr. Vol 6, pp 200-228 - - ''' - - # The following formulas are from Lewis and Allos 1990: - zeta = g * fetch / (u10 ** 2) # dimensionless fetch, Table 1 - #zeta = min(zeta, 2.414655013429281e+004) - if method.startswith('h'): - if method[-1] == '3': # Hasselman et.al (1973) - A = 0.076 * zeta ** (-0.22) - ny = 3.5 * zeta ** (-0.33) # dimensionless peakfrequency, Table 1 - epsilon1 = 9.91e-8 * zeta ** 1.1 # dimensionless surface variance, Table 1 - else: # Hasselman et.al (1976) - A = 0.0662 * zeta ** (-0.2) - ny = 2.84 * zeta ** (-0.3) # dimensionless peakfrequency, Table 1 - epsilon1 = 1.6e-7 * zeta # dimensionless surface variance, Eq.4 - - sa = 0.07 - sb = 0.09 - gam = 3.3 - else: - A = 0.074 * zeta ** (-0.22) # Eq. 10 - ny = 3.57 * zeta ** (-0.33) # dimensionless peakfrequency, Eq. 11 - epsilon1 = 3.512e-4 * A * ny ** (-4.) * zeta ** (-0.1) # dimensionless surface variance, Eq.12 - sa = 0.05468 * ny ** (-0.32) # Eq. 13 - sb = 0.078314 * ny ** (-0.16) # Eq. 14 - gam = maximum(17.54 * zeta ** (-0.28384), 1) # Eq. 15 - - Tp = u10 / (ny * g) # Table 1 - Hm0 = 4 * sqrt(epsilon1) * u10 ** 2. / g # Table 1 - if output[0] == 'l': - return Hm0, Tp, gam, sa, sb, A - else: - return dict(Hm0=Hm0, Tp=Tp, gamma=gam, sigmaA=sa, sigmaB=sb, Ag=A) - -class Jonswap(ModelSpectrum): - ''' - Jonswap spectral density model - - Member variables - ---------------- - Hm0 : significant wave height (default 7 (m)) - Tp : peak period (default 11 (sec)) - gamma : peakedness factor determines the concentraton - of the spectrum on the peak frequency. - Usually in the range 1 <= gamma <= 7. - default depending on Hm0, Tp, see jonswap_peakedness) - sigmaA : spectral width parameter for w1: - N : scalar defining decay of high frequency part. (default 5) - M : scalar defining spectral width around the peak. (default 4) - method : String defining method used to estimate Ag when gamma>1 - 'integration': Ag = 1/gaussq(Gf*ggamspec(wn,N,M),0,wnc) (default) - 'parametric' : Ag = (1+f1(N,M)*log(gamma)**f2(N,M))/gamma - 'custom' : Ag = Ag - wnc : wc/wp normalized cut off frequency used when calculating Ag - by integration (default 6) - Parameters - ---------- - w : array-like - angular frequencies [rad/s] - - Description - ----------- - The JONSWAP spectrum is defined as - - S(w) = A * Gf * G0 * wn**(-N)*exp(-N/(M*wn**M)) - where - G0 = Normalizing factor related to Bretschneider form - A = Ag * (Hm0/4)**2 / wp (Normalization factor) - Gf = j**exp(-.5*((wn-1)/s)**2) (Peak enhancement factor) - wn = w/wp - wp = angular peak frequency - s = sigmaA for wn <= 1 - sigmaB for 1 < wn - j = gamma, (j=1, => Bretschneider spectrum) - - The JONSWAP spectrum is assumed to be especially suitable for the North Sea, - and does not represent a fully developed sea. It is a reasonable model for - wind generated sea when the seastate is in the so called JONSWAP range, i.e., - 3.6*sqrt(Hm0) < Tp < 5*sqrt(Hm0) - - The relation between the peak period and mean zero-upcrossing period - may be approximated by - Tz = Tp/(1.30301-0.01698*gamma+0.12102/gamma) - - Examples - --------- - >>> import pylab as plb - >>> import wafo.spectrum.models as wsm - >>> S = wsm.Jonswap(Hm0=7, Tp=11,gamma=1) - >>> w = plb.linspace(0,5) - >>> h = plb.plot(w,S(w)) - - >>> S2 = wsm.Bretschneider(Hm0=7, Tp=11) - >>> all(abs(S(w)-S2(w))<1.e-7) - True - >>> plb.close('all') - - See also - -------- - Bretschneider - Tmaspec - Torsethaugen - - References - ----------- - Torsethaugen et al. (1984) - Characteristica for extreme Sea States on the Norwegian continental shelf. - Report No. STF60 A84123. Norwegian Hydrodyn. Lab., Trondheim - - Hasselmann et al. (1973) - Measurements of Wind-Wave Growth and Swell Decay during the Joint - North Sea Project (JONSWAP). - Ergansungsheft, Reihe A(8), Nr. 12, Deutschen Hydrografischen Zeitschrift. - ''' - def __init__(self, Hm0=7.0, Tp=11.0, gamma=None, sigmaA=0.07, sigmaB=0.09, - Ag=None, N=5, M=4, method='integration', wnc=6.0, - chk_seastate=True): - - self.type = 'Jonswap' - self.Hm0 = Hm0 - self.Tp = Tp - self.N = N - self.M = M - self.sigmaA = sigmaA - self.sigmaB = sigmaB - self.gamma = gamma - self.Ag = Ag - self.method = method - self.wnc = wnc - - if self.gamma == None or not isfinite(self.gamma) or self.gamma < 1: - self.gamma = jonswap_peakfact(Hm0, Tp) - - self._preCalculateAg() - - if chk_seastate: - self.chk_seastate() - - def _chk_extra_param(self): - Tp = self.Tp - Hm0 = self.Hm0 - gam = self.gamma - outsideJonswapRange = Tp > 5 * sqrt(Hm0) or Tp < 3.6 * sqrt(Hm0) - if outsideJonswapRange: - txt0 = ''' - Hm0=%g,Tp=%g is outside the JONSWAP range. - The validity of the spectral density is questionable. - ''' % (Hm0, Tp) - warnings.warn(txt0) - - if gam < 1 or 7 < gam: - txt = ''' - The peakedness factor, gamma, is possibly too large. - The validity of the spectral density is questionable. - ''' - warnings.warn(txt) - - - def _localspec(self, wn): - Gf = self.peak_e_factor(wn) - return Gf * _gengamspec(wn, self.N, self.M) - - def _preCalculateAg(self): - ''' PRECALCULATEAG Precalculate normalization. - ''' - if self.gamma == 1: - self.Ag = 1.0 - self.method = 'parametric' - elif self.Ag != None: - self.method = 'custom' - if self.Ag <= 0: - raise ValueError('Ag must be larger than 0!') - elif self.method[0] == 'i': - # normalizing by integration - self.method = 'integration' - if self.wnc < 1.0: - raise ValueError('Normalized cutoff frequency, wnc, must be larger than one!') - area1, unused_err1 = integrate.quad(self._localspec, 0, 1) - area2, unused_err2 = integrate.quad(self._localspec, 1, self.wnc) - area = area1 + area2 - self.Ag = 1.0 / area - elif self.method[1] == 'p': - self.method = 'parametric' - ## # Original normalization - ## # NOTE: that Hm0**2/16 generally is not equal to intS(w)dw - ## # with this definition of Ag if sa or sb are changed from the - ## # default values - N = self.N - M = self.M - gammai = self.gamma - parametersOK = (3 <= N and N <= 50) or (2 <= M and M <= 9.5) and (1 <= gammai and gammai <= 20) - if parametersOK: - f1NM = 4.1 * (N - 2 * M ** 0.28 + 5.3) ** (-1.45 * M ** 0.1 + 0.96) - f2NM = (2.2 * M ** (-3.3) + 0.57) * N ** (-0.58 * M ** 0.37 + 0.53) - 1.04 * M ** (-1.9) + 0.94 - self.Ag = (1 + f1NM * log(gammai) ** f2NM) / gammai - - ### elseif N == 5 && M == 4, - ### options.Ag = (1+1.0*log(gammai).**1.16)/gammai - ### #options.Ag = (1-0.287*log(gammai)) - ### options.normalizeMethod = 'Three' - ### elseif N == 4 && M == 4, - ### options.Ag = (1+1.1*log(gammai).**1.19)/gammai - else: - raise ValueError('Not knowing the normalization because N, M or peakedness parameter is out of bounds!') - - if self.sigmaA != 0.07 or self.sigmaB != 0.09: - warnings.warn('Use integration to calculate Ag when sigmaA~=0.07 or sigmaB~=0.09') - - - - def peak_e_factor(self, wn): - ''' PEAKENHANCEMENTFACTOR - ''' - w = maximum(atleast_1d(wn), 0.0) - sab = where(w > 1, self.sigmaB, self.sigmaA) - - wnm12 = 0.5 * ((w - 1.0) / sab) ** 2.0 - Gf = self.gamma ** (exp(-wnm12)) - return Gf - - def __call__(self, wi): - ''' JONSWAP spectral density - ''' - w = atleast_1d(wi) - if (self.Hm0 > 0.0): - - N = self.N - M = self.M - wp = 2 * pi / self.Tp - wn = w / wp - Ag = self.Ag - Hm0 = self.Hm0 - Gf = self.peak_e_factor(wn) - S = ((Hm0 / 4.0) ** 2 / wp * Ag) * Gf * _gengamspec(wn, N, M) - else: - S = zeros_like(w) - return S - -def phi1(wi, h, g=9.81): - ''' Factor transforming spectra to finite water depth spectra. - - Input - ----- - w : arraylike - angular frequency [rad/s] - h : scalar - water depth [m] - g : scalar - acceleration of gravity [m/s**2] - Returns - ------- - tr : arraylike - transformation factors - - Example: - ------- - Transform a JONSWAP spectrum to a spectrum for waterdepth = 30 m - >>> import wafo.spectrum.models as wsm - >>> S = wsm.Jonswap() - >>> w = np.arange(3.0) - >>> S(w)*wsm.phi1(w,30.0) - array([ 0. , 1.0358056 , 0.03796281]) - - - Reference - --------- - Buows, E., Gunther, H., Rosenthal, W. and Vincent, C.L. (1985) - 'Similarity of the wind wave spectrum in finite depth water: 1 spectral form.' - J. Geophys. Res., Vol 90, No. C1, pp 975-986 - - ''' - w = atleast_1d(wi) - if h == inf: # % special case infinite water depth - return ones_like(w) - - k1 = w2k(w, 0, inf, g=g)[0] - dw1 = 2.0 * w / g # % dw/dk|h=inf - k2 = w2k(w, 0, h, g=g)[0] - - k2h = k2 * h - den = where(k1 == 0, 1, (tanh(k2h) + k2h / cosh(k2h) ** 2.0)) - dw2 = where(k1 == 0, 0, dw1 / den) # dw/dk|h=h0 - return where(k1 == 0, 0, (k1 / k2) ** 3.0 * dw2 / dw1) - -class Tmaspec(Jonswap): - ''' JONSWAP spectrum for finite water depth - - Member variables - ---------------- - h = water depth (default 42 [m]) - g : acceleration of gravity [m/s**2] - Hm0 = significant wave height (default 7 [m]) - Tp = peak period (default 11 (sec)) - gamma = peakedness factor determines the concentraton - of the spectrum on the peak frequency. - Usually in the range 1 <= gamma <= 7. - default depending on Hm0, Tp, see getjonswappeakedness) - sigmaA = spectral width parameter for w1: - N = scalar defining decay of high frequency part. (default 5) - M = scalar defining spectral width around the peak. (default 4) - method = String defining method used to estimate Ag when gamma>1 - 'integrate' : Ag = 1/gaussq(Gf.*ggamspec(wn,N,M),0,wnc) (default) - 'parametric': Ag = (1+f1(N,M)*log(gamma)^f2(N,M))/gamma - 'custom' : Ag = Ag - wnc = wc/wp normalized cut off frequency used when calculating Ag - by integration (default 6) - Parameters - ---------- - w : array-like - angular frequencies [rad/s] - - Description - ------------ - The evaluated spectrum is - S(w) = Sj(w)*phi(w,h) - where - Sj = jonswap spectrum - phi = modification due to water depth - - The concept is based on a similarity law, and its validity is verified - through analysis of 3 data sets from: TEXEL, MARSEN projects (North - Sea) and ARSLOE project (Duck, North Carolina, USA). The data include - observations at water depths ranging from 6 m to 42 m. - - Example - -------- - >>> import wafo.spectrum.models as wsm - >>> import pylab as plb - >>> w = plb.linspace(0,2.5) - >>> S = wsm.Tmaspec(h=10,gamma=1) # Bretschneider spectrum Hm0=7, Tp=11 - >>> o=plb.plot(w,S(w)) - >>> o=plb.plot(w,S(w,h=21)) - >>> o=plb.plot(w,S(w,h=42)) - >>> plb.show() - >>> plb.close('all') - - See also - --------- - Bretschneider, - Jonswap, - phi1, - Torsethaugen - - References - ---------- - Buows, E., Gunther, H., Rosenthal, W., and Vincent, C.L. (1985) - 'Similarity of the wind wave spectrum in finite depth water: 1 spectral form.' - J. Geophys. Res., Vol 90, No. C1, pp 975-986 - - Hasselman et al. (1973) - Measurements of Wind-Wave Growth and Swell Decay during the Joint - North Sea Project (JONSWAP). - Ergansungsheft, Reihe A(8), Nr. 12, deutschen Hydrografischen - Zeitschrift. - - ''' - def __init__(self, Hm0=7.0, Tp=11.0, gamma=None, sigmaA=0.07, sigmaB=0.09, - Ag=None, N=5, M=4, method='integration', wnc=6.0, - chk_seastate=True, h=42, g=9.81): - self.g = g - self.h = h - super(Tmaspec, self).__init__(Hm0, Tp, gamma, sigmaA, sigmaB, Ag, N, M, method, wnc, chk_seastate) - self.type = 'TMA' - - def phi(self, w, h=None, g=None): - if h == None: - h = self.h - if g == None: - g = self.g - return phi1(w, h, g) - - def __call__(self, w, h=None, g=None): - jonswap = super(Tmaspec, self).__call__(w) - return jonswap * self.phi(w, h, g) - -class Torsethaugen(ModelSpectrum): - ''' - Torsethaugen double peaked (swell + wind) spectrum model - - Member variables - ---------------- - Hm0 : significant wave height (default 7 (m)) - Tp : peak period (default 11 (sec)) - wnc : wc/wp normalized cut off frequency used when calculating Ag - by integration (default 6) - method : String defining method used to estimate normalization factors, Ag, - in the the modified JONSWAP spectra when gamma>1 - 'integrate' : Ag = 1/quad(Gf.*gengamspec(wn,N,M),0,wnc) - 'parametric': Ag = (1+f1(N,M)*log(gamma)**f2(N,M))/gamma - Parameters - ---------- - w : array-like - angular frequencies [rad/s] - - Description - ----------- - The double peaked (swell + wind) Torsethaugen spectrum is - modelled as S(w) = Ss(w) + Sw(w) where Ss and Sw are modified - JONSWAP spectrums for swell and wind peak, respectively. - The energy is divided between the two peaks according - to empirical parameters, which peak that is primary depends on parameters. - The empirical parameters are found for classes of Hm0 and Tp, - originating from a dataset consisting of 20 000 spectra divided - into 146 different classes of Hm0 and Tp. (Data measured at the - Statfjord field in the North Sea in a period from 1980 to 1989.) - The range of the measured Hm0 and Tp for the dataset - are from 0.5 to 11 meters and from 3.5 to 19 sec, respectively. - - Preliminary comparisons with spectra from other areas indicate that - some of the empirical parameters are dependent on geographical location. - Thus the model must be used with care for other areas than the - North Sea and sea states outside the area where measured data - are available. - - Example - ------- - >>> import wafo.spectrum.models as wsm - >>> import pylab as plb - >>> w = plb.linspace(0,4) - >>> S = wsm.Torsethaugen(Hm0=6, Tp=8) - >>> h=plb.plot(w,S(w),w,S.wind(w),w,S.swell(w)) - - See also - -------- - Bretschneider - Jonswap - - - References - ---------- - Torsethaugen, K. (2004) - "Simplified Double Peak Spectral Model for Ocean Waves" - In Proc. 14th ISOPE - - Torsethaugen, K. (1996) - Model for a doubly peaked wave spectrum - Report No. STF22 A96204. SINTEF Civil and Environm. Engineering, Trondheim - - Torsethaugen, K. (1994) - 'Model for a doubly peaked spectrum. Lifetime and fatigue strength - estimation implications.' - International Workshop on Floating Structures in Coastal zone, - Hiroshima, November 1994. - - Torsethaugen, K. (1993) - 'A two peak wave spectral model.' - In proceedings OMAE, Glasgow - - ''' - - def __init__(self, Hm0=7, Tp=11, method='integration', wnc=6, gravity=9.81, - chk_seastate=True, **kwds): - self.type = 'Torsethaugen' - self.Hm0 = Hm0 - self.Tp = Tp - self.method = method - self.wnc = wnc - self.gravity = gravity - self.wind = None - self.swell = None - if chk_seastate: - self.chk_seastate() - - self._init_spec() - - def __call__(self, w): - ''' TORSETHAUGEN spectral density - ''' - return self.wind(w) + self.swell(w) - - def _chk_extra_param(self): - Hm0 = self.Hm0 - Tp = self.Tp - if Hm0 > 11 or Hm0 > max((Tp / 3.6) ** 2, (Tp - 2) * 12 / 11): - txt0 = '''Hm0 is outside the valid range. - The validity of the spectral density is questionable''' - warnings.warn(txt0) - - if Tp > 20 or Tp < 3: - txt1 = '''Tp is outside the valid range. - The validity of the spectral density is questionable''' - warnings.warn(txt1) - - def _init_spec(self): - ''' Initialize swell and wind part of Torsethaugen spectrum - ''' - monitor = 0 - Hm0 = self.Hm0 - Tp = self.Tp - gravity1 = self.gravity # m/s**2 - - min = minimum #@ReservedAssignment - max = maximum #@ReservedAssignment - - # The parameter values below are found comparing the - # model to average measured spectra for the Statfjord Field - # in the Northern North Sea. - Af = 6.6 #m**(-1/3)*sec - AL = 2 #sec/sqrt(m) - Au = 25 #sec - KG = 35 - KG0 = 3.5 - KG1 = 1 # m - r = 0.857 # 6/7 - K0 = 0.5 #1/sqrt(m) - K00 = 3.2 - - M0 = 4 - B1 = 2 #sec - B2 = 0.7 - B3 = 3.0 #m - S0 = 0.08 #m**2*s - S1 = 3 #m - - # Preliminary comparisons with spectra from other areas indicate that - # the parameters on the line below can be dependent on geographical location - A10 = 0.7; A1 = 0.5; A20 = 0.6; A2 = 0.3; A3 = 6 - - Tf = Af * (Hm0) ** (1.0 / 3.0) - Tl = AL * sqrt(Hm0) # lower limit - Tu = Au # upper limit - - #Non-dimensional scales - # New call pab April 2005 - El = min(max((Tf - Tp) / (Tf - Tl), 0), 1) #wind sea - Eu = min(max((Tp - Tf) / (Tu - Tf), 0), 1) #Swell - - - - if Tp < Tf: # Wind dominated seas - # Primary peak (wind dominated) - Nw = K0 * sqrt(Hm0) + K00 # high frequency exponent - Mw = M0 # spectral width exponent - Rpw = min((1 - A10) * exp(-(El / A1) ** 2) + A10, 1) - Hpw = Rpw * Hm0 # significant waveheight wind - Tpw = Tp # primary peak period - # peak enhancement factor - gammaw = KG * (1 + KG0 * exp(-Hm0 / KG1)) * (2 * pi / gravity1 * Rpw * Hm0 / (Tp ** 2)) ** r - gammaw = max(gammaw, 1) - # Secondary peak (swell) - Ns = Nw # high frequency exponent - Ms = Mw # spectral width exponent - Rps = sqrt(1.0 - Rpw ** 2.0) - Hps = Rps * Hm0 # significant waveheight swell - Tps = Tf + B1 - gammas = 1.0 - - if monitor: - if Rps > 0.1: - print(' Spectrum for Wind dominated sea') - else: - print(' Spectrum for pure wind sea') - else: #swell dominated seas - - # Primary peak (swell) - Ns = K0 * sqrt(Hm0) + K00 #high frequency exponent - Ms = M0 #spectral width exponent - Rps = min((1 - A20) * exp(-(Eu / A2) ** 2) + A20, 1) - Hps = Rps * Hm0 # significant waveheight swell - Tps = Tp # primary peak period - # peak enhancement factor - gammas = KG * (1 + KG0 * exp(-Hm0 / KG1)) * (2 * pi / gravity1 * Hm0 / (Tf ** 2)) ** r * (1 + A3 * Eu) - gammas = max(gammas, 1) - - # Secondary peak (wind) - Nw = Ns # high frequency exponent - Mw = M0 * (1 - B2 * exp(-Hm0 / B3)) # spectral width exponent - Rpw = sqrt(1 - Rps ** 2) - Hpw = Rpw * Hm0 # significant waveheight wind - - C = (Nw - 1) / Mw - B = Nw / Mw - G0w = B ** C * Mw / sp.gamma(C)#normalizing factor - #G0w = exp(C*log(B)+log(Mw)-gammaln(C)) - #G0w = Mw/((B)**(-C)*gamma(C)) - - if Hpw > 0: - Tpw = (16 * S0 * (1 - exp(-Hm0 / S1)) * (0.4) ** Nw / (G0w * Hpw ** 2)) ** (-1.0 / (Nw - 1.0)) - else: - Tpw = inf - - #Tpw = max(Tpw,2.5) - gammaw = 1 - if monitor: - if Rpw > 0.1: - print(' Spectrum for swell dominated sea') - else: - print(' Spectrum for pure swell sea') - - - if monitor: - if (3.6 * sqrt(Hm0) <= Tp & Tp <= 5 * sqrt(Hm0)): - print(' Jonswap range') - - print('Hm0 = %g' % Hm0) - print('Ns, Ms = %g, %g Nw, Mw = %g, %g' % (Ns, Ms, Nw, Mw)) - print('gammas = %g gammaw = ' % (gammas, gammaw)) - print('Rps = %g Rpw = %g' % (Rps, Rpw)) - print('Hps = %g Hpw = %g' % (Hps, Hpw)) - print('Tps = %g Tpw = %g' % (Tps, Tpw)) - - - #G0s=Ms/((Ns/Ms)**(-(Ns-1)/Ms)*gamma((Ns-1)/Ms )) #normalizing factor - - # Wind part - self.wind = Jonswap(Hm0=Hpw, Tp=Tpw, gamma=gammaw, N=Nw, M=Mw, - method=self.method, chk_seastate=False) - # Swell part - self.swell = Jonswap(Hm0=Hps, Tp=Tps, gamma=gammas, N=Ns, M=Ms, - method=self.method, chk_seastate=False) - -class McCormick(Bretschneider): - ''' McCormick spectral density model - - Member variables - ---------------- - Hm0 = significant wave height (default 7 (m)) - Tp = peak period (default 11 (sec)) - Tz = zero-down crossing period (default 0.8143*Tp) - M = scalar defining spectral width around the peak. - (default depending on Tp and Tz) - - Parameters - ---------- - w : array-like - angular frequencies [rad/s] - - Description - ----------- - The McCormick spectrum parameterization is a modification of the Bretschneider - spectrum and defined as - - S(w) = (M+1)*(Hm0/4)^2/wp*(wp./w)^(M+1)*exp(-(M+1)/M*(wp/w)^M) - where - Tp/Tz=(1+1/M)^(1/M)/gamma(1+1/M) - - - Example: - -------- - >>> import wafo.spectrum.models as wsm - >>> S = wsm.McCormick(Hm0=6.5,Tp=10) - >>> S(range(4)) - array([ 0. , 1.87865908, 0.15050447, 0.02994663]) - - - See also - -------- - Bretschneider - Jonswap, - Torsethaugen - - - References: - ----------- - M.E. McCormick (1999) - "Application of the Generic Spectral Formula to Fetch-Limited Seas" - Marine Technology Society, Vol 33, No. 3, pp 27-32 - ''' - - def __init__(self, Hm0=7, Tp=11, Tz=None, M=None, chk_seastate=True): - self.type = 'McCormick' - self.Hm0 = Hm0 - self.Tp = Tp - if Tz == None: - Tz = 0.8143 * Tp - - self.Tz = Tz - if chk_seastate: - self.chk_seastate() - - if M == None and self.Hm0 > 0: - self._TpdTz = Tp / Tz - M = 1.0 / optimize.fminbound(self._localoptfun, 0.01, 5) - self.M = M - self.N = M + 1.0 - - def _localoptfun(self, x): - #LOCALOPTFUN Local function to optimize. - y = 1.0 + x - return (y ** (x) / sp.gamma(y) - self._TpdTz) ** 2.0 - -class OchiHubble(ModelSpectrum): - ''' OchiHubble bimodal spectral density model. - - Member variables - ---------------- - - Hm0 : significant wave height (default 7 (m)) - par : integer defining the parametrization (default 0) - 0 : The most probable spectrum - 1,2,...10 : gives 95% Confidence spectra - - The OchiHubble bimodal spectrum is modelled as - S(w) = Ss(w) + Sw(w) where Ss and Sw are modified Bretschneider - spectra for swell and wind peak, respectively. - - The OH spectrum is a six parameter spectrum, all functions of Hm0. - The values of these parameters are determined from a analysis of data - obtained in the North Atlantic. The source of the data is the same as - that for the development of the Pierson-Moskowitz spectrum, but - analysis is carried out on over 800 spectra including those in - partially developed seas and those having a bimodal shape. From a - statistical analysis of the data, a family of wave spectra consisting - of 11 members is generated for a desired sea severity (Hm0) with the - coefficient of 0.95. - A significant advantage of using a family of spectra for design of - marine systems is that one of the family members yields the largest - response such as motions or wave induced forces for a specified sea - severity, while another yields the smallest response with confidence - coefficient of 0.95. - - Examples - -------- - >>> import wafo.spectrum.models as wsm - >>> S = wsm.OchiHubble(par=2) - >>> S(range(4)) - array([ 0. , 0.90155636, 0.04185445, 0.00583207]) - - - See also - -------- - Bretschneider, - Jonswap, - Torsethaugen - - References: - ---------- - Ochi, M.K. and Hubble, E.N. (1976) - 'On six-parameter wave spectra.' - In Proc. 15th Conf. Coastal Engng., Vol.1, pp301-328 - - ''' - - def __init__(self, Hm0=7, par=1, chk_seastate=True): - self.type = 'Ochi Hubble' - self.Hm0 = Hm0 - self.Tp = 1 - self.par = par - self.wind = None - self.swell = None - - if chk_seastate: - self.chk_seastate() - self._init_spec() - - def __call__(self, w): - return self.wind(w) + self.swell(w) - - - def _init_spec(self): - - hp = array([[0.84, 0.54], - [0.84, 0.54], - [0.84, 0.54], - [0.84, 0.54], - [0.84, 0.54], - [0.95, 0.31], - [0.65, 0.76], - [0.90, 0.44], - [0.77, 0.64], - [0.73, 0.68], - [0.92, 0.39]]) - wa = array([ [0.7, 1.15], - [0.93, 1.5], - [0.41, 0.88], - [0.74, 1.3], - [0.62, 1.03], - [0.70, 1.50], - [0.61, 0.94], - [0.81, 1.60], - [0.54, 0.61], - [0.70, 0.99], - [0.70, 1.37]]) - wb = array([ [0.046, 0.039], - [0.056, 0.046], - [0.016, 0.026], - [0.052, 0.039], - [0.039, 0.030], - [0.046, 0.046], - [0.039, 0.036], - [0.052, 0.033], - [0.039, 0.000], - [0.046, 0.039], - [0.046, 0.039]]) - Lpar = array([[3.00, 1.54, -0.062], - [3.00, 2.77, -0.112], - [2.55, 1.82, -0.089], - [2.65, 3.90, -0.085], - [2.60, 0.53, -0.069], - [1.35, 2.48, -0.102], - [4.95, 2.48, -0.102], - [1.80, 2.95, -0.105], - [4.50, 1.95, -0.082], - [6.40, 1.78, -0.069], - [0.70, 1.78, -0.069]]) - Hm0 = self.Hm0 - Lpari = Lpar[self.par] - Li = hstack((Lpari[0], Lpari[1] * exp(Lpari[2] * Hm0))) - - Hm0i = hp[self.par] * Hm0 - Tpi = 2 * pi * exp(wb[self.par] * Hm0) / wa[self.par] - Ni = 4 * Li + 1 - Mi = [4, 4] - - self.swell = Bretschneider(Hm0=Hm0i[0], Tp=Tpi[0], N=Ni[0], M=Mi[0]) - self.wind = Bretschneider(Hm0=Hm0i[1], Tp=Tpi[1], N=Ni[1], M=Mi[1]) - - def _chk_extra_param(self): - if self.par < 0 or 10 < self.par: - raise ValueError('Par must be an integer from 0 to 10!') - -class Wallop(Bretschneider): - '''Wallop spectral density model. - - Member variables - ---------------- - Hm0 = significant wave height (default 7 (m)) - Tp = peak period (default 11 (sec)) - N = shape factor, i.e. slope for the high frequency -% part (default depending on Hm0 and Tp, see below) - - Parameters - ---------- - w : array-like - angular frequencies [rad/s] - - Description - ----------- - The WALLOP spectrum parameterization is a modification of the Bretschneider - spectrum and defined as - - S(w) = A * G0 * wn**(-N)*exp(-N/(4*wn**4)) - where - G0 = Normalizing factor related to Bretschneider form - A = (Hm0/4)^2 / wp (Normalization factor) - wn = w/wp - wp = 2*pi/Tp, angular peak frequency - N = abs((log(2*pi^2)+2*log(Hm0/4)-2*log(Lp))/log(2)) - Lp = wave length corresponding to the peak frequency, wp. - - If N=5 it becomes the same as the JONSWAP spectrum with - peak enhancement factor gamma=1 or the Bretschneider - (Pierson-Moskowitz) spectrum. - - Example: - -------- - >>> import wafo.spectrum.models as wsm - >>> S = wsm.Wallop(Hm0=6.5, Tp=10) - >>> S(range(4)) - array([ 0.00000000e+00, 9.36921871e-01, 2.76991078e-03, - 7.72996150e-05]) - - See also - -------- - Bretschneider - Jonswap, - Torsethaugen - - References: - ----------- - Huang, N.E., Long, S.R., Tung, C.C, Yuen, Y. and Bilven, L.F. (1981) - "A unified two parameter wave spectral model for a generous sea state" - J. Fluid Mechanics, Vol.112, pp 203-224 - ''' - - def __init__(self, Hm0=7, Tp=11, N=None, chk_seastate=True): - self.type = 'Wallop' - self.Hm0 = Hm0 - self.Tp = Tp - self.M = 4 - if N is None: - wp = 2. * pi / Tp - kp = w2k(wp, 0, inf)[0] # wavenumber at peak frequency - Lp = 2. * pi / kp # wave length at the peak frequency - N = abs((log(2. * pi ** 2.) + 2 * log(Hm0 / 4) - 2.0 * log(Lp)) / log(2)) - - self.N = N - - if chk_seastate: - self.chk_seastate() - -class Spreading(object): - ''' - Directional spreading function. - - Member variables - ---------------- - Returns - -------- - D = Directional spreading function returning - S = D(theta,w,wc) where S is a Nt X Nw matrix with the principal - direction always along the x-axis. - Member varialbes - ---------------- - type = type of spreading function, see options below (default 'cos2s') - 'cos2s' : cos-2s spreading N(S)*[cos((theta-theta0)/2)]**(2*S) (0 < S) - 'box' : Box-car spreading N(A)*I( -A < theta-theta0 < A) (0 < A < pi) - 'mises' : von Mises spreading N(K)*exp(K*cos(theta-theta0)) (0 < K) - 'poisson': Poisson spreading N(X)/(1-2*X*cos(theta-theta0)+X**2) (0 < X < 1) - 'sech2' : sech-2 spreading N(B)*sech(B*(theta-theta0))**2 (0 < B) - 'wnormal': Wrapped Normal - [1 + 2*sum exp(-(n*D1)^2/2)*cos(n*(theta-theta0))]/(2*pi) (0 < D1) - (N(.) = normalization factor) - (the first letter is enough for unique identification) - - theta0 = function handle, inline object, matrix or a scalar defining - average direction given in radians at every angular frequency. - (length 1 or length == length(wn)) (default 0) - method = Defines function used for direcional spreading parameter: - 0, None : S(wn) = spa, frequency independent - 1, 'mitsuyasu': S(wn) frequency dependent (default) - 2, 'donelan' : B(wn) frequency dependent - 3, 'banner' : B(wn) frequency dependent - S(wn) = spa *(wn)^ma, : wnlo <= wn < wnc - = spb *(wn)^mb, : wnc <= wn < wnup - = 0 : wnup <= wn - B(wn) = S(wn) : wnlo <= wn < wnup - = spb*wnup^mb : wnup <= wn, method = 2 - = sc*F(wn) : wnup <= wn , method = 3 - where F(wn) = 10^(-0.4+0.8393*exp(-0.567*log(wn^2))) and sc is - scalefactor to make the spreading funtion continous. - wnlimits = [wnlo wnc wnup] limits used in the function defining the - directional spreading parameter. - wnc is the normalized cutover frequency (default [0 1 inf]) - sp = [spa,spb] maximum spread parameters (default [15 15]) - m = [ma,mb] shape parameters (default [5 -2.5]) - - SPREADING Return function handle to a Directional spreading function. - Here the S- or B-parameter, of the COS-2S and SECH-2 spreading function, - respectively, is used as a measure of spread. All the parameters of the - other distributions are related to this parameter through the first Fourier - coefficient, R1, of the directional distribution as follows: - R1 = S/(S+1) or S = R1/(1-R1). - where - Box-car spreading : R1 = sin(A)/A - Von Mises spreading: R1 = besseli(1,K)/besseli(0,K), - Poisson spreading : R1 = X - sech-2 spreading : R1 = pi/(2*B*sinh(pi/(2*B)) - Wrapped Normal : R1 = exp(-D1^2/2) - - A value of S = 15 corresponds to - 'box' : A=0.62, 'sech2' : B=0.89 - 'mises' : K=8.3, 'poisson': X=0.94 - 'wnormal': D=0.36 - - The COS2S is the most frequently used spreading in engineering practice. - Apart from the current meter/pressure cell data in WADIC all - instruments seem to support the 'cos2s' distribution for heavier sea - states, (Krogstad and Barstow, 1999). For medium sea states - a spreading function between COS2S and POISSON seem appropriate, - while POISSON seems appropriate for swell. - For the COS2S Mitsuyasu et al. parameterized SPa = SPb = - 11.5*(U10/Cp) where Cp = g/wp is the deep water phase speed at wp and - U10 the wind speed at reference height 10m. Hasselman et al. (1980) - parameterized mb = -2.33-1.45*(U10/Cp-1.17). - Mitsuyasu et al. (1975) showed that SP for wind waves varies from - 5 to 30 being a function of dimensionless wind speed. - However, Goda and Suzuki (1975) proposed SP = 10 for wind waves, SP = 25 - for swell with short decay distance and SP = 75 for long decay distance. - Compared to experiments Krogstad et al. (1998) found that ma = 5 +/- eps and - that -1< mb < -3.5. - Values given in the litterature: [spa spb ma mb wlim(1:3) ] - (Mitsuyasu: spa == spb) (cos-2s) [15 15 5 -2.5 0 1 3 ] - (Hasselman: spa ~= spb) (cos-2s) [6.97 9.77 4.06 -2.3 0 1.05 3 ] - (Banner : spa ~= spb) (sech2) [2.61 2.28 1.3 -1.3 0.56 0.95 1.6] - - Examples - -------- - >>> import wafo.spectrum.models as wsm - >>> import pylab as plb - >>> D = wsm.Spreading('cos2s',s_a=10.0) - - # Make directionale spectrum - >>> S = wsm.Jonswap().tospecdata() - >>> SD = D.tospecdata2d(S) - >>> h = SD.plot() - - >>> w = plb.linspace(0,3,257) - >>> theta = plb.linspace(-pi,pi,129) - >>> t = plb.contour(D(theta,w)[0].squeeze()) - - # Make frequency dependent direction spreading - >>> theta0 = lambda w: w*plb.pi/6.0 - >>> D2 = wsm.Spreading('cos2s',theta0=theta0) - >>> t = plb.contour(D2(theta,w)[0]) - - # Plot all spreading functions - >>> alltypes = ('cos2s','box','mises','poisson','sech2','wrap_norm') - >>> for ix in range(len(alltypes)): - ... D3 = wsm.Spreading(alltypes[ix]) - ... t = plb.figure(ix) - ... t = plb.contour(D3(theta,w)[0]) - ... t = plb.title(alltypes[ix]) - >>> plb.close('all') - - - See also - -------- - mkdspec, plotspec, spec2spec - - References - --------- - Krogstad, H.E. and Barstow, S.F. (1999) - "Directional Distributions in Ocean Wave Spectra" - Proceedings of the 9th ISOPE Conference, Vol III, pp. 79-86 - - Goda, Y. (1999) - "Numerical simulation of ocean waves for statistical analysis" - Marine Tech. Soc. Journal, Vol. 33, No. 3, pp 5--14 - - Banner, M.L. (1990) - "Equilibrium spectra of wind waves." - J. Phys. Ocean, Vol 20, pp 966--984 - Donelan M.A., Hamilton J, Hui W.H. (1985) - "Directional spectra of wind generated waves." - Phil. Trans. Royal Soc. London, Vol A315, pp 387--407 - - Hasselmann D, Dunckel M, Ewing JA (1980) - "Directional spectra observed during JONSWAP." - J. Phys. Ocean, Vol.10, pp 1264--1280 - - Mitsuyasu, H, et al. (1975) - "Observation of the directional spectrum of ocean waves using a - coverleaf buoy." - J. Physical Oceanography, Vol.5, No.4, pp 750--760 - Some of this might be included in help header: - cos-2s: - NB! The generally strong frequency dependence in directional spread - makes it questionable to run load tests of ships and structures with a - directional spread independent of frequency (Krogstad and Barstow, 1999). - ''' -##% Parameterization of B -##% def = 2 Donelan et al freq. parametrization for 'sech2' -##% def = 3 Banner freq. parametrization for 'sech2' -##% (spa ~= spb) (sech-2) [2.61 2.28 1.3 -1.3 0.56 0.95 1.6] -## -## -##% Tested on: Matlab 7.0 -##% History: -##% revised pab jan 2007 -##% - renamed from spreading to mkspreading -##% - the function now return a function handle to the actual spreading function. -##% - removed wc, the cut over frequency -> input is now assumed as normalized frequency, w/wc. -##% revised pab 17.06.2001 -##% - added wrapped normal spreading -##% revised pab 6 April 2001 -##% - added fourier2distpar -##% - Fixed the normalization of sech2 spreading -##% revised by PAB and IR 1 April 2001: Introducing the azymuth as a -##% standard parameter in order to avoid rotations of the directions -##% theta. The x-axis is always pointing into the principal direction -##% as defined in the spreading function D(omega,theta). The actual -##% principal direction is defined by means of field D.phi. -##% revised es 06.06.2000, commented away: if ((ma==0) & (mb==0)), ..., -##% hoping that the check is unnecessary -##% revised pab 13.06.2000 -##% - fixed a serious bug: made sure -pi<= th-th0 <=pi -##% revised pab 16.02.2000 -##% -fixed default value for Hasselman parametrization -##% revised pab 02.02.2000 -##% - Nt or th may be specified + check on th -##% - added frequency dependence for sech-2 -##% - th0 as separate input -##% - updated header info -##% - changed check for nargins -##% - added the possibility of nan's in data vector -##% Revised by jr 2000.01.25 -##% - changed check of nargins -##% - frequency dependence only for cos-2s -##% - updated information -##% By es, jr 1999.11.25 - - - def __init__(self, type='cos2s', theta0=0, method='mitsuyasu', s_a=15., s_b=15., m_a=5., m_b= -2.5, wn_lo=0.0, wn_c=1., wn_up=inf): #@ReservedAssignment - - self.type = type - self.theta0 = theta0 - self.method = method - self.s_a = s_a - self.s_b = s_b - self.m_a = m_a - self.m_b = m_b - self.wn_lo = wn_lo - self.wn_c = wn_c - self.wn_up = wn_up - - methods = dict(n=None, m='mitsuyasu', d='donelan', b='banner') - methodslist = (None, 'mitsuyasu', 'donelan', 'banner') - - if isinstance(self.method, str): - if not self.method[0] in methods: - raise ValueError('Unknown method') - self.method = methods[self.method[0]] - elif self.method == None: - pass - else: - if method < 0 or 3 < method: - method = 2 - self.method = methodslist[method] - - self._spreadfun = dict(c=self.cos2s, b=self.box, m=self.mises, - p=self.poisson, s=self.sech2, w=self.wrap_norm) - self._fourierdispatch = dict(b=self.fourier2a, m=self.fourier2k, - p=self.fourier2x, s=self.fourier2b, - w=self.fourier2d) - - def __call__(self, *args, **kwds): - spreadfun = self._spreadfun[self.type[0]] - return spreadfun(*args, **kwds) - - def chk_input(self, theta, w=1, wc=1): # [s_par,TH,phi0,Nt] = - ''' CHK_INPUT - - CALL [s_par,TH,phi0,Nt] = inputchk(theta,w,wc) - ''' - - wn = atleast_1d(w / wc) - theta = theta.ravel() - Nt = len(theta) - - # Make sure theta is from -pi to pi - phi0 = 0.0 - theta = mod(theta + pi, 2 * pi) - pi - - - if hasattr(self.theta0, '__call__'): - th0 = self.theta0(wn.flatten()) - else: - th0 = atleast_1d(self.theta0).flatten() - - Nt0 = th0.size - Nw = w.size - isFreqDepDir = (Nt0 == Nw) - if isFreqDepDir: - # frequency dependent spreading and/or - # frequency dependent direction - # make sure -pi<=TH= 1): - raise ValueError('POISSON spreading: X value must be less than 1') - return X - - def fourier2a(self, r1): - ''' Returns the solution of R1 = sin(A)/A. - ''' - A0 = flipud(linspace(0, pi + 0.1, 1025)) - funA = interp1d(sinc(A0 / pi), A0) - A0 = funA(r1.ravel()) - A = asarray(A0) - - # Newton-Raphson - da = ones_like(r1) - - max_count = 100 - ix = flatnonzero(A) - for unused_iy in range(max_count): - Ai = A[ix] - da[ix] = (sin(Ai) - Ai * r1[ix]) / (cos(Ai) - r1[ix]) - Ai = Ai - da[ix] - # Make sure that the current guess is larger than zero and less than pi - #x(ix) = xi + 0.1*(dx(ix) - 9*xi).*(xi<=0) + 0.38*(dx(ix)-6.2*xi +6.2).*(xi>pi) - # Make sure that the current guess is larger than zero. - A[ix] = Ai + 0.5 * (da[ix] - Ai) * (Ai <= 0.0) - - ix = flatnonzero((abs(da) > sqrt(eps) * abs(A)) * (abs(da) > sqrt(eps))) - if ix.size == 0: - if any(A > pi): - raise ValueError('BOX-CAR spreading: The A value must be less than pi') - return A.clip(min=1e-16, max=pi) - - - warnings.warn('Newton raphson method did not converge.') - return A.clip(min=1e-16) # Avoid division by zero - - def fourier2k(self, r1): - ''' - Returns the solution of R1 = besseli(1,K)/besseli(0,K), - ''' - K0 = hstack((linspace(0, 10, 513), linspace(10.00001, 100))) - fun0 = lambda x : sp.ive(1, x) / sp.ive(0, x) - funK = interp1d(fun0(K0), K0) - K0 = funK(r1.ravel()) - k1 = flatnonzero(isnan(K0)) - if (k1.size > 0): - K0[k1] = 0.0 - K0[k1] = K0.max() - - ix0 = flatnonzero(r1 != 0.0) - K = zeros_like(r1) - fun = lambda x : fun0(x) - r1[ix] - for ix in ix0: - K[ix] = optimize.fsolve(fun, K0[ix]) - return K - - def fourier2b(self, r1): - ''' Returns the solution of R1 = pi/(2*B*sinh(pi/(2*B)). - ''' - B0 = hstack((linspace(eps, 5, 513), linspace(5.0001, 100))) - funB = interp1d(self._r1ofsech2(B0), B0) - - B0 = funB(r1.ravel()) - k1 = flatnonzero(isnan(B0)) - if (k1.size > 0): - B0[k1] = 0.0 - B0[k1] = max(B0) - - ix0 = flatnonzero(r1 != 0.0) - B = zeros_like(r1) - fun = lambda x : 0.5 * pi / (sinh(.5 * pi / x)) - x * r1[ix] - for ix in ix0: - B[ix] = abs(optimize.fsolve(fun, B0[ix])) - return B - - def fourier2d(self, r1): - ''' Returns the solution of R1 = exp(-D**2/2). - ''' - r = clip(r1, 0., 1.0) - return where(r <= 0, inf, sqrt(-2.0 * log(r))) - - def spread_par_s(self, wn): - ''' Return spread parameter, S, of COS2S function - - Parameters - ---------- - wn : array_like - normalized frequencies. - Returns - ------- - S : ndarray - spread parameter of COS2S functions - ''' - if self.method == None: - # no frequency dependent spreading, - # but possible frequency dependent direction - s = atleast_1d(self.s_a) - else: - wn_lo = self.wn_lo - wn_up = self.wn_up - wn_c = self.wn_c - - spa = self.s_a - spb = self.s_b - ma = self.m_a - mb = self.m_b - - - # Mitsuyasu et. al and Hasselman et. al parametrization of - # frequency dependent spreading - s = where(wn <= wn_c, spa * wn ** ma, spb * wn ** mb) - s[wn <= wn_lo] = 0.0 - - k = flatnonzero(wn_up < wn) - if k.size > 0: - if self.method[0] == 'd': - # Donelan et. al. parametrization for B in SECH-2 - s[k] = spb * (wn_up) ** mb - - # Convert to S-paramater in COS-2S distribution - r1 = self.r1ofsech2(s) - s = r1 / (1. - r1) - - elif self.method[0] == 'b': - # Banner parametrization for B in SECH-2 - s3m = spb * (wn_up) ** mb - s3p = self._donelan(wn_up) - scale = s3m / s3p #% Scale so that parametrization will be continous - s[k] = scale * self.donelan(wn[k]) - r1 = self.r1ofsech2(s) - - #% Convert to S-paramater in COS-2S distribution - s = r1 / (1. - r1) - else: - s[k] = 0.0 - - if any(s < 0): - raise ValueError('The COS2S spread parameter, S(w), value must be larger than 0') - return s - - - def _donelan(self, wn): - ''' High frequency decay of B of sech2 paramater - ''' - return 10.0 ** (-0.4 + 0.8393 * exp(-0.567 * log(wn ** 2))) - - def _r1ofsech2(self, B): - ''' R1OFSECH2 Computes R1 = pi./(2*B.*sinh(pi./(2*B))) - ''' - realmax = finfo(float).max - tiny = 1. / realmax - x = clip(2. * B, tiny, realmax) - xk = pi / x - return where(x < 100., xk / sinh(xk), -2. * xk / (exp(xk) * expm1(-2. * xk))) - - def tospecdata2d(self, specdata=None, theta=None, wc=0.52, nt=51): - ''' - MKDSPEC Make a directional spectrum - frequency spectrum times spreading function - - CALL: Snew=mkdspec(S,D,plotflag) - - Snew = directional spectrum (spectrum struct) - S = frequency spectrum (spectrum struct) - (default jonswap) - D = spreading function (special struct) - (default spreading([],'cos2s')) - plotflag = 1: plot the spectrum, else: do not plot (default 0) - - Creates a directional spectrum through multiplication of a frequency - spectrum and a spreading function: S(w,theta)=S(w)*D(w,theta) - - The spreading structure must contain the following fields: - .S (size [np 1] or [np nf]) and .theta (length np) - optional fields: .w (length nf), .note (memo) .phi (rotation-azymuth) - - NB! S.w and D.w (if any) must be identical. - - Example - ------- - >>> import wafo.spectrum.models as wsm - >>> S = wsm.Jonswap().tospecdata() - >>> D = wsm.Spreading('cos2s') - >>> SD = D.tospecdata2d(S) - >>> h = SD.plot() - - See also spreading, rotspec, jonswap, torsethaugen - ''' - - if specdata is None: - specdata = Jonswap().tospecdata() - if theta is None: - pi = np.pi - theta = np.linspace(-pi,pi,nt) - else: - L = abs(theta[-1]-theta[0]) - if abs(L-pi)>eps: - raise ValueError('theta must cover all angles -pi -> pi') - nt = len(theta) - - if nt<40: - warnings.warn('Number of angles is less than 40. Spreading too sparsely sampled!') - - w = specdata.args - S = specdata.data - D, phi0 = self(theta, w=w, wc=wc) - if D.ndim != 2: # frequency dependent spreading - D = D[:, None] - - SD = D * S[None,:] - - Snew = SpecData2D(SD,(w,theta), type='dir', freqtype=specdata.freqtype) - Snew.tr = specdata.tr - Snew.h = specdata.h - Snew.phi = phi0 - Snew.norm = specdata.norm - #Snew.note = specdata.note + ', spreading: %s' % self.type - return Snew - -def _test_some_spectra(): - S = Jonswap() - - w = arange(3.0) - S(w) * phi1(w, 30.0) - S1 = S.tospecdata(w) - S1.plot() - - - import pylab as plb - w = plb.linspace(0, 2.5) - S = Tmaspec(h=10, gamma=1) # Bretschneider spectrum Hm0=7, Tp=11 - plb.plot(w, S(w)) - plb.plot(w, S(w, h=21)) - plb.plot(w, S(w, h=42)) - plb.show() - plb.close('all') - - - - #import pylab as plb - #w = plb.linspace(0,3) - w, th = plb.ogrid[0:4, 0:6] - k, k2 = w2k(w, th) - #k1, k12 = w2k(w, th, h=20) - plb.plot(w, k, w, k2) - - plb.show() - - plb.close('all') - w = plb.linspace(0, 2, 100) - S = Torsethaugen(Hm0=6, Tp=8) - plb.plot(w, S(w), w, S.wind(w), w, S.swell(w)) - - S1 = Jonswap(Hm0=7, Tp=11, gamma=1) - w = plb.linspace(0, 2, 100) - plb.plot(w, S1(w)) - plb.show() - plb.close('all') - - - Hm0 = plb.arange(1, 11) - Tp = plb.linspace(2, 16) - T, H = plb.meshgrid(Tp, Hm0) - gam = jonswap_peakfact(H, T) - plb.plot(Tp, gam.T) - plb.xlabel('Tp [s]') - plb.ylabel('Peakedness parameter') - - Hm0 = plb.linspace(1, 20) - Tp = Hm0 - [T, H] = plb.meshgrid(Tp, Hm0) - gam = jonswap_peakfact(H, T) - v = plb.arange(0, 8) - plb.contourf(Tp, Hm0, gam, v) - plb.colorbar() - plb.show() - plb.close('all') - -def _test_spreading(): - import pylab as plb - pi = plb.pi - w = plb.linspace(0, 3, 257) - theta = plb.linspace(-pi, pi, 129) - theta0 = lambda w: w * plb.pi / 6.0 - D2 = Spreading('cos2s', theta0=theta0) - d1 = D2(theta, w)[0] - _t = plb.contour(d1.squeeze()) - - pi = plb.pi - D = Spreading('wrap_norm', s_a=10.0) - - w = plb.linspace(0, 3, 257) - theta = plb.linspace(-pi, pi, 129) - d1 = D(theta, w) - plb.contour(d1[0]) - plb.show() - -def test_docstrings(): - import doctest - doctest.testmod() - -def main(): - if False: # True: # - _test_some_spectra() - else: - test_docstrings() - -if __name__ == '__main__': - main() +""" +Models module +------------- + +Dispersion relation +------------------- +k2w - Translates from wave number to frequency +w2k - Translates from frequency to wave number + +Model spectra +------------- +Bretschneider - Bretschneider spectral density. +Jonswap - JONSWAP spectral density +McCormick - McCormick spectral density. +OchiHubble - OchiHubble bimodal spectral density model. +Tmaspec - JONSWAP spectral density for finite water depth +Torsethaugen - Torsethaugen double peaked (swell + wind) spectrum model +Wallop - Wallop spectral density. +demospec - Loads a precreated spectrum of chosen type +jonswap_peakfact - Jonswap peakedness factor Gamma given Hm0 and Tp +jonswap_seastate - jonswap seastate from windspeed and fetch + +Directional spreading functions +------------------------------- +Spreading - Directional spreading function. + +""" + +# Name: models +# Purpose: Interface to various spectrum models +# +# Author: pab +# +# Created: 29.08.2008 +# Copyright: (c) pab 2008 +# Licence: + +#!/usr/bin/env python +from __future__ import division + +import warnings +from scipy.interpolate import interp1d +import scipy.optimize as optimize +import scipy.integrate as integrate +import scipy.special as sp +from scipy.fftpack import fft +import numpy as np +from numpy import (inf, atleast_1d, newaxis, any, minimum, maximum, array, + asarray, exp, log, sqrt, where, pi, arange, linspace, sin, + cos, abs, sinh, isfinite, mod, expm1, tanh, cosh, finfo, + ones, ones_like, isnan, zeros_like, flatnonzero, sinc, + hstack, vstack, real, flipud, clip) +from wafo.wave_theory.dispersion_relation import w2k, k2w # @UnusedImport +from wafo.spectrum import SpecData1D, SpecData2D +sech = lambda x: 1.0 / cosh(x) + +eps = finfo(float).eps + + +__all__ = ['Bretschneider', 'Jonswap', 'Torsethaugen', 'Wallop', 'McCormick', + 'OchiHubble', 'Tmaspec', 'jonswap_peakfact', 'jonswap_seastate', + 'spreading', 'w2k', 'k2w', 'phi1'] + + +def _gengamspec(wn, N=5, M=4): + ''' Return Generalized gamma spectrum in dimensionless form + + Parameters + ---------- + wn : arraylike + normalized frequencies, w/wp. + N : scalar + defining the decay of the high frequency part. + M : scalar + defining the spectral width around the peak. + + Returns + ------- + S : arraylike + spectral values, same size as wn. + + The generalized gamma spectrum in non- + dimensional form is defined as: + + S = G0.*wn.**(-N).*exp(-B*wn.**(-M)) for wn > 0 + = 0 otherwise + where + B = N/M + C = (N-1)/M + G0 = B**C*M/gamma(C), Normalizing factor related to Bretschneider form + + Note that N = 5, M = 4 corresponds to a normalized + Bretschneider spectrum. + + Examples + -------- + >>> import wafo.spectrum.models as wsm + >>> import numpy as np + >>> wn = np.linspace(0,4,5) + >>> wsm._gengamspec(wn, N=6, M=2) + array([ 0. , 1.16765216, 0.17309961, 0.02305179, 0.00474686]) + + See also + -------- + Bretschneider + Jonswap, + Torsethaugen + + + References + ---------- + Torsethaugen, K. (2004) + "Simplified Double Peak Spectral Model for Ocean Waves" + In Proc. 14th ISOPE + ''' + w = atleast_1d(wn) + S = zeros_like(w) + + k = flatnonzero(w > 0.0) + if k.size > 0: + B = N / M + C = (N - 1.0) / M + + # A = Normalizing factor related to Bretschneider form + # A = B**C*M/gamma(C) + # S[k] = A*wn[k]**(-N)*exp(-B*wn[k]**(-M)) + logwn = log(w.take(k)) + logA = (C * log(B) + log(M) - sp.gammaln(C)) + S.put(k, exp(logA - N * logwn - B * exp(-M * logwn))) + return S + + +class ModelSpectrum(object): + + def __init__(self, Hm0=7.0, Tp=11.0, **kwds): + self.Hm0 = Hm0 + self.Tp = Tp + self.type = 'ModelSpectrum' + + def tospecdata(self, w=None, wc=None, nw=257): + ''' + Return SpecData1D object from ModelSpectrum + + Parameter + --------- + w : arraylike + vector of angular frequencies used in discretization of spectrum + wc : scalar + cut off frequency (default 33/Tp) + nw : int + number of frequencies + + Returns + ------- + S : SpecData1D object + member attributes of model spectrum are copied to S.workspace + ''' + + if w is None: + if wc is None: + wc = 33. / self.Tp + w = linspace(0, wc, nw) + S = SpecData1D(self.__call__(w), w) + try: + h = self.h + S.h = h + except: + pass + S.labels.title = self.type + ' ' + S.labels.title + S.workspace = self.__dict__.copy() + return S + + def chk_seastate(self): + ''' Check if seastate is valid + ''' + + if self.Hm0 < 0: + raise ValueError('Hm0 can not be negative!') + + if self.Tp <= 0: + raise ValueError('Tp must be positve!') + + if self.Hm0 == 0.0: + warnings.warn('Hm0 is zero!') + + self._chk_extra_param() + + def _chk_extra_param(self): + pass + + +class Bretschneider(ModelSpectrum): + + ''' + Bretschneider spectral density model + + Member variables + ---------------- + Hm0 : significant wave height (default 7 (m)) + Tp : peak period (default 11 (sec)) + N : scalar defining decay of high frequency part. (default 5) + M : scalar defining spectral width around the peak. (default 4) + + Parameters + ---------- + w : array-like + angular frequencies [rad/s] + + + The Bretschneider spectrum is defined as + + S(w) = A * G0 * wn**(-N)*exp(-N/(M*wn**M)) + where + G0 = Normalizing factor related to Bretschneider form + A = (Hm0/4)**2 / wp (Normalization factor) + wn = w/wp + wp = 2*pi/Tp, angular peak frequency + + This spectrum is a suitable model for fully developed sea, + i.e. a sea state where the wind has been blowing long enough over a + sufficiently open stretch of water, so that the high-frequency waves have + reached an equilibrium. In the part of the spectrum where the frequency is + greater than the peak frequency (w>wp), the energy distribution is + proportional to w**-5. + The spectrum is identical with ITTC (International Towing Tank + Conference), ISSC (International Ship and Offshore Structures Congress) + and Pierson-Moskowitz, wave spectrum given Hm0 and Tm01. It is also + identical with JONSWAP when the peakedness factor, gamma, is one. + For this spectrum, the following relations exist between the mean + period Tm01 = 2*pi*m0/m1, the peak period Tp and the mean + zero-upcrossing period Tz: + + Tm01 = 1.086*Tz, Tp = 1.408*Tz and Tp=1.2965*Tm01 + + Examples + -------- + >>> import wafo.spectrum.models as wsm + >>> S = wsm.Bretschneider(Hm0=6.5,Tp=10) + >>> S((0,1,2,3)) + array([ 0. , 1.69350993, 0.06352698, 0.00844783]) + + See also + -------- + Jonswap, + Torsethaugen + ''' + + def __init__(self, Hm0=7.0, Tp=11.0, N=5, M=4, chk_seastate=True, **kwds): + self.type = 'Bretschneider' + self.Hm0 = Hm0 + self.Tp = Tp + self.N = N + self.M = M + if chk_seastate: + self.chk_seastate() + + def __call__(self, wi): + ''' Return Bretschnieder spectrum + ''' + w = atleast_1d(wi) + if self.Hm0 > 0: + wp = 2 * pi / self.Tp + wn = w / wp + S = (self.Hm0 / 4.0) ** 2 / wp * _gengamspec(wn, self.N, self.M) + else: + S = zeros_like(w) + return S + + +def jonswap_peakfact(Hm0, Tp): + ''' Jonswap peakedness factor, gamma, given Hm0 and Tp + + Parameters + ---------- + Hm0 : significant wave height [m]. + Tp : peak period [s] + + Returns + ------- + gamma : Peakedness parameter of the JONSWAP spectrum + + Details + ------- + A standard value for GAMMA is 3.3. However, a more correct approach is + to relate GAMMA to Hm0 and Tp: + D = 0.036-0.0056*Tp/sqrt(Hm0) + gamma = exp(3.484*(1-0.1975*D*Tp**4/(Hm0**2))) + This parameterization is based on qualitative considerations of deep water + wave data from the North Sea, see Torsethaugen et. al. (1984) + Here GAMMA is limited to 1..7. + + NOTE: The size of GAMMA is the common shape of Hm0 and Tp. + + Examples + -------- + >>> import wafo.spectrum.models as wsm + >>> import pylab as plb + >>> Tp,Hs = plb.meshgrid(range(4,8),range(2,6)) + >>> gam = wsm.jonswap_peakfact(Hs,Tp) + + >>> Hm0 = plb.linspace(1,20) + >>> Tp = Hm0 + >>> [T,H] = plb.meshgrid(Tp,Hm0) + >>> gam = wsm.jonswap_peakfact(H,T) + >>> v = plb.arange(0,8) + >>> h = plb.contourf(Tp,Hm0,gam,v);h=plb.colorbar() + + >>> Hm0 = plb.arange(1,11) + >>> Tp = plb.linspace(2,16) + >>> T,H = plb.meshgrid(Tp,Hm0) + >>> gam = wsm.jonswap_peakfact(H,T) + >>> h = plb.plot(Tp,gam.T) + >>> h = plb.xlabel('Tp [s]') + >>> h = plb.ylabel('Peakedness parameter') + + >>> plb.close('all') + + See also + -------- + jonswap + ''' + Hm0, Tp = atleast_1d(Hm0, Tp) + + x = Tp / sqrt(Hm0) + + gam = ones_like(x) + + k1 = flatnonzero(x <= 5.14285714285714) + if k1.size > 0: # limiting gamma to [1 7] + xk = x.take(k1) + D = 0.036 - 0.0056 * xk # approx 5.061*Hm0**2/Tp**4*(1-0.287*log(gam)) + # gamma + gam.put(k1, minimum(exp(3.484 * (1.0 - 0.1975 * D * xk ** 4.0)), 7.0)) + + return gam + + +def jonswap_seastate(u10, fetch=150000., method='lewis', g=9.81, + output='dict'): + ''' + Return Jonswap seastate from windspeed and fetch + + Parameters + ---------- + U10 : real scalar + windspeed at 10 m above mean water surface [m/s] + fetch : real scalar + fetch [m] + method : 'hasselman73' seastate according to Hasselman et. al. 1973 + 'hasselman76' seastate according to Hasselman et. al. 1976 + 'lewis' seastate according to Lewis and Allos 1990 + g : real scalar + accelaration of gravity [m/s**2] + output : 'dict' or 'list' + + Returns + ------- + seastate: dict where + Hm0 : significant wave height [m] + Tp : peak period [s] + gamma : jonswap peak enhancement factor. + sigmaA, + sigmaB : jonswap spectral width parameters. + Ag : jonswap alpha, normalization factor. + + Example + -------- + >>> import wafo.spectrum.models as wsm + >>> fetch = 10000; u10 = 10 + >>> ss = wsm.jonswap_seastate(u10, fetch, output='dict') + >>> for key in sorted(ss.keys()): key, ss[key] + ('Ag', 0.016257903375341734) + ('Hm0', 0.51083679198275533) + ('Tp', 2.7727680999585265) + ('gamma', 2.4824142635861119) + ('sigmaA', 0.07531733139517202) + ('sigmaB', 0.09191208451225134) + >>> S = wsm.Jonswap(**ss) + >>> S.Hm0 + 0.51083679198275533 + + # Alternatively + >>> ss1 = wsm.jonswap_seastate(u10, fetch, output='list') + >>> S1 = wsm.Jonswap(*ss1) + >>> S1.Hm0 + 0.51083679198275533 + + See also + -------- + Jonswap + + + References + ---------- + Lewis, A. W. and Allos, R.N. (1990) + JONSWAP's parameters: sorting out the inconscistencies. + Ocean Engng, Vol 17, No 4, pp 409-415 + + Hasselmann et al. (1973) + Measurements of Wind-Wave Growth and Swell Decay during the Joint + North Sea Project (JONSWAP). + Ergansungsheft, Reihe A(8), Nr. 12, Deutschen Hydrografischen Zeitschrift. + + Hasselmann et al. (1976) + A parametric wave prediction model. + J. phys. oceanogr. Vol 6, pp 200-228 + + ''' + + # The following formulas are from Lewis and Allos 1990: + zeta = g * fetch / (u10 ** 2) # dimensionless fetch, Table 1 + #zeta = min(zeta, 2.414655013429281e+004) + if method.startswith('h'): + if method[-1] == '3': # Hasselman et.al (1973) + A = 0.076 * zeta ** (-0.22) + # dimensionless peakfrequency, Table 1 + ny = 3.5 * zeta ** (-0.33) + # dimensionless surface variance, Table 1 + epsilon1 = 9.91e-8 * zeta ** 1.1 + else: # Hasselman et.al (1976) + A = 0.0662 * zeta ** (-0.2) + ny = 2.84 * zeta ** (-0.3) # dimensionless peakfrequency, Table 1 + # dimensionless surface variance, Eq.4 + epsilon1 = 1.6e-7 * zeta + + sa = 0.07 + sb = 0.09 + gam = 3.3 + else: + A = 0.074 * zeta ** (-0.22) # Eq. 10 + ny = 3.57 * zeta ** (-0.33) # dimensionless peakfrequency, Eq. 11 + # dimensionless surface variance, Eq.12 + epsilon1 = 3.512e-4 * A * ny ** (-4.) * zeta ** (-0.1) + sa = 0.05468 * ny ** (-0.32) # Eq. 13 + sb = 0.078314 * ny ** (-0.16) # Eq. 14 + gam = maximum(17.54 * zeta ** (-0.28384), 1) # Eq. 15 + + Tp = u10 / (ny * g) # Table 1 + Hm0 = 4 * sqrt(epsilon1) * u10 ** 2. / g # Table 1 + if output[0] == 'l': + return Hm0, Tp, gam, sa, sb, A + else: + return dict(Hm0=Hm0, Tp=Tp, gamma=gam, sigmaA=sa, sigmaB=sb, Ag=A) + + +class Jonswap(ModelSpectrum): + + ''' + Jonswap spectral density model + + Member variables + ---------------- + Hm0 : significant wave height (default 7 (m)) + Tp : peak period (default 11 (sec)) + gamma : peakedness factor determines the concentraton + of the spectrum on the peak frequency. + Usually in the range 1 <= gamma <= 7. + default depending on Hm0, Tp, see jonswap_peakedness) + sigmaA : spectral width parameter for w1: + N : scalar defining decay of high frequency part. (default 5) + M : scalar defining spectral width around the peak. (default 4) + method : String defining method used to estimate Ag when gamma>1 + 'integration': Ag = 1/gaussq(Gf*ggamspec(wn,N,M),0,wnc) (default) + 'parametric' : Ag = (1+f1(N,M)*log(gamma)**f2(N,M))/gamma + 'custom' : Ag = Ag + wnc : wc/wp normalized cut off frequency used when calculating Ag + by integration (default 6) + Parameters + ---------- + w : array-like + angular frequencies [rad/s] + + Description + ----------- + The JONSWAP spectrum is defined as + + S(w) = A * Gf * G0 * wn**(-N)*exp(-N/(M*wn**M)) + where + G0 = Normalizing factor related to Bretschneider form + A = Ag * (Hm0/4)**2 / wp (Normalization factor) + Gf = j**exp(-.5*((wn-1)/s)**2) (Peak enhancement factor) + wn = w/wp + wp = angular peak frequency + s = sigmaA for wn <= 1 + sigmaB for 1 < wn + j = gamma, (j=1, => Bretschneider spectrum) + + The JONSWAP spectrum is assumed to be especially suitable for the North + Sea, and does not represent a fully developed sea. It is a reasonable model + for wind generated sea when the seastate is in the so called JONSWAP range, + i.e., 3.6*sqrt(Hm0) < Tp < 5*sqrt(Hm0) + + The relation between the peak period and mean zero-upcrossing period + may be approximated by + Tz = Tp/(1.30301-0.01698*gamma+0.12102/gamma) + + Examples + --------- + >>> import pylab as plb + >>> import wafo.spectrum.models as wsm + >>> S = wsm.Jonswap(Hm0=7, Tp=11,gamma=1) + >>> w = plb.linspace(0,5) + >>> h = plb.plot(w,S(w)) + + >>> S2 = wsm.Bretschneider(Hm0=7, Tp=11) + >>> all(abs(S(w)-S2(w))<1.e-7) + True + >>> plb.close('all') + + See also + -------- + Bretschneider + Tmaspec + Torsethaugen + + References + ----------- + Torsethaugen et al. (1984) + Characteristica for extreme Sea States on the Norwegian continental shelf. + Report No. STF60 A84123. Norwegian Hydrodyn. Lab., Trondheim + + Hasselmann et al. (1973) + Measurements of Wind-Wave Growth and Swell Decay during the Joint + North Sea Project (JONSWAP). + Ergansungsheft, Reihe A(8), Nr. 12, Deutschen Hydrografischen Zeitschrift. + ''' + + def __init__(self, Hm0=7.0, Tp=11.0, gamma=None, sigmaA=0.07, sigmaB=0.09, + Ag=None, N=5, M=4, method='integration', wnc=6.0, + chk_seastate=True): + + self.type = 'Jonswap' + self.Hm0 = Hm0 + self.Tp = Tp + self.N = N + self.M = M + self.sigmaA = sigmaA + self.sigmaB = sigmaB + self.gamma = gamma + self.Ag = Ag + self.method = method + self.wnc = wnc + + if self.gamma == None or not isfinite(self.gamma) or self.gamma < 1: + self.gamma = jonswap_peakfact(Hm0, Tp) + + self._preCalculateAg() + + if chk_seastate: + self.chk_seastate() + + def _chk_extra_param(self): + Tp = self.Tp + Hm0 = self.Hm0 + gam = self.gamma + outsideJonswapRange = Tp > 5 * sqrt(Hm0) or Tp < 3.6 * sqrt(Hm0) + if outsideJonswapRange: + txt0 = ''' + Hm0=%g,Tp=%g is outside the JONSWAP range. + The validity of the spectral density is questionable. + ''' % (Hm0, Tp) + warnings.warn(txt0) + + if gam < 1 or 7 < gam: + txt = ''' + The peakedness factor, gamma, is possibly too large. + The validity of the spectral density is questionable. + ''' + warnings.warn(txt) + + def _localspec(self, wn): + Gf = self.peak_e_factor(wn) + return Gf * _gengamspec(wn, self.N, self.M) + + def _preCalculateAg(self): + ''' PRECALCULATEAG Precalculate normalization. + ''' + if self.gamma == 1: + self.Ag = 1.0 + self.method = 'parametric' + elif self.Ag != None: + self.method = 'custom' + if self.Ag <= 0: + raise ValueError('Ag must be larger than 0!') + elif self.method[0] == 'i': + # normalizing by integration + self.method = 'integration' + if self.wnc < 1.0: + raise ValueError('Normalized cutoff frequency, wnc, ' + + 'must be larger than one!') + area1, unused_err1 = integrate.quad(self._localspec, 0, 1) + area2, unused_err2 = integrate.quad(self._localspec, 1, self.wnc) + area = area1 + area2 + self.Ag = 1.0 / area + elif self.method[1] == 'p': + self.method = 'parametric' + # Original normalization + # NOTE: that Hm0**2/16 generally is not equal to intS(w)dw + # with this definition of Ag if sa or sb are changed from the + # default values + N = self.N + M = self.M + gammai = self.gamma + parametersOK = (3 <= N and N <= 50) or ( + 2 <= M and M <= 9.5) and (1 <= gammai and gammai <= 20) + if parametersOK: + f1NM = 4.1 * \ + (N - 2 * M ** 0.28 + 5.3) ** (-1.45 * M ** 0.1 + 0.96) + f2NM = (2.2 * M ** (-3.3) + 0.57) * \ + N ** (-0.58 * M ** 0.37 + 0.53) - 1.04 * M ** (-1.9) + 0.94 + self.Ag = (1 + f1NM * log(gammai) ** f2NM) / gammai + + # elseif N == 5 && M == 4, + # options.Ag = (1+1.0*log(gammai).**1.16)/gammai + # options.Ag = (1-0.287*log(gammai)) + ### options.normalizeMethod = 'Three' + # elseif N == 4 && M == 4, + # options.Ag = (1+1.1*log(gammai).**1.19)/gammai + else: + raise ValueError('Not knowing the normalization because N, ' + + 'M or peakedness parameter is out of bounds!') + + if self.sigmaA != 0.07 or self.sigmaB != 0.09: + warnings.warn('Use integration to calculate Ag when ' + + 'sigmaA~=0.07 or sigmaB~=0.09') + + def peak_e_factor(self, wn): + ''' PEAKENHANCEMENTFACTOR + ''' + w = maximum(atleast_1d(wn), 0.0) + sab = where(w > 1, self.sigmaB, self.sigmaA) + + wnm12 = 0.5 * ((w - 1.0) / sab) ** 2.0 + Gf = self.gamma ** (exp(-wnm12)) + return Gf + + def __call__(self, wi): + ''' JONSWAP spectral density + ''' + w = atleast_1d(wi) + if (self.Hm0 > 0.0): + + N = self.N + M = self.M + wp = 2 * pi / self.Tp + wn = w / wp + Ag = self.Ag + Hm0 = self.Hm0 + Gf = self.peak_e_factor(wn) + S = ((Hm0 / 4.0) ** 2 / wp * Ag) * Gf * _gengamspec(wn, N, M) + else: + S = zeros_like(w) + return S + + +def phi1(wi, h, g=9.81): + ''' Factor transforming spectra to finite water depth spectra. + + Input + ----- + w : arraylike + angular frequency [rad/s] + h : scalar + water depth [m] + g : scalar + acceleration of gravity [m/s**2] + Returns + ------- + tr : arraylike + transformation factors + + Example: + ------- + Transform a JONSWAP spectrum to a spectrum for waterdepth = 30 m + >>> import wafo.spectrum.models as wsm + >>> S = wsm.Jonswap() + >>> w = np.arange(3.0) + >>> S(w)*wsm.phi1(w,30.0) + array([ 0. , 1.0358056 , 0.03796281]) + + + Reference + --------- + Buows, E., Gunther, H., Rosenthal, W. and Vincent, C.L. (1985) + 'Similarity of the wind wave spectrum in finite depth water: + 1 spectral form.' + J. Geophys. Res., Vol 90, No. C1, pp 975-986 + + ''' + w = atleast_1d(wi) + if h == inf: # % special case infinite water depth + return ones_like(w) + + k1 = w2k(w, 0, inf, g=g)[0] + dw1 = 2.0 * w / g # % dw/dk|h=inf + k2 = w2k(w, 0, h, g=g)[0] + + k2h = k2 * h + den = where(k1 == 0, 1, (tanh(k2h) + k2h / cosh(k2h) ** 2.0)) + dw2 = where(k1 == 0, 0, dw1 / den) # dw/dk|h=h0 + return where(k1 == 0, 0, (k1 / k2) ** 3.0 * dw2 / dw1) + + +class Tmaspec(Jonswap): + + ''' JONSWAP spectrum for finite water depth + + Member variables + ---------------- + h = water depth (default 42 [m]) + g : acceleration of gravity [m/s**2] + Hm0 = significant wave height (default 7 [m]) + Tp = peak period (default 11 (sec)) + gamma = peakedness factor determines the concentraton + of the spectrum on the peak frequency. + Usually in the range 1 <= gamma <= 7. + default depending on Hm0, Tp, see getjonswappeakedness) + sigmaA = spectral width parameter for w1: + N = scalar defining decay of high frequency part. (default 5) + M = scalar defining spectral width around the peak. (default 4) + method = String defining method used to estimate Ag when gamma>1 + 'integrate' : Ag = 1/gaussq(Gf.*ggamspec(wn,N,M),0,wnc) (default) + 'parametric': Ag = (1+f1(N,M)*log(gamma)^f2(N,M))/gamma + 'custom' : Ag = Ag + wnc = wc/wp normalized cut off frequency used when calculating Ag + by integration (default 6) + Parameters + ---------- + w : array-like + angular frequencies [rad/s] + + Description + ------------ + The evaluated spectrum is + S(w) = Sj(w)*phi(w,h) + where + Sj = jonswap spectrum + phi = modification due to water depth + + The concept is based on a similarity law, and its validity is verified + through analysis of 3 data sets from: TEXEL, MARSEN projects (North + Sea) and ARSLOE project (Duck, North Carolina, USA). The data include + observations at water depths ranging from 6 m to 42 m. + + Example + -------- + >>> import wafo.spectrum.models as wsm + >>> import pylab as plb + >>> w = plb.linspace(0,2.5) + >>> S = wsm.Tmaspec(h=10,gamma=1) # Bretschneider spectrum Hm0=7, Tp=11 + >>> o=plb.plot(w,S(w)) + >>> o=plb.plot(w,S(w,h=21)) + >>> o=plb.plot(w,S(w,h=42)) + >>> plb.show() + >>> plb.close('all') + + See also + --------- + Bretschneider, + Jonswap, + phi1, + Torsethaugen + + References + ---------- + Buows, E., Gunther, H., Rosenthal, W., and Vincent, C.L. (1985) + 'Similarity of the wind wave spectrum in finite depth water: + 1 spectral form.' + J. Geophys. Res., Vol 90, No. C1, pp 975-986 + + Hasselman et al. (1973) + Measurements of Wind-Wave Growth and Swell Decay during the Joint + North Sea Project (JONSWAP). + Ergansungsheft, Reihe A(8), Nr. 12, deutschen Hydrografischen + Zeitschrift. + + ''' + + def __init__(self, Hm0=7.0, Tp=11.0, gamma=None, sigmaA=0.07, sigmaB=0.09, + Ag=None, N=5, M=4, method='integration', wnc=6.0, + chk_seastate=True, h=42, g=9.81): + self.g = g + self.h = h + super(Tmaspec, self).__init__(Hm0, Tp, gamma, sigmaA, sigmaB, Ag, N, + M, method, wnc, chk_seastate) + self.type = 'TMA' + + def phi(self, w, h=None, g=None): + if h == None: + h = self.h + if g == None: + g = self.g + return phi1(w, h, g) + + def __call__(self, w, h=None, g=None): + jonswap = super(Tmaspec, self).__call__(w) + return jonswap * self.phi(w, h, g) + + +class Torsethaugen(ModelSpectrum): + + ''' + Torsethaugen double peaked (swell + wind) spectrum model + + Member variables + ---------------- + Hm0 : significant wave height (default 7 (m)) + Tp : peak period (default 11 (sec)) + wnc : wc/wp normalized cut off frequency used when calculating Ag + by integration (default 6) + method : String defining method used to estimate normalization factors, Ag, + in the the modified JONSWAP spectra when gamma>1 + 'integrate' : Ag = 1/quad(Gf.*gengamspec(wn,N,M),0,wnc) + 'parametric': Ag = (1+f1(N,M)*log(gamma)**f2(N,M))/gamma + Parameters + ---------- + w : array-like + angular frequencies [rad/s] + + Description + ----------- + The double peaked (swell + wind) Torsethaugen spectrum is + modelled as S(w) = Ss(w) + Sw(w) where Ss and Sw are modified + JONSWAP spectrums for swell and wind peak, respectively. + The energy is divided between the two peaks according + to empirical parameters, which peak that is primary depends on parameters. + The empirical parameters are found for classes of Hm0 and Tp, + originating from a dataset consisting of 20 000 spectra divided + into 146 different classes of Hm0 and Tp. (Data measured at the + Statfjord field in the North Sea in a period from 1980 to 1989.) + The range of the measured Hm0 and Tp for the dataset + are from 0.5 to 11 meters and from 3.5 to 19 sec, respectively. + + Preliminary comparisons with spectra from other areas indicate that + some of the empirical parameters are dependent on geographical location. + Thus the model must be used with care for other areas than the + North Sea and sea states outside the area where measured data + are available. + + Example + ------- + >>> import wafo.spectrum.models as wsm + >>> import pylab as plb + >>> w = plb.linspace(0,4) + >>> S = wsm.Torsethaugen(Hm0=6, Tp=8) + >>> h=plb.plot(w,S(w),w,S.wind(w),w,S.swell(w)) + + See also + -------- + Bretschneider + Jonswap + + + References + ---------- + Torsethaugen, K. (2004) + "Simplified Double Peak Spectral Model for Ocean Waves" + In Proc. 14th ISOPE + + Torsethaugen, K. (1996) + Model for a doubly peaked wave spectrum + Report No. STF22 A96204. SINTEF Civil and Environm. Engineering, Trondheim + + Torsethaugen, K. (1994) + 'Model for a doubly peaked spectrum. Lifetime and fatigue strength + estimation implications.' + International Workshop on Floating Structures in Coastal zone, + Hiroshima, November 1994. + + Torsethaugen, K. (1993) + 'A two peak wave spectral model.' + In proceedings OMAE, Glasgow + + ''' + + def __init__(self, Hm0=7, Tp=11, method='integration', wnc=6, gravity=9.81, + chk_seastate=True, **kwds): + self.type = 'Torsethaugen' + self.Hm0 = Hm0 + self.Tp = Tp + self.method = method + self.wnc = wnc + self.gravity = gravity + self.wind = None + self.swell = None + if chk_seastate: + self.chk_seastate() + + self._init_spec() + + def __call__(self, w): + ''' TORSETHAUGEN spectral density + ''' + return self.wind(w) + self.swell(w) + + def _chk_extra_param(self): + Hm0 = self.Hm0 + Tp = self.Tp + if Hm0 > 11 or Hm0 > max((Tp / 3.6) ** 2, (Tp - 2) * 12 / 11): + txt0 = '''Hm0 is outside the valid range. + The validity of the spectral density is questionable''' + warnings.warn(txt0) + + if Tp > 20 or Tp < 3: + txt1 = '''Tp is outside the valid range. + The validity of the spectral density is questionable''' + warnings.warn(txt1) + + def _init_spec(self): + ''' Initialize swell and wind part of Torsethaugen spectrum + ''' + monitor = 0 + Hm0 = self.Hm0 + Tp = self.Tp + gravity1 = self.gravity # m/s**2 + + min = minimum # @ReservedAssignment + max = maximum # @ReservedAssignment + + # The parameter values below are found comparing the + # model to average measured spectra for the Statfjord Field + # in the Northern North Sea. + Af = 6.6 # m**(-1/3)*sec + AL = 2 # sec/sqrt(m) + Au = 25 # sec + KG = 35 + KG0 = 3.5 + KG1 = 1 # m + r = 0.857 # 6/7 + K0 = 0.5 # 1/sqrt(m) + K00 = 3.2 + + M0 = 4 + B1 = 2 # sec + B2 = 0.7 + B3 = 3.0 # m + S0 = 0.08 # m**2*s + S1 = 3 # m + + # Preliminary comparisons with spectra from other areas indicate that + # the parameters on the line below can be dependent on geographical + # location + A10 = 0.7 + A1 = 0.5 + A20 = 0.6 + A2 = 0.3 + A3 = 6 + + Tf = Af * (Hm0) ** (1.0 / 3.0) + Tl = AL * sqrt(Hm0) # lower limit + Tu = Au # upper limit + + # Non-dimensional scales + # New call pab April 2005 + El = min(max((Tf - Tp) / (Tf - Tl), 0), 1) # wind sea + Eu = min(max((Tp - Tf) / (Tu - Tf), 0), 1) # Swell + + if Tp < Tf: # Wind dominated seas + # Primary peak (wind dominated) + Nw = K0 * sqrt(Hm0) + K00 # high frequency exponent + Mw = M0 # spectral width exponent + Rpw = min((1 - A10) * exp(-(El / A1) ** 2) + A10, 1) + Hpw = Rpw * Hm0 # significant waveheight wind + Tpw = Tp # primary peak period + # peak enhancement factor + gammaw = KG * (1 + KG0 * exp(-Hm0 / KG1)) * \ + (2 * pi / gravity1 * Rpw * Hm0 / (Tp ** 2)) ** r + gammaw = max(gammaw, 1) + # Secondary peak (swell) + Ns = Nw # high frequency exponent + Ms = Mw # spectral width exponent + Rps = sqrt(1.0 - Rpw ** 2.0) + Hps = Rps * Hm0 # significant waveheight swell + Tps = Tf + B1 + gammas = 1.0 + + if monitor: + if Rps > 0.1: + print(' Spectrum for Wind dominated sea') + else: + print(' Spectrum for pure wind sea') + else: # swell dominated seas + + # Primary peak (swell) + Ns = K0 * sqrt(Hm0) + K00 # high frequency exponent + Ms = M0 # spectral width exponent + Rps = min((1 - A20) * exp(-(Eu / A2) ** 2) + A20, 1) + Hps = Rps * Hm0 # significant waveheight swell + Tps = Tp # primary peak period + # peak enhancement factor + gammas = KG * (1 + KG0 * exp(-Hm0 / KG1)) * \ + (2 * pi / gravity1 * Hm0 / (Tf ** 2)) ** r * (1 + A3 * Eu) + gammas = max(gammas, 1) + + # Secondary peak (wind) + Nw = Ns # high frequency exponent + Mw = M0 * (1 - B2 * exp(-Hm0 / B3)) # spectral width exponent + Rpw = sqrt(1 - Rps ** 2) + Hpw = Rpw * Hm0 # significant waveheight wind + + C = (Nw - 1) / Mw + B = Nw / Mw + G0w = B ** C * Mw / sp.gamma(C) # normalizing factor + #G0w = exp(C*log(B)+log(Mw)-gammaln(C)) + #G0w = Mw/((B)**(-C)*gamma(C)) + + if Hpw > 0: + Tpw = (16 * S0 * (1 - exp(-Hm0 / S1)) * (0.4) ** + Nw / (G0w * Hpw ** 2)) ** (-1.0 / (Nw - 1.0)) + else: + Tpw = inf + + #Tpw = max(Tpw,2.5) + gammaw = 1 + if monitor: + if Rpw > 0.1: + print(' Spectrum for swell dominated sea') + else: + print(' Spectrum for pure swell sea') + + if monitor: + if (3.6 * sqrt(Hm0) <= Tp & Tp <= 5 * sqrt(Hm0)): + print(' Jonswap range') + + print('Hm0 = %g' % Hm0) + print('Ns, Ms = %g, %g Nw, Mw = %g, %g' % (Ns, Ms, Nw, Mw)) + print('gammas = %g gammaw = ' % (gammas, gammaw)) + print('Rps = %g Rpw = %g' % (Rps, Rpw)) + print('Hps = %g Hpw = %g' % (Hps, Hpw)) + print('Tps = %g Tpw = %g' % (Tps, Tpw)) + + # G0s=Ms/((Ns/Ms)**(-(Ns-1)/Ms)*gamma((Ns-1)/Ms )) #normalizing factor + # Wind part + self.wind = Jonswap(Hm0=Hpw, Tp=Tpw, gamma=gammaw, N=Nw, M=Mw, + method=self.method, chk_seastate=False) + # Swell part + self.swell = Jonswap(Hm0=Hps, Tp=Tps, gamma=gammas, N=Ns, M=Ms, + method=self.method, chk_seastate=False) + + +class McCormick(Bretschneider): + + ''' McCormick spectral density model + + Member variables + ---------------- + Hm0 = significant wave height (default 7 (m)) + Tp = peak period (default 11 (sec)) + Tz = zero-down crossing period (default 0.8143*Tp) + M = scalar defining spectral width around the peak. + (default depending on Tp and Tz) + + Parameters + ---------- + w : array-like + angular frequencies [rad/s] + + Description + ----------- + The McCormick spectrum parameterization is a modification of the + Bretschneider spectrum and defined as + + S(w) = (M+1)*(Hm0/4)^2/wp*(wp./w)^(M+1)*exp(-(M+1)/M*(wp/w)^M) + where + Tp/Tz=(1+1/M)^(1/M)/gamma(1+1/M) + + + Example: + -------- + >>> import wafo.spectrum.models as wsm + >>> S = wsm.McCormick(Hm0=6.5,Tp=10) + >>> S(range(4)) + array([ 0. , 1.87865908, 0.15050447, 0.02994663]) + + + See also + -------- + Bretschneider + Jonswap, + Torsethaugen + + + References: + ----------- + M.E. McCormick (1999) + "Application of the Generic Spectral Formula to Fetch-Limited Seas" + Marine Technology Society, Vol 33, No. 3, pp 27-32 + ''' + + def __init__(self, Hm0=7, Tp=11, Tz=None, M=None, chk_seastate=True): + self.type = 'McCormick' + self.Hm0 = Hm0 + self.Tp = Tp + if Tz == None: + Tz = 0.8143 * Tp + + self.Tz = Tz + if chk_seastate: + self.chk_seastate() + + if M == None and self.Hm0 > 0: + self._TpdTz = Tp / Tz + M = 1.0 / optimize.fminbound(self._localoptfun, 0.01, 5) + self.M = M + self.N = M + 1.0 + + def _localoptfun(self, x): + # LOCALOPTFUN Local function to optimize. + y = 1.0 + x + return (y ** (x) / sp.gamma(y) - self._TpdTz) ** 2.0 + + +class OchiHubble(ModelSpectrum): + + ''' OchiHubble bimodal spectral density model. + + Member variables + ---------------- + + Hm0 : significant wave height (default 7 (m)) + par : integer defining the parametrization (default 0) + 0 : The most probable spectrum + 1,2,...10 : gives 95% Confidence spectra + + The OchiHubble bimodal spectrum is modelled as + S(w) = Ss(w) + Sw(w) where Ss and Sw are modified Bretschneider + spectra for swell and wind peak, respectively. + + The OH spectrum is a six parameter spectrum, all functions of Hm0. + The values of these parameters are determined from a analysis of data + obtained in the North Atlantic. The source of the data is the same as + that for the development of the Pierson-Moskowitz spectrum, but + analysis is carried out on over 800 spectra including those in + partially developed seas and those having a bimodal shape. From a + statistical analysis of the data, a family of wave spectra consisting + of 11 members is generated for a desired sea severity (Hm0) with the + coefficient of 0.95. + A significant advantage of using a family of spectra for design of + marine systems is that one of the family members yields the largest + response such as motions or wave induced forces for a specified sea + severity, while another yields the smallest response with confidence + coefficient of 0.95. + + Examples + -------- + >>> import wafo.spectrum.models as wsm + >>> S = wsm.OchiHubble(par=2) + >>> S(range(4)) + array([ 0. , 0.90155636, 0.04185445, 0.00583207]) + + + See also + -------- + Bretschneider, + Jonswap, + Torsethaugen + + References: + ---------- + Ochi, M.K. and Hubble, E.N. (1976) + 'On six-parameter wave spectra.' + In Proc. 15th Conf. Coastal Engng., Vol.1, pp301-328 + + ''' + + def __init__(self, Hm0=7, par=1, chk_seastate=True): + self.type = 'Ochi Hubble' + self.Hm0 = Hm0 + self.Tp = 1 + self.par = par + self.wind = None + self.swell = None + + if chk_seastate: + self.chk_seastate() + self._init_spec() + + def __call__(self, w): + return self.wind(w) + self.swell(w) + + def _init_spec(self): + + hp = array([[0.84, 0.54], + [0.84, 0.54], + [0.84, 0.54], + [0.84, 0.54], + [0.84, 0.54], + [0.95, 0.31], + [0.65, 0.76], + [0.90, 0.44], + [0.77, 0.64], + [0.73, 0.68], + [0.92, 0.39]]) + wa = array([[0.7, 1.15], + [0.93, 1.5], + [0.41, 0.88], + [0.74, 1.3], + [0.62, 1.03], + [0.70, 1.50], + [0.61, 0.94], + [0.81, 1.60], + [0.54, 0.61], + [0.70, 0.99], + [0.70, 1.37]]) + wb = array([[0.046, 0.039], + [0.056, 0.046], + [0.016, 0.026], + [0.052, 0.039], + [0.039, 0.030], + [0.046, 0.046], + [0.039, 0.036], + [0.052, 0.033], + [0.039, 0.000], + [0.046, 0.039], + [0.046, 0.039]]) + Lpar = array([[3.00, 1.54, -0.062], + [3.00, 2.77, -0.112], + [2.55, 1.82, -0.089], + [2.65, 3.90, -0.085], + [2.60, 0.53, -0.069], + [1.35, 2.48, -0.102], + [4.95, 2.48, -0.102], + [1.80, 2.95, -0.105], + [4.50, 1.95, -0.082], + [6.40, 1.78, -0.069], + [0.70, 1.78, -0.069]]) + Hm0 = self.Hm0 + Lpari = Lpar[self.par] + Li = hstack((Lpari[0], Lpari[1] * exp(Lpari[2] * Hm0))) + + Hm0i = hp[self.par] * Hm0 + Tpi = 2 * pi * exp(wb[self.par] * Hm0) / wa[self.par] + Ni = 4 * Li + 1 + Mi = [4, 4] + + self.swell = Bretschneider(Hm0=Hm0i[0], Tp=Tpi[0], N=Ni[0], M=Mi[0]) + self.wind = Bretschneider(Hm0=Hm0i[1], Tp=Tpi[1], N=Ni[1], M=Mi[1]) + + def _chk_extra_param(self): + if self.par < 0 or 10 < self.par: + raise ValueError('Par must be an integer from 0 to 10!') + + +class Wallop(Bretschneider): + + '''Wallop spectral density model. + + Member variables + ---------------- + Hm0 = significant wave height (default 7 (m)) + Tp = peak period (default 11 (sec)) + N = shape factor, i.e. slope for the high frequency +% part (default depending on Hm0 and Tp, see below) + + Parameters + ---------- + w : array-like + angular frequencies [rad/s] + + Description + ----------- + The WALLOP spectrum parameterization is a modification of the Bretschneider + spectrum and defined as + + S(w) = A * G0 * wn**(-N)*exp(-N/(4*wn**4)) + where + G0 = Normalizing factor related to Bretschneider form + A = (Hm0/4)^2 / wp (Normalization factor) + wn = w/wp + wp = 2*pi/Tp, angular peak frequency + N = abs((log(2*pi^2)+2*log(Hm0/4)-2*log(Lp))/log(2)) + Lp = wave length corresponding to the peak frequency, wp. + + If N=5 it becomes the same as the JONSWAP spectrum with + peak enhancement factor gamma=1 or the Bretschneider + (Pierson-Moskowitz) spectrum. + + Example: + -------- + >>> import wafo.spectrum.models as wsm + >>> S = wsm.Wallop(Hm0=6.5, Tp=10) + >>> S(range(4)) + array([ 0.00000000e+00, 9.36921871e-01, 2.76991078e-03, + 7.72996150e-05]) + + See also + -------- + Bretschneider + Jonswap, + Torsethaugen + + References: + ----------- + Huang, N.E., Long, S.R., Tung, C.C, Yuen, Y. and Bilven, L.F. (1981) + "A unified two parameter wave spectral model for a generous sea state" + J. Fluid Mechanics, Vol.112, pp 203-224 + ''' + + def __init__(self, Hm0=7, Tp=11, N=None, chk_seastate=True): + self.type = 'Wallop' + self.Hm0 = Hm0 + self.Tp = Tp + self.M = 4 + if N is None: + wp = 2. * pi / Tp + kp = w2k(wp, 0, inf)[0] # wavenumber at peak frequency + Lp = 2. * pi / kp # wave length at the peak frequency + N = abs((log(2. * pi ** 2.) + 2 * log(Hm0 / 4) - + 2.0 * log(Lp)) / log(2)) + + self.N = N + + if chk_seastate: + self.chk_seastate() + + +class Spreading(object): + ''' + Directional spreading function. + + Parameters + ---------- + theta, w : arrays + angles and angular frequencies given in radians and rad/s, + respectively. Lenghts are Nt and Nw. + wc : real scalar + cut over frequency + + Returns + ------- + D : 2D array + Directonal spreading function. size Nt X Nw. + The principal direction of D is always along the x-axis. + phi0 : real scalar + Parameter defining the actual principal direction of D. + + Member variables + ---------------- + type : string (default 'cos-2s') + type of spreading function, see options below + 'cos-2s' : N(S)*[cos((theta-theta0)/2)]**(2*S) (0 < S) + 'Box-car' : N(A)*I( -A < theta-theta0 < A) (0 < A < pi) + 'von-Mises' : N(K)*exp(K*cos(theta-theta0)) (0 < K) + 'Poisson' : N(X)/(1-2*X*cos(theta-theta0)+X**2) (0 < X < 1) + 'sech-2' : N(B)*sech(B*(theta-theta0))**2 (0 < B) + 'wrapped-normal': + [1 + 2*sum exp(-(n*D1)^2/2)*cos(n*(theta-theta0))]/(2*pi) (0 < D1) + (N(.) = normalization factor) + (the first letter is enough for unique identification) + + theta0 : callable, matrix or a scalar + defines average direction given in radians at every angular frequency. + (length 1 or length == length(wn)) (default 0) + method : string or integer + Defines function used for direcional spreading parameter: + 0, None : S(wn) = s_a, frequency independent + 1, 'mitsuyasu': S(wn) frequency dependent (default) + where S(wn) = s_a *(wn)**m_a, for wn_lo <= wn < wn_c + = s_b *(wn)**m_b, for wn_c <= wn < wn_up + = 0 otherwise + 2, 'donelan' : B(wn) frequency dependent + 3, 'banner' : B(wn) frequency dependent + where B(wn) = S(wn) for wn_lo <= wn < wn_up + = s_b*wn_up**m_b, for wn_up <= wn and method = 2 + = sc*F(wn) for wn_up <= wn and method = 3 + where F(wn) = 10^(-0.4+0.8393*exp(-0.567*log(wn^2))) and + sc is scalefactor to make the spreading funtion continous. + wn_lo, wn_c, wn_up: real scalars (default 0, 1, inf) + limits used in the function defining the directional spreading + parameter, S() or B() defined above. + wn_c is the normalized cutover frequency + s_a, s_b : real scalars + maximum spread parameters (default [15 15]) + m_a, m_b : real scalars + shape parameters (default [5 -2.5]) + + SPREADING return a Directional spreading function. + Here the S- or B-parameter, of the COS-2S and SECH-2 spreading function, + respectively, is used as a measure of spread. All the parameters of the + other distributions are related to this parameter through the first Fourier + coefficient, R1, of the directional distribution as follows: + R1 = S/(S+1) or S = R1/(1-R1). + where + Box-car spreading : R1 = sin(A)/A + Von Mises spreading: R1 = besseli(1,K)/besseli(0,K), + Poisson spreading : R1 = X + sech-2 spreading : R1 = pi/(2*B*sinh(pi/(2*B)) + Wrapped Normal : R1 = exp(-D1^2/2) + + A value of S = 15 corresponds to + 'box' : A=0.62, 'sech-2' : B=0.89 + 'von-mises' : K=8.3, 'poisson': X=0.94 + 'wrapped-normal': D=0.36 + + The COS2S is the most frequently used spreading in engineering practice. + Apart from the current meter/pressure cell data in WADIC all + instruments seem to support the 'cos2s' distribution for heavier sea + states, (Krogstad and Barstow, 1999). For medium sea states + a spreading function between COS2S and POISSON seem appropriate, + while POISSON seems appropriate for swell. + For the COS2S Mitsuyasu et al. parameterized SPa = SPb = + 11.5*(U10/Cp) where Cp = g/wp is the deep water phase speed at wp and + U10 the wind speed at reference height 10m. Hasselman et al. (1980) + parameterized mb = -2.33-1.45*(U10/Cp-1.17). + Mitsuyasu et al. (1975) showed that SP for wind waves varies from + 5 to 30 being a function of dimensionless wind speed. + However, Goda and Suzuki (1975) proposed SP = 10 for wind waves, SP = 25 + for swell with short decay distance and SP = 75 for long decay distance. + Compared to experiments Krogstad et al. (1998) found that m_a = 5 +/- eps + and that -1< m_b < -3.5. + Values given in the litterature: [s_a s_b m_a m_b wn_lo wn_c wn_up] + (Mitsuyasu: s_a == s_b) (cos-2s) [15 15 5 -2.5 0 1 3 ] + (Hasselman: s_a ~= s_b) (cos-2s) [6.97 9.77 4.06 -2.3 0 1.05 3 ] + (Banner : s_a ~= s_b) (sech2) [2.61 2.28 1.3 -1.3 0.56 0.95 1.6] + + Examples + -------- + >>> import wafo.spectrum.models as wsm + >>> import pylab as plb + >>> D = wsm.Spreading('cos2s',s_a=10.0) + + # Make directionale spectrum + >>> S = wsm.Jonswap().tospecdata() + >>> SD = D.tospecdata2d(S) + >>> h = SD.plot() + + >>> w = plb.linspace(0,3,257) + >>> theta = plb.linspace(-pi,pi,129) + >>> t = plb.contour(D(theta,w)[0].squeeze()) + + # Make frequency dependent direction spreading + >>> theta0 = lambda w: w*plb.pi/6.0 + >>> D2 = wsm.Spreading('cos2s',theta0=theta0) + >>> t = plb.contour(D2(theta,w)[0]) + + # Plot all spreading functions + >>> alltypes = ('cos2s','box','mises','poisson','sech2','wrap_norm') + >>> for ix in range(len(alltypes)): + ... D3 = wsm.Spreading(alltypes[ix]) + ... t = plb.figure(ix) + ... t = plb.contour(D3(theta,w)[0]) + ... t = plb.title(alltypes[ix]) + >>> plb.close('all') + + + See also + -------- + mkdspec, plotspec, spec2spec + + References + --------- + Krogstad, H.E. and Barstow, S.F. (1999) + "Directional Distributions in Ocean Wave Spectra" + Proceedings of the 9th ISOPE Conference, Vol III, pp. 79-86 + + Goda, Y. (1999) + "Numerical simulation of ocean waves for statistical analysis" + Marine Tech. Soc. Journal, Vol. 33, No. 3, pp 5--14 + + Banner, M.L. (1990) + "Equilibrium spectra of wind waves." + J. Phys. Ocean, Vol 20, pp 966--984 + Donelan M.A., Hamilton J, Hui W.H. (1985) + "Directional spectra of wind generated waves." + Phil. Trans. Royal Soc. London, Vol A315, pp 387--407 + + Hasselmann D, Dunckel M, Ewing JA (1980) + "Directional spectra observed during JONSWAP." + J. Phys. Ocean, Vol.10, pp 1264--1280 + + Mitsuyasu, H, et al. (1975) + "Observation of the directional spectrum of ocean waves using a + coverleaf buoy." + J. Physical Oceanography, Vol.5, No.4, pp 750--760 + Some of this might be included in help header: + cos-2s: + NB! The generally strong frequency dependence in directional spread + makes it questionable to run load tests of ships and structures with a + directional spread independent of frequency (Krogstad and Barstow, 1999). + ''' +# Parameterization of B +# def = 2 Donelan et al freq. parametrization for 'sech2' +# def = 3 Banner freq. parametrization for 'sech2' +# (spa ~= spb) (sech-2) [2.61 2.28 1.3 -1.3 0.56 0.95 1.6] +# + + def __init__(self, type='cos-2s', theta0=0, # @ReservedAssignment + method='mitsuyasu', s_a=15., s_b=15., m_a=5., m_b=-2.5, + wn_lo=0.0, wn_c=1., wn_up=inf): + + self.type = type + self.theta0 = theta0 + self.method = method + self.s_a = s_a + self.s_b = s_b + self.m_a = m_a + self.m_b = m_b + self.wn_lo = wn_lo + self.wn_c = wn_c + self.wn_up = wn_up + + methods = dict(n=None, m='mitsuyasu', d='donelan', b='banner') + methodslist = (None, 'mitsuyasu', 'donelan', 'banner') + + if isinstance(self.method, str): + if not self.method[0] in methods: + raise ValueError('Unknown method') + self.method = methods[self.method[0]] + elif self.method == None: + pass + else: + if method < 0 or 3 < method: + method = 2 + self.method = methodslist[method] + + self._spreadfun = dict(c=self.cos2s, b=self.box, m=self.mises, + v=self.mises, + p=self.poisson, s=self.sech2, w=self.wrap_norm) + self._fourierdispatch = dict(b=self.fourier2a, m=self.fourier2k, + v=self.fourier2k, + p=self.fourier2x, s=self.fourier2b, + w=self.fourier2d) + + def __call__(self, theta, w=1, wc=1): + spreadfun = self._spreadfun[self.type[0]] + return spreadfun(theta, w, wc) + + def chk_input(self, theta, w=1, wc=1): # [s_par,TH,phi0,Nt] = + ''' CHK_INPUT + + CALL [s_par,TH,phi0,Nt] = inputchk(theta,w,wc) + ''' + + wn = atleast_1d(w / wc) + theta = theta.ravel() + Nt = len(theta) + + # Make sure theta is from -pi to pi + phi0 = 0.0 + theta = mod(theta + pi, 2 * pi) - pi + + if hasattr(self.theta0, '__call__'): + th0 = self.theta0(wn.flatten()) + else: + th0 = atleast_1d(self.theta0).flatten() + + Nt0 = th0.size + Nw = wn.size + isFreqDepDir = (Nt0 == Nw) + if isFreqDepDir: + # frequency dependent spreading and/or + # frequency dependent direction + # make sure -pi<=TH= 1): + raise ValueError('POISSON spreading: X value must be less than 1') + return X + + def fourier2a(self, r1): + ''' Returns the solution of R1 = sin(A)/A. + ''' + A0 = flipud(linspace(0, pi + 0.1, 1025)) + funA = interp1d(sinc(A0 / pi), A0) + A0 = funA(r1.ravel()) + A = asarray(A0) + + # Newton-Raphson + da = ones_like(r1) + + max_count = 100 + ix = flatnonzero(A) + for unused_iy in range(max_count): + Ai = A[ix] + da[ix] = (sin(Ai) - Ai * r1[ix]) / (cos(Ai) - r1[ix]) + Ai = Ai - da[ix] + # Make sure that the current guess is larger than zero. + A[ix] = Ai + 0.5 * (da[ix] - Ai) * (Ai <= 0.0) + + ix = flatnonzero( + (abs(da) > sqrt(eps) * abs(A)) * (abs(da) > sqrt(eps))) + if ix.size == 0: + if any(A > pi): + raise ValueError( + 'BOX-CAR spreading: The A value must be less than pi') + return A.clip(min=1e-16, max=pi) + + warnings.warn('Newton raphson method did not converge.') + return A.clip(min=1e-16) # Avoid division by zero + + def fourier2k(self, r1): + ''' + Returns the solution of R1 = besseli(1,K)/besseli(0,K), + ''' + K0 = hstack((linspace(0, 10, 513), linspace(10.00001, 100))) + fun0 = lambda x: sp.ive(1, x) / sp.ive(0, x) + funK = interp1d(fun0(K0), K0) + K0 = funK(r1.ravel()) + k1 = flatnonzero(isnan(K0)) + if (k1.size > 0): + K0[k1] = 0.0 + K0[k1] = K0.max() + + ix0 = flatnonzero(r1 != 0.0) + K = zeros_like(r1) + fun = lambda x: fun0(x) - r1[ix] + for ix in ix0: + K[ix] = optimize.fsolve(fun, K0[ix]) + return K + + def fourier2b(self, r1): + ''' Returns the solution of R1 = pi/(2*B*sinh(pi/(2*B)). + ''' + B0 = hstack((linspace(eps, 5, 513), linspace(5.0001, 100))) + funB = interp1d(self._r1ofsech2(B0), B0) + + B0 = funB(r1.ravel()) + k1 = flatnonzero(isnan(B0)) + if (k1.size > 0): + B0[k1] = 0.0 + B0[k1] = max(B0) + + ix0 = flatnonzero(r1 != 0.0) + B = zeros_like(r1) + fun = lambda x: 0.5 * pi / (sinh(.5 * pi / x)) - x * r1[ix] + for ix in ix0: + B[ix] = abs(optimize.fsolve(fun, B0[ix])) + return B + + def fourier2d(self, r1): + ''' Returns the solution of R1 = exp(-D**2/2). + ''' + r = clip(r1, 0., 1.0) + return where(r <= 0, inf, sqrt(-2.0 * log(r))) + + def spread_par_s(self, wn): + ''' Return spread parameter, S, of COS2S function + + Parameters + ---------- + wn : array_like + normalized frequencies. + Returns + ------- + S : ndarray + spread parameter of COS2S functions + ''' + if self.method == None: + # no frequency dependent spreading, + # but possible frequency dependent direction + s = atleast_1d(self.s_a) + else: + wn_lo = self.wn_lo + wn_up = self.wn_up + wn_c = self.wn_c + + spa = self.s_a + spb = self.s_b + ma = self.m_a + mb = self.m_b + + # Mitsuyasu et. al and Hasselman et. al parametrization of + # frequency dependent spreading + s = where(wn <= wn_c, spa * wn ** ma, spb * wn ** mb) + s[wn <= wn_lo] = 0.0 + + k = flatnonzero(wn_up < wn) + if k.size > 0: + if self.method[0] == 'd': + # Donelan et. al. parametrization for B in SECH-2 + s[k] = spb * (wn_up) ** mb + + # Convert to S-paramater in COS-2S distribution + r1 = self.r1ofsech2(s) + s = r1 / (1. - r1) + + elif self.method[0] == 'b': + # Banner parametrization for B in SECH-2 + s3m = spb * (wn_up) ** mb + s3p = self._donelan(wn_up) + # % Scale so that parametrization will be continous + scale = s3m / s3p + s[k] = scale * self.donelan(wn[k]) + r1 = self.r1ofsech2(s) + + #% Convert to S-paramater in COS-2S distribution + s = r1 / (1. - r1) + else: + s[k] = 0.0 + + if any(s < 0): + raise ValueError('The COS2S spread parameter, S(w), ' + + 'value must be larger than 0') + return s + + def _donelan(self, wn): + ''' High frequency decay of B of sech2 paramater + ''' + return 10.0 ** (-0.4 + 0.8393 * exp(-0.567 * log(wn ** 2))) + + def _r1ofsech2(self, B): + ''' R1OFSECH2 Computes R1 = pi./(2*B.*sinh(pi./(2*B))) + ''' + realmax = finfo(float).max + tiny = 1. / realmax + x = clip(2. * B, tiny, realmax) + xk = pi / x + return where(x < 100., xk / sinh(xk), + -2. * xk / (exp(xk) * expm1(-2. * xk))) + + def tospecdata2d(self, specdata=None, theta=None, wc=0.52, nt=51): + ''' + MKDSPEC Make a directional spectrum + frequency spectrum times spreading function + + CALL: Snew=mkdspec(S,D,plotflag) + + Snew = directional spectrum (spectrum struct) + S = frequency spectrum (spectrum struct) + (default jonswap) + D = spreading function (special struct) + (default spreading([],'cos2s')) + plotflag = 1: plot the spectrum, else: do not plot (default 0) + + Creates a directional spectrum through multiplication of a frequency + spectrum and a spreading function: S(w,theta)=S(w)*D(w,theta) + + The spreading structure must contain the following fields: + .S (size [np 1] or [np nf]) and .theta (length np) + optional fields: .w (length nf), .note (memo) .phi (rotation-azymuth) + + NB! S.w and D.w (if any) must be identical. + + Example + ------- + >>> import wafo.spectrum.models as wsm + >>> S = wsm.Jonswap().tospecdata() + >>> D = wsm.Spreading('cos2s') + >>> SD = D.tospecdata2d(S) + >>> h = SD.plot() + + See also spreading, rotspec, jonswap, torsethaugen + ''' + + if specdata is None: + specdata = Jonswap().tospecdata() + if theta is None: + pi = np.pi + theta = np.linspace(-pi, pi, nt) + else: + L = abs(theta[-1] - theta[0]) + if abs(L - pi) > eps: + raise ValueError('theta must cover all angles -pi -> pi') + nt = len(theta) + + if nt < 40: + warnings.warn('Number of angles is less than 40. ' + + 'Spreading too sparsely sampled!') + + w = specdata.args + S = specdata.data + D, phi0 = self(theta, w=w, wc=wc) + if D.ndim != 2: # frequency dependent spreading + D = D[:, None] + SD = D * S[None, :] + + Snew = SpecData2D(SD, (w, theta), type='dir', + freqtype=specdata.freqtype) + Snew.tr = specdata.tr + Snew.h = specdata.h + Snew.phi = phi0 + Snew.norm = specdata.norm + #Snew.note = specdata.note + ', spreading: %s' % self.type + return Snew + + +def _test_some_spectra(): + S = Jonswap() + + w = arange(3.0) + S(w) * phi1(w, 30.0) + S1 = S.tospecdata(w) + S1.plot() + + import pylab as plb + w = plb.linspace(0, 2.5) + S = Tmaspec(h=10, gamma=1) # Bretschneider spectrum Hm0=7, Tp=11 + plb.plot(w, S(w)) + plb.plot(w, S(w, h=21)) + plb.plot(w, S(w, h=42)) + plb.show() + plb.close('all') + + #import pylab as plb + #w = plb.linspace(0,3) + w, th = plb.ogrid[0:4, 0:6] + k, k2 = w2k(w, th) + #k1, k12 = w2k(w, th, h=20) + plb.plot(w, k, w, k2) + + plb.show() + + plb.close('all') + w = plb.linspace(0, 2, 100) + S = Torsethaugen(Hm0=6, Tp=8) + plb.plot(w, S(w), w, S.wind(w), w, S.swell(w)) + + S1 = Jonswap(Hm0=7, Tp=11, gamma=1) + w = plb.linspace(0, 2, 100) + plb.plot(w, S1(w)) + plb.show() + plb.close('all') + + Hm0 = plb.arange(1, 11) + Tp = plb.linspace(2, 16) + T, H = plb.meshgrid(Tp, Hm0) + gam = jonswap_peakfact(H, T) + plb.plot(Tp, gam.T) + plb.xlabel('Tp [s]') + plb.ylabel('Peakedness parameter') + + Hm0 = plb.linspace(1, 20) + Tp = Hm0 + [T, H] = plb.meshgrid(Tp, Hm0) + gam = jonswap_peakfact(H, T) + v = plb.arange(0, 8) + plb.contourf(Tp, Hm0, gam, v) + plb.colorbar() + plb.show() + plb.close('all') + + +def _test_spreading(): + import pylab as plb + pi = plb.pi + w = plb.linspace(0, 3, 257) + theta = plb.linspace(-pi, pi, 129) + theta0 = lambda w: w * plb.pi / 6.0 + D2 = Spreading('cos2s', theta0=theta0) + d1 = D2(theta, w)[0] + _t = plb.contour(d1.squeeze()) + + pi = plb.pi + D = Spreading('wrap_norm', s_a=10.0) + + w = plb.linspace(0, 3, 257) + theta = plb.linspace(-pi, pi, 129) + d1 = D(theta, w) + plb.contour(d1[0]) + plb.show() + + +def test_docstrings(): + import doctest + print('Testing docstrings in %s' % __file__) + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) + + +def main(): + if False: # True: # + _test_some_spectra() + else: + test_docstrings() + +if __name__ == '__main__': + main() diff --git a/pywafo/src/wafo/spectrum/test/test_models.py b/pywafo/src/wafo/spectrum/test/test_models.py index a3ad5d1..a52eb9f 100644 --- a/pywafo/src/wafo/spectrum/test/test_models.py +++ b/pywafo/src/wafo/spectrum/test/test_models.py @@ -1,76 +1,133 @@ +import unittest import numpy as np -from wafo.spectrum.models import (Bretschneider, Jonswap, OchiHubble, Tmaspec, - Torsethaugen, McCormick, Wallop) - - -def test_bretschneider(): - S = Bretschneider(Hm0=6.5, Tp=10) - vals = S((0, 1, 2, 3)) - true_vals = np.array([0., 1.69350993, 0.06352698, 0.00844783]) - assert((np.abs(vals - true_vals) < 1e-7).all()) - - -def test_if_jonswap_with_gamma_one_equals_bretschneider(): - S = Jonswap(Hm0=7, Tp=11, gamma=1) - vals = S((0, 1, 2, 3)) - true_vals = np.array([0., 1.42694133, 0.05051648, 0.00669692]) - assert((np.abs(vals - true_vals) < 1e-7).all()) - - w = np.linspace(0, 5) - S2 = Bretschneider(Hm0=7, Tp=11) - # JONSWAP with gamma=1 should be equal to Bretscneider: - assert(np.all(np.abs(S(w) - S2(w)) < 1.e-7)) - - -def test_tmaspec(): - S = Tmaspec(Hm0=7, Tp=11, gamma=1, h=10) - vals = S((0, 1, 2, 3)) - true_vals = np.array([0., 0.70106233, 0.05022433, 0.00669692]) - assert((np.abs(vals - true_vals) < 1e-7).all()) - - -def test_torsethaugen(): - - S = Torsethaugen(Hm0=7, Tp=11, gamma=1, h=10) - vals = S((0, 1, 2, 3)) - true_vals = np.array([0., 1.19989709, 0.05819794, 0.0093541]) - assert((np.abs(vals - true_vals) < 1e-7).all()) - vals = S.wind(range(4)) - true_vals = np.array([0., 1.13560528, 0.05529849, 0.00888989]) - assert((np.abs(vals - true_vals) < 1e-7).all()) - vals = S.swell(range(4)) - true_vals = np.array([0., 0.0642918, 0.00289946, 0.00046421]) - assert((np.abs(vals - true_vals) < 1e-7).all()) - - -def test_ochihubble(): - - S = OchiHubble(par=2) - vals = S(range(4)) - true_vals = np.array([0., 0.90155636, 0.04185445, 0.00583207]) - assert((np.abs(vals - true_vals) < 1e-7).all()) - - -def test_mccormick(): - - S = McCormick(Hm0=6.5, Tp=10) - vals = S(range(4)) - true_vals = np.array([0., 1.87865908, 0.15050447, 0.02994663]) - assert((np.abs(vals - true_vals) < 1e-7).all()) - - -def test_wallop(): - - S = Wallop(Hm0=6.5, Tp=10) - vals = S(range(4)) - true_vals = np.array([0.00000000e+00, 9.36921871e-01, 2.76991078e-03, - 7.72996150e-05]) - assert((np.abs(vals - true_vals) < 1e-7).all()) +from wafo.spectrum.models import (Bretschneider, Jonswap, OchiHubble, Tmaspec, + Torsethaugen, McCormick, Wallop, Spreading) + + +class TestCase(unittest.TestCase): + def assertListAlmostEqual(self, list1, list2, places=None, msg=None): + self.assertEqual(len(list1), len(list2)) + for a, b in zip(list1, list2): + self.assertAlmostEqual(a, b, places, msg) + + +class TestSpectra(TestCase): + def test_bretschneider(self): + S = Bretschneider(Hm0=6.5, Tp=10) + vals = S((0, 1, 2, 3)).tolist() + true_vals = [0., 1.69350993, 0.06352698, 0.00844783] + self.assertListAlmostEqual(vals, true_vals) + + def test_if_jonswap_with_gamma_one_equals_bretschneider(self): + S = Jonswap(Hm0=7, Tp=11, gamma=1) + vals = S((0, 1, 2, 3)) + true_vals = np.array([0., 1.42694133, 0.05051648, 0.00669692]) + self.assertListAlmostEqual(vals, true_vals) + w = np.linspace(0, 5) + S2 = Bretschneider(Hm0=7, Tp=11) + # JONSWAP with gamma=1 should be equal to Bretscneider: + self.assertListAlmostEqual(S(w), S2(w)) + + def test_tmaspec(self): + S = Tmaspec(Hm0=7, Tp=11, gamma=1, h=10) + vals = S((0, 1, 2, 3)) + true_vals = np.array([0., 0.70106233, 0.05022433, 0.00669692]) + self.assertListAlmostEqual(vals, true_vals) + + def test_torsethaugen(self): + S = Torsethaugen(Hm0=7, Tp=11, gamma=1, h=10) + vals = S((0, 1, 2, 3)) + true_vals = np.array([0., 1.19989709, 0.05819794, 0.0093541]) + self.assertListAlmostEqual(vals, true_vals) + + vals = S.wind(range(4)) + true_vals = np.array([0., 1.13560528, 0.05529849, 0.00888989]) + self.assertListAlmostEqual(vals, true_vals) + + vals = S.swell(range(4)) + true_vals = np.array([0., 0.0642918, 0.00289946, 0.00046421]) + self.assertListAlmostEqual(vals, true_vals) + + def test_ochihubble(self): + + S = OchiHubble(par=2) + vals = S(range(4)) + true_vals = np.array([0., 0.90155636, 0.04185445, 0.00583207]) + self.assertListAlmostEqual(vals, true_vals) + + def test_mccormick(self): + + S = McCormick(Hm0=6.5, Tp=10) + vals = S(range(4)) + true_vals = np.array([0., 1.87865908, 0.15050447, 0.02994663]) + self.assertListAlmostEqual(vals, true_vals) + + def test_wallop(self): + S = Wallop(Hm0=6.5, Tp=10) + vals = S(range(4)) + true_vals = np.array([0.00000000e+00, 9.36921871e-01, 2.76991078e-03, + 7.72996150e-05]) + self.assertListAlmostEqual(vals, true_vals) + + +class TestSpreading(TestCase): + def test_cos2s(self): + theta = np.linspace(0, 2 * np.pi) + d = Spreading(type='cos2s') + dvals = [[1.10168934e+00], + [1.03576796e+00], + [8.60302298e-01], + [6.30309013e-01], + [4.06280137e-01], + [2.29514882e-01], + [1.13052757e-01], + [4.82339343e-02], + [1.76754409e-02], + [5.50490020e-03], + [1.43800617e-03], + [3.09907242e-04], + [5.39672445e-05], + [7.39553743e-06], + [7.70796579e-07], + [5.84247670e-08], + [3.03264905e-09], + [9.91950201e-11], + [1.81442131e-12], + [1.55028269e-14], + [4.63223469e-17], + [2.90526245e-20], + [1.35842977e-24], + [3.26077455e-31], + [1.65021852e-45], + [1.65021852e-45], + [3.26077455e-31], + [1.35842977e-24], + [2.90526245e-20], + [4.63223469e-17], + [1.55028269e-14], + [1.81442131e-12], + [9.91950201e-11], + [3.03264905e-09], + [5.84247670e-08], + [7.70796579e-07], + [7.39553743e-06], + [5.39672445e-05], + [3.09907242e-04], + [1.43800617e-03], + [5.50490020e-03], + [1.76754409e-02], + [4.82339343e-02], + [1.13052757e-01], + [2.29514882e-01], + [4.06280137e-01], + [6.30309013e-01], + [8.60302298e-01], + [1.03576796e+00], + [1.10168934e+00]] + + self.assertListAlmostEqual(d(theta)[0], dvals) if __name__ == '__main__': - # main() - import nose - nose.run() - #test_tmaspec() + unittest.main() diff --git a/pywafo/src/wafo/spectrum/test/test_specdata1d.py b/pywafo/src/wafo/spectrum/test/test_specdata1d.py index c9f7b17..46b043f 100644 --- a/pywafo/src/wafo/spectrum/test/test_specdata1d.py +++ b/pywafo/src/wafo/spectrum/test/test_specdata1d.py @@ -1,7 +1,9 @@ import wafo.spectrum.models as sm +import wafo.transform.models as wtm +import wafo.objects as wo from wafo.spectrum import SpecData1D import numpy as np -import unittest +import unittest def slow(f): @@ -18,11 +20,11 @@ class TestSpectrum(unittest.TestCase): acfmat = S.tocov_matrix(nr=3, nt=256, dt=0.1) vals = acfmat[:2, :] true_vals = np.array([[3.06073383, 0.0000000, -1.67748256, 0.], - [3.05235423, -0.1674357, -1.66811444, 0.18693242]]) + [3.05235423, -0.1674357, -1.66811444, + 0.18693242]]) self.assertTrue((np.abs(vals - true_vals) < 1e-7).all()) - def test_tocovdata(): Sj = sm.Jonswap() S = Sj.tospecdata() @@ -41,22 +43,25 @@ def test_to_t_pdf(): f = S.to_t_pdf(pdef='Tc', paramt=(0, 10, 51), speed=7, seed=100) vals = ['%2.3f' % val for val in f.data[:10]] truevals = ['0.000', '0.014', '0.027', '0.040', - '0.050', '0.059', '0.067', '0.072', '0.077', '0.081'] + '0.050', '0.059', '0.067', '0.073', '0.077', '0.082'] + for t, v in zip(truevals, vals): + assert(t == v) # estimated error bounds vals = ['%2.4f' % val for val in f.err[:10]] truevals = ['0.0000', '0.0003', '0.0003', '0.0004', - '0.0006', '0.0009', '0.0016', '0.0019', '0.0020', '0.0021'] + '0.0006', '0.0008', '0.0016', '0.0019', '0.0020', '0.0021'] + for t, v in zip(truevals, vals): + assert(t == v) @slow def test_sim(): - Sj = sm.Jonswap() S = Sj.tospecdata() - ns = 100 - dt = .2 - x1 = S.sim(ns, dt=dt) + #ns = 100 + #dt = .2 + #x1 = S.sim(ns, dt=dt) import scipy.stats as st x2 = S.sim(20000, 20) @@ -75,13 +80,11 @@ def test_sim_nl(): Sj = sm.Jonswap() S = Sj.tospecdata() - ns = 100 - dt = .2 - x1 = S.sim_nl(ns, dt=dt) - - import numpy as np +# ns = 100 +# dt = .2 +# x1 = S.sim_nl(ns, dt=dt) import scipy.stats as st - x2, x1 = S.sim_nl(ns=20000, cases=40) + x2, _x1 = S.sim_nl(ns=20000, cases=40) truth1 = [0, np.sqrt(S.moment(1)[0][0])] + S.stats_nl(moments='sk') truth1[-1] = truth1[-1] - 3 @@ -110,26 +113,22 @@ def test_stats_nl(): def test_testgaussian(): - ''' - >>> import wafo.spectrum.models as sm - >>> import wafo.transform.models as wtm - >>> import wafo.objects as wo - >>> Hs = 7 - >>> Sj = sm.Jonswap(Hm0=Hs) - >>> S0 = Sj.tospecdata() - >>> ns =100; dt = .2 - >>> x1 = S0.sim(ns, dt=dt) - - >>> S = S0.copy() - >>> me, va, sk, ku = S.stats_nl(moments='mvsk') - >>> S.tr = wtm.TrHermite(mean=me, sigma=Hs/4, skew=sk, kurt=ku, ysigma=Hs/4) - >>> ys = wo.mat2timeseries(S.sim(ns=2**13)) - >>> g0, gemp = ys.trdata() - >>> t0 = g0.dist2gauss() - >>> t1 = S0.testgaussian(ns=2**13, t0=t0, cases=50) - >>> sum(t1>t0)<5 - True - ''' + + Hs = 7 + Sj = sm.Jonswap(Hm0=Hs) + S0 = Sj.tospecdata() + #ns =100; dt = .2 + #x1 = S0.sim(ns, dt=dt) + + S = S0.copy() + me, _va, sk, ku = S.stats_nl(moments='mvsk') + S.tr = wtm.TrHermite( + mean=me, sigma=Hs / 4, skew=sk, kurt=ku, ysigma=Hs / 4) + ys = wo.mat2timeseries(S.sim(ns=2 ** 13)) + g0, _gemp = ys.trdata() + t0 = g0.dist2gauss() + t1 = S0.testgaussian(ns=2 ** 13, t0=t0, cases=50) + assert(sum(t1 > t0) < 5) def test_moment(): @@ -140,29 +139,28 @@ def test_moment(): true_txt = ['m0', 'm0tt'] for tv, v in zip(true_vals, vals): assert(tv == v) + for tv, v in zip(true_txt, txt): + assert(tv == v) def test_nyquist_freq(): - Sj = sm.Jonswap(Hm0=5) S = Sj.tospecdata() # Make spectrum ob assert(S.nyquist_freq() == 3.0) def test_sampling_period(): - Sj = sm.Jonswap(Hm0=5) S = Sj.tospecdata() # Make spectrum ob assert(S.sampling_period() == 1.0471975511965976) def test_normalize(): - Sj = sm.Jonswap(Hm0=5) S = Sj.tospecdata() # Make spectrum ob S.moment(2) ([1.5614600345079888, 0.95567089481941048], ['m0', 'm0tt']) - vals, txt = S.moment(2) + vals, _txt = S.moment(2) true_vals = [1.5614600345079888, 0.95567089481941048] for tv, v in zip(true_vals, vals): assert(tv == v) @@ -171,7 +169,7 @@ def test_normalize(): Sn.normalize() # Now the moments should be one - new_vals, txt = Sn.moment(2) + new_vals, _txt = Sn.moment(2) for v in new_vals: assert(np.abs(v - 1.0) < 1e-7) @@ -191,7 +189,7 @@ def test_characteristic(): [ 0.02834263, 0.0274645 , nan], [ nan, nan, 0.01500249]]) ['Tm01', 'Tm02', 'Tm24'] - + >>> S.characteristic('Ss') # fact a string (array([ 0.04963112]), array([[ 2.63624782e-06]]), ['Ss']) diff --git a/pywafo/src/wafo/stats/__init__.py b/pywafo/src/wafo/stats/__init__.py index 7d4ec85..35ca55f 100644 --- a/pywafo/src/wafo/stats/__init__.py +++ b/pywafo/src/wafo/stats/__init__.py @@ -8,7 +8,7 @@ Statistical functions (:mod:`scipy.stats`) This module contains a large number of probability distributions as well as a growing library of statistical functions. -Each included distribution is an instance of the class rv_continous: +Each included distribution is an instance of the class rv_continuous: For each given name the following methods are available: .. autosummary:: @@ -77,7 +77,7 @@ Continuous distributions exponweib -- Exponentiated Weibull exponpow -- Exponential Power f -- F (Snecdor F) - fatiguelife -- Fatigue Life (Birnbaum-Sanders) + fatiguelife -- Fatigue Life (Birnbaum-Saunders) fisk -- Fisk foldcauchy -- Folded Cauchy foldnorm -- Folded Normal @@ -149,6 +149,7 @@ Multivariate distributions :toctree: generated/ multivariate_normal -- Multivariate normal distribution + dirichlet -- Dirichlet Discrete distributions ====================== @@ -231,6 +232,7 @@ which work for masked arrays. .. autosummary:: :toctree: generated/ + sigmaclip threshold trimboth trim1 @@ -244,6 +246,7 @@ which work for masked arrays. pointbiserialr kendalltau linregress + theilslopes .. autosummary:: :toctree: generated/ @@ -271,8 +274,10 @@ which work for masked arrays. levene shapiro anderson + anderson_ksamp binom_test fligner + median_test mood .. autosummary:: @@ -282,6 +287,8 @@ which work for masked arrays. boxcox_normmax boxcox_llf + entropy + Contingency table functions =========================== @@ -344,3 +351,5 @@ __all__ = [s for s in dir() if not (s.startswith('_') or s.endswith('cython'))] #import distributions #@Reimport #from wafo.stats.distributions import * +from numpy.testing import Tester +test = Tester().test diff --git a/pywafo/src/wafo/stats/_binned_statistic.py b/pywafo/src/wafo/stats/_binned_statistic.py index cae5103..a022ace 100644 --- a/pywafo/src/wafo/stats/_binned_statistic.py +++ b/pywafo/src/wafo/stats/_binned_statistic.py @@ -16,8 +16,6 @@ def binned_statistic(x, values, statistic='mean', each bin. This function allows the computation of the sum, mean, median, or other statistic of the values within each bin. - .. versionadded:: 0.11.0 - Parameters ---------- x : array_like @@ -78,6 +76,8 @@ def binned_statistic(x, values, statistic='mean', second ``[2, 3)``. The last bin, however, is ``[3, 4]``, which *includes* 4. + .. versionadded:: 0.11.0 + Examples -------- >>> stats.binned_statistic([1, 2, 1, 2, 4], np.arange(5), statistic='mean', @@ -116,8 +116,6 @@ def binned_statistic_2d(x, y, values, statistic='mean', each bin. This function allows the computation of the sum, mean, median, or other statistic of the values within each bin. - .. versionadded:: 0.11.0 - Parameters ---------- x : (N,) array_like @@ -175,6 +173,11 @@ def binned_statistic_2d(x, y, values, statistic='mean', -------- numpy.histogram2d, binned_statistic, binned_statistic_dd + Notes + ----- + + .. versionadded:: 0.11.0 + """ # This code is based on np.histogram2d @@ -203,8 +206,6 @@ def binned_statistic_dd(sample, values, statistic='mean', each bin. This function allows the computation of the sum, mean, median, or other statistic of the values within each bin. - .. versionadded:: 0.11.0 - Parameters ---------- sample : array_like @@ -258,6 +259,11 @@ def binned_statistic_dd(sample, values, statistic='mean', -------- np.histogramdd, binned_statistic, binned_statistic_2d + Notes + ----- + + .. versionadded:: 0.11.0 + """ if type(statistic) == str: if statistic not in ['mean', 'median', 'count', 'sum', 'std']: diff --git a/pywafo/src/wafo/stats/_continuous_distns.py b/pywafo/src/wafo/stats/_continuous_distns.py index 79c822f..946f2e1 100644 --- a/pywafo/src/wafo/stats/_continuous_distns.py +++ b/pywafo/src/wafo/stats/_continuous_distns.py @@ -6,23 +6,23 @@ from __future__ import division, print_function, absolute_import import warnings -from scipy.misc import comb # @UnresolvedImport +from scipy.special import comb from scipy.misc.doccer import inherit_docstring_from from scipy import special from scipy import optimize from scipy import integrate -from scipy.special import (gammaln as gamln, gamma as gam, log1p) +from scipy.special import (gammaln as gamln, gamma as gam, boxcox, boxcox1p, log1p, expm1) from numpy import (where, arange, putmask, ravel, sum, shape, log, sqrt, exp, arctanh, tan, sin, arcsin, arctan, - tanh, cos, cosh, sinh, expm1) + tanh, cos, cosh, sinh) from numpy import polyval, place, extract, any, asarray, nan, inf, pi import numpy as np import numpy.random as mtrand try: - from scipy.stats.distributions import vonmises_cython + from scipy.stats import vonmises_cython except: vonmises_cython = None # try: @@ -36,13 +36,12 @@ from ._tukeylambda_stats import (tukeylambda_variance as _tlvar, tukeylambda_kurtosis as _tlkurt) from ._distn_infrastructure import ( - rv_continuous, valarray, - _skew, _kurtosis, _lazywhere, - _ncx2_log_pdf, _ncx2_pdf, _ncx2_cdf, + rv_continuous, valarray, _skew, _kurtosis, _lazywhere, + _ncx2_log_pdf, _ncx2_pdf, _ncx2_cdf, get_distribution_names, ) from ._constants import _XMIN, _EULER, _ZETA3, _EPS - +from .stats import mode #from .estimation import FitDistribution __all__ = [ @@ -94,25 +93,25 @@ class kstwobign_gen(rv_continuous): return special.kolmogorov(x) def _ppf(self, q): - return special.kolmogi(1.0 - q) + return special.kolmogi(1.0-q) kstwobign = kstwobign_gen(a=0.0, name='kstwobign') -# Normal distribution +## Normal distribution # loc = mu, scale = std # Keep these implementations out of the class definition so they can be reused # by other distributions. -_norm_pdf_C = np.sqrt(2 * pi) +_norm_pdf_C = np.sqrt(2*pi) _norm_pdf_logC = np.log(_norm_pdf_C) def _norm_pdf(x): - return exp(-x ** 2 / 2.0) / _norm_pdf_C + return exp(-x**2/2.0) / _norm_pdf_C def _norm_logpdf(x): - return -x ** 2 / 2.0 - _norm_pdf_logC + return -x**2 / 2.0 - _norm_pdf_logC def _norm_cdf(x): @@ -187,7 +186,7 @@ class norm_gen(rv_continuous): return 0.0, 1.0, 0.0, 0.0 def _entropy(self): - return 0.5 * (log(2 * pi) + 1) + return 0.5*(log(2*pi)+1) @inherit_docstring_from(rv_continuous) def fit(self, data, **kwds): @@ -214,7 +213,7 @@ class norm_gen(rv_continuous): loc = floc if fscale is None: - scale = np.sqrt(((data - loc) ** 2).mean()) + scale = np.sqrt(((data - loc)**2).mean()) else: scale = fscale @@ -240,19 +239,19 @@ class alpha_gen(rv_continuous): """ def _pdf(self, x, a): - return 1.0 / (x ** 2) / special.ndtr(a) * _norm_pdf(a - 1.0 / x) + return 1.0/(x**2)/special.ndtr(a)*_norm_pdf(a-1.0/x) def _logpdf(self, x, a): - return -2 * log(x) + _norm_logpdf(a - 1.0 / x) - log(special.ndtr(a)) + return -2*log(x) + _norm_logpdf(a-1.0/x) - log(special.ndtr(a)) def _cdf(self, x, a): - return special.ndtr(a - 1.0 / x) / special.ndtr(a) + return special.ndtr(a-1.0/x) / special.ndtr(a) def _ppf(self, q, a): - return 1.0 / asarray(a - special.ndtri(q * special.ndtr(a))) + return 1.0/asarray(a-special.ndtri(q*special.ndtr(a))) def _stats(self, a): - return [inf] * 2 + [nan] * 2 + return [inf]*2 + [nan]*2 alpha = alpha_gen(a=0.0, name='alpha') @@ -273,21 +272,20 @@ class anglit_gen(rv_continuous): """ def _pdf(self, x): - return cos(2 * x) + return cos(2*x) def _cdf(self, x): - return sin(x + pi / 4) ** 2.0 + return sin(x+pi/4)**2.0 def _ppf(self, q): - return (arcsin(sqrt(q)) - pi / 4) + return (arcsin(sqrt(q))-pi/4) def _stats(self): - return (0.0, pi * pi / 16 - 0.5, 0.0, - -2 * (pi ** 4 - 96) / (pi * pi - 8) ** 2) + return 0.0, pi*pi/16-0.5, 0.0, -2*(pi**4 - 96)/(pi*pi-8)**2 def _entropy(self): - return 1 - log(2) -anglit = anglit_gen(a=-pi / 4, b=pi / 4, name='anglit') + return 1-log(2) +anglit = anglit_gen(a=-pi/4, b=pi/4, name='anglit') class arcsine_gen(rv_continuous): @@ -306,19 +304,19 @@ class arcsine_gen(rv_continuous): """ def _pdf(self, x): - return 1.0 / pi / sqrt(x * (1 - x)) + return 1.0/pi/sqrt(x*(1-x)) def _cdf(self, x): - return 2.0 / pi * arcsin(sqrt(x)) + return 2.0/pi*arcsin(sqrt(x)) def _ppf(self, q): - return sin(pi / 2.0 * q) ** 2.0 + return sin(pi/2.0*q)**2.0 def _stats(self): mu = 0.5 - mu2 = 1.0 / 8 + mu2 = 1.0/8 g1 = 0 - g2 = -3.0 / 2.0 + g2 = -3.0/2.0 return mu, mu2, g1, g2 def _entropy(self): @@ -380,10 +378,12 @@ class beta_gen(rv_continuous): ----- The probability density function for `beta` is:: - beta.pdf(x, a, b) = gamma(a+b)/(gamma(a)*gamma(b)) * x**(a-1) * - (1-x)**(b-1), + gamma(a+b) * x**(a-1) * (1-x)**(b-1) + beta.pdf(x, a, b) = ------------------------------------ + gamma(a)*gamma(b) - for ``0 < x < 1``, ``a > 0``, ``b > 0``. + for ``0 < x < 1``, ``a > 0``, ``b > 0``, where ``gamma(z)`` is the gamma + function (`scipy.special.gamma`). %(example)s @@ -395,7 +395,7 @@ class beta_gen(rv_continuous): return np.exp(self._logpdf(x, a, b)) def _logpdf(self, x, a, b): - lPx = special.xlog1py(b - 1.0, -x) + special.xlogy(a - 1.0, x) + lPx = special.xlog1py(b-1.0, -x) + special.xlogy(a-1.0, x) lPx -= special.betaln(a, b) return lPx @@ -406,13 +406,11 @@ class beta_gen(rv_continuous): return special.btdtri(a, b, q) def _stats(self, a, b): - mn = a * 1.0 / (a + b) - var = (a * b * 1.0) / (a + b + 1.0) / (a + b) ** 2.0 - g1 = 2.0 * (b - a) * sqrt((1.0 + a + b) / (a * b)) / (2 + a + b) - g2 = 6.0 * \ - (a ** 3 + a ** 2 * (1 - 2 * b) + - b ** 2 * (1 + b) - 2 * a * b * (2 + b)) - g2 /= a * b * (a + b + 2) * (a + b + 3) + mn = a*1.0 / (a + b) + var = (a*b*1.0)/(a+b+1.0)/(a+b)**2.0 + g1 = 2.0*(b-a)*sqrt((1.0+a+b)/(a*b)) / (2+a+b) + g2 = 6.0*(a**3 + a**2*(1-2*b) + b**2*(1+b) - 2*a*b*(2+b)) + g2 /= a*b*(a+b+2)*(a+b+3) return mn, var, g1, g2 def _fitstart(self, data): @@ -421,12 +419,11 @@ class beta_gen(rv_continuous): def func(x): a, b = x - sk = 2 * (b - a) * sqrt(a + b + 1) / (a + b + 2) / sqrt(a * b) - ku = a ** 3 - a ** 2 * \ - (2 * b - 1) + b ** 2 * (b + 1) - 2 * a * b * (b + 2) - ku /= a * b * (a + b + 2) * (a + b + 3) + sk = 2*(b-a)*sqrt(a + b + 1) / (a + b + 2) / sqrt(a*b) + ku = a**3 - a**2*(2*b-1) + b**2*(b+1) - 2*a*b*(b+2) + ku /= a*b*(a+b+2)*(a+b+3) ku *= 6 - return [sk - g1, ku - g2] + return [sk-g1, ku-g2] a, b = optimize.fsolve(func, (1.0, 1.0)) return super(beta_gen, self)._fitstart(data, args=(a, b)) @@ -486,7 +483,7 @@ class beta_gen(rv_continuous): a = b * xbar / (1 - xbar) # Compute the MLE for `a` by solving _beta_mle_a. - theta, _info, ier, mesg = optimize.fsolve( + theta, info, ier, mesg = optimize.fsolve( _beta_mle_a, a, args=(b, len(data), np.log(data).sum()), full_output=True @@ -515,7 +512,7 @@ class beta_gen(rv_continuous): b = (1 - xbar) * fac # Compute the MLE for a and b by solving _beta_mle_ab. - theta, _info, ier, mesg = optimize.fsolve( + theta, info, ier, mesg = optimize.fsolve( _beta_mle_ab, [a, b], args=(len(data), s1, s2), full_output=True @@ -555,29 +552,39 @@ class betaprime_gen(rv_continuous): return np.exp(self._logpdf(x, a, b)) def _logpdf(self, x, a, b): - return (special.xlogy(a - 1.0, x) - special.xlog1py(a + b, x) - + return (special.xlogy(a-1.0, x) - special.xlog1py(a+b, x) - special.betaln(a, b)) - def _cdf_skip(self, x, a, b): + def _cdf(self, x, a, b): + return special.betainc(a, b, x/(1.+x)) # remove for now: special.hyp2f1 is incorrect for large a - x = where(x == 1.0, 1.0 - 1e-6, x) - return (pow(x, a) * special.hyp2f1(a + b, a, 1 + a, -x) / a / - special.beta(a, b)) + # x = where(x == 1.0, 1.0-1e-6, x) + # return pow(x, a)*special.hyp2f1(a+b, a, 1+a, -x)/a/special.beta(a, b) + + def _fitstart(self, data): + g1 = np.mean(data) + g2 = mode(data)[0] + + def func(x): + a, b = x + me = a / (b-1) if 1 < b else 1e100 + mo = (a-1)/(b+1) if 1 <= a else 0 + return [me-g1, mo-g2] + a, b = optimize.fsolve(func, (1.0, 1.5)) + return super(betaprime_gen, self)._fitstart(data, args=(a, b)) def _munp(self, n, a, b): if (n == 1.0): - return where(b > 1, a / (b - 1.0), inf) + return where(b > 1, a/(b-1.0), inf) elif (n == 2.0): - return where(b > 2, a * (a + 1.0) / ((b - 2.0) * (b - 1.0)), inf) + return where(b > 2, a*(a+1.0)/((b-2.0)*(b-1.0)), inf) elif (n == 3.0): - return where( - b > 3, a * - (a + 1.0) * (a + 2.0) / ((b - 3.0) * (b - 2.0) * (b - 1.0)), - inf) + return where(b > 3, a*(a+1.0)*(a+2.0)/((b-3.0)*(b-2.0)*(b-1.0)), + inf) elif (n == 4.0): return where(b > 4, - a * (a + 1.0) * (a + 2.0) * (a + 3.0) / - ((b - 4.0) * (b - 3.0) * (b - 2.0) * (b - 1.0)), inf) + a*(a+1.0)*(a+2.0)*(a+3.0)/((b-4.0)*(b-3.0) + * (b-2.0)*(b-1.0)), inf) else: raise NotImplementedError betaprime = betaprime_gen(a=0.0, name='betaprime') @@ -606,7 +613,7 @@ class bradford_gen(rv_continuous): return log1p(c * x) / log1p(c) def _ppf(self, q, c): - return ((1.0 + c) ** q - 1) / c + return ((1.0+c)**q-1)/c def _stats(self, c, moments='mv'): k = log1p(c) @@ -615,15 +622,12 @@ class bradford_gen(rv_continuous): g1 = None g2 = None if 's' in moments: - g1 = sqrt(2) * \ - (12 * c * c - 9 * c * k * (c + 2) - + 2 * k * k * (c * (c + 3) + 3)) - g1 /= sqrt(c * (c * (k - 2) + 2 * k)) * (3 * c * (k - 2) + 6 * k) + g1 = sqrt(2)*(12*c*c-9*c*k*(c+2)+2*k*k*(c*(c+3)+3)) + g1 /= sqrt(c*(c*(k-2)+2*k))*(3*c*(k-2)+6*k) if 'k' in moments: - g2 = (c ** 3 * (k - 3) * (k * (3 * k - 16) + 24) + - 12 * k * c * c * (k - 4) * (k - 3) + - 6 * c * k * k * (3 * k - 14) + 12 * k ** 3) - g2 /= 3 * c * (c * (k - 2) + 2 * k) ** 2 + g2 = (c**3*(k-3)*(k*(3*k-16)+24)+12*k*c*c*(k-4)*(k-3) + + 6*c*k*k*(3*k-14) + 12*k**3) + g2 /= 3*c*(c*(k-2)+2*k)**2 return mu, mu2, g1, g2 def _entropy(self, c): @@ -662,13 +666,13 @@ class burr_gen(rv_continuous): """ def _pdf(self, x, c, d): - return c * d * (x ** (-c - 1.0)) * ((1 + x ** (-c * 1.)) ** (-d - 1.)) + return c*d*(x**(-c-1.0))*((1+x**(-c*1.0))**(-d-1.0)) def _cdf(self, x, c, d): - return (1 + x ** (-c * 1.0)) ** (-d ** 1.0) + return (1+x**(-c*1.0))**(-d**1.0) def _ppf(self, q, c, d): - return (q ** (-1.0 / d) - 1) ** (-1.0 / c) + return (q**(-1.0/d)-1)**(-1.0/c) def _munp(self, n, c, d): nc = 1. * n / c @@ -724,25 +728,25 @@ class cauchy_gen(rv_continuous): """ def _pdf(self, x): - return 1.0 / pi / (1.0 + x * x) + return 1.0/pi/(1.0+x*x) def _cdf(self, x): - return 0.5 + 1.0 / pi * arctan(x) + return 0.5 + 1.0/pi*arctan(x) def _ppf(self, q): - return tan(pi * q - pi / 2.0) + return tan(pi*q-pi/2.0) def _sf(self, x): - return 0.5 - 1.0 / pi * arctan(x) + return 0.5 - 1.0/pi*arctan(x) def _isf(self, q): - return tan(pi / 2.0 - pi * q) + return tan(pi/2.0-pi*q) def _stats(self): return inf, inf, nan, nan def _entropy(self): - return log(4 * pi) + return log(4*pi) def _fitstart(self, data, args=None): return (0, 1) @@ -764,33 +768,31 @@ class chi_gen(rv_continuous): Special cases of `chi` are: - - ``chi(1, loc, scale) = `halfnormal` - - ``chi(2, 0, scale) = `rayleigh` - - ``chi(3, 0, scale) : `maxwell` + - ``chi(1, loc, scale)`` is equivalent to `halfnorm` + - ``chi(2, 0, scale)`` is equivalent to `rayleigh` + - ``chi(3, 0, scale)`` is equivalent to `maxwell` %(example)s """ - def _rvs(self, df): return sqrt(chi2.rvs(df, size=self._size)) def _pdf(self, x, df): - return (x ** (df - 1.) * exp(-x * x * 0.5) / (2.0) ** (df * 0.5 - 1) / - gam(df * 0.5)) + return x**(df-1.)*exp(-x*x*0.5)/(2.0)**(df*0.5-1)/gam(df*0.5) def _cdf(self, x, df): - return special.gammainc(df * 0.5, 0.5 * x * x) + return special.gammainc(df*0.5, 0.5*x*x) def _ppf(self, q, df): - return sqrt(2 * special.gammaincinv(df * 0.5, q)) + return sqrt(2*special.gammaincinv(df*0.5, q)) def _stats(self, df): - mu = sqrt(2) * special.gamma(df / 2.0 + 0.5) / special.gamma(df / 2.0) - mu2 = df - mu * mu - g1 = (2 * mu ** 3.0 + mu * (1 - 2 * df)) / asarray(np.power(mu2, 1.5)) - g2 = 2 * df * (1.0 - df) - 6 * mu ** 4 + 4 * mu ** 2 * (2 * df - 1) - g2 /= asarray(mu2 ** 2.0) + mu = sqrt(2)*special.gamma(df/2.0+0.5)/special.gamma(df/2.0) + mu2 = df - mu*mu + g1 = (2*mu**3.0 + mu*(1-2*df))/asarray(np.power(mu2, 1.5)) + g2 = 2*df*(1.0-df)-6*mu**4 + 4*mu**2 * (2*df-1) + g2 /= asarray(mu2**2.0) return mu, mu2, g1, g2 def _fitstart(self, data): @@ -802,7 +804,7 @@ class chi_gen(rv_continuous): chi = chi_gen(a=0.0, name='chi') -# Chi-squared (gamma-distributed with loc=0 and scale=2 and shape=df/2) +## Chi-squared (gamma-distributed with loc=0 and scale=2 and shape=df/2) class chi2_gen(rv_continuous): """A chi-squared continuous random variable. @@ -824,8 +826,7 @@ class chi2_gen(rv_continuous): return exp(self._logpdf(x, df)) def _logpdf(self, x, df): - return (special.xlogy(df / 2. - 1, x) - x / 2. - gamln(df / 2.) - - (log(2) * df) / 2.) + return special.xlogy(df/2.-1, x) - x/2. - gamln(df/2.) - (log(2)*df)/2. def _cdf(self, x, df): return special.chdtr(df, x) @@ -837,13 +838,13 @@ class chi2_gen(rv_continuous): return special.chdtri(df, p) def _ppf(self, p, df): - return self._isf(1.0 - p, df) + return self._isf(1.0-p, df) def _stats(self, df): mu = df - mu2 = 2 * df - g1 = 2 * sqrt(2.0 / df) - g2 = 12.0 / df + mu2 = 2*df + g1 = 2*sqrt(2.0/df) + g2 = 12.0/df return mu, mu2, g1, g2 def _fitstart(self, data): @@ -873,17 +874,16 @@ class cosine_gen(rv_continuous): """ def _pdf(self, x): - return 1.0 / 2 / pi * (1 + cos(x)) + return 1.0/2/pi*(1+cos(x)) def _cdf(self, x): - return 1.0 / 2 / pi * (pi + x + sin(x)) + return 1.0/2/pi*(pi + x + sin(x)) def _stats(self): - return (0.0, pi * pi / 3.0 - 2.0, 0.0, - -6.0 * (pi ** 4 - 90) / (5.0 * (pi * pi - 6) ** 2)) + return 0.0, pi*pi/3.0-2.0, 0.0, -6.0*(pi**4-90)/(5.0*(pi*pi-6)**2) def _entropy(self): - return log(4 * pi) - 1.0 + return log(4*pi)-1.0 cosine = cosine_gen(a=-pi, b=pi, name='cosine') @@ -905,31 +905,31 @@ class dgamma_gen(rv_continuous): """ def _rvs(self, a): u = mtrand.random_sample(size=self._size) - return (gamma.rvs(a, size=self._size) * where(u >= 0.5, 1, -1)) + return (gamma.rvs(a, size=self._size)*where(u >= 0.5, 1, -1)) def _pdf(self, x, a): ax = abs(x) - return 1.0 / (2 * special.gamma(a)) * ax ** (a - 1.0) * exp(-ax) + return 1.0/(2*special.gamma(a))*ax**(a-1.0) * exp(-ax) def _logpdf(self, x, a): ax = abs(x) - return special.xlogy(a - 1.0, ax) - ax - log(2) - gamln(a) + return special.xlogy(a-1.0, ax) - ax - log(2) - gamln(a) def _cdf(self, x, a): - fac = 0.5 * special.gammainc(a, abs(x)) + fac = 0.5*special.gammainc(a, abs(x)) return where(x > 0, 0.5 + fac, 0.5 - fac) def _sf(self, x, a): - fac = 0.5 * special.gammainc(a, abs(x)) - return where(x > 0, 0.5 - fac, 0.5 + fac) + fac = 0.5*special.gammainc(a, abs(x)) + return where(x > 0, 0.5-fac, 0.5+fac) def _ppf(self, q, a): - fac = special.gammainccinv(a, 1 - abs(2 * q - 1)) + fac = special.gammainccinv(a, 1-abs(2*q-1)) return where(q > 0.5, fac, -fac) def _stats(self, a): - mu2 = a * (a + 1.0) - return 0.0, mu2, 0.0, (a + 2.0) * (a + 3.0) / mu2 - 3.0 + mu2 = a*(a+1.0) + return 0.0, mu2, 0.0, (a+2.0)*(a+3.0)/mu2-3.0 dgamma = dgamma_gen(name='dgamma') @@ -953,15 +953,15 @@ class dweibull_gen(rv_continuous): def _pdf(self, x, c): ax = abs(x) - Px = c / 2.0 * ax ** (c - 1.0) * exp(-ax ** c) + Px = c / 2.0 * ax**(c-1.0) * exp(-ax**c) return Px def _logpdf(self, x, c): ax = abs(x) - return log(c) - log(2.0) + special.xlogy(c - 1.0, ax) - ax ** c + return log(c) - log(2.0) + special.xlogy(c - 1.0, ax) - ax**c def _cdf(self, x, c): - Cx1 = 0.5 * exp(-abs(x) ** c) + Cx1 = 0.5 * exp(-abs(x)**c) return where(x > 0, 1 - Cx1, Cx1) def _ppf(self, q, c): @@ -980,7 +980,7 @@ class dweibull_gen(rv_continuous): dweibull = dweibull_gen(name='dweibull') -# Exponential (gamma distributed with a=1.0, loc=loc and scale=scale) +## Exponential (gamma distributed with a=1.0, loc=loc and scale=scale) class expon_gen(rv_continuous): """An exponential continuous random variable. @@ -1063,9 +1063,11 @@ class exponweib_gen(rv_continuous): return exp(self._logpdf(x, a, c)) def _logpdf(self, x, a, c): - exc = exp(-x ** c) - return (log(a) + log(c) + special.xlog1py(a - 1., -exc) - x ** c + - special.xlogy(c - 1.0, x)) + negxc = -x**c + exm1c = -special.expm1(negxc) + logp = (log(a) + log(c) + special.xlogy(a - 1.0, exm1c) + + negxc + special.xlogy(c - 1.0, x)) + return logp def _cdf(self, x, a, c): exm1c = -expm1(-x ** c) @@ -1099,9 +1101,9 @@ class exponpow_gen(rv_continuous): """ def _pdf(self, x, b): - xbm1 = x ** (b - 1.0) + xbm1 = x**(b-1.0) xb = xbm1 * x - return exp(1) * b * xbm1 * exp(xb - exp(xb)) + return exp(1)*b*xbm1 * exp(xb - exp(xb)) def _logpdf(self, x, b): xb = x ** (b - 1.0) * x @@ -1122,7 +1124,7 @@ exponpow = exponpow_gen(a=0.0, name='exponpow') class fatiguelife_gen(rv_continuous): - """A fatigue-life (Birnbaum-Sanders) continuous random variable. + """A fatigue-life (Birnbaum-Saunders) continuous random variable. %(before_notes)s @@ -1135,29 +1137,34 @@ class fatiguelife_gen(rv_continuous): for ``x > 0``. + References + ---------- + .. [1] "Birnbaum-Saunders distribution", + http://en.wikipedia.org/wiki/Birnbaum-Saunders_distribution + %(example)s """ def _rvs(self, c): z = mtrand.standard_normal(self._size) - x = 0.5 * c * z - x2 = x * x - t = 1.0 + 2 * x2 + 2 * x * sqrt(1 + x2) + x = 0.5*c*z + x2 = x*x + t = 1.0 + 2*x2 + 2*x*sqrt(1 + x2) return t def _pdf(self, x, c): return np.exp(self._logpdf(x, c)) def _logpdf(self, x, c): - return (log(x + 1) - (x - 1) ** 2 / (2.0 * x * c ** 2) - log(2 * c) - - 0.5 * (log(2 * pi) + 3 * log(x))) + return (log(x+1) - (x-1)**2 / (2.0*x*c**2) - log(2*c) - + 0.5*(log(2*pi) + 3*log(x))) def _cdf(self, x, c): - return special.ndtr(1.0 / c * (sqrt(x) - 1.0 / sqrt(x))) + return special.ndtr(1.0 / c * (sqrt(x) - 1.0/sqrt(x))) def _ppf(self, q, c): - tmp = c * special.ndtri(q) - return 0.25 * (tmp + sqrt(tmp ** 2 + 4)) ** 2 + tmp = c*special.ndtri(q) + return 0.25 * (tmp + sqrt(tmp**2 + 4))**2 def _stats(self, c): # NB: the formula for kurtosis in wikipedia seems to have an error: @@ -1165,12 +1172,12 @@ class fatiguelife_gen(rv_continuous): # Alpha. And the latter one, below, passes the tests, while the wiki # one doesn't So far I didn't have the guts to actually check the # coefficients from the expressions for the raw moments. - c2 = c * c + c2 = c*c mu = c2 / 2.0 + 1.0 den = 5.0 * c2 + 4.0 - mu2 = c2 * den / 4.0 - g1 = 4 * c * (11 * c2 + 6.0) / np.power(den, 1.5) - g2 = 6 * c2 * (93 * c2 + 40.0) / den ** 2.0 + mu2 = c2*den / 4.0 + g1 = 4 * c * (11*c2 + 6.0) / np.power(den, 1.5) + g2 = 6 * c2 * (93*c2 + 40.0) / den**2.0 return mu, mu2, g1, g2 fatiguelife = fatiguelife_gen(a=0.0, name='fatiguelife') @@ -1195,10 +1202,10 @@ class foldcauchy_gen(rv_continuous): return abs(cauchy.rvs(loc=c, size=self._size)) def _pdf(self, x, c): - return 1.0 / pi * (1.0 / (1 + (x - c) ** 2) + 1.0 / (1 + (x + c) ** 2)) + return 1.0/pi*(1.0/(1+(x-c)**2) + 1.0/(1+(x+c)**2)) def _cdf(self, x, c): - return 1.0 / pi * (arctan(x - c) + arctan(x + c)) + return 1.0/pi*(arctan(x-c) + arctan(x+c)) def _stats(self, c): return inf, inf, nan, nan @@ -1232,8 +1239,8 @@ class f_gen(rv_continuous): def _logpdf(self, x, dfn, dfd): n = 1.0 * dfn m = 1.0 * dfd - lPx = m / 2 * log(m) + n / 2 * log(n) + (n / 2 - 1) * log(x) - lPx -= ((n + m) / 2) * log(m + n * x) + special.betaln(n / 2, m / 2) + lPx = m/2 * log(m) + n/2 * log(n) + (n/2 - 1) * log(x) + lPx -= ((n+m)/2) * log(m + n*x) + special.betaln(n/2, m/2) return lPx def _cdf(self, x, dfn, dfd): @@ -1257,7 +1264,7 @@ class f_gen(rv_continuous): mu2 = _lazywhere( v2 > 4, (v1, v2, v2_2, v2_4), lambda v1, v2, v2_2, v2_4: - 2 * v2 * v2 * (v1 + v2_2) / (v1 * v2_2 ** 2 * v2_4), + 2 * v2 * v2 * (v1 + v2_2) / (v1 * v2_2**2 * v2_4), np.inf) g1 = _lazywhere( @@ -1318,36 +1325,36 @@ class foldnorm_gen(rv_continuous): return abs(mtrand.standard_normal(self._size) + c) def _pdf(self, x, c): - return _norm_pdf(x + c) + _norm_pdf(x - c) + return _norm_pdf(x + c) + _norm_pdf(x-c) def _cdf(self, x, c): - return special.ndtr(x - c) + special.ndtr(x + c) - 1.0 + return special.ndtr(x-c) + special.ndtr(x+c) - 1.0 def _stats(self, c): # Regina C. Elandt, Technometrics 3, 551 (1961) # http://www.jstor.org/stable/1266561 # - c2 = c * c - expfac = np.exp(-0.5 * c2) / np.sqrt(2. * pi) + c2 = c*c + expfac = np.exp(-0.5*c2) / np.sqrt(2.*pi) - mu = 2. * expfac + c * special.erf(c / sqrt(2)) - mu2 = c2 + 1 - mu * mu + mu = 2.*expfac + c * special.erf(c/sqrt(2)) + mu2 = c2 + 1 - mu*mu - g1 = 2. * (mu * mu * mu - c2 * mu - expfac) + g1 = 2. * (mu*mu*mu - c2*mu - expfac) g1 /= np.power(mu2, 1.5) - g2 = c2 * (c2 + 6.) + 3 + 8. * expfac * mu - g2 += (2. * (c2 - 3.) - 3. * mu ** 2) * mu ** 2 - g2 = g2 / mu2 ** 2.0 - 3. + g2 = c2 * (c2 + 6.) + 3 + 8.*expfac*mu + g2 += (2. * (c2 - 3.) - 3. * mu**2) * mu**2 + g2 = g2 / mu2**2.0 - 3. return mu, mu2, g1, g2 foldnorm = foldnorm_gen(a=0.0, name='foldnorm') -# Extreme Value Type II or Frechet -# (defined in Regress+ documentation as Extreme LB) as -# a limiting value distribution. -# +## Extreme Value Type II or Frechet +## (defined in Regress+ documentation as Extreme LB) as +## a limiting value distribution. +## class frechet_r_gen(rv_continuous): """A Frechet right (or Weibull minimum) continuous random variable. @@ -1382,7 +1389,7 @@ class frechet_r_gen(rv_continuous): return phati def _pdf(self, x, c): - return c * pow(x, c - 1) * exp(-pow(x, c)) + return c*pow(x, c-1)*exp(-pow(x, c)) def _logpdf(self, x, c): return log(c) + special.xlogy(c - 1, x) - pow(x, c) @@ -1394,7 +1401,7 @@ class frechet_r_gen(rv_continuous): return pow(-log1p(-q), 1.0 / c) def _munp(self, n, c): - return special.gamma(1.0 + n * 1.0 / c) + return special.gamma(1.0+n*1.0/c) def _entropy(self, c): return -_EULER / c - log(c) + _EULER + 1 @@ -1431,16 +1438,16 @@ class frechet_l_gen(rv_continuous): """ def _pdf(self, x, c): - return c * pow(-x, c - 1) * exp(-pow(-x, c)) + return c*pow(-x, c-1)*exp(-pow(-x, c)) def _cdf(self, x, c): return exp(-pow(-x, c)) def _ppf(self, q, c): - return -pow(-log(q), 1.0 / c) + return -pow(-log(q), 1.0/c) def _munp(self, n, c): - val = special.gamma(1.0 + n * 1.0 / c) + val = special.gamma(1.0+n*1.0/c) if (int(n) % 2): sgn = -1 else: @@ -1482,21 +1489,21 @@ class genlogistic_gen(rv_continuous): return log(c) - x - (c + 1.0) * log1p(exp(-x)) def _cdf(self, x, c): - Cx = (1 + exp(-x)) ** (-c) + Cx = (1+exp(-x))**(-c) return Cx def _ppf(self, q, c): - vals = -log(pow(q, -1.0 / c) - 1) + vals = -log(pow(q, -1.0/c)-1) return vals def _stats(self, c): zeta = special.zeta mu = _EULER + special.psi(c) - mu2 = pi * pi / 6.0 + zeta(2, c) - g1 = -2 * zeta(3, c) + 2 * _ZETA3 + mu2 = pi*pi/6.0 + zeta(2, c) + g1 = -2*zeta(3, c) + 2*_ZETA3 g1 /= np.power(mu2, 1.5) - g2 = pi ** 4 / 15.0 + 6 * zeta(4, c) - g2 /= mu2 ** 2.0 + g2 = pi**4/15.0 + 6*zeta(4, c) + g2 /= mu2**2.0 return mu, mu2, g1, g2 genlogistic = genlogistic_gen(name='genlogistic') @@ -1518,14 +1525,19 @@ class genpareto_gen(rv_continuous): ----- The probability density function for `genpareto` is:: - genpareto.pdf(x, c) = exp(-x) + genpareto.pdf(x, c) = (1 + c * x)**(-1 - 1/c) - for c==0 + for ``c >= 0`` ``x >= 0``, and + for ``c < 0`` ``0 <= x <= -1/c`` - genpareto.pdf(x, c) = (1 + c * x)**(-1 - 1/c) + For ``c == 0``, `genpareto` reduces to the exponential + distribution, `expon`:: + + genpareto.pdf(x, c=0) = exp(-x) + + For ``c == -1``, `genpareto` is uniform on ``[0, 1]``:: - for ``c != 0``, and for ``x >= 0`` for all c, - and ``x < 1/abs(c)`` for ``c < 0``. + genpareto.cdf(x, c=-1) = x %(example)s @@ -1561,55 +1573,32 @@ class genpareto_gen(rv_continuous): c = asarray(c) self.b = _lazywhere(c < 0, (c,), lambda c: -1. / c, np.inf) - return where(abs(c) == inf, 0, 1) + return where(abs(c) == inf, False, True) def _pdf(self, x, c): return exp(self._logpdf(x, c)) def _logpdf(self, x, c): return _lazywhere((x == x) & (c != 0), (x, c), - lambda x, c: -special.xlog1py(c+1., c*x) / c, - -x) - #return -(c + 1.) * self._log1pcx(x, c) -# x1 = where((c == 0) & (x == inf), 0.0, x) -# cx = where((c == 0) & (x == inf), 0.0, c * x1) -# logpdf = where((cx == inf) | (cx == -1), -# -inf, -(x + cx) * log1pxdx(cx)) -# putmask(logpdf, (c == -1) & (x == 1.0), 0.0) -# return logpdf - # return (-1.0-1.0/c) * np.log1p(c*x) - - def _logsf(self, x, c): - return -self._log1pcx(x, c) - #cx = c * x - # return where((0.0 < x) & (-1.0 <= cx) & (c != 0), -log1p(cx) / c, -x) + lambda x, c: -special.xlog1py(c+1., c*x) / c, + -x) def _cdf(self, x, c): - log_sf = self._logsf(x, c) - return - expm1(log_sf) - # return 1.0 - power(1+c*x,asarray(-1.0/c)) + return - expm1( self._logsf(x, c)) def _sf(self, x, c): - log_sf = self._logsf(x, c) - return exp(log_sf) + return exp(self._logsf(x, c)) + def _logsf(self, x, c): + return _lazywhere((x == x) & (c != 0), (x, c), + lambda x, c: -log1p(c * x) / c, + -x) def _ppf(self, q, c): - log_sf = log1p(-q) - return where((c != 0) & (-inf < log_sf), expm1(-c * log_sf) / c, - -log_sf) + return -boxcox1p(-q, -c) def _isf(self, q, c): - log_sf = log(q) - return where((c != 0) & (-inf < log_sf), expm1(-c * log_sf) / c, - -log_sf) - #vals = 1.0/c * (power(1-q, -c)-1) - # return vals - - def _log1pcx(self, x, c): - ''' log(1+c*x)/c incl c\to 0 limit''' - return _lazywhere((x == x) & (c != 0), (x, c), - lambda x, c: np.log1p(c * x) / c, - x) + return -boxcox(q, -c) + def _fitstart(self, data): d = asarray(data) @@ -1705,14 +1694,8 @@ class genpareto_gen(rv_continuous): munp = lambda c: __munp(n, c) return _lazywhere(c != 0, (c,), munp, gam(n + 1)) - def _munp2(self, n, c): - k = arange(0, n + 1) - val = (-1.0 / c) ** n * \ - sum(comb(n, k) * (-1) ** k / (1.0 - c * k), axis=0) - return where(c * n < 1, val, inf) - def _entropy(self, c): - return 1 + c + return 1. + c genpareto = genpareto_gen(a=0.0, name='genpareto') @@ -1754,7 +1737,7 @@ class genexpon_gen(rv_continuous): raise NotImplementedError('Only implemented for index in [0,1]!') else: raise IndexError('Index to the fixed parameter is out of bounds') - + return phati def _pdf(self, x, a, b, c): @@ -1788,12 +1771,15 @@ class genextreme_gen(rv_continuous): exp(-exp(-x))*exp(-x), for c==0 exp(-(1-c*x)**(1/c))*(1-c*x)**(1/c-1), for x <= 1/c, c > 0 + Note that several sources and software packages use the opposite + convention for the sign of the shape parameter ``c``. + %(example)s """ def _argcheck(self, c): - min = np.minimum # @ReservedAssignment - max = np.maximum # @ReservedAssignment + min = np.minimum + max = np.maximum self.b = where(c > 0, 1.0 / max(c, _XMIN), inf) self.a = where(c < 0, 1.0 / min(c, -_XMIN), -inf) return where(abs(c) == inf, 0, 1) @@ -1807,13 +1793,13 @@ class genextreme_gen(rv_continuous): cond1 = (c == 0) * (x == x) logex2 = where(cond1, 0.0, log1p(-cx)) logpex2 = -x * log1pxdx(-cx) - #logpex2 = where(cond1,-x,logex2/c) + # logpex2 = where(cond1,-x, logex2/c) pex2 = exp(logpex2) # Handle special cases logpdf = where( (cx == 1) | (cx == -inf), -inf, -pex2 + logpex2 - logex2) putmask(logpdf, (c == 1) & (x == 1), 0.0) - return exp(logpdf) + return logpdf def _cdf(self, x, c): return exp(self._logcdf(x, c)) @@ -1830,45 +1816,41 @@ class genextreme_gen(rv_continuous): def _ppf(self, q, c): x = -log(-log(q)) - #return where((c == 0) * (x == x), x, -expm1(-c * x) / c) - return _lazywhere((x==x) & (c != 0), (x, c), - lambda x, c: -expm1(-c*x) / c, x) + return _lazywhere((x == x) & (c != 0), (x, c), + lambda x, c: -expm1(-c * x) / c, x) def _stats(self, c): - g = lambda n: gam(n * c + 1) + g = lambda n: gam(n*c+1) g1 = g(1) g2 = g(2) g3 = g(3) g4 = g(4) - g2mg12 = where(abs(c) < 1e-7, (c * pi) ** 2.0 / 6.0, g2 - g1 ** 2.0) - gam2k = where(abs(c) < 1e-7, pi ** 2.0 / 6.0, - expm1(gamln(2.0 * c + 1.0) - - 2 * gamln(c + 1.0)) / c ** 2.0) + g2mg12 = where(abs(c) < 1e-7, (c*pi)**2.0/6.0, g2-g1**2.0) + gam2k = where(abs(c) < 1e-7, pi**2.0/6.0, + expm1(gamln(2.0*c+1.0)-2*gamln(c+1.0))/c**2.0) eps = 1e-14 gamk = where(abs(c) < eps, -_EULER, expm1(gamln(c + 1)) / c) m = where(c < -1.0, nan, -gamk) - v = where(c < -0.5, nan, g1 ** 2.0 * gam2k) + v = where(c < -0.5, nan, g1**2.0*gam2k) # skewness - sk1 = where(c < -1. / 3, nan, - np.sign(c) * (-g3 + (g2 + 2 * g2mg12) * g1) / - ((g2mg12) ** (3. / 2.))) - sk = where(abs(c) <= eps ** 0.29, 12 * sqrt(6) * _ZETA3 / pi ** 3, sk1) + sk1 = where(c < -1./3, nan, + np.sign(c)*(-g3+(g2+2*g2mg12)*g1)/((g2mg12)**(3./2.))) + sk = where(abs(c) <= eps**0.29, 12*sqrt(6)*_ZETA3/pi**3, sk1) # kurtosis - ku1 = where(c < -1. / 4, nan, - (g4 + (-4 * g3 + 3 * (g2 + g2mg12) * g1) * g1) / - ((g2mg12) ** 2)) - ku = where(abs(c) <= (eps) ** 0.23, 12.0 / 5.0, ku1 - 3.0) + ku1 = where(c < -1./4, nan, + (g4+(-4*g3+3*(g2+g2mg12)*g1)*g1)/((g2mg12)**2)) + ku = where(abs(c) <= (eps)**0.23, 12.0/5.0, ku1-3.0) return m, v, sk, ku def _munp(self, n, c): - k = arange(0, n + 1) - vals = 1.0 / c ** n * sum( - comb(n, k) * (-1) ** k * special.gamma(c * k + 1), + k = arange(0, n+1) + vals = 1.0/c**n * sum( + comb(n, k) * (-1)**k * special.gamma(c*k + 1), axis=0) - return where(c * n > -1, vals, inf) + return where(c*n > -1, vals, inf) def _fitstart(self, data): d = asarray(data) @@ -1914,11 +1896,11 @@ def _digammainv(y): value = optimize.newton(func, x0, tol=1e-10) return value elif y > -3: - x0 = exp(y / 2.332) + 0.08661 + x0 = exp(y/2.332) + 0.08661 else: x0 = 1.0 / (-y - _em) - value, _info, ier, _msg = optimize.fsolve(func, x0, xtol=1e-11, + value, info, ier, mesg = optimize.fsolve(func, x0, xtol=1e-11, full_output=True) if ier != 1: raise RuntimeError("_digammainv: fsolve failed, y = %r" % y) @@ -1926,11 +1908,11 @@ def _digammainv(y): return value[0] -# Gamma (Use MATLAB and MATHEMATICA (b=theta=scale, a=alpha=shape) definition) +## Gamma (Use MATLAB and MATHEMATICA (b=theta=scale, a=alpha=shape) definition) -# gamma(a, loc, scale) with a an integer is the Erlang distribution -# gamma(1, loc, scale) is the Exponential distribution -# gamma(df/2, 0, 2) is the chi2 distribution with df degrees of freedom. +## gamma(a, loc, scale) with a an integer is the Erlang distribution +## gamma(1, loc, scale) is the Exponential distribution +## gamma(df/2, 0, 2) is the chi2 distribution with df degrees of freedom. class gamma_gen(rv_continuous): """A gamma continuous random variable. @@ -1973,7 +1955,7 @@ class gamma_gen(rv_continuous): return exp(self._logpdf(x, a)) def _logpdf(self, x, a): - return special.xlogy(a - 1.0, x) - x - gamln(a) + return special.xlogy(a-1.0, x) - x - gamln(a) def _cdf(self, x, a): return special.gammainc(a, x) @@ -1985,10 +1967,10 @@ class gamma_gen(rv_continuous): return special.gammaincinv(a, q) def _stats(self, a): - return a, a, 2.0 / sqrt(a), 6.0 / a + return a, a, 2.0/sqrt(a), 6.0/a def _entropy(self, a): - return special.psi(a) * (1 - a) + a + gamln(a) + return special.psi(a)*(1-a) + a + gamln(a) def _fitstart(self, data): # The skewness of the gamma distribution is `4 / sqrt(a)`. @@ -1996,7 +1978,7 @@ class gamma_gen(rv_continuous): # of the data. The formula is regularized with 1e-8 in the # denominator to allow for degenerate data where the skewness # is close to 0. - a = 4 / (1e-8 + _skew(data) ** 2) + a = 4 / (1e-8 + _skew(data)**2) return super(gamma_gen, self)._fitstart(data, args=(a,)) @inherit_docstring_from(rv_continuous) @@ -2044,9 +2026,9 @@ class gamma_gen(rv_continuous): # log(a) - special.digamma(a) - log(xbar) + log(data.mean) = 0 s = log(xbar) - log(data).mean() func = lambda a: log(a) - special.digamma(a) - s - aest = (3 - s + np.sqrt((s - 3) ** 2 + 24 * s)) / (12 * s) - xa = aest * (1 - 0.4) - xb = aest * (1 + 0.4) + aest = (3-s + np.sqrt((s-3)**2 + 24*s)) / (12*s) + xa = aest*(1-0.4) + xb = aest*(1+0.4) a = optimize.brentq(func, xa, xb, disp=0) # The MLE for the scale parameter is just the data mean @@ -2101,7 +2083,7 @@ class erlang_gen(gamma_gen): # Override gamma_gen_fitstart so that an integer initial value is # used. (Also regularize the division, to avoid issues when # _skew(data) is 0 or close to 0.) - a = int(4.0 / (1e-8 + _skew(data) ** 2)) + a = int(4.0 / (1e-8 + _skew(data)**2)) return super(gamma_gen, self)._fitstart(data, args=(a,)) # Trivial override of the fit method, so we can monkey-patch its @@ -2150,28 +2132,27 @@ class gengamma_gen(rv_continuous): return log(abs(c)) + special.xlogy(c * a - 1, x) - x ** c - gamln(a) def _cdf(self, x, a, c): - val = special.gammainc(a, x ** c) - cond = c + 0 * val - return where(cond > 0, val, 1 - val) + val = special.gammainc(a, x**c) + cond = c + 0*val + return where(cond > 0, val, 1-val) def _ppf(self, q, a, c): val1 = special.gammaincinv(a, q) - val2 = special.gammaincinv(a, 1.0 - q) - ic = 1.0 / c - cond = c + 0 * val1 - return where(cond > 0, val1 ** ic, val2 ** ic) + val2 = special.gammaincinv(a, 1.0-q) + ic = 1.0/c + cond = c+0*val1 + return where(cond > 0, val1**ic, val2**ic) def _munp(self, n, a, c): - return special.gamma(a + n * 1.0 / c) / special.gamma(a) + return special.gamma(a+n*1.0/c) / special.gamma(a) def _entropy(self, a, c): val = special.psi(a) - return a * (1 - val) + 1.0 / c * val + gamln(a) - log(abs(c)) + return a*(1-val) + 1.0/c*val + gamln(a)-log(abs(c)) gengamma = gengamma_gen(a=0.0, name='gengamma') class genhalflogistic_gen(rv_continuous): - """A generalized half-logistic continuous random variable. %(before_notes)s @@ -2180,36 +2161,35 @@ class genhalflogistic_gen(rv_continuous): ----- The probability density function for `genhalflogistic` is:: - genhalflogistic.pdf(x, c) = 2 * (1-c*x)**(1/c-1) / (1+(1-c*x)**(1/c))**2 + genhalflogistic.pdf(x, c) = 2 * (1-c*x)**(1/c-1) / (1+(1-c*x)**(1/c))**2 for ``0 <= x <= 1/c``, and ``c > 0``. %(example)s """ - def _argcheck(self, c): self.b = 1.0 / c return (c > 0) def _pdf(self, x, c): - limit = 1.0 / c - tmp = asarray(1 - c * x) - tmp0 = tmp ** (limit - 1) - tmp2 = tmp0 * tmp - return 2 * tmp0 / (1 + tmp2) ** 2 + limit = 1.0/c + tmp = asarray(1-c*x) + tmp0 = tmp**(limit-1) + tmp2 = tmp0*tmp + return 2*tmp0 / (1+tmp2)**2 def _cdf(self, x, c): - limit = 1.0 / c - tmp = asarray(1 - c * x) - tmp2 = tmp ** (limit) - return (1.0 - tmp2) / (1 + tmp2) + limit = 1.0/c + tmp = asarray(1-c*x) + tmp2 = tmp**(limit) + return (1.0-tmp2) / (1+tmp2) def _ppf(self, q, c): - return 1.0 / c * (1 - ((1.0 - q) / (1.0 + q)) ** c) + return 1.0/c*(1-((1.0-q)/(1.0+q))**c) def _entropy(self, c): - return 2 - (2 * c + 1) * log(2) + return 2 - (2*c+1)*log(2) genhalflogistic = genhalflogistic_gen(a=0.0, name='genhalflogistic') @@ -2284,7 +2264,7 @@ class gumbel_r_gen(rv_continuous): return -log(-log(q)) def _stats(self): - return _EULER, pi * pi / 6.0, 12 * sqrt(6) / pi ** 3 * _ZETA3, 12.0 / 5 + return _EULER, pi*pi/6.0, 12*sqrt(6)/pi**3 * _ZETA3, 12.0/5 def _entropy(self): # http://en.wikipedia.org/wiki/Gumbel_distribution @@ -2327,8 +2307,8 @@ class gumbel_l_gen(rv_continuous): return log(-log1p(-q)) def _stats(self): - return -_EULER, pi * pi / 6.0, \ - -12 * sqrt(6) / pi ** 3 * _ZETA3, 12.0 / 5 + return -_EULER, pi*pi/6.0, \ + -12*sqrt(6)/pi**3 * _ZETA3, 12.0/5 def _entropy(self): return _EULER + 1. @@ -2352,22 +2332,22 @@ class halfcauchy_gen(rv_continuous): """ def _pdf(self, x): - return 2.0 / pi / (1.0 + x * x) + return 2.0/pi/(1.0+x*x) def _logpdf(self, x): - return np.log(2.0 / pi) - np.log1p(x * x) + return np.log(2.0/pi) - special.log1p(x*x) def _cdf(self, x): - return 2.0 / pi * arctan(x) + return 2.0/pi*arctan(x) def _ppf(self, q): - return tan(pi / 2 * q) + return tan(pi/2*q) def _stats(self): return inf, inf, nan, nan def _entropy(self): - return log(2 * pi) + return log(2*pi) halfcauchy = halfcauchy_gen(a=0.0, name='halfcauchy') @@ -2394,25 +2374,24 @@ class halflogistic_gen(rv_continuous): return log(2) - x - 2. * special.log1p(exp(-x)) def _cdf(self, x): - return tanh(x / 2.0) + return tanh(x/2.0) def _ppf(self, q): - return 2 * arctanh(q) + return 2*arctanh(q) def _munp(self, n): if n == 1: - return 2 * log(2) + return 2*log(2) if n == 2: - return pi * pi / 3.0 + return pi*pi/3.0 if n == 3: - return 9 * _ZETA3 + return 9*_ZETA3 if n == 4: - return 7 * pi ** 4 / 15.0 - return (2 * (1 - pow(2.0, 1 - n)) * special.gamma(n + 1) * - special.zeta(n, 1)) + return 7*pi**4 / 15.0 + return 2*(1-pow(2.0, 1-n))*special.gamma(n+1)*special.zeta(n, 1) def _entropy(self): - return 2 - log(2) + return 2-log(2) halflogistic = halflogistic_gen(a=0.0, name='halflogistic') @@ -2438,24 +2417,23 @@ class halfnorm_gen(rv_continuous): return abs(mtrand.standard_normal(size=self._size)) def _pdf(self, x): - return sqrt(2.0 / pi) * exp(-x * x / 2.0) + return sqrt(2.0/pi)*exp(-x*x/2.0) def _logpdf(self, x): - return 0.5 * np.log(2.0 / pi) - x * x / 2.0 + return 0.5 * np.log(2.0/pi) - x*x/2.0 def _cdf(self, x): - return special.ndtr(x) * 2 - 1.0 + return special.ndtr(x)*2-1.0 def _ppf(self, q): - return special.ndtri((1 + q) / 2.0) + return special.ndtri((1+q)/2.0) def _stats(self): - return ( - sqrt(2.0 / pi), 1 - 2.0 / pi, sqrt(2) * (4 - pi) / (pi - 2) ** 1.5, - 8 * (pi - 3) / (pi - 2) ** 2) + return (sqrt(2.0/pi), 1-2.0/pi, sqrt(2)*(4-pi)/(pi-2)**1.5, + 8*(pi-3)/(pi-2)**2) def _entropy(self): - return 0.5 * log(pi / 2.0) + 0.5 + return 0.5*log(pi/2.0)+0.5 halfnorm = halfnorm_gen(a=0.0, name='halfnorm') @@ -2474,19 +2452,19 @@ class hypsecant_gen(rv_continuous): """ def _pdf(self, x): - return 1.0 / (pi * cosh(x)) + return 1.0/(pi*cosh(x)) def _cdf(self, x): - return 2.0 / pi * arctan(exp(x)) + return 2.0/pi*arctan(exp(x)) def _ppf(self, q): - return log(tan(pi * q / 2.0)) + return log(tan(pi*q/2.0)) def _stats(self): - return 0, pi * pi / 4, 0, 2 + return 0, pi*pi/4, 0, 2 def _entropy(self): - return log(2 * pi) + return log(2*pi) hypsecant = hypsecant_gen(name='hypsecant') @@ -2512,15 +2490,14 @@ class gausshyper_gen(rv_continuous): return (a > 0) & (b > 0) & (c == c) & (z == z) def _pdf(self, x, a, b, c, z): - Cinv = gam(a) * gam(b) / gam(a + b) * special.hyp2f1(c, a, a + b, -z) - return (1.0 / Cinv * x ** (a - 1.0) * (1.0 - x) ** (b - 1.0) / - (1.0 + z * x) ** c) + Cinv = gam(a)*gam(b)/gam(a+b)*special.hyp2f1(c, a, a+b, -z) + return 1.0/Cinv * x**(a-1.0) * (1.0-x)**(b-1.0) / (1.0+z*x)**c def _munp(self, n, a, b, c, z): - fac = special.beta(n + a, b) / special.beta(a, b) - num = special.hyp2f1(c, a + n, a + b + n, -z) - den = special.hyp2f1(c, a, a + b, -z) - return fac * num / den + fac = special.beta(n+a, b) / special.beta(a, b) + num = special.hyp2f1(c, a+n, a+b+n, -z) + den = special.hyp2f1(c, a, a+b, -z) + return fac*num / den gausshyper = gausshyper_gen(a=0.0, b=1.0, name='gausshyper') @@ -2546,17 +2523,17 @@ class invgamma_gen(rv_continuous): return exp(self._logpdf(x, a)) def _logpdf(self, x, a): - return (-(a + 1) * log(x) - gamln(a) - 1.0 / x) + return (-(a+1) * log(x) - gamln(a) - 1.0/x) def _cdf(self, x, a): - return 1.0 - special.gammainc(a, 1.0 / x) + return 1.0 - special.gammainc(a, 1.0/x) def _ppf(self, q, a): - return 1.0 / special.gammaincinv(a, 1. - q) + return 1.0 / special.gammaincinv(a, 1.-q) def _stats(self, a, moments='mvsk'): m1 = _lazywhere(a > 1, (a,), lambda x: 1. / (x - 1.), np.inf) - m2 = _lazywhere(a > 2, (a,), lambda x: 1. / (x - 1.) ** 2 / (x - 2.), + m2 = _lazywhere(a > 2, (a,), lambda x: 1. / (x - 1.)**2 / (x - 2.), np.inf) g1, g2 = None, None @@ -2571,7 +2548,7 @@ class invgamma_gen(rv_continuous): return m1, m2, g1, g2 def _entropy(self, a): - return a - (a + 1.0) * special.psi(a) + gamln(a) + return a - (a+1.0) * special.psi(a) + gamln(a) invgamma = invgamma_gen(a=0.0, name='invgamma') @@ -2600,22 +2577,20 @@ class invgauss_gen(rv_continuous): return mtrand.wald(mu, 1.0, size=self._size) def _pdf(self, x, mu): - return (1.0 / sqrt(2 * pi * x ** 3.0) * - exp(-1.0 / (2 * x) * ((x - mu) / mu) ** 2)) + return 1.0/sqrt(2*pi*x**3.0)*exp(-1.0/(2*x)*((x-mu)/mu)**2) def _logpdf(self, x, mu): - return (-0.5 * log(2 * pi) - 1.5 * log(x) - - ((x - mu) / mu) ** 2 / (2 * x)) + return -0.5*log(2*pi) - 1.5*log(x) - ((x-mu)/mu)**2/(2*x) def _cdf(self, x, mu): - fac = sqrt(1.0 / x) + fac = sqrt(1.0/x) # Numerical accuracy for small `mu` is bad. See #869. - C1 = _norm_cdf(fac * (x - mu) / mu) - C1 += exp(1.0 / mu) * _norm_cdf(-fac * (x + mu) / mu) * exp(1.0 / mu) + C1 = _norm_cdf(fac*(x-mu)/mu) + C1 += exp(1.0/mu) * _norm_cdf(-fac*(x+mu)/mu) * exp(1.0/mu) return C1 def _stats(self, mu): - return mu, mu ** 3.0, 3 * sqrt(mu), 15 * mu + return mu, mu**3.0, 3*sqrt(mu), 15*mu invgauss = invgauss_gen(a=0.0, name='invgauss') @@ -2651,13 +2626,13 @@ class invweibull_gen(rv_continuous): return exp(-xc1) def _ppf(self, q, c): - return np.power(-log(q), -1.0 / c) + return np.power(-log(q), -1.0/c) def _munp(self, n, c): return special.gamma(1 - n / c) def _entropy(self, c): - return 1 + _EULER + _EULER / c - log(c) + return 1+_EULER + _EULER / c - log(c) invweibull = invweibull_gen(a=0, name='invweibull') @@ -2685,15 +2660,15 @@ class johnsonsb_gen(rv_continuous): return (b > 0) & (a == a) def _pdf(self, x, a, b): - trm = _norm_pdf(a + b * log(x / (1.0 - x))) - return b * 1.0 / (x * (1 - x)) * trm + trm = _norm_pdf(a + b*log(x/(1.0-x))) + return b*1.0/(x*(1-x))*trm def _cdf(self, x, a, b): - return _norm_cdf(a + b * log(x / (1.0 - x))) + return _norm_cdf(a + b*log(x/(1.0-x))) def _ppf(self, q, a, b): return 1.0 / (1 + exp(-1.0 / b * (_norm_ppf(q) - a))) -johnsonsb = johnsonsb_gen(a=0.0, b=1.0, name='johnsonb') +johnsonsb = johnsonsb_gen(a=0.0, b=1.0, name='johnsonsb') class johnsonsu_gen(rv_continuous): @@ -2721,12 +2696,12 @@ class johnsonsu_gen(rv_continuous): return (b > 0) & (a == a) def _pdf(self, x, a, b): - x2 = x * x - trm = _norm_pdf(a + b * log(x + sqrt(x2 + 1))) - return b * 1.0 / sqrt(x2 + 1.0) * trm + x2 = x*x + trm = _norm_pdf(a + b * log(x + sqrt(x2+1))) + return b*1.0/sqrt(x2+1.0)*trm def _cdf(self, x, a, b): - return _norm_cdf(a + b * log(x + sqrt(x * x + 1))) + return _norm_cdf(a + b * log(x + sqrt(x*x + 1))) def _ppf(self, q, a, b): return sinh((_norm_ppf(q) - a) / b) @@ -2751,10 +2726,10 @@ class laplace_gen(rv_continuous): return mtrand.laplace(0, 1, size=self._size) def _pdf(self, x): - return 0.5 * exp(-abs(x)) + return 0.5*exp(-abs(x)) def _cdf(self, x): - return where(x > 0, 1.0 - 0.5 * exp(-x), 0.5 * exp(x)) + return where(x > 0, 1.0-0.5*exp(-x), 0.5*exp(x)) def _ppf(self, q): return where(q > 0.5, -log(2) - log1p(-q), log(2 * q)) @@ -2763,7 +2738,7 @@ class laplace_gen(rv_continuous): return 0, 2, 0, 3 def _entropy(self): - return log(2) + 1 + return log(2)+1 laplace = laplace_gen(name='laplace') @@ -2789,15 +2764,16 @@ class levy_gen(rv_continuous): %(example)s """ - def _pdf(self, x): return 1 / sqrt(2*pi*x) / x * exp(-1/(2*x)) def _cdf(self, x): - return 2 * (1 - _norm_cdf(1 / sqrt(x))) + # Equivalent to 2*norm.sf(sqrt(1/x)) + return special.erfc(sqrt(0.5 / x)) def _ppf(self, q): - val = _norm_ppf(1 - q / 2.0) + # Equivalent to 1.0/(norm.isf(q/2)**2) or 0.5/(erfcinv(q)**2) + val = -special.ndtri(q/2) return 1.0 / (val * val) def _stats(self): @@ -2829,7 +2805,7 @@ class levy_l_gen(rv_continuous): """ def _pdf(self, x): ax = abs(x) - return 1 / sqrt(2 * pi * ax) / ax * exp(-1 / (2 * ax)) + return 1/sqrt(2*pi*ax)/ax*exp(-1/(2*ax)) def _cdf(self, x): ax = abs(x) @@ -2863,24 +2839,20 @@ class levy_stable_gen(rv_continuous): """ def _rvs(self, alpha, beta): sz = self._size - TH = uniform.rvs(loc=-pi / 2.0, scale=pi, size=sz) + TH = uniform.rvs(loc=-pi/2.0, scale=pi, size=sz) W = expon.rvs(size=sz) if alpha == 1: - return (2. / pi * (pi / 2 + beta * TH) * tan(TH) - - beta * log((pi / 2 * W * cos(TH)) / (pi / 2 + beta * TH))) + return 2/pi*(pi/2+beta*TH)*tan(TH)-beta*log((pi/2*W*cos(TH))/(pi/2+beta*TH)) - ialpha = 1.0 / alpha - aTH = alpha * TH + ialpha = 1.0/alpha + aTH = alpha*TH if beta == 0: - return (W / (cos(TH) / tan(aTH) + sin(TH)) * - ((cos(aTH) + sin(aTH) * tan(TH)) / W) ** ialpha) - - val0 = beta * tan(pi * alpha / 2) - th0 = arctan(val0) / alpha - val3 = W / (cos(TH) / tan(alpha * (th0 + TH)) + sin(TH)) - res3 = val3 * \ - ((cos(aTH) + sin(aTH) * tan(TH) - val0 * - (sin(aTH) - cos(aTH) * tan(TH))) / W) ** ialpha + return W/(cos(TH)/tan(aTH)+sin(TH))*((cos(aTH)+sin(aTH)*tan(TH))/W)**ialpha + + val0 = beta*tan(pi*alpha/2) + th0 = arctan(val0)/alpha + val3 = W/(cos(TH)/tan(alpha*(th0+TH))+sin(TH)) + res3 = val3*((cos(aTH)+sin(aTH)*tan(TH)-val0*(sin(aTH)-cos(aTH)*tan(TH)))/W)**ialpha return res3 def _argcheck(self, alpha, beta): @@ -2924,10 +2896,10 @@ class logistic_gen(rv_continuous): return special.expit(x) def _ppf(self, q): - return -log(1.0 / q - 1) + return -log1p(-q) + log(q) def _stats(self): - return 0, pi * pi / 3.0, 0, 6.0 / 5.0 + return 0, pi*pi/3.0, 0, 6.0/5.0 def _entropy(self): # http://en.wikipedia.org/wiki/Logistic_distribution @@ -2955,7 +2927,7 @@ class loggamma_gen(rv_continuous): return log(mtrand.gamma(c, size=self._size)) def _pdf(self, x, c): - return exp(c * x - exp(x) - gamln(c)) + return exp(c*x-exp(x)-gamln(c)) def _cdf(self, x, c): return special.gammainc(c, exp(x)) @@ -2969,7 +2941,7 @@ class loggamma_gen(rv_continuous): mean = special.digamma(c) var = special.polygamma(1, c) skewness = special.polygamma(2, c) / np.power(var, 1.5) - excess_kurtosis = special.polygamma(3, c) / (var * var) + excess_kurtosis = special.polygamma(3, c) / (var*var) return mean, var, skewness, excess_kurtosis loggamma = loggamma_gen(name='loggamma') @@ -2998,28 +2970,26 @@ class loglaplace_gen(rv_continuous): """ def _pdf(self, x, c): - cd2 = c / 2.0 + cd2 = c/2.0 c = where(x < 1, c, -c) - return cd2 * x ** (c - 1) + return cd2*x**(c-1) def _cdf(self, x, c): - return where(x < 1, 0.5 * x ** c, 1 - 0.5 * x ** (-c)) + return where(x < 1, 0.5*x**c, 1-0.5*x**(-c)) def _ppf(self, q, c): - return where(q < 0.5, (2.0 * q) ** (1.0 / c), - (2 * (1.0 - q)) ** (-1.0 / c)) + return where(q < 0.5, (2.0*q)**(1.0/c), (2*(1.0-q))**(-1.0/c)) def _munp(self, n, c): - return c ** 2 / (c ** 2 - n ** 2) + return c**2 / (c**2 - n**2) def _entropy(self, c): - return log(2.0 / c) + 1.0 + return log(2.0/c) + 1.0 loglaplace = loglaplace_gen(a=0.0, name='loglaplace') def _lognorm_logpdf(x, s): - return (-log(x) ** 2 / (2 * s ** 2) + np.where(x == 0, 0, - -log(s * x * sqrt(2 * pi)))) + return -log(x)**2 / (2*s**2) + np.where(x == 0, 0, -log(s*x*sqrt(2*pi))) class lognorm_gen(rv_continuous): @@ -3058,15 +3028,15 @@ class lognorm_gen(rv_continuous): return exp(s * _norm_ppf(q)) def _stats(self, s): - p = exp(s * s) + p = exp(s*s) mu = sqrt(p) - mu2 = p * (p - 1) - g1 = sqrt((p - 1)) * (2 + p) + mu2 = p*(p-1) + g1 = sqrt((p-1))*(2+p) g2 = np.polyval([1, 2, 3, 0, -6.0], p) return mu, mu2, g1, g2 def _entropy(self, s): - return 0.5 * (1 + log(2 * pi) + 2 * log(s)) + return 0.5 * (1 + log(2*pi) + 2 * log(s)) def _fitstart(self, data): scale = data.std() @@ -3154,23 +3124,21 @@ class maxwell_gen(rv_continuous): return chi.rvs(3.0, size=self._size) def _pdf(self, x): - return sqrt(2.0 / pi) * x * x * exp(-x * x / 2.0) + return sqrt(2.0/pi)*x*x*exp(-x*x/2.0) def _cdf(self, x): - return special.gammainc(1.5, x * x / 2.0) + return special.gammainc(1.5, x*x/2.0) def _ppf(self, q): - return sqrt(2 * special.gammaincinv(1.5, q)) + return sqrt(2*special.gammaincinv(1.5, q)) def _stats(self): - val = 3 * pi - 8 - return ( - 2 * sqrt(2.0 / pi), 3 - 8 / pi, sqrt( - 2) * (32 - 10 * pi) / val ** 1.5, - (-12 * pi * pi + 160 * pi - 384) / val ** 2.0) + val = 3*pi-8 + return (2*sqrt(2.0/pi), 3-8/pi, sqrt(2)*(32-10*pi)/val**1.5, + (-12*pi*pi + 160*pi - 384) / val**2.0) def _entropy(self): - return _EULER + 0.5 * log(2 * pi) - 0.5 + return _EULER + 0.5*log(2*pi)-0.5 maxwell = maxwell_gen(a=0.0, name='maxwell') @@ -3191,14 +3159,14 @@ class mielke_gen(rv_continuous): """ def _pdf(self, x, k, s): - return k * x ** (k - 1.0) / (1.0 + x ** s) ** (1.0 + k * 1.0 / s) + return k*x**(k-1.0) / (1.0+x**s)**(1.0+k*1.0/s) def _cdf(self, x, k, s): - return x ** k / (1.0 + x ** s) ** (k * 1.0 / s) + return x**k / (1.0+x**s)**(k*1.0/s) def _ppf(self, q, k, s): - qsk = pow(q, s * 1.0 / k) - return pow(qsk / (1.0 - qsk), 1.0 / s) + qsk = pow(q, s*1.0/k) + return pow(qsk/(1.0-qsk), 1.0/s) mielke = mielke_gen(a=0.0, name='mielke') @@ -3220,21 +3188,20 @@ class nakagami_gen(rv_continuous): """ def _pdf(self, x, nu): - return (2 * nu ** nu / gam(nu) * (x ** (2 * nu - 1.0)) * - exp(-nu * x * x)) + return 2*nu**nu/gam(nu)*(x**(2*nu-1.0))*exp(-nu*x*x) def _cdf(self, x, nu): - return special.gammainc(nu, nu * x * x) + return special.gammainc(nu, nu*x*x) def _ppf(self, q, nu): - return sqrt(1.0 / nu * special.gammaincinv(nu, q)) + return sqrt(1.0/nu*special.gammaincinv(nu, q)) def _stats(self, nu): - mu = gam(nu + 0.5) / gam(nu) / sqrt(nu) - mu2 = 1.0 - mu * mu - g1 = mu * (1 - 4 * nu * mu2) / 2.0 / nu / np.power(mu2, 1.5) - g2 = -6 * mu ** 4 * nu + (8 * nu - 2) * mu ** 2 - 2 * nu + 1 - g2 /= nu * mu2 ** 2.0 + mu = gam(nu+0.5)/gam(nu)/sqrt(nu) + mu2 = 1.0-mu*mu + g1 = mu * (1 - 4*nu*mu2) / 2.0 / nu / np.power(mu2, 1.5) + g2 = -6*mu**4*nu + (8*nu-2)*mu**2-2*nu + 1 + g2 /= nu*mu2**2.0 return mu, mu2, g1, g2 nakagami = nakagami_gen(a=0.0, name="nakagami") @@ -3272,9 +3239,9 @@ class ncx2_gen(rv_continuous): return special.chndtrix(q, df, nc) def _stats(self, df, nc): - val = df + 2.0 * nc - return (df + nc, 2 * val, sqrt(8) * (val + nc) / val ** 1.5, - 12.0 * (val + 2 * nc) / val ** 2.0) + val = df + 2.0*nc + return (df + nc, 2*val, sqrt(8)*(val+nc)/val**1.5, + 12.0*(val+2*nc)/val**2.0) def _fitstart(self, data): m = data.mean() @@ -3295,12 +3262,12 @@ class ncf_gen(rv_continuous): ----- The probability density function for `ncf` is:: - ncf.pdf(x, df1, df2, nc) = exp(nc/2 + nc*df1*x/(2*(df1*x+df2))) - * df1**(df1/2) * df2**(df2/2) * x**(df1/2-1) - * (df2+df1*x)**(-(df1+df2)/2) - * gamma(df1/2)*gamma(1+df2/2) - * L^{v1/2-1}^{v2/2}(-nc*v1*x/(2*(v1*x+v2))) - / (B(v1/2, v2/2) * gamma((v1+v2)/2)) + ncf.pdf(x, df1, df2, nc) = exp(nc/2 + nc*df1*x/(2*(df1*x+df2))) * + df1**(df1/2) * df2**(df2/2) * x**(df1/2-1) * + (df2+df1*x)**(-(df1+df2)/2) * + gamma(df1/2)*gamma(1+df2/2) * + L^{v1/2-1}^{v2/2}(-nc*v1*x/(2*(v1*x+v2))) / + (B(v1/2, v2/2) * gamma((v1+v2)/2)) for ``df1, df2, nc > 0``. @@ -3312,17 +3279,15 @@ class ncf_gen(rv_continuous): def _pdf_skip(self, x, dfn, dfd, nc): n1, n2 = dfn, dfd - term = -nc / 2 + nc * n1 * x / \ - (2 * (n2 + n1 * x)) + gamln(n1 / 2.) + gamln(1 + n2 / 2.) - term -= gamln((n1 + n2) / 2.0) + term = -nc/2+nc*n1*x/(2*(n2+n1*x)) + gamln(n1/2.)+gamln(1+n2/2.) + term -= gamln((n1+n2)/2.0) Px = exp(term) - Px *= n1 ** (n1 / 2) * n2 ** (n2 / 2) * x ** (n1 / 2 - 1) - Px *= (n2 + n1 * x) ** (-(n1 + n2) / 2) - Px *= special.assoc_laguerre( - -nc * n1 * x / (2.0 * (n2 + n1 * x)), n2 / 2, n1 / 2 - 1) - Px /= special.beta(n1 / 2, n2 / 2) - # this function does not have a return - # drop it for now, the generic function seems to work ok + Px *= n1**(n1/2) * n2**(n2/2) * x**(n1/2-1) + Px *= (n2+n1*x)**(-(n1+n2)/2) + Px *= special.assoc_laguerre(-nc*n1*x/(2.0*(n2+n1*x)), n2/2, n1/2-1) + Px /= special.beta(n1/2, n2/2) + # This function does not have a return. Drop it for now, the generic + # function seems to work OK. def _cdf(self, x, dfn, dfd, nc): return special.ncfdtr(dfn, dfd, nc, x) @@ -3331,17 +3296,17 @@ class ncf_gen(rv_continuous): return special.ncfdtri(dfn, dfd, nc, q) def _munp(self, n, dfn, dfd, nc): - val = (dfn * 1.0 / dfd) ** n - term = gamln(n + 0.5 * dfn) + gamln(0.5 * dfd - n) - gamln(dfd * 0.5) - val *= exp(-nc / 2.0 + term) - val *= special.hyp1f1(n + 0.5 * dfn, 0.5 * dfn, 0.5 * nc) + val = (dfn * 1.0/dfd)**n + term = gamln(n+0.5*dfn) + gamln(0.5*dfd-n) - gamln(dfd*0.5) + val *= exp(-nc / 2.0+term) + val *= special.hyp1f1(n+0.5*dfn, 0.5*dfn, 0.5*nc) return val def _stats(self, dfn, dfd, nc): - mu = where(dfd <= 2, inf, dfd / (dfd - 2.0) * (1 + nc * 1.0 / dfn)) - mu2 = where(dfd <= 4, inf, 2 * (dfd * 1.0 / dfn) ** 2.0 * - ((dfn + nc / 2.0) ** 2.0 + (dfn + nc) * (dfd - 2.0)) / - ((dfd - 2.0) ** 2.0 * (dfd - 4.0))) + mu = where(dfd <= 2, inf, dfd / (dfd-2.0)*(1+nc*1.0/dfn)) + mu2 = where(dfd <= 4, inf, 2*(dfd*1.0/dfn)**2.0 * + ((dfn+nc/2.0)**2.0 + (dfn+nc)*(dfd-2.0)) / + ((dfd-2.0)**2.0 * (dfd-4.0))) return mu, mu2, None, None ncf = ncf_gen(a=0.0, name='ncf') @@ -3368,15 +3333,15 @@ class t_gen(rv_continuous): return mtrand.standard_t(df, size=self._size) def _pdf(self, x, df): - r = asarray(df * 1.0) - Px = exp(gamln((r + 1) / 2) - gamln(r / 2)) - Px /= sqrt(r * pi) * (1 + (x ** 2) / r) ** ((r + 1) / 2) + r = asarray(df*1.0) + Px = exp(gamln((r+1)/2)-gamln(r/2)) + Px /= sqrt(r*pi)*(1+(x**2)/r)**((r+1)/2) return Px def _logpdf(self, x, df): - r = df * 1.0 - lPx = gamln((r + 1) / 2) - gamln(r / 2) - lPx -= 0.5 * log(r * pi) + (r + 1) / 2 * log1p((x ** 2) / r) + r = df*1.0 + lPx = gamln((r+1)/2)-gamln(r/2) + lPx -= 0.5*log(r*pi) + (r+1)/2*log(1+(x**2)/r) return lPx def _cdf(self, x, df): @@ -3392,9 +3357,9 @@ class t_gen(rv_continuous): return -special.stdtrit(df, q) def _stats(self, df): - mu2 = where(df > 2, df / (df - 2.0), inf) + mu2 = where(df > 2, df / (df-2.0), inf) g1 = where(df > 3, 0.0, nan) - g2 = where(df > 4, 6.0 / (df - 4.0), nan) + g2 = where(df > 4, 6.0/(df-4.0), nan) return 0, mu2, g1, g2 t = t_gen(name='t') @@ -3408,9 +3373,9 @@ class nct_gen(rv_continuous): ----- The probability density function for `nct` is:: - df**(df/2) * gamma(df+1) - nct.pdf(x, df, nc) = ---------------------------------------------------- - 2**df*exp(nc**2/2) * (df+x**2)**(df/2) * gamma(df/2) + df**(df/2) * gamma(df+1) + nct.pdf(x, df, nc) = ---------------------------------------------------- + 2**df*exp(nc**2/2) * (df+x**2)**(df/2) * gamma(df/2) for ``df > 0``. @@ -3425,21 +3390,20 @@ class nct_gen(rv_continuous): sqrt(chi2.rvs(df, size=self._size))) def _pdf(self, x, df, nc): - n = df * 1.0 - nc = nc * 1.0 - x2 = x * x - ncx2 = nc * nc * x2 + n = df*1.0 + nc = nc*1.0 + x2 = x*x + ncx2 = nc*nc*x2 fac1 = n + x2 - trm1 = n / 2. * log(n) + gamln(n + 1) - trm1 -= n * \ - log(2) + nc * nc / 2. + (n / 2.) * log(fac1) + gamln(n / 2.) + trm1 = n/2.*log(n) + gamln(n+1) + trm1 -= n*log(2)+nc*nc/2.+(n/2.)*log(fac1)+gamln(n/2.) Px = exp(trm1) - valF = ncx2 / (2 * fac1) - trm1 = sqrt(2) * nc * x * special.hyp1f1(n / 2 + 1, 1.5, valF) - trm1 /= asarray(fac1 * special.gamma((n + 1) / 2)) - trm2 = special.hyp1f1((n + 1) / 2, 0.5, valF) - trm2 /= asarray(sqrt(fac1) * special.gamma(n / 2 + 1)) - Px *= trm1 + trm2 + valF = ncx2 / (2*fac1) + trm1 = sqrt(2)*nc*x*special.hyp1f1(n/2+1, 1.5, valF) + trm1 /= asarray(fac1*special.gamma((n+1)/2)) + trm2 = special.hyp1f1((n+1)/2, 0.5, valF) + trm2 /= asarray(sqrt(fac1)*special.gamma(n/2+1)) + Px *= trm1+trm2 return Px def _cdf(self, x, df, nc): @@ -3448,6 +3412,20 @@ class nct_gen(rv_continuous): def _ppf(self, q, df, nc): return special.nctdtrit(df, nc, q) + def _fitstart(self, data): + me = np.mean(data) + # g2 = mode(data)[0] + sa = np.std(data) + + def func(df): + return ((df-2)*(4 * df - 1)-(4 * df - 1)*df/(sa**2+me**2) + + me**2/(sa**2+me**2) * (df*(4 * df - 1) - 3*df)) + + df0 = np.maximum(2*sa/(sa-1), 1) + df = optimize.fsolve(func, df0) + mu = me*(1 - 3 / (4 * df - 1)) + return super(nct_gen, self)._fitstart(data, args=(df, mu)) + def _stats(self, df, nc, moments='mv'): # # See D. Hogben, R.S. Pinkham, and M.B. Wilk, @@ -3455,30 +3433,30 @@ class nct_gen(rv_continuous): # Biometrika 48, p. 465 (2961). # e.g. http://www.jstor.org/stable/2332772 (gated) # - _mu, _mu2, g1, g2 = None, None, None, None - - gfac = gam(df / 2. - 0.5) / gam(df / 2.) - c11 = sqrt(df / 2.) * gfac - c20 = df / (df - 2.) - c22 = c20 - c11 * c11 - mu = np.where(df > 1, nc * c11, np.inf) - mu2 = np.where(df > 2, c22 * nc * nc + c20, np.inf) + mu, mu2, g1, g2 = None, None, None, None + + gfac = gam(df/2.-0.5) / gam(df/2.) + c11 = sqrt(df/2.) * gfac + c20 = df / (df-2.) + c22 = c20 - c11*c11 + mu = np.where(df > 1, nc*c11, np.inf) + mu2 = np.where(df > 2, c22*nc*nc + c20, np.inf) if 's' in moments: - c33t = df * (7. - 2. * df) / (df - 2.) / (df - 3.) + 2. * c11 * c11 - c31t = 3. * df / (df - 2.) / (df - 3.) - mu3 = (c33t * nc * nc + c31t) * c11 * nc + c33t = df * (7.-2.*df) / (df-2.) / (df-3.) + 2.*c11*c11 + c31t = 3.*df / (df-2.) / (df-3.) + mu3 = (c33t*nc*nc + c31t) * c11*nc g1 = np.where(df > 3, mu3 / np.power(mu2, 1.5), np.nan) - # kurtosis + #kurtosis if 'k' in moments: - c44 = df * df / (df - 2.) / (df - 4.) - c44 -= c11 * c11 * 2. * df * (5. - df) / (df - 2.) / (df - 3.) - c44 -= 3. * c11 ** 4 - c42 = df / (df - 4.) - c11 * c11 * (df - 1.) / (df - 3.) - c42 *= 6. * df / (df - 2.) - c40 = 3. * df * df / (df - 2.) / (df - 4.) - - mu4 = c44 * nc ** 4 + c42 * nc ** 2 + c40 - g2 = np.where(df > 4, mu4 / mu2 ** 2 - 3., np.nan) + c44 = df*df / (df-2.) / (df-4.) + c44 -= c11*c11 * 2.*df*(5.-df) / (df-2.) / (df-3.) + c44 -= 3.*c11**4 + c42 = df / (df-4.) - c11*c11 * (df-1.) / (df-3.) + c42 *= 6.*df / (df-2.) + c40 = 3.*df*df / (df-2.) / (df-4.) + + mu4 = c44 * nc**4 + c42*nc**2 + c40 + g2 = np.where(df > 4, mu4/mu2**2 - 3., np.nan) return mu, mu2, g1, g2 nct = nct_gen(name="nct") @@ -3500,13 +3478,13 @@ class pareto_gen(rv_continuous): """ def _pdf(self, x, b): - return b * x ** (-b - 1) + return b * x**(-b-1) def _cdf(self, x, b): - return 1 - x ** (-b) + return 1 - x**(-b) def _ppf(self, q, b): - return pow(1 - q, -1.0 / b) + return pow(1-q, -1.0/b) def _stats(self, b, moments='mv'): mu, mu2, g1, g2 = None, None, None, None @@ -3514,12 +3492,12 @@ class pareto_gen(rv_continuous): mask = b > 1 bt = extract(mask, b) mu = valarray(shape(b), value=inf) - place(mu, mask, bt / (bt - 1.0)) + place(mu, mask, bt / (bt-1.0)) if 'v' in moments: mask = b > 2 bt = extract(mask, b) mu2 = valarray(shape(b), value=inf) - place(mu2, mask, bt / (bt - 2.0) / (bt - 1.0) ** 2) + place(mu2, mask, bt / (bt-2.0) / (bt-1.0)**2) if 's' in moments: mask = b > 3 bt = extract(mask, b) @@ -3530,13 +3508,13 @@ class pareto_gen(rv_continuous): mask = b > 4 bt = extract(mask, b) g2 = valarray(shape(b), value=nan) - vals = (6.0 * polyval([1.0, 1.0, -6, -2], bt) / + vals = (6.0*polyval([1.0, 1.0, -6, -2], bt) / polyval([1.0, -7.0, 12.0, 0.0], bt)) place(g2, mask, vals) return mu, mu2, g1, g2 def _entropy(self, c): - return 1 + 1.0 / c - log(c) + return 1 + 1.0/c - log(c) pareto = pareto_gen(a=1.0, name="pareto") @@ -3560,29 +3538,29 @@ class lomax_gen(rv_continuous): """ def _pdf(self, x, c): - return c * 1.0 / (1.0 + x) ** (c + 1.0) + return c*1.0/(1.0+x)**(c+1.0) def _logpdf(self, x, c): return log(c) - (c + 1) * log1p(x) def _cdf(self, x, c): - return 1.0 - 1.0 / (1.0 + x) ** c + return 1.0-1.0/(1.0+x)**c def _sf(self, x, c): - return 1.0 / (1.0 + x) ** c + return 1.0/(1.0+x)**c def _logsf(self, x, c): return -c * log1p(x) def _ppf(self, q, c): - return pow(1.0 - q, -1.0 / c) - 1 + return pow(1.0-q, -1.0/c)-1 def _stats(self, c): mu, mu2, g1, g2 = pareto.stats(c, loc=-1.0, moments='mvsk') return mu, mu2, g1, g2 def _entropy(self, c): - return 1 + 1.0 / c - log(c) + return 1+1.0/c-log(c) lomax = lomax_gen(a=0.0, name="lomax") @@ -3679,7 +3657,7 @@ class pearson3_gen(rv_continuous): # + (alpha - 1)*log(beta*(x - zeta)) + (a - 1)*log(x) # - beta*(x - zeta) - x # - gamln(alpha) - gamln(a) - ans, x, transx, skew, mask, invmask, beta, alpha, _zeta = ( + ans, x, transx, skew, mask, invmask, beta, alpha, zeta = ( self._preprocess(x, skew)) ans[mask] = np.log(_norm_pdf(x[mask])) @@ -3687,7 +3665,7 @@ class pearson3_gen(rv_continuous): return ans def _cdf(self, x, skew): - ans, x, transx, skew, mask, invmask, beta, alpha, _zeta = ( + ans, x, transx, skew, mask, invmask, beta, alpha, zeta = ( self._preprocess(x, skew)) ans[mask] = _norm_cdf(x[mask]) @@ -3695,7 +3673,7 @@ class pearson3_gen(rv_continuous): return ans def _rvs(self, skew): - _ans, _x, _transx, skew, mask, _invmask, beta, alpha, zeta = ( + ans, x, transx, skew, mask, invmask, beta, alpha, zeta = ( self._preprocess([0], skew)) if mask[0]: return mtrand.standard_normal(self._size) @@ -3726,25 +3704,25 @@ class powerlaw_gen(rv_continuous): for ``0 <= x <= 1``, ``a > 0``. - `powerlaw` is a special case of `beta` with ``d == 1``. + `powerlaw` is a special case of `beta` with ``b == 1``. %(example)s """ def _pdf(self, x, a): - return a * x ** (a - 1.0) + return a*x**(a-1.0) def _logpdf(self, x, a): return log(a) + special.xlogy(a - 1, x) def _cdf(self, x, a): - return x ** (a * 1.0) + return x**(a*1.0) def _logcdf(self, x, a): - return a * log(x) + return a*log(x) def _ppf(self, q, a): - return pow(q, 1.0 / a) + return pow(q, 1.0/a) def _stats(self, a): return (a / (a + 1.0), @@ -3753,7 +3731,7 @@ class powerlaw_gen(rv_continuous): 6 * polyval([1, -1, -6, 2], a) / (a * (a + 3.0) * (a + 4))) def _entropy(self, a): - return 1 - 1.0 / a - log(a) + return 1 - 1.0/a - log(a) powerlaw = powerlaw_gen(a=0.0, b=1.0, name="powerlaw") @@ -3776,11 +3754,11 @@ class powerlognorm_gen(rv_continuous): """ def _pdf(self, x, c, s): - return (c / (x * s) * _norm_pdf(log(x) / s) * - pow(_norm_cdf(-log(x) / s), c * 1.0 - 1.0)) + return (c/(x*s) * _norm_pdf(log(x)/s) * + pow(_norm_cdf(-log(x)/s), c*1.0-1.0)) def _cdf(self, x, c, s): - return 1.0 - pow(_norm_cdf(-log(x) / s), c * 1.0) + return 1.0 - pow(_norm_cdf(-log(x)/s), c*1.0) def _ppf(self, q, c, s): return exp(-s * _norm_ppf(pow(1.0 - q, 1.0 / c))) @@ -3805,13 +3783,13 @@ class powernorm_gen(rv_continuous): """ def _pdf(self, x, c): - return (c * _norm_pdf(x) * (_norm_cdf(-x) ** (c - 1.0))) + return (c*_norm_pdf(x) * (_norm_cdf(-x)**(c-1.0))) def _logpdf(self, x, c): - return log(c) + _norm_logpdf(x) + (c - 1) * _norm_logcdf(-x) + return log(c) + _norm_logpdf(x) + (c-1)*_norm_logcdf(-x) def _cdf(self, x, c): - return 1.0 - _norm_cdf(-x) ** (c * 1.0) + return 1.0-_norm_cdf(-x)**(c*1.0) def _ppf(self, q, c): return -_norm_ppf(pow(1.0 - q, 1.0 / c)) @@ -3835,12 +3813,11 @@ class rdist_gen(rv_continuous): """ def _pdf(self, x, c): - return (np.power((1.0 - x ** 2), c / 2.0 - 1) / - special.beta(0.5, c / 2.0)) + return np.power((1.0 - x**2), c / 2.0 - 1) / special.beta(0.5, c / 2.0) def _cdf(self, x, c): term1 = x / special.beta(0.5, c / 2.0) - res = 0.5 + term1 * special.hyp2f1(0.5, 1 - c / 2.0, 1.5, x ** 2) + res = 0.5 + term1 * special.hyp2f1(0.5, 1 - c / 2.0, 1.5, x**2) # There's an issue with hyp2f1, it returns nans near x = +-1, c > 100. # Use the generic implementation in that case. See gh-1285 for # background. @@ -3902,11 +3879,11 @@ class rayleigh_gen(rv_continuous): def _stats(self): val = 4 - pi - return (np.sqrt(pi / 2), val / 2, 2 * (pi - 3) * sqrt(pi) / val ** 1.5, - 6 * pi / val - 16 / val ** 2) + return (np.sqrt(pi/2), val/2, 2*(pi-3)*sqrt(pi)/val**1.5, + 6*pi/val-16/val**2) def _entropy(self): - return _EULER / 2.0 + 1 - 0.5 * log(2) + return _EULER/2.0 + 1 - 0.5*log(2) rayleigh = rayleigh_gen(a=0.0, name="rayleigh") @@ -4005,7 +3982,7 @@ class reciprocal_gen(rv_continuous): def _argcheck(self, a, b): self.a = a self.b = b - self.d = log(b * 1.0 / a) + self.d = log(b*1.0 / a) return (a > 0) & (b > 0) & (b > a) def _pdf(self, x, a, b): @@ -4015,16 +3992,27 @@ class reciprocal_gen(rv_continuous): return -log(x) - log(self.d) def _cdf(self, x, a, b): - return (log(x) - log(a)) / self.d + return (log(x)-log(a)) / self.d def _ppf(self, q, a, b): - return a * pow(b * 1.0 / a, q) + return a*pow(b*1.0/a, q) def _munp(self, n, a, b): - return 1.0 / self.d / n * (pow(b * 1.0, n) - pow(a * 1.0, n)) + return 1.0/self.d / n * (pow(b*1.0, n) - pow(a*1.0, n)) + + def _fitstart(self, data): + a = np.min(data) + a -= 0.01*np.abs(a) + b = np.max(data) + b += 0.01*np.abs(b) + if a <= 0: + da = np.abs(a)+0.001 + a += da + b += da + return super(reciprocal_gen, self)._fitstart(data, args=(a, b)) def _entropy(self, a, b): - return 0.5 * log(a * b) + log(log(b / a)) + return 0.5*log(a*b)+log(log(b/a)) reciprocal = reciprocal_gen(name="reciprocal") @@ -4051,17 +4039,17 @@ class rice_gen(rv_continuous): def _rvs(self, b): # http://en.wikipedia.org/wiki/Rice_distribution sz = self._size if self._size else 1 - t = b / np.sqrt(2) + mtrand.standard_normal(size=(2, sz)) - return np.sqrt((t * t).sum(axis=0)) + t = b/np.sqrt(2) + mtrand.standard_normal(size=(2, sz)) + return np.sqrt((t*t).sum(axis=0)) def _pdf(self, x, b): - return x * exp(-(x - b) * (x - b) / 2.0) * special.i0e(x * b) + return x * exp(-(x-b)*(x-b)/2.0) * special.i0e(x*b) def _munp(self, n, b): - nd2 = n / 2.0 + nd2 = n/2.0 n1 = 1 + nd2 - b2 = b * b / 2.0 - return (2.0 ** (nd2) * exp(-b2) * special.gamma(n1) * + b2 = b*b/2.0 + return (2.0**(nd2) * exp(-b2) * special.gamma(n1) * special.hyp1f1(n1, 1, b2)) rice = rice_gen(a=0.0, name="rice") @@ -4076,7 +4064,7 @@ class recipinvgauss_gen(rv_continuous): ----- The probability density function for `recipinvgauss` is:: - recipinvgauss.pdf(x, mu) = 1/sqrt(2*pi*x) * exp(-(1-mu*x)**2/(2*x*mu**2)) + recipinvgauss.pdf(x, mu) = 1/sqrt(2*pi*x) * exp(-(1-mu*x)**2/(2*x*mu**2)) for ``x >= 0``. @@ -4084,22 +4072,19 @@ class recipinvgauss_gen(rv_continuous): """ def _rvs(self, mu): - return 1.0 / mtrand.wald(mu, 1.0, size=self._size) + return 1.0/mtrand.wald(mu, 1.0, size=self._size) def _pdf(self, x, mu): - return (1.0 / sqrt(2 * pi * x) * exp(-(1 - mu * x) ** 2.0 / - (2 * x * mu ** 2.0))) + return 1.0/sqrt(2*pi*x)*exp(-(1-mu*x)**2.0 / (2*x*mu**2.0)) def _logpdf(self, x, mu): - return (-(1 - mu * x) ** 2.0 / (2 * x * mu ** 2.0) - - 0.5 * log(2 * pi * x)) + return -(1-mu*x)**2.0 / (2*x*mu**2.0) - 0.5*log(2*pi*x) def _cdf(self, x, mu): - trm1 = 1.0 / mu - x - trm2 = 1.0 / mu + x - isqx = 1.0 / sqrt(x) - return (1.0 - _norm_cdf(isqx * trm1) - - exp(2.0 / mu) * _norm_cdf(-isqx * trm2)) + trm1 = 1.0/mu - x + trm2 = 1.0/mu + x + isqx = 1.0/sqrt(x) + return 1.0-_norm_cdf(isqx*trm1)-exp(2.0/mu)*_norm_cdf(-isqx*trm2) recipinvgauss = recipinvgauss_gen(a=0.0, name='recipinvgauss') @@ -4120,10 +4105,10 @@ class semicircular_gen(rv_continuous): """ def _pdf(self, x): - return 2.0 / pi * sqrt(1 - x * x) + return 2.0/pi*sqrt(1-x*x) def _cdf(self, x): - return 0.5 + 1.0 / pi * (x * sqrt(1 - x * x) + arcsin(x)) + return 0.5+1.0/pi*(x*sqrt(1-x*x) + arcsin(x)) def _stats(self): return 0, 0.25, 0, -1.0 @@ -4158,23 +4143,20 @@ class triang_gen(rv_continuous): return (c >= 0) & (c <= 1) def _pdf(self, x, c): - return where(x < c, 2 * x / c, 2 * (1 - x) / (1 - c)) + return where(x < c, 2*x/c, 2*(1-x)/(1-c)) def _cdf(self, x, c): - return where(x < c, x * x / c, (x * x - 2 * x + c) / (c - 1)) + return where(x < c, x*x/c, (x*x-2*x+c)/(c-1)) def _ppf(self, q, c): - return where(q < c, sqrt(c * q), 1 - sqrt((1 - c) * (1 - q))) + return where(q < c, sqrt(c*q), 1-sqrt((1-c)*(1-q))) def _stats(self, c): - return ((c + 1.0) / 3.0, - (1.0 - c + c * c) / 18, - sqrt(2) * (2 * c - 1) * (c + 1) * (c - 2) / - (5 * np.power((1.0 - c + c * c), 1.5)), - -3.0 / 5.0) + return (c+1.0)/3.0, (1.0-c+c*c)/18, sqrt(2)*(2*c-1)*(c+1)*(c-2) / \ + (5 * np.power((1.0-c+c*c), 1.5)), -3.0/5.0 def _entropy(self, c): - return 0.5 - log(2) + return 0.5-log(2) triang = triang_gen(a=0.0, b=1.0, name="triang") @@ -4224,7 +4206,7 @@ class truncexpon_gen(rv_continuous): def _entropy(self, b): eB = exp(b) - return log(eB - 1) + (1 + eB * (b - 1.0)) / (1.0 - eB) + return log(eB-1)+(1+eB*(b-1.0))/(1.0-eB) truncexpon = truncexpon_gen(a=0.0, name='truncexpon') @@ -4270,16 +4252,16 @@ class truncnorm_gen(rv_continuous): def _ppf(self, q, a, b): if self.a > 0: - return _norm_isf(q * self._sb + self._sa * (1.0 - q)) + return _norm_isf(q*self._sb + self._sa*(1.0-q)) else: - return _norm_ppf(q * self._nb + self._na * (1.0 - q)) + return _norm_ppf(q*self._nb + self._na*(1.0-q)) def _stats(self, a, b): nA, nB = self._na, self._nb d = nB - nA pA, pB = _norm_pdf(a), _norm_pdf(b) mu = (pA - pB) / d # correction sign - mu2 = 1 + (a * pA - b * pB) / d - mu * mu + mu2 = 1 + (a*pA - b*pB) / d - mu*mu return mu, mu2, None, None truncnorm = truncnorm_gen(name='truncnorm') @@ -4309,17 +4291,17 @@ class tukeylambda_gen(rv_continuous): def _pdf(self, x, lam): Fx = asarray(special.tklmbda(x, lam)) - Px = Fx ** (lam - 1.0) + (asarray(1 - Fx)) ** (lam - 1.0) - Px = 1.0 / asarray(Px) - return where((lam <= 0) | (abs(x) < 1.0 / asarray(lam)), Px, 0.0) + Px = Fx**(lam-1.0) + (asarray(1-Fx))**(lam-1.0) + Px = 1.0/asarray(Px) + return where((lam <= 0) | (abs(x) < 1.0/asarray(lam)), Px, 0.0) def _cdf(self, x, lam): return special.tklmbda(x, lam) def _ppf(self, q, lam): - q = q * 1.0 - vals1 = (q ** lam - (1 - q) ** lam) / lam - vals2 = log(q / (1 - q)) + q = q*1.0 + vals1 = (q**lam - (1-q)**lam)/lam + vals2 = log(q/(1-q)) return where((lam == 0) & (q == q), vals2, vals1) def _stats(self, lam): @@ -4327,7 +4309,7 @@ class tukeylambda_gen(rv_continuous): def _entropy(self, lam): def integ(p): - return log(pow(p, lam - 1) + pow(1 - p, lam - 1)) + return log(pow(p, lam-1)+pow(1-p, lam-1)) return integrate.quad(integ, 0, 1)[0] tukeylambda = tukeylambda_gen(name='tukeylambda') @@ -4346,7 +4328,7 @@ class uniform_gen(rv_continuous): return mtrand.uniform(0.0, 1.0, self._size) def _pdf(self, x): - return 1.0 * (x == x) + return 1.0*(x == x) def _cdf(self, x): return x @@ -4355,7 +4337,7 @@ class uniform_gen(rv_continuous): return q def _stats(self): - return 0.5, 1.0 / 12, 0, -1.2 + return 0.5, 1.0/12, 0, -1.2 def _entropy(self): return 0.0 @@ -4390,7 +4372,7 @@ class vonmises_gen(rv_continuous): return mtrand.vonmises(0.0, kappa, size=self._size) def _pdf(self, x, kappa): - return exp(kappa * cos(x)) / (2 * pi * special.i0(kappa)) + return exp(kappa * cos(x)) / (2*pi*special.i0(kappa)) def _cdf(self, x, kappa): return vonmises_cython.von_mises_cdf(kappa, x) @@ -4410,7 +4392,7 @@ class wald_gen(invgauss_gen): ----- The probability density function for `wald` is:: - wald.pdf(x, a) = 1/sqrt(2*pi*x**3) * exp(-(x-1)**2/(2*x)) + wald.pdf(x) = 1/sqrt(2*pi*x**3) * exp(-(x-1)**2/(2*x)) for ``x > 0``. @@ -4455,34 +4437,41 @@ class wrapcauchy_gen(rv_continuous): return (c > 0) & (c < 1) def _pdf(self, x, c): - return (1.0 - c * c) / (2 * pi * (1 + c * c - 2 * c * cos(x))) + return (1.0-c*c)/(2*pi*(1+c*c-2*c*cos(x))) def _cdf(self, x, c): - output = 0.0 * x - val = (1.0 + c) / (1.0 - c) + output = 0.0*x + val = (1.0+c)/(1.0-c) c1 = x < pi - c2 = 1 - c1 + c2 = 1-c1 xp = extract(c1, x) xn = extract(c2, x) if (any(xn)): - valn = extract(c2, np.ones_like(x) * val) - xn = 2 * pi - xn - yn = tan(xn / 2.0) - on = 1.0 - 1.0 / pi * arctan(valn * yn) + valn = extract(c2, np.ones_like(x)*val) + xn = 2*pi - xn + yn = tan(xn/2.0) + on = 1.0-1.0/pi*arctan(valn*yn) place(output, c2, on) if (any(xp)): - valp = extract(c1, np.ones_like(x) * val) - yp = tan(xp / 2.0) - op = 1.0 / pi * arctan(valp * yp) + valp = extract(c1, np.ones_like(x)*val) + yp = tan(xp/2.0) + op = 1.0/pi*arctan(valp*yp) place(output, c1, op) return output def _ppf(self, q, c): - val = (1.0 - c) / (1.0 + c) - rcq = 2 * arctan(val * tan(pi * q)) - rcmq = 2 * pi - 2 * arctan(val * tan(pi * (1 - q))) - return where(q < 1.0 / 2, rcq, rcmq) + val = (1.0-c)/(1.0+c) + rcq = 2*arctan(val*tan(pi*q)) + rcmq = 2*pi-2*arctan(val*tan(pi*(1-q))) + return where(q < 1.0/2, rcq, rcmq) def _entropy(self, c): - return log(2 * pi * (1 - c * c)) -wrapcauchy = wrapcauchy_gen(a=0.0, b=2 * pi, name='wrapcauchy') + return log(2*pi*(1-c*c)) +wrapcauchy = wrapcauchy_gen(a=0.0, b=2*pi, name='wrapcauchy') + + +# Collect names of classes and objects in this module. +pairs = list(globals().items()) +_distn_names, _distn_gen_names = get_distribution_names(pairs, rv_continuous) + +__all__ = _distn_names + _distn_gen_names diff --git a/pywafo/src/wafo/stats/_discrete_distns.py b/pywafo/src/wafo/stats/_discrete_distns.py index 07ed14a..ae29faa 100644 --- a/pywafo/src/wafo/stats/_discrete_distns.py +++ b/pywafo/src/wafo/stats/_discrete_distns.py @@ -13,17 +13,10 @@ import numpy as np import numpy.random as mtrand from ._distn_infrastructure import ( - rv_discrete, _lazywhere, _ncx2_pdf, _ncx2_cdf) - -__all__ = [ - 'binom', 'bernoulli', 'nbinom', 'geom', 'hypergeom', - 'logser', 'poisson', 'planck', 'boltzmann', 'randint', - 'zipf', 'dlaplace', 'skellam' -] + rv_discrete, _lazywhere, _ncx2_pdf, _ncx2_cdf, get_distribution_names) class binom_gen(rv_discrete): - """A binomial discrete random variable. %(before_notes)s @@ -41,7 +34,6 @@ class binom_gen(rv_discrete): %(example)s """ - def _rvs(self, n, p): return mtrand.binomial(n, p, self._size) @@ -51,8 +43,8 @@ class binom_gen(rv_discrete): def _logpmf(self, x, n, p): k = floor(x) - combiln = (gamln(n + 1) - (gamln(k + 1) + gamln(n - k + 1))) - return combiln + special.xlogy(k, p) + special.xlog1py(n - k, -p) + combiln = (gamln(n+1) - (gamln(k+1) + gamln(n-k+1))) + return combiln + special.xlogy(k, p) + special.xlog1py(n-k, -p) def _pmf(self, x, n, p): return exp(self._logpmf(x, n, p)) @@ -68,16 +60,19 @@ class binom_gen(rv_discrete): def _ppf(self, q, n, p): vals = ceil(special.bdtrik(q, n, p)) - vals1 = vals - 1 + vals1 = np.maximum(vals - 1, 0) temp = special.bdtr(vals1, n, p) return np.where(temp >= q, vals1, vals) - def _stats(self, n, p): + def _stats(self, n, p, moments='mv'): q = 1.0 - p mu = n * p var = n * p * q - g1 = (q - p) / sqrt(n * p * q) - g2 = (1.0 - 6 * p * q) / (n * p * q) + g1, g2 = None, None + if 's' in moments: + g1 = (q - p) / sqrt(var) + if 'k' in moments: + g2 = (1.0 - 6*p*q) / var return mu, var, g1, g2 def _entropy(self, n, p): @@ -89,7 +84,6 @@ binom = binom_gen(name='binom') class bernoulli_gen(binom_gen): - """A Bernoulli discrete random variable. %(before_notes)s @@ -108,7 +102,6 @@ class bernoulli_gen(binom_gen): %(example)s """ - def _rvs(self, p): return binom_gen._rvs(self, 1, p) @@ -140,7 +133,6 @@ bernoulli = bernoulli_gen(b=1, name='bernoulli') class nbinom_gen(rv_discrete): - """A negative binomial discrete random variable. %(before_notes)s @@ -158,7 +150,6 @@ class nbinom_gen(rv_discrete): %(example)s """ - def _rvs(self, n, p): return mtrand.negative_binomial(n, p, self._size) @@ -174,7 +165,7 @@ class nbinom_gen(rv_discrete): def _cdf(self, x, n, p): k = floor(x) - return special.betainc(n, k + 1, p) + return special.betainc(n, k+1, p) def _sf_skip(self, x, n, p): # skip because special.nbdtrc doesn't work for 0= q, vals1, vals) def _stats(self, n, p): Q = 1.0 / p P = Q - 1.0 - mu = n * P - var = n * P * Q - g1 = (Q + P) / sqrt(n * P * Q) - g2 = (1.0 + 6 * P * Q) / (n * P * Q) + mu = n*P + var = n*P*Q + g1 = (Q+P)/sqrt(n*P*Q) + g2 = (1.0 + 6*P*Q) / (n*P*Q) return mu, var, g1, g2 nbinom = nbinom_gen(name='nbinom') class geom_gen(rv_discrete): - """A geometric discrete random variable. %(before_notes)s @@ -217,7 +207,6 @@ class geom_gen(rv_discrete): %(example)s """ - def _rvs(self, p): return mtrand.geometric(p, size=self._size) @@ -225,7 +214,7 @@ class geom_gen(rv_discrete): return (p <= 1) & (p >= 0) def _pmf(self, k, p): - return np.power(1 - p, k - 1) * p + return np.power(1-p, k-1) * p def _logpmf(self, k, p): return (k - 1) * log1p(-p) + log(p) @@ -247,17 +236,16 @@ class geom_gen(rv_discrete): return np.where((temp >= q) & (vals > 0), vals - 1, vals) def _stats(self, p): - mu = 1.0 / p - qr = 1.0 - p + mu = 1.0/p + qr = 1.0-p var = qr / p / p - g1 = (2.0 - p) / sqrt(qr) - g2 = np.polyval([1, -6, 6], p) / (1.0 - p) + g1 = (2.0-p) / sqrt(qr) + g2 = np.polyval([1, -6, 6], p)/(1.0-p) return mu, var, g1, g2 geom = geom_gen(a=1, name='geom', longname="A geometric") class hypergeom_gen(rv_discrete): - """A hypergeometric discrete random variable. The hypergeometric distribution models drawing objects from a bin. @@ -277,6 +265,7 @@ class hypergeom_gen(rv_discrete): Examples -------- >>> from scipy.stats import hypergeom + >>> import matplotlib.pyplot as plt Suppose we have a collection of 20 animals, of which 7 are dogs. Then if we want to know the probability of finding a given number of dogs if we @@ -307,23 +296,22 @@ class hypergeom_gen(rv_discrete): >>> R = hypergeom.rvs(M, n, N, size=10) """ - def _rvs(self, M, n, N): - return mtrand.hypergeometric(n, M - n, N, size=self._size) + return mtrand.hypergeometric(n, M-n, N, size=self._size) def _argcheck(self, M, n, N): cond = rv_discrete._argcheck(self, M, n, N) cond &= (n <= M) & (N <= M) - self.a = max(N - (M - n), 0) + self.a = max(N-(M-n), 0) self.b = min(n, N) return cond def _logpmf(self, k, M, n, N): tot, good = M, n bad = tot - good - return gamln(good + 1) - gamln(good - k + 1) - gamln(k + 1) + \ - gamln(bad + 1) - gamln(bad - N + k + 1) - gamln(N - k + 1) - \ - gamln(tot + 1) + gamln(tot - N + 1) + gamln(N + 1) + return gamln(good+1) - gamln(good-k+1) - gamln(k+1) + gamln(bad+1) \ + - gamln(bad-N+k+1) - gamln(N-k+1) - gamln(tot+1) + gamln(tot-N+1) \ + + gamln(N+1) def _pmf(self, k, M, n, N): # same as the following but numerically more precise @@ -333,19 +321,18 @@ class hypergeom_gen(rv_discrete): def _stats(self, M, n, N): # tot, good, sample_size = M, n, N # "wikipedia".replace('N', 'M').replace('n', 'N').replace('K', 'n') - M, n, N = 1. * M, 1. * n, 1. * N + M, n, N = 1.*M, 1.*n, 1.*N m = M - n - p = n / M - mu = N * p + p = n/M + mu = N*p - var = m * n * N * (M - N) * 1.0 / (M * M * (M - 1)) - g1 = (m - n) * (M - 2 * N) / (M - 2.0) * \ - sqrt((M - 1.0) / (m * n * N * (M - N))) + var = m*n*N*(M - N)*1.0/(M*M*(M-1)) + g1 = (m - n)*(M-2*N) / (M-2.0) * sqrt((M-1.0) / (m*n*N*(M-N))) - g2 = M * (M + 1) - 6. * N * (M - N) - 6. * n * m - g2 *= (M - 1) * M * M - g2 += 6. * n * N * (M - N) * m * (5. * M - 6) - g2 /= n * N * (M - N) * m * (M - 2.) * (M - 3.) + g2 = M*(M+1) - 6.*N*(M-N) - 6.*n*m + g2 *= (M-1)*M*M + g2 += 6.*n*N*(M-N)*m*(5.*M-6) + g2 /= n * N * (M-N) * m * (M-2.) * (M-3.) return mu, var, g1, g2 def _entropy(self, M, n, N): @@ -372,7 +359,6 @@ hypergeom = hypergeom_gen(name='hypergeom') # FIXME: Fails _cdfvec class logser_gen(rv_discrete): - """A Logarithmic (Log-Series, Series) discrete random variable. %(before_notes)s @@ -390,7 +376,6 @@ class logser_gen(rv_discrete): %(example)s """ - def _rvs(self, p): # looks wrong for p>0.5, too few k=1 # trying to use generic is worse, no k=1 at all @@ -405,22 +390,21 @@ class logser_gen(rv_discrete): def _stats(self, p): r = log1p(-p) mu = p / (p - 1.0) / r - mu2p = -p / r / (p - 1.0) ** 2 - var = mu2p - mu * mu - mu3p = -p / r * (1.0 + p) / (1.0 - p) ** 3 - mu3 = mu3p - 3 * mu * mu2p + 2 * mu ** 3 + mu2p = -p / r / (p - 1.0)**2 + var = mu2p - mu*mu + mu3p = -p / r * (1.0+p) / (1.0 - p)**3 + mu3 = mu3p - 3*mu*mu2p + 2*mu**3 g1 = mu3 / np.power(var, 1.5) mu4p = -p / r * ( - 1.0 / (p - 1) ** 2 - 6 * p / (p - 1) ** 3 + 6 * p * p / (p - 1) ** 4) - mu4 = mu4p - 4 * mu3p * mu + 6 * mu2p * mu * mu - 3 * mu ** 4 - g2 = mu4 / var ** 2 - 3.0 + 1.0 / (p-1)**2 - 6*p / (p - 1)**3 + 6*p*p / (p-1)**4) + mu4 = mu4p - 4*mu3p*mu + 6*mu2p*mu*mu - 3*mu**4 + g2 = mu4 / var**2 - 3.0 return mu, var, g1, g2 logser = logser_gen(a=1, name='logser', longname='A logarithmic') class poisson_gen(rv_discrete): - """A Poisson discrete random variable. %(before_notes)s @@ -438,12 +422,11 @@ class poisson_gen(rv_discrete): %(example)s """ - def _rvs(self, mu): return mtrand.poisson(mu, self._size) def _logpmf(self, k, mu): - Pk = k * log(mu) - gamln(k + 1) - mu + Pk = k*log(mu)-gamln(k+1) - mu return Pk def _pmf(self, k, mu): @@ -459,9 +442,9 @@ class poisson_gen(rv_discrete): def _ppf(self, q, mu): vals = ceil(special.pdtrik(q, mu)) - vals1 = vals - 1 + vals1 = np.maximum(vals - 1, 0) temp = special.pdtr(vals1, mu) - return np.where((temp >= q), vals1, vals) + return np.where(temp >= q, vals1, vals) def _stats(self, mu): var = mu @@ -473,7 +456,6 @@ poisson = poisson_gen(name="poisson", longname='A Poisson') class planck_gen(rv_discrete): - """A Planck discrete exponential random variable. %(before_notes)s @@ -491,7 +473,6 @@ class planck_gen(rv_discrete): %(example)s """ - def _argcheck(self, lambda_): if (lambda_ > 0): self.a = 0 @@ -513,27 +494,26 @@ class planck_gen(rv_discrete): return - expm1(-lambda_ * (k + 1)) def _ppf(self, q, lambda_): - vals = ceil(-1.0 / lambda_ * log1p(-q) - 1) - vals1 = (vals - 1).clip(self.a, np.inf) + vals = ceil(-1.0/lambda_ * log1p(-q)-1) + vals1 = (vals-1).clip(self.a, np.inf) temp = self._cdf(vals1, lambda_) return np.where(temp >= q, vals1, vals) def _stats(self, lambda_): - mu = 1 / (exp(lambda_) - 1) - var = exp(-lambda_) / (expm1(-lambda_)) ** 2 - g1 = 2 * cosh(lambda_ / 2.0) - g2 = 4 + 2 * cosh(lambda_) + mu = 1/(exp(lambda_)-1) + var = exp(-lambda_)/(expm1(-lambda_))**2 + g1 = 2*cosh(lambda_/2.0) + g2 = 4+2*cosh(lambda_) return mu, var, g1, g2 def _entropy(self, lambda_): l = lambda_ C = -expm1(-l) - return l * exp(-l) / C - log(C) + return l*exp(-l)/C - log(C) planck = planck_gen(name='planck', longname='A discrete exponential ') class boltzmann_gen(rv_discrete): - """A Boltzmann (Truncated Discrete Exponential) random variable. %(before_notes)s @@ -551,7 +531,6 @@ class boltzmann_gen(rv_discrete): %(example)s """ - def _pmf(self, k, lambda_, N): fact = (expm1(-lambda_)) / (expm1(-lambda_ * N)) return fact * exp(-lambda_ * k) @@ -569,23 +548,21 @@ class boltzmann_gen(rv_discrete): def _stats(self, lambda_, N): z = exp(-lambda_) - zN = exp(-lambda_ * N) - mu = z / (1.0 - z) - N * zN / (1 - zN) - var = z / (1.0 - z) ** 2 - N * N * zN / (1 - zN) ** 2 - trm = (1 - zN) / (1 - z) - trm2 = (z * trm ** 2 - N * N * zN) - g1 = z * (1 + z) * trm ** 3 - N ** 3 * zN * (1 + zN) - g1 = g1 / trm2 ** (1.5) - g2 = z * (1 + 4 * z + z * z) * \ - trm ** 4 - N ** 4 * zN * (1 + 4 * zN + zN * zN) + zN = exp(-lambda_*N) + mu = z/(1.0-z)-N*zN/(1-zN) + var = z/(1.0-z)**2 - N*N*zN/(1-zN)**2 + trm = (1-zN)/(1-z) + trm2 = (z*trm**2 - N*N*zN) + g1 = z*(1+z)*trm**3 - N**3*zN*(1+zN) + g1 = g1 / trm2**(1.5) + g2 = z*(1+4*z+z*z)*trm**4 - N**4 * zN*(1+4*zN+zN*zN) g2 = g2 / trm2 / trm2 return mu, var, g1, g2 boltzmann = boltzmann_gen(name='boltzmann', - longname='A truncated discrete exponential ') + longname='A truncated discrete exponential ') class randint_gen(rv_discrete): - """A uniform discrete random variable. %(before_notes)s @@ -606,7 +583,6 @@ class randint_gen(rv_discrete): %(example)s """ - def _argcheck(self, low, high): self.a = low self.b = high - 1 @@ -630,9 +606,9 @@ class randint_gen(rv_discrete): m2, m1 = np.asarray(high), np.asarray(low) mu = (m2 + m1 - 1.0) / 2 d = m2 - m1 - var = (d * d - 1) / 12.0 + var = (d*d - 1) / 12.0 g1 = 0.0 - g2 = -6.0 / 5.0 * (d * d + 1.0) / (d * d - 1.0) + g2 = -6.0/5.0 * (d*d + 1.0) / (d*d - 1.0) return mu, var, g1, g2 def _rvs(self, low, high=None): @@ -648,9 +624,22 @@ randint = randint_gen(name='randint', longname='A discrete uniform ' '(random integer)') +def harmonic(n,r): + return 1./n + special.polygamma(r-1, n)/special.gamma(r) + special.zeta(r, 1) + + +def H(n): + """Returns the n-th harmonic number. + + http://en.wikipedia.org/wiki/Harmonic_number + """ + # Euler-Mascheroni constant + gamma = 0.57721566490153286060651209008240243104215933593992 + return gamma + special.digamma(n+1) + + # FIXME: problems sampling. class zipf_gen(rv_discrete): - """A Zipf discrete random variable. %(before_notes)s @@ -668,7 +657,6 @@ class zipf_gen(rv_discrete): %(example)s """ - def _rvs(self, a): return mtrand.zipf(a, size=self._size) @@ -676,7 +664,7 @@ class zipf_gen(rv_discrete): return a > 1 def _pmf(self, k, a): - Pk = 1.0 / special.zeta(a, 1) / k ** a + Pk = 1.0 / special.zeta(a, 1) / k**a return Pk def _munp(self, n, a): @@ -688,7 +676,6 @@ zipf = zipf_gen(a=1, name='zipf', longname='A Zipf') class dlaplace_gen(rv_discrete): - """A Laplacian discrete random variable. %(before_notes)s @@ -706,37 +693,35 @@ class dlaplace_gen(rv_discrete): %(example)s """ - def _pmf(self, k, a): - return tanh(a / 2.0) * exp(-a * abs(k)) + return tanh(a/2.0) * exp(-a * abs(k)) def _cdf(self, x, a): k = floor(x) f = lambda k, a: 1.0 - exp(-a * k) / (exp(a) + 1) - f2 = lambda k, a: exp(a * (k + 1)) / (exp(a) + 1) + f2 = lambda k, a: exp(a * (k+1)) / (exp(a) + 1) return _lazywhere(k >= 0, (k, a), f=f, f2=f2) def _ppf(self, q, a): const = 1 + exp(a) - vals = ceil(np.where(q < 1.0 / (1 + exp(-a)), log(q * const) / a - 1, - -log((1 - q) * const) / a)) + vals = ceil(np.where(q < 1.0 / (1 + exp(-a)), log(q*const) / a - 1, + -log((1-q) * const) / a)) vals1 = vals - 1 return np.where(self._cdf(vals1, a) >= q, vals1, vals) def _stats(self, a): ea = exp(a) - mu2 = 2. * ea / (ea - 1.) ** 2 - mu4 = 2. * ea * (ea ** 2 + 10. * ea + 1.) / (ea - 1.) ** 4 - return 0., mu2, 0., mu4 / mu2 ** 2 - 3. + mu2 = 2.*ea/(ea-1.)**2 + mu4 = 2.*ea*(ea**2+10.*ea+1.) / (ea-1.)**4 + return 0., mu2, 0., mu4/mu2**2 - 3. def _entropy(self, a): - return a / sinh(a) - log(tanh(a / 2.0)) + return a / sinh(a) - log(tanh(a/2.0)) dlaplace = dlaplace_gen(a=-np.inf, name='dlaplace', longname='A discrete Laplacian') class skellam_gen(rv_discrete): - """A Skellam discrete random variable. %(before_notes)s @@ -762,29 +747,35 @@ class skellam_gen(rv_discrete): %(example)s """ - def _rvs(self, mu1, mu2): n = self._size return mtrand.poisson(mu1, n) - mtrand.poisson(mu2, n) def _pmf(self, x, mu1, mu2): px = np.where(x < 0, - _ncx2_pdf(2 * mu2, 2 * (1 - x), 2 * mu1) * 2, - _ncx2_pdf(2 * mu1, 2 * (1 + x), 2 * mu2) * 2) + _ncx2_pdf(2*mu2, 2*(1-x), 2*mu1)*2, + _ncx2_pdf(2*mu1, 2*(1+x), 2*mu2)*2) # ncx2.pdf() returns nan's for extremely low probabilities return px def _cdf(self, x, mu1, mu2): x = floor(x) px = np.where(x < 0, - _ncx2_cdf(2 * mu2, -2 * x, 2 * mu1), - 1 - _ncx2_cdf(2 * mu1, 2 * (x + 1), 2 * mu2)) + _ncx2_cdf(2*mu2, -2*x, 2*mu1), + 1-_ncx2_cdf(2*mu1, 2*(x+1), 2*mu2)) return px def _stats(self, mu1, mu2): mean = mu1 - mu2 var = mu1 + mu2 - g1 = mean / sqrt((var) ** 3) + g1 = mean / sqrt((var)**3) g2 = 1 / var return mean, var, g1, g2 skellam = skellam_gen(a=-np.inf, name="skellam", longname='A Skellam') + + +# Collect names of classes and objects in this module. +pairs = list(globals().items()) +_distn_names, _distn_gen_names = get_distribution_names(pairs, rv_discrete) + +__all__ = _distn_names + _distn_gen_names diff --git a/pywafo/src/wafo/stats/_distn_infrastructure.py b/pywafo/src/wafo/stats/_distn_infrastructure.py index b9c6224..05901a5 100644 --- a/pywafo/src/wafo/stats/_distn_infrastructure.py +++ b/pywafo/src/wafo/stats/_distn_infrastructure.py @@ -12,9 +12,11 @@ import re import inspect import types import warnings + from scipy.misc import doccer +from ._distr_params import distcont, distdiscrete -from scipy.special import xlogy, chndtr, gammaln, hyp0f1 +from scipy.special import xlogy, chndtr, gammaln, hyp0f1, comb # for root finding for discrete distribution ppf, and max likelihood estimation from scipy import optimize @@ -23,11 +25,11 @@ from scipy import optimize from scipy import integrate # to approximate the pdf of a continuous distribution given its cdf -from scipy.misc import comb, derivative # @UnresolvedImport +from scipy.misc import derivative from numpy import (arange, putmask, ravel, take, ones, sum, shape, product, reshape, zeros, floor, logical_and, log, sqrt, exp, - ndarray, newaxis) + ndarray) from numpy import (place, any, argsort, argmax, vectorize, asarray, nan, inf, isinf, NINF, empty) @@ -55,91 +57,91 @@ docheaders = {'methods': """\nMethods\n-------\n""", 'examples': """\nExamples\n--------\n"""} _doc_rvs = """\ -rvs(%(shapes)s, loc=0, scale=1, size=1) +``rvs(%(shapes)s, loc=0, scale=1, size=1)`` Random variates. """ _doc_pdf = """\ -pdf(x, %(shapes)s, loc=0, scale=1) +``pdf(x, %(shapes)s, loc=0, scale=1)`` Probability density function. """ _doc_logpdf = """\ -logpdf(x, %(shapes)s, loc=0, scale=1) +``logpdf(x, %(shapes)s, loc=0, scale=1)`` Log of the probability density function. """ _doc_pmf = """\ -pmf(x, %(shapes)s, loc=0, scale=1) +``pmf(x, %(shapes)s, loc=0, scale=1)`` Probability mass function. """ _doc_logpmf = """\ -logpmf(x, %(shapes)s, loc=0, scale=1) +``logpmf(x, %(shapes)s, loc=0, scale=1)`` Log of the probability mass function. """ _doc_cdf = """\ -cdf(x, %(shapes)s, loc=0, scale=1) +``cdf(x, %(shapes)s, loc=0, scale=1)`` Cumulative density function. """ _doc_logcdf = """\ -logcdf(x, %(shapes)s, loc=0, scale=1) +``logcdf(x, %(shapes)s, loc=0, scale=1)`` Log of the cumulative density function. """ _doc_sf = """\ -sf(x, %(shapes)s, loc=0, scale=1) +``sf(x, %(shapes)s, loc=0, scale=1)`` Survival function (1-cdf --- sometimes more accurate). """ _doc_logsf = """\ -logsf(x, %(shapes)s, loc=0, scale=1) +``logsf(x, %(shapes)s, loc=0, scale=1)`` Log of the survival function. """ _doc_ppf = """\ -ppf(q, %(shapes)s, loc=0, scale=1) +``ppf(q, %(shapes)s, loc=0, scale=1)`` Percent point function (inverse of cdf --- percentiles). """ _doc_isf = """\ -isf(q, %(shapes)s, loc=0, scale=1) +``isf(q, %(shapes)s, loc=0, scale=1)`` Inverse survival function (inverse of sf). """ _doc_moment = """\ -moment(n, %(shapes)s, loc=0, scale=1) +``moment(n, %(shapes)s, loc=0, scale=1)`` Non-central moment of order n """ _doc_stats = """\ -stats(%(shapes)s, loc=0, scale=1, moments='mv') +``stats(%(shapes)s, loc=0, scale=1, moments='mv')`` Mean('m'), variance('v'), skew('s'), and/or kurtosis('k'). """ _doc_entropy = """\ -entropy(%(shapes)s, loc=0, scale=1) +``entropy(%(shapes)s, loc=0, scale=1)`` (Differential) entropy of the RV. """ _doc_fit = """\ -fit(data, %(shapes)s, loc=0, scale=1) +``fit(data, %(shapes)s, loc=0, scale=1)`` Parameter estimates for generic data. """ _doc_expect = """\ -expect(func, %(shapes)s, loc=0, scale=1, lb=None, ub=None, conditional=False, **kwds) +``expect(func, %(shapes)s, loc=0, scale=1, lb=None, ub=None, conditional=False, **kwds)`` Expected value of a function (of one argument) with respect to the distribution. """ _doc_expect_discrete = """\ -expect(func, %(shapes)s, loc=0, lb=None, ub=None, conditional=False) +``expect(func, %(shapes)s, loc=0, lb=None, ub=None, conditional=False)`` Expected value of a function (of one argument) with respect to the distribution. """ _doc_median = """\ -median(%(shapes)s, loc=0, scale=1) +``median(%(shapes)s, loc=0, scale=1)`` Median of the distribution. """ _doc_mean = """\ -mean(%(shapes)s, loc=0, scale=1) +``mean(%(shapes)s, loc=0, scale=1)`` Mean of the distribution. """ _doc_var = """\ -var(%(shapes)s, loc=0, scale=1) +``var(%(shapes)s, loc=0, scale=1)`` Variance of the distribution. """ _doc_std = """\ -std(%(shapes)s, loc=0, scale=1) +``std(%(shapes)s, loc=0, scale=1)`` Standard deviation of the distribution. """ _doc_interval = """\ -interval(alpha, %(shapes)s, loc=0, scale=1) +``interval(alpha, %(shapes)s, loc=0, scale=1)`` Endpoints of the range that contains alpha percent of the distribution """ _doc_allmethods = ''.join([docheaders['methods'], _doc_rvs, _doc_pdf, @@ -151,7 +153,7 @@ _doc_allmethods = ''.join([docheaders['methods'], _doc_rvs, _doc_pdf, # Note that the two lines for %(shapes) are searched for and replaced in # rv_continuous and rv_discrete - update there if the exact string changes -_doc_default_callparams = """\ +_doc_default_callparams = """ Parameters ---------- x : array_like @@ -169,7 +171,8 @@ size : int or tuple of ints, optional moments : str, optional composed of letters ['mvsk'] specifying which moments to compute where 'm' = mean, 'v' = variance, 's' = (Fisher's) skew and - 'k' = (Fisher's) kurtosis. (default='mv') + 'k' = (Fisher's) kurtosis. + Default is 'mv'. """ _doc_default_longsummary = """\ Continuous random variables are defined from a standard form and may @@ -188,27 +191,42 @@ rv = %(name)s(%(shapes)s, loc=0, scale=1) _doc_default_example = """\ Examples -------- ->>> import matplotlib.pyplot as plt >>> from wafo.stats import %(name)s ->>> numargs = %(name)s.numargs ->>> [ %(shapes)s ] = [0.9,] * numargs ->>> rv = %(name)s(%(shapes)s) +>>> import matplotlib.pyplot as plt +>>> fig, ax = plt.subplots(1, 1) -Display frozen pdf +Calculate a few first moments: ->>> x = np.linspace(0, np.minimum(rv.dist.b, 3)) ->>> h = plt.plot(x, rv.pdf(x)) +%(set_vals_stmt)s +>>> mean, var, skew, kurt = %(name)s.stats(%(shapes)s, moments='mvsk') -Here, ``rv.dist.b`` is the right endpoint of the support of ``rv.dist``. +Display the probability density function (``pdf``): -Check accuracy of cdf and ppf +>>> x = np.linspace(%(name)s.ppf(0.01, %(shapes)s), +... %(name)s.ppf(0.99, %(shapes)s), 100) +>>> ax.plot(x, %(name)s.pdf(x, %(shapes)s), +... 'r-', lw=5, alpha=0.6, label='%(name)s pdf') + +Alternatively, freeze the distribution and display the frozen pdf: + +>>> rv = %(name)s(%(shapes)s) +>>> ax.plot(x, rv.pdf(x), 'k-', lw=2, label='frozen pdf') ->>> prb = %(name)s.cdf(x, %(shapes)s) ->>> h = plt.semilogy(np.abs(x - %(name)s.ppf(prb, %(shapes)s)) + 1e-20) +Check accuracy of ``cdf`` and ``ppf``: -Random number generation +>>> vals = %(name)s.ppf([0.001, 0.5, 0.999], %(shapes)s) +>>> np.allclose([0.001, 0.5, 0.999], %(name)s.cdf(vals, %(shapes)s)) +True ->>> R = %(name)s.rvs(%(shapes)s, size=100) +Generate random numbers: + +>>> r = %(name)s.rvs(%(shapes)s, size=1000) + +And compare the histogram: + +>>> ax.hist(r, normed=True, histtype='stepfilled', alpha=0.2) +>>> ax.legend(loc='best', frameon=False) +>>> plt.show() Compare ML and MPS method >>> phat = %(name)s.fit2(R, method='ml'); @@ -301,26 +319,39 @@ docdict_discrete['frozennote'] = _doc_default_frozen_note _doc_default_discrete_example = """\ Examples -------- ->>> from scipy.stats import %(name)s ->>> [ %(shapes)s ] = [] ->>> rv = %(name)s(%(shapes)s) +>>> from wafo.stats import %(name)s +>>> import matplotlib.pyplot as plt +>>> fig, ax = plt.subplots(1, 1) -Display frozen pmf +Calculate a few first moments: ->>> x = np.arange(0, np.minimum(rv.dist.b, 3)) ->>> h = plt.vlines(x, 0, rv.pmf(x), lw=2) +%(set_vals_stmt)s +>>> mean, var, skew, kurt = %(name)s.stats(%(shapes)s, moments='mvsk') -Here, ``rv.dist.b`` is the right endpoint of the support of ``rv.dist``. +Display the probability mass function (``pmf``): -Check accuracy of cdf and ppf +>>> x = np.arange(%(name)s.ppf(0.01, %(shapes)s), +... %(name)s.ppf(0.99, %(shapes)s)) +>>> ax.plot(x, %(name)s.pmf(x, %(shapes)s), 'bo', ms=8, label='%(name)s pmf') +>>> ax.vlines(x, 0, %(name)s.pmf(x, %(shapes)s), colors='b', lw=5, alpha=0.5) ->>> prb = %(name)s.cdf(x, %(shapes)s) ->>> h = plt.semilogy(np.abs(x - %(name)s.ppf(prb, %(shapes)s)) + 1e-20) +Alternatively, freeze the distribution and display the frozen ``pmf``: -Random number generation +>>> rv = %(name)s(%(shapes)s) +>>> ax.vlines(x, 0, rv.pmf(x), colors='k', linestyles='-', lw=1, +... label='frozen pmf') +>>> ax.legend(loc='best', frameon=False) +>>> plt.show() + +Check accuracy of ``cdf`` and ``ppf``: + +>>> prob = %(name)s.cdf(x, %(shapes)s) +>>> np.allclose(x, %(name)s.ppf(prob, %(shapes)s)) +True ->>> R = %(name)s.rvs(%(shapes)s, size=100) +Generate random numbers: +>>> r = %(name)s.rvs(%(shapes)s, size=1000) """ docdict_discrete['example'] = _doc_default_discrete_example @@ -408,6 +439,82 @@ def _kurtosis(data): return m4 / m2**2 - 3 +# Frozen RV class +class rv_frozen_old(object): + + def __init__(self, dist, *args, **kwds): + self.args = args + self.kwds = kwds + + # create a new instance + self.dist = dist.__class__(**dist._ctor_param) + + # a, b may be set in _argcheck, depending on *args, **kwds. Ouch. + shapes, _, _ = self.dist._parse_args(*args, **kwds) + self.dist._argcheck(*shapes) + + def pdf(self, x): # raises AttributeError in frozen discrete distribution + return self.dist.pdf(x, *self.args, **self.kwds) + + def logpdf(self, x): + return self.dist.logpdf(x, *self.args, **self.kwds) + + def cdf(self, x): + return self.dist.cdf(x, *self.args, **self.kwds) + + def logcdf(self, x): + return self.dist.logcdf(x, *self.args, **self.kwds) + + def ppf(self, q): + return self.dist.ppf(q, *self.args, **self.kwds) + + def isf(self, q): + return self.dist.isf(q, *self.args, **self.kwds) + + def rvs(self, size=None): + kwds = self.kwds.copy() + kwds.update({'size': size}) + return self.dist.rvs(*self.args, **kwds) + + def sf(self, x): + return self.dist.sf(x, *self.args, **self.kwds) + + def logsf(self, x): + return self.dist.logsf(x, *self.args, **self.kwds) + + def stats(self, moments='mv'): + kwds = self.kwds.copy() + kwds.update({'moments': moments}) + return self.dist.stats(*self.args, **kwds) + + def median(self): + return self.dist.median(*self.args, **self.kwds) + + def mean(self): + return self.dist.mean(*self.args, **self.kwds) + + def var(self): + return self.dist.var(*self.args, **self.kwds) + + def std(self): + return self.dist.std(*self.args, **self.kwds) + + def moment(self, n): + return self.dist.moment(n, *self.args, **self.kwds) + + def entropy(self): + return self.dist.entropy(*self.args, **self.kwds) + + def pmf(self, k): + return self.dist.pmf(k, *self.args, **self.kwds) + + def logpmf(self, k): + return self.dist.logpmf(k, *self.args, **self.kwds) + + def interval(self, alpha): + return self.dist.interval(alpha, *self.args, **self.kwds) + + # Frozen RV class class rv_frozen(object): ''' Frozen continous or discrete 1D Random Variable object (RV) @@ -452,149 +559,81 @@ class rv_frozen(object): moments : string, optional, keyword one or more of 'm' mean, 'v' variance, 's' skewness, 'k' kurtosis ''' - def __init__(self, dist, *args, **kwds): - self.dist = dist - args, loc, scale = dist._parse_args(*args, **kwds) - if isinstance(dist, rv_continuous): - self.par = args + (loc, scale) - else: # rv_discrete - self.par = args + (loc,) - - def pdf(self, x): - ''' Probability density function at x of the given RV.''' - return self.dist.pdf(x, *self.par) - - def logpdf(self, x): - return self.dist.logpdf(x, *self.par) - - def cdf(self, x): - '''Cumulative distribution function at x of the given RV.''' - return self.dist.cdf(x, *self.par) - - def logcdf(self, x): - return self.dist.logcdf(x, *self.par) - - def ppf(self, q): - '''Percent point function (inverse of cdf) at q of the given RV.''' - return self.dist.ppf(q, *self.par) - - def isf(self, q): - '''Inverse survival function at q of the given RV.''' - return self.dist.isf(q, *self.par) - - def rvs(self, size=None): - '''Random variates of given type.''' - kwds = dict(size=size) - return self.dist.rvs(*self.par, **kwds) - - def sf(self, x): - '''Survival function (1-cdf) at x of the given RV.''' - return self.dist.sf(x, *self.par) - - def logsf(self, x): - return self.dist.logsf(x, *self.par) - - def stats(self, moments='mv'): - ''' Some statistics of the given RV''' - kwds = dict(moments=moments) - return self.dist.stats(*self.par, **kwds) - - def median(self): - return self.dist.median(*self.par) - - def mean(self): - return self.dist.mean(*self.par) - - def var(self): - return self.dist.var(*self.par) - - def std(self): - return self.dist.std(*self.par) - - def moment(self, n): - return self.dist.moment(n, *self.par) - - def entropy(self): - return self.dist.entropy(*self.par) - - def pmf(self, k): - '''Probability mass function at k of the given RV''' - return self.dist.pmf(k, *self.par) - - def logpmf(self, k): - return self.dist.logpmf(k, *self.par) - - def interval(self, alpha): - return self.dist.interval(alpha, *self.par) - - -# Frozen RV class -class rv_frozen_old(object): def __init__(self, dist, *args, **kwds): - self.args = args - self.kwds = kwds self.dist = dist + args, loc, scale = dist._parse_args(*args, **kwds) + if isinstance(dist, rv_continuous): + self.par = args + (loc, scale) + else: # rv_discrete + self.par = args + (loc,) - def pdf(self, x): # raises AttributeError in frozen discrete distribution - return self.dist.pdf(x, *self.args, **self.kwds) + def pdf(self, x): + ''' Probability density function at x of the given RV.''' + return self.dist.pdf(x, *self.par) def logpdf(self, x): - return self.dist.logpdf(x, *self.args, **self.kwds) + return self.dist.logpdf(x, *self.par) def cdf(self, x): - return self.dist.cdf(x, *self.args, **self.kwds) + '''Cumulative distribution function at x of the given RV.''' + return self.dist.cdf(x, *self.par) def logcdf(self, x): - return self.dist.logcdf(x, *self.args, **self.kwds) + return self.dist.logcdf(x, *self.par) def ppf(self, q): - return self.dist.ppf(q, *self.args, **self.kwds) + '''Percent point function (inverse of cdf) at q of the given RV.''' + return self.dist.ppf(q, *self.par) def isf(self, q): - return self.dist.isf(q, *self.args, **self.kwds) + '''Inverse survival function at q of the given RV.''' + return self.dist.isf(q, *self.par) def rvs(self, size=None): - kwds = self.kwds.copy() - kwds.update({'size': size}) - return self.dist.rvs(*self.args, **kwds) + '''Random variates of given type.''' + kwds = dict(size=size) + return self.dist.rvs(*self.par, **kwds) def sf(self, x): - return self.dist.sf(x, *self.args, **self.kwds) + '''Survival function (1-cdf) at x of the given RV.''' + return self.dist.sf(x, *self.par) def logsf(self, x): - return self.dist.logsf(x, *self.args, **self.kwds) + return self.dist.logsf(x, *self.par) def stats(self, moments='mv'): - kwds = self.kwds.copy() - kwds.update({'moments': moments}) - return self.dist.stats(*self.args, **kwds) + ''' Some statistics of the given RV''' + kwds = dict(moments=moments) + return self.dist.stats(*self.par, **kwds) def median(self): - return self.dist.median(*self.args, **self.kwds) + return self.dist.median(*self.par) def mean(self): - return self.dist.mean(*self.args, **self.kwds) + return self.dist.mean(*self.par) def var(self): - return self.dist.var(*self.args, **self.kwds) + return self.dist.var(*self.par) def std(self): - return self.dist.std(*self.args, **self.kwds) + return self.dist.std(*self.par) def moment(self, n): - return self.dist.moment(n, *self.args, **self.kwds) + return self.dist.moment(n, *self.par) def entropy(self): - return self.dist.entropy(*self.args, **self.kwds) + return self.dist.entropy(*self.par) def pmf(self, k): - return self.dist.pmf(k, *self.args, **self.kwds) + '''Probability mass function at k of the given RV''' + return self.dist.pmf(k, *self.par) def logpmf(self, k): - return self.dist.logpmf(k, *self.args, **self.kwds) + return self.dist.logpmf(k, *self.par) def interval(self, alpha): - return self.dist.interval(alpha, *self.args, **self.kwds) + return self.dist.interval(alpha, *self.par) + def valarray(shape, value=nan, typecode=None): @@ -693,9 +732,11 @@ def _ncx2_log_pdf(x, df, nc): fac = -nc/2.0 - x/2.0 + (a-1)*log(x) - a*log(2) - gammaln(a) return fac + np.nan_to_num(log(hyp0f1(a, nc * x/4.0))) + def _ncx2_pdf(x, df, nc): return np.exp(_ncx2_log_pdf(x, df, nc)) + def _ncx2_cdf(x, df, nc): return chndtr(x, df, nc) @@ -713,7 +754,8 @@ class rv_generic(object): self._stats_has_moments = ((sign[2] is not None) or ('moments' in sign[0])) - def _construct_argparser(self, meths_to_inspect, locscale_in, locscale_out): + def _construct_argparser( + self, meths_to_inspect, locscale_in, locscale_out): """Construct the parser for the shape arguments. Generates the argument-parsing functions dynamically and attaches @@ -789,6 +831,36 @@ class rv_generic(object): # allows more general subclassing with *args self.numargs = len(shapes) + def _construct_doc(self, docdict, shapes_vals=None): + """Construct the instance docstring with string substitutions.""" + tempdict = docdict.copy() + tempdict['name'] = self.name or 'distname' + tempdict['shapes'] = self.shapes or '' + + if shapes_vals is None: + shapes_vals = () + vals = ', '.join(str(_) for _ in shapes_vals) + tempdict['vals'] = vals + + if self.shapes: + tempdict['set_vals_stmt'] = '>>> %s = %s' % (self.shapes, vals) + else: + tempdict['set_vals_stmt'] = '' + + if self.shapes is None: + # remove shapes from call parameters if there are none + for item in ['callparams', 'default', 'before_notes']: + tempdict[item] = tempdict[item].replace( + "\n%(shapes)s : array_like\n shape parameters", "") + for i in range(2): + if self.shapes is None: + # necessary because we use %(shapes)s in two forms (w w/o ", ") + self.__doc__ = self.__doc__.replace("%(shapes)s, ", "") + self.__doc__ = doccer.docformat(self.__doc__, tempdict) + + # correct for empty shapes + self.__doc__ = self.__doc__.replace('(, ', '(').replace(', )', ')') + def freeze(self, *args, **kwds): """Freeze the distribution for the given arguments. @@ -1297,68 +1369,67 @@ class rv_continuous(rv_generic): Methods ------- - rvs(, loc=0, scale=1, size=1) + ``rvs(, loc=0, scale=1, size=1)`` random variates - pdf(x, , loc=0, scale=1) + ``pdf(x, , loc=0, scale=1)`` probability density function - logpdf(x, , loc=0, scale=1) + ``logpdf(x, , loc=0, scale=1)`` log of the probability density function - cdf(x, , loc=0, scale=1) + ``cdf(x, , loc=0, scale=1)`` cumulative density function - logcdf(x, , loc=0, scale=1) + ``logcdf(x, , loc=0, scale=1)`` log of the cumulative density function - sf(x, , loc=0, scale=1) + ``sf(x, , loc=0, scale=1)`` survival function (1-cdf --- sometimes more accurate) - logsf(x, , loc=0, scale=1) + ``logsf(x, , loc=0, scale=1)`` log of the survival function - ppf(q, , loc=0, scale=1) + ``ppf(q, , loc=0, scale=1)`` percent point function (inverse of cdf --- quantiles) - isf(q, , loc=0, scale=1) + ``isf(q, , loc=0, scale=1)`` inverse survival function (inverse of sf) - moment(n, , loc=0, scale=1) + ``moment(n, , loc=0, scale=1)`` non-central n-th moment of the distribution. May not work for array arguments. - stats(, loc=0, scale=1, moments='mv') + ``stats(, loc=0, scale=1, moments='mv')`` mean('m'), variance('v'), skew('s'), and/or kurtosis('k') - entropy(, loc=0, scale=1) + ``entropy(, loc=0, scale=1)`` (differential) entropy of the RV. - fit(data, , loc=0, scale=1) + ``fit(data, , loc=0, scale=1)`` Parameter estimates for generic data - expect(func=None, args=(), loc=0, scale=1, lb=None, ub=None, - conditional=False, **kwds) + ``expect(func=None, args=(), loc=0, scale=1, lb=None, ub=None, conditional=False, **kwds)`` Expected value of a function with respect to the distribution. Additional kwd arguments passed to integrate.quad - median(, loc=0, scale=1) + ``median(, loc=0, scale=1)`` Median of the distribution. - mean(, loc=0, scale=1) + ``mean(, loc=0, scale=1)`` Mean of the distribution. - std(, loc=0, scale=1) + ``std(, loc=0, scale=1)`` Standard deviation of the distribution. - var(, loc=0, scale=1) + ``var(, loc=0, scale=1)`` Variance of the distribution. - interval(alpha, , loc=0, scale=1) + ``interval(alpha, , loc=0, scale=1)`` Interval that with `alpha` percent probability contains a random realization of this distribution. - __call__(, loc=0, scale=1) + ``__call__(, loc=0, scale=1)`` Calling a distribution instance creates a frozen RV object with the same methods but holding the given shape, location, and scale fixed. See Notes section. @@ -1469,6 +1540,12 @@ class rv_continuous(rv_generic): super(rv_continuous, self).__init__() + # save the ctor parameters, cf generic freeze + self._ctor_param = dict( + momtype=momtype, a=a, b=b, xtol=xtol, + badvalue=badvalue, name=name, longname=longname, + shapes=shapes, extradoc=extradoc) + if badvalue is None: badvalue = nan if name is None: @@ -1483,11 +1560,7 @@ class rv_continuous(rv_generic): self.b = inf self.xtol = xtol self._size = 1 - self.m = 0.0 self.moment_type = momtype - - self.expandarr = 1 - self.shapes = shapes self._construct_argparser(meths_to_inspect=[self._pdf, self._cdf], locscale_in='loc=0, scale=1', @@ -1497,13 +1570,13 @@ class rv_continuous(rv_generic): self._ppfvec = vectorize(self._ppf_single, otypes='d') self._ppfvec.nin = self.numargs + 1 self.vecentropy = vectorize(self._entropy, otypes='d') - self.vecentropy.nin = self.numargs + 1 self._cdfvec = vectorize(self._cdf_single, otypes='d') self._cdfvec.nin = self.numargs + 1 - # backwards compatibility - self.vecfunc = self._ppfvec - self.veccdf = self._cdfvec + # backwards compat. these were removed in 0.14.0, put back but + # deprecated in 0.14.1: + self.vecfunc = np.deprecate(self._ppfvec, "vecfunc") + self.veccdf = np.deprecate(self._cdfvec, "veccdf") self.extradoc = extradoc if momtype == 0: @@ -1527,7 +1600,8 @@ class rv_continuous(rv_generic): self._construct_default_doc(longname=longname, extradoc=extradoc) else: - self._construct_doc() + dct = dict(distcont) + self._construct_doc(docdict, dct.get(self.name)) def _construct_default_doc(self, longname=None, extradoc=None): """Construct instance docstring from the default template.""" @@ -1540,24 +1614,7 @@ class rv_continuous(rv_generic): self.__doc__ = ''.join(['%s continuous random variable.' % longname, '\n\n%(before_notes)s\n', docheaders['notes'], extradoc, '\n%(example)s']) - self._construct_doc() - - def _construct_doc(self): - """Construct the instance docstring with string substitutions.""" - tempdict = docdict.copy() - tempdict['name'] = self.name or 'distname' - tempdict['shapes'] = self.shapes or '' - - if self.shapes is None: - # remove shapes from call parameters if there are none - for item in ['callparams', 'default', 'before_notes']: - tempdict[item] = tempdict[item].replace( - "\n%(shapes)s : array_like\n shape parameters", "") - for _i in range(2): - if self.shapes is None: - # necessary because we use %(shapes)s in two forms (w w/o ", ") - self.__doc__ = self.__doc__.replace("%(shapes)s, ", "") - self.__doc__ = doccer.docformat(self.__doc__, tempdict) + self._construct_doc(docdict) def _ppf_to_solve(self, x, q, *args): return self.cdf(*(x, )+args)-q @@ -1979,14 +2036,14 @@ class rv_continuous(rv_generic): See also estimation.Profile - ''' + ''' return self._link(x, logSF, theta, i) - def _link(self, x, logSF, theta, i): - msg = ('Link function not implemented for the %s distribution' % - self.name) - raise NotImplementedError(msg) - + def _link(self, x, logSF, theta, i): + msg = ('Link function not implemented for the %s distribution' % + self.name) + raise NotImplementedError(msg) + def _nnlf(self, x, *args): return -sum(self._logpdf(x, *args), axis=0) @@ -2162,7 +2219,7 @@ class rv_continuous(rv_generic): # logDj = log((yU-yL)/(r-1)) for j = i+1,i+2,...i+r-1 # The following is OK when only minimization of T is wanted - i_tie = np.nonzero(tie) + i_tie, = np.nonzero(tie) tiedata = x[i_tie] logD[i_tie + 1] = log(self._pdf(tiedata, *args)) - log(scale) @@ -2265,7 +2322,8 @@ class rv_continuous(rv_generic): restore = None else: if len(fixedn) == len(index): - raise ValueError("All parameters fixed. There is nothing to optimize.") + raise ValueError( + "All parameters fixed. There is nothing to optimize.") def restore(args, theta): # Replace with theta for all numbers not in fixedn @@ -2462,15 +2520,15 @@ class rv_continuous(rv_generic): def _entropy(self, *args): def integ(x): val = self._pdf(x, *args) - return xlogy(val, val) + return -xlogy(val, val) # upper limit is often inf, so suppress warnings when integrating olderr = np.seterr(over='ignore') - entr = -integrate.quad(integ, self.a, self.b)[0] + h = integrate.quad(integ, self.a, self.b)[0] np.seterr(**olderr) - if not np.isnan(entr): - return entr + if not np.isnan(h): + return h else: # try with different limits if integration problems low, upp = self.ppf([1e-10, 1. - 1e-10], *args) @@ -2482,7 +2540,7 @@ class rv_continuous(rv_generic): lower = low else: lower = self.a - return -integrate.quad(integ, lower, upper)[0] + return integrate.quad(integ, lower, upper)[0] def entropy(self, *args, **kwds): """ @@ -2606,12 +2664,12 @@ def _drv_nonzero(self, k, *args): def _drv_moment(self, n, *args): n = asarray(n) - return sum(self.xk**n[newaxis, ...] * self.pk, axis=0) + return sum(self.xk**n[np.newaxis, ...] * self.pk, axis=0) def _drv_moment_gen(self, t, *args): t = asarray(t) - return sum(exp(self.xk * t[newaxis, ...]) * self.pk, axis=0) + return sum(exp(self.xk * t[np.newaxis, ...]) * self.pk, axis=0) def _drv2_moment(self, n, *args): @@ -2684,10 +2742,10 @@ def _drv2_ppfsingle(self, q, *args): # Use basic bisection algorithm if (qb == q): return b if b <= a+1: - # testcase: return wrong number at lower index - # python -c "from scipy.stats import zipf;print zipf.ppf(0.01, 2)" wrong - # python -c "from scipy.stats import zipf;print zipf.ppf([0.01, 0.61, 0.77, 0.83], 2)" - # python -c "from scipy.stats import logser;print logser.ppf([0.1, 0.66, 0.86, 0.93], 0.6)" + # testcase: return wrong number at lower index + # python -c "from scipy.stats import zipf;print zipf.ppf(0.01, 2)" wrong + # python -c "from scipy.stats import zipf;print zipf.ppf([0.01, 0.61, 0.77, 0.83], 2)" + # python -c "from scipy.stats import logser;print logser.ppf([0.1, 0.66, 0.86, 0.93], 0.6)" if qa > q: return a else: @@ -2716,8 +2774,7 @@ def entropy(pk, qk=None, base=None): If only probabilities `pk` are given, the entropy is calculated as ``S = -sum(pk * log(pk), axis=0)``. - If `qk` is not None, then compute a relative entropy (also known as - Kullback-Leibler divergence or Kullback-Leibler distance) + If `qk` is not None, then compute the Kullback-Leibler divergence ``S = sum(pk * log(pk / qk), axis=0)``. This routine will normalize `pk` and `qk` if they don't sum to 1. @@ -2809,65 +2866,64 @@ class rv_discrete(rv_generic): Methods ------- - generic.rvs(, loc=0, size=1) + ``generic.rvs(, loc=0, size=1)`` random variates - generic.pmf(x, , loc=0) + ``generic.pmf(x, , loc=0)`` probability mass function - logpmf(x, , loc=0) + ``logpmf(x, , loc=0)`` log of the probability density function - generic.cdf(x, , loc=0) + ``generic.cdf(x, , loc=0)`` cumulative density function - generic.logcdf(x, , loc=0) + ``generic.logcdf(x, , loc=0)`` log of the cumulative density function - generic.sf(x, , loc=0) + ``generic.sf(x, , loc=0)`` survival function (1-cdf --- sometimes more accurate) - generic.logsf(x, , loc=0, scale=1) + ``generic.logsf(x, , loc=0, scale=1)`` log of the survival function - generic.ppf(q, , loc=0) + ``generic.ppf(q, , loc=0)`` percent point function (inverse of cdf --- percentiles) - generic.isf(q, , loc=0) + ``generic.isf(q, , loc=0)`` inverse survival function (inverse of sf) - generic.moment(n, , loc=0) + ``generic.moment(n, , loc=0)`` non-central n-th moment of the distribution. May not work for array arguments. - generic.stats(, loc=0, moments='mv') + ``generic.stats(, loc=0, moments='mv')`` mean('m', axis=0), variance('v'), skew('s'), and/or kurtosis('k') - generic.entropy(, loc=0) + ``generic.entropy(, loc=0)`` entropy of the RV - generic.expect(func=None, args=(), loc=0, lb=None, ub=None, - conditional=False) + ``generic.expect(func=None, args=(), loc=0, lb=None, ub=None, conditional=False)`` Expected value of a function with respect to the distribution. Additional kwd arguments passed to integrate.quad - generic.median(, loc=0) + ``generic.median(, loc=0)`` Median of the distribution. - generic.mean(, loc=0) + ``generic.mean(, loc=0)`` Mean of the distribution. - generic.std(, loc=0) + ``generic.std(, loc=0)`` Standard deviation of the distribution. - generic.var(, loc=0) + ``generic.var(, loc=0)`` Variance of the distribution. - generic.interval(alpha, , loc=0) + ``generic.interval(alpha, , loc=0)`` Interval that with `alpha` percent probability contains a random realization of this distribution. - generic(, loc=0) + ``generic(, loc=0)`` calling a distribution instance returns a frozen distribution Notes @@ -2881,7 +2937,7 @@ class rv_discrete(rv_generic): To create a new discrete distribution, we would do the following:: class poisson_gen(rv_discrete): - #"Poisson distribution" + # "Poisson distribution" def _pmf(self, k, mu): ... @@ -2911,32 +2967,25 @@ class rv_discrete(rv_generic): Custom made discrete distribution: - >>> import matplotlib.pyplot as plt >>> from scipy import stats >>> xk = np.arange(7) - >>> pk = (0.1, 0.2, 0.3, 0.1, 0.1, 0.1, 0.1) + >>> pk = (0.1, 0.2, 0.3, 0.1, 0.1, 0.0, 0.2) >>> custm = stats.rv_discrete(name='custm', values=(xk, pk)) - >>> h = plt.plot(xk, custm.pmf(xk)) + >>> + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots(1, 1) + >>> ax.plot(xk, custm.pmf(xk), 'ro', ms=12, mec='r') + >>> ax.vlines(xk, 0, custm.pmf(xk), colors='r', lw=4) + >>> plt.show() Random number generation: >>> R = custm.rvs(size=100) - Display frozen pmf: - - >>> numargs = generic.numargs - >>> [ ] = ['Replace with resonable value', ]*numargs - >>> rv = generic() - >>> x = np.arange(0, np.min(rv.dist.b, 3)+1) - >>> h = plt.plot(x, rv.pmf(x)) - - Here, ``rv.dist.b`` is the right endpoint of the support of ``rv.dist``. - Check accuracy of cdf and ppf: - >>> prb = generic.cdf(x, ) - >>> h = plt.semilogy(np.abs(x-generic.ppf(prb, ))+1e-20) - + >>> prb = custm.cdf(x, ) + >>> h = plt.semilogy(np.abs(x-custm.ppf(prb, ))+1e-20) """ def __init__(self, a=0, b=inf, name=None, badvalue=None, @@ -2945,6 +2994,12 @@ class rv_discrete(rv_generic): super(rv_discrete, self).__init__() + # cf generic freeze + self._ctor_param = dict( + a=a, b=b, name=name, badvalue=badvalue, + moment_tol=moment_tol, values=values, inc=inc, + longname=longname, shapes=shapes, extradoc=extradoc) + if badvalue is None: badvalue = nan if name is None: @@ -3001,9 +3056,11 @@ class rv_discrete(rv_generic): _vec_generic_moment.nin = self.numargs + 2 self.generic_moment = instancemethod(_vec_generic_moment, self, rv_discrete) - - # backwards compatibility - self.vec_generic_moment = _vec_generic_moment + # backwards compat. was removed in 0.14.0, put back but + # deprecated in 0.14.1: + self.vec_generic_moment = np.deprecate(_vec_generic_moment, + "vec_generic_moment", + "generic_moment") # correct nin for ppf vectorization _vppf = vectorize(_drv2_ppfsingle, otypes='d') @@ -3028,7 +3085,8 @@ class rv_discrete(rv_generic): self._construct_default_doc(longname=longname, extradoc=extradoc) else: - self._construct_doc() + dct = dict(distdiscrete) + self._construct_doc(docdict_discrete, dct.get(self.name)) #discrete RV do not have the scale parameter, remove it self.__doc__ = self.__doc__.replace( @@ -3044,24 +3102,7 @@ class rv_discrete(rv_generic): self.__doc__ = ''.join(['%s discrete random variable.' % longname, '\n\n%(before_notes)s\n', docheaders['notes'], extradoc, '\n%(example)s']) - self._construct_doc() - - def _construct_doc(self): - """Construct the instance docstring with string substitutions.""" - tempdict = docdict_discrete.copy() - tempdict['name'] = self.name or 'distname' - tempdict['shapes'] = self.shapes or '' - - if self.shapes is None: - # remove shapes from call parameters if there are none - for item in ['callparams', 'default', 'before_notes']: - tempdict[item] = tempdict[item].replace( - "\n%(shapes)s : array_like\n shape parameters", "") - for _i in range(2): - if self.shapes is None: - # necessary because we use %(shapes)s in two forms (w w/o ", ") - self.__doc__ = self.__doc__.replace("%(shapes)s, ", "") - self.__doc__ = doccer.docformat(self.__doc__, tempdict) + self._construct_doc(docdict_discrete) def _nonzero(self, k, *args): return floor(k) == k @@ -3137,7 +3178,7 @@ class rv_discrete(rv_generic): place(output, (1-cond0) + np.isnan(k), self.badvalue) if any(cond): goodargs = argsreduce(cond, *((k,)+args)) - place(output, cond, self._pmf(*goodargs)) + place(output, cond, np.clip(self._pmf(*goodargs), 0, 1)) if output.ndim == 0: return output[()] return output @@ -3213,7 +3254,7 @@ class rv_discrete(rv_generic): if any(cond): goodargs = argsreduce(cond, *((k,)+args)) - place(output, cond, self._cdf(*goodargs)) + place(output, cond, np.clip(self._cdf(*goodargs), 0, 1)) if output.ndim == 0: return output[()] return output @@ -3291,7 +3332,7 @@ class rv_discrete(rv_generic): place(output, cond2, 1.0) if any(cond): goodargs = argsreduce(cond, *((k,)+args)) - place(output, cond, self._sf(*goodargs)) + place(output, cond, np.clip(self._sf(*goodargs), 0, 1)) if output.ndim == 0: return output[()] return output @@ -3382,7 +3423,7 @@ class rv_discrete(rv_generic): def isf(self, q, *args, **kwds): """ - Inverse survival function (1-sf) at q of the given RV. + Inverse survival function (inverse of `sf`) at q of the given RV. Parameters ---------- @@ -3555,3 +3596,36 @@ class rv_discrete(rv_generic): if count > maxcount: warnings.warn('expect(): sum did not converge', RuntimeWarning) return tot/invfac + + +def get_distribution_names(namespace_pairs, rv_base_class): + """ + Collect names of statistical distributions and their generators. + + Parameters + ---------- + namespace_pairs : sequence + A snapshot of (name, value) pairs in the namespace of a module. + rv_base_class : class + The base class of random variable generator classes in a module. + + Returns + ------- + distn_names : list of strings + Names of the statistical distributions. + distn_gen_names : list of strings + Names of the generators of the statistical distributions. + Note that these are not simply the names of the statistical + distributions, with a _gen suffix added. + + """ + distn_names = [] + distn_gen_names = [] + for name, value in namespace_pairs: + if name.startswith('_'): + continue + if name.endswith('_gen') and issubclass(value, rv_base_class): + distn_gen_names.append(name) + if isinstance(value, rv_base_class): + distn_names.append(name) + return distn_names, distn_gen_names diff --git a/pywafo/src/wafo/stats/_distr_params.py b/pywafo/src/wafo/stats/_distr_params.py new file mode 100644 index 0000000..ee19707 --- /dev/null +++ b/pywafo/src/wafo/stats/_distr_params.py @@ -0,0 +1,116 @@ +""" +Sane parameters for stats.distributions. +""" + +distcont = [ + ['alpha', (3.5704770516650459,)], + ['anglit', ()], + ['arcsine', ()], + ['beta', (2.3098496451481823, 0.62687954300963677)], + ['betaprime', (5, 6)], + ['bradford', (0.29891359763170633,)], + ['burr', (10.5, 4.3)], + ['cauchy', ()], + ['chi', (78,)], + ['chi2', (55,)], + ['cosine', ()], + ['dgamma', (1.1023326088288166,)], + ['dweibull', (2.0685080649914673,)], + ['erlang', (10,)], + ['expon', ()], + ['exponpow', (2.697119160358469,)], + ['exponweib', (2.8923945291034436, 1.9505288745913174)], + ['f', (29, 18)], + ['fatiguelife', (29,)], # correction numargs = 1 + ['fisk', (3.0857548622253179,)], + ['foldcauchy', (4.7164673455831894,)], + ['foldnorm', (1.9521253373555869,)], + ['frechet_l', (3.6279911255583239,)], + ['frechet_r', (1.8928171603534227,)], + ['gamma', (1.9932305483800778,)], + ['gausshyper', (13.763771604130699, 3.1189636648681431, + 2.5145980350183019, 5.1811649903971615)], # veryslow + ['genexpon', (9.1325976465418908, 16.231956600590632, 3.2819552690843983)], + ['genextreme', (-0.1,)], + ['gengamma', (4.4162385429431925, 3.1193091679242761)], + ['genhalflogistic', (0.77274727809929322,)], + ['genlogistic', (0.41192440799679475,)], + ['genpareto', (0.1,)], # use case with finite moments + ['gilbrat', ()], + ['gompertz', (0.94743713075105251,)], + ['gumbel_l', ()], + ['gumbel_r', ()], + ['halfcauchy', ()], + ['halflogistic', ()], + ['halfnorm', ()], + ['hypsecant', ()], + ['invgamma', (4.0668996136993067,)], + ['invgauss', (0.14546264555347513,)], + ['invweibull', (10.58,)], + ['johnsonsb', (4.3172675099141058, 3.1837781130785063)], + ['johnsonsu', (2.554395574161155, 2.2482281679651965)], + ['ksone', (1000,)], # replace 22 by 100 to avoid failing range, ticket 956 + ['kstwobign', ()], + ['laplace', ()], + ['levy', ()], + ['levy_l', ()], + ['levy_stable', (0.35667405469844993, + -0.67450531578494011)], # NotImplementedError + # rvs not tested + ['loggamma', (0.41411931826052117,)], + ['logistic', ()], + ['loglaplace', (3.2505926592051435,)], + ['lognorm', (0.95368226960575331,)], + ['lomax', (1.8771398388773268,)], + ['maxwell', ()], + ['mielke', (10.4, 3.6)], + ['nakagami', (4.9673794866666237,)], + ['ncf', (27, 27, 0.41578441799226107)], + ['nct', (14, 0.24045031331198066)], + ['ncx2', (21, 1.0560465975116415)], + ['norm', ()], + ['pareto', (2.621716532144454,)], + ['pearson3', (0.1,)], + ['powerlaw', (1.6591133289905851,)], + ['powerlognorm', (2.1413923530064087, 0.44639540782048337)], + ['powernorm', (4.4453652254590779,)], + ['rayleigh', ()], + ['rdist', (0.9,)], # feels also slow + ['recipinvgauss', (0.63004267809369119,)], + ['reciprocal', (0.0062309367010521255, 1.0062309367010522)], + ['rice', (0.7749725210111873,)], + ['semicircular', ()], + ['t', (2.7433514990818093,)], + ['triang', (0.15785029824528218,)], + ['truncexpon', (4.6907725456810478,)], + ['truncnorm', (-1.0978730080013919, 2.7306754109031979)], + ['truncnorm', (0.1, 2.)], + ['tukeylambda', (3.1321477856738267,)], + ['uniform', ()], + ['vonmises', (3.9939042581071398,)], + ['vonmises_line', (3.9939042581071398,)], + ['wald', ()], + ['weibull_max', (2.8687961709100187,)], + ['weibull_min', (1.7866166930421596,)], + ['wrapcauchy', (0.031071279018614728,)]] + + +distdiscrete = [ + ['bernoulli',(0.3,)], + ['binom', (5, 0.4)], + ['boltzmann',(1.4, 19)], + ['dlaplace', (0.8,)], # 0.5 + ['geom', (0.5,)], + ['hypergeom',(30, 12, 6)], + ['hypergeom',(21,3,12)], # numpy.random (3,18,12) numpy ticket:921 + ['hypergeom',(21,18,11)], # numpy.random (18,3,11) numpy ticket:921 + ['logser', (0.6,)], # reenabled, numpy ticket:921 + ['nbinom', (5, 0.5)], + ['nbinom', (0.4, 0.4)], # from tickets: 583 + ['planck', (0.51,)], # 4.1 + ['poisson', (0.6,)], + ['randint', (7, 31)], + ['skellam', (15, 8)], + ['zipf', (6.5,)] +] + diff --git a/pywafo/src/wafo/stats/_multivariate.py b/pywafo/src/wafo/stats/_multivariate.py index 83f0381..5455a61 100644 --- a/pywafo/src/wafo/stats/_multivariate.py +++ b/pywafo/src/wafo/stats/_multivariate.py @@ -3,13 +3,13 @@ # from __future__ import division, print_function, absolute_import -from scipy.misc import doccer -from functools import wraps import numpy as np import scipy.linalg +from scipy.misc import doccer +from scipy.special import gammaln -__all__ = ['multivariate_normal'] +__all__ = ['multivariate_normal', 'dirichlet'] _LOG_2PI = np.log(2 * np.pi) @@ -53,15 +53,24 @@ def _process_parameters(dim, mean, cov): cov.shape = (1, 1) if mean.ndim != 1 or mean.shape[0] != dim: - raise ValueError("Array 'mean' must be vector of length %d." % dim) + raise ValueError("Array 'mean' must be a vector of length %d." % dim) if cov.ndim == 0: cov = cov * np.eye(dim) elif cov.ndim == 1: cov = np.diag(cov) - else: - if cov.shape != (dim, dim): - raise ValueError("Array 'cov' must be at most two-dimensional," - " but cov.ndim = %d" % cov.ndim) + elif cov.ndim == 2 and cov.shape != (dim, dim): + rows, cols = cov.shape + if rows != cols: + msg = ("Array 'cov' must be square if it is two dimensional," + " but cov.shape = %s." % str(cov.shape)) + else: + msg = ("Dimension mismatch: array 'cov' is of shape %s," + " but 'mean' is a vector of length %d.") + msg = msg % (str(cov.shape), len(mean)) + raise ValueError(msg) + elif cov.ndim > 2: + raise ValueError("Array 'cov' must be at most two-dimensional," + " but cov.ndim = %d" % cov.ndim) return dim, mean, cov @@ -97,6 +106,41 @@ def _squeeze_output(out): return out +def _eigvalsh_to_eps(spectrum, cond=None, rcond=None): + """ + Determine which eigenvalues are "small" given the spectrum. + + This is for compatibility across various linear algebra functions + that should agree about whether or not a Hermitian matrix is numerically + singular and what is its numerical matrix rank. + This is designed to be compatible with scipy.linalg.pinvh. + + Parameters + ---------- + spectrum : 1d ndarray + Array of eigenvalues of a Hermitian matrix. + cond, rcond : float, optional + Cutoff for small eigenvalues. + Singular values smaller than rcond * largest_eigenvalue are + considered zero. + If None or -1, suitable machine precision is used. + + Returns + ------- + eps : float + Magnitude cutoff for numerical negligibility. + + """ + if rcond is not None: + cond = rcond + if cond in [None, -1]: + t = spectrum.dtype.char.lower() + factor = {'f': 1E3, 'd': 1E6} + cond = factor[t] * np.finfo(t).eps + eps = cond * np.max(abs(spectrum)) + return eps + + def _pinv_1d(v, eps=1e-5): """ A helper function for computing the pseudoinverse. @@ -106,7 +150,7 @@ def _pinv_1d(v, eps=1e-5): v : iterable of numbers This may be thought of as a vector of eigenvalues or singular values. eps : float - Elements of v smaller than eps are considered negligible. + Values with magnitude no greater than eps are considered negligible. Returns ------- @@ -114,97 +158,101 @@ def _pinv_1d(v, eps=1e-5): A vector of pseudo-inverted numbers. """ - return np.array([0 if abs(x) < eps else 1/x for x in v], dtype=float) + return np.array([0 if abs(x) <= eps else 1/x for x in v], dtype=float) -def _psd_pinv_decomposed_log_pdet(mat, cond=None, rcond=None, - lower=True, check_finite=True): +class _PSD(object): """ - Compute a decomposition of the pseudo-inverse and the logarithm of - the pseudo-determinant of a symmetric positive semi-definite - matrix. - - The pseudo-determinant of a matrix is defined as the product of - the non-zero eigenvalues, and coincides with the usual determinant - for a full matrix. + Compute coordinated functions of a symmetric positive semidefinite matrix. + + This class addresses two issues. Firstly it allows the pseudoinverse, + the logarithm of the pseudo-determinant, and the rank of the matrix + to be computed using one call to eigh instead of three. + Secondly it allows these functions to be computed in a way + that gives mutually compatible results. + All of the functions are computed with a common understanding as to + which of the eigenvalues are to be considered negligibly small. + The functions are designed to coordinate with scipy.linalg.pinvh() + but not necessarily with np.linalg.det() or with np.linalg.matrix_rank(). Parameters ---------- - mat : array_like - Input array of shape (`m`, `n`) - cond, rcond : float or None - Cutoff for 'small' singular values. - Eigenvalues smaller than ``rcond*largest_eigenvalue`` - are considered zero. + M : 2d array-like + Symmetric positive semidefinite matrix. + cond, rcond : float, optional + Cutoff for small eigenvalues. + Singular values smaller than rcond * largest_eigenvalue are + considered zero. If None or -1, suitable machine precision is used. lower : bool, optional - Whether the pertinent array data is taken from the lower or upper - triangle of `mat`. (Default: lower) - check_finite : boolean, optional - Whether to check that the input matrix contains only finite numbers. - Disabling may give a performance gain, but may result in problems - (crashes, non-termination) if the inputs do contain infinities or NaNs. + Whether the pertinent array data is taken from the lower + or upper triangle of M. (Default: lower) + check_finite : bool, optional + Whether to check that the input matrices contain only finite + numbers. Disabling may give a performance gain, but may result + in problems (crashes, non-termination) if the inputs do contain + infinities or NaNs. + allow_singular : bool, optional + Whether to allow a singular matrix. (Default: True) - Returns - ------- - M : array_like - The pseudo-inverse of the input matrix is np.dot(M, M.T). - log_pdet : float - Logarithm of the pseudo-determinant of the matrix. + Notes + ----- + The arguments are similar to those of scipy.linalg.pinvh(). """ - # Compute the symmetric eigendecomposition. - # The input covariance matrix is required to be real symmetric - # and positive semidefinite which implies that its eigenvalues - # are all real and non-negative, - # but clip them anyway to avoid numerical issues. - - # TODO: the code to set cond/rcond is identical to that in - # scipy.linalg.{pinvh, pinv2} and if/when this function is subsumed - # into scipy.linalg it should probably be shared between all of - # these routines. - - # Note that eigh takes care of array conversion, chkfinite, - # and assertion that the matrix is square. - s, u = scipy.linalg.eigh(mat, lower=lower, check_finite=check_finite) - if rcond is not None: - cond = rcond - if cond in [None, -1]: - t = u.dtype.char.lower() - factor = {'f': 1E3, 'd': 1E6} - cond = factor[t] * np.finfo(t).eps - eps = cond * np.max(abs(s)) - - if np.min(s) < -eps: - raise ValueError('the covariance matrix must be positive semidefinite') - - s_pinv = _pinv_1d(s, eps) - U = np.multiply(u, np.sqrt(s_pinv)) - log_pdet = np.sum(np.log(s[s > eps])) - - return U, log_pdet - - -_doc_default_callparams = \ -"""mean : array_like, optional + def __init__(self, M, cond=None, rcond=None, lower=True, + check_finite=True, allow_singular=True): + # Compute the symmetric eigendecomposition. + # Note that eigh takes care of array conversion, chkfinite, + # and assertion that the matrix is square. + s, u = scipy.linalg.eigh(M, lower=lower, check_finite=check_finite) + + eps = _eigvalsh_to_eps(s, cond, rcond) + if np.min(s) < -eps: + raise ValueError('the input matrix must be positive semidefinite') + d = s[s > eps] + if len(d) < len(s) and not allow_singular: + raise np.linalg.LinAlgError('singular matrix') + s_pinv = _pinv_1d(s, eps) + U = np.multiply(u, np.sqrt(s_pinv)) + + # Initialize the eagerly precomputed attributes. + self.rank = len(d) + self.U = U + self.log_pdet = np.sum(np.log(d)) + + # Initialize an attribute to be lazily computed. + self._pinv = None + + @property + def pinv(self): + if self._pinv is None: + self._pinv = np.dot(self.U, self.U.T) + return self._pinv + + +_doc_default_callparams = """\ +mean : array_like, optional Mean of the distribution (default zero) cov : array_like, optional Covariance matrix of the distribution (default one) +allow_singular : bool, optional + Whether to allow a singular covariance matrix. (Default: False) """ _doc_callparams_note = \ -"""Setting the parameter `mean` to `None` is equivalent to having `mean` -be the zero-vector. The parameter `cov` can be a scalar, in which case -the covariance matrix is the identity times that value, a vector of -diagonal entries for the covariance matrix, or a two-dimensional -array_like. -""" + """Setting the parameter `mean` to `None` is equivalent to having `mean` + be the zero-vector. The parameter `cov` can be a scalar, in which case + the covariance matrix is the identity times that value, a vector of + diagonal entries for the covariance matrix, or a two-dimensional + array_like. + """ _doc_frozen_callparams = "" _doc_frozen_callparams_note = \ -"""See class definition for a detailed description of parameters.""" + """See class definition for a detailed description of parameters.""" docdict_params = { '_doc_default_callparams': _doc_default_callparams, @@ -224,15 +272,13 @@ class multivariate_normal_gen(object): The `mean` keyword specifies the mean. The `cov` keyword specifies the covariance matrix. - .. versionadded:: 0.14.0 - Methods ------- - pdf(x, mean=None, cov=1) + pdf(x, mean=None, cov=1, allow_singular=False) Probability density function. - logpdf(x, mean=None, cov=1) + logpdf(x, mean=None, cov=1, allow_singular=False) Log of the probability density function. - rvs(mean=None, cov=1) + rvs(mean=None, cov=1, allow_singular=False, size=1) Draw random samples from a multivariate normal distribution. entropy() Compute the differential entropy of the multivariate normal. @@ -247,8 +293,8 @@ class multivariate_normal_gen(object): and covariance parameters, returning a "frozen" multivariate normal random variable: - rv = multivariate_normal(mean=None, scale=1) - - Frozen object with the same methods but holding the given + rv = multivariate_normal(mean=None, cov=1, allow_singular=False) + - Frozen object with the same methods but holding the given mean and covariance fixed. Notes @@ -269,8 +315,11 @@ class multivariate_normal_gen(object): where :math:`\mu` is the mean, :math:`\Sigma` the covariance matrix, and :math:`k` is the dimension of the space where :math:`x` takes values. + .. versionadded:: 0.14.0 + Examples -------- + >>> import matplotlib.pyplot as plt >>> from scipy.stats import multivariate_normal >>> x = np.linspace(0, 5, 10, endpoint=False) >>> y = multivariate_normal.pdf(x, mean=2.5, cov=0.5); y @@ -294,16 +343,17 @@ class multivariate_normal_gen(object): def __init__(self): self.__doc__ = doccer.docformat(self.__doc__, docdict_params) - def __call__(self, mean=None, cov=1): + def __call__(self, mean=None, cov=1, allow_singular=False): """ Create a frozen multivariate normal distribution. See `multivariate_normal_frozen` for more information. """ - return multivariate_normal_frozen(mean, cov) + return multivariate_normal_frozen(mean, cov, + allow_singular=allow_singular) - def _logpdf(self, x, mean, prec_U, log_det_cov): + def _logpdf(self, x, mean, prec_U, log_det_cov, rank): """ Parameters ---------- @@ -317,6 +367,8 @@ class multivariate_normal_gen(object): is the precision matrix, i.e. inverse of the covariance matrix. log_det_cov : float Logarithm of the determinant of the covariance matrix + rank : int + Rank of the covariance matrix. Notes ----- @@ -324,12 +376,11 @@ class multivariate_normal_gen(object): called directly; use 'logpdf' instead. """ - dim = x.shape[-1] dev = x - mean maha = np.sum(np.square(np.dot(dev, prec_U)), axis=-1) - return -0.5 * (dim * _LOG_2PI + log_det_cov + maha) + return -0.5 * (rank * _LOG_2PI + log_det_cov + maha) - def logpdf(self, x, mean, cov): + def logpdf(self, x, mean, cov, allow_singular=False): """ Log of the multivariate normal probability density function. @@ -351,11 +402,11 @@ class multivariate_normal_gen(object): """ dim, mean, cov = _process_parameters(None, mean, cov) x = _process_quantiles(x, dim) - prec_U, log_det_cov = _psd_pinv_decomposed_log_pdet(cov) - out = self._logpdf(x, mean, prec_U, log_det_cov) + psd = _PSD(cov, allow_singular=allow_singular) + out = self._logpdf(x, mean, psd.U, psd.log_pdet, psd.rank) return _squeeze_output(out) - def pdf(self, x, mean, cov): + def pdf(self, x, mean, cov, allow_singular=False): """ Multivariate normal probability density function. @@ -377,8 +428,8 @@ class multivariate_normal_gen(object): """ dim, mean, cov = _process_parameters(None, mean, cov) x = _process_quantiles(x, dim) - prec_U, log_det_cov = _psd_pinv_decomposed_log_pdet(cov) - out = np.exp(self._logpdf(x, mean, prec_U, log_det_cov)) + psd = _PSD(cov, allow_singular=allow_singular) + out = np.exp(self._logpdf(x, mean, psd.U, psd.log_pdet, psd.rank)) return _squeeze_output(out) def rvs(self, mean=None, cov=1, size=1): @@ -425,13 +476,14 @@ class multivariate_normal_gen(object): """ dim, mean, cov = _process_parameters(None, mean, cov) - return 1/2 * np.log(np.linalg.det(2 * np.pi * np.e * cov)) + return 0.5 * np.log(np.linalg.det(2 * np.pi * np.e * cov)) + multivariate_normal = multivariate_normal_gen() class multivariate_normal_frozen(object): - def __init__(self, mean=None, cov=1): + def __init__(self, mean=None, cov=1, allow_singular=False): """ Create a frozen multivariate normal distribution. @@ -441,6 +493,9 @@ class multivariate_normal_frozen(object): Mean of the distribution (default zero) cov : array_like, optional Covariance matrix of the distribution (default one) + allow_singular : bool, optional + If this flag is True then tolerate a singular + covariance matrix (default False). Examples -------- @@ -456,13 +511,13 @@ class multivariate_normal_frozen(object): """ self.dim, self.mean, self.cov = _process_parameters(None, mean, cov) - self.prec_U, self._log_det_cov = _psd_pinv_decomposed_log_pdet(self.cov) - + self.cov_info = _PSD(self.cov, allow_singular=allow_singular) self._mnorm = multivariate_normal_gen() def logpdf(self, x): x = _process_quantiles(x, self.dim) - out = self._mnorm._logpdf(x, self.mean, self.prec_U, self._log_det_cov) + out = self._mnorm._logpdf(x, self.mean, self.cov_info.U, + self.cov_info.log_pdet, self.cov_info.rank) return _squeeze_output(out) def pdf(self, x): @@ -481,7 +536,9 @@ class multivariate_normal_frozen(object): Entropy of the multivariate normal distribution """ - return 1/2 * (self.dim * (_LOG_2PI + 1) + self._log_det_cov) + log_pdet = self.cov_info.log_pdet + rank = self.cov_info.rank + return 0.5 * (rank * (_LOG_2PI + 1) + log_pdet) # Set frozen generator docstrings from corresponding docstrings in @@ -491,3 +548,337 @@ for name in ['logpdf', 'pdf', 'rvs']: method_frozen = multivariate_normal_frozen.__dict__[name] method_frozen.__doc__ = doccer.docformat(method.__doc__, docdict_noparams) method.__doc__ = doccer.docformat(method.__doc__, docdict_params) + +_dirichlet_doc_default_callparams = """\ +alpha : array_like + The concentration parameters. The number of entries determines the + dimensionality of the distribution. +""" +_dirichlet_doc_frozen_callparams = "" + +_dirichlet_doc_frozen_callparams_note = \ + """See class definition for a detailed description of parameters.""" + +dirichlet_docdict_params = { + '_dirichlet_doc_default_callparams': _dirichlet_doc_default_callparams, +} + +dirichlet_docdict_noparams = { + '_dirichlet_doc_default_callparams': _dirichlet_doc_frozen_callparams, +} + + +def _dirichlet_check_parameters(alpha): + alpha = np.asarray(alpha) + if np.min(alpha) <= 0: + raise ValueError("All parameters must be greater than 0") + elif alpha.ndim != 1: + raise ValueError("Parameter vector 'a' must be one dimensional, " + + "but a.shape = %s." % str(alpha.shape)) + return alpha + + +def _dirichlet_check_input(alpha, x): + x = np.asarray(x) + + if x.shape[0] + 1 != alpha.shape[0] and x.shape[0] != alpha.shape[0]: + raise ValueError("Vector 'x' must have one entry less then the" + + " parameter vector 'a', but alpha.shape = " + + "%s and " % alpha.shape + + "x.shape = %s." % x.shape) + + if x.shape[0] != alpha.shape[0]: + xk = np.array([1 - np.sum(x, 0)]) + if xk.ndim == 1: + x = np.append(x, xk) + elif xk.ndim == 2: + x = np.vstack((x, xk)) + else: + raise ValueError("The input must be one dimensional or a two " + "dimensional matrix containing the entries.") + + if np.min(x) < 0: + raise ValueError("Each entry in 'x' must be greater or equal zero.") + + if np.max(x) > 1: + raise ValueError("Each entry in 'x' must be smaller or equal one.") + + if (np.abs(np.sum(x, 0) - 1.0) > 10e-10).any(): + raise ValueError("The input vector 'x' must lie within the normal " + + "simplex. but sum(x)=%f." % np.sum(x, 0)) + + return x + + +def _lnB(alpha): + r""" + Internal helper function to compute the log of the useful quotient + + .. math:: + B(\alpha) = \frac{\prod_{i=1}{K}\Gamma(\alpha_i)}{\Gamma\left(\sum_{i=1}^{K}\alpha_i\right)} + + Parameters + ---------- + %(_dirichlet_doc_default_callparams)s + + Returns + ------- + B : scalar + Helper quotient, internal use only + + """ + return np.sum(gammaln(alpha)) - gammaln(np.sum(alpha)) + + +class dirichlet_gen(object): + r""" + A Dirichlet random variable. + + The `alpha` keyword specifies the concentration parameters of the + distribution. + + .. versionadded:: 0.15.0 + + Methods + ------- + pdf(x, alpha) + Probability density function. + logpdf(x, alpha) + Log of the probability density function. + rvs(alpha, size=1) + Draw random samples from a Dirichlet distribution. + mean(alpha) + The mean of the Dirichlet distribution + var(alpha) + The variance of the Dirichlet distribution + entropy(alpha) + Compute the differential entropy of the multivariate normal. + + Parameters + ---------- + x : array_like + Quantiles, with the last axis of `x` denoting the components. + %(_dirichlet_doc_default_callparams)s + + Alternatively, the object may be called (as a function) to fix + concentration parameters, returning a "frozen" Dirichlet + random variable: + + rv = dirichlet(alpha) + - Frozen object with the same methods but holding the given + concentration parameters fixed. + + Notes + ----- + Each :math:`\alpha` entry must be positive. The distribution has only + support on the simplex defined by + + .. math:: + \sum_{i=1}^{K} x_i \le 1 + + + The probability density function for `dirichlet` is + + .. math:: + + f(x) = \frac{1}{\mathrm{B}(\boldsymbol\alpha)} \prod_{i=1}^K x_i^{\alpha_i - 1} + + where + + .. math:: + \mathrm{B}(\boldsymbol\alpha) = \frac{\prod_{i=1}^K \Gamma(\alpha_i)}{\Gamma\bigl(\sum_{i=1}^K \alpha_i\bigr)} + + and :math:`\boldsymbol\alpha=(\alpha_1,\ldots,\alpha_K)`, the + concentration parameters and :math:`K` is the dimension of the space + where :math:`x` takes values. + + """ + + def __init__(self): + self.__doc__ = doccer.docformat(self.__doc__, dirichlet_docdict_params) + + def __call__(self, alpha): + return dirichlet_frozen(alpha) + + def _logpdf(self, x, alpha): + """ + Parameters + ---------- + x : ndarray + Points at which to evaluate the log of the probability + density function + %(_dirichlet_doc_default_callparams)s + + Notes + ----- + As this function does no argument checking, it should not be + called directly; use 'logpdf' instead. + + """ + lnB = _lnB(alpha) + return - lnB + np.sum((np.log(x.T) * (alpha - 1)).T, 0) + + def logpdf(self, x, alpha): + """ + Log of the Dirichlet probability density function. + + Parameters + ---------- + x : array_like + Quantiles, with the last axis of `x` denoting the components. + %(_dirichlet_doc_default_callparams)s + + Returns + ------- + pdf : ndarray + Log of the probability density function evaluated at `x` + """ + alpha = _dirichlet_check_parameters(alpha) + x = _dirichlet_check_input(alpha, x) + + out = self._logpdf(x, alpha) + return _squeeze_output(out) + + def pdf(self, x, alpha): + """ + The Dirichlet probability density function. + + Parameters + ---------- + x : array_like + Quantiles, with the last axis of `x` denoting the components. + %(_dirichlet_doc_default_callparams)s + + Returns + ------- + pdf : ndarray + The probability density function evaluated at `x` + """ + alpha = _dirichlet_check_parameters(alpha) + x = _dirichlet_check_input(alpha, x) + + out = np.exp(self._logpdf(x, alpha)) + return _squeeze_output(out) + + def mean(self, alpha): + """ + Compute the mean of the dirichlet distribution. + + Parameters + ---------- + %(_dirichlet_doc_default_callparams)s + + Returns + ------- + mu : scalar + Mean of the Dirichlet distribution + + """ + alpha = _dirichlet_check_parameters(alpha) + + out = alpha / (np.sum(alpha)) + return _squeeze_output(out) + + def var(self, alpha): + """ + Compute the variance of the dirichlet distribution. + + Parameters + ---------- + %(_dirichlet_doc_default_callparams)s + + Returns + ------- + v : scalar + Variance of the Dirichlet distribution + + """ + + alpha = _dirichlet_check_parameters(alpha) + + alpha0 = np.sum(alpha) + out = (alpha * (alpha0 - alpha)) / ((alpha0 * alpha0) * (alpha0 + 1)) + return out + + def entropy(self, alpha): + """ + Compute the differential entropy of the dirichlet distribution. + + Parameters + ---------- + %(_dirichlet_doc_default_callparams)s + + Returns + ------- + h : scalar + Entropy of the Dirichlet distribution + + """ + + alpha = _dirichlet_check_parameters(alpha) + + alpha0 = np.sum(alpha) + lnB = _lnB(alpha) + K = alpha.shape[0] + + out = lnB + (alpha0 - K) * scipy.special.psi(alpha0) - np.sum( + (alpha - 1) * scipy.special.psi(alpha)) + return _squeeze_output(out) + + def rvs(self, alpha, size=1): + """ + Draw random samples from a Dirichlet distribution. + + Parameters + ---------- + %(_dirichlet_doc_default_callparams)s + size : integer, optional + Number of samples to draw (default 1). + + + Returns + ------- + rvs : ndarray or scalar + Random variates of size (`size`, `N`), where `N` is the + dimension of the random variable. + + """ + alpha = _dirichlet_check_parameters(alpha) + return np.random.dirichlet(alpha, size=size) + + +dirichlet = dirichlet_gen() + + +class dirichlet_frozen(object): + def __init__(self, alpha): + self.alpha = _dirichlet_check_parameters(alpha) + self._dirichlet = dirichlet_gen() + + def logpdf(self, x): + return self._dirichlet.logpdf(x, self.alpha) + + def pdf(self, x): + return self._dirichlet.pdf(x, self.alpha) + + def mean(self): + return self._dirichlet.mean(self.alpha) + + def var(self): + return self._dirichlet.var(self.alpha) + + def entropy(self): + return self._dirichlet.entropy(self.alpha) + + def rvs(self, size=1): + return self._dirichlet.rvs(self.alpha, size) + + +# Set frozen generator docstrings from corresponding docstrings in +# multivariate_normal_gen and fill in default strings in class docstrings +for name in ['logpdf', 'pdf', 'rvs', 'mean', 'var', 'entropy']: + method = dirichlet_gen.__dict__[name] + method_frozen = dirichlet_frozen.__dict__[name] + method_frozen.__doc__ = doccer.docformat( + method.__doc__, dirichlet_docdict_noparams) + method.__doc__ = doccer.docformat(method.__doc__, dirichlet_docdict_params) diff --git a/pywafo/src/wafo/stats/_numpy_compat.py b/pywafo/src/wafo/stats/_numpy_compat.py new file mode 100644 index 0000000..071d2eb --- /dev/null +++ b/pywafo/src/wafo/stats/_numpy_compat.py @@ -0,0 +1,54 @@ +"""Functions copypasted from newer versions of numpy. + +""" +from __future__ import division, print_function, absolute_import + +import warnings + +import numpy as np + +from scipy.lib._version import NumpyVersion + +if NumpyVersion(np.__version__) > '1.7.0.dev': + _assert_warns = np.testing.assert_warns +else: + def _assert_warns(warning_class, func, *args, **kw): + r""" + Fail unless the given callable throws the specified warning. + + This definition is copypasted from numpy 1.9.0.dev. + The version in earlier numpy returns None. + + Parameters + ---------- + warning_class : class + The class defining the warning that `func` is expected to throw. + func : callable + The callable to test. + *args : Arguments + Arguments passed to `func`. + **kwargs : Kwargs + Keyword arguments passed to `func`. + + Returns + ------- + The value returned by `func`. + + """ + with warnings.catch_warnings(record=True) as l: + warnings.simplefilter('always') + result = func(*args, **kw) + if not len(l) > 0: + raise AssertionError("No warning raised when calling %s" + % func.__name__) + if not l[0].category is warning_class: + raise AssertionError("First warning for %s is not a " + "%s( is %s)" % (func.__name__, warning_class, l[0])) + return result + + +if NumpyVersion(np.__version__) >= '1.6.0': + count_nonzero = np.count_nonzero +else: + def count_nonzero(a): + return (a != 0).sum() diff --git a/pywafo/src/wafo/stats/contingency.py b/pywafo/src/wafo/stats/contingency.py index 226a5b1..c67306c 100644 --- a/pywafo/src/wafo/stats/contingency.py +++ b/pywafo/src/wafo/stats/contingency.py @@ -246,9 +246,9 @@ def chi2_contingency(observed, correction=True, lambda_=None): if np.any(expected == 0): # Include one of the positions where expected is zero in # the exception message. - zeropos = list(np.where(expected == 0)[0]) + zeropos = list(zip(*np.where(expected == 0)))[0] raise ValueError("The internally computed table of expected " - "frequencies has a zero element at %s." % zeropos) + "frequencies has a zero element at %s." % (zeropos,)) # The degrees of freedom dof = expected.size - sum(expected.shape) + expected.ndim - 1 diff --git a/pywafo/src/wafo/stats/core.py b/pywafo/src/wafo/stats/core.py index 86a325a..4830954 100644 --- a/pywafo/src/wafo/stats/core.py +++ b/pywafo/src/wafo/stats/core.py @@ -1,6 +1,6 @@ from __future__ import division import warnings -from wafo.wafodata import PlotData +from wafo.containers import PlotData from wafo.misc import findextrema from scipy import special import numpy as np diff --git a/pywafo/src/wafo/stats/distributions.py b/pywafo/src/wafo/stats/distributions.py index 7e76ca9..ecf8942 100644 --- a/pywafo/src/wafo/stats/distributions.py +++ b/pywafo/src/wafo/stats/distributions.py @@ -7,7 +7,19 @@ # from __future__ import division, print_function, absolute_import -from ._distn_infrastructure import entropy, rv_discrete, rv_continuous +from ._distn_infrastructure import (entropy, rv_discrete, rv_continuous, + rv_frozen) + +from . import _continuous_distns +from . import _discrete_distns from ._continuous_distns import * from ._discrete_distns import * + +# For backwards compatibility e.g. pymc expects distributions.__all__. +__all__ = ['entropy', 'rv_discrete', 'rv_continuous'] + +# Add only the distribution names, not the *_gen names. +__all__ += _continuous_distns._distn_names +__all__ += _discrete_distns._distn_names + diff --git a/pywafo/src/wafo/stats/estimation.py b/pywafo/src/wafo/stats/estimation.py index 808856c..4c825d9 100644 --- a/pywafo/src/wafo/stats/estimation.py +++ b/pywafo/src/wafo/stats/estimation.py @@ -7,10 +7,11 @@ Distributions Author: Per A. Brodtkorb 2008 ''' -from __future__ import division +from __future__ import division, absolute_import import warnings -from wafo.plotbackend import plotbackend -from wafo.misc import ecross, findcross + +from ..plotbackend import plotbackend +from ..misc import ecross, findcross import numdifftools # @UnresolvedImport @@ -27,12 +28,10 @@ from numpy import ( from numpy import flatnonzero as nonzero -__all__ = [ - 'Profile', 'FitDistribution' -] +__all__ = ['Profile', 'FitDistribution'] floatinfo = np.finfo(float) -# arr = atleast_1d + arr = asarray all = alltrue # @ReservedAssignment @@ -77,7 +76,8 @@ class rv_frozen(object): def __init__(self, dist, *args, **kwds): self.dist = dist args, loc, scale = dist._parse_args(*args, **kwds) - if len(args) == dist.numargs - 2: # isinstance(dist, rv_continuous): + if len(args) == dist.numargs - 2: # + # if isinstance(dist, rv_continuous): self.par = args + (loc, scale) else: # rv_discrete self.par = args + (loc,) @@ -152,7 +152,7 @@ class Profile(object): with ML or MPS estimated distribution parameters. **kwds : named arguments with keys i : scalar integer - defining which distribution parameter to keep fixed in the + defining which distribution parameter to keep fixed in the profiling process (default first non-fixed parameter) pmin, pmax : real scalars Interval for either the parameter, phat(i), prb, or x, used in the @@ -283,27 +283,25 @@ class Profile(object): self._par = phatv.copy() # Set up variable to profile and _local_link function - self.profile_x = not self.x == None - self.profile_logSF = not (self.logSF == None or self.profile_x) + self.profile_x = self.x is not None + self.profile_logSF = not (self.logSF is None or self.profile_x) self.profile_par = not (self.profile_x or self.profile_logSF) - if self.link == None: + if self.link is None: self.link = self.fit_dist.dist.link if self.profile_par: - self._local_link = lambda fix_par, par: fix_par + self._local_link = self._par_link self.xlabel = 'phat(%d)' % self.i_fixed p_opt = self._par[self.i_fixed] elif self.profile_x: self.logSF = fit_dist.logsf(self.x) - self._local_link = lambda fix_par, par: self.link( - fix_par, self.logSF, par, self.i_fixed) + self._local_link = self._x_link self.xlabel = 'x' p_opt = self.x elif self.profile_logSF: p_opt = self.logSF self.x = fit_dist.isf(exp(p_opt)) - self._local_link = lambda fix_par, par: self.link( - self.x, fix_par, par, self.i_fixed) + self._local_link = self._logSF_link self.xlabel = 'log(SF)' else: raise ValueError( @@ -315,6 +313,15 @@ class Profile(object): phatfree = phatv[self.i_free].copy() self._set_profile(phatfree, p_opt) + def _par_link(self, fix_par, par): + return fix_par + + def _x_link(self, fix_par, par): + return self.link(fix_par, self.logSF, par, self.i_fixed) + + def _logSF_link(self, fix_par, par): + return self.link(self.x, fix_par, par, self.i_fixed) + def _correct_Lmax(self, Lmax): if Lmax > self.Lmax: # foundNewphat = True warnings.warn( @@ -386,7 +393,7 @@ class Profile(object): ''' linspace = numpy.linspace - if self.pmin == None or self.pmax == None: + if self.pmin is None or self.pmax is None: pvar = self._get_variance() @@ -395,12 +402,12 @@ class Profile(object): p_crit = (-norm_ppf(self.alpha / 2.0) * sqrt(numpy.ravel(pvar)) * 1.5) - if self.pmin == None: + if self.pmin is None: self.pmin = self._search_pmin(phatfree0, p_opt - 5.0 * p_crit, p_opt) p_crit_low = (p_opt - self.pmin) / 5 - if self.pmax == None: + if self.pmax is None: self.pmax = self._search_pmax(phatfree0, p_opt + 5.0 * p_crit, p_opt) p_crit_up = (self.pmax - p_opt) / 5 @@ -526,66 +533,20 @@ class Profile(object): ''' if axis is None: axis = plotbackend.gca() - + p_ci = self.get_bounds(self.alpha) axis.plot( self.args, self.data, self.args[[0, -1]], [self.Lmax, ] * 2, 'r--', self.args[[0, -1]], [self.alpha_cross_level, ] * 2, 'r--') axis.vlines(p_ci, ymin=axis.get_ylim()[0], - ymax=self.Lmax, #self.alpha_cross_level, - color='r', linestyles='--') + ymax=self.Lmax, # self.alpha_cross_level, + color='r', linestyles='--') axis.set_title(self.title) axis.set_ylabel(self.ylabel) axis.set_xlabel(self.xlabel) -def _discretize_adaptive(fun, a, b, tol=0.005, n=5): - ''' - Automatic discretization of function, adaptive gridding. - ''' - tiny = floatinfo.tiny - n += (np.mod(n, 2) == 0) # make sure n is odd - x = np.linspace(a, b, n) - fx = fun(x) - - n2 = (n - 1) / 2 - erri = np.hstack((np.zeros((n2, 1)), np.ones((n2, 1)))).ravel() - err = erri.max() - err0 = np.inf - # while (err != err0 and err > tol and n < nmax): - for j in range(50): - if err != err0 and np.any(erri > tol): - err0 = err - # find top errors - - I, = np.where(erri > tol) - # double the sample rate in intervals with the most error - y = (np.vstack(((x[I] + x[I - 1]) / 2, - (x[I + 1] + x[I]) / 2)).T).ravel() - fy = fun(y) - - fy0 = np.interp(y, x, fx) - erri = 0.5 * (abs((fy0 - fy) / (abs(fy0 + fy) + tiny))) - - err = erri.max() - - x = np.hstack((x, y)) - - I = x.argsort() - x = x[I] - erri = np.hstack((zeros(len(fx)), erri))[I] - fx = np.hstack((fx, fy))[I] - - else: - break - else: - warnings.warn('Recursion level limit reached j=%d' % j) - - return x, fx -# class to fit given distribution to data - - class FitDistribution(rv_frozen): ''' @@ -846,7 +807,7 @@ class FitDistribution(rv_frozen): optimizer = kwds.get('optimizer', optimize.fmin) # convert string to function in scipy.optimize if (not callable(optimizer) and - isinstance(optimizer, (str, unicode))): + isinstance(optimizer, (str, unicode))): if not optimizer.startswith('fmin_'): optimizer = "fmin_" + optimizer if optimizer == 'fmin_': @@ -867,7 +828,7 @@ class FitDistribution(rv_frozen): def _compute_cov(self): '''Compute covariance ''' - somefixed = (self.par_fix != None) and any(isfinite(self.par_fix)) + somefixed = (self.par_fix is not None) and any(isfinite(self.par_fix)) # H1 = numpy.asmatrix(self.dist.hessian_nnlf(self.par, self.data)) H = numpy.asmatrix(self.dist.hessian_nlogps(self.par, self.data)) self.H = H @@ -1000,7 +961,7 @@ class FitDistribution(rv_frozen): self.plotresprb() fixstr = '' - if not self.par_fix == None: + if self.par_fix is not None: numfix = len(self.i_fixed) if numfix > 0: format0 = ', '.join(['%d'] * numfix) @@ -1160,7 +1121,7 @@ class FitDistribution(rv_frozen): n = len(x) np1 = n + 1 - if unknown_numpar == None: + if unknown_numpar is None: k = len(theta) else: k = unknown_numpar @@ -1184,7 +1145,7 @@ def test_doctstrings(): def test1(): import wafo.stats as ws dist = ws.weibull_min - #dist = ws.bradford + # dist = ws.bradford R = dist.rvs(0.3, size=1000) phat = FitDistribution(dist, R, method='ml') diff --git a/pywafo/src/wafo/stats/kde.py b/pywafo/src/wafo/stats/kde.py index 74a7b0d..c6cf197 100644 --- a/pywafo/src/wafo/stats/kde.py +++ b/pywafo/src/wafo/stats/kde.py @@ -93,6 +93,10 @@ class gaussian_kde(object): high_bounds. kde.integrate_kde(other_kde) : float Integrate two kernel density estimates multiplied together. + kde.pdf(points) : ndarray + Alias for ``kde.evaluate(points)``. + kde.logpdf(points) : ndarray + Equivalent to ``np.log(kde.evaluate(points))``. kde.resample(size=None) : ndarray Randomly sample a dataset from the estimated pdf. kde.set_bandwidth(bw_method='scott') : None @@ -106,7 +110,6 @@ class gaussian_kde(object): to provide a different method, or set it through a call to `kde.set_bandwidth`. - Notes ----- Bandwidth selection strongly influences the estimate obtained from the KDE @@ -122,7 +125,7 @@ class gaussian_kde(object): with ``n`` the number of data points and ``d`` the number of dimensions. Silverman's Rule [2]_, implemented as `silverman_factor`, is:: - n * (d + 2) / 4.)**(-1. / (d + 4)). + (n * (d + 2) / 4.)**(-1. / (d + 4)). Good general descriptions of kernel density estimation can be found in [1]_ and [2]_, the mathematics for this multi-dimensional implementation can be @@ -388,11 +391,12 @@ class gaussian_kde(object): large = other sum_cov = small.covariance + large.covariance + sum_cov_chol = linalg.cho_factor(sum_cov) result = 0.0 for i in range(small.n): mean = small.dataset[:, i, newaxis] diff = large.dataset - mean - tdiff = dot(linalg.inv(sum_cov), diff) + tdiff = linalg.cho_solve(sum_cov_chol, diff) energies = sum(diff * tdiff, axis=0) / 2.0 result += sum(exp(-energies), axis=0) @@ -511,3 +515,27 @@ class gaussian_kde(object): self.covariance = self._data_covariance * self.factor**2 self.inv_cov = self._data_inv_cov / self.factor**2 self._norm_factor = sqrt(linalg.det(2*pi*self.covariance)) * self.n + + def pdf(self, x): + """ + Evaluate the estimated pdf on a provided set of points. + + Notes + ----- + This is an alias for `gaussian_kde.evaluate`. See the ``evaluate`` + docstring for more details. + + """ + return self.evaluate(x) + + def logpdf(self, x): + """ + Evaluate the log of the estimated pdf on a provided set of points. + + Notes + ----- + See `gaussian_kde.evaluate` for more details; this method simply + returns ``np.log(gaussian_kde.evaluate(x))``. + + """ + return np.log(self.evaluate(x)) diff --git a/pywafo/src/wafo/stats/kde_test.py b/pywafo/src/wafo/stats/kde_example.py similarity index 100% rename from pywafo/src/wafo/stats/kde_test.py rename to pywafo/src/wafo/stats/kde_example.py diff --git a/pywafo/src/wafo/stats/morestats.py b/pywafo/src/wafo/stats/morestats.py index aa63ea6..0724a05 100644 --- a/pywafo/src/wafo/stats/morestats.py +++ b/pywafo/src/wafo/stats/morestats.py @@ -8,29 +8,29 @@ import math import warnings import numpy as np -from numpy import (isscalar, r_, log, sum, around, unique, asarray, zeros, - arange, sort, amin, amax, any, atleast_1d, sqrt, ceil, - floor, array, poly1d, compress, not_equal, pi, exp, ravel, - angle) +from numpy import (isscalar, r_, log, sum, around, unique, asarray, + zeros, arange, sort, amin, amax, any, atleast_1d, sqrt, ceil, + floor, array, poly1d, compress, not_equal, pi, exp, ravel, angle) from numpy.testing.decorators import setastest from scipy.lib.six import string_types +from ._numpy_compat import count_nonzero from scipy import optimize from scipy import special -from wafo.stats import statlib -from wafo.stats import stats -from wafo.stats.stats import find_repeats -from wafo.stats import distributions -from wafo.stats._distn_infrastructure import rv_generic +from . import statlib +from . import stats +from .stats import find_repeats +from .contingency import chi2_contingency +from . import distributions +from ._distn_infrastructure import rv_generic __all__ = ['mvsdist', - 'bayes_mvs', 'kstat', 'kstatvar', 'probplot', 'ppcc_max', - 'ppcc_plot', + 'bayes_mvs', 'kstat', 'kstatvar', 'probplot', 'ppcc_max', 'ppcc_plot', 'boxcox_llf', 'boxcox', 'boxcox_normmax', 'boxcox_normplot', 'shapiro', 'anderson', 'ansari', 'bartlett', 'levene', 'binom_test', - 'fligner', 'mood', 'wilcoxon', - 'pdf_fromgamma', 'circmean', 'circvar', 'circstd', + 'fligner', 'mood', 'wilcoxon', 'median_test', + 'pdf_fromgamma', 'circmean', 'circvar', 'circstd', 'anderson_ksamp' ] @@ -80,8 +80,7 @@ def bayes_mvs(data, alpha=0.90): """ res = mvsdist(data) if alpha >= 1 or alpha <= 0: - raise ValueError( - "0 < alpha < 1 is required, but alpha=%s was given." % alpha) + raise ValueError("0 < alpha < 1 is required, but alpha=%s was given." % alpha) return tuple((x.mean(), x.interval(alpha)) for x in res) @@ -137,21 +136,20 @@ def mvsdist(data): xbar = x.mean() C = x.var() if (n > 1000): # gaussian approximations for large n - mdist = distributions.norm(loc=xbar, scale=math.sqrt(C / n)) - sdist = distributions.norm( - loc=math.sqrt(C), scale=math.sqrt(C / (2. * n))) - vdist = distributions.norm(loc=C, scale=math.sqrt(2.0 / n) * C) + mdist = distributions.norm(loc=xbar, scale=math.sqrt(C/n)) + sdist = distributions.norm(loc=math.sqrt(C), scale=math.sqrt(C/(2.*n))) + vdist = distributions.norm(loc=C, scale=math.sqrt(2.0/n)*C) else: - nm1 = n - 1 - fac = n * C / 2. - val = nm1 / 2. - mdist = distributions.t(nm1, loc=xbar, scale=math.sqrt(C / nm1)) - sdist = distributions.gengamma(val, -2, scale=math.sqrt(fac)) - vdist = distributions.invgamma(val, scale=fac) + nm1 = n-1 + fac = n*C/2. + val = nm1/2. + mdist = distributions.t(nm1,loc=xbar,scale=math.sqrt(C/nm1)) + sdist = distributions.gengamma(val,-2,scale=math.sqrt(fac)) + vdist = distributions.invgamma(val,scale=fac) return mdist, vdist, sdist -def kstat(data, n=2): +def kstat(data,n=2): """ Return the nth k-statistic (1<=n<=4 so far). @@ -201,26 +199,26 @@ def kstat(data, n=2): if n > 4 or n < 1: raise ValueError("k-statistics only supported for 1<=n<=4") n = int(n) - S = zeros(n + 1, 'd') + S = zeros(n+1,'d') data = ravel(data) N = len(data) - for k in range(1, n + 1): - S[k] = sum(data ** k, axis=0) + for k in range(1,n+1): + S[k] = sum(data**k,axis=0) if n == 1: - return S[1] * 1.0 / N + return S[1]*1.0/N elif n == 2: - return (N * S[2] - S[1] ** 2.0) / (N * (N - 1.0)) + return (N*S[2]-S[1]**2.0)/(N*(N-1.0)) elif n == 3: - return (2 * S[1] ** 3 - 3 * N * S[1] * S[2] + N * N * S[3]) / (N * (N - 1.0) * (N - 2.0)) + return (2*S[1]**3 - 3*N*S[1]*S[2]+N*N*S[3]) / (N*(N-1.0)*(N-2.0)) elif n == 4: - return (-6 * S[1] ** 4 + 12 * N * S[1] ** 2 * S[2] - 3 * N * (N - 1.0) * S[2] ** 2 - - 4 * N * (N + 1) * S[1] * S[3] + N * N * (N + 1) * S[4]) / \ - (N * (N - 1.0) * (N - 2.0) * (N - 3.0)) + return (-6*S[1]**4 + 12*N*S[1]**2 * S[2] - 3*N*(N-1.0)*S[2]**2 - + 4*N*(N+1)*S[1]*S[3] + N*N*(N+1)*S[4]) / \ + (N*(N-1.0)*(N-2.0)*(N-3.0)) else: raise ValueError("Should not be here.") -def kstatvar(data, n=2): +def kstatvar(data,n=2): """ Returns an unbiased estimator of the variance of the k-statistic. @@ -246,11 +244,11 @@ def kstatvar(data, n=2): data = ravel(data) N = len(data) if n == 1: - return kstat(data, n=2) * 1.0 / N + return kstat(data,n=2)*1.0/N elif n == 2: - k2 = kstat(data, n=2) - k4 = kstat(data, n=4) - return (2 * k2 * k2 * N + (N - 1) * k4) / (N * (N + 1)) + k2 = kstat(data,n=2) + k4 = kstat(data,n=4) + return (2*k2*k2*N + (N-1)*k4)/(N*(N+1)) else: raise ValueError("Only n=1 or n=2 supported.") @@ -259,7 +257,7 @@ def _calc_uniform_order_statistic_medians(x): """See Notes section of `probplot` for details.""" N = len(x) osm_uniform = np.zeros(N, dtype=np.float64) - osm_uniform[-1] = 0.5 ** (1.0 / N) + osm_uniform[-1] = 0.5**(1.0 / N) osm_uniform[0] = 1 - osm_uniform[-1] i = np.arange(2, N) osm_uniform[1:-1] = (i - 0.3175) / (N + 0.365) @@ -291,7 +289,7 @@ def _parse_dist_kw(dist, enforce_subclass=True): raise ValueError("%s is not a valid distribution name" % dist) elif enforce_subclass: msg = ("`dist` should be a stats.distributions instance or a string " - "with the name of such a distribution.") + "with the name of such a distribution.") raise ValueError(msg) return dist @@ -422,10 +420,10 @@ def probplot(x, sparams=(), dist='norm', fit=True, plot=None): osr = sort(x) if fit or (plot is not None): # perform a linear fit. - slope, intercept, r, _prob, _sterrest = stats.linregress(osm, osr) + slope, intercept, r, prob, sterrest = stats.linregress(osm, osr) if plot is not None: - plot.plot(osm, osr, 'bo', osm, slope * osm + intercept, 'r-') + plot.plot(osm, osr, 'bo', osm, slope*osm + intercept, 'r-') try: if hasattr(plot, 'set_title'): # Matplotlib Axes instance or something that looks like it @@ -457,7 +455,7 @@ def probplot(x, sparams=(), dist='norm', fit=True, plot=None): return osm, osr -def ppcc_max(x, brack=(0.0, 1.0), dist='tukeylambda'): +def ppcc_max(x, brack=(0.0,1.0), dist='tukeylambda'): """Returns the shape parameter that maximizes the probability plot correlation coefficient for the given data to a one-parameter family of distributions. @@ -474,14 +472,13 @@ def ppcc_max(x, brack=(0.0, 1.0), dist='tukeylambda'): # correlation def tempfunc(shape, mi, yvals, func): xvals = func(mi, shape) - r, _prob = stats.pearsonr(xvals, yvals) - return 1 - r + r, prob = stats.pearsonr(xvals, yvals) + return 1-r - return optimize.brent(tempfunc, brack=brack, - args=(osm_uniform, osr, dist.ppf)) + return optimize.brent(tempfunc, brack=brack, args=(osm_uniform, osr, dist.ppf)) -def ppcc_plot(x, a, b, dist='tukeylambda', plot=None, N=80): +def ppcc_plot(x,a,b,dist='tukeylambda', plot=None, N=80): """Returns (shape, ppcc), and optionally plots shape vs. ppcc (probability plot correlation coefficient) as a function of shape parameter for a one-parameter family of distributions from shape @@ -490,10 +487,10 @@ def ppcc_plot(x, a, b, dist='tukeylambda', plot=None, N=80): See also ppcc_max """ svals = r_[a:b:complex(N)] - ppcc = svals * 0.0 + ppcc = svals*0.0 k = 0 for sval in svals: - _r1, r2 = probplot(x, sval, dist=dist, fit=1) + r1,r2 = probplot(x,sval,dist=dist,fit=1) ppcc[k] = r2[-1] k += 1 if plot is not None: @@ -591,7 +588,7 @@ def boxcox_llf(lmb, data): y = boxcox(data, lmb) y_mean = np.mean(y, axis=0) llf = (lmb - 1) * np.sum(np.log(data), axis=0) - llf -= N / 2.0 * np.log(np.sum((y - y_mean) ** 2. / N, axis=0)) + llf -= N / 2.0 * np.log(np.sum((y - y_mean)**2. / N, axis=0)) return llf @@ -724,7 +721,7 @@ def boxcox(x, lmbda=None, alpha=None): raise ValueError("Data must be positive.") if lmbda is not None: # single transformation - return special.boxcox(x, lmbda) # @UndefinedVariable + return special.boxcox(x, lmbda) # If lmbda=None, find the lmbda that maximizes the log-likelihood function. lmax = boxcox_normmax(x, method='mle') @@ -815,7 +812,7 @@ def boxcox_normmax(x, brack=(-2.0, 2.0), method='pearsonr'): # correlation. y = boxcox(samps, lmbda) yvals = np.sort(y) - r, _prob = stats.pearsonr(xvals, yvals) + r, prob = stats.pearsonr(xvals, yvals) return 1 - r return optimize.brent(_eval_pearsonr, brack=brack, args=(xvals, x)) @@ -836,7 +833,7 @@ def boxcox_normmax(x, brack=(-2.0, 2.0), method='pearsonr'): methods = {'pearsonr': _pearsonr, 'mle': _mle, 'all': _all} - if not method in methods.keys(): + if method not in methods.keys(): raise ValueError("Method %s not recognized." % method) optimfunc = methods[method] @@ -986,15 +983,15 @@ def shapiro(x, a=None, reta=False): if N < 3: raise ValueError("Data must be at least length 3.") if a is None: - a = zeros(N, 'f') + a = zeros(N,'f') init = 0 else: - if len(a) != N // 2: + if len(a) != N//2: raise ValueError("len(a) must equal len(x)/2") init = 1 y = sort(x) - a, w, pw, ifault = statlib.swilk(y, a[:N // 2], init) - if not ifault in [0, 2]: + a, w, pw, ifault = statlib.swilk(y, a[:N//2], init) + if ifault not in [0,2]: warnings.warn(str(ifault)) if N > 5000: warnings.warn("p-value may not be accurate for N > 5000.") @@ -1017,12 +1014,12 @@ _Avals_gumbel = array([0.474, 0.637, 0.757, 0.877, 1.038]) _Avals_logistic = array([0.426, 0.563, 0.660, 0.769, 0.906, 1.010]) -def anderson(x, dist='norm'): +def anderson(x,dist='norm'): """ Anderson-Darling test for data coming from a particular distribution The Anderson-Darling test is a modification of the Kolmogorov- - Smirnov test kstest_ for the null hypothesis that a sample is + Smirnov test `kstest` for the null hypothesis that a sample is drawn from a population that follows a particular distribution. For the Anderson-Darling test, the critical values depend on which distribution is being tested against. This function works @@ -1083,60 +1080,283 @@ def anderson(x, dist='norm'): pp. 591-595. """ - if not dist in ['norm', 'expon', 'gumbel', 'extreme1', 'logistic']: + if dist not in ['norm','expon','gumbel','extreme1','logistic']: raise ValueError("Invalid distribution; dist must be 'norm', " - "'expon', 'gumbel', 'extreme1' or 'logistic'.") + "'expon', 'gumbel', 'extreme1' or 'logistic'.") y = sort(x) xbar = np.mean(x, axis=0) N = len(y) if dist == 'norm': s = np.std(x, ddof=1, axis=0) - w = (y - xbar) / s + w = (y-xbar)/s z = distributions.norm.cdf(w) - sig = array([15, 10, 5, 2.5, 1]) - critical = around(_Avals_norm / (1.0 + 4.0 / N - 25.0 / N / N), 3) + sig = array([15,10,5,2.5,1]) + critical = around(_Avals_norm / (1.0 + 4.0/N - 25.0/N/N),3) elif dist == 'expon': w = y / xbar z = distributions.expon.cdf(w) - sig = array([15, 10, 5, 2.5, 1]) - critical = around(_Avals_expon / (1.0 + 0.6 / N), 3) + sig = array([15,10,5,2.5,1]) + critical = around(_Avals_expon / (1.0 + 0.6/N),3) elif dist == 'logistic': - def rootfunc(ab, xj, N): - a, b = ab - tmp = (xj - a) / b + def rootfunc(ab,xj,N): + a,b = ab + tmp = (xj-a)/b tmp2 = exp(tmp) - val = [sum(1.0 / (1 + tmp2), axis=0) - 0.5 * N, - sum(tmp * (1.0 - tmp2) / (1 + tmp2), axis=0) + N] + val = [sum(1.0/(1+tmp2),axis=0)-0.5*N, + sum(tmp*(1.0-tmp2)/(1+tmp2),axis=0)+N] return array(val) - sol0 = array([xbar, np.std(x, ddof=1, axis=0)]) - sol = optimize.fsolve(rootfunc, sol0, args=(x, N), xtol=1e-5) - w = (y - sol[0]) / sol[1] + sol0 = array([xbar,np.std(x, ddof=1, axis=0)]) + sol = optimize.fsolve(rootfunc,sol0,args=(x,N),xtol=1e-5) + w = (y-sol[0])/sol[1] z = distributions.logistic.cdf(w) - sig = array([25, 10, 5, 2.5, 1, 0.5]) - critical = around(_Avals_logistic / (1.0 + 0.25 / N), 3) + sig = array([25,10,5,2.5,1,0.5]) + critical = around(_Avals_logistic / (1.0+0.25/N),3) else: # (dist == 'gumbel') or (dist == 'extreme1'): # the following is incorrect, see ticket:1097 -# def fixedsolve(th,xj,N): -## val = stats.sum(xj)*1.0/N -## tmp = exp(-xj/th) -## term = sum(xj*tmp,axis=0) -## term /= sum(tmp,axis=0) -# return val - term -## s = optimize.fixed_point(fixedsolve, 1.0, args=(x,N),xtol=1e-5) -## xbar = -s*log(sum(exp(-x/s),axis=0)*1.0/N) + #def fixedsolve(th,xj,N): + # val = stats.sum(xj)*1.0/N + # tmp = exp(-xj/th) + # term = sum(xj*tmp,axis=0) + # term /= sum(tmp,axis=0) + # return val - term + #s = optimize.fixed_point(fixedsolve, 1.0, args=(x,N),xtol=1e-5) + #xbar = -s*log(sum(exp(-x/s),axis=0)*1.0/N) xbar, s = distributions.gumbel_l.fit(x) - w = (y - xbar) / s + w = (y-xbar)/s z = distributions.gumbel_l.cdf(w) - sig = array([25, 10, 5, 2.5, 1]) - critical = around(_Avals_gumbel / (1.0 + 0.2 / sqrt(N)), 3) + sig = array([25,10,5,2.5,1]) + critical = around(_Avals_gumbel / (1.0 + 0.2/sqrt(N)),3) - i = arange(1, N + 1) - S = sum((2 * i - 1.0) / N * (log(z) + log(1 - z[::-1])), axis=0) - A2 = -N - S + i = arange(1,N+1) + S = sum((2*i-1.0)/N*(log(z)+log(1-z[::-1])),axis=0) + A2 = -N-S return A2, critical, sig -def ansari(x, y): +def _anderson_ksamp_midrank(samples, Z, Zstar, k, n, N): + """ + Compute A2akN equation 7 of Scholz and Stephens. + + Parameters + ---------- + samples : sequence of 1-D array_like + Array of sample arrays. + Z : array_like + Sorted array of all observations. + Zstar : array_like + Sorted array of unique observations. + k : int + Number of samples. + n : array_like + Number of observations in each sample. + N : int + Total number of observations. + + Returns + ------- + A2aKN : float + The A2aKN statistics of Scholz and Stephens 1987. + """ + + A2akN = 0. + Z_ssorted_left = Z.searchsorted(Zstar, 'left') + if N == Zstar.size: + lj = 1. + else: + lj = Z.searchsorted(Zstar, 'right') - Z_ssorted_left + Bj = Z_ssorted_left + lj / 2. + for i in arange(0, k): + s = np.sort(samples[i]) + s_ssorted_right = s.searchsorted(Zstar, side='right') + Mij = s_ssorted_right.astype(np.float) + fij = s_ssorted_right - s.searchsorted(Zstar, 'left') + Mij -= fij / 2. + inner = lj / float(N) * (N * Mij - Bj * n[i])**2 / \ + (Bj * (N - Bj) - N * lj / 4.) + A2akN += inner.sum() / n[i] + A2akN *= (N - 1.) / N + return A2akN + + +def _anderson_ksamp_right(samples, Z, Zstar, k, n, N): + """ + Compute A2akN equation 6 of Scholz & Stephens. + + Parameters + ---------- + samples : sequence of 1-D array_like + Array of sample arrays. + Z : array_like + Sorted array of all observations. + Zstar : array_like + Sorted array of unique observations. + k : int + Number of samples. + n : array_like + Number of observations in each sample. + N : int + Total number of observations. + + Returns + ------- + A2KN : float + The A2KN statistics of Scholz and Stephens 1987. + """ + + A2kN = 0. + lj = Z.searchsorted(Zstar[:-1], 'right') - Z.searchsorted(Zstar[:-1], + 'left') + Bj = lj.cumsum() + for i in arange(0, k): + s = np.sort(samples[i]) + Mij = s.searchsorted(Zstar[:-1], side='right') + inner = lj / float(N) * (N * Mij - Bj * n[i])**2 / (Bj * (N - Bj)) + A2kN += inner.sum() / n[i] + return A2kN + + +def anderson_ksamp(samples, midrank=True): + """The Anderson-Darling test for k-samples. + + The k-sample Anderson-Darling test is a modification of the + one-sample Anderson-Darling test. It tests the null hypothesis + that k-samples are drawn from the same population without having + to specify the distribution function of that population. The + critical values depend on the number of samples. + + Parameters + ---------- + samples : sequence of 1-D array_like + Array of sample data in arrays. + midrank : bool, optional + Type of Anderson-Darling test which is computed. Default + (True) is the midrank test applicable to continuous and + discrete populations. If False, the right side empirical + distribution is used. + + Returns + ------- + A2 : float + Normalized k-sample Anderson-Darling test statistic. + critical : array + The critical values for significance levels 25%, 10%, 5%, 2.5%, 1%. + p : float + An approximate significance level at which the null hypothesis for the + provided samples can be rejected. + + Raises + ------ + ValueError + If less than 2 samples are provided, a sample is empty, or no + distinct observations are in the samples. + + See Also + -------- + ks_2samp : 2 sample Kolmogorov-Smirnov test + anderson : 1 sample Anderson-Darling test + + Notes + ----- + [1]_ Defines three versions of the k-sample Anderson-Darling test: + one for continuous distributions and two for discrete + distributions, in which ties between samples may occur. The + default of this routine is to compute the version based on the + midrank empirical distribution function. This test is applicable + to continuous and discrete data. If midrank is set to False, the + right side empirical distribution is used for a test for discrete + data. According to [1]_, the two discrete test statistics differ + only slightly if a few collisions due to round-off errors occur in + the test not adjusted for ties between samples. + + .. versionadded:: 0.14.0 + + References + ---------- + .. [1] Scholz, F. W and Stephens, M. A. (1987), K-Sample + Anderson-Darling Tests, Journal of the American Statistical + Association, Vol. 82, pp. 918-924. + + Examples + -------- + >>> from scipy import stats + >>> np.random.seed(314159) + + The null hypothesis that the two random samples come from the same + distribution can be rejected at the 5% level because the returned + test value is greater than the critical value for 5% (1.961) but + not at the 2.5% level. The interpolation gives an approximate + significance level of 3.1%: + + >>> stats.anderson_ksamp([np.random.normal(size=50), + ... np.random.normal(loc=0.5, size=30)]) + (2.4615796189876105, + array([ 0.325, 1.226, 1.961, 2.718, 3.752]), + 0.03134990135800783) + + + The null hypothesis cannot be rejected for three samples from an + identical distribution. The approximate p-value (87%) has to be + computed by extrapolation and may not be very accurate: + + >>> stats.anderson_ksamp([np.random.normal(size=50), + ... np.random.normal(size=30), np.random.normal(size=20)]) + (-0.73091722665244196, + array([ 0.44925884, 1.3052767 , 1.9434184 , 2.57696569, 3.41634856]), + 0.8789283903979661) + + """ + k = len(samples) + if (k < 2): + raise ValueError("anderson_ksamp needs at least two samples") + + samples = list(map(np.asarray, samples)) + Z = np.sort(np.hstack(samples)) + N = Z.size + Zstar = np.unique(Z) + if Zstar.size < 2: + raise ValueError("anderson_ksamp needs more than one distinct " + "observation") + + n = np.array([sample.size for sample in samples]) + if any(n == 0): + raise ValueError("anderson_ksamp encountered sample without " + "observations") + + if midrank: + A2kN = _anderson_ksamp_midrank(samples, Z, Zstar, k, n, N) + else: + A2kN = _anderson_ksamp_right(samples, Z, Zstar, k, n, N) + + h = (1. / arange(1, N)).sum() + H = (1. / n).sum() + g = 0 + for l in arange(1, N-1): + inner = np.array([1. / ((N - l) * m) for m in arange(l+1, N)]) + g += inner.sum() + + a = (4*g - 6) * (k - 1) + (10 - 6*g)*H + b = (2*g - 4)*k**2 + 8*h*k + (2*g - 14*h - 4)*H - 8*h + 4*g - 6 + c = (6*h + 2*g - 2)*k**2 + (4*h - 4*g + 6)*k + (2*h - 6)*H + 4*h + d = (2*h + 6)*k**2 - 4*h*k + sigmasq = (a*N**3 + b*N**2 + c*N + d) / ((N - 1.) * (N - 2.) * (N - 3.)) + m = k - 1 + A2 = (A2kN - m) / math.sqrt(sigmasq) + + # The b_i values are the interpolation coefficients from Table 2 + # of Scholz and Stephens 1987 + b0 = np.array([0.675, 1.281, 1.645, 1.96, 2.326]) + b1 = np.array([-0.245, 0.25, 0.678, 1.149, 1.822]) + b2 = np.array([-0.105, -0.305, -0.362, -0.391, -0.396]) + critical = b0 + b1 / math.sqrt(m) + b2 / m + pf = np.polyfit(critical, log(np.array([0.25, 0.1, 0.05, 0.025, 0.01])), 2) + if A2 < critical.min() or A2 > critical.max(): + warnings.warn("approximate p-value will be computed by extrapolation") + + p = math.exp(np.polyval(pf, A2)) + return A2, critical, p + + +def ansari(x,y): """ Perform the Ansari-Bradley test for equal scale parameters @@ -1173,58 +1393,56 @@ def ansari(x, y): methods. 3rd ed. Chapman and Hall/CRC. 2001. Section 5.8.2. """ - x, y = asarray(x), asarray(y) + x,y = asarray(x),asarray(y) n = len(x) m = len(y) if m < 1: raise ValueError("Not enough other observations.") if n < 1: raise ValueError("Not enough test observations.") - N = m + n - xy = r_[x, y] # combine + N = m+n + xy = r_[x,y] # combine rank = stats.rankdata(xy) - symrank = amin(array((rank, N - rank + 1)), 0) - AB = sum(symrank[:n], axis=0) + symrank = amin(array((rank,N-rank+1)),0) + AB = sum(symrank[:n],axis=0) uxy = unique(xy) repeats = (len(uxy) != len(xy)) exact = ((m < 55) and (n < 55) and not repeats) if repeats and ((m < 55) or (n < 55)): warnings.warn("Ties preclude use of exact statistic.") if exact: - astart, a1, _ifault = statlib.gscale(n, m) - ind = AB - astart - total = sum(a1, axis=0) - if ind < len(a1) / 2.0: + astart, a1, ifault = statlib.gscale(n,m) + ind = AB-astart + total = sum(a1,axis=0) + if ind < len(a1)/2.0: cind = int(ceil(ind)) if (ind == cind): - pval = 2.0 * sum(a1[:cind + 1], axis=0) / total + pval = 2.0*sum(a1[:cind+1],axis=0)/total else: - pval = 2.0 * sum(a1[:cind], axis=0) / total + pval = 2.0*sum(a1[:cind],axis=0)/total else: find = int(floor(ind)) if (ind == floor(ind)): - pval = 2.0 * sum(a1[find:], axis=0) / total + pval = 2.0*sum(a1[find:],axis=0)/total else: - pval = 2.0 * sum(a1[find + 1:], axis=0) / total - return AB, min(1.0, pval) + pval = 2.0*sum(a1[find+1:],axis=0)/total + return AB, min(1.0,pval) # otherwise compute normal approximation if N % 2: # N odd - mnAB = n * (N + 1.0) ** 2 / 4.0 / N - varAB = n * m * (N + 1.0) * (3 + N ** 2) / (48.0 * N ** 2) + mnAB = n*(N+1.0)**2 / 4.0 / N + varAB = n*m*(N+1.0)*(3+N**2)/(48.0*N**2) else: - mnAB = n * (N + 2.0) / 4.0 - varAB = m * n * (N + 2) * (N - 2.0) / 48 / (N - 1.0) + mnAB = n*(N+2.0)/4.0 + varAB = m*n*(N+2)*(N-2.0)/48/(N-1.0) if repeats: # adjust variance estimates # compute sum(tj * rj**2,axis=0) - fac = sum(symrank ** 2, axis=0) + fac = sum(symrank**2,axis=0) if N % 2: # N odd - varAB = m * n * \ - (16 * N * fac - (N + 1) ** 4) / (16.0 * N ** 2 * (N - 1)) + varAB = m*n*(16*N*fac-(N+1)**4)/(16.0 * N**2 * (N-1)) else: # N even - varAB = m * n * \ - (16 * fac - N * (N + 2) ** 2) / (16.0 * N * (N - 1)) - z = (AB - mnAB) / sqrt(varAB) + varAB = m*n*(16*fac-N*(N+2)**2)/(16.0 * N * (N-1)) + z = (AB - mnAB)/sqrt(varAB) pval = distributions.norm.sf(abs(z)) * 2.0 return AB, pval @@ -1236,7 +1454,7 @@ def bartlett(*args): Bartlett's test tests the null hypothesis that all input samples are from populations with equal variances. For samples from significantly non-normal populations, Levene's test - `levene`_ is more robust. + `levene` is more robust. Parameters ---------- @@ -1262,17 +1480,16 @@ def bartlett(*args): if k < 2: raise ValueError("Must enter at least two input sample vectors.") Ni = zeros(k) - ssq = zeros(k, 'd') + ssq = zeros(k,'d') for j in range(k): Ni[j] = len(args[j]) ssq[j] = np.var(args[j], ddof=1) - Ntot = sum(Ni, axis=0) - spsq = sum((Ni - 1) * ssq, axis=0) / (1.0 * (Ntot - k)) - numer = (Ntot * 1.0 - k) * log(spsq) - sum((Ni - 1.0) * log(ssq), axis=0) - denom = 1.0 + (1.0 / (3 * (k - 1))) * \ - ((sum(1.0 / (Ni - 1.0), axis=0)) - 1.0 / (Ntot - k)) + Ntot = sum(Ni,axis=0) + spsq = sum((Ni-1)*ssq,axis=0)/(1.0*(Ntot-k)) + numer = (Ntot*1.0-k)*log(spsq) - sum((Ni-1.0)*log(ssq),axis=0) + denom = 1.0 + (1.0/(3*(k-1)))*((sum(1.0/(Ni-1.0),axis=0))-1.0/(Ntot-k)) T = numer / denom - pval = distributions.chi2.sf(T, k - 1) # 1 - cdf + pval = distributions.chi2.sf(T,k-1) # 1 - cdf return T, pval @@ -1328,8 +1545,8 @@ def levene(*args, **kwds): proportiontocut = 0.05 for kw, value in kwds.items(): if kw not in ['center', 'proportiontocut']: - raise TypeError( - "levene() got an unexpected keyword argument '%s'" % kw) + raise TypeError("levene() got an unexpected keyword " + "argument '%s'" % kw) if kw == 'center': center = value else: @@ -1341,9 +1558,9 @@ def levene(*args, **kwds): Ni = zeros(k) Yci = zeros(k, 'd') - if not center in ['mean', 'median', 'trimmed']: + if center not in ['mean', 'median', 'trimmed']: raise ValueError("Keyword argument
must be 'mean', 'median'" - + "or 'trimmed'.") + + "or 'trimmed'.") if center == 'median': func = lambda x: np.median(x, axis=0) @@ -1360,28 +1577,28 @@ def levene(*args, **kwds): Ntot = sum(Ni, axis=0) # compute Zij's - Zij = [None] * k + Zij = [None]*k for i in range(k): - Zij[i] = abs(asarray(args[i]) - Yci[i]) + Zij[i] = abs(asarray(args[i])-Yci[i]) # compute Zbari Zbari = zeros(k, 'd') Zbar = 0.0 for i in range(k): Zbari[i] = np.mean(Zij[i], axis=0) - Zbar += Zbari[i] * Ni[i] + Zbar += Zbari[i]*Ni[i] Zbar /= Ntot - numer = (Ntot - k) * sum(Ni * (Zbari - Zbar) ** 2, axis=0) + numer = (Ntot-k) * sum(Ni*(Zbari-Zbar)**2, axis=0) # compute denom_variance dvar = 0.0 for i in range(k): - dvar += sum((Zij[i] - Zbari[i]) ** 2, axis=0) + dvar += sum((Zij[i]-Zbari[i])**2, axis=0) - denom = (k - 1.0) * dvar + denom = (k-1.0)*dvar W = numer / denom - pval = distributions.f.sf(W, k - 1, Ntot - k) # 1 - cdf + pval = distributions.f.sf(W, k-1, Ntot-k) # 1 - cdf return W, pval @@ -1418,7 +1635,7 @@ def binom_test(x, n=None, p=0.5): """ x = atleast_1d(x).astype(np.integer) if len(x) == 2: - n = x[1] + x[0] + n = x[1]+x[0] x = x[0] elif len(x) == 1: x = x[0] @@ -1432,20 +1649,20 @@ def binom_test(x, n=None, p=0.5): raise ValueError("p must be in range [0,1]") d = distributions.binom.pmf(x, n, p) - rerr = 1 + 1e-7 - if (x == p * n): + rerr = 1+1e-7 + if (x == p*n): # special case as shortcut, would also be handled by `else` below pval = 1. - elif (x < p * n): - i = np.arange(np.ceil(p * n), n + 1) - y = np.sum(distributions.binom.pmf(i, n, p) <= d * rerr, axis=0) - pval = distributions.binom.cdf( - x, n, p) + distributions.binom.sf(n - y, n, p) + elif (x < p*n): + i = np.arange(np.ceil(p*n), n+1) + y = np.sum(distributions.binom.pmf(i, n, p) <= d*rerr, axis=0) + pval = (distributions.binom.cdf(x, n, p) + + distributions.binom.sf(n-y, n, p)) else: - i = np.arange(np.floor(p * n) + 1) - y = np.sum(distributions.binom.pmf(i, n, p) <= d * rerr, axis=0) - pval = distributions.binom.cdf( - y - 1, n, p) + distributions.binom.sf(x - 1, n, p) + i = np.arange(np.floor(p*n) + 1) + y = np.sum(distributions.binom.pmf(i, n, p) <= d*rerr, axis=0) + pval = (distributions.binom.cdf(y-1, n, p) + + distributions.binom.sf(x-1, n, p)) return min(1.0, pval) @@ -1456,8 +1673,8 @@ def _apply_func(x, g, func): # func should be applied over the groups g = unique(r_[0, g, len(x)]) output = [] - for k in range(len(g) - 1): - output.append(func(x[g[k]:g[k + 1]])) + for k in range(len(g)-1): + output.append(func(x[g[k]:g[k+1]])) return asarray(output) @@ -1473,11 +1690,10 @@ def fligner(*args, **kwds): Parameters ---------- sample1, sample2, ... : array_like - arrays of sample data. Need not be the same length + Arrays of sample data. Need not be the same length. center : {'mean', 'median', 'trimmed'}, optional - keyword argument controlling which function of the data - is used in computing the test statistic. The default - is 'median'. + Keyword argument controlling which function of the data is used in + computing the test statistic. The default is 'median'. proportiontocut : float, optional When `center` is 'trimmed', this gives the proportion of data points to cut from each end. (See `scipy.stats.trim_mean`.) @@ -1486,15 +1702,15 @@ def fligner(*args, **kwds): Returns ------- Xsq : float - the test statistic + The test statistic. p-value : float - the p-value for the hypothesis test + The p-value for the hypothesis test. Notes ----- - As with Levene's test there are three variants - of Fligner's test that differ by the measure of central - tendency used in the test. See `levene` for more information. + As with Levene's test there are three variants of Fligner's test that + differ by the measure of central tendency used in the test. See `levene` + for more information. References ---------- @@ -1510,8 +1726,8 @@ def fligner(*args, **kwds): proportiontocut = 0.05 for kw, value in kwds.items(): if kw not in ['center', 'proportiontocut']: - raise TypeError( - "fligner() got an unexpected keyword argument '%s'" % kw) + raise TypeError("fligner() got an unexpected keyword " + "argument '%s'" % kw) if kw == 'center': center = value else: @@ -1521,9 +1737,9 @@ def fligner(*args, **kwds): if k < 2: raise ValueError("Must enter at least two input sample vectors.") - if not center in ['mean', 'median', 'trimmed']: + if center not in ['mean','median','trimmed']: raise ValueError("Keyword argument
must be 'mean', 'median'" - + "or 'trimmed'.") + + "or 'trimmed'.") if center == 'median': func = lambda x: np.median(x, axis=0) @@ -1545,13 +1761,13 @@ def fligner(*args, **kwds): g.append(len(allZij)) ranks = stats.rankdata(allZij) - a = distributions.norm.ppf(ranks / (2 * (Ntot + 1.0)) + 0.5) + a = distributions.norm.ppf(ranks/(2*(Ntot + 1.0)) + 0.5) # compute Aibar Aibar = _apply_func(a, g, sum) / Ni anbar = np.mean(a, axis=0) varsq = np.var(a, axis=0, ddof=1) - Xsq = sum(Ni * (asarray(Aibar) - anbar) ** 2.0, axis=0) / varsq + Xsq = sum(Ni*(asarray(Aibar) - anbar)**2.0, axis=0)/varsq pval = distributions.chi2.sf(Xsq, k - 1) # 1 - cdf return Xsq, pval @@ -1578,7 +1794,7 @@ def mood(x, y, axis=0): ------- z : scalar or ndarray The z-score for the hypothesis test. For 1-D inputs a scalar is - returned; + returned. p-value : scalar ndarray The p-value for the hypothesis test. @@ -1631,8 +1847,7 @@ def mood(x, y, axis=0): axis = 0 # Determine shape of the result arrays - res_shape = tuple([x.shape[ax] - for ax in range(len(x.shape)) if ax != axis]) + res_shape = tuple([x.shape[ax] for ax in range(len(x.shape)) if ax != axis]) if not (res_shape == tuple([y.shape[ax] for ax in range(len(y.shape)) if ax != axis])): raise ValueError("Dimensions of x and y on all axes except `axis` " @@ -1732,7 +1947,7 @@ def wilcoxon(x, y=None, zero_method="wilcox", correction=False): """ - if not zero_method in ["wilcox", "pratt", "zsplit"]: + if zero_method not in ["wilcox", "pratt", "zsplit"]: raise ValueError("Zero method should be either 'wilcox' \ or 'pratt' or 'zsplit'") @@ -1742,16 +1957,14 @@ def wilcoxon(x, y=None, zero_method="wilcox", correction=False): x, y = map(asarray, (x, y)) if len(x) != len(y): raise ValueError('Unequal N in wilcoxon. Aborting.') - d = x - y + d = x-y if zero_method == "wilcox": - # Keep all non-zero differences - d = compress(not_equal(d, 0), d, axis=-1) + d = compress(not_equal(d, 0), d, axis=-1) # Keep all non-zero differences count = len(d) if (count < 10): - warnings.warn( - "Warning: sample size too small for normal approximation.") + warnings.warn("Warning: sample size too small for normal approximation.") r = stats.rankdata(abs(d)) r_plus = sum((d > 0) * r, axis=0) r_minus = sum((d < 0) * r, axis=0) @@ -1762,13 +1975,13 @@ def wilcoxon(x, y=None, zero_method="wilcox", correction=False): r_minus += r_zero / 2. T = min(r_plus, r_minus) - mn = count * (count + 1.) * 0.25 - se = count * (count + 1.) * (2. * count + 1.) + mn = count*(count + 1.) * 0.25 + se = count*(count + 1.) * (2. * count + 1.) if zero_method == "pratt": r = r[d != 0] - _replist, repnum = find_repeats(r) + replist, repnum = find_repeats(r) if repnum.size != 0: # Correction for repeated elements. se -= 0.5 * (repnum * (repnum * repnum - 1)).sum() @@ -1780,41 +1993,246 @@ def wilcoxon(x, y=None, zero_method="wilcox", correction=False): return T, prob +@setastest(False) +def median_test(*args, **kwds): + """ + Mood's median test. + + Test that two or more samples come from populations with the same median. + + Let ``n = len(args)`` be the number of samples. The "grand median" of + all the data is computed, and a contingency table is formed by + classifying the values in each sample as being above or below the grand + median. The contingency table, along with `correction` and `lambda_`, + are passed to `scipy.stats.chi2_contingency` to compute the test statistic + and p-value. + + Parameters + ---------- + sample1, sample2, ... : array_like + The set of samples. There must be at least two samples. + Each sample must be a one-dimensional sequence containing at least + one value. The samples are not required to have the same length. + ties : str, optional + Determines how values equal to the grand median are classified in + the contingency table. The string must be one of:: + + "below": + Values equal to the grand median are counted as "below". + "above": + Values equal to the grand median are counted as "above". + "ignore": + Values equal to the grand median are not counted. + + The default is "below". + correction : bool, optional + If True, *and* there are just two samples, apply Yates' correction + for continuity when computing the test statistic associated with + the contingency table. Default is True. + lambda_ : float or str, optional. + By default, the statistic computed in this test is Pearson's + chi-squared statistic. `lambda_` allows a statistic from the + Cressie-Read power divergence family to be used instead. See + `power_divergence` for details. + Default is 1 (Pearson's chi-squared statistic). + + Returns + ------- + stat : float + The test statistic. The statistic that is returned is determined by + `lambda_`. The default is Pearson's chi-squared statistic. + p : float + The p-value of the test. + m : float + The grand median. + table : ndarray + The contingency table. The shape of the table is (2, n), where + n is the number of samples. The first row holds the counts of the + values above the grand median, and the second row holds the counts + of the values below the grand median. The table allows further + analysis with, for example, `scipy.stats.chi2_contingency`, or with + `scipy.stats.fisher_exact` if there are two samples, without having + to recompute the table. + + See Also + -------- + kruskal : Compute the Kruskal-Wallis H-test for independent samples. + mannwhitneyu : Computes the Mann-Whitney rank test on samples x and y. + + Notes + ----- + .. versionadded:: 0.15.0 + + References + ---------- + .. [1] Mood, A. M., Introduction to the Theory of Statistics. McGraw-Hill + (1950), pp. 394-399. + .. [2] Zar, J. H., Biostatistical Analysis, 5th ed. Prentice Hall (2010). + See Sections 8.12 and 10.15. + + Examples + -------- + A biologist runs an experiment in which there are three groups of plants. + Group 1 has 16 plants, group 2 has 15 plants, and group 3 has 17 plants. + Each plant produces a number of seeds. The seed counts for each group + are:: + + Group 1: 10 14 14 18 20 22 24 25 31 31 32 39 43 43 48 49 + Group 2: 28 30 31 33 34 35 36 40 44 55 57 61 91 92 99 + Group 3: 0 3 9 22 23 25 25 33 34 34 40 45 46 48 62 67 84 + + The following code applies Mood's median test to these samples. + + >>> g1 = [10, 14, 14, 18, 20, 22, 24, 25, 31, 31, 32, 39, 43, 43, 48, 49] + >>> g2 = [28, 30, 31, 33, 34, 35, 36, 40, 44, 55, 57, 61, 91, 92, 99] + >>> g3 = [0, 3, 9, 22, 23, 25, 25, 33, 34, 34, 40, 45, 46, 48, 62, 67, 84] + >>> stat, p, med, tbl = median_test(g1, g2, g3) + + The median is + + >>> med + 34.0 + + and the contingency table is + + >>> tbl + array([[ 5, 10, 7], + [11, 5, 10]]) + + `p` is too large to conclude that the medians are not the same: + + >>> p + 0.12609082774093244 + + The "G-test" can be performed by passing ``lambda_="log-likelihood"`` to + `median_test`. + + >>> g, p, med, tbl = median_test(g1, g2, g3, lambda_="log-likelihood") + >>> p + 0.12224779737117837 + + The median occurs several times in the data, so we'll get a different + result if, for example, ``ties="above"`` is used: + + >>> stat, p, med, tbl = median_test(g1, g2, g3, ties="above") + >>> p + 0.063873276069553273 + + >>> tbl + array([[ 5, 11, 9], + [11, 4, 8]]) + + This example demonstrates that if the data set is not large and there + are values equal to the median, the p-value can be sensitive to the + choice of `ties`. + + """ + ties = kwds.pop('ties', 'below') + correction = kwds.pop('correction', True) + lambda_ = kwds.pop('lambda_', None) + + if len(kwds) > 0: + bad_kwd = kwds.keys()[0] + raise TypeError("median_test() got an unexpected keyword " + "argument %r" % bad_kwd) + + if len(args) < 2: + raise ValueError('median_test requires two or more samples.') + + ties_options = ['below', 'above', 'ignore'] + if ties not in ties_options: + raise ValueError("invalid 'ties' option '%s'; 'ties' must be one " + "of: %s" % (ties, str(ties_options)[1:-1])) + + data = [np.asarray(arg) for arg in args] + + # Validate the sizes and shapes of the arguments. + for k, d in enumerate(data): + if d.size == 0: + raise ValueError("Sample %d is empty. All samples must " + "contain at least one value." % (k + 1)) + if d.ndim != 1: + raise ValueError("Sample %d has %d dimensions. All " + "samples must be one-dimensional sequences." % + (k + 1, d.ndim)) + + grand_median = np.median(np.concatenate(data)) + + # Create the contingency table. + table = np.zeros((2, len(data)), dtype=np.int64) + for k, sample in enumerate(data): + nabove = count_nonzero(sample > grand_median) + nbelow = count_nonzero(sample < grand_median) + nequal = sample.size - (nabove + nbelow) + table[0, k] += nabove + table[1, k] += nbelow + if ties == "below": + table[1, k] += nequal + elif ties == "above": + table[0, k] += nequal + + # Check that no row or column of the table is all zero. + # Such a table can not be given to chi2_contingency, because it would have + # a zero in the table of expected frequencies. + rowsums = table.sum(axis=1) + if rowsums[0] == 0: + raise ValueError("All values are below the grand median (%r)." % + grand_median) + if rowsums[1] == 0: + raise ValueError("All values are above the grand median (%r)." % + grand_median) + if ties == "ignore": + # We already checked that each sample has at least one value, but it + # is possible that all those values equal the grand median. If `ties` + # is "ignore", that would result in a column of zeros in `table`. We + # check for that case here. + zero_cols = np.where((table == 0).all(axis=0))[0] + if len(zero_cols) > 0: + msg = ("All values in sample %d are equal to the grand " + "median (%r), so they are ignored, resulting in an " + "empty sample." % (zero_cols[0] + 1, grand_median)) + raise ValueError(msg) + + stat, p, dof, expected = chi2_contingency(table, lambda_=lambda_, + correction=correction) + return stat, p, grand_median, table + + def _hermnorm(N): # return the negatively normalized hermite polynomials up to order N-1 # (inclusive) # using the recursive relationship # p_n+1 = p_n(x)' - x*p_n(x) # and p_0(x) = 1 - plist = [None] * N + plist = [None]*N plist[0] = poly1d(1) - for n in range(1, N): - plist[n] = plist[n - 1].deriv() - poly1d([1, 0]) * plist[n - 1] + for n in range(1,N): + plist[n] = plist[n-1].deriv() - poly1d([1,0])*plist[n-1] return plist def pdf_fromgamma(g1, g2, g3=0.0, g4=None): if g4 is None: - g4 = 3 * g2 * g2 - sigsq = 1.0 / g2 + g4 = 3*g2*g2 + sigsq = 1.0/g2 sig = sqrt(sigsq) - mu = g1 * sig ** 3.0 + mu = g1*sig**3.0 p12 = _hermnorm(13) for k in range(13): - p12[k] = p12[k] / sig ** k + p12[k] = p12[k]/sig**k # Add all of the terms to polynomial - totp = p12[0] - (g1 / 6.0 * p12[3]) + \ - (g2 / 24.0 * p12[4] + g1 * g1 / 72.0 * p12[6]) - \ - (g3 / 120.0 * p12[5] + g1 * g2 / 144.0 * p12[7] + g1 ** 3.0 / 1296.0 * p12[9]) + \ - (g4 / 720 * p12[6] + (g2 * g2 / 1152.0 + g1 * g3 / 720) * p12[8] + - g1 * g1 * g2 / 1728.0 * p12[10] + g1 ** 4.0 / 31104.0 * p12[12]) + totp = p12[0] - (g1/6.0*p12[3]) + \ + (g2/24.0*p12[4] + g1*g1/72.0*p12[6]) - \ + (g3/120.0*p12[5] + g1*g2/144.0*p12[7] + g1**3.0/1296.0*p12[9]) + \ + (g4/720*p12[6] + (g2*g2/1152.0+g1*g3/720)*p12[8] + + g1*g1*g2/1728.0*p12[10] + g1**4.0/31104.0*p12[12]) # Final normalization - totp = totp / sqrt(2 * pi) / sig + totp = totp / sqrt(2*pi)/sig def thefunc(x): - xn = (x - mu) / sig - return totp(xn) * exp(-xn * xn / 2.0) + xn = (x-mu)/sig + return totp(xn)*exp(-xn*xn/2.0) return thefunc @@ -1823,11 +2241,11 @@ def _circfuncs_common(samples, high, low): if samples.size == 0: return np.nan, np.nan - ang = (samples - low) * 2 * pi / (high - low) + ang = (samples - low)*2*pi / (high-low) return samples, ang -def circmean(samples, high=2 * pi, low=0, axis=None): +def circmean(samples, high=2*pi, low=0, axis=None): """ Compute the circular mean for samples in a range. @@ -1850,17 +2268,17 @@ def circmean(samples, high=2 * pi, low=0, axis=None): """ samples, ang = _circfuncs_common(samples, high, low) - res = angle(np.mean(exp(1j * ang), axis=axis)) + res = angle(np.mean(exp(1j*ang), axis=axis)) mask = res < 0 if (mask.ndim > 0): - res[mask] += 2 * pi + res[mask] += 2*pi elif mask: - res = res + 2 * pi + res = res + 2*pi - return res * (high - low) / 2.0 / pi + low + return res*(high-low)/2.0/pi + low -def circvar(samples, high=2 * pi, low=0, axis=None): +def circvar(samples, high=2*pi, low=0, axis=None): """ Compute the circular variance for samples assumed to be in a range @@ -1888,12 +2306,12 @@ def circvar(samples, high=2 * pi, low=0, axis=None): """ samples, ang = _circfuncs_common(samples, high, low) - res = np.mean(exp(1j * ang), axis=axis) + res = np.mean(exp(1j*ang), axis=axis) R = abs(res) - return ((high - low) / 2.0 / pi) ** 2 * 2 * log(1 / R) + return ((high-low)/2.0/pi)**2 * 2 * log(1/R) -def circstd(samples, high=2 * pi, low=0, axis=None): +def circstd(samples, high=2*pi, low=0, axis=None): """ Compute the circular standard deviation for samples assumed to be in the range [low to high]. @@ -1923,13 +2341,13 @@ def circstd(samples, high=2 * pi, low=0, axis=None): """ samples, ang = _circfuncs_common(samples, high, low) - res = np.mean(exp(1j * ang), axis=axis) + res = np.mean(exp(1j*ang), axis=axis) R = abs(res) - return ((high - low) / 2.0 / pi) * sqrt(-2 * log(R)) + return ((high-low)/2.0/pi) * sqrt(-2*log(R)) # Tests to include (from R) -- some of these already in stats. -# +######## # X Ansari-Bradley # X Bartlett (and Levene) # X Binomial diff --git a/pywafo/src/wafo/stats/mstats.py b/pywafo/src/wafo/stats/mstats.py index 790c44d..c5b62cc 100644 --- a/pywafo/src/wafo/stats/mstats.py +++ b/pywafo/src/wafo/stats/mstats.py @@ -24,13 +24,9 @@ is a relatively new package, some API changes are still possible. f_value_wilks_lambda find_repeats friedmanchisquare - gmean - hmean kendalltau kendalltau_seasonal kruskalwallis - kruskalwallis - ks_twosamp ks_twosamp kurtosis kurtosistest @@ -80,3 +76,4 @@ from __future__ import division, print_function, absolute_import from .mstats_basic import * from .mstats_extras import * +from scipy.stats import gmean, hmean diff --git a/pywafo/src/wafo/stats/mstats_basic.py b/pywafo/src/wafo/stats/mstats_basic.py index 8ca98d9..3700064 100644 --- a/pywafo/src/wafo/stats/mstats_basic.py +++ b/pywafo/src/wafo/stats/mstats_basic.py @@ -1,25 +1,21 @@ """ An extension of scipy.stats.stats to support masked arrays -:author: Pierre GF Gerard-Marchant -:contact: pierregm_at_uga_edu """ +# Original author (2007): Pierre GF Gerard-Marchant + # TODO : f_value_wilks_lambda looks botched... what are dfnum & dfden for ? -# TODO : ttest_reel looks botched: what are x1,x2,v1,v2 for ? +# TODO : ttest_rel looks botched: what are x1,x2,v1,v2 for ? # TODO : reimplement ksonesamp from __future__ import division, print_function, absolute_import -__author__ = "Pierre GF Gerard-Marchant" -__docformat__ = "restructuredtext en" __all__ = ['argstoarray', 'betai', 'chisquare','count_tied_groups', 'describe', 'f_oneway','f_value_wilks_lambda','find_repeats','friedmanchisquare', - 'gmean', - 'hmean', 'kendalltau','kendalltau_seasonal','kruskal','kruskalwallis', 'ks_twosamp','ks_2samp','kurtosis','kurtosistest', 'linregress', @@ -42,7 +38,7 @@ __all__ = ['argstoarray', import numpy as np from numpy import ndarray import numpy.ma as ma -from numpy.ma import MaskedArray, masked, nomask +from numpy.ma import masked, nomask from scipy.lib.six import iteritems @@ -52,17 +48,16 @@ import warnings from . import stats from . import distributions import scipy.special as special -import scipy.misc as misc from . import futil genmissingvaldoc = """ -Notes ------ + + Notes + ----- Missing values are considered pair-wise: if a value is missing in x, the corresponding value in y is masked. -""" -#------------------------------------------------------------------------------ + """ def _chk_asarray(a, axis): @@ -94,8 +89,8 @@ def _chk_size(a,b): (na, nb) = (a.size, b.size) if na != nb: raise ValueError("The size of the input array should match!" - " (%s <> %s)" % (na,nb)) - return (a,b,na) + " (%s <> %s)" % (na, nb)) + return (a, b, na) def argstoarray(*args): @@ -118,8 +113,8 @@ def argstoarray(*args): Notes ----- - numpy.ma.row_stack has identical behavior, but is called with a sequence of - sequences. + `numpy.ma.row_stack` has identical behavior, but is called with a sequence + of sequences. """ if len(args) == 1 and not isinstance(args[0], ndarray): @@ -132,25 +127,22 @@ def argstoarray(*args): output = ma.array(np.empty((n,m), dtype=float), mask=True) for (k,v) in enumerate(args): output[k,:len(v)] = v + output[np.logical_not(np.isfinite(output._data))] = masked return output -#####-------------------------------------------------------------------------- -#---- --- Ranking --- -#####-------------------------------------------------------------------------- - def find_repeats(arr): """Find repeats in arr and return a tuple (repeats, repeat_count). Masked values are discarded. -Parameters ----------- + Parameters + ---------- arr : sequence Input array. The array is flattened if it is not 1D. -Returns -------- + Returns + ------- repeats : ndarray Array of repeated values. counts : ndarray @@ -160,6 +152,7 @@ Returns marr = ma.compressed(arr) if not marr.size: return (np.array(0), np.array(0)) + (v1, v2, n) = futil.dfreps(ma.array(ma.compressed(arr), copy=True)) return (v1[:n], v2[:n]) @@ -182,18 +175,19 @@ def count_tied_groups(x, use_missing=False): Examples -------- + >>> from scipy.stats import mstats >>> z = [0, 0, 0, 2, 2, 2, 3, 3, 4, 5, 6] - >>> count_tied_groups(z) - >>> {2:1, 3:2} - >>> # The ties were 0 (3x), 2 (3x) and 3 (2x) - >>> z = ma.array([0, 0, 1, 2, 2, 2, 3, 3, 4, 5, 6]) - >>> count_tied_groups(z) - >>> {2:2, 3:1} - >>> # The ties were 0 (2x), 2 (3x) and 3 (2x) - >>> z[[1,-1]] = masked - >>> count_tied_groups(z, use_missing=True) - >>> {2:2, 3:1} - >>> # The ties were 2 (3x), 3 (2x) and masked (2x) + >>> mstats.count_tied_groups(z) + {2: 1, 3: 2} + + In the above example, the ties were 0 (3x), 2 (3x) and 3 (2x). + + >>> z = np.ma.array([0, 0, 1, 2, 2, 2, 3, 3, 4, 5, 6]) + >>> mstats.count_tied_groups(z) + {2: 2, 3: 1} + >>> z[[1,-1]] = np.ma.masked + >>> mstats.count_tied_groups(z, use_missing=True) + {2: 2, 3: 1} """ nmasked = ma.getmask(x).sum() @@ -204,11 +198,13 @@ def count_tied_groups(x, use_missing=False): if len(ties): nties = dict(zip(np.unique(counts), itertools.repeat(1))) nties.update(dict(zip(*find_repeats(counts)))) + if nmasked and use_missing: try: nties[nmasked] += 1 except KeyError: nties[nmasked] = 1 + return nties @@ -222,34 +218,34 @@ def rankdata(data, axis=None, use_missing=False): Parameters ---------- - data : sequence - Input data. The data is transformed to a masked array - axis : {None,int}, optional - Axis along which to perform the ranking. - If None, the array is first flattened. An exception is raised if - the axis is specified for arrays with a dimension larger than 2 - use_missing : {boolean}, optional - Whether the masked values have a rank of 0 (False) or equal to the - average rank of the unmasked values (True). + data : sequence + Input data. The data is transformed to a masked array + axis : {None,int}, optional + Axis along which to perform the ranking. + If None, the array is first flattened. An exception is raised if + the axis is specified for arrays with a dimension larger than 2 + use_missing : {boolean}, optional + Whether the masked values have a rank of 0 (False) or equal to the + average rank of the unmasked values (True). + """ - # def _rank1d(data, use_missing=False): n = data.count() rk = np.empty(data.size, dtype=float) idx = data.argsort() rk[idx[:n]] = np.arange(1,n+1) - # + if use_missing: rk[idx[n:]] = (n+1)/2. else: rk[idx[n:]] = 0 - # + repeats = find_repeats(data.copy()) for r in repeats[0]: condition = (data == r).filled(False) rk[condition] = rk[condition].mean() return rk - # + data = ma.array(data, copy=False) if axis is None: if data.ndim > 1: @@ -260,27 +256,6 @@ def rankdata(data, axis=None, use_missing=False): return ma.apply_along_axis(_rank1d,axis,data,use_missing).view(ndarray) -#####-------------------------------------------------------------------------- -#---- --- Central tendency --- -#####-------------------------------------------------------------------------- - -def gmean(a, axis=0): - a, axis = _chk_asarray(a, axis) - log_a = ma.log(a) - return ma.exp(log_a.mean(axis=axis)) -gmean.__doc__ = stats.gmean.__doc__ - - -def hmean(a, axis=0): - a, axis = _chk_asarray(a, axis) - if isinstance(a, MaskedArray): - size = a.count(axis) - else: - size = a.shape[axis] - return size / (1.0/a).sum(axis) -hmean.__doc__ = stats.hmean.__doc__ - - def mode(a, axis=0): a, axis = _chk_asarray(a, axis) @@ -312,10 +287,6 @@ def mode(a, axis=0): mode.__doc__ = stats.mode.__doc__ -#####-------------------------------------------------------------------------- -#---- --- Probabilities --- -#####-------------------------------------------------------------------------- - def betai(a, b, x): x = np.asanyarray(x) x = ma.where(x < 1.0, x, 1.0) # if x > 1 then return 1.0 @@ -323,10 +294,6 @@ def betai(a, b, x): betai.__doc__ = stats.betai.__doc__ -#####-------------------------------------------------------------------------- -#---- --- Correlation --- -#####-------------------------------------------------------------------------- - def msign(x): """Returns the sign of x, or 0 if x is masked.""" return ma.filled(np.sign(x), 0) @@ -375,10 +342,10 @@ def pearsonr(x,y): df = n-2 if df < 0: return (masked, masked) - # + (mx, my) = (x.mean(), y.mean()) (xm, ym) = (x-mx, y-my) - # + r_num = ma.add.reduce(xm*ym) r_den = ma.sqrt(ma.dot(xm,xm) * ma.dot(ym,ym)) r = r_num / r_den @@ -393,6 +360,7 @@ def pearsonr(x,y): else: t_squared = (df / ((1.0 - r) * (1.0 + r))) * r * r prob = betai(0.5*df, 0.5, df/(df + t_squared)) + return r, prob @@ -439,7 +407,7 @@ def spearmanr(x, y, use_ties=True): """ (x, y, n) = _chk_size(x, y) (x, y) = (x.ravel(), y.ravel()) - # + m = ma.mask_or(ma.getmask(x), ma.getmask(y)) n -= m.sum() if m is not nomask: @@ -448,6 +416,7 @@ def spearmanr(x, y, use_ties=True): df = n-2 if df < 0: raise ValueError("The input must have at least 3 entries!") + # Gets the ranks and rank differences rankx = rankdata(x) ranky = rankdata(y) @@ -460,18 +429,20 @@ def spearmanr(x, y, use_ties=True): corr_y = np.sum(v*k*(k**2-1) for (k,v) in iteritems(yties))/12. else: corr_x = corr_y = 0 + denom = n*(n**2 - 1)/6. if corr_x != 0 or corr_y != 0: rho = denom - dsq - corr_x - corr_y rho /= ma.sqrt((denom-2*corr_x)*(denom-2*corr_y)) else: rho = 1. - dsq/denom - # + t = ma.sqrt(ma.divide(df,(rho+1.0)*(1.0-rho))) * rho if t is masked: prob = 0. else: prob = betai(0.5*df,0.5,df/(df+t*t)) + return rho, prob @@ -506,10 +477,10 @@ def kendalltau(x, y, use_ties=True, use_missing=False): x = ma.array(x, mask=m, copy=True) y = ma.array(y, mask=m, copy=True) n -= m.sum() - # + if n < 2: return (np.nan, np.nan) - # + rx = ma.masked_equal(rankdata(x, use_missing=use_missing), 0) ry = ma.masked_equal(rankdata(y, use_missing=use_missing), 0) idx = rx.argsort() @@ -527,7 +498,7 @@ def kendalltau(x, y, use_ties=True, use_missing=False): else: denom = n*(n-1)/2. tau = (C-D) / denom - # + var_s = n*(n-1)*(2*n+5) if use_ties: var_s -= np.sum(v*k*(k-1)*(2*k+5)*1. for (k,v) in iteritems(xties)) @@ -545,6 +516,7 @@ def kendalltau(x, y, use_ties=True, use_missing=False): v2 = 0 else: v1 = v2 = 0 + var_s /= 18. var_s += (v1 + v2) z = (C-D)/np.sqrt(var_s) @@ -565,19 +537,18 @@ def kendalltau_seasonal(x): x = ma.array(x, subok=True, copy=False, ndmin=2) (n,m) = x.shape n_p = x.count(0) - # + S_szn = np.sum(msign(x[i:]-x[i]).sum(0) for i in range(n)) S_tot = S_szn.sum() - # + n_tot = x.count() ties = count_tied_groups(x.compressed()) corr_ties = np.sum(v*k*(k-1) for (k,v) in iteritems(ties)) denom_tot = ma.sqrt(1.*n_tot*(n_tot-1)*(n_tot*(n_tot-1)-corr_ties))/2. - # + R = rankdata(x, axis=0, use_missing=True) K = ma.empty((m,m), dtype=int) covmat = ma.empty((m,m), dtype=float) -# cov_jj = ma.empty(m, dtype=float) denom_szn = ma.empty(m, dtype=float) for j in range(m): ties_j = count_tied_groups(x[:,j].compressed()) @@ -590,20 +561,19 @@ def kendalltau_seasonal(x): n*(n_p[j]+1)*(n_p[k]+1))/3. K[k,j] = K[j,k] covmat[k,j] = covmat[j,k] -# cov_jj[j] = (nn_p*(2*n_p[j]+5)) -# cov_jj[j] -= np.sum(v*k*(k-1)*(2*k+5) for (k,v) in ties_j.iteritems()) -# cov_jj[j] /= 18. + denom_szn[j] = ma.sqrt(cmb*(cmb-corr_j)) / 2. + var_szn = covmat.diagonal() - # + z_szn = msign(S_szn) * (abs(S_szn)-1) / ma.sqrt(var_szn) z_tot_ind = msign(S_tot) * (abs(S_tot)-1) / ma.sqrt(var_szn.sum()) z_tot_dep = msign(S_tot) * (abs(S_tot)-1) / ma.sqrt(covmat.sum()) - # + prob_szn = special.erfc(abs(z_szn)/np.sqrt(2)) prob_tot_ind = special.erfc(abs(z_tot_ind)/np.sqrt(2)) prob_tot_dep = special.erfc(abs(z_tot_dep)/np.sqrt(2)) - # + chi2_tot = (z_szn*z_szn).sum() chi2_trd = m * z_szn.mean()**2 output = {'seasonal tau': S_szn/denom_szn, @@ -621,13 +591,13 @@ def kendalltau_seasonal(x): def pointbiserialr(x, y): x = ma.fix_invalid(x, copy=True).astype(bool) y = ma.fix_invalid(y, copy=True).astype(float) - # Get rid of the missing data .......... + # Get rid of the missing data m = ma.mask_or(ma.getmask(x), ma.getmask(y)) if m is not nomask: unmask = np.logical_not(m) x = x[unmask] y = y[unmask] - # + n = len(x) # phat is the fraction of x values that are True phat = x.sum() / float(n) @@ -635,9 +605,9 @@ def pointbiserialr(x, y): y1 = y[x] # y-values where x is True y0m = y0.mean() y1m = y1.mean() - # + rpb = (y1m - y0m)*np.sqrt(phat * (1-phat)) / y.std() - # + df = n-2 t = rpb*ma.sqrt(df/(1.0-rpb**2)) prob = betai(0.5*df, 0.5, df/(df+t*t)) @@ -648,104 +618,63 @@ if stats.pointbiserialr.__doc__: def linregress(*args): - if len(args) == 1: # more than 1D array? + """ + Linear regression calculation + + Note that the non-masked version is used, and that this docstring is + replaced by the non-masked docstring + some info on missing data. + + """ + if len(args) == 1: + # Input is a single 2-D array containing x and y args = ma.array(args[0], copy=True) if len(args) == 2: x = args[0] y = args[1] else: - x = args[:,0] - y = args[:,1] + x = args[:, 0] + y = args[:, 1] else: + # Input is two 1-D arrays x = ma.array(args[0]).flatten() y = ma.array(args[1]).flatten() - m = ma.mask_or(ma.getmask(x), ma.getmask(y)) + + m = ma.mask_or(ma.getmask(x), ma.getmask(y), shrink=False) if m is not nomask: - x = ma.array(x,mask=m) - y = ma.array(y,mask=m) - n = len(x) - (xmean, ymean) = (x.mean(), y.mean()) - (xm, ym) = (x-xmean, y-ymean) - (Sxx, Syy) = (ma.add.reduce(xm*xm), ma.add.reduce(ym*ym)) - Sxy = ma.add.reduce(xm*ym) - r_den = ma.sqrt(Sxx*Syy) - if r_den == 0.0: - r = 0.0 + x = ma.array(x, mask=m) + y = ma.array(y, mask=m) + if np.any(~m): + slope, intercept, r, prob, sterrest = stats.linregress(x.data[~m], + y.data[~m]) + else: + # All data is masked + return None, None, None, None, None else: - r = Sxy / r_den - if (r > 1.0): - r = 1.0 # from numerical error - # z = 0.5*log((1.0+r+TINY)/(1.0-r+TINY)) - df = n-2 - t = r * ma.sqrt(df/(1.0-r*r)) - prob = betai(0.5*df,0.5,df/(df+t*t)) - slope = Sxy / Sxx - intercept = ymean - slope*xmean - sterrest = ma.sqrt(1.-r*r) * y.std() + slope, intercept, r, prob, sterrest = stats.linregress(x.data, y.data) + return slope, intercept, r, prob, sterrest if stats.linregress.__doc__: linregress.__doc__ = stats.linregress.__doc__ + genmissingvaldoc -def theilslopes(y, x=None, alpha=0.05): - """ - Computes the Theil slope as the median of all slopes between paired values. - - Parameters - ---------- - y : array_like - Dependent variable. - x : {None, array_like}, optional - Independent variable. If None, use arange(len(y)) instead. - alpha : float - Confidence degree. - - Returns - ------- - medslope : float - Theil slope - medintercept : float - Intercept of the Theil line, as median(y)-medslope*median(x) - lo_slope : float - Lower bound of the confidence interval on medslope - up_slope : float - Upper bound of the confidence interval on medslope - - """ +def theilslopes(y, x=None, alpha=0.95): y = ma.asarray(y).flatten() - y[-1] = masked - n = len(y) if x is None: x = ma.arange(len(y), dtype=float) else: x = ma.asarray(x).flatten() - if len(x) != n: - raise ValueError("Incompatible lengths ! (%s<>%s)" % (n,len(x))) + if len(x) != len(y): + raise ValueError("Incompatible lengths ! (%s<>%s)" % (len(y),len(x))) + m = ma.mask_or(ma.getmask(x), ma.getmask(y)) y._mask = x._mask = m - ny = y.count() - # - slopes = ma.hstack([(y[i+1:]-y[i])/(x[i+1:]-x[i]) for i in range(n-1)]) - slopes.sort() - medslope = ma.median(slopes) - medinter = ma.median(y) - medslope*ma.median(x) - # - if alpha > 0.5: - alpha = 1.-alpha - z = stats.distributions.norm.ppf(alpha/2.) - # - (xties, yties) = (count_tied_groups(x), count_tied_groups(y)) - nt = ny*(ny-1)/2. - sigsq = (ny*(ny-1)*(2*ny+5)/18.) - sigsq -= np.sum(v*k*(k-1)*(2*k+5) for (k,v) in iteritems(xties)) - sigsq -= np.sum(v*k*(k-1)*(2*k+5) for (k,v) in iteritems(yties)) - sigma = np.sqrt(sigsq) - - Ru = min(np.round((nt - z*sigma)/2. + 1), len(slopes)-1) - Rl = max(np.round((nt + z*sigma)/2.), 0) - delta = slopes[[Rl,Ru]] - return medslope, medinter, delta[0], delta[1] + # Disregard any masked elements of x or y + y = y.compressed() + x = x.compressed().astype(float) + # We now have unmasked arrays so can use `stats.theilslopes` + return stats.theilslopes(y, x, alpha=alpha) +theilslopes.__doc__ = stats.theilslopes.__doc__ def sen_seasonal_slopes(x): @@ -759,10 +688,6 @@ def sen_seasonal_slopes(x): return szn_medslopes, medslope -#####-------------------------------------------------------------------------- -#---- --- Inferential statistics --- -#####-------------------------------------------------------------------------- - def ttest_1samp(a, popmean, axis=0): a, axis = _chk_asarray(a, axis) if a.size == 0: @@ -790,7 +715,7 @@ def ttest_ind(a, b, axis=0): (n1, n2) = (a.count(axis), b.count(axis)) df = n1 + n2 - 2. svar = ((n1-1)*v1+(n2-1)*v2) / df - t = (x1-x2)/ma.sqrt(svar*(1.0/n1 + 1.0/n2)) # N-D COMPUTATION HERE!!!!!! + t = (x1-x2)/ma.sqrt(svar*(1.0/n1 + 1.0/n2)) # n-D computation here! t = ma.filled(t, 1) # replace NaN t-values with 1.0 probs = betai(0.5 * df, 0.5, df/(df + t*t)).reshape(t.shape) return t, probs.squeeze() @@ -811,7 +736,6 @@ def ttest_rel(a, b, axis=0): df = (n-1.0) d = (a-b).astype('d') denom = ma.sqrt((n*ma.add.reduce(d*d,axis) - ma.add.reduce(d,axis)**2) / df) - # zerodivproblem = denom == 0 t = ma.add.reduce(d, axis) / denom t = ma.filled(t, 1) probs = betai(0.5*df,0.5,df/(df+t*t)).reshape(t.shape).squeeze() @@ -857,17 +781,18 @@ def mannwhitneyu(x,y, use_continuity=True): U = ranks[:nx].sum() - nx*(nx+1)/2. U = max(U, nx*ny - U) u = nx*ny - U - # + mu = (nx*ny)/2. sigsq = (nt**3 - nt)/12. ties = count_tied_groups(ranks) sigsq -= np.sum(v*(k**3-k) for (k,v) in iteritems(ties))/12. sigsq *= nx*ny/float(nt*(nt-1)) - # + if use_continuity: z = (U - 1/2. - mu) / ma.sqrt(sigsq) else: z = (U - mu) / ma.sqrt(sigsq) + prob = special.erfc(abs(z)/np.sqrt(2)) return (u, prob) @@ -878,16 +803,14 @@ def kruskalwallis(*args): sumrk = ranks.sum(-1) ngrp = ranks.count(-1) ntot = ranks.count() -# ssbg = (sumrk**2/ranks.count(-1)).sum() - ranks.sum()**2/ntotal -# H = ssbg / (ntotal*(ntotal+1)/12.) H = 12./(ntot*(ntot+1)) * (sumrk**2/ngrp).sum() - 3*(ntot+1) # Tie correction ties = count_tied_groups(ranks) T = 1. - np.sum(v*(k**3-k) for (k,v) in iteritems(ties))/float(ntot**3-ntot) if T == 0: raise ValueError('All numbers are identical in kruskal') + H /= T - # df = len(output) - 1 prob = stats.chisqprob(H,df) return (H, prob) @@ -895,20 +818,6 @@ kruskal = kruskalwallis kruskalwallis.__doc__ = stats.kruskal.__doc__ -_kolmog2 = special.kolmogorov - - -def _kolmog1(x,n): - if x <= 0: - return 0 - if x >= 1: - return 1 - j = np.arange(np.floor(n*(1-x))+1) - return 1 - x * np.sum(np.exp(np.log(misc.comb(n,j)) - + (n-j) * np.log(1-x-j/float(n)) - + (j-1) * np.log(x+j/float(n)))) - - def ks_twosamp(data1, data2, alternative="two-sided"): """ Computes the Kolmogorov-Smirnov test on two samples. @@ -941,11 +850,11 @@ def ks_twosamp(data1, data2, alternative="two-sided"): # Check for ties if len(np.unique(mix)) < (n1+n2): csum = csum[np.r_[np.diff(mix[mixsort]).nonzero()[0],-1]] - # + alternative = str(alternative).lower()[0] if alternative == 't': d = ma.abs(csum).max() - prob = _kolmog2(np.sqrt(n)*d) + prob = special.kolmogorov(np.sqrt(n)*d) elif alternative == 'l': d = -csum.min() prob = np.exp(-2*n*d**2) @@ -955,6 +864,7 @@ def ks_twosamp(data1, data2, alternative="two-sided"): else: raise ValueError("Invalid value for the alternative hypothesis: " "should be in 'two-sided', 'less' or 'greater'") + return (d, prob) ks_2samp = ks_twosamp @@ -971,10 +881,6 @@ def ks_twosamp_old(data1, data2): return stats.ks_2samp(data1,data2) -#####-------------------------------------------------------------------------- -#---- --- Trimming --- -#####-------------------------------------------------------------------------- - def threshold(a, threshmin=None, threshmax=None, newval=0): """ Clip array to a given value. @@ -1005,33 +911,38 @@ def threshold(a, threshmin=None, threshmax=None, newval=0): mask = np.zeros(a.shape, dtype=bool) if threshmin is not None: mask |= (a < threshmin).filled(False) + if threshmax is not None: mask |= (a > threshmax).filled(False) + a[mask] = newval return a def trima(a, limits=None, inclusive=(True,True)): - """Trims an array by masking the data outside some given limits. + """ + Trims an array by masking the data outside some given limits. + Returns a masked version of the input array. Parameters ---------- - a : sequence + a : array_like Input array. limits : {None, tuple}, optional Tuple of (lower limit, upper limit) in absolute values. Values of the input array lower (greater) than the lower (upper) limit - will be masked. A limit is None indicates an open interval. - inclusive : {(True,True) tuple}, optional + will be masked. A limit is None indicates an open interval. + inclusive : (bool, bool) tuple, optional Tuple of (lower flag, upper flag), indicating whether values exactly equal to the lower (upper) limit are allowed. """ a = ma.asarray(a) a.unshare_mask() - if limits is None: + if (limits is None) or (limits == (None, None)): return a + (lower_lim, upper_lim) = limits (lower_in, upper_in) = inclusive condition = False @@ -1040,11 +951,13 @@ def trima(a, limits=None, inclusive=(True,True)): condition |= (a < lower_lim) else: condition |= (a <= lower_lim) + if upper_lim is not None: if upper_in: condition |= (a > upper_lim) else: condition |= (a >= upper_lim) + a[condition.filled(True)] = masked return a @@ -1091,11 +1004,12 @@ def trimr(a, limits=None, inclusive=(True, True), axis=None): upidx = n - np.round(n*up_limit) a[idx[upidx:]] = masked return a - # + a = ma.asarray(a) a.unshare_mask() if limits is None: return a + # Check the limits (lolim, uplim) = limits errmsg = "The proportion to cut from the %s should be between 0. and 1." @@ -1105,9 +1019,9 @@ def trimr(a, limits=None, inclusive=(True, True), axis=None): if uplim is not None: if uplim > 1. or uplim < 0: raise ValueError(errmsg % 'end' + "(got %s)" % uplim) - # + (loinc, upinc) = inclusive - # + if axis is None: shp = a.shape return _trimr1D(a.ravel(),lolim,uplim,loinc,upinc).reshape(shp) @@ -1202,8 +1116,6 @@ def trimboth(data, proportiontocut=0.2, inclusive=(True,True), axis=None): return trimr(data, limits=(proportiontocut,proportiontocut), inclusive=inclusive, axis=axis) -#.............................................................................. - def trimtail(data, proportiontocut=0.2, tail='left', inclusive=(True,True), axis=None): @@ -1243,6 +1155,7 @@ def trimtail(data, proportiontocut=0.2, tail='left', inclusive=(True,True), limits = (None, proportiontocut) else: raise TypeError("The tail argument should be in ('left','right')") + return trimr(data, limits=limits, axis=axis, inclusive=inclusive) trim1 = trimtail @@ -1280,6 +1193,7 @@ def trimmed_var(a, limits=(0.1,0.1), inclusive=(1,1), relative=True, out = trimr(a,limits=limits, inclusive=inclusive,axis=axis) else: out = trima(a,limits=limits,inclusive=inclusive) + return out.var(axis=axis, ddof=ddof) @@ -1332,7 +1246,6 @@ def trimmed_stde(a, limits=(0.1,0.1), inclusive=(1,1), axis=None): trimmed_stde : scalar or ndarray """ - #........................ def _trimmed_stde_1D(a, low_limit, up_limit, low_inclusive, up_inclusive): "Returns the standard error of the trimmed mean for a 1D input data." n = a.count() @@ -1353,13 +1266,14 @@ def trimmed_stde(a, limits=(0.1,0.1), inclusive=(1,1), axis=None): a[idx[upidx:]] = a[idx[upidx-1]] winstd = a.std(ddof=1) return winstd / ((1-low_limit-up_limit)*np.sqrt(len(a))) - #........................ + a = ma.array(a, copy=True, subok=True) a.unshare_mask() if limits is None: return a.std(axis=axis,ddof=1)/ma.sqrt(a.count(axis)) if (not isinstance(limits,tuple)) and isinstance(limits,float): limits = (limits, limits) + # Check the limits (lolim, uplim) = limits errmsg = "The proportion to cut from the %s should be between 0. and 1." @@ -1369,7 +1283,7 @@ def trimmed_stde(a, limits=(0.1,0.1), inclusive=(1,1), axis=None): if uplim is not None: if uplim > 1. or uplim < 0: raise ValueError(errmsg % 'end' + "(got %s)" % uplim) - # + (loinc, upinc) = inclusive if (axis is None): return _trimmed_stde_1D(a.ravel(),lolim,uplim,loinc,upinc) @@ -1386,7 +1300,14 @@ tmean.__doc__ = stats.tmean.__doc__ def tvar(a, limits=None, inclusive=(True,True)): - return trima(a, limits=limits, inclusive=inclusive).var() + a = a.astype(float).ravel() + if limits is None: + n = (~a.mask).sum() # todo: better way to do that? + r = trima(a, limits=limits, inclusive=inclusive).var() * (n/(n-1.)) + else: + raise ValueError('mstats.tvar() with limits not implemented yet so far') + + return r tvar.__doc__ = stats.tvar.__doc__ @@ -1408,19 +1329,20 @@ def tsem(a, limits=None, inclusive=(True,True)): a = ma.asarray(a).ravel() if limits is None: n = float(a.count()) - return a.std()/ma.sqrt(n) + return a.std(ddof=1)/ma.sqrt(n) + am = trima(a.ravel(), limits, inclusive) - sd = np.sqrt(am.var()) - return sd / am.count() + sd = np.sqrt(am.var(ddof=1)) + return sd / np.sqrt(am.count()) tsem.__doc__ = stats.tsem.__doc__ -def winsorize(a, limits=None, inclusive=(True,True), inplace=False, axis=None): - """ - Returns a Winsorized version of the input array. +def winsorize(a, limits=None, inclusive=(True, True), inplace=False, + axis=None): + """Returns a Winsorized version of the input array. The (limits[0])th lowest values are set to the (limits[0])th percentile, - and the (limits[1])th highest values are set to the (limits[1])th + and the (limits[1])th highest values are set to the (1 - limits[1])th percentile. Masked values are skipped. @@ -1446,30 +1368,37 @@ def winsorize(a, limits=None, inclusive=(True,True), inplace=False, axis=None): Axis along which to trim. If None, the whole array is trimmed, but its shape is maintained. + Notes + ----- + This function is applied to reduce the effect of possibly spurious outliers + by limiting the extreme values. + """ def _winsorize1D(a, low_limit, up_limit, low_include, up_include): n = a.count() idx = a.argsort() if low_limit: if low_include: - lowidx = int(low_limit*n) + lowidx = int(low_limit * n) else: - lowidx = np.round(low_limit*n) + lowidx = np.round(low_limit * n) a[idx[:lowidx]] = a[idx[lowidx]] if up_limit is not None: if up_include: - upidx = n - int(n*up_limit) + upidx = n - int(n * up_limit) else: - upidx = n - np.round(n*up_limit) - a[idx[upidx:]] = a[idx[upidx-1]] + upidx = n - np.round(n * up_limit) + a[idx[upidx:]] = a[idx[upidx - 1]] return a - # We gonna modify a: better make a copy + + # We are going to modify a: better make a copy a = ma.array(a, copy=np.logical_not(inplace)) - # + if limits is None: return a - if (not isinstance(limits,tuple)) and isinstance(limits,float): + if (not isinstance(limits, tuple)) and isinstance(limits, float): limits = (limits, limits) + # Check the limits (lolim, uplim) = limits errmsg = "The proportion to cut from the %s should be between 0. and 1." @@ -1479,19 +1408,16 @@ def winsorize(a, limits=None, inclusive=(True,True), inplace=False, axis=None): if uplim is not None: if uplim > 1. or uplim < 0: raise ValueError(errmsg % 'end' + "(got %s)" % uplim) - # + (loinc, upinc) = inclusive - # + if axis is None: shp = a.shape - return _winsorize1D(a.ravel(),lolim,uplim,loinc,upinc).reshape(shp) + return _winsorize1D(a.ravel(), lolim, uplim, loinc, upinc).reshape(shp) else: - return ma.apply_along_axis(_winsorize1D, axis,a,lolim,uplim,loinc,upinc) - + return ma.apply_along_axis(_winsorize1D, axis, a, lolim, uplim, loinc, + upinc) -#####-------------------------------------------------------------------------- -#---- --- Moments --- -#####-------------------------------------------------------------------------- def moment(a, moment=1, axis=0): a, axis = _chk_asarray(a, axis) @@ -1542,8 +1468,8 @@ skew.__doc__ = stats.skew.__doc__ def kurtosis(a, axis=0, fisher=True, bias=True): a, axis = _chk_asarray(a, axis) - m2 = moment(a,2,axis) - m4 = moment(a,4,axis) + m2 = moment(a, 2, axis) + m4 = moment(a, 4, axis) olderr = np.seterr(all='ignore') try: vals = ma.where(m2 == 0, 0, m4 / m2**2.0) @@ -1566,7 +1492,7 @@ def kurtosis(a, axis=0, fisher=True, bias=True): kurtosis.__doc__ = stats.kurtosis.__doc__ -def describe(a, axis=0): +def describe(a, axis=0,ddof=0): """ Computes several descriptive statistics of the passed array. @@ -1576,6 +1502,10 @@ def describe(a, axis=0): axis : int or None + ddof : int + degree of freedom (default 0); note that default ddof is different + from the same routine in stats.describe + Returns ------- n : int @@ -1611,25 +1541,23 @@ def describe(a, axis=0): n = a.count(axis) mm = (ma.minimum.reduce(a), ma.maximum.reduce(a)) m = a.mean(axis) - v = a.var(axis) + v = a.var(axis,ddof=ddof) sk = skew(a, axis) kurt = kurtosis(a, axis) return n, mm, m, v, sk, kurt -#............................................................................. - def stde_median(data, axis=None): """Returns the McKean-Schrader estimate of the standard error of the sample -median along the given axis. masked values are discarded. + median along the given axis. masked values are discarded. Parameters ---------- - data : ndarray - Data to trim. - axis : {None,int}, optional - Axis along which to perform the trimming. - If None, the input array is first flattened. + data : ndarray + Data to trim. + axis : {None,int}, optional + Axis along which to perform the trimming. + If None, the input array is first flattened. """ def _stdemed_1D(data): @@ -1638,19 +1566,16 @@ median along the given axis. masked values are discarded. z = 2.5758293035489004 k = int(np.round((n+1)/2. - z * np.sqrt(n/4.),0)) return ((data[n-k] - data[k-1])/(2.*z)) - # + data = ma.array(data, copy=False, subok=True) if (axis is None): return _stdemed_1D(data) else: if data.ndim > 2: - raise ValueError("Array 'data' must be at most two dimensional, but got data.ndim = %d" % data.ndim) + raise ValueError("Array 'data' must be at most two dimensional, " + "but got data.ndim = %d" % data.ndim) return ma.apply_along_axis(_stdemed_1D, axis, data) -#####-------------------------------------------------------------------------- -#---- --- Normality Tests --- -#####-------------------------------------------------------------------------- - def skewtest(a, axis=0): a, axis = _chk_asarray(a, axis) @@ -1663,6 +1588,7 @@ def skewtest(a, axis=0): raise ValueError( "skewtest is not valid with less than 8 samples; %i samples" " were given." % np.min(n)) + y = b2 * ma.sqrt(((n+1)*(n+3)) / (6.0*(n-2))) beta2 = (3.0*(n*n+27*n-70)*(n+1)*(n+3)) / ((n-2.0)*(n+5)*(n+7)*(n+9)) W2 = -1 + ma.sqrt(2*(beta2-1)) @@ -1670,7 +1596,7 @@ def skewtest(a, axis=0): alpha = ma.sqrt(2.0/(W2-1)) y = ma.where(y == 0, 1, y) Z = delta*ma.log(y/alpha + ma.sqrt((y/alpha)**2+1)) - return Z, (1.0 - stats.zprob(Z))*2 + return Z, 2 * distributions.norm.sf(np.abs(Z)) skewtest.__doc__ = stats.skewtest.__doc__ @@ -1685,9 +1611,10 @@ def kurtosistest(a, axis=0): warnings.warn( "kurtosistest only valid for n>=20 ... continuing anyway, n=%i" % np.min(n)) + b2 = kurtosis(a, axis, fisher=False) E = 3.0*(n-1) / (n+1) - varb2 = 24.0*n*(n-2)*(n-3) / ((n+1)*(n+1)*(n+3)*(n+5)) + varb2 = 24.0*n*(n-2.)*(n-3) / ((n+1)*(n+1.)*(n+3)*(n+5)) x = (b2-E)/ma.sqrt(varb2) sqrtbeta1 = 6.0*(n*n-5*n+2)/((n+7)*(n+9)) * np.sqrt((6.0*(n+3)*(n+5)) / (n*(n-2)*(n-3))) @@ -1708,20 +1635,12 @@ kurtosistest.__doc__ = stats.kurtosistest.__doc__ def normaltest(a, axis=0): a, axis = _chk_asarray(a, axis) - s,_ = skewtest(a,axis) - k,_ = kurtosistest(a,axis) + s, _ = skewtest(a, axis) + k, _ = kurtosistest(a, axis) k2 = s*s + k*k return k2, stats.chisqprob(k2,2) normaltest.__doc__ = stats.normaltest.__doc__ -# Martinez-Iglewicz test -# K-S test - - -#####-------------------------------------------------------------------------- -#---- --- Percentiles --- -#####-------------------------------------------------------------------------- - def mquantiles(a, prob=list([.25,.5,.75]), alphap=.4, betap=.4, axis=None, limit=()): @@ -1786,7 +1705,9 @@ def mquantiles(a, prob=list([.25,.5,.75]), alphap=.4, betap=.4, axis=None, References ---------- - .. [1] *R* statistical software at http://www.r-project.org/ + .. [1] *R* statistical software: http://www.r-project.org/ + .. [2] *R* ``quantile`` function: + http://stat.ethz.ch/R-manual/R-devel/library/stats/html/quantile.html Examples -------- @@ -1838,20 +1759,20 @@ def mquantiles(a, prob=list([.25,.5,.75]), alphap=.4, betap=.4, axis=None, gamma = (aleph-k).clip(0,1) return (1.-gamma)*x[(k-1).tolist()] + gamma*x[k.tolist()] - # Initialization & checks --------- data = ma.array(a, copy=False) if data.ndim > 2: raise TypeError("Array should be 2D at most !") - # + if limit: condition = (limit[0] < data) & (data < limit[1]) data[~condition.filled(True)] = masked - # + p = np.array(prob, copy=False, ndmin=1) m = alphap + p*(1.-alphap-betap) # Computes quantiles along axis (or globally) if (axis is None): return _quantiles1D(data, m, p) + return ma.apply_along_axis(_quantiles1D, axis, data, m, p) @@ -1865,6 +1786,7 @@ def scoreatpercentile(data, per, limit=(), alphap=.4, betap=.4): if (per < 0) or (per > 100.): raise ValueError("The percentile should be between 0. and 100. !" " (got %s)" % per) + return mquantiles(data, prob=[per/100.], alphap=alphap, betap=betap, limit=limit, axis=0).squeeze() @@ -1915,26 +1837,22 @@ def plotting_positions(data, alpha=0.4, beta=0.4): n = data.count() plpos = np.empty(data.size, dtype=float) plpos[n:] = 0 - plpos[data.argsort()[:n]] = (np.arange(1, n+1) - alpha) / \ - (n + 1.0 - alpha - beta) + plpos[data.argsort()[:n]] = ((np.arange(1, n+1) - alpha) / + (n + 1.0 - alpha - beta)) return ma.array(plpos, mask=data._mask) meppf = plotting_positions -#####-------------------------------------------------------------------------- -#---- --- Variability --- -#####-------------------------------------------------------------------------- - def obrientransform(*args): """ -Computes a transform on input data (any number of columns). Used to -test for homogeneity of variance prior to running one-way stats. Each -array in *args is one level of a factor. If an F_oneway() run on the -transformed data and found significant, variances are unequal. From -Maxwell and Delaney, p.112. + Computes a transform on input data (any number of columns). Used to + test for homogeneity of variance prior to running one-way stats. Each + array in *args is one level of a factor. If an F_oneway() run on the + transformed data and found significant, variances are unequal. From + Maxwell and Delaney, p.112. -Returns: transformed data for use in an ANOVA + Returns: transformed data for use in an ANOVA """ data = argstoarray(*args).T v = data.var(axis=0,ddof=1) @@ -1948,6 +1866,7 @@ Returns: transformed data for use in an ANOVA data /= (n-1.)*(n-2.) if not ma.allclose(v,data.mean(0)): raise ValueError("Lack of convergence in obrientransform.") + return data @@ -1957,12 +1876,12 @@ def signaltonoise(data, axis=0): Parameters ---------- - data : sequence - Input data - axis : {0, int}, optional - Axis along which to compute. If None, the computation is performed - on a flat version of the array. -""" + data : sequence + Input data + axis : {0, int}, optional + Axis along which to compute. If None, the computation is performed + on a flat version of the array. + """ data = ma.array(data, copy=False) m = data.mean(axis) sd = data.std(axis, ddof=0) @@ -1970,30 +1889,70 @@ def signaltonoise(data, axis=0): def sem(a, axis=0, ddof=1): + """ + Calculates the standard error of the mean of the input array. + + Also sometimes called standard error of measurement. + + Parameters + ---------- + a : array_like + An array containing the values for which the standard error is + returned. + axis : int or None, optional. + If axis is None, ravel `a` first. If axis is an integer, this will be + the axis over which to operate. Defaults to 0. + ddof : int, optional + Delta degrees-of-freedom. How many degrees of freedom to adjust + for bias in limited samples relative to the population estimate + of variance. Defaults to 1. + + Returns + ------- + s : ndarray or float + The standard error of the mean in the sample(s), along the input axis. + + Notes + ----- + The default value for `ddof` changed in scipy 0.15.0 to be consistent with + `stats.sem` as well as with the most common definition used (like in the R + documentation). + + Examples + -------- + Find standard error along the first axis: + + >>> from scipy import stats + >>> a = np.arange(20).reshape(5,4) + >>> stats.sem(a) + array([ 2.8284, 2.8284, 2.8284, 2.8284]) + + Find standard error across the whole array, using n degrees of freedom: + + >>> stats.sem(a, axis=None, ddof=0) + 1.2893796958227628 + + """ a, axis = _chk_asarray(a, axis) n = a.count(axis=axis) s = a.std(axis=axis, ddof=ddof) / ma.sqrt(n) return s -sem.__doc__ = stats.sem.__doc__ + zmap = stats.zmap zscore = stats.zscore -#####-------------------------------------------------------------------------- -#---- --- ANOVA --- -#####-------------------------------------------------------------------------- - - def f_oneway(*args): """ -Performs a 1-way ANOVA, returning an F-value and probability given -any number of groups. From Heiman, pp.394-7. + Performs a 1-way ANOVA, returning an F-value and probability given + any number of groups. From Heiman, pp.394-7. -Usage: f_oneway (*args) where *args is 2 or more arrays, one per - treatment group -Returns: f-value, probability -""" + Usage: ``f_oneway(*args)``, where ``*args`` is 2 or more arrays, + one per treatment group. + Returns: f-value, probability + + """ # Construct a single array of arguments: each row is a group data = argstoarray(*args) ngroups = len(data) @@ -2006,12 +1965,12 @@ Returns: f-value, probability msb = ssbg/float(dfbg) msw = sswg/float(dfwg) f = msb/msw - prob = stats.fprob(dfbg,dfwg,f) + prob = special.fdtrc(dfbg, dfwg, f) # equivalent to stats.f.sf return f, prob def f_value_wilks_lambda(ER, EF, dfnum, dfden, a, b): - """Calculation of Wilks lambda F-statistic for multivarite data, per + """Calculation of Wilks lambda F-statistic for multivariate data, per Maxwell & Delaney p.657. """ ER = ma.array(ER, copy=False, ndmin=2) @@ -2019,6 +1978,7 @@ def f_value_wilks_lambda(ER, EF, dfnum, dfden, a, b): if ma.getmask(ER).any() or ma.getmask(EF).any(): raise NotImplementedError("Not implemented when the inputs " "have missing data") + lmbda = np.linalg.det(EF) / np.linalg.det(ER) q = ma.sqrt(((a-1)**2*(b-1)**2 - 2) / ((a-1)**2 + (b-1)**2 - 5)) q = ma.filled(q, 1) @@ -2049,6 +2009,7 @@ def friedmanchisquare(*args): if k < 3: raise ValueError("Less than 3 groups (%i): " % k + "the Friedman test is NOT appropriate.") + ranked = ma.masked_values(rankdata(data, axis=0), 0) if ranked._mask is not nomask: ranked = ma.mask_cols(ranked) @@ -2060,9 +2021,7 @@ def friedmanchisquare(*args): repeats = np.array([find_repeats(_) for _ in ranked.T], dtype=object) ties = repeats[repeats.nonzero()].reshape(-1,2)[:,-1].astype(int) tie_correction = 1 - (ties**3-ties).sum()/float(n*(k**3-k)) - # + ssbg = np.sum((ranked.sum(-1) - n*(k+1)/2.)**2) chisq = ssbg * 12./(n*k*(k+1)) * 1./tie_correction return chisq, stats.chisqprob(chisq,k-1) - -#-############################################################################-# diff --git a/pywafo/src/wafo/stats/mstats_extras.py b/pywafo/src/wafo/stats/mstats_extras.py index e71f99c..1a0ddcc 100644 --- a/pywafo/src/wafo/stats/mstats_extras.py +++ b/pywafo/src/wafo/stats/mstats_extras.py @@ -1,15 +1,12 @@ """ -Additional statistics functions, with support to MA. +Additional statistics functions with support for masked arrays. -:author: Pierre GF Gerard-Marchant -:contact: pierregm_at_uga_edu -:date: $Date: 2007-10-29 17:18:13 +0200 (Mon, 29 Oct 2007) $ -:version: $Id: morestats.py 3473 2007-10-29 15:18:13Z jarrod.millman $ """ -from __future__ import division, print_function, absolute_import -__author__ = "Pierre GF Gerard-Marchant" -__docformat__ = "restructuredtext en" +# Original author (2007): Pierre GF Gerard-Marchant + + +from __future__ import division, print_function, absolute_import __all__ = ['compare_medians_ms', @@ -19,6 +16,7 @@ __all__ = ['compare_medians_ms', 'rsh', 'trimmed_mean_ci',] + import numpy as np from numpy import float_, int_, ndarray @@ -30,9 +28,6 @@ from . import mstats_basic as mstats from scipy.stats.distributions import norm, beta, t, binom -#####-------------------------------------------------------------------------- -#---- --- Quantiles --- -#####-------------------------------------------------------------------------- def hdquantiles(data, prob=list([.25,.5,.75]), axis=None, var=False,): """ Computes quantile estimates with the Harrell-Davis method. @@ -65,14 +60,14 @@ def hdquantiles(data, prob=list([.25,.5,.75]), axis=None, var=False,): xsorted = np.squeeze(np.sort(data.compressed().view(ndarray))) # Don't use length here, in case we have a numpy scalar n = xsorted.size - #......... + hd = np.empty((2,len(prob)), float_) if n < 2: hd.flat = np.nan if var: return hd return hd[0] - #......... + v = np.arange(n+1) / float(n) betacdf = beta.cdf for (i,p) in enumerate(prob): @@ -89,7 +84,7 @@ def hdquantiles(data, prob=list([.25,.5,.75]), axis=None, var=False,): hd[1, prob == 0] = hd[1, prob == 1] = np.nan return hd return hd[0] - # Initialization & checks --------- + # Initialization & checks data = ma.array(data, copy=False, dtype=float_) p = np.array(prob, copy=False, ndmin=1) # Computes quantiles along axis (or globally) @@ -97,12 +92,11 @@ def hdquantiles(data, prob=list([.25,.5,.75]), axis=None, var=False,): result = _hd_1D(data, p, var) else: if data.ndim > 2: - raise ValueError("Array 'data' must be at most two dimensional, but got data.ndim = %d" % data.ndim) + raise ValueError("Array 'data' must be at most two dimensional, " + "but got data.ndim = %d" % data.ndim) result = ma.apply_along_axis(_hd_1D, axis, data, p, var) - # - return ma.fix_invalid(result, copy=False) -#.............................................................................. + return ma.fix_invalid(result, copy=False) def hdmedian(data, axis=-1, var=False): @@ -124,7 +118,6 @@ def hdmedian(data, axis=-1, var=False): return result.squeeze() -#.............................................................................. def hdquantiles_sd(data, prob=list([.25,.5,.75]), axis=None): """ The standard error of the Harrell-Davis quantile estimates by jackknife. @@ -153,10 +146,10 @@ def hdquantiles_sd(data, prob=list([.25,.5,.75]), axis=None): hdsd = np.empty(len(prob), float_) if n < 2: hdsd.flat = np.nan - #......... + vv = np.arange(n) / float(n-1) betacdf = beta.cdf - # + for (i,p) in enumerate(prob): _w = betacdf(vv, (n+1)*p, (n+1)*(1-p)) w = _w[1:] - _w[:-1] @@ -166,7 +159,7 @@ def hdquantiles_sd(data, prob=list([.25,.5,.75]), axis=None): mx_var = np.array(mx_.var(), copy=False, ndmin=1) * n / float(n-1) hdsd[i] = float(n-1) * np.sqrt(np.diag(mx_var).diagonal() / float(n)) return hdsd - # Initialization & checks --------- + # Initialization & checks data = ma.array(data, copy=False, dtype=float_) p = np.array(prob, copy=False, ndmin=1) # Computes quantiles along axis (or globally) @@ -174,15 +167,12 @@ def hdquantiles_sd(data, prob=list([.25,.5,.75]), axis=None): result = _hdsd_1D(data, p) else: if data.ndim > 2: - raise ValueError("Array 'data' must be at most two dimensional, but got data.ndim = %d" % data.ndim) + raise ValueError("Array 'data' must be at most two dimensional, " + "but got data.ndim = %d" % data.ndim) result = ma.apply_along_axis(_hdsd_1D, axis, data, p) - # - return ma.fix_invalid(result, copy=False).ravel() + return ma.fix_invalid(result, copy=False).ravel() -#####-------------------------------------------------------------------------- -#---- --- Confidence intervals --- -#####-------------------------------------------------------------------------- def trimmed_mean_ci(data, limits=(0.2,0.2), inclusive=(True,True), alpha=0.05, axis=None): @@ -198,9 +188,9 @@ def trimmed_mean_ci(data, limits=(0.2,0.2), inclusive=(True,True), Tuple of the percentages to cut on each side of the array, with respect to the number of unmasked data, as floats between 0. and 1. If ``n`` is the number of unmasked data before trimming, then - (``n`` * `limits[0]`)th smallest data and (``n`` * `limits[1]`)th + (``n * limits[0]``)th smallest data and (``n * limits[1]``)th largest data are masked. The total number of unmasked data after - trimming is ``n`` * (1. - sum(`limits`)). + trimming is ``n * (1. - sum(limits))``. The value of one limit can be set to None to indicate an open interval. Defaults to (0.2, 0.2). @@ -234,8 +224,6 @@ def trimmed_mean_ci(data, limits=(0.2,0.2), inclusive=(True,True), tppf = t.ppf(1-alpha/2.,df) return np.array((tmean - tppf*tstde, tmean+tppf*tstde)) -#.............................................................................. - def mjci(data, prob=[0.25,0.5,0.75], axis=None): """ @@ -258,7 +246,7 @@ def mjci(data, prob=[0.25,0.5,0.75], axis=None): n = data.size prob = (np.array(p) * n + 0.5).astype(int_) betacdf = beta.cdf - # + mj = np.empty(len(prob), float_) x = np.arange(1,n+1, dtype=float_) / n y = x - 1./n @@ -269,10 +257,12 @@ def mjci(data, prob=[0.25,0.5,0.75], axis=None): C2 = np.dot(W,data**2) mj[i] = np.sqrt(C2 - C1**2) return mj - # + data = ma.array(data, copy=False) if data.ndim > 2: - raise ValueError("Array 'data' must be at most two dimensional, but got data.ndim = %d" % data.ndim) + raise ValueError("Array 'data' must be at most two dimensional, " + "but got data.ndim = %d" % data.ndim) + p = np.array(prob, copy=False, ndmin=1) # Computes quantiles along axis (or globally) if (axis is None): @@ -280,8 +270,6 @@ def mjci(data, prob=[0.25,0.5,0.75], axis=None): else: return ma.apply_along_axis(_mjci_1D, axis, data, p) -#.............................................................................. - def mquantiles_cimj(data, prob=[0.25,0.50,0.75], alpha=0.05, axis=None): """ @@ -308,7 +296,6 @@ def mquantiles_cimj(data, prob=[0.25,0.50,0.75], alpha=0.05, axis=None): return (xq - z * smj, xq + z * smj) -#............................................................................. def median_cihs(data, alpha=0.05, axis=None): """ Computes the alpha-level confidence interval for the median of the data. @@ -353,12 +340,11 @@ def median_cihs(data, alpha=0.05, axis=None): result = _cihs_1D(data.compressed(), alpha) else: if data.ndim > 2: - raise ValueError("Array 'data' must be at most two dimensional, but got data.ndim = %d" % data.ndim) + raise ValueError("Array 'data' must be at most two dimensional, " + "but got data.ndim = %d" % data.ndim) result = ma.apply_along_axis(_cihs_1D, axis, data, alpha) - # - return result -#.............................................................................. + return result def compare_medians_ms(group_1, group_2, axis=None): @@ -453,14 +439,13 @@ def rsh(data, points=None): points = data else: points = np.array(points, copy=False, ndmin=1) + if data.ndim != 1: raise AttributeError("The input array should be 1D only !") + n = data.count() r = idealfourths(data, axis=None) h = 1.2 * (r[-1]-r[0]) / n**(1./5) nhi = (data[:,None] <= points[None,:] + h).sum(0) nlo = (data[:,None] < points[None,:] - h).sum(0) return (nhi-nlo) / (2.*n*h) - - -############################################################################### diff --git a/pywafo/src/wafo/stats/stats.py b/pywafo/src/wafo/stats/stats.py index 97291f5..8120a37 100644 --- a/pywafo/src/wafo/stats/stats.py +++ b/pywafo/src/wafo/stats/stats.py @@ -112,6 +112,7 @@ Correlation Functions pointbiserialr kendalltau linregress + theilslopes Inferential Stats ----------------- @@ -170,7 +171,7 @@ from __future__ import division, print_function, absolute_import import warnings import math -#from .six import xrange +from scipy.lib.six import xrange # friedmanchisquare patch uses python sum pysum = sum # save it before it gets overwritten @@ -188,25 +189,23 @@ try: from scipy.stats._rank import rankdata, tiecorrect except: rankdata = tiecorrect = None -__all__ = ['find_repeats', 'gmean', 'hmean', 'mode', - 'tmean', 'tvar', 'tmin', 'tmax', 'tstd', 'tsem', - 'moment', 'variation', 'skew', 'kurtosis', 'describe', - 'skewtest', 'kurtosistest', 'normaltest', 'jarque_bera', - 'itemfreq', 'scoreatpercentile', 'percentileofscore', - 'histogram', 'histogram2', 'cumfreq', 'relfreq', - 'obrientransform', 'signaltonoise', 'sem', 'zmap', 'zscore', - 'threshold', 'sigmaclip', 'trimboth', 'trim1', 'trim_mean', - 'f_oneway', 'pearsonr', 'fisher_exact', - 'spearmanr', 'pointbiserialr', 'kendalltau', 'linregress', - 'ttest_1samp', 'ttest_ind', 'ttest_rel', 'kstest', - 'chisquare', 'power_divergence', 'ks_2samp', 'mannwhitneyu', +__all__ = ['find_repeats', 'gmean', 'hmean', 'mode', 'tmean', 'tvar', + 'tmin', 'tmax', 'tstd', 'tsem', 'moment', 'variation', + 'skew', 'kurtosis', 'describe', 'skewtest', 'kurtosistest', + 'normaltest', 'jarque_bera', 'itemfreq', + 'scoreatpercentile', 'percentileofscore', 'histogram', + 'histogram2', 'cumfreq', 'relfreq', 'obrientransform', + 'signaltonoise', 'sem', 'zmap', 'zscore', 'threshold', + 'sigmaclip', 'trimboth', 'trim1', 'trim_mean', 'f_oneway', + 'pearsonr', 'fisher_exact', 'spearmanr', 'pointbiserialr', + 'kendalltau', 'linregress', 'theilslopes', 'ttest_1samp', + 'ttest_ind', 'ttest_rel', 'kstest', 'chisquare', + 'power_divergence', 'ks_2samp', 'mannwhitneyu', 'tiecorrect', 'ranksums', 'kruskal', 'friedmanchisquare', 'zprob', 'chisqprob', 'ksprob', 'fprob', 'betai', 'f_value_wilks_lambda', 'f_value', 'f_value_multivariate', - 'ss', 'square_of_sums', - 'fastsort', 'rankdata', - 'nanmean', 'nanstd', 'nanmedian', - ] + 'ss', 'square_of_sums', 'fastsort', 'rankdata', 'nanmean', + 'nanstd', 'nanmedian', ] def _chk_asarray(a, axis): @@ -250,19 +249,20 @@ def find_repeats(arr): Examples -------- - >>> sp.stats.find_repeats([2, 1, 2, 3, 2, 2, 5]) - (array([ 2. ]), array([ 4 ], dtype=int32) + >>> import scipy.stats as stats + >>> stats.find_repeats([2, 1, 2, 3, 2, 2, 5]) + (array([ 2. ]), array([ 4 ], dtype=int32) - >>> sp.stats.find_repeats([[10, 20, 1, 2], [5, 5, 4, 4]]) - (array([ 4., 5.]), array([2, 2], dtype=int32)) + >>> stats.find_repeats([[10, 20, 1, 2], [5, 5, 4, 4]]) + (array([ 4., 5.]), array([2, 2], dtype=int32)) """ - v1, v2, n = futil.dfreps(arr) - return v1[:n], v2[:n] + v1,v2, n = futil.dfreps(arr) + return v1[:n],v2[:n] -# -# NAN friendly functions -# +####### +### NAN friendly functions +######## def nanmean(x, axis=0): @@ -387,11 +387,20 @@ def _nanmedian(arr1d): # This only works on 1d arrays m : float The median. """ - cond = 1 - np.isnan(arr1d) - x = np.sort(np.compress(cond, arr1d, axis=-1)) - if x.size == 0: + x = arr1d.copy() + c = np.isnan(x) + s = np.where(c)[0] + if s.size == x.size: + warnings.warn("All-NaN slice encountered", RuntimeWarning) return np.nan - return np.median(x) + elif s.size != 0: + # select non-nans at end of array + enonan = x[-s.size:][~c[-s.size:]] + # fill nans in beginning of array with non-nans of end + x[s[:enonan.size]] = enonan + # slice nans away + x = x[:-s.size] + return np.median(x, overwrite_input=True) def nanmedian(x, axis=0): @@ -413,7 +422,7 @@ def nanmedian(x, axis=0): See Also -------- - nanstd, nanmean + nanstd, nanmean, numpy.nanmedian Examples -------- @@ -444,16 +453,17 @@ def nanmedian(x, axis=0): x, axis = _chk_asarray(x, axis) if x.ndim == 0: return float(x.item()) - x = x.copy() + if hasattr(np, 'nanmedian'): # numpy 1.9 faster for some cases + return np.nanmedian(x, axis) x = np.apply_along_axis(_nanmedian, axis, x) if x.ndim == 0: x = float(x.item()) return x -# -# CENTRAL TENDENCY ######## -# +##################################### +######## CENTRAL TENDENCY ######## +##################################### def gmean(a, axis=0, dtype=None): @@ -498,11 +508,10 @@ def gmean(a, axis=0, dtype=None): arrays automatically mask any non-finite values. """ - # if not an ndarray object attempt to convert it - if not isinstance(a, np.ndarray): + if not isinstance(a, np.ndarray): # if not an ndarray object attempt to convert it log_a = np.log(np.array(a, dtype=dtype)) elif dtype: # Must change the default dtype allowing array type - if isinstance(a, np.ma.MaskedArray): + if isinstance(a,np.ma.MaskedArray): log_a = np.log(np.ma.asarray(a, dtype=dtype)) else: log_a = np.log(np.asarray(a, dtype=dtype)) @@ -562,10 +571,9 @@ def hmean(a, axis=0, dtype=None): size = a.shape[0] else: size = a.shape[axis] - return size / np.sum(1.0 / a, axis=axis, dtype=dtype) + return size / np.sum(1.0/a, axis=axis, dtype=dtype) else: - raise ValueError( - "Harmonic mean only defined if all elements greater than zero") + raise ValueError("Harmonic mean only defined if all elements greater than zero") def mode(a, axis=0): @@ -610,11 +618,11 @@ def mode(a, axis=0): scores = np.unique(np.ravel(a)) # get ALL unique values testshape = list(a.shape) testshape[axis] = 1 - oldmostfreq = np.zeros(testshape) + oldmostfreq = np.zeros(testshape, dtype=a.dtype) oldcounts = np.zeros(testshape) for score in scores: template = (a == score) - counts = np.expand_dims(np.sum(template, axis), axis) + counts = np.expand_dims(np.sum(template, axis),axis) mostfrequent = np.where(counts > oldcounts, score, oldmostfreq) oldcounts = np.maximum(counts, oldcounts) oldmostfreq = mostfrequent @@ -702,7 +710,7 @@ def tmean(a, limits=None, inclusive=(True, True)): def masked_var(am): m = am.mean() - s = ma.add.reduce((am - m) ** 2) + s = ma.add.reduce((am - m)**2) n = am.count() - 1.0 return s / n @@ -743,7 +751,7 @@ def tvar(a, limits=None, inclusive=(True, True)): a = a.astype(float).ravel() if limits is None: n = len(a) - return a.var() * (n / (n - 1.)) + return a.var()*(n/(n-1.)) am = mask_to_limits(a, limits, inclusive) return masked_var(am) @@ -887,9 +895,9 @@ def tsem(a, limits=None, inclusive=(True, True)): return sd / np.sqrt(am.count()) -# -# MOMENTS ############# -# +##################################### +############ MOMENTS ############# +##################################### def moment(a, moment=1, axis=0): """ @@ -928,8 +936,8 @@ def moment(a, moment=1, axis=0): # the input was 1D, so return a scalar instead of a rank-0 array return np.float64(0.0) else: - mn = np.expand_dims(np.mean(a, axis), axis) - s = np.power((a - mn), moment) + mn = np.expand_dims(np.mean(a,axis), axis) + s = np.power((a-mn), moment) return np.mean(s, axis) @@ -953,7 +961,7 @@ def variation(a, axis=0): """ a, axis = _chk_asarray(a, axis) - return a.std(axis) / a.mean(axis) + return a.std(axis)/a.mean(axis) def skew(a, axis=0, bias=True): @@ -989,18 +997,18 @@ def skew(a, axis=0, bias=True): York. 2000. """ - a, axis = _chk_asarray(a, axis) + a, axis = _chk_asarray(a,axis) n = a.shape[axis] m2 = moment(a, 2, axis) m3 = moment(a, 3, axis) zero = (m2 == 0) - vals = np.where(zero, 0, m3 / m2 ** 1.5) + vals = np.where(zero, 0, m3 / m2**1.5) if not bias: can_correct = (n > 2) & (m2 > 0) if can_correct.any(): m2 = np.extract(can_correct, m2) m3 = np.extract(can_correct, m3) - nval = np.sqrt((n - 1.0) * n) / (n - 2.0) * m3 / m2 ** 1.5 + nval = np.sqrt((n-1.0)*n)/(n-2.0)*m3/m2**1.5 np.place(vals, can_correct, nval) if vals.ndim == 0: return vals.item() @@ -1047,12 +1055,12 @@ def kurtosis(a, axis=0, fisher=True, bias=True): """ a, axis = _chk_asarray(a, axis) n = a.shape[axis] - m2 = moment(a, 2, axis) - m4 = moment(a, 4, axis) + m2 = moment(a,2,axis) + m4 = moment(a,4,axis) zero = (m2 == 0) olderr = np.seterr(all='ignore') try: - vals = np.where(zero, 0, m4 / m2 ** 2.0) + vals = np.where(zero, 0, m4 / m2**2.0) finally: np.seterr(**olderr) @@ -1061,10 +1069,8 @@ def kurtosis(a, axis=0, fisher=True, bias=True): if can_correct.any(): m2 = np.extract(can_correct, m2) m4 = np.extract(can_correct, m4) - nval = 1.0 / \ - (n - 2) / (n - 3) * \ - ((n * n - 1.0) * m4 / m2 ** 2.0 - 3 * (n - 1) ** 2.0) - np.place(vals, can_correct, nval + 3.0) + nval = 1.0/(n-2)/(n-3)*((n*n-1.0)*m4/m2**2.0-3*(n-1)**2.0) + np.place(vals, can_correct, nval+3.0) if vals.ndim == 0: vals = vals.item() # array scalar @@ -1075,17 +1081,19 @@ def kurtosis(a, axis=0, fisher=True, bias=True): return vals -def describe(a, axis=0): +def describe(a, axis=0, ddof=1): """ Computes several descriptive statistics of the passed array. Parameters ---------- a : array_like - data - axis : int or None - axis along which statistics are calculated. If axis is None, then data - array is raveled. The default axis is zero. + Input data. + axis : int, optional + Axis along which statistics are calculated. If axis is None, then data + array is raveled. The default axis is zero. + ddof : int, optional + Delta degrees of freedom. Default is 1. Returns ------- @@ -1107,22 +1115,21 @@ def describe(a, axis=0): See Also -------- - skew - kurtosis + skew, kurtosis """ a, axis = _chk_asarray(a, axis) n = a.shape[axis] mm = (np.min(a, axis=axis), np.max(a, axis=axis)) m = np.mean(a, axis=axis) - v = np.var(a, axis=axis, ddof=1) + v = np.var(a, axis=axis, ddof=ddof) sk = skew(a, axis) kurt = kurtosis(a, axis) return n, mm, m, v, sk, kurt -# -# NORMALITY TESTS ########## -# +##################################### +######## NORMALITY TESTS ########## +##################################### def skewtest(a, axis=0): @@ -1210,20 +1217,17 @@ def kurtosistest(a, axis=0): "kurtosistest only valid for n>=20 ... continuing anyway, n=%i" % int(n)) b2 = kurtosis(a, axis, fisher=False) - E = 3.0 * (n - 1) / (n + 1) - varb2 = 24.0 * n * \ - (n - 2) * (n - 3) / ((n + 1) * (n + 1) * (n + 3) * (n + 5)) - x = (b2 - E) / np.sqrt(varb2) - sqrtbeta1 = 6.0 * (n * n - 5 * n + 2) / ((n + 7) * (n + 9)) * np.sqrt((6.0 * (n + 3) * (n + 5)) / - (n * (n - 2) * (n - 3))) - A = 6.0 + 8.0 / sqrtbeta1 * \ - (2.0 / sqrtbeta1 + np.sqrt(1 + 4.0 / (sqrtbeta1 ** 2))) - term1 = 1 - 2 / (9.0 * A) - denom = 1 + x * np.sqrt(2 / (A - 4.0)) + E = 3.0*(n-1) / (n+1) + varb2 = 24.0*n*(n-2)*(n-3) / ((n+1)*(n+1.)*(n+3)*(n+5)) + x = (b2-E)/np.sqrt(varb2) + sqrtbeta1 = 6.0*(n*n-5*n+2)/((n+7)*(n+9)) * np.sqrt((6.0*(n+3)*(n+5)) / + (n*(n-2)*(n-3))) + A = 6.0 + 8.0/sqrtbeta1 * (2.0/sqrtbeta1 + np.sqrt(1+4.0/(sqrtbeta1**2))) + term1 = 1 - 2/(9.0*A) + denom = 1 + x*np.sqrt(2/(A-4.0)) denom = np.where(denom < 0, 99, denom) - term2 = np.where( - denom < 0, term1, np.power((1 - 2.0 / A) / denom, 1 / 3.0)) - Z = (term1 - term2) / np.sqrt(2 / (9.0 * A)) + term2 = np.where(denom < 0, term1, np.power((1-2.0/A)/denom,1/3.0)) + Z = (term1 - term2) / np.sqrt(2/(9.0*A)) Z = np.where(denom == 99, 0, Z) if Z.ndim == 0: Z = Z[()] @@ -1268,10 +1272,10 @@ def normaltest(a, axis=0): """ a, axis = _chk_asarray(a, axis) - s, _p = skewtest(a, axis) - k, _p = kurtosistest(a, axis) - k2 = s * s + k * k - return k2, chisqprob(k2, 2) + s, _ = skewtest(a, axis) + k, _ = kurtosistest(a, axis) + k2 = s*s + k*k + return k2, chisqprob(k2,2) def jarque_bera(x): @@ -1322,18 +1326,17 @@ def jarque_bera(x): mu = x.mean() diffx = x - mu - skewness = (1 / n * np.sum(diffx ** 3)) / \ - (1 / n * np.sum(diffx ** 2)) ** (3 / 2.) - kurtosis = (1 / n * np.sum(diffx ** 4)) / (1 / n * np.sum(diffx ** 2)) ** 2 - jb_value = n / 6 * (skewness ** 2 + (kurtosis - 3) ** 2 / 4) + skewness = (1 / n * np.sum(diffx**3)) / (1 / n * np.sum(diffx**2))**(3 / 2.) + kurtosis = (1 / n * np.sum(diffx**4)) / (1 / n * np.sum(diffx**2))**2 + jb_value = n / 6 * (skewness**2 + (kurtosis - 3)**2 / 4) p = 1 - distributions.chi2.cdf(jb_value, 2) return jb_value, p -# -# FREQUENCY FUNCTIONS ####### -# +##################################### +###### FREQUENCY FUNCTIONS ####### +##################################### def itemfreq(a): """ @@ -1411,12 +1414,20 @@ def scoreatpercentile(a, per, limit=(), interpolation_method='fraction', Returns ------- - score : float (or sequence of floats) - Score at percentile. + score : float or ndarray + Score at percentile(s). See Also -------- - percentileofscore + percentileofscore, numpy.percentile + + Notes + ----- + This function will become obsolete in the future. + For Numpy 1.9 and higher, `numpy.percentile` provides all the functionality + that `scoreatpercentile` provides. And it's significantly faster. + Therefore it's recommended to use `numpy.percentile` for users that have + numpy >= 1.9. Examples -------- @@ -1426,17 +1437,19 @@ def scoreatpercentile(a, per, limit=(), interpolation_method='fraction', 49.5 """ - # adapted from NumPy's percentile function + # adapted from NumPy's percentile function. When we require numpy >= 1.8, + # the implementation of this function can be replaced by np.percentile. a = np.asarray(a) + if a.size == 0: + # empty array, return nan(s) with shape matching `per` + if np.isscalar(per): + return np.nan + else: + return np.ones(np.asarray(per).shape, dtype=np.float64) * np.nan if limit: a = a[(limit[0] <= a) & (a <= limit[1])] - if per == 0: - return a.min(axis=axis) - elif per == 100: - return a.max(axis=axis) - sorted = np.sort(a, axis=axis) if axis is None: axis = 0 @@ -1447,8 +1460,9 @@ def scoreatpercentile(a, per, limit=(), interpolation_method='fraction', # handle sequence of per's without calling sort multiple times def _compute_qth_percentile(sorted, per, interpolation_method, axis): if not np.isscalar(per): - return [_compute_qth_percentile(sorted, i, interpolation_method, axis) - for i in per] + score = [_compute_qth_percentile(sorted, i, interpolation_method, axis) + for i in per] + return np.array(score) if (per < 0) or (per > 100): raise ValueError("percentile must be in the range [0, 100]") @@ -1482,7 +1496,7 @@ def _compute_qth_percentile(sorted, per, interpolation_method, axis): weights.shape = wshape sumval = weights.sum() - # Use np.add.reduce to coerce data type + # Use np.add.reduce (== np.sum but a little faster) to coerce data type return np.add.reduce(sorted[indexer] * weights, axis=axis) / sumval @@ -1605,11 +1619,10 @@ def histogram2(a, bins): # comment: probably obsoleted by numpy.histogram() n = np.searchsorted(np.sort(a), bins) n = np.concatenate([n, [len(a)]]) - return n[1:] - n[:-1] + return n[1:]-n[:-1] -def histogram(a, numbins=10, defaultlimits=None, weights=None, - printextras=False): +def histogram(a, numbins=10, defaultlimits=None, weights=None, printextras=False): """ Separates the range into several bins and returns the number of instances in each bin. @@ -1624,7 +1637,7 @@ def histogram(a, numbins=10, defaultlimits=None, weights=None, The lower and upper values for the range of the histogram. If no value is given, a range slightly larger then the range of the values in a is used. Specifically ``(a.min() - s, a.max() + s)``, - where ``s = (1/2)(a.max() - a.min()) / (numbins - 1)``. + where ``s = (1/2)(a.max() - a.min()) / (numbins - 1)``. weights : array_like, optional The weights for each value in `a`. Default is None, which gives each value a weight of 1.0 @@ -1711,21 +1724,22 @@ def cumfreq(a, numbins=10, defaultreallimits=None, weights=None): Examples -------- + >>> import scipy.stats as stats >>> x = [1, 4, 2, 1, 3, 1] - >>> cumfreqs, lowlim, binsize, extrapoints = sp.stats.cumfreq(x, numbins=4) + >>> cumfreqs, lowlim, binsize, extrapoints = stats.cumfreq(x, numbins=4) >>> cumfreqs array([ 3., 4., 5., 6.]) >>> cumfreqs, lowlim, binsize, extrapoints = \ - ... sp.stats.cumfreq(x, numbins=4, defaultreallimits=(1.5, 5)) + ... stats.cumfreq(x, numbins=4, defaultreallimits=(1.5, 5)) >>> cumfreqs array([ 1., 2., 3., 3.]) >>> extrapoints 3 """ - h, l, b, e = histogram(a, numbins, defaultreallimits, weights=weights) - cumhist = np.cumsum(h * 1, axis=0) - return cumhist, l, b, e + h,l,b,e = histogram(a, numbins, defaultreallimits, weights=weights) + cumhist = np.cumsum(h*1, axis=0) + return cumhist,l,b,e def relfreq(a, numbins=10, defaultreallimits=None, weights=None): @@ -1742,7 +1756,7 @@ def relfreq(a, numbins=10, defaultreallimits=None, weights=None): The lower and upper values for the range of the histogram. If no value is given, a range slightly larger then the range of the values in a is used. Specifically ``(a.min() - s, a.max() + s)``, - where ``s = (1/2)(a.max() - a.min()) / (numbins - 1)``. + where ``s = (1/2)(a.max() - a.min()) / (numbins - 1)``. weights : array_like, optional The weights for each value in `a`. Default is None, which gives each value a weight of 1.0 @@ -1760,8 +1774,9 @@ def relfreq(a, numbins=10, defaultreallimits=None, weights=None): Examples -------- + >>> import scipy.stats as stats >>> a = np.array([1, 4, 2, 1, 3, 1]) - >>> relfreqs, lowlim, binsize, extrapoints = sp.stats.relfreq(a, numbins=4) + >>> relfreqs, lowlim, binsize, extrapoints = stats.relfreq(a, numbins=4) >>> relfreqs array([ 0.5 , 0.16666667, 0.16666667, 0.16666667]) >>> np.sum(relfreqs) # relative frequencies should add up to 1 @@ -1773,9 +1788,9 @@ def relfreq(a, numbins=10, defaultreallimits=None, weights=None): return h, l, b, e -# -# VARIABILITY FUNCTIONS ##### -# +##################################### +###### VARIABILITY FUNCTIONS ##### +##################################### def obrientransform(*args): """ @@ -1836,7 +1851,7 @@ def obrientransform(*args): a = np.asarray(arg) n = len(a) mu = np.mean(a) - sq = (a - mu) ** 2 + sq = (a - mu)**2 sumsq = sq.sum() # The O'Brien transform. @@ -1888,7 +1903,7 @@ def signaltonoise(a, axis=0, ddof=0): a = np.asanyarray(a) m = a.mean(axis) sd = a.std(axis=axis, ddof=ddof) - return np.where(sd == 0, 0, m / sd) + return np.where(sd == 0, 0, m/sd) def sem(a, axis=0, ddof=1): @@ -1936,7 +1951,7 @@ def sem(a, axis=0, ddof=1): """ a, axis = _chk_asarray(a, axis) n = a.shape[axis] - s = np.std(a, axis=axis, ddof=ddof) / np.sqrt(n) # JP check normalization + s = np.std(a, axis=axis, ddof=ddof) / np.sqrt(n) return s @@ -1997,7 +2012,7 @@ def zscore(a, axis=0, ddof=0): sstd = a.std(axis=axis, ddof=ddof) if axis and mns.ndim < a.ndim: return ((a - np.expand_dims(mns, axis=axis)) / - np.expand_dims(sstd, axis=axis)) + np.expand_dims(sstd,axis=axis)) else: return (a - mns) / sstd @@ -2048,14 +2063,14 @@ def zmap(scores, compare, axis=0, ddof=0): sstd = compare.std(axis=axis, ddof=ddof) if axis and mns.ndim < compare.ndim: return ((scores - np.expand_dims(mns, axis=axis)) / - np.expand_dims(sstd, axis=axis)) + np.expand_dims(sstd,axis=axis)) else: return (scores - mns) / sstd -# -# TRIMMING FUNCTIONS ####### -# +##################################### +####### TRIMMING FUNCTIONS ####### +##################################### def threshold(a, threshmin=None, threshmax=None, newval=0): """ @@ -2159,10 +2174,10 @@ def sigmaclip(a, low=4., high=4.): c_std = c.std() c_mean = c.mean() size = c.size - critlower = c_mean - c_std * low - critupper = c_mean + c_std * high + critlower = c_mean - c_std*low + critupper = c_mean + c_std*high c = c[(c > critlower) & (c < critupper)] - delta = size - c.size + delta = size-c.size return c, critlower, critupper @@ -2249,9 +2264,9 @@ def trim1(a, proportiontocut, tail='right'): a = asarray(a) if tail.lower() == 'right': lowercut = 0 - uppercut = len(a) - int(proportiontocut * len(a)) + uppercut = len(a) - int(proportiontocut*len(a)) elif tail.lower() == 'left': - lowercut = int(proportiontocut * len(a)) + lowercut = int(proportiontocut*len(a)) uppercut = len(a) return a[lowercut:uppercut] @@ -2370,14 +2385,15 @@ def f_oneway(*args): .. [2] Heiman, G.W. Research Methods in Statistics. 2002. """ - args = list(map(np.asarray, args)) # convert to an numpy array - na = len(args) # ANOVA on 'na' groups, each in it's own array + args = [np.asarray(arg, dtype=float) for arg in args] + na = len(args) # ANOVA on 'na' groups, each in it's own array alldata = np.concatenate(args) bign = len(alldata) sstot = ss(alldata) - (square_of_sums(alldata) / float(bign)) ssbn = 0 for a in args: ssbn += square_of_sums(a) / float(len(a)) + ssbn -= (square_of_sums(alldata) / float(bign)) sswn = sstot - ssbn dfbn = na - 1 @@ -2385,7 +2401,7 @@ def f_oneway(*args): msb = ssbn / float(dfbn) msw = sswn / float(dfwn) f = msb / msw - prob = fprob(dfbn, dfwn, f) + prob = special.fdtrc(dfbn, dfwn, f) # equivalent to stats.f.sf return f, prob @@ -2430,7 +2446,7 @@ def pearsonr(x, y): n = len(x) mx = x.mean() my = y.mean() - xm, ym = x - mx, y - my + xm, ym = x-mx, y-my r_num = np.add.reduce(xm * ym) r_den = np.sqrt(ss(xm) * ss(ym)) r = r_num / r_den @@ -2438,12 +2454,12 @@ def pearsonr(x, y): # Presumably, if abs(r) > 1, then it is only some small artifact of floating # point arithmetic. r = max(min(r, 1.0), -1.0) - df = n - 2 + df = n-2 if abs(r) == 1.0: prob = 0.0 else: - t_squared = r * r * (df / ((1.0 - r) * (1.0 + r))) - prob = betai(0.5 * df, 0.5, df / (df + t_squared)) + t_squared = r*r * (df / ((1.0 - r) * (1.0 + r))) + prob = betai(0.5*df, 0.5, df / (df + t_squared)) return r, prob @@ -2506,8 +2522,7 @@ def fisher_exact(table, alternative='two-sided'): """ hypergeom = distributions.hypergeom - # int32 is not enough for the algorithm - c = np.asarray(table, dtype=np.int64) + c = np.asarray(table, dtype=np.int64) # int32 is not enough for the algorithm if not c.shape == (2, 2): raise ValueError("The input `table` must be of shape (2, 2).") @@ -2519,14 +2534,14 @@ def fisher_exact(table, alternative='two-sided'): # the odds ratio is NaN. return np.nan, 1.0 - if c[1, 0] > 0 and c[0, 1] > 0: - oddsratio = c[0, 0] * c[1, 1] / float(c[1, 0] * c[0, 1]) + if c[1,0] > 0 and c[0,1] > 0: + oddsratio = c[0,0] * c[1,1] / float(c[1,0] * c[0,1]) else: oddsratio = np.inf - n1 = c[0, 0] + c[0, 1] - n2 = c[1, 0] + c[1, 1] - n = c[0, 0] + c[1, 0] + n1 = c[0,0] + c[0,1] + n2 = c[1,0] + c[1,1] + n = c[0,0] + c[1,0] def binary_search(n, n1, n2, side): """Binary search for where to begin lower/upper halves in two-sided @@ -2570,28 +2585,28 @@ def fisher_exact(table, alternative='two-sided'): return guess if alternative == 'less': - pvalue = hypergeom.cdf(c[0, 0], n1 + n2, n1, n) + pvalue = hypergeom.cdf(c[0,0], n1 + n2, n1, n) elif alternative == 'greater': # Same formula as the 'less' case, but with the second column. - pvalue = hypergeom.cdf(c[0, 1], n1 + n2, n1, c[0, 1] + c[1, 1]) + pvalue = hypergeom.cdf(c[0,1], n1 + n2, n1, c[0,1] + c[1,1]) elif alternative == 'two-sided': mode = int(float((n + 1) * (n1 + 1)) / (n1 + n2 + 2)) - pexact = hypergeom.pmf(c[0, 0], n1 + n2, n1, n) + pexact = hypergeom.pmf(c[0,0], n1 + n2, n1, n) pmode = hypergeom.pmf(mode, n1 + n2, n1, n) epsilon = 1 - 1e-4 - if float(np.abs(pexact - pmode)) / np.abs(np.max(pexact, pmode)) <= 1 - epsilon: + if np.abs(pexact - pmode) / np.maximum(pexact, pmode) <= 1 - epsilon: return oddsratio, 1. - elif c[0, 0] < mode: - plower = hypergeom.cdf(c[0, 0], n1 + n2, n1, n) + elif c[0,0] < mode: + plower = hypergeom.cdf(c[0,0], n1 + n2, n1, n) if hypergeom.pmf(n, n1 + n2, n1, n) > pexact / epsilon: return oddsratio, plower guess = binary_search(n, n1, n2, "upper") pvalue = plower + hypergeom.sf(guess - 1, n1 + n2, n1, n) else: - pupper = hypergeom.sf(c[0, 0] - 1, n1 + n2, n1, n) + pupper = hypergeom.sf(c[0,0] - 1, n1 + n2, n1, n) if hypergeom.pmf(0, n1 + n2, n1, n) > pexact / epsilon: return oddsratio, pupper @@ -2701,24 +2716,24 @@ def spearmanr(a, b=None, axis=0): """ a, axisout = _chk_asarray(a, axis) - ar = np.apply_along_axis(rankdata, axisout, a) + ar = np.apply_along_axis(rankdata,axisout,a) br = None - if not b is None: + if b is not None: b, axisout = _chk_asarray(b, axis) - br = np.apply_along_axis(rankdata, axisout, b) + br = np.apply_along_axis(rankdata,axisout,b) n = a.shape[axisout] - rs = np.corrcoef(ar, br, rowvar=axisout) + rs = np.corrcoef(ar,br,rowvar=axisout) olderr = np.seterr(divide='ignore') # rs can have elements equal to 1 try: - t = rs * np.sqrt((n - 2) / ((rs + 1.0) * (1.0 - rs))) + t = rs * np.sqrt((n-2) / ((rs+1.0)*(1.0-rs))) finally: np.seterr(**olderr) - prob = distributions.t.sf(np.abs(t), n - 2) * 2 + prob = distributions.t.sf(np.abs(t),n-2)*2 - if rs.shape == (2, 2): - return rs[1, 0], prob[1, 0] + if rs.shape == (2,2): + return rs[1,0], prob[1,0] else: return rs, prob @@ -2780,13 +2795,13 @@ def pointbiserialr(x, y): y1m = y1.mean() # phat - phat**2 is more stable than phat*(1-phat) - rpb = (y1m - y0m) * np.sqrt(phat - phat ** 2) / y.std() + rpb = (y1m - y0m) * np.sqrt(phat - phat**2) / y.std() - df = n - 2 + df = n-2 # fixme: see comment about TINY in pearsonr() TINY = 1e-20 - t = rpb * np.sqrt(df / ((1.0 - rpb + TINY) * (1.0 + rpb + TINY))) - prob = betai(0.5 * df, 0.5, df / (df + t * t)) + t = rpb*np.sqrt(df/((1.0-rpb+TINY)*(1.0+rpb+TINY))) + prob = betai(0.5*df, 0.5, df/(df+t*t)) return rpb, prob @@ -2838,9 +2853,10 @@ def kendalltau(x, y, initial_lexsort=True): Examples -------- + >>> import scipy.stats as stats >>> x1 = [12, 2, 1, 12, 2] >>> x2 = [1, 4, 7, 1, 0] - >>> tau, p_value = sp.stats.kendalltau(x1, x2) + >>> tau, p_value = stats.kendalltau(x1, x2) >>> tau -0.47140452079103173 >>> p_value @@ -2850,6 +2866,10 @@ def kendalltau(x, y, initial_lexsort=True): x = np.asarray(x).ravel() y = np.asarray(y).ravel() + + if not x.size or not y.size: + return (np.nan, np.nan) # Return NaN if arrays are empty + n = np.int64(len(x)) temp = list(range(n)) # support structure used by mergesort # this closure recursively sorts sections of perm[] by comparing @@ -2861,11 +2881,11 @@ def kendalltau(x, y, initial_lexsort=True): if length == 1: return 0 if length == 2: - if y[perm[offs]] <= y[perm[offs + 1]]: + if y[perm[offs]] <= y[perm[offs+1]]: return 0 t = perm[offs] - perm[offs] = perm[offs + 1] - perm[offs + 1] = t + perm[offs] = perm[offs+1] + perm[offs+1] = t return 1 length0 = length // 2 length1 = length - length0 @@ -2878,7 +2898,7 @@ def kendalltau(x, y, initial_lexsort=True): i = j = k = 0 while j < length0 or k < length1: if k >= length1 or (j < length0 and y[perm[offs + j]] <= - y[perm[middle + k]]): + y[perm[middle + k]]): temp[i] = perm[offs + j] d = i - j j += 1 @@ -2889,7 +2909,7 @@ def kendalltau(x, y, initial_lexsort=True): if d > 0: exchcnt += d i += 1 - perm[offs:offs + length] = temp[0:length] + perm[offs:offs+length] = temp[0:length] return exchcnt # initial sort on values of x and, if tied, on values of y @@ -2913,7 +2933,7 @@ def kendalltau(x, y, initial_lexsort=True): # compute ties in x first = 0 u = 0 - for i in xrange(1, n): + for i in xrange(1,n): if x[perm[first]] != x[perm[i]]: u += ((i - first) * (i - first - 1)) // 2 first = i @@ -2924,7 +2944,7 @@ def kendalltau(x, y, initial_lexsort=True): # compute ties in y after mergesort with counting first = 0 v = 0 - for i in xrange(1, n): + for i in xrange(1,n): if y[perm[first]] != y[perm[i]]: v += ((i - first) * (i - first - 1)) // 2 first = i @@ -3005,13 +3025,13 @@ def linregress(x, y=None): x = asarray(x) y = asarray(y) n = len(x) - xmean = np.mean(x, None) - ymean = np.mean(y, None) + xmean = np.mean(x,None) + ymean = np.mean(y,None) # average sum of squares: ssxm, ssxym, ssyxm, ssym = np.cov(x, y, bias=1).flat r_num = ssxym - r_den = np.sqrt(ssxm * ssym) + r_den = np.sqrt(ssxm*ssym) if r_den == 0.0: r = 0.0 else: @@ -3022,18 +3042,135 @@ def linregress(x, y=None): elif (r < -1.0): r = -1.0 - df = n - 2 - t = r * np.sqrt(df / ((1.0 - r + TINY) * (1.0 + r + TINY))) - prob = distributions.t.sf(np.abs(t), df) * 2 + df = n-2 + t = r*np.sqrt(df/((1.0-r+TINY)*(1.0+r+TINY))) + prob = distributions.t.sf(np.abs(t),df)*2 slope = r_num / ssxm - intercept = ymean - slope * xmean - sterrest = np.sqrt((1 - r * r) * ssym / ssxm / df) + intercept = ymean - slope*xmean + sterrest = np.sqrt((1-r*r)*ssym / ssxm / df) return slope, intercept, r, prob, sterrest -# -# INFERENTIAL STATISTICS ##### -# +def theilslopes(y, x=None, alpha=0.95): + r""" + Computes the Theil-Sen estimator for a set of points (x, y). + + `theilslopes` implements a method for robust linear regression. It + computes the slope as the median of all slopes between paired values. + + Parameters + ---------- + y : array_like + Dependent variable. + x : {None, array_like}, optional + Independent variable. If None, use ``arange(len(y))`` instead. + alpha : float + Confidence degree between 0 and 1. Default is 95% confidence. + Note that `alpha` is symmetric around 0.5, i.e. both 0.1 and 0.9 are + interpreted as "find the 90% confidence interval". + + Returns + ------- + medslope : float + Theil slope. + medintercept : float + Intercept of the Theil line, as ``median(y) - medslope*median(x)``. + lo_slope : float + Lower bound of the confidence interval on `medslope`. + up_slope : float + Upper bound of the confidence interval on `medslope`. + + Notes + ----- + The implementation of `theilslopes` follows [1]_. The intercept is + not defined in [1]_, and here it is defined as ``median(y) - + medslope*median(x)``, which is given in [3]_. Other definitions of + the intercept exist in the literature. A confidence interval for + the intercept is not given as this question is not addressed in + [1]_. + + References + ---------- + .. [1] P.K. Sen, "Estimates of the regression coefficient based on Kendall's tau", + J. Am. Stat. Assoc., Vol. 63, pp. 1379-1389, 1968. + .. [2] H. Theil, "A rank-invariant method of linear and polynomial + regression analysis I, II and III", Nederl. Akad. Wetensch., Proc. + 53:, pp. 386-392, pp. 521-525, pp. 1397-1412, 1950. + .. [3] W.L. Conover, "Practical nonparametric statistics", 2nd ed., + John Wiley and Sons, New York, pp. 493. + + Examples + -------- + >>> from scipy import stats + >>> import matplotlib.pyplot as plt + + >>> x = np.linspace(-5, 5, num=150) + >>> y = x + np.random.normal(size=x.size) + >>> y[11:15] += 10 # add outliers + >>> y[-5:] -= 7 + + Compute the slope, intercept and 90% confidence interval. For comparison, + also compute the least-squares fit with `linregress`: + + >>> res = stats.theilslopes(y, x, 0.90) + >>> lsq_res = stats.linregress(x, y) + + Plot the results. The Theil-Sen regression line is shown in red, with the + dashed red lines illustrating the confidence interval of the slope (note + that the dashed red lines are not the confidence interval of the regression + as the confidence interval of the intercept is not included). The green + line shows the least-squares fit for comparison. + + >>> fig = plt.figure() + >>> ax = fig.add_subplot(111) + >>> ax.plot(x, y, 'b.') + >>> ax.plot(x, res[1] + res[0] * x, 'r-') + >>> ax.plot(x, res[1] + res[2] * x, 'r--') + >>> ax.plot(x, res[1] + res[3] * x, 'r--') + >>> ax.plot(x, lsq_res[1] + lsq_res[0] * x, 'g-') + >>> plt.show() + + """ + y = np.asarray(y).flatten() + if x is None: + x = np.arange(len(y), dtype=float) + else: + x = np.asarray(x, dtype=float).flatten() + if len(x) != len(y): + raise ValueError("Incompatible lengths ! (%s<>%s)" % (len(y),len(x))) + + # Compute sorted slopes only when deltax > 0 + deltax = x[:, np.newaxis] - x + deltay = y[:, np.newaxis] - y + slopes = deltay[deltax > 0] / deltax[deltax > 0] + slopes.sort() + medslope = np.median(slopes) + medinter = np.median(y) - medslope * np.median(x) + # Now compute confidence intervals + if alpha > 0.5: + alpha = 1. - alpha + + z = distributions.norm.ppf(alpha / 2.) + # This implements (2.6) from Sen (1968) + _, nxreps = find_repeats(x) + _, nyreps = find_repeats(y) + nt = len(slopes) # N in Sen (1968) + ny = len(y) # n in Sen (1968) + # Equation 2.6 in Sen (1968): + sigsq = 1/18. * (ny * (ny-1) * (2*ny+5) - + np.sum(k * (k-1) * (2*k + 5) for k in nxreps) - + np.sum(k * (k-1) * (2*k + 5) for k in nyreps)) + # Find the confidence interval indices in `slopes` + sigma = np.sqrt(sigsq) + Ru = min(int(np.round((nt - z*sigma)/2.)), len(slopes)-1) + Rl = max(int(np.round((nt + z*sigma)/2.)) - 1, 0) + delta = slopes[[Rl, Ru]] + return medslope, medinter, delta[0], delta[1] + + +##################################### +##### INFERENTIAL STATISTICS ##### +##################################### def ttest_1samp(a, popmean, axis=0): """ @@ -3103,10 +3240,9 @@ def ttest_1samp(a, popmean, axis=0): return t, prob -def _ttest_finish(df, t): +def _ttest_finish(df,t): """Common code between all 3 t-test functions.""" - # use np.abs to get upper tail - prob = distributions.t.sf(np.abs(t), df) * 2 + prob = distributions.t.sf(np.abs(t), df) * 2 # use np.abs to get upper tail if t.ndim == 0: t = t[()] @@ -3217,8 +3353,7 @@ def ttest_ind(a, b, axis=0, equal_var=True): else: vn1 = v1 / n1 vn2 = v2 / n2 - df = ((vn1 + vn2) ** 2) / \ - ((vn1 ** 2) / (n1 - 1) + (vn2 ** 2) / (n2 - 1)) + df = ((vn1 + vn2)**2) / ((vn1**2) / (n1 - 1) + (vn2**2) / (n2 - 1)) # If df is undefined, variances are zero (assumes n1 > 0 & n2 > 0). # Hence it doesn't matter what df is as long as it's not NaN. @@ -3423,8 +3558,8 @@ def kstest(rvs, cdf, args=(), N=20, alternative='two-sided', mode='approx'): if isinstance(cdf, string_types): cdf = getattr(distributions, cdf).cdf if callable(rvs): - kwds = {'size': N} - vals = np.sort(rvs(*args, **kwds)) + kwds = {'size':N} + vals = np.sort(rvs(*args,**kwds)) else: vals = np.sort(rvs) N = len(vals) @@ -3435,25 +3570,25 @@ def kstest(rvs, cdf, args=(), N=20, alternative='two-sided', mode='approx'): alternative = 'two-sided' if alternative in ['two-sided', 'greater']: - Dplus = (np.arange(1.0, N + 1) / N - cdfvals).max() + Dplus = (np.arange(1.0, N+1)/N - cdfvals).max() if alternative == 'greater': - return Dplus, distributions.ksone.sf(Dplus, N) + return Dplus, distributions.ksone.sf(Dplus,N) if alternative in ['two-sided', 'less']: - Dmin = (cdfvals - np.arange(0.0, N) / N).max() + Dmin = (cdfvals - np.arange(0.0, N)/N).max() if alternative == 'less': - return Dmin, distributions.ksone.sf(Dmin, N) + return Dmin, distributions.ksone.sf(Dmin,N) if alternative == 'two-sided': - D = np.max([Dplus, Dmin]) + D = np.max([Dplus,Dmin]) if mode == 'asymp': - return D, distributions.kstwobign.sf(D * np.sqrt(N)) + return D, distributions.kstwobign.sf(D*np.sqrt(N)) if mode == 'approx': - pval_two = distributions.kstwobign.sf(D * np.sqrt(N)) - if N > 2666 or pval_two > 0.80 - N * 0.3 / 1000.0: - return D, distributions.kstwobign.sf(D * np.sqrt(N)) + pval_two = distributions.kstwobign.sf(D*np.sqrt(N)) + if N > 2666 or pval_two > 0.80 - N*0.3/1000.0: + return D, distributions.kstwobign.sf(D*np.sqrt(N)) else: - return D, distributions.ksone.sf(D, N) * 2 + return D, distributions.ksone.sf(D,N)*2 # Map from names to lambda_ values used in power_divergence(). @@ -3463,7 +3598,7 @@ _power_div_lambda_names = { "freeman-tukey": -0.5, "mod-log-likelihood": -1, "neyman": -2, - "cressie-read": 2 / 3, + "cressie-read": 2/3, } @@ -3592,7 +3727,7 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): are uniform and given by the mean of the observed frequencies. Here we perform a G-test (i.e. use the log-likelihood ratio statistic): - >>> power_divergence([16, 18, 16, 14, 12, 12], method='log-likelihood') + >>> power_divergence([16, 18, 16, 14, 12, 12], lambda_='log-likelihood') (2.006573162632538, 0.84823476779463769) The expected frequencies can be given with the `f_exp` argument: @@ -3646,7 +3781,7 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): if lambda_ not in _power_div_lambda_names: names = repr(list(_power_div_lambda_names.keys()))[1:-1] raise ValueError("invalid string for lambda_: {0!r}. Valid strings " - "are {1}".format(lambda_, names)) + "are {1}".format(lambda_, names)) lambda_ = _power_div_lambda_names[lambda_] elif lambda_ is None: lambda_ = 1 @@ -3674,7 +3809,7 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): # cases of lambda_. if lambda_ == 1: # Pearson's chi-squared statistic - terms = (f_obs - f_exp) ** 2 / f_exp + terms = (f_obs - f_exp)**2 / f_exp elif lambda_ == 0: # Log-likelihood ratio (i.e. G-test) terms = 2.0 * special.xlogy(f_obs, f_obs / f_exp) @@ -3683,7 +3818,7 @@ def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): terms = 2.0 * special.xlogy(f_exp, f_exp / f_obs) else: # General Cressie-Read power divergence. - terms = f_obs * ((f_obs / f_exp) ** lambda_ - 1) + terms = f_obs * ((f_obs / f_exp)**lambda_ - 1) terms /= 0.5 * lambda_ * (lambda_ + 1) stat = terms.sum(axis=axis) @@ -3873,20 +4008,20 @@ def ks_2samp(data1, data2): """ data1, data2 = map(asarray, (data1, data2)) - #n1 = data1.shape[0] - #n2 = data2.shape[0] + n1 = data1.shape[0] + n2 = data2.shape[0] n1 = len(data1) n2 = len(data2) data1 = np.sort(data1) data2 = np.sort(data2) - data_all = np.concatenate([data1, data2]) - cdf1 = np.searchsorted(data1, data_all, side='right') / (1.0 * n1) - cdf2 = (np.searchsorted(data2, data_all, side='right')) / (1.0 * n2) - d = np.max(np.absolute(cdf1 - cdf2)) + data_all = np.concatenate([data1,data2]) + cdf1 = np.searchsorted(data1,data_all,side='right')/(1.0*n1) + cdf2 = (np.searchsorted(data2,data_all,side='right'))/(1.0*n2) + d = np.max(np.absolute(cdf1-cdf2)) # Note: d absolute not signed distance - en = np.sqrt(n1 * n2 / float(n1 + n2)) + en = np.sqrt(n1*n2/float(n1+n2)) try: - prob = ksprob((en + 0.12 + 0.11 / en) * d) + prob = distributions.kstwobign.sf((en + 0.12 + 0.11 / en) * d) except: prob = 1.0 return d, prob @@ -3927,24 +4062,22 @@ def mannwhitneyu(x, y, use_continuity=True): y = asarray(y) n1 = len(x) n2 = len(y) - ranked = rankdata(np.concatenate((x, y))) + ranked = rankdata(np.concatenate((x,y))) rankx = ranked[0:n1] # get the x-ranks - # calc U for x - u1 = n1 * n2 + (n1 * (n1 + 1)) / 2.0 - np.sum(rankx, axis=0) - u2 = n1 * n2 - u1 # remainder is U for y - bigu = max(u1, u2) - smallu = min(u1, u2) + u1 = n1*n2 + (n1*(n1+1))/2.0 - np.sum(rankx,axis=0) # calc U for x + u2 = n1*n2 - u1 # remainder is U for y + bigu = max(u1,u2) + smallu = min(u1,u2) T = tiecorrect(ranked) if T == 0: raise ValueError('All numbers are identical in amannwhitneyu') - sd = np.sqrt(T * n1 * n2 * (n1 + n2 + 1) / 12.0) + sd = np.sqrt(T*n1*n2*(n1+n2+1)/12.0) if use_continuity: # normal approximation for prob calc with continuity correction - z = abs((bigu - 0.5 - n1 * n2 / 2.0) / sd) + z = abs((bigu-0.5-n1*n2/2.0) / sd) else: - # normal approximation for prob calc - z = abs((bigu - n1 * n2 / 2.0) / sd) + z = abs((bigu-n1*n2/2.0) / sd) # normal approximation for prob calc return smallu, distributions.norm.sf(z) # (1.0 - zprob(z)) @@ -3980,16 +4113,16 @@ def ranksums(x, y): .. [1] http://en.wikipedia.org/wiki/Wilcoxon_rank-sum_test """ - x, y = map(np.asarray, (x, y)) + x,y = map(np.asarray, (x, y)) n1 = len(x) n2 = len(y) - alldata = np.concatenate((x, y)) + alldata = np.concatenate((x,y)) ranked = rankdata(alldata) x = ranked[:n1] y = ranked[n1:] - s = np.sum(x, axis=0) - expected = n1 * (n1 + n2 + 1) / 2.0 - z = (s - expected) / np.sqrt(n1 * n2 * (n1 + n2 + 1) / 12.0) + s = np.sum(x,axis=0) + expected = n1*(n1+n2+1) / 2.0 + z = (s - expected) / np.sqrt(n1*n2*(n1+n2+1)/12.0) prob = 2 * distributions.norm.sf(abs(z)) return z, prob @@ -4047,7 +4180,7 @@ def kruskal(*args): j = np.insert(np.cumsum(n), 0, 0) ssbn = 0 for i in range(na): - ssbn += square_of_sums(ranked[j[i]:j[i + 1]]) / float(n[i]) + ssbn += square_of_sums(ranked[j[i]:j[i+1]]) / float(n[i]) totaln = np.sum(n) h = 12.0 / (totaln * (totaln + 1)) * ssbn - 3 * (totaln + 1) @@ -4094,8 +4227,7 @@ def friedmanchisquare(*args): """ k = len(args) if k < 3: - raise ValueError( - '\nLess than 3 levels. Friedman test not appropriate.\n') + raise ValueError('\nLess than 3 levels. Friedman test not appropriate.\n') n = len(args[0]) for i in range(1, k): @@ -4111,21 +4243,23 @@ def friedmanchisquare(*args): # Handle ties ties = 0 for i in range(len(data)): - _replist, repnum = find_repeats(array(data[i])) + replist, repnum = find_repeats(array(data[i])) for t in repnum: - ties += t * (t * t - 1) - c = 1 - ties / float(k * (k * k - 1) * n) + ties += t*(t*t-1) + c = 1 - ties / float(k*(k*k-1)*n) - ssbn = pysum(pysum(data) ** 2) - chisq = (12.0 / (k * n * (k + 1)) * ssbn - 3 * n * (k + 1)) / c - return chisq, chisqprob(chisq, k - 1) + ssbn = pysum(pysum(data)**2) + chisq = (12.0 / (k*n*(k+1)) * ssbn - 3*n*(k+1)) / c + return chisq, chisqprob(chisq,k-1) -# -# PROBABILITY CALCULATIONS #### -# +##################################### +#### PROBABILITY CALCULATIONS #### +##################################### -zprob = special.ndtr +zprob = np.deprecate(message='zprob is deprecated in scipy 0.14, ' + 'use norm.cdf or special.ndtr instead\n', + old_name='zprob')(special.ndtr) def chisqprob(chisq, df): @@ -4147,10 +4281,15 @@ def chisqprob(chisq, df): distribution with degrees of freedom `df`. """ - return special.chdtrc(df, chisq) + return special.chdtrc(df,chisq) + +ksprob = np.deprecate(message='ksprob is deprecated in scipy 0.14, ' + 'use stats.kstwobign.sf or special.kolmogorov instead\n', + old_name='ksprob')(special.kolmogorov) -ksprob = special.kolmogorov -fprob = special.fdtrc +fprob = np.deprecate(message='fprob is deprecated in scipy 0.14, ' + 'use stats.f.sf or special.fdtrc instead\n', + old_name='fprob')(special.fdtrc) def betai(a, b, x): @@ -4184,9 +4323,9 @@ def betai(a, b, x): return special.betainc(a, b, x) -# -# ANOVA CALCULATIONS ####### -# +##################################### +####### ANOVA CALCULATIONS ####### +##################################### def f_value_wilks_lambda(ER, EF, dfnum, dfden, a, b): """Calculation of Wilks lambda F-statistic for multivarite data, per @@ -4197,13 +4336,12 @@ def f_value_wilks_lambda(ER, EF, dfnum, dfden, a, b): if isinstance(EF, (int, float)): EF = array([[EF]]) lmbda = linalg.det(EF) / linalg.det(ER) - if (a - 1) ** 2 + (b - 1) ** 2 == 5: + if (a-1)**2 + (b-1)**2 == 5: q = 1 else: - q = np.sqrt( - ((a - 1) ** 2 * (b - 1) ** 2 - 2) / ((a - 1) ** 2 + (b - 1) ** 2 - 5)) - n_um = (1 - lmbda ** (1.0 / q)) * (a - 1) * (b - 1) - d_en = lmbda ** (1.0 / q) / (n_um * q - 0.5 * (a - 1) * (b - 1) + 1) + q = np.sqrt(((a-1)**2*(b-1)**2 - 2) / ((a-1)**2 + (b-1)**2 - 5)) + n_um = (1 - lmbda**(1.0/q))*(a-1)*(b-1) + d_en = lmbda**(1.0/q) / (n_um*q - 0.5*(a-1)*(b-1) + 1) return n_um / d_en @@ -4232,7 +4370,7 @@ def f_value(ER, EF, dfR, dfF): F-statistic : float """ - return ((ER - EF) / float(dfR - dfF) / (EF / float(dfF))) + return ((ER-EF)/float(dfR-dfF) / (EF/float(dfF))) def f_value_multivariate(ER, EF, dfnum, dfden): @@ -4267,9 +4405,9 @@ def f_value_multivariate(ER, EF, dfnum, dfden): return n_um / d_en -# -# SUPPORT FUNCTIONS ######## -# +##################################### +####### SUPPORT FUNCTIONS ######## +##################################### def ss(a, axis=0): """ @@ -4307,7 +4445,7 @@ def ss(a, axis=0): """ a, axis = _chk_asarray(a, axis) - return np.sum(a * a, axis) + return np.sum(a*a, axis) def square_of_sums(a, axis=0): @@ -4342,11 +4480,11 @@ def square_of_sums(a, axis=0): """ a, axis = _chk_asarray(a, axis) - s = np.sum(a, axis) + s = np.sum(a,axis) if not np.isscalar(s): - return s.astype(float) * s + return s.astype(float)*s else: - return float(s) * s + return float(s)*s def fastsort(a): diff --git a/pywafo/src/wafo/stats/tests/common_tests.py b/pywafo/src/wafo/stats/tests/common_tests.py index 726d485..3f390c4 100644 --- a/pywafo/src/wafo/stats/tests/common_tests.py +++ b/pywafo/src/wafo/stats/tests/common_tests.py @@ -6,12 +6,11 @@ import warnings import numpy as np import numpy.testing as npt -#from scipy.lib._version import NumpyVersion -from scipy import stats +from scipy.lib._version import NumpyVersion +from wafo import stats -#NUMPY_BELOW_1_7 = NumpyVersion(np.__version__) < '1.7.0' -NUMPY_BELOW_1_7 = np.__version__ < '1.7.0' +NUMPY_BELOW_1_7 = NumpyVersion(np.__version__) < '1.7.0' def check_normalization(distfn, args, distname): @@ -27,7 +26,7 @@ def check_normalization(distfn, args, distname): normalization_expect = distfn.expect(lambda x: 1, args=args) npt.assert_allclose(normalization_expect, 1.0, atol=atol, rtol=rtol, - err_msg=distname, verbose=True) + err_msg=distname, verbose=True) normalization_cdf = distfn.cdf(distfn.b, *args) npt.assert_allclose(normalization_cdf, 1.0) @@ -38,48 +37,47 @@ def check_moment(distfn, arg, m, v, msg): m2 = distfn.moment(2, *arg) if not np.isinf(m): npt.assert_almost_equal(m1, m, decimal=10, err_msg=msg + - ' - 1st moment') + ' - 1st moment') else: # or np.isnan(m1), npt.assert_(np.isinf(m1), - msg + ' - 1st moment -infinite, m1=%s' % str(m1)) + msg + ' - 1st moment -infinite, m1=%s' % str(m1)) if not np.isinf(v): npt.assert_almost_equal(m2 - m1 * m1, v, decimal=10, err_msg=msg + - ' - 2ndt moment') + ' - 2ndt moment') else: # or np.isnan(m2), npt.assert_(np.isinf(m2), - msg + ' - 2nd moment -infinite, m2=%s' % str(m2)) + msg + ' - 2nd moment -infinite, m2=%s' % str(m2)) def check_mean_expect(distfn, arg, m, msg): if np.isfinite(m): m1 = distfn.expect(lambda x: x, arg) npt.assert_almost_equal(m1, m, decimal=5, err_msg=msg + - ' - 1st moment (expect)') + ' - 1st moment (expect)') def check_var_expect(distfn, arg, m, v, msg): if np.isfinite(v): - m2 = distfn.expect(lambda x: x * x, arg) - npt.assert_almost_equal(m2, v + m * m, decimal=5, err_msg=msg + - ' - 2st moment (expect)') + m2 = distfn.expect(lambda x: x*x, arg) + npt.assert_almost_equal(m2, v + m*m, decimal=5, err_msg=msg + + ' - 2st moment (expect)') def check_skew_expect(distfn, arg, m, v, s, msg): if np.isfinite(s): - m3e = distfn.expect(lambda x: np.power(x - m, 3), arg) + m3e = distfn.expect(lambda x: np.power(x-m, 3), arg) npt.assert_almost_equal(m3e, s * np.power(v, 1.5), - decimal=5, err_msg=msg + ' - skew') + decimal=5, err_msg=msg + ' - skew') else: npt.assert_(np.isnan(s)) def check_kurt_expect(distfn, arg, m, v, k, msg): if np.isfinite(k): - m4e = distfn.expect(lambda x: np.power(x - m, 4), arg) - npt.assert_allclose( - m4e, (k + 3.) * np.power(v, 2), atol=1e-5, rtol=1e-5, - err_msg=msg + ' - kurtosis') + m4e = distfn.expect(lambda x: np.power(x-m, 4), arg) + npt.assert_allclose(m4e, (k + 3.) * np.power(v, 2), atol=1e-5, rtol=1e-5, + err_msg=msg + ' - kurtosis') else: npt.assert_(np.isnan(k)) @@ -106,7 +104,7 @@ def check_edge_support(distfn, args): npt.assert_equal(distfn.logsf(x, *args), [0.0, -np.inf]) if isinstance(distfn, stats.rv_discrete): - x = [distfn.a - 1, distfn.b] + x = [distfn.a-1, distfn.b] npt.assert_equal(distfn.ppf([0.0, 1.0], *args), x) npt.assert_equal(distfn.isf([0.0, 1.0], *args), x[::-1]) @@ -116,7 +114,7 @@ def check_edge_support(distfn, args): def check_named_args(distfn, x, shape_args, defaults, meths): - # Check calling w/ named arguments. + ## Check calling w/ named arguments. # check consistency of shapes, numargs and _parse signature signature = inspect.getargspec(distfn._parse_args) @@ -124,8 +122,7 @@ def check_named_args(distfn, x, shape_args, defaults, meths): npt.assert_(signature.keywords is None) npt.assert_(signature.defaults == defaults) - # self, a, b, loc=0, scale=1 - shape_argnames = signature.args[1:-len(defaults)] + shape_argnames = signature.args[1:-len(defaults)] # self, a, b, loc=0, scale=1 if distfn.shapes: shapes_ = distfn.shapes.replace(',', ' ').split() else: @@ -144,7 +141,7 @@ def check_named_args(distfn, x, shape_args, defaults, meths): k.update({names.pop(): a.pop()}) v = [meth(x, *a, **k) for meth in meths] npt.assert_array_equal(vals, v) - if not 'n' in k.keys(): + if 'n' not in k.keys(): # `n` is first parameter of moment(), so can't be used as named arg with warnings.catch_warnings(): warnings.simplefilter("ignore", UserWarning) @@ -154,3 +151,4 @@ def check_named_args(distfn, x, shape_args, defaults, meths): # unknown arguments should not go through: k.update({'kaboom': 42}) npt.assert_raises(TypeError, distfn.cdf, x, **k) + diff --git a/pywafo/src/wafo/stats/tests/test_binned_statistic.py b/pywafo/src/wafo/stats/tests/test_binned_statistic.py index f814329..26cc4be 100644 --- a/pywafo/src/wafo/stats/tests/test_binned_statistic.py +++ b/pywafo/src/wafo/stats/tests/test_binned_statistic.py @@ -1,4 +1,5 @@ from __future__ import division, print_function, absolute_import + import numpy as np from numpy.testing import assert_array_almost_equal, run_module_suite from scipy.stats import \ @@ -19,7 +20,7 @@ class TestBinnedStatistic(object): x = self.x v = self.v - count1, edges1, _bc = binned_statistic(x, v, 'count', bins=10) + count1, edges1, bc = binned_statistic(x, v, 'count', bins=10) count2, edges2 = np.histogram(x, bins=10) assert_array_almost_equal(count1, count2) @@ -29,7 +30,7 @@ class TestBinnedStatistic(object): x = self.x v = self.v - sum1, edges1, _bc = binned_statistic(x, v, 'sum', bins=10) + sum1, edges1, bc = binned_statistic(x, v, 'sum', bins=10) sum2, edges2 = np.histogram(x, bins=10, weights=v) assert_array_almost_equal(sum1, sum2) @@ -39,8 +40,8 @@ class TestBinnedStatistic(object): x = self.x v = self.v - stat1, edges1, _bc = binned_statistic(x, v, 'mean', bins=10) - stat2, edges2, _bc = binned_statistic(x, v, np.mean, bins=10) + stat1, edges1, bc = binned_statistic(x, v, 'mean', bins=10) + stat2, edges2, bc = binned_statistic(x, v, np.mean, bins=10) assert_array_almost_equal(stat1, stat2) assert_array_almost_equal(edges1, edges2) @@ -49,8 +50,8 @@ class TestBinnedStatistic(object): x = self.x v = self.v - stat1, edges1, _bc = binned_statistic(x, v, 'std', bins=10) - stat2, edges2, _bc = binned_statistic(x, v, np.std, bins=10) + stat1, edges1, bc = binned_statistic(x, v, 'std', bins=10) + stat2, edges2, bc = binned_statistic(x, v, np.std, bins=10) assert_array_almost_equal(stat1, stat2) assert_array_almost_equal(edges1, edges2) @@ -59,8 +60,8 @@ class TestBinnedStatistic(object): x = self.x v = self.v - stat1, edges1, _bc = binned_statistic(x, v, 'median', bins=10) - stat2, edges2, _bc = binned_statistic(x, v, np.median, bins=10) + stat1, edges1, bc = binned_statistic(x, v, 'median', bins=10) + stat2, edges2, bc = binned_statistic(x, v, np.median, bins=10) assert_array_almost_equal(stat1, stat2) assert_array_almost_equal(edges1, edges2) @@ -69,7 +70,7 @@ class TestBinnedStatistic(object): x = self.x[:20] v = self.v[:20] - count1, _edges1, bc = binned_statistic(x, v, 'count', bins=3) + count1, edges1, bc = binned_statistic(x, v, 'count', bins=3) bc2 = np.array([3, 2, 1, 3, 2, 3, 3, 3, 3, 1, 1, 3, 3, 1, 2, 3, 1, 1, 2, 1]) @@ -86,7 +87,7 @@ class TestBinnedStatistic(object): mean, bins, _ = binned_statistic(x[:15], data[:15]) mean_range, bins_range, _ = binned_statistic(x, data, range=[(0, 14)]) - mean_range2, bins_range2, _ = binned_statistic(x, data, range=[(0, 14)]) + mean_range2, bins_range2, _ = binned_statistic(x, data, range=(0, 14)) assert_array_almost_equal(mean, mean_range) assert_array_almost_equal(bins, bins_range) @@ -98,8 +99,7 @@ class TestBinnedStatistic(object): y = self.y v = self.v - count1, binx1, biny1, _bc = binned_statistic_2d(x, y, v, 'count', - bins=5) + count1, binx1, biny1, bc = binned_statistic_2d(x, y, v, 'count', bins=5) count2, binx2, biny2 = np.histogram2d(x, y, bins=5) assert_array_almost_equal(count1, count2) @@ -111,7 +111,7 @@ class TestBinnedStatistic(object): y = self.y v = self.v - sum1, binx1, biny1, _bc = binned_statistic_2d(x, y, v, 'sum', bins=5) + sum1, binx1, biny1, bc = binned_statistic_2d(x, y, v, 'sum', bins=5) sum2, binx2, biny2 = np.histogram2d(x, y, bins=5, weights=v) assert_array_almost_equal(sum1, sum2) @@ -123,8 +123,8 @@ class TestBinnedStatistic(object): y = self.y v = self.v - stat1, binx1, biny1, _b = binned_statistic_2d(x, y, v, 'mean', bins=5) - stat2, binx2, biny2, _b = binned_statistic_2d(x, y, v, np.mean, bins=5) + stat1, binx1, biny1, bc = binned_statistic_2d(x, y, v, 'mean', bins=5) + stat2, binx2, biny2, bc = binned_statistic_2d(x, y, v, np.mean, bins=5) assert_array_almost_equal(stat1, stat2) assert_array_almost_equal(binx1, binx2) @@ -135,8 +135,8 @@ class TestBinnedStatistic(object): y = self.y v = self.v - stat1, binx1, biny1, _bc = binned_statistic_2d(x, y, v, 'std', bins=5) - stat2, binx2, biny2, _bc = binned_statistic_2d(x, y, v, np.std, bins=5) + stat1, binx1, biny1, bc = binned_statistic_2d(x, y, v, 'std', bins=5) + stat2, binx2, biny2, bc = binned_statistic_2d(x, y, v, np.std, bins=5) assert_array_almost_equal(stat1, stat2) assert_array_almost_equal(binx1, binx2) @@ -147,9 +147,8 @@ class TestBinnedStatistic(object): y = self.y v = self.v - stat1, binx1, biny1, _ = binned_statistic_2d(x, y, v, 'median', bins=5) - stat2, binx2, biny2, _ = binned_statistic_2d(x, y, v, np.median, - bins=5) + stat1, binx1, biny1, bc = binned_statistic_2d(x, y, v, 'median', bins=5) + stat2, binx2, biny2, bc = binned_statistic_2d(x, y, v, np.median, bins=5) assert_array_almost_equal(stat1, stat2) assert_array_almost_equal(binx1, binx2) @@ -160,8 +159,7 @@ class TestBinnedStatistic(object): y = self.y[:20] v = self.v[:20] - count1, _binx1, _biny1, bc = binned_statistic_2d(x, y, v, 'count', - bins=3) + count1, binx1, biny1, bc = binned_statistic_2d(x, y, v, 'count', bins=3) bc2 = np.array([17, 11, 6, 16, 11, 17, 18, 17, 17, 7, 6, 18, 16, 6, 11, 16, 6, 6, 11, 8]) @@ -175,7 +173,7 @@ class TestBinnedStatistic(object): X = self.X v = self.v - count1, edges1, _bc = binned_statistic_dd(X, v, 'count', bins=3) + count1, edges1, bc = binned_statistic_dd(X, v, 'count', bins=3) count2, edges2 = np.histogramdd(X, bins=3) assert_array_almost_equal(count1, count2) @@ -185,7 +183,7 @@ class TestBinnedStatistic(object): X = self.X v = self.v - sum1, edges1, _bc = binned_statistic_dd(X, v, 'sum', bins=3) + sum1, edges1, bc = binned_statistic_dd(X, v, 'sum', bins=3) sum2, edges2 = np.histogramdd(X, bins=3, weights=v) assert_array_almost_equal(sum1, sum2) @@ -195,8 +193,8 @@ class TestBinnedStatistic(object): X = self.X v = self.v - stat1, edges1, _bc = binned_statistic_dd(X, v, 'mean', bins=3) - stat2, edges2, _bc = binned_statistic_dd(X, v, np.mean, bins=3) + stat1, edges1, bc = binned_statistic_dd(X, v, 'mean', bins=3) + stat2, edges2, bc = binned_statistic_dd(X, v, np.mean, bins=3) assert_array_almost_equal(stat1, stat2) assert_array_almost_equal(edges1, edges2) @@ -205,8 +203,8 @@ class TestBinnedStatistic(object): X = self.X v = self.v - stat1, edges1, _bc = binned_statistic_dd(X, v, 'std', bins=3) - stat2, edges2, _bc = binned_statistic_dd(X, v, np.std, bins=3) + stat1, edges1, bc = binned_statistic_dd(X, v, 'std', bins=3) + stat2, edges2, bc = binned_statistic_dd(X, v, np.std, bins=3) assert_array_almost_equal(stat1, stat2) assert_array_almost_equal(edges1, edges2) @@ -215,8 +213,8 @@ class TestBinnedStatistic(object): X = self.X v = self.v - stat1, edges1, _bc = binned_statistic_dd(X, v, 'median', bins=3) - stat2, edges2, _bc = binned_statistic_dd(X, v, np.median, bins=3) + stat1, edges1, bc = binned_statistic_dd(X, v, 'median', bins=3) + stat2, edges2, bc = binned_statistic_dd(X, v, np.median, bins=3) assert_array_almost_equal(stat1, stat2) assert_array_almost_equal(edges1, edges2) @@ -225,7 +223,7 @@ class TestBinnedStatistic(object): X = self.X[:20] v = self.v[:20] - count1, _edges1, bc = binned_statistic_dd(X, v, 'count', bins=3) + count1, edges1, bc = binned_statistic_dd(X, v, 'count', bins=3) bc2 = np.array([63, 33, 86, 83, 88, 67, 57, 33, 42, 41, 82, 83, 92, 32, 36, 91, 43, 87, 81, 81]) @@ -237,5 +235,4 @@ class TestBinnedStatistic(object): if __name__ == "__main__": - #unittest.main() run_module_suite() diff --git a/pywafo/src/wafo/stats/tests/test_continuous_basic.py b/pywafo/src/wafo/stats/tests/test_continuous_basic.py index be2868c..9a4d662 100644 --- a/pywafo/src/wafo/stats/tests/test_continuous_basic.py +++ b/pywafo/src/wafo/stats/tests/test_continuous_basic.py @@ -13,6 +13,8 @@ from wafo.stats.tests.common_tests import (check_normalization, check_moment, check_entropy, check_private_entropy, NUMPY_BELOW_1_7, check_edge_support, check_named_args) +from wafo.stats._distr_params import distcont + """ Test all continuous distributions. @@ -26,98 +28,6 @@ not for numerically exact results. DECIMAL = 5 # specify the precision of the tests # increased from 0 to 5 -distcont = [ - ['alpha', (3.5704770516650459,)], - ['anglit', ()], - ['arcsine', ()], - ['beta', (2.3098496451481823, 0.62687954300963677)], - ['betaprime', (5, 6)], - ['bradford', (0.29891359763170633,)], - ['burr', (10.5, 4.3)], - ['cauchy', ()], - ['chi', (78,)], - ['chi2', (55,)], - ['cosine', ()], - ['dgamma', (1.1023326088288166,)], - ['dweibull', (2.0685080649914673,)], - ['erlang', (10,)], - ['expon', ()], - ['exponpow', (2.697119160358469,)], - ['exponweib', (2.8923945291034436, 1.9505288745913174)], - ['f', (29, 18)], - ['fatiguelife', (29,)], # correction numargs = 1 - ['fisk', (3.0857548622253179,)], - ['foldcauchy', (4.7164673455831894,)], - ['foldnorm', (1.9521253373555869,)], - ['frechet_l', (3.6279911255583239,)], - ['frechet_r', (1.8928171603534227,)], - ['gamma', (1.9932305483800778,)], - ['gausshyper', (13.763771604130699, 3.1189636648681431, - 2.5145980350183019, 5.1811649903971615)], # veryslow - ['genexpon', (9.1325976465418908, 16.231956600590632, 3.2819552690843983)], - ['genextreme', (-0.1,)], - ['gengamma', (4.4162385429431925, 3.1193091679242761)], - ['genhalflogistic', (0.77274727809929322,)], - ['genlogistic', (0.41192440799679475,)], - ['genpareto', (0.1,)], # use case with finite moments - ['gilbrat', ()], - ['gompertz', (0.94743713075105251,)], - ['gumbel_l', ()], - ['gumbel_r', ()], - ['halfcauchy', ()], - ['halflogistic', ()], - ['halfnorm', ()], - ['hypsecant', ()], - ['invgamma', (4.0668996136993067,)], - ['invgauss', (0.14546264555347513,)], - ['invweibull', (10.58,)], - ['johnsonsb', (4.3172675099141058, 3.1837781130785063)], - ['johnsonsu', (2.554395574161155, 2.2482281679651965)], - ['ksone', (1000,)], # replace 22 by 100 to avoid failing range, ticket 956 - ['kstwobign', ()], - ['laplace', ()], - ['levy', ()], - ['levy_l', ()], -# ['levy_stable', (0.35667405469844993, -# -0.67450531578494011)], #NotImplementedError - # rvs not tested - ['loggamma', (0.41411931826052117,)], - ['logistic', ()], - ['loglaplace', (3.2505926592051435,)], - ['lognorm', (0.95368226960575331,)], - ['lomax', (1.8771398388773268,)], - ['maxwell', ()], - ['mielke', (10.4, 3.6)], - ['nakagami', (4.9673794866666237,)], - ['ncf', (27, 27, 0.41578441799226107)], - ['nct', (14, 0.24045031331198066)], - ['ncx2', (21, 1.0560465975116415)], - ['norm', ()], - ['pareto', (2.621716532144454,)], - ['pearson3', (0.1,)], - ['powerlaw', (1.6591133289905851,)], - ['powerlognorm', (2.1413923530064087, 0.44639540782048337)], - ['powernorm', (4.4453652254590779,)], - ['rayleigh', ()], - ['rdist', (0.9,)], # feels also slow - ['recipinvgauss', (0.63004267809369119,)], - ['reciprocal', (0.0062309367010521255, 1.0062309367010522)], - ['rice', (0.7749725210111873,)], - ['semicircular', ()], - ['t', (2.7433514990818093,)], - ['triang', (0.15785029824528218,)], - ['truncexpon', (4.6907725456810478,)], - ['truncnorm', (-1.0978730080013919, 2.7306754109031979)], - ['truncnorm', (0.1, 2.)], - ['tukeylambda', (3.1321477856738267,)], - ['uniform', ()], - ['vonmises', (3.9939042581071398,)], - ['vonmises_line', (3.9939042581071398,)], - ['wald', ()], - ['weibull_max', (2.8687961709100187,)], - ['weibull_min', (1.7866166930421596,)], - ['wrapcauchy', (0.031071279018614728,)]] - ## Last four of these fail all around. Need to be checked distcont_extra = [ ['betaprime', (100, 86)], @@ -159,7 +69,7 @@ distmissing = ['wald', 'gausshyper', 'genexpon', 'rv_continuous', 'johnsonsb', 'truncexpon', 'rice', 'invgauss', 'invgamma', 'powerlognorm'] -distmiss = [[dist, args] for dist, args in distcont if dist in distmissing] +distmiss = [[dist,args] for dist,args in distcont if dist in distmissing] distslow = ['rdist', 'gausshyper', 'recipinvgauss', 'ksone', 'genexpon', 'vonmises', 'vonmises_line', 'mielke', 'semicircular', 'cosine', 'invweibull', 'powerlognorm', 'johnsonsu', 'kstwobign'] @@ -182,11 +92,12 @@ def _silence_fp_errors(func): def test_cont_basic(): # this test skips slow distributions with warnings.catch_warnings(): -# warnings.filterwarnings('ignore', -# category=integrate.IntegrationWarning) + warnings.filterwarnings('ignore', category=integrate.IntegrationWarning) for distname, arg in distcont[:]: if distname in distslow: continue + if distname is 'levy_stable': + continue distfn = getattr(stats, distname) np.random.seed(765456) sn = 500 @@ -231,19 +142,21 @@ def test_cont_basic(): yield knf(distname == 'truncnorm')(check_ppf_private), distfn, \ arg, distname + @npt.dec.slow def test_cont_basic_slow(): # same as above for slow distributions with warnings.catch_warnings(): -# warnings.filterwarnings('ignore', -# category=integrate.IntegrationWarning) + warnings.filterwarnings('ignore', category=integrate.IntegrationWarning) for distname, arg in distcont[:]: if distname not in distslow: continue + if distname is 'levy_stable': + continue distfn = getattr(stats, distname) np.random.seed(765456) sn = 500 - rvs = distfn.rvs(size=sn, *arg) + rvs = distfn.rvs(size=sn,*arg) sm = rvs.mean() sv = rvs.var() m, v = distfn.stats(*arg) @@ -287,12 +200,13 @@ def test_cont_basic_slow(): @npt.dec.slow def test_moments(): with warnings.catch_warnings(): -# warnings.filterwarnings('ignore', -# category=integrate.IntegrationWarning) + warnings.filterwarnings('ignore', category=integrate.IntegrationWarning) knf = npt.dec.knownfailureif fail_normalization = set(['vonmises', 'ksone']) fail_higher = set(['vonmises', 'ksone', 'ncf']) for distname, arg in distcont[:]: + if distname is 'levy_stable': + continue distfn = getattr(stats, distname) m, v, s, k = distfn.stats(*arg, moments='mvsk') cond1, cond2 = distname in fail_normalization, distname in fail_higher @@ -316,130 +230,125 @@ def check_sample_meanvar_(distfn, arg, m, v, sm, sv, sn, msg): check_sample_var(sv, sn, v) -def check_sample_mean(sm, v, n, popmean): +def check_sample_mean(sm,v,n, popmean): # from stats.stats.ttest_1samp(a, popmean): # Calculates the t-obtained for the independent samples T-test on ONE group # of scores a, given a population mean. # # Returns: t-value, two-tailed prob - df = n - 1 - svar = ((n - 1) * v) / float(df) # looks redundant - t = (sm - popmean) / np.sqrt(svar * (1.0 / n)) - prob = stats.betai(0.5 * df, 0.5, df / (df + t * t)) - - # return t,prob - npt.assert_(prob > 0.01, 'mean fail, t,prob = %f, %f, m, sm=%f,%f' % - (t, prob, popmean, sm)) - - -def check_sample_var(sv, n, popvar): - # two-sided chisquare test for sample variance equal to hypothesized - # variance - df = n - 1 - chi2 = (n - 1) * popvar / float(popvar) - pval = stats.chisqprob(chi2, df) * 2 - npt.assert_(pval > 0.01, 'var fail, t, pval = %f, %f, v, sv=%f, %f' % - (chi2, pval, popvar, sv)) - - -def check_cdf_ppf(distfn, arg, msg): + df = n-1 + svar = ((n-1)*v) / float(df) # looks redundant + t = (sm-popmean) / np.sqrt(svar*(1.0/n)) + prob = stats.betai(0.5*df, 0.5, df/(df+t*t)) + + # return t,prob + npt.assert_(prob > 0.01, 'mean fail, t,prob = %f, %f, m, sm=%f,%f' % + (t, prob, popmean, sm)) + + +def check_sample_var(sv,n, popvar): + # two-sided chisquare test for sample variance equal to hypothesized variance + df = n-1 + chi2 = (n-1)*popvar/float(popvar) + pval = stats.chisqprob(chi2,df)*2 + npt.assert_(pval > 0.01, 'var fail, t, pval = %f, %f, v, sv=%f, %f' % + (chi2,pval,popvar,sv)) + + +def check_cdf_ppf(distfn,arg,msg): values = [0.001, 0.5, 0.999] npt.assert_almost_equal(distfn.cdf(distfn.ppf(values, *arg), *arg), values, decimal=DECIMAL, err_msg=msg + ' - cdf-ppf roundtrip') -def check_sf_isf(distfn, arg, msg): - npt.assert_almost_equal(distfn.sf(distfn.isf([0.1, 0.5, 0.9], *arg), *arg), - [0.1, 0.5, 0.9], decimal=DECIMAL, err_msg=msg + - ' - sf-isf roundtrip') - npt.assert_almost_equal(distfn.cdf([0.1, 0.9], *arg), - 1.0 - distfn.sf([0.1, 0.9], *arg), - decimal=DECIMAL, err_msg=msg + - ' - cdf-sf relationship') - - -def check_pdf(distfn, arg, msg): - # compares pdf at median with numerical derivative of cdf - median = distfn.ppf(0.5, *arg) - eps = 1e-6 - pdfv = distfn.pdf(median, *arg) - if (pdfv < 1e-4) or (pdfv > 1e4): - # avoid checking a case where pdf is close to zero or huge - # (singularity) - median = median + 0.1 - pdfv = distfn.pdf(median, *arg) - cdfdiff = (distfn.cdf(median + eps, *arg) - - distfn.cdf(median - eps, *arg)) / eps / 2.0 - # replace with better diff and better test (more points), - # actually, this works pretty well - npt.assert_almost_equal(pdfv, cdfdiff, decimal=DECIMAL, - err_msg=msg + ' - cdf-pdf relationship') - - -def check_pdf_logpdf(distfn, args, msg): - # compares pdf at several points with the log of the pdf - points = np.array([0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]) - vals = distfn.ppf(points, *args) - pdf = distfn.pdf(vals, *args) - logpdf = distfn.logpdf(vals, *args) - pdf = pdf[pdf != 0] - logpdf = logpdf[np.isfinite(logpdf)] - npt.assert_almost_equal(np.log(pdf), logpdf, decimal=7, - err_msg=msg + " - logpdf-log(pdf) relationship") - - -def check_sf_logsf(distfn, args, msg): - # compares sf at several points with the log of the sf - points = np.array([0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]) - vals = distfn.ppf(points, *args) - sf = distfn.sf(vals, *args) - logsf = distfn.logsf(vals, *args) - sf = sf[sf != 0] - logsf = logsf[np.isfinite(logsf)] - npt.assert_almost_equal(np.log(sf), logsf, decimal=7, - err_msg=msg + " - logsf-log(sf) relationship") - - -def check_cdf_logcdf(distfn, args, msg): - # compares cdf at several points with the log of the cdf - points = np.array([0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]) - vals = distfn.ppf(points, *args) - cdf = distfn.cdf(vals, *args) - logcdf = distfn.logcdf(vals, *args) - cdf = cdf[cdf != 0] - logcdf = logcdf[np.isfinite(logcdf)] - npt.assert_almost_equal(np.log(cdf), logcdf, decimal=7, - err_msg=msg + " - logcdf-log(cdf) relationship") - - -def check_distribution_rvs(dist, args, alpha, rvs): - # test from scipy.stats.tests - # this version reuses existing random variables - D, pval = stats.kstest(rvs, dist, args=args, N=1000) - if (pval < alpha): - D, pval = stats.kstest(dist, '', args=args, N=1000) - npt.assert_(pval > alpha, "D = " + str(D) + "; pval = " + str(pval) + - "; alpha = " + str(alpha) + "\nargs = " + str(args)) - - -def check_vecentropy(distfn, args): - npt.assert_equal(distfn.vecentropy(*args), distfn._entropy(*args)) - - -@npt.dec.skipif(NUMPY_BELOW_1_7) -def check_loc_scale(distfn, arg, m, v, msg): - loc, scale = 10.0, 10.0 - mt, vt = distfn.stats(loc=loc, scale=scale, *arg) - npt.assert_allclose(m * scale + loc, mt) - npt.assert_allclose(v * scale * scale, vt) - - -def check_ppf_private(distfn, arg, msg): - # fails by design for truncnorm self.nb not defined - ppfs = distfn._ppf(np.array([0.1, 0.5, 0.9]), *arg) - npt.assert_(not np.any(np.isnan(ppfs)), msg + 'ppf private is nan') - - -if __name__ == "__main__": - npt.run_module_suite() +def check_sf_isf(distfn,arg,msg): + npt.assert_almost_equal(distfn.sf(distfn.isf([0.1,0.5,0.9], *arg), *arg), + [0.1,0.5,0.9], decimal=DECIMAL, err_msg=msg + + ' - sf-isf roundtrip') + npt.assert_almost_equal(distfn.cdf([0.1,0.9], *arg), + 1.0-distfn.sf([0.1,0.9], *arg), + decimal=DECIMAL, err_msg=msg + + ' - cdf-sf relationship') + + +def check_pdf(distfn, arg, msg): + # compares pdf at median with numerical derivative of cdf + median = distfn.ppf(0.5, *arg) + eps = 1e-6 + pdfv = distfn.pdf(median, *arg) + if (pdfv < 1e-4) or (pdfv > 1e4): + # avoid checking a case where pdf is close to zero or huge (singularity) + median = median + 0.1 + pdfv = distfn.pdf(median, *arg) + cdfdiff = (distfn.cdf(median + eps, *arg) - + distfn.cdf(median - eps, *arg))/eps/2.0 + # replace with better diff and better test (more points), + # actually, this works pretty well + npt.assert_almost_equal(pdfv, cdfdiff, + decimal=DECIMAL, err_msg=msg + ' - cdf-pdf relationship') + + +def check_pdf_logpdf(distfn, args, msg): + # compares pdf at several points with the log of the pdf + points = np.array([0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]) + vals = distfn.ppf(points, *args) + pdf = distfn.pdf(vals, *args) + logpdf = distfn.logpdf(vals, *args) + pdf = pdf[pdf != 0] + logpdf = logpdf[np.isfinite(logpdf)] + npt.assert_almost_equal(np.log(pdf), logpdf, decimal=7, err_msg=msg + " - logpdf-log(pdf) relationship") + + +def check_sf_logsf(distfn, args, msg): + # compares sf at several points with the log of the sf + points = np.array([0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]) + vals = distfn.ppf(points, *args) + sf = distfn.sf(vals, *args) + logsf = distfn.logsf(vals, *args) + sf = sf[sf != 0] + logsf = logsf[np.isfinite(logsf)] + npt.assert_almost_equal(np.log(sf), logsf, decimal=7, err_msg=msg + " - logsf-log(sf) relationship") + + +def check_cdf_logcdf(distfn, args, msg): + # compares cdf at several points with the log of the cdf + points = np.array([0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]) + vals = distfn.ppf(points, *args) + cdf = distfn.cdf(vals, *args) + logcdf = distfn.logcdf(vals, *args) + cdf = cdf[cdf != 0] + logcdf = logcdf[np.isfinite(logcdf)] + npt.assert_almost_equal(np.log(cdf), logcdf, decimal=7, err_msg=msg + " - logcdf-log(cdf) relationship") + + +def check_distribution_rvs(dist, args, alpha, rvs): + # test from scipy.stats.tests + # this version reuses existing random variables + D,pval = stats.kstest(rvs, dist, args=args, N=1000) + if (pval < alpha): + D,pval = stats.kstest(dist,'',args=args, N=1000) + npt.assert_(pval > alpha, "D = " + str(D) + "; pval = " + str(pval) + + "; alpha = " + str(alpha) + "\nargs = " + str(args)) + + +def check_vecentropy(distfn, args): + npt.assert_equal(distfn.vecentropy(*args), distfn._entropy(*args)) + + +@npt.dec.skipif(NUMPY_BELOW_1_7) +def check_loc_scale(distfn, arg, m, v, msg): + loc, scale = 10.0, 10.0 + mt, vt = distfn.stats(loc=loc, scale=scale, *arg) + npt.assert_allclose(m*scale + loc, mt) + npt.assert_allclose(v*scale*scale, vt) + + +def check_ppf_private(distfn, arg, msg): + #fails by design for truncnorm self.nb not defined + ppfs = distfn._ppf(np.array([0.1, 0.5, 0.9]), *arg) + npt.assert_(not np.any(np.isnan(ppfs)), msg + 'ppf private is nan') + + +if __name__ == "__main__": + npt.run_module_suite() diff --git a/pywafo/src/wafo/stats/tests/test_discrete_basic.py b/pywafo/src/wafo/stats/tests/test_discrete_basic.py index 90008e5..53e6216 100644 --- a/pywafo/src/wafo/stats/tests/test_discrete_basic.py +++ b/pywafo/src/wafo/stats/tests/test_discrete_basic.py @@ -2,37 +2,17 @@ from __future__ import division, print_function, absolute_import import numpy.testing as npt import numpy as np -try: - from wafo.stats.six import xrange -except: - pass +from scipy.lib.six import xrange + from wafo import stats from wafo.stats.tests.common_tests import (check_normalization, check_moment, check_mean_expect, check_var_expect, check_skew_expect, check_kurt_expect, check_entropy, check_private_entropy, check_edge_support, check_named_args) +from wafo.stats._distr_params import distdiscrete knf = npt.dec.knownfailureif -distdiscrete = [ - ['bernoulli', (0.3, )], - ['binom', (5, 0.4)], - ['boltzmann', (1.4, 19)], - ['dlaplace', (0.8,)], # 0.5 - ['geom', (0.5,)], - ['hypergeom', (30, 12, 6)], - ['hypergeom', (21, 3, 12)], # numpy.random (3,18,12) numpy ticket:921 - ['hypergeom', (21, 18, 11)], # numpy.random (18,3,11) numpy ticket:921 - ['logser', (0.6,)], # reenabled, numpy ticket:921 - ['nbinom', (5, 0.5)], - ['nbinom', (0.4, 0.4)], # from tickets: 583 - ['planck', (0.51,)], # 4.1 - ['poisson', (0.6,)], - ['randint', (7, 31)], - ['skellam', (15, 8)], - ['zipf', (6.5,)] -] - def test_discrete_basic(): for distname, arg in distdiscrete: @@ -40,7 +20,7 @@ def test_discrete_basic(): np.random.seed(9765456) rvs = distfn.rvs(size=2000, *arg) supp = np.unique(rvs) - #_m, v = distfn.stats(*arg) + m, v = distfn.stats(*arg) yield check_cdf_ppf, distfn, arg, supp, distname + ' cdf_ppf' yield check_pmf_cdf, distfn, arg, distname @@ -56,7 +36,7 @@ def test_discrete_basic(): if distname in seen: continue seen.add(distname) - distfn = getattr(stats, distname) + distfn = getattr(stats,distname) locscale_defaults = (0,) meths = [distfn.pmf, distfn.logpmf, distfn.cdf, distfn.logcdf, distfn.logsf] @@ -74,7 +54,7 @@ def test_discrete_basic(): def test_moments(): for distname, arg in distdiscrete: - distfn = getattr(stats, distname) + distfn = getattr(stats,distname) m, v, s, k = distfn.stats(*arg, moments='mvsk') yield check_normalization, distfn, arg, distname @@ -84,13 +64,13 @@ def test_moments(): yield check_var_expect, distfn, arg, m, v, distname yield check_skew_expect, distfn, arg, m, v, s, distname - cond = distname in ['zipf'] + cond = False #distname in ['zipf'] msg = distname + ' fails kurtosis' yield knf(cond, msg)(check_kurt_expect), distfn, arg, m, v, k, distname # frozen distr moments yield check_moment_frozen, distfn, arg, m, 1 - yield check_moment_frozen, distfn, arg, v + m * m, 2 + yield check_moment_frozen, distfn, arg, v+m*m, 2 def check_cdf_ppf(distfn, arg, supp, msg): @@ -108,7 +88,7 @@ def check_cdf_ppf(distfn, arg, supp, msg): def check_pmf_cdf(distfn, arg, distname): startind = np.int(distfn.ppf(0.01, *arg) - 1) index = list(range(startind, startind + 10)) - cdfs, pmfs_cum = distfn.cdf(index, *arg), distfn.pmf(index, *arg).cumsum() + cdfs, pmfs_cum = distfn.cdf(index,*arg), distfn.pmf(index, *arg).cumsum() atol, rtol = 1e-10, 1e-10 if distname == 'skellam': # ncx2 accuracy @@ -158,7 +138,7 @@ def check_discrete_chisquare(distfn, arg, rvs, alpha, msg): """ n = len(rvs) nsupp = 20 - wsupp = 1.0 / nsupp + wsupp = 1.0/nsupp # construct intervals with minimum mass 1/nsupp # intervals are left-half-open as in a cdf difference @@ -167,30 +147,30 @@ def check_discrete_chisquare(distfn, arg, rvs, alpha, msg): distsupp = [max(distfn.a, -1000)] distmass = [] for ii in distsupport: - current = distfn.cdf(ii, *arg) - if current - last >= wsupp - 1e-14: + current = distfn.cdf(ii,*arg) + if current - last >= wsupp-1e-14: distsupp.append(ii) distmass.append(current - last) last = current - if current > (1 - wsupp): + if current > (1-wsupp): break if distsupp[-1] < distfn.b: distsupp.append(distfn.b) - distmass.append(1 - last) + distmass.append(1-last) distsupp = np.array(distsupp) distmass = np.array(distmass) # convert intervals to right-half-open as required by histogram - histsupp = distsupp + 1e-8 + histsupp = distsupp+1e-8 histsupp[0] = distfn.a # find sample frequencies and perform chisquare test - freq, _hsupp = np.histogram(rvs, histsupp) - #cdfs = distfn.cdf(distsupp, *arg) - (_chis, pval) = stats.chisquare(np.array(freq), n * distmass) + freq,hsupp = np.histogram(rvs,histsupp) + cdfs = distfn.cdf(distsupp,*arg) + (chis,pval) = stats.chisquare(np.array(freq),n*distmass) npt.assert_(pval > alpha, 'chisquare - test for %s' - ' at arg = %s with pval = %s' % (msg, str(arg), str(pval))) + ' at arg = %s with pval = %s' % (msg,str(arg),str(pval))) def check_scale_docstring(distfn): diff --git a/pywafo/src/wafo/stats/tests/test_distributions.py b/pywafo/src/wafo/stats/tests/test_distributions.py index a7c6c7b..acb85b0 100644 --- a/pywafo/src/wafo/stats/tests/test_distributions.py +++ b/pywafo/src/wafo/stats/tests/test_distributions.py @@ -2,24 +2,25 @@ """ from __future__ import division, print_function, absolute_import -#import unittest + import warnings import re import sys from numpy.testing import (TestCase, run_module_suite, assert_equal, - assert_array_equal, assert_almost_equal, - assert_array_almost_equal, - assert_allclose, assert_, assert_raises, rand, dec) + assert_array_equal, assert_almost_equal, assert_array_almost_equal, + assert_allclose, assert_, assert_raises, rand, dec) from nose import SkipTest import numpy import numpy as np from numpy import typecodes, array -#from scipy.lib._version import NumpyVersion +from scipy.lib._version import NumpyVersion from scipy import special import wafo.stats as stats from wafo.stats._distn_infrastructure import argsreduce +import wafo.stats.distributions + from scipy.special import xlogy @@ -27,31 +28,44 @@ from scipy.special import xlogy DOCSTRINGS_STRIPPED = sys.flags.optimize > 1 -# generate test cases to test cdf and distribution consistency -dists = ['uniform', 'norm', 'lognorm', 'expon', 'beta', - 'powerlaw', 'bradford', 'burr', 'fisk', 'cauchy', 'halfcauchy', - 'foldcauchy', 'gamma', 'gengamma', 'loggamma', - 'alpha', 'anglit', 'arcsine', 'betaprime', - 'dgamma', 'exponweib', 'exponpow', 'frechet_l', 'frechet_r', - 'gilbrat', 'f', 'ncf', 'chi2', 'chi', 'nakagami', 'genpareto', - 'genextreme', 'genhalflogistic', 'pareto', 'lomax', 'halfnorm', - 'halflogistic', 'fatiguelife', 'foldnorm', 'ncx2', 't', 'nct', - 'weibull_min', 'weibull_max', 'dweibull', 'maxwell', 'rayleigh', - 'genlogistic', 'logistic', 'gumbel_l', 'gumbel_r', 'gompertz', - 'hypsecant', 'laplace', 'reciprocal', 'triang', 'tukeylambda', +# Generate test cases to test cdf and distribution consistency. +# Note that this list does not include all distributions. +dists = ['uniform','norm','lognorm','expon','beta', + 'powerlaw','bradford','burr','fisk','cauchy','halfcauchy', + 'foldcauchy','gamma','gengamma','loggamma', + 'alpha','anglit','arcsine','betaprime', + 'dgamma','exponweib','exponpow','frechet_l','frechet_r', + 'gilbrat','f','ncf','chi2','chi','nakagami','genpareto', + 'genextreme','genhalflogistic','pareto','lomax','halfnorm', + 'halflogistic','fatiguelife','foldnorm','ncx2','t','nct', + 'weibull_min','weibull_max','dweibull','maxwell','rayleigh', + 'genlogistic', 'logistic','gumbel_l','gumbel_r','gompertz', + 'hypsecant', 'laplace', 'reciprocal','triang','tukeylambda', 'vonmises', 'vonmises_line', 'pearson3'] + +def _assert_hasattr(a, b, msg=None): + if msg is None: + msg = '%s does not have attribute %s' % (a, b) + assert_(hasattr(a, b), msg=msg) + + +def test_api_regression(): + # https://github.com/scipy/scipy/issues/3802 + _assert_hasattr(stats.distributions, 'f_gen') + + # check function for test generator def check_distribution(dist, args, alpha): - D, pval = stats.kstest(dist, '', args=args, N=1000) + D,pval = stats.kstest(dist,'', args=args, N=1000) if (pval < alpha): - D, pval = stats.kstest(dist, '', args=args, N=1000) + D,pval = stats.kstest(dist,'',args=args, N=1000) # if (pval < alpha): # D,pval = stats.kstest(dist,'',args=args, N=1000) assert_(pval > alpha, msg="D = " + str(D) + "; pval = " + str(pval) + - "; alpha = " + str(alpha) + "\nargs = " + str(args)) + "; alpha = " + str(alpha) + "\nargs = " + str(args)) # nose test generator @@ -65,7 +79,7 @@ def test_all_distributions(): alpha = 0.001 if dist == 'frechet': - args = tuple(2 * rand(1)) + (0,) + tuple(2 * rand(2)) + args = tuple(2*rand(1))+(0,)+tuple(2*rand(2)) elif dist == 'triang': args = tuple(rand(nargs)) elif dist == 'reciprocal': @@ -75,26 +89,26 @@ def test_all_distributions(): elif dist == 'vonmises': yield check_distribution, dist, (10,), alpha yield check_distribution, dist, (101,), alpha - args = tuple(1.0 + rand(nargs)) + args = tuple(1.0+rand(nargs)) else: - args = tuple(1.0 + rand(nargs)) + args = tuple(1.0+rand(nargs)) yield check_distribution, dist, args, alpha -def check_vonmises_pdf_periodic(k, l, s, x): - vm = stats.vonmises(k, loc=l, scale=s) - assert_almost_equal(vm.pdf(x), vm.pdf(x % (2 * numpy.pi * s))) +def check_vonmises_pdf_periodic(k,l,s,x): + vm = stats.vonmises(k,loc=l,scale=s) + assert_almost_equal(vm.pdf(x),vm.pdf(x % (2*numpy.pi*s))) -def check_vonmises_cdf_periodic(k, l, s, x): - vm = stats.vonmises(k, loc=l, scale=s) - assert_almost_equal(vm.cdf(x) % 1, vm.cdf(x % (2 * numpy.pi * s)) % 1) +def check_vonmises_cdf_periodic(k,l,s,x): + vm = stats.vonmises(k,loc=l,scale=s) + assert_almost_equal(vm.cdf(x) % 1,vm.cdf(x % (2*numpy.pi*s)) % 1) def test_vonmises_pdf_periodic(): for k in [0.1, 1, 101]: - for x in [0, 1, numpy.pi, 10, 100]: + for x in [0,1,numpy.pi,10,100]: yield check_vonmises_pdf_periodic, k, 0, 1, x yield check_vonmises_pdf_periodic, k, 1, 1, x yield check_vonmises_pdf_periodic, k, 0, 10, x @@ -110,37 +124,34 @@ def test_vonmises_line_support(): class TestRandInt(TestCase): - def test_rvs(self): - vals = stats.randint.rvs(5, 30, size=100) + vals = stats.randint.rvs(5,30,size=100) assert_(numpy.all(vals < 30) & numpy.all(vals >= 5)) assert_(len(vals) == 100) - vals = stats.randint.rvs(5, 30, size=(2, 50)) - assert_(numpy.shape(vals) == (2, 50)) + vals = stats.randint.rvs(5,30,size=(2,50)) + assert_(numpy.shape(vals) == (2,50)) assert_(vals.dtype.char in typecodes['AllInteger']) - val = stats.randint.rvs(15, 46) + val = stats.randint.rvs(15,46) assert_((val >= 15) & (val < 46)) assert_(isinstance(val, numpy.ScalarType), msg=repr(type(val))) - val = stats.randint(15, 46).rvs(3) + val = stats.randint(15,46).rvs(3) assert_(val.dtype.char in typecodes['AllInteger']) def test_pdf(self): k = numpy.r_[0:36] - out = numpy.where((k >= 5) & (k < 30), 1.0 / (30 - 5), 0) - vals = stats.randint.pmf(k, 5, 30) - assert_array_almost_equal(vals, out) + out = numpy.where((k >= 5) & (k < 30), 1.0/(30-5), 0) + vals = stats.randint.pmf(k,5,30) + assert_array_almost_equal(vals,out) def test_cdf(self): x = numpy.r_[0:36:100j] k = numpy.floor(x) - out = numpy.select( - [k >= 30, k >= 5], [1.0, (k - 5.0 + 1) / (30 - 5.0)], 0) - vals = stats.randint.cdf(x, 5, 30) + out = numpy.select([k >= 30,k >= 5],[1.0,(k-5.0+1)/(30-5.0)],0) + vals = stats.randint.cdf(x,5,30) assert_array_almost_equal(vals, out, decimal=12) class TestBinom(TestCase): - def test_rvs(self): vals = stats.binom.rvs(10, 0.75, size=(2, 50)) assert_(numpy.all(vals >= 0) & numpy.all(vals <= 10)) @@ -154,8 +165,8 @@ class TestBinom(TestCase): def test_pmf(self): # regression test for Ticket #1842 - vals1 = stats.binom.pmf(100, 100, 1) - vals2 = stats.binom.pmf(0, 100, 0) + vals1 = stats.binom.pmf(100, 100,1) + vals2 = stats.binom.pmf(0, 100,0) assert_allclose(vals1, 1.0, rtol=1e-15, atol=0) assert_allclose(vals2, 1.0, rtol=1e-15, atol=0) @@ -175,9 +186,15 @@ class TestBinom(TestCase): h = b.entropy() assert_equal(h, 0.0) + def test_warns_p0(self): + # no spurious warnigns are generated for p=0; gh-3817 + with warnings.catch_warnings(): + warnings.simplefilter("error", RuntimeWarning) + assert_equal(stats.binom(n=2, p=0).mean(), 0) + assert_equal(stats.binom(n=2, p=0).std(), 0) -class TestBernoulli(TestCase): +class TestBernoulli(TestCase): def test_rvs(self): vals = stats.bernoulli.rvs(0.75, size=(2, 50)) assert_(numpy.all(vals >= 0) & numpy.all(vals <= 1)) @@ -192,7 +209,7 @@ class TestBernoulli(TestCase): def test_entropy(self): # Simple tests of entropy. b = stats.bernoulli(0.25) - expected_h = -0.25 * np.log(0.25) - 0.75 * np.log(0.75) + expected_h = -0.25*np.log(0.25) - 0.75*np.log(0.75) h = b.entropy() assert_allclose(h, expected_h) @@ -206,7 +223,6 @@ class TestBernoulli(TestCase): class TestNBinom(TestCase): - def test_rvs(self): vals = stats.nbinom.rvs(10, 0.75, size=(2, 50)) assert_(numpy.all(vals >= 0)) @@ -225,7 +241,6 @@ class TestNBinom(TestCase): class TestGeom(TestCase): - def test_rvs(self): vals = stats.geom.rvs(0.75, size=(2, 50)) assert_(numpy.all(vals >= 0)) @@ -238,13 +253,13 @@ class TestGeom(TestCase): assert_(val.dtype.char in typecodes['AllInteger']) def test_pmf(self): - vals = stats.geom.pmf([1, 2, 3], 0.5) - assert_array_almost_equal(vals, [0.5, 0.25, 0.125]) + vals = stats.geom.pmf([1,2,3],0.5) + assert_array_almost_equal(vals,[0.5,0.25,0.125]) def test_logpmf(self): # regression test for ticket 1793 - vals1 = np.log(stats.geom.pmf([1, 2, 3], 0.5)) - vals2 = stats.geom.logpmf([1, 2, 3], 0.5) + vals1 = np.log(stats.geom.pmf([1,2,3], 0.5)) + vals2 = stats.geom.logpmf([1,2,3], 0.5) assert_allclose(vals1, vals2, rtol=1e-15, atol=0) def test_cdf_sf(self): @@ -252,7 +267,7 @@ class TestGeom(TestCase): vals_sf = stats.geom.sf([1, 2, 3], 0.5) expected = array([0.5, 0.75, 0.875]) assert_array_almost_equal(vals, expected) - assert_array_almost_equal(vals_sf, 1 - expected) + assert_array_almost_equal(vals_sf, 1-expected) def test_logcdf_logsf(self): vals = stats.geom.logcdf([1, 2, 3], 0.5) @@ -268,18 +283,15 @@ class TestGeom(TestCase): class TestTruncnorm(TestCase): - def test_ppf_ticket1131(self): - vals = stats.truncnorm.ppf( - [-0.5, 0, 1e-4, 0.5, 1 - 1e-4, 1, 2], -1., 1., - loc=[3] * 7, scale=2) + vals = stats.truncnorm.ppf([-0.5,0,1e-4,0.5, 1-1e-4,1,2], -1., 1., + loc=[3]*7, scale=2) expected = np.array([np.nan, 1, 1.00056419, 3, 4.99943581, 5, np.nan]) assert_array_almost_equal(vals, expected) def test_isf_ticket1131(self): - vals = stats.truncnorm.isf( - [-0.5, 0, 1e-4, 0.5, 1 - 1e-4, 1, 2], -1., 1., - loc=[3] * 7, scale=2) + vals = stats.truncnorm.isf([-0.5,0,1e-4,0.5, 1-1e-4,1,2], -1., 1., + loc=[3]*7, scale=2) expected = np.array([np.nan, 5, 4.99943581, 3, 1.00056419, 1, np.nan]) assert_array_almost_equal(vals, expected) @@ -308,11 +320,10 @@ class TestTruncnorm(TestCase): class TestHypergeom(TestCase): - def test_rvs(self): vals = stats.hypergeom.rvs(20, 10, 3, size=(2, 50)) assert_(numpy.all(vals >= 0) & - numpy.all(vals <= 3)) + numpy.all(vals <= 3)) assert_(numpy.shape(vals) == (2, 50)) assert_(vals.dtype.char in typecodes['AllInteger']) val = stats.hypergeom.rvs(20, 3, 10) @@ -331,6 +342,10 @@ class TestHypergeom(TestCase): hgpmf = stats.hypergeom.pmf(2, tot, good, N) assert_almost_equal(hgpmf, 0.0010114963068932233, 11) + def test_cdf_above_one(self): + # for some values of parameters, hypergeom cdf was >1, see gh-2238 + assert_(0 <= stats.hypergeom.cdf(30, 13397950, 4363, 12390) <= 1.0) + def test_precision2(self): # Test hypergeom precision for large numbers. See #1218. # Results compared with those from R. @@ -340,8 +355,7 @@ class TestHypergeom(TestCase): quantile = 2e4 res = [] for eaten in fruits_eaten: - res.append( - stats.hypergeom.sf(quantile, oranges + pears, oranges, eaten)) + res.append(stats.hypergeom.sf(quantile, oranges + pears, oranges, eaten)) expected = np.array([0, 1.904153e-114, 2.752693e-66, 4.931217e-32, 8.265601e-11, 0.1237904, 1]) assert_allclose(res, expected, atol=0, rtol=5e-7) @@ -372,11 +386,11 @@ class TestLoggamma(TestCase): # of "A Statistical Study of Log-Gamma Distribution", by Ping Shing # Chan (thesis, McMaster University, 1993). table = np.array([ - # c, mean, var, skew, exc. kurt. - 0.5, -1.9635, 4.9348, -1.5351, 4.0000, - 1.0, -0.5772, 1.6449, -1.1395, 2.4000, - 12.0, 2.4427, 0.0869, -0.2946, 0.1735, - ]).reshape(-1, 5) + # c, mean, var, skew, exc. kurt. + 0.5, -1.9635, 4.9348, -1.5351, 4.0000, + 1.0, -0.5772, 1.6449, -1.1395, 2.4000, + 12.0, 2.4427, 0.0869, -0.2946, 0.1735, + ]).reshape(-1, 5) for c, mean, var, skew, kurt in table: computed = stats.loggamma.stats(c, moments='msvk') assert_array_almost_equal(computed, [mean, var, skew, kurt], @@ -384,7 +398,6 @@ class TestLoggamma(TestCase): class TestLogser(TestCase): - def test_rvs(self): vals = stats.logser.rvs(0.75, size=(2, 50)) assert_(numpy.all(vals >= 1)) @@ -398,7 +411,6 @@ class TestLogser(TestCase): class TestPareto(TestCase): - def test_stats(self): # Check the stats() method with some simple values. Also check # that the calculations do not trigger RuntimeWarnings. @@ -431,7 +443,7 @@ class TestPareto(TestCase): m, v, s, k = stats.pareto.stats(2.5, moments='mvsk') assert_allclose(m, 2.5 / 1.5) - assert_allclose(v, 2.5 / (1.5 * 1.5 * 0.5)) + assert_allclose(v, 2.5 / (1.5*1.5*0.5)) assert_equal(s, np.nan) assert_equal(k, np.nan) @@ -443,27 +455,121 @@ class TestPareto(TestCase): m, v, s, k = stats.pareto.stats(3.5, moments='mvsk') assert_allclose(m, 3.5 / 2.5) - assert_allclose(v, 3.5 / (2.5 * 2.5 * 1.5)) - assert_allclose(s, (2 * 4.5 / 0.5) * np.sqrt(1.5 / 3.5)) + assert_allclose(v, 3.5 / (2.5*2.5*1.5)) + assert_allclose(s, (2*4.5/0.5)*np.sqrt(1.5/3.5)) assert_equal(k, np.nan) m, v, s, k = stats.pareto.stats(4.0, moments='mvsk') assert_allclose(m, 4.0 / 3.0) assert_allclose(v, 4.0 / 18.0) - assert_allclose( - s, 2 * (1 + 4.0) / (4.0 - 3) * np.sqrt((4.0 - 2) / 4.0)) + assert_allclose(s, 2*(1+4.0)/(4.0-3) * np.sqrt((4.0-2)/4.0)) assert_equal(k, np.nan) m, v, s, k = stats.pareto.stats(4.5, moments='mvsk') assert_allclose(m, 4.5 / 3.5) - assert_allclose(v, 4.5 / (3.5 * 3.5 * 2.5)) - assert_allclose(s, (2 * 5.5 / 1.5) * np.sqrt(2.5 / 4.5)) - assert_allclose( - k, 6 * (4.5 ** 3 + 4.5 ** 2 - 6 * 4.5 - 2) / (4.5 * 1.5 * 0.5)) + assert_allclose(v, 4.5 / (3.5*3.5*2.5)) + assert_allclose(s, (2*5.5/1.5) * np.sqrt(2.5/4.5)) + assert_allclose(k, 6*(4.5**3 + 4.5**2 - 6*4.5 - 2)/(4.5*1.5*0.5)) + + +class TestGenpareto(TestCase): + def test_ab(self): + # c >= 0: a, b = [0, inf] + for c in [1., 0.]: + c = np.asarray(c) + stats.genpareto._argcheck(c) # ugh + assert_equal(stats.genpareto.a, 0.) + assert_(np.isposinf(stats.genpareto.b)) + + # c < 0: a=0, b=1/|c| + c = np.asarray(-2.) + stats.genpareto._argcheck(c) + assert_allclose([stats.genpareto.a, stats.genpareto.b], [0., 0.5]) + + def test_c0(self): + # with c=0, genpareto reduces to the exponential distribution + rv = stats.genpareto(c=0.) + x = np.linspace(0, 10., 30) + assert_allclose(rv.pdf(x), stats.expon.pdf(x)) + assert_allclose(rv.cdf(x), stats.expon.cdf(x)) + assert_allclose(rv.sf(x), stats.expon.sf(x)) + + q = np.linspace(0., 1., 10) + assert_allclose(rv.ppf(q), stats.expon.ppf(q)) + + def test_cm1(self): + # with c=-1, genpareto reduces to the uniform distr on [0, 1] + rv = stats.genpareto(c=-1.) + x = np.linspace(0, 10., 30) + assert_allclose(rv.pdf(x), stats.uniform.pdf(x)) + assert_allclose(rv.cdf(x), stats.uniform.cdf(x)) + assert_allclose(rv.sf(x), stats.uniform.sf(x)) + + q = np.linspace(0., 1., 10) + assert_allclose(rv.ppf(q), stats.uniform.ppf(q)) + + # logpdf(1., c=-1) should be zero + assert_allclose(rv.logpdf(1), 0) + + def test_x_inf(self): + # make sure x=inf is handled gracefully + rv = stats.genpareto(c=0.1) + assert_allclose([rv.pdf(np.inf), rv.cdf(np.inf)], [0., 1.]) + assert_(np.isneginf(rv.logpdf(np.inf))) + + rv = stats.genpareto(c=0.) + assert_allclose([rv.pdf(np.inf), rv.cdf(np.inf)], [0., 1.]) + assert_(np.isneginf(rv.logpdf(np.inf))) + + rv = stats.genpareto(c=-1.) + assert_allclose([rv.pdf(np.inf), rv.cdf(np.inf)], [0., 1.]) + assert_(np.isneginf(rv.logpdf(np.inf))) + + def test_c_continuity(self): + # pdf is continuous at c=0, -1 + x = np.linspace(0, 10, 30) + for c in [0, -1]: + pdf0 = stats.genpareto.pdf(x, c) + for dc in [1e-14, -1e-14]: + pdfc = stats.genpareto.pdf(x, c + dc) + assert_allclose(pdf0, pdfc, atol=1e-12) + + cdf0 = stats.genpareto.cdf(x, c) + for dc in [1e-14, 1e-14]: + cdfc = stats.genpareto.cdf(x, c + dc) + assert_allclose(cdf0, cdfc, atol=1e-12) + + def test_c_continuity_ppf(self): + q = np.r_[np.logspace(1e-12, 0.01, base=0.1), + np.linspace(0.01, 1, 30, endpoint=False), + 1. - np.logspace(1e-12, 0.01, base=0.1)] + for c in [0., -1.]: + ppf0 = stats.genpareto.ppf(q, c) + for dc in [1e-14, -1e-14]: + ppfc = stats.genpareto.ppf(q, c + dc) + assert_allclose(ppf0, ppfc, atol=1e-12) + + def test_c_continuity_isf(self): + q = np.r_[np.logspace(1e-12, 0.01, base=0.1), + np.linspace(0.01, 1, 30, endpoint=False), + 1. - np.logspace(1e-12, 0.01, base=0.1)] + for c in [0., -1.]: + isf0 = stats.genpareto.isf(q, c) + for dc in [1e-14, -1e-14]: + isfc = stats.genpareto.isf(q, c + dc) + assert_allclose(isf0, isfc, atol=1e-12) + + def test_cdf_ppf_roundtrip(self): + # this should pass with machine precision. hat tip @pbrod + q = np.r_[np.logspace(1e-12, 0.01, base=0.1), + np.linspace(0.01, 1, 30, endpoint=False), + 1. - np.logspace(1e-12, 0.01, base=0.1)] + for c in [1e-8, -1e-18, 1e-15, -1e-15]: + assert_allclose(stats.genpareto.cdf(stats.genpareto.ppf(q, c), c), + q, atol=1e-15) class TestPearson3(TestCase): - def test_rvs(self): vals = stats.pearson3.rvs(0.1, size=(2, 50)) assert_(numpy.shape(vals) == (2, 50)) @@ -481,7 +587,7 @@ class TestPearson3(TestCase): atol=1e-6) vals = stats.pearson3.pdf(-3, 0.1) assert_allclose(vals, np.array([0.00313791]), atol=1e-6) - vals = stats.pearson3.pdf([-3, -2, -1, 0, 1], 0.1) + vals = stats.pearson3.pdf([-3,-2,-1,0,1], 0.1) assert_allclose(vals, np.array([0.00313791, 0.05192304, 0.25028092, 0.39885918, 0.23413173]), atol=1e-6) @@ -491,13 +597,12 @@ class TestPearson3(TestCase): atol=1e-6) vals = stats.pearson3.cdf(-3, 0.1) assert_allclose(vals, [0.00082256], atol=1e-6) - vals = stats.pearson3.cdf([-3, -2, -1, 0, 1], 0.1) + vals = stats.pearson3.cdf([-3,-2,-1,0,1], 0.1) assert_allclose(vals, [8.22563821e-04, 1.99860448e-02, 1.58550710e-01, 5.06649130e-01, 8.41442111e-01], atol=1e-6) class TestPoisson(TestCase): - def test_rvs(self): vals = stats.poisson.rvs(0.5, size=(2, 50)) assert_(numpy.all(vals >= 0)) @@ -512,11 +617,10 @@ class TestPoisson(TestCase): def test_stats(self): mu = 16.0 result = stats.poisson.stats(mu, moments='mvsk') - assert_allclose(result, [mu, mu, np.sqrt(1.0 / mu), 1.0 / mu]) + assert_allclose(result, [mu, mu, np.sqrt(1.0/mu), 1.0/mu]) class TestZipf(TestCase): - def test_rvs(self): vals = stats.zipf.rvs(1.5, size=(2, 50)) assert_(numpy.all(vals >= 1)) @@ -539,7 +643,6 @@ class TestZipf(TestCase): class TestDLaplace(TestCase): - def test_rvs(self): vals = stats.dlaplace.rvs(1.5, size=(2, 50)) assert_(numpy.shape(vals) == (2, 50)) @@ -558,24 +661,23 @@ class TestDLaplace(TestCase): m, v, s, k = dl.stats('mvsk') N = 37 - xx = np.arange(-N, N + 1) + xx = np.arange(-N, N+1) pp = dl.pmf(xx) - m2, m4 = np.sum(pp * xx ** 2), np.sum(pp * xx ** 4) - assert_equal((m, s), (0, 0)) - assert_allclose((v, k), (m2, m4 / m2 ** 2 - 3.), atol=1e-14, rtol=1e-8) + m2, m4 = np.sum(pp*xx**2), np.sum(pp*xx**4) + assert_equal((m, s), (0,0)) + assert_allclose((v, k), (m2, m4/m2**2 - 3.), atol=1e-14, rtol=1e-8) def test_stats2(self): a = np.log(2.) dl = stats.dlaplace(a) m, v, s, k = dl.stats('mvsk') - assert_equal((m, s), (0., 0.)) + assert_equal((m, s), (0.,0.)) assert_allclose((v, k), (4., 3.25)) class TestInvGamma(TestCase): - -# @dec.skipif(NumpyVersion(np.__version__) < '1.7.0', -# "assert_* funcs broken with inf/nan") + @dec.skipif(NumpyVersion(np.__version__) < '1.7.0', + "assert_* funcs broken with inf/nan") def test_invgamma_inf_gh_1866(self): # invgamma's moments are only finite for a>n # specific numbers checked w/ boost 1.54 @@ -583,8 +685,7 @@ class TestInvGamma(TestCase): warnings.simplefilter('error', RuntimeWarning) mvsk = stats.invgamma.stats(a=19.31, moments='mvsk') assert_allclose(mvsk, - [0.05461496450, 0.0001723162534, - 1.020362676, 2.055616582]) + [0.05461496450, 0.0001723162534, 1.020362676, 2.055616582]) a = [1.1, 3.1, 5.6] mvsk = stats.invgamma.stats(a=a, moments='mvsk') @@ -597,7 +698,6 @@ class TestInvGamma(TestCase): class TestF(TestCase): - def test_f_moments(self): # n-th moment of F distributions is only finite for n < dfd / 2 m, v, s, k = stats.f.stats(11, 6.5, moments='mvsk') @@ -610,12 +710,12 @@ class TestF(TestCase): # no warnings should be generated for dfd = 2, 4, 6, 8 (div by zero) with warnings.catch_warnings(): warnings.simplefilter('error', RuntimeWarning) - stats.f.stats(dfn=[11] * 4, dfd=[2, 4, 6, 8], moments='mvsk') + stats.f.stats(dfn=[11]*4, dfd=[2, 4, 6, 8], moments='mvsk') - #@dec.knownfailureif(True, 'f stats does not properly broadcast') + @dec.knownfailureif(True, 'f stats does not properly broadcast') def test_stats_broadcast(self): # stats do not fully broadcast just yet - _mv = stats.f.stats(dfn=11, dfd=[11, 12]) + mv = stats.f.stats(dfn=11, dfd=[11, 12]) def test_rvgeneric_std(): @@ -624,17 +724,16 @@ def test_rvgeneric_std(): class TestRvDiscrete(TestCase): - def test_rvs(self): - states = [-1, 0, 1, 2, 3, 4] - probability = [0.0, 0.3, 0.4, 0.0, 0.3, 0.0] + states = [-1,0,1,2,3,4] + probability = [0.0,0.3,0.4,0.0,0.3,0.0] samples = 1000 - r = stats.rv_discrete(name='sample', values=(states, probability)) + r = stats.rv_discrete(name='sample',values=(states,probability)) x = r.rvs(size=samples) assert_(isinstance(x, numpy.ndarray)) - for s, p in zip(states, probability): - assert_(abs(sum(x == s) / float(samples) - p) < 0.05) + for s,p in zip(states,probability): + assert_(abs(sum(x == s)/float(samples) - p) < 0.05) x = r.rvs() assert_(isinstance(x, int)) @@ -653,9 +752,8 @@ class TestRvDiscrete(TestCase): class TestExpon(TestCase): - def test_zero(self): - assert_equal(stats.expon.pdf(0), 1) + assert_equal(stats.expon.pdf(0),1) def test_tail(self): # Regression test for ticket 807 assert_equal(stats.expon.cdf(1e-18), 1e-18) @@ -663,11 +761,10 @@ class TestExpon(TestCase): class TestGenExpon(TestCase): - def test_pdf_unity_area(self): from scipy.integrate import simps # PDF should integrate to one - assert_almost_equal(simps(stats.genexpon.pdf(numpy.arange(0, 10, 0.01), + assert_almost_equal(simps(stats.genexpon.pdf(numpy.arange(0,10,0.01), 0.5, 0.5, 2.0), dx=0.01), 1, 1) @@ -678,33 +775,30 @@ class TestGenExpon(TestCase): class TestExponpow(TestCase): - def test_tail(self): assert_almost_equal(stats.exponpow.cdf(1e-10, 2.), 1e-20) - assert_almost_equal( - stats.exponpow.isf(stats.exponpow.sf(5, .8), .8), 5) + assert_almost_equal(stats.exponpow.isf(stats.exponpow.sf(5, .8), .8), 5) class TestSkellam(TestCase): - def test_pmf(self): # comparison to R k = numpy.arange(-10, 15) mu1, mu2 = 10, 5 skpmfR = numpy.array( - [4.2254582961926893e-005, 1.1404838449648488e-004, - 2.8979625801752660e-004, 6.9177078182101231e-004, - 1.5480716105844708e-003, 3.2412274963433889e-003, - 6.3373707175123292e-003, 1.1552351566696643e-002, - 1.9606152375042644e-002, 3.0947164083410337e-002, - 4.5401737566767360e-002, 6.1894328166820688e-002, - 7.8424609500170578e-002, 9.2418812533573133e-002, - 1.0139793148019728e-001, 1.0371927988298846e-001, - 9.9076583077406091e-002, 8.8546660073089561e-002, - 7.4187842052486810e-002, 5.8392772862200251e-002, - 4.3268692953013159e-002, 3.0248159818374226e-002, - 1.9991434305603021e-002, 1.2516877303301180e-002, - 7.4389876226229707e-003]) + [4.2254582961926893e-005, 1.1404838449648488e-004, + 2.8979625801752660e-004, 6.9177078182101231e-004, + 1.5480716105844708e-003, 3.2412274963433889e-003, + 6.3373707175123292e-003, 1.1552351566696643e-002, + 1.9606152375042644e-002, 3.0947164083410337e-002, + 4.5401737566767360e-002, 6.1894328166820688e-002, + 7.8424609500170578e-002, 9.2418812533573133e-002, + 1.0139793148019728e-001, 1.0371927988298846e-001, + 9.9076583077406091e-002, 8.8546660073089561e-002, + 7.4187842052486810e-002, 5.8392772862200251e-002, + 4.3268692953013159e-002, 3.0248159818374226e-002, + 1.9991434305603021e-002, 1.2516877303301180e-002, + 7.4389876226229707e-003]) assert_almost_equal(stats.skellam.pmf(k, mu1, mu2), skpmfR, decimal=15) @@ -713,25 +807,24 @@ class TestSkellam(TestCase): k = numpy.arange(-10, 15) mu1, mu2 = 10, 5 skcdfR = numpy.array( - [6.4061475386192104e-005, 1.7810985988267694e-004, - 4.6790611790020336e-004, 1.1596768997212152e-003, - 2.7077485103056847e-003, 5.9489760066490718e-003, - 1.2286346724161398e-002, 2.3838698290858034e-002, - 4.3444850665900668e-002, 7.4392014749310995e-002, - 1.1979375231607835e-001, 1.8168808048289900e-001, - 2.6011268998306952e-001, 3.5253150251664261e-001, - 4.5392943399683988e-001, 5.5764871387982828e-001, - 6.5672529695723436e-001, 7.4527195703032389e-001, - 8.1945979908281064e-001, 8.7785257194501087e-001, - 9.2112126489802404e-001, 9.5136942471639818e-001, - 9.7136085902200120e-001, 9.8387773632530240e-001, - 9.9131672394792536e-001]) + [6.4061475386192104e-005, 1.7810985988267694e-004, + 4.6790611790020336e-004, 1.1596768997212152e-003, + 2.7077485103056847e-003, 5.9489760066490718e-003, + 1.2286346724161398e-002, 2.3838698290858034e-002, + 4.3444850665900668e-002, 7.4392014749310995e-002, + 1.1979375231607835e-001, 1.8168808048289900e-001, + 2.6011268998306952e-001, 3.5253150251664261e-001, + 4.5392943399683988e-001, 5.5764871387982828e-001, + 6.5672529695723436e-001, 7.4527195703032389e-001, + 8.1945979908281064e-001, 8.7785257194501087e-001, + 9.2112126489802404e-001, 9.5136942471639818e-001, + 9.7136085902200120e-001, 9.8387773632530240e-001, + 9.9131672394792536e-001]) assert_almost_equal(stats.skellam.cdf(k, mu1, mu2), skcdfR, decimal=5) class TestLognorm(TestCase): - def test_pdf(self): # Regression test for Ticket #1471: avoid nan with 0/0 situation with np.errstate(divide='ignore'): @@ -740,12 +833,11 @@ class TestLognorm(TestCase): class TestBeta(TestCase): - def test_logpdf(self): # Regression test for Ticket #1326: avoid nan with 0*log(0) situation - logpdf = stats.beta.logpdf(0, 1, 0.5) + logpdf = stats.beta.logpdf(0,1,0.5) assert_almost_equal(logpdf, -0.69314718056) - logpdf = stats.beta.logpdf(0, 0.5, 1) + logpdf = stats.beta.logpdf(0,0.5,1) assert_almost_equal(logpdf, np.inf) def test_logpdf_ticket_1866(self): @@ -757,7 +849,6 @@ class TestBeta(TestCase): class TestBetaPrime(TestCase): - def test_logpdf(self): alpha, beta = 267, 1472 x = np.array([0.2, 0.5, 0.6]) @@ -767,41 +858,35 @@ class TestBetaPrime(TestCase): class TestGamma(TestCase): - def test_pdf(self): # a few test cases to compare with R - pdf = stats.gamma.pdf(90, 394, scale=1. / 5) + pdf = stats.gamma.pdf(90, 394, scale=1./5) assert_almost_equal(pdf, 0.002312341) - pdf = stats.gamma.pdf(3, 10, scale=1. / 5) + pdf = stats.gamma.pdf(3, 10, scale=1./5) assert_almost_equal(pdf, 0.1620358) def test_logpdf(self): # Regression test for Ticket #1326: cornercase avoid nan with 0*log(0) # situation - logpdf = stats.gamma.logpdf(0, 1) + logpdf = stats.gamma.logpdf(0,1) assert_almost_equal(logpdf, 0) class TestChi2(TestCase): # regression tests after precision improvements, ticket:1041, not verified - def test_precision(self): - assert_almost_equal( - stats.chi2.pdf(1000, 1000), 8.919133934753128e-003, 14) + assert_almost_equal(stats.chi2.pdf(1000, 1000), 8.919133934753128e-003, 14) assert_almost_equal(stats.chi2.pdf(100, 100), 0.028162503162596778, 14) class TestArrayArgument(TestCase): # test for ticket:992 - def test_noexception(self): - rvs = stats.norm.rvs( - loc=(np.arange(5)), scale=np.ones(5), size=(10, 5)) - assert_equal(rvs.shape, (10, 5)) + rvs = stats.norm.rvs(loc=(np.arange(5)), scale=np.ones(5), size=(10,5)) + assert_equal(rvs.shape, (10,5)) class TestDocstring(TestCase): - def test_docstrings(self): # See ticket #761 if stats.rayleigh.__doc__ is not None: @@ -816,13 +901,12 @@ class TestDocstring(TestCase): class TestEntropy(TestCase): - def test_entropy_positive(self): # See ticket #497 - pk = [0.5, 0.2, 0.3] - qk = [0.1, 0.25, 0.65] - eself = stats.entropy(pk, pk) - edouble = stats.entropy(pk, qk) + pk = [0.5,0.2,0.3] + qk = [0.1,0.25,0.65] + eself = stats.entropy(pk,pk) + edouble = stats.entropy(pk,qk) assert_(0.0 == eself) assert_(edouble >= 0.0) @@ -835,7 +919,7 @@ class TestEntropy(TestCase): qk[:8] = 2. S = stats.entropy(pk, qk) S2 = stats.entropy(pk, qk, base=2.) - assert_(abs(S / S2 - np.log(2.)) < 1.e-5) + assert_(abs(S/S2 - np.log(2.)) < 1.e-5) def test_entropy_zero(self): # Test for PR-479 @@ -846,33 +930,33 @@ class TestEntropy(TestCase): pk = [[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]] qk = [[0.2, 0.1], [0.3, 0.6], [0.5, 0.3]] assert_array_almost_equal(stats.entropy(pk, qk), - [0.1933259, 0.18609809]) + [0.1933259, 0.18609809]) -# @dec.skipif(NumpyVersion(np.__version__) < '1.7.0', -# "assert_* funcs broken with inf/nan") + @dec.skipif(NumpyVersion(np.__version__) < '1.7.0', + "assert_* funcs broken with inf/nan") def test_entropy_2d_zero(self): pk = [[0.1, 0.2], [0.6, 0.3], [0.3, 0.5]] qk = [[0.0, 0.1], [0.3, 0.6], [0.5, 0.3]] assert_array_almost_equal(stats.entropy(pk, qk), - [np.inf, 0.18609809]) + [np.inf, 0.18609809]) pk[0][0] = 0.0 assert_array_almost_equal(stats.entropy(pk, qk), - [0.17403988, 0.18609809]) + [0.17403988, 0.18609809]) def TestArgsreduce(): - a = array([1, 3, 2, 1, 2, 3, 3]) - b, c = argsreduce(a > 1, a, 2) + a = array([1,3,2,1,2,3,3]) + b,c = argsreduce(a > 1, a, 2) - assert_array_equal(b, [3, 2, 2, 3, 3]) - assert_array_equal(c, [2, 2, 2, 2, 2]) + assert_array_equal(b, [3,2,2,3,3]) + assert_array_equal(c, [2,2,2,2,2]) - b, c = argsreduce(2 > 1, a, 2) + b,c = argsreduce(2 > 1, a, 2) assert_array_equal(b, a[0]) assert_array_equal(c, [2]) - b, c = argsreduce(a > 0, a, 2) + b,c = argsreduce(a > 0, a, 2) assert_array_equal(b, a) assert_array_equal(c, [2] * numpy.size(a)) @@ -887,7 +971,7 @@ class TestFitMethod(object): raise SkipTest("%s fit known to fail" % dist) distfunc = getattr(stats, dist) with np.errstate(all='ignore'): - res = distfunc.rvs(*args, **{'size': 200}) + res = distfunc.rvs(*args, **{'size':200}) vals = distfunc.fit(res) vals2 = distfunc.fit(res, optimizer='powell') # Only check the length of the return @@ -897,8 +981,8 @@ class TestFitMethod(object): assert_(len(vals) == len(args)) assert_(len(vals2) == len(args)) else: - assert_(len(vals) == 2 + len(args)) - assert_(len(vals2) == 2 + len(args)) + assert_(len(vals) == 2+len(args)) + assert_(len(vals2) == 2+len(args)) for func, dist, args, alpha in test_all_distributions(): yield check, func, dist, args, alpha @@ -912,24 +996,24 @@ class TestFitMethod(object): raise SkipTest("%s fit known to fail" % dist) distfunc = getattr(stats, dist) with np.errstate(all='ignore'): - res = distfunc.rvs(*args, **{'size': 200}) - vals = distfunc.fit(res, floc=0) - vals2 = distfunc.fit(res, fscale=1) - assert_(len(vals) == 2 + len(args)) + res = distfunc.rvs(*args, **{'size':200}) + vals = distfunc.fit(res,floc=0) + vals2 = distfunc.fit(res,fscale=1) + assert_(len(vals) == 2+len(args)) assert_(vals[-2] == 0) assert_(vals2[-1] == 1) - assert_(len(vals2) == 2 + len(args)) + assert_(len(vals2) == 2+len(args)) if len(args) > 0: vals3 = distfunc.fit(res, f0=args[0]) - assert_(len(vals3) == 2 + len(args)) + assert_(len(vals3) == 2+len(args)) assert_(vals3[0] == args[0]) if len(args) > 1: vals4 = distfunc.fit(res, f1=args[1]) - assert_(len(vals4) == 2 + len(args)) + assert_(len(vals4) == 2+len(args)) assert_(vals4[1] == args[1]) if len(args) > 2: vals5 = distfunc.fit(res, f2=args[2]) - assert_(len(vals5) == 2 + len(args)) + assert_(len(vals5) == 2+len(args)) assert_(vals5[2] == args[2]) for func, dist, args, alpha in test_all_distributions(): @@ -968,7 +1052,7 @@ class TestFitMethod(object): s = np.log(x.mean()) - meanlog assert_almost_equal(np.log(a) - special.digamma(a), s, decimal=5) assert_equal(loc, floc) - assert_almost_equal(scale, x.mean() / a, decimal=8) + assert_almost_equal(scale, x.mean()/a, decimal=8) # Regression tests for gh-2514. # The problem was that if `floc=0` was given, any other fixed @@ -978,14 +1062,14 @@ class TestFitMethod(object): a, loc, scale = stats.gamma.fit(x, f0=f0, floc=floc) assert_equal(a, f0) assert_equal(loc, floc) - assert_almost_equal(scale, x.mean() / a, decimal=8) + assert_almost_equal(scale, x.mean()/a, decimal=8) f0 = 2 floc = 0 a, loc, scale = stats.gamma.fit(x, f0=f0, floc=floc) assert_equal(a, f0) assert_equal(loc, floc) - assert_almost_equal(scale, x.mean() / a, decimal=8) + assert_almost_equal(scale, x.mean()/a, decimal=8) # loc and scale fixed. floc = 0 @@ -1004,7 +1088,7 @@ class TestFitMethod(object): # the maximum likelihood function. n = len(x) s1 = np.log(x).sum() - s2 = np.log(1 - x).sum() + s2 = np.log(1-x).sum() psiab = special.psi(a + b) func = [s1 - n * (-psiab + special.psi(a)), s2 - n * (-psiab + special.psi(b))] @@ -1015,7 +1099,7 @@ class TestFitMethod(object): a, b, loc, scale = stats.beta.fit(x, floc=0, fscale=1) assert_equal(loc, 0) assert_equal(scale, 1) - assert_allclose(mlefunc(a, b, x), [0, 0], atol=1e-6) + assert_allclose(mlefunc(a, b, x), [0,0], atol=1e-6) # Basic test with f0, floc and fscale given. # This is also a regression test for gh-2514. @@ -1024,7 +1108,7 @@ class TestFitMethod(object): assert_equal(a, 2) assert_equal(loc, 0) assert_equal(scale, 1) - _da, db = mlefunc(a, b, x) + da, db = mlefunc(a, b, x) assert_allclose(db, 0, atol=1e-5) # Same floc and fscale values as above, but reverse the data @@ -1049,15 +1133,14 @@ class TestFitMethod(object): # Check that attempting to fix all the parameters raises a ValueError. assert_raises(ValueError, stats.beta.fit, y, f0=0, f1=1, - floc=2, fscale=3) + floc=2, fscale=3) class TestFrozen(TestCase): - # Test that a frozen distribution gives the same results as the original - # object. + # Test that a frozen distribution gives the same results as the original object. + # # Only tested for the normal distribution (with loc and scale specified) # and for the gamma distribution (with a shape parameter specified). - def test_norm(self): dist = stats.norm frozen = stats.norm(loc=10.0, scale=3.0) @@ -1103,7 +1186,7 @@ class TestFrozen(TestCase): assert_equal(result_f, result) result_f = frozen.moment(2) - result = dist.moment(2, loc=10.0, scale=3.0) + result = dist.moment(2,loc=10.0, scale=3.0) assert_equal(result_f, result) def test_gamma(self): @@ -1170,15 +1253,37 @@ class TestFrozen(TestCase): # the focus of this test. assert_equal(m1, m2) + def test_ab(self): + # test that the support of a frozen distribution + # (i) remains frozen even if it changes for the original one + # (ii) is actually correct if the shape parameters are such that + # the values of [a, b] are not the default [0, inf] + # take a genpareto as an example where the support + # depends on the value of the shape parameter: + # for c > 0: a, b = 0, inf + # for c < 0: a, b = 0, -1/c + rv = stats.genpareto(c=-0.1) + a, b = rv.dist.a, rv.dist.b + assert_equal([a, b], [0., 10.]) + + stats.genpareto.pdf(0, c=0.1) # this changes genpareto.b + assert_equal([rv.dist.a, rv.dist.b], [a, b]) + + rv1 = stats.genpareto(c=0.1) + assert_(rv1.dist is not rv.dist) + + def test_rv_frozen_in_namespace(self): + # Regression test for gh-3522 + assert_(hasattr(stats.distributions, 'rv_frozen')) + class TestExpect(TestCase): # Test for expect method. # # Uses normal distribution and beta distribution for finite bounds, and # hypergeom for discrete distribution with finite support - def test_norm(self): - v = stats.norm.expect(lambda x: (x - 5) * (x - 5), loc=5, scale=2) + v = stats.norm.expect(lambda x: (x-5)*(x-5), loc=5, scale=2) assert_almost_equal(v, 4, decimal=14) m = stats.norm.expect(lambda x: (x), loc=5, scale=2) @@ -1195,21 +1300,20 @@ class TestExpect(TestCase): def test_beta(self): # case with finite support interval - v = stats.beta.expect( - lambda x: (x - 19 / 3.) * (x - 19 / 3.), args=(10, 5), - loc=5, scale=2) - assert_almost_equal(v, 1. / 18., decimal=13) + v = stats.beta.expect(lambda x: (x-19/3.)*(x-19/3.), args=(10,5), + loc=5, scale=2) + assert_almost_equal(v, 1./18., decimal=13) - m = stats.beta.expect(lambda x: x, args=(10, 5), loc=5., scale=2.) - assert_almost_equal(m, 19 / 3., decimal=13) + m = stats.beta.expect(lambda x: x, args=(10,5), loc=5., scale=2.) + assert_almost_equal(m, 19/3., decimal=13) ub = stats.beta.ppf(0.95, 10, 10, loc=5, scale=2) lb = stats.beta.ppf(0.05, 10, 10, loc=5, scale=2) - prob90 = stats.beta.expect(lambda x: 1., args=(10, 10), loc=5., - scale=2., lb=lb, ub=ub, conditional=False) + prob90 = stats.beta.expect(lambda x: 1., args=(10,10), loc=5., + scale=2.,lb=lb, ub=ub, conditional=False) assert_almost_equal(prob90, 0.9, decimal=13) - prob90c = stats.beta.expect(lambda x: 1, args=(10, 10), loc=5, + prob90c = stats.beta.expect(lambda x: 1, args=(10,10), loc=5, scale=2, lb=lb, ub=ub, conditional=True) assert_almost_equal(prob90c, 1., decimal=13) @@ -1221,25 +1325,24 @@ class TestExpect(TestCase): m = stats.hypergeom.expect(lambda x: x, args=(20, 10, 8), loc=5.) assert_almost_equal(m, m_true, decimal=13) - v = stats.hypergeom.expect(lambda x: (x - 9.) ** 2, args=(20, 10, 8), + v = stats.hypergeom.expect(lambda x: (x-9.)**2, args=(20, 10, 8), loc=5.) assert_almost_equal(v, v_true, decimal=14) # with bounds, bounds equal to shifted support - v_bounds = stats.hypergeom.expect( - lambda x: (x - 9.) ** 2, args=(20, 10, 8), - loc=5., lb=5, ub=13) + v_bounds = stats.hypergeom.expect(lambda x: (x-9.)**2, args=(20, 10, 8), + loc=5., lb=5, ub=13) assert_almost_equal(v_bounds, v_true, decimal=14) # drop boundary points - prob_true = 1 - stats.hypergeom.pmf([5, 13], 20, 10, 8, loc=5).sum() + prob_true = 1-stats.hypergeom.pmf([5, 13], 20, 10, 8, loc=5).sum() prob_bounds = stats.hypergeom.expect(lambda x: 1, args=(20, 10, 8), - loc=5., lb=6, ub=12) + loc=5., lb=6, ub=12) assert_almost_equal(prob_bounds, prob_true, decimal=13) # conditional prob_bc = stats.hypergeom.expect(lambda x: 1, args=(20, 10, 8), loc=5., - lb=6, ub=12, conditional=True) + lb=6, ub=12, conditional=True) assert_almost_equal(prob_bc, 1, decimal=14) # check simple integral @@ -1250,8 +1353,8 @@ class TestExpect(TestCase): def test_poisson(self): # poisson, use lower bound only prob_bounds = stats.poisson.expect(lambda x: 1, args=(2,), lb=3, - conditional=False) - prob_b_true = 1 - stats.poisson.cdf(2, 2) + conditional=False) + prob_b_true = 1-stats.poisson.cdf(2,2) assert_almost_equal(prob_bounds, prob_b_true, decimal=14) prob_lb = stats.poisson.expect(lambda x: 1, args=(2,), lb=2, @@ -1279,7 +1382,6 @@ class TestExpect(TestCase): class TestNct(TestCase): - def test_nc_parameter(self): # Parameter values c<=0 were not enabled (gh-2402). # For negative values c and for c=0 results of rv.cdf(0) below were nan @@ -1289,8 +1391,7 @@ class TestNct(TestCase): assert_almost_equal(rv.cdf(0), 0.841344746069, decimal=10) def test_broadcasting(self): - res = stats.nct.pdf( - 5, np.arange(4, 7)[:, None], np.linspace(0.1, 1, 4)) + res = stats.nct.pdf(5, np.arange(4,7)[:,None], np.linspace(0.1, 1, 4)) expected = array([[0.00321886, 0.00557466, 0.00918418, 0.01442997], [0.00217142, 0.00395366, 0.00683888, 0.01126276], [0.00153078, 0.00291093, 0.00525206, 0.00900815]]) @@ -1316,7 +1417,6 @@ class TestNct(TestCase): class TestRice(TestCase): - def test_rice_zero_b(self): # rice distribution should work with b=0, cf gh-2164 x = [0.2, 1., 5.] @@ -1336,7 +1436,7 @@ class TestRice(TestCase): # see e.g. Abramovich & Stegun 9.6.7 & 9.6.10 b = 1e-8 assert_allclose(stats.rice.pdf(x, 0), stats.rice.pdf(x, b), - atol=b, rtol=0) + atol=b, rtol=0) def test_rice_rvs(self): rvs = stats.rice.rvs @@ -1345,17 +1445,15 @@ class TestRice(TestCase): class TestErlang(TestCase): - def test_erlang_runtimewarning(self): # erlang should generate a RuntimeWarning if a non-integer # shape parameter is used. with warnings.catch_warnings(): warnings.simplefilter("error", RuntimeWarning) - # The non-integer shape parameter 1.3 should trigger a - # RuntimeWarning + # The non-integer shape parameter 1.3 should trigger a RuntimeWarning assert_raises(RuntimeWarning, - stats.erlang.rvs, 1.3, loc=0, scale=1, size=4) + stats.erlang.rvs, 1.3, loc=0, scale=1, size=4) # Calling the fit method with `f0` set to an integer should # *not* trigger a RuntimeWarning. It should return the same @@ -1366,33 +1464,75 @@ class TestErlang(TestCase): assert_allclose(result_erlang, result_gamma, rtol=1e-3) -class TestRdist(TestCase): +class TestExponWeib(TestCase): + + def test_pdf_logpdf(self): + # Regression test for gh-3508. + x = 0.1 + a = 1.0 + c = 100.0 + p = stats.exponweib.pdf(x, a, c) + logp = stats.exponweib.logpdf(x, a, c) + # Expected values were computed with mpmath. + assert_allclose([p, logp], + [1.0000000000000054e-97, -223.35075402042244]) + + def test_a_is_1(self): + # For issue gh-3508. + # Check that when a=1, the pdf and logpdf methods of exponweib are the + # same as those of weibull_min. + x = np.logspace(-4, -1, 4) + a = 1 + c = 100 + + p = stats.exponweib.pdf(x, a, c) + expected = stats.weibull_min.pdf(x, c) + assert_allclose(p, expected) + + logp = stats.exponweib.logpdf(x, a, c) + expected = stats.weibull_min.logpdf(x, c) + assert_allclose(logp, expected) + + def test_a_is_1_c_is_1(self): + # When a = 1 and c = 1, the distribution is exponential. + x = np.logspace(-8, 1, 10) + a = 1 + c = 1 + + p = stats.exponweib.pdf(x, a, c) + expected = stats.expon.pdf(x) + assert_allclose(p, expected) + + logp = stats.exponweib.logpdf(x, a, c) + expected = stats.expon.logpdf(x) + assert_allclose(logp, expected) + +class TestRdist(TestCase): @dec.slow def test_rdist_cdf_gh1285(self): # check workaround in rdist._cdf for issue gh-1285. distfn = stats.rdist values = [0.001, 0.5, 0.999] assert_almost_equal(distfn.cdf(distfn.ppf(values, 541.0), 541.0), - values, decimal=5) + values, decimal=5) def test_540_567(): # test for nan returned in tickets 540, 567 - assert_almost_equal(stats.norm.cdf(-1.7624320982), 0.03899815971089126, - decimal=10, err_msg='test_540_567') - assert_almost_equal(stats.norm.cdf(-1.7624320983), 0.038998159702449846, - decimal=10, err_msg='test_540_567') + assert_almost_equal(stats.norm.cdf(-1.7624320982),0.03899815971089126, + decimal=10, err_msg='test_540_567') + assert_almost_equal(stats.norm.cdf(-1.7624320983),0.038998159702449846, + decimal=10, err_msg='test_540_567') assert_almost_equal(stats.norm.cdf(1.38629436112, loc=0.950273420309, - scale=0.204423758009), - 0.98353464004309321, decimal=10, - err_msg='test_540_567') + scale=0.204423758009),0.98353464004309321, + decimal=10, err_msg='test_540_567') def test_regression_ticket_1316(): # The following was raising an exception, because _construct_default_doc() # did not handle the default keyword extradoc=None. See ticket #1316. - _g = stats._continuous_distns.gamma_gen(name='gamma') + g = stats._continuous_distns.gamma_gen(name='gamma') def test_regression_ticket_1326(): @@ -1401,8 +1541,7 @@ def test_regression_ticket_1326(): def test_regression_tukey_lambda(): - # Make sure that Tukey-Lambda distribution correctly handles non-positive - # lambdas. + # Make sure that Tukey-Lambda distribution correctly handles non-positive lambdas. x = np.linspace(-5.0, 5.0, 101) olderr = np.seterr(divide='ignore') @@ -1431,23 +1570,24 @@ def test_regression_ticket_1421(): def test_nan_arguments_gh_issue_1362(): - assert_(np.isnan(stats.t.logcdf(1, np.nan))) - assert_(np.isnan(stats.t.cdf(1, np.nan))) - assert_(np.isnan(stats.t.logsf(1, np.nan))) - assert_(np.isnan(stats.t.sf(1, np.nan))) - assert_(np.isnan(stats.t.pdf(1, np.nan))) - assert_(np.isnan(stats.t.logpdf(1, np.nan))) - assert_(np.isnan(stats.t.ppf(1, np.nan))) - assert_(np.isnan(stats.t.isf(1, np.nan))) - - assert_(np.isnan(stats.bernoulli.logcdf(np.nan, 0.5))) - assert_(np.isnan(stats.bernoulli.cdf(np.nan, 0.5))) - assert_(np.isnan(stats.bernoulli.logsf(np.nan, 0.5))) - assert_(np.isnan(stats.bernoulli.sf(np.nan, 0.5))) - assert_(np.isnan(stats.bernoulli.pmf(np.nan, 0.5))) - assert_(np.isnan(stats.bernoulli.logpmf(np.nan, 0.5))) - assert_(np.isnan(stats.bernoulli.ppf(np.nan, 0.5))) - assert_(np.isnan(stats.bernoulli.isf(np.nan, 0.5))) + with np.errstate(invalid='ignore'): + assert_(np.isnan(stats.t.logcdf(1, np.nan))) + assert_(np.isnan(stats.t.cdf(1, np.nan))) + assert_(np.isnan(stats.t.logsf(1, np.nan))) + assert_(np.isnan(stats.t.sf(1, np.nan))) + assert_(np.isnan(stats.t.pdf(1, np.nan))) + assert_(np.isnan(stats.t.logpdf(1, np.nan))) + assert_(np.isnan(stats.t.ppf(1, np.nan))) + assert_(np.isnan(stats.t.isf(1, np.nan))) + + assert_(np.isnan(stats.bernoulli.logcdf(np.nan, 0.5))) + assert_(np.isnan(stats.bernoulli.cdf(np.nan, 0.5))) + assert_(np.isnan(stats.bernoulli.logsf(np.nan, 0.5))) + assert_(np.isnan(stats.bernoulli.sf(np.nan, 0.5))) + assert_(np.isnan(stats.bernoulli.pmf(np.nan, 0.5))) + assert_(np.isnan(stats.bernoulli.logpmf(np.nan, 0.5))) + assert_(np.isnan(stats.bernoulli.ppf(np.nan, 0.5))) + assert_(np.isnan(stats.bernoulli.isf(np.nan, 0.5))) def test_frozen_fit_ticket_1536(): @@ -1477,7 +1617,7 @@ def test_frozen_fit_ticket_1536(): floc = 0.9 x = stats.norm.rvs(loc, 2., size=100) params = np.array(stats.norm.fit(x, floc=floc)) - expected = np.array([floc, np.sqrt(((x - floc) ** 2).mean())]) + expected = np.array([floc, np.sqrt(((x-floc)**2).mean())]) assert_almost_equal(params, expected, decimal=4) @@ -1496,7 +1636,7 @@ def test_tukeylambda_stats_ticket_1545(): mv = stats.tukeylambda.stats(0, moments='mvsk') # Known exact values: - expected = [0, np.pi ** 2 / 3, 0, 1.2] + expected = [0, np.pi**2/3, 0, 1.2] assert_almost_equal(mv, expected, decimal=10) mv = stats.tukeylambda.stats(3.13, moments='mvsk') @@ -1545,8 +1685,8 @@ def test_powerlaw_stats(): which can be rearranged to gamma_2 = 6 * (a**3 - a**2 - 6*a + 2) / (a*(a+3)*(a+4)) """ - cases = [(1.0, (0.5, 1. / 12, 0.0, -1.2)), - (2.0, (2. / 3, 2. / 36, -0.56568542494924734, -0.6))] + cases = [(1.0, (0.5, 1./12, 0.0, -1.2)), + (2.0, (2./3, 2./36, -0.56568542494924734, -0.6))] for a, exact_mvsk in cases: mvsk = stats.powerlaw.stats(a, moments="mvsk") assert_array_almost_equal(mvsk, exact_mvsk) @@ -1557,10 +1697,10 @@ def test_ksone_fit_freeze(): d = np.array( [-0.18879233, 0.15734249, 0.18695107, 0.27908787, -0.248649, -0.2171497, 0.12233512, 0.15126419, 0.03119282, 0.4365294, - 0.08930393, -0.23509903, 0.28231224, -0.09974875, -0.25196048, - 0.11102028, 0.1427649, 0.10176452, 0.18754054, 0.25826724, - 0.05988819, 0.0531668, 0.21906056, 0.32106729, 0.2117662, - 0.10886442, 0.09375789, 0.24583286, -0.22968366, -0.07842391, + 0.08930393, -0.23509903, 0.28231224, -0.09974875, -0.25196048, + 0.11102028, 0.1427649, 0.10176452, 0.18754054, 0.25826724, + 0.05988819, 0.0531668, 0.21906056, 0.32106729, 0.2117662, + 0.10886442, 0.09375789, 0.24583286, -0.22968366, -0.07842391, -0.31195432, -0.21271196, 0.1114243, -0.13293002, 0.01331725, -0.04330977, -0.09485776, -0.28434547, 0.22245721, -0.18518199, -0.10943985, -0.35243174, 0.06897665, -0.03553363, -0.0701746, @@ -1597,6 +1737,30 @@ def test_norm_logcdf(): np.seterr(**olderr) +def test_levy_cdf_ppf(): + # Test levy.cdf, including small arguments. + x = np.array([1000, 1.0, 0.5, 0.1, 0.01, 0.001]) + + # Expected values were calculated separately with mpmath. + # E.g. + # >>> mpmath.mp.dps = 100 + # >>> x = mpmath.mp.mpf('0.01') + # >>> cdf = mpmath.erfc(mpmath.sqrt(1/(2*x))) + expected = np.array([0.9747728793699604, + 0.3173105078629141, + 0.1572992070502851, + 0.0015654022580025495, + 1.523970604832105e-23, + 1.795832784800726e-219]) + + y = stats.levy.cdf(x) + assert_allclose(y, expected, rtol=1e-10) + + # ppf(expected) should get us back to x. + xx = stats.levy.ppf(expected) + assert_allclose(xx, x, rtol=1e-13) + + def test_hypergeom_interval_1802(): # these two had endless loops assert_equal(stats.hypergeom.interval(.95, 187601, 43192, 757), @@ -1654,7 +1818,7 @@ def test_ncx2_tails_ticket_955(): # Trac #955 -- check that the cdf computed by special functions # matches the integrated pdf a = stats.ncx2.cdf(np.arange(20, 25, 0.2), 2, 1.07458615e+02) - b = stats.ncx2.veccdf(np.arange(20, 25, 0.2), 2, 1.07458615e+02) + b = stats.ncx2._cdfvec(np.arange(20, 25, 0.2), 2, 1.07458615e+02) assert_allclose(a, b, rtol=1e-3, atol=0) @@ -1672,8 +1836,7 @@ def test_stats_shapes_argcheck(): mv2_augmented = tuple(np.r_[np.nan, _] for _ in mv2) assert_equal(mv2_augmented, mv3) - # -1 is not a legal shape parameter - mv3 = stats.lognorm.stats([2, 2.4, -1]) + mv3 = stats.lognorm.stats([2, 2.4, -1]) # -1 is not a legal shape parameter mv2 = stats.lognorm.stats([2, 2.4]) mv2_augmented = tuple(np.r_[_, np.nan] for _ in mv2) assert_equal(mv2_augmented, mv3) @@ -1683,22 +1846,19 @@ def test_stats_shapes_argcheck(): # anyway, so some distributions may or may not fail. -# Test subclassing distributions w/ explicit shapes +## Test subclassing distributions w/ explicit shapes class _distr_gen(stats.rv_continuous): - def _pdf(self, x, a): return 42 class _distr2_gen(stats.rv_continuous): - def _cdf(self, x, a): return 42 * a + x class _distr3_gen(stats.rv_continuous): - def _pdf(self, x, a, b): return a + b @@ -1710,9 +1870,8 @@ class _distr3_gen(stats.rv_continuous): class _distr6_gen(stats.rv_continuous): # Two shape parameters (both _pdf and _cdf defined, consistent shapes.) - def _pdf(self, x, a, b): - return a * x + b + return a*x + b def _cdf(self, x, a, b): return 42 * a + x @@ -1772,17 +1931,15 @@ class TestSubclassingExplicitShapes(TestCase): def test_shapes_signature(self): # test explicit shapes which agree w/ the signature of _pdf class _dist_gen(stats.rv_continuous): - def _pdf(self, x, a): return stats.norm._pdf(x) * a dist = _dist_gen(shapes='a') - assert_equal(dist.pdf(0.5, a=2), stats.norm.pdf(0.5) * 2) + assert_equal(dist.pdf(0.5, a=2), stats.norm.pdf(0.5)*2) def test_shapes_signature_inconsistent(self): # test explicit shapes which do not agree w/ the signature of _pdf class _dist_gen(stats.rv_continuous): - def _pdf(self, x, a): return stats.norm._pdf(x) * a @@ -1793,40 +1950,36 @@ class TestSubclassingExplicitShapes(TestCase): # test _pdf with only starargs # NB: **kwargs of pdf will never reach _pdf class _dist_gen(stats.rv_continuous): - def _pdf(self, x, *args): extra_kwarg = args[0] return stats.norm._pdf(x) * extra_kwarg dist = _dist_gen(shapes='extra_kwarg') - assert_equal(dist.pdf(0.5, extra_kwarg=33), stats.norm.pdf(0.5) * 33) - assert_equal(dist.pdf(0.5, 33), stats.norm.pdf(0.5) * 33) + assert_equal(dist.pdf(0.5, extra_kwarg=33), stats.norm.pdf(0.5)*33) + assert_equal(dist.pdf(0.5, 33), stats.norm.pdf(0.5)*33) assert_raises(TypeError, dist.pdf, 0.5, **dict(xxx=33)) def test_star_args_2(self): # test _pdf with named & starargs # NB: **kwargs of pdf will never reach _pdf class _dist_gen(stats.rv_continuous): - def _pdf(self, x, offset, *args): extra_kwarg = args[0] return stats.norm._pdf(x) * extra_kwarg + offset dist = _dist_gen(shapes='offset, extra_kwarg') assert_equal(dist.pdf(0.5, offset=111, extra_kwarg=33), - stats.norm.pdf(0.5) * 33 + 111) + stats.norm.pdf(0.5)*33 + 111) assert_equal(dist.pdf(0.5, 111, 33), - stats.norm.pdf(0.5) * 33 + 111) + stats.norm.pdf(0.5)*33 + 111) def test_extra_kwarg(self): # **kwargs to _pdf are ignored. # this is a limitation of the framework (_pdf(x, *goodargs)) class _distr_gen(stats.rv_continuous): - def _pdf(self, x, *args, **kwargs): - # _pdf should handle *args, **kwargs itself. Here "handling" - # is ignoring *args and looking for ``extra_kwarg`` and using - # that. + # _pdf should handle *args, **kwargs itself. Here "handling" is + # ignoring *args and looking for ``extra_kwarg`` and using that. extra_kwarg = kwargs.pop('extra_kwarg', 1) return stats.norm._pdf(x) * extra_kwarg @@ -1836,7 +1989,6 @@ class TestSubclassingExplicitShapes(TestCase): def shapes_empty_string(self): # shapes='' is equivalent to shapes=None class _dist_gen(stats.rv_continuous): - def _pdf(self, x): return stats.norm.pdf(x) @@ -1889,7 +2041,6 @@ class TestSubclassingNoShapes(TestCase): def test_defaults_raise(self): # default arguments should raise class _dist_gen(stats.rv_continuous): - def _pdf(self, x, a=42): return 42 assert_raises(TypeError, _dist_gen, **dict(name='dummy')) @@ -1897,7 +2048,6 @@ class TestSubclassingNoShapes(TestCase): def test_starargs_raise(self): # without explicit shapes, *args are not allowed class _dist_gen(stats.rv_continuous): - def _pdf(self, x, a, *args): return 42 assert_raises(TypeError, _dist_gen, **dict(name='dummy')) @@ -1905,7 +2055,6 @@ class TestSubclassingNoShapes(TestCase): def test_kwargs_raise(self): # without explicit shapes, **kwargs are not allowed class _dist_gen(stats.rv_continuous): - def _pdf(self, x, a, **kwargs): return 42 assert_raises(TypeError, _dist_gen, **dict(name='dummy')) @@ -1927,5 +2076,4 @@ def test_infinite_input(): if __name__ == "__main__": - #unittest.main() run_module_suite() diff --git a/pywafo/src/wafo/stats/tests/test_fit.py b/pywafo/src/wafo/stats/tests/test_fit.py index 8b7776b..45fd567 100644 --- a/pywafo/src/wafo/stats/tests/test_fit.py +++ b/pywafo/src/wafo/stats/tests/test_fit.py @@ -33,6 +33,7 @@ failing_fits = [ 'tukeylambda', 'vonmises', 'wrapcauchy', + 'levy_stable' ] # Don't run the fit test on these: @@ -45,14 +46,15 @@ skip_fit = [ def test_cont_fit(): # this tests the closeness of the estimated parameters to the true # parameters with fit method of continuous distributions - # Note: slow, some distributions don't converge with sample size <= 10000 + # Note: is slow, some distributions don't converge with sample size <= 10000 for distname, arg in distcont: if distname not in skip_fit: - yield check_cont_fit, distname, arg + yield check_cont_fit, distname,arg -def check_cont_fit(distname, arg): +def check_cont_fit(distname,arg): + options = dict(method='mps', floc=0.) if distname in failing_fits: # Skip failing fits unless overridden xfail = True @@ -62,16 +64,18 @@ def check_cont_fit(distname, arg): pass if xfail: msg = "Fitting %s doesn't work reliably yet" % distname - msg += " [Set environment variable SCIPY_XFAIL=1 to run this " + \ - "test nevertheless.]" - dec.knownfailureif(True, msg)(lambda: None)() + msg += " [Set environment variable SCIPY_XFAIL=1 to run this test nevertheless.]" + #dec.knownfailureif(True, msg)(lambda: None)() + options['floc']=0. + options['fscale']=1. + + # print('Testing %s' % distname) distfn = getattr(stats, distname) - - truearg = np.hstack([arg, [0.0, 1.0]]) - diffthreshold = np.max(np.vstack([ - truearg * thresh_percent, - np.ones(distfn.numargs + 2) * thresh_min]), 0) + + truearg = np.hstack([arg,[0.0,1.0]]) + diffthreshold = np.max(np.vstack([truearg*thresh_percent, + np.ones(distfn.numargs+2)*thresh_min]),0) for fit_size in fit_sizes: # Note that if a fit succeeds, the other fit_sizes are skipped @@ -79,16 +83,17 @@ def check_cont_fit(distname, arg): with np.errstate(all='ignore'): rvs = distfn.rvs(size=fit_size, *arg) - #phat = distfn.fit2(rvs) - phat = distfn.fit2(rvs, method='mps') + # phat = distfn.fit2(rvs) + + phat = distfn.fit2(rvs, **options) + est = phat.par #est = distfn.fit(rvs) # start with default values diff = est - truearg # threshold for location - diffthreshold[-2] = np.max([np.abs(rvs.mean()) * thresh_percent, - thresh_min]) + diffthreshold[-2] = np.max([np.abs(rvs.mean())*thresh_percent,thresh_min]) if np.any(np.isnan(est)): raise AssertionError('nan returned in fit') diff --git a/pywafo/src/wafo/stats/tests/test_kdeoth.py b/pywafo/src/wafo/stats/tests/test_kdeoth.py index 198f24c..9b2941d 100644 --- a/pywafo/src/wafo/stats/tests/test_kdeoth.py +++ b/pywafo/src/wafo/stats/tests/test_kdeoth.py @@ -180,5 +180,23 @@ def test_kde_integer_input(): assert_array_almost_equal(kde(x1), y_expected, decimal=6) +def test_pdf_logpdf(): + np.random.seed(1) + n_basesample = 50 + xn = np.random.randn(n_basesample) + + # Default + gkde = stats.gaussian_kde(xn) + + xs = np.linspace(-15, 12, 25) + pdf = gkde.evaluate(xs) + pdf2 = gkde.pdf(xs) + assert_almost_equal(pdf, pdf2, decimal=12) + + logpdf = np.log(pdf) + logpdf2 = gkde.logpdf(xs) + assert_almost_equal(logpdf, logpdf2, decimal=12) + + if __name__ == "__main__": run_module_suite() diff --git a/pywafo/src/wafo/stats/tests/test_morestats.py b/pywafo/src/wafo/stats/tests/test_morestats.py index 1e09f36..3813d57 100644 --- a/pywafo/src/wafo/stats/tests/test_morestats.py +++ b/pywafo/src/wafo/stats/tests/test_morestats.py @@ -9,9 +9,8 @@ import warnings import numpy as np from numpy.random import RandomState from numpy.testing import (TestCase, run_module_suite, assert_array_equal, - assert_almost_equal, assert_array_less, - assert_array_almost_equal, assert_raises, assert_, - assert_allclose, assert_equal, dec) + assert_almost_equal, assert_array_less, assert_array_almost_equal, + assert_raises, assert_, assert_allclose, assert_equal, dec, assert_warns) from wafo import stats @@ -37,20 +36,19 @@ g10 = [0.991, 0.995, 0.984, 0.994, 0.997, 0.997, 0.991, 0.998, 1.004, 0.997] class TestShapiro(TestCase): - def test_basic(self): - x1 = [0.11, 7.87, 4.61, 10.14, 7.95, 3.14, 0.46, - 4.43, 0.21, 4.75, 0.71, 1.52, 3.24, - 0.93, 0.42, 4.97, 9.53, 4.55, 0.47, 6.66] - w, pw = stats.shapiro(x1) - assert_almost_equal(w, 0.90047299861907959, 6) - assert_almost_equal(pw, 0.042089745402336121, 6) - x2 = [1.36, 1.14, 2.92, 2.55, 1.46, 1.06, 5.27, -1.11, - 3.48, 1.10, 0.88, -0.51, 1.46, 0.52, 6.20, 1.69, - 0.08, 3.67, 2.81, 3.49] - w, pw = stats.shapiro(x2) - assert_almost_equal(w, 0.9590270, 6) - assert_almost_equal(pw, 0.52460, 3) + x1 = [0.11,7.87,4.61,10.14,7.95,3.14,0.46, + 4.43,0.21,4.75,0.71,1.52,3.24, + 0.93,0.42,4.97,9.53,4.55,0.47,6.66] + w,pw = stats.shapiro(x1) + assert_almost_equal(w,0.90047299861907959,6) + assert_almost_equal(pw,0.042089745402336121,6) + x2 = [1.36,1.14,2.92,2.55,1.46,1.06,5.27,-1.11, + 3.48,1.10,0.88,-0.51,1.46,0.52,6.20,1.69, + 0.08,3.67,2.81,3.49] + w,pw = stats.shapiro(x2) + assert_almost_equal(w,0.9590270,6) + assert_almost_equal(pw,0.52460,3) def test_bad_arg(self): # Length of x is less than 3. @@ -59,25 +57,24 @@ class TestShapiro(TestCase): class TestAnderson(TestCase): - def test_normal(self): rs = RandomState(1234567890) x1 = rs.standard_exponential(size=50) x2 = rs.standard_normal(size=50) - A, crit, _sig = stats.anderson(x1) + A,crit,sig = stats.anderson(x1) assert_array_less(crit[:-1], A) - A, crit, _sig = stats.anderson(x2) + A,crit,sig = stats.anderson(x2) assert_array_less(A, crit[-2:]) def test_expon(self): rs = RandomState(1234567890) x1 = rs.standard_exponential(size=50) x2 = rs.standard_normal(size=50) - A, crit, _sig = stats.anderson(x1, 'expon') + A,crit,sig = stats.anderson(x1,'expon') assert_array_less(A, crit[-2:]) olderr = np.seterr(all='ignore') try: - A, crit, _sig = stats.anderson(x2, 'expon') + A,crit,sig = stats.anderson(x2,'expon') finally: np.seterr(**olderr) assert_(A > crit[-1]) @@ -86,34 +83,150 @@ class TestAnderson(TestCase): assert_raises(ValueError, stats.anderson, [1], dist='plate_of_shrimp') +class TestAndersonKSamp(TestCase): + def test_example1a(self): + # Example data from Scholz & Stephens (1987), originally + # published in Lehmann (1995, Nonparametrics, Statistical + # Methods Based on Ranks, p. 309) + # Pass a mixture of lists and arrays + t1 = [38.7, 41.5, 43.8, 44.5, 45.5, 46.0, 47.7, 58.0] + t2 = np.array([39.2, 39.3, 39.7, 41.4, 41.8, 42.9, 43.3, 45.8]) + t3 = np.array([34.0, 35.0, 39.0, 40.0, 43.0, 43.0, 44.0, 45.0]) + t4 = np.array([34.0, 34.8, 34.8, 35.4, 37.2, 37.8, 41.2, 42.8]) + assert_warns(UserWarning, stats.anderson_ksamp, (t1, t2, t3, t4), + midrank=False) + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', message='approximate p-value') + Tk, tm, p = stats.anderson_ksamp((t1, t2, t3, t4), midrank=False) + + assert_almost_equal(Tk, 4.449, 3) + assert_array_almost_equal([0.4985, 1.3237, 1.9158, 2.4930, 3.2459], + tm, 4) + assert_almost_equal(p, 0.0021, 4) + + def test_example1b(self): + # Example data from Scholz & Stephens (1987), originally + # published in Lehmann (1995, Nonparametrics, Statistical + # Methods Based on Ranks, p. 309) + # Pass arrays + t1 = np.array([38.7, 41.5, 43.8, 44.5, 45.5, 46.0, 47.7, 58.0]) + t2 = np.array([39.2, 39.3, 39.7, 41.4, 41.8, 42.9, 43.3, 45.8]) + t3 = np.array([34.0, 35.0, 39.0, 40.0, 43.0, 43.0, 44.0, 45.0]) + t4 = np.array([34.0, 34.8, 34.8, 35.4, 37.2, 37.8, 41.2, 42.8]) + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', message='approximate p-value') + Tk, tm, p = stats.anderson_ksamp((t1, t2, t3, t4), midrank=True) + + assert_almost_equal(Tk, 4.480, 3) + assert_array_almost_equal([0.4985, 1.3237, 1.9158, 2.4930, 3.2459], + tm, 4) + assert_almost_equal(p, 0.0020, 4) + + def test_example2a(self): + # Example data taken from an earlier technical report of + # Scholz and Stephens + # Pass lists instead of arrays + t1 = [194, 15, 41, 29, 33, 181] + t2 = [413, 14, 58, 37, 100, 65, 9, 169, 447, 184, 36, 201, 118] + t3 = [34, 31, 18, 18, 67, 57, 62, 7, 22, 34] + t4 = [90, 10, 60, 186, 61, 49, 14, 24, 56, 20, 79, 84, 44, 59, 29, + 118, 25, 156, 310, 76, 26, 44, 23, 62] + t5 = [130, 208, 70, 101, 208] + t6 = [74, 57, 48, 29, 502, 12, 70, 21, 29, 386, 59, 27] + t7 = [55, 320, 56, 104, 220, 239, 47, 246, 176, 182, 33] + t8 = [23, 261, 87, 7, 120, 14, 62, 47, 225, 71, 246, 21, 42, 20, 5, + 12, 120, 11, 3, 14, 71, 11, 14, 11, 16, 90, 1, 16, 52, 95] + t9 = [97, 51, 11, 4, 141, 18, 142, 68, 77, 80, 1, 16, 106, 206, 82, + 54, 31, 216, 46, 111, 39, 63, 18, 191, 18, 163, 24] + t10 = [50, 44, 102, 72, 22, 39, 3, 15, 197, 188, 79, 88, 46, 5, 5, 36, + 22, 139, 210, 97, 30, 23, 13, 14] + t11 = [359, 9, 12, 270, 603, 3, 104, 2, 438] + t12 = [50, 254, 5, 283, 35, 12] + t13 = [487, 18, 100, 7, 98, 5, 85, 91, 43, 230, 3, 130] + t14 = [102, 209, 14, 57, 54, 32, 67, 59, 134, 152, 27, 14, 230, 66, + 61, 34] + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', message='approximate p-value') + Tk, tm, p = stats.anderson_ksamp((t1, t2, t3, t4, t5, t6, t7, t8, + t9, t10, t11, t12, t13, t14), + midrank=False) + + assert_almost_equal(Tk, 3.288, 3) + assert_array_almost_equal([0.5990, 1.3269, 1.8052, 2.2486, 2.8009], + tm, 4) + assert_almost_equal(p, 0.0041, 4) + + def test_example2b(self): + # Example data taken from an earlier technical report of + # Scholz and Stephens + t1 = [194, 15, 41, 29, 33, 181] + t2 = [413, 14, 58, 37, 100, 65, 9, 169, 447, 184, 36, 201, 118] + t3 = [34, 31, 18, 18, 67, 57, 62, 7, 22, 34] + t4 = [90, 10, 60, 186, 61, 49, 14, 24, 56, 20, 79, 84, 44, 59, 29, + 118, 25, 156, 310, 76, 26, 44, 23, 62] + t5 = [130, 208, 70, 101, 208] + t6 = [74, 57, 48, 29, 502, 12, 70, 21, 29, 386, 59, 27] + t7 = [55, 320, 56, 104, 220, 239, 47, 246, 176, 182, 33] + t8 = [23, 261, 87, 7, 120, 14, 62, 47, 225, 71, 246, 21, 42, 20, 5, + 12, 120, 11, 3, 14, 71, 11, 14, 11, 16, 90, 1, 16, 52, 95] + t9 = [97, 51, 11, 4, 141, 18, 142, 68, 77, 80, 1, 16, 106, 206, 82, + 54, 31, 216, 46, 111, 39, 63, 18, 191, 18, 163, 24] + t10 = [50, 44, 102, 72, 22, 39, 3, 15, 197, 188, 79, 88, 46, 5, 5, 36, + 22, 139, 210, 97, 30, 23, 13, 14] + t11 = [359, 9, 12, 270, 603, 3, 104, 2, 438] + t12 = [50, 254, 5, 283, 35, 12] + t13 = [487, 18, 100, 7, 98, 5, 85, 91, 43, 230, 3, 130] + t14 = [102, 209, 14, 57, 54, 32, 67, 59, 134, 152, 27, 14, 230, 66, + 61, 34] + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', message='approximate p-value') + Tk, tm, p = stats.anderson_ksamp((t1, t2, t3, t4, t5, t6, t7, t8, + t9, t10, t11, t12, t13, t14), + midrank=True) + + assert_almost_equal(Tk, 3.294, 3) + assert_array_almost_equal([0.5990, 1.3269, 1.8052, 2.2486, 2.8009], + tm, 4) + assert_almost_equal(p, 0.0041, 4) + + def test_not_enough_samples(self): + assert_raises(ValueError, stats.anderson_ksamp, np.ones(5)) + + def test_no_distinct_observations(self): + assert_raises(ValueError, stats.anderson_ksamp, + (np.ones(5), np.ones(5))) + + def test_empty_sample(self): + assert_raises(ValueError, stats.anderson_ksamp, (np.ones(5), [])) + + class TestAnsari(TestCase): def test_small(self): - x = [1, 2, 3, 3, 4] - y = [3, 2, 6, 1, 6, 1, 4, 1] - W, pval = stats.ansari(x, y) - assert_almost_equal(W, 23.5, 11) - assert_almost_equal(pval, 0.13499256881897437, 11) + x = [1,2,3,3,4] + y = [3,2,6,1,6,1,4,1] + W, pval = stats.ansari(x,y) + assert_almost_equal(W,23.5,11) + assert_almost_equal(pval,0.13499256881897437,11) def test_approx(self): ramsay = np.array((111, 107, 100, 99, 102, 106, 109, 108, 104, 99, 101, 96, 97, 102, 107, 113, 116, 113, 110, 98)) - parekh = np.array((107, 108, 106, 98, 105, 103, 110, 105, 104, 100, - 96, 108, 103, 104, 114, 114, 113, 108, 106, 99)) + parekh = np.array((107, 108, 106, 98, 105, 103, 110, 105, 104, + 100, 96, 108, 103, 104, 114, 114, 113, 108, 106, 99)) with warnings.catch_warnings(): warnings.filterwarnings('ignore', - message="Ties preclude use of exact " + - "statistic.") + message="Ties preclude use of exact statistic.") W, pval = stats.ansari(ramsay, parekh) - assert_almost_equal(W, 185.5, 11) - assert_almost_equal(pval, 0.18145819972867083, 11) + assert_almost_equal(W,185.5,11) + assert_almost_equal(pval,0.18145819972867083,11) def test_exact(self): - W, pval = stats.ansari([1, 2, 3, 4], [15, 5, 20, 8, 10, 12]) - assert_almost_equal(W, 10.0, 11) - assert_almost_equal(pval, 0.533333333333333333, 7) + W,pval = stats.ansari([1,2,3,4],[15,5,20,8,10,12]) + assert_almost_equal(W,10.0,11) + assert_almost_equal(pval,0.533333333333333333,7) def test_bad_arg(self): assert_raises(ValueError, stats.ansari, [], [1]) @@ -125,8 +238,8 @@ class TestBartlett(TestCase): def test_data(self): args = [g1, g2, g3, g4, g5, g6, g7, g8, g9, g10] T, pval = stats.bartlett(*args) - assert_almost_equal(T, 20.78587342806484, 7) - assert_almost_equal(pval, 0.0136358632781, 7) + assert_almost_equal(T,20.78587342806484,7) + assert_almost_equal(pval,0.0136358632781,7) def test_bad_arg(self): # Too few args raises ValueError. @@ -138,15 +251,14 @@ class TestLevene(TestCase): def test_data(self): args = [g1, g2, g3, g4, g5, g6, g7, g8, g9, g10] W, pval = stats.levene(*args) - assert_almost_equal(W, 1.7059176930008939, 7) - assert_almost_equal(pval, 0.0990829755522, 7) + assert_almost_equal(W,1.7059176930008939,7) + assert_almost_equal(pval,0.0990829755522,7) def test_trimmed1(self): # Test that center='trimmed' gives the same result as center='mean' # when proportiontocut=0. W1, pval1 = stats.levene(g1, g2, g3, center='mean') - W2, pval2 = stats.levene( - g1, g2, g3, center='trimmed', proportiontocut=0.0) + W2, pval2 = stats.levene(g1, g2, g3, center='trimmed', proportiontocut=0.0) assert_almost_equal(W1, W2) assert_almost_equal(pval1, pval2) @@ -157,10 +269,8 @@ class TestLevene(TestCase): x2 = np.random.permutation(x) # Use center='trimmed' - W0, _pval0 = stats.levene(x, y, center='trimmed', - proportiontocut=0.125) - W1, pval1 = stats.levene( - x2, y, center='trimmed', proportiontocut=0.125) + W0, pval0 = stats.levene(x, y, center='trimmed', proportiontocut=0.125) + W1, pval1 = stats.levene(x2, y, center='trimmed', proportiontocut=0.125) # Trim the data here, and use center='mean' W2, pval2 = stats.levene(x[1:-1], y[1:-1], center='mean') # Result should be the same. @@ -169,21 +279,21 @@ class TestLevene(TestCase): assert_almost_equal(pval1, pval2) def test_equal_mean_median(self): - x = np.linspace(-1, 1, 21) + x = np.linspace(-1,1,21) np.random.seed(1234) x2 = np.random.permutation(x) - y = x ** 3 + y = x**3 W1, pval1 = stats.levene(x, y, center='mean') W2, pval2 = stats.levene(x2, y, center='median') assert_almost_equal(W1, W2) assert_almost_equal(pval1, pval2) def test_bad_keyword(self): - x = np.linspace(-1, 1, 21) + x = np.linspace(-1,1,21) assert_raises(TypeError, stats.levene, x, x, portiontocut=0.1) def test_bad_center_value(self): - x = np.linspace(-1, 1, 21) + x = np.linspace(-1,1,21) assert_raises(ValueError, stats.levene, x, x, center='trim') def test_too_few_args(self): @@ -193,16 +303,16 @@ class TestLevene(TestCase): class TestBinomP(TestCase): def test_data(self): - pval = stats.binom_test(100, 250) - assert_almost_equal(pval, 0.0018833009350757682, 11) - pval = stats.binom_test(201, 405) - assert_almost_equal(pval, 0.92085205962670713, 11) - pval = stats.binom_test([682, 243], p=3.0 / 4) - assert_almost_equal(pval, 0.38249155957481695, 11) + pval = stats.binom_test(100,250) + assert_almost_equal(pval,0.0018833009350757682,11) + pval = stats.binom_test(201,405) + assert_almost_equal(pval,0.92085205962670713,11) + pval = stats.binom_test([682,243],p=3.0/4) + assert_almost_equal(pval,0.38249155957481695,11) def test_bad_len_x(self): # Length of x must be 1 or 2. - assert_raises(ValueError, stats.binom_test, [1, 2, 3]) + assert_raises(ValueError, stats.binom_test, [1,2,3]) def test_bad_n(self): # len(x) is 1, but n is invalid. @@ -218,10 +328,10 @@ class TestBinomP(TestCase): class TestFindRepeats(TestCase): def test_basic(self): - a = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 5] - res, nums = stats.find_repeats(a) - assert_array_equal(res, [1, 2, 3, 4]) - assert_array_equal(nums, [3, 3, 2, 2]) + a = [1,2,3,4,1,2,3,4,1,2,5] + res,nums = stats.find_repeats(a) + assert_array_equal(res,[1,2,3,4]) + assert_array_equal(nums,[3,3,2,2]) def test_empty_result(self): # Check that empty arrays are returned when there are no repeats. @@ -236,16 +346,14 @@ class TestFligner(TestCase): def test_data(self): # numbers from R: fligner.test in package stats x1 = np.arange(5) - assert_array_almost_equal(stats.fligner(x1, x1 ** 2), - (3.2282229927203536, 0.072379187848207877), - 11) + assert_array_almost_equal(stats.fligner(x1,x1**2), + (3.2282229927203536, 0.072379187848207877), 11) def test_trimmed1(self): # Test that center='trimmed' gives the same result as center='mean' # when proportiontocut=0. Xsq1, pval1 = stats.fligner(g1, g2, g3, center='mean') - Xsq2, pval2 = stats.fligner( - g1, g2, g3, center='trimmed', proportiontocut=0.0) + Xsq2, pval2 = stats.fligner(g1, g2, g3, center='trimmed', proportiontocut=0.0) assert_almost_equal(Xsq1, Xsq2) assert_almost_equal(pval1, pval2) @@ -253,8 +361,7 @@ class TestFligner(TestCase): x = [1.2, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 100.0] y = [0.0, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 200.0] # Use center='trimmed' - Xsq1, pval1 = stats.fligner( - x, y, center='trimmed', proportiontocut=0.125) + Xsq1, pval1 = stats.fligner(x, y, center='trimmed', proportiontocut=0.125) # Trim the data here, and use center='mean' Xsq2, pval2 = stats.fligner(x[1:-1], y[1:-1], center='mean') # Result should be the same. @@ -267,7 +374,7 @@ class TestFligner(TestCase): # errors) there are not. This difference leads to differences in the # third significant digit of W. # - # def test_equal_mean_median(self): + #def test_equal_mean_median(self): # x = np.linspace(-1,1,21) # y = x**3 # W1, pval1 = stats.fligner(x, y, center='mean') @@ -276,11 +383,11 @@ class TestFligner(TestCase): # assert_almost_equal(pval1, pval2) def test_bad_keyword(self): - x = np.linspace(-1, 1, 21) + x = np.linspace(-1,1,21) assert_raises(TypeError, stats.fligner, x, x, portiontocut=0.1) def test_bad_center_value(self): - x = np.linspace(-1, 1, 21) + x = np.linspace(-1,1,21) assert_raises(ValueError, stats.fligner, x, x, center='trim') def test_bad_num_args(self): @@ -289,13 +396,11 @@ class TestFligner(TestCase): class TestMood(TestCase): - def test_mood(self): # numbers from R: mood.test in package stats x1 = np.arange(5) - assert_array_almost_equal(stats.mood(x1, x1 ** 2), - (-1.3830857299399906, 0.16663858066771478), - 11) + assert_array_almost_equal(stats.mood(x1, x1**2), + (-1.3830857299399906, 0.16663858066771478), 11) def test_mood_order_of_args(self): # z should change sign when the order of arguments changes, pvalue @@ -308,24 +413,24 @@ class TestMood(TestCase): assert_array_almost_equal([z1, p1], [-z2, p2]) def test_mood_with_axis_none(self): - # Test with axis = None, compare with results from R + #Test with axis = None, compare with results from R x1 = [-0.626453810742332, 0.183643324222082, -0.835628612410047, - 1.59528080213779, 0.329507771815361, -0.820468384118015, - 0.487429052428485, 0.738324705129217, 0.575781351653492, + 1.59528080213779, 0.329507771815361, -0.820468384118015, + 0.487429052428485, 0.738324705129217, 0.575781351653492, -0.305388387156356, 1.51178116845085, 0.389843236411431, -0.621240580541804, -2.2146998871775, 1.12493091814311, -0.0449336090152309, -0.0161902630989461, 0.943836210685299, - 0.821221195098089, 0.593901321217509] + 0.821221195098089, 0.593901321217509] x2 = [-0.896914546624981, 0.184849184646742, 1.58784533120882, -1.13037567424629, -0.0802517565509893, 0.132420284381094, - 0.707954729271733, -0.23969802417184, 1.98447393665293, + 0.707954729271733, -0.23969802417184, 1.98447393665293, -0.138787012119665, 0.417650750792556, 0.981752777463662, -0.392695355503813, -1.03966897694891, 1.78222896030858, -2.31106908460517, 0.878604580921265, 0.035806718015226, - 1.01282869212708, 0.432265154539617, 2.09081920524915, + 1.01282869212708, 0.432265154539617, 2.09081920524915, -1.19992581964387, 1.58963820029007, 1.95465164222325, - 0.00493777682814261, -2.45170638784613, 0.477237302613617, + 0.00493777682814261, -2.45170638784613, 0.477237302613617, -0.596558168631403, 0.792203270299649, 0.289636710177348] x1 = np.array(x1) @@ -387,8 +492,7 @@ class TestMood(TestCase): stats.mood(slice1, slice2)) def test_mood_bad_arg(self): - # Raise ValueError when the sum of the lengths of the args is less than - # 3 + # Raise ValueError when the sum of the lengths of the args is less than 3 assert_raises(ValueError, stats.mood, [1], []) @@ -406,7 +510,7 @@ class TestProbplot(TestCase): assert_allclose(osr, np.sort(x)) assert_allclose(osm, osm_expected) - _res, res_fit = stats.probplot(x, fit=True) + res, res_fit = stats.probplot(x, fit=True) res_fit_expected = [1.05361841, 0.31297795, 0.98741609] assert_allclose(res_fit, res_fit_expected) @@ -423,7 +527,7 @@ class TestProbplot(TestCase): assert_allclose(osr1, osr2) assert_allclose(osr1, osr3) # Check giving (loc, scale) params for normal distribution - _osm, _osr = stats.probplot(x, sparams=(), fit=False) + osm, osr = stats.probplot(x, sparams=(), fit=False) def test_dist_keyword(self): np.random.seed(12345) @@ -437,9 +541,7 @@ class TestProbplot(TestCase): assert_raises(AttributeError, stats.probplot, x, dist=[]) class custom_dist(object): - """Some class that looks just enough like a distribution.""" - def ppf(self, q): return stats.norm.ppf(q, loc=2) @@ -482,8 +584,8 @@ class TestProbplot(TestCase): def test_wilcoxon_bad_arg(): # Raise ValueError when two args of different lengths are given or # zero_method is unknown. - assert_raises(ValueError, stats.wilcoxon, [1], [1, 2]) - assert_raises(ValueError, stats.wilcoxon, [1, 2], [1, 2], "dummy") + assert_raises(ValueError, stats.wilcoxon, [1], [1,2]) + assert_raises(ValueError, stats.wilcoxon, [1,2], [1,2], "dummy") def test_mvsdist_bad_arg(): @@ -519,7 +621,7 @@ class TestBoxcox_llf(TestCase): x = stats.norm.rvs(size=10000, loc=10) lmbda = 1 llf = stats.boxcox_llf(lmbda, x) - llf_expected = -x.size / 2. * np.log(np.sum(x.std() ** 2)) + llf_expected = -x.size / 2. * np.log(np.sum(x.std()**2)) assert_allclose(llf, llf_expected) def test_array_like(self): @@ -553,7 +655,7 @@ class TestBoxcox(TestCase): xt = stats.boxcox(x, lmbda=1) assert_allclose(xt, x - 1) xt = stats.boxcox(x, lmbda=-1) - assert_allclose(xt, 1 - 1 / x) + assert_allclose(xt, 1 - 1/x) xt = stats.boxcox(x, lmbda=0) assert_allclose(xt, np.log(x)) @@ -569,8 +671,8 @@ class TestBoxcox(TestCase): np.random.seed(1245) lmbda = 2.5 x = stats.norm.rvs(loc=10, size=50000) - x_inv = (x * lmbda + 1) ** (-lmbda) - _xt, maxlog = stats.boxcox(x_inv) + x_inv = (x * lmbda + 1)**(-lmbda) + xt, maxlog = stats.boxcox(x_inv) assert_almost_equal(maxlog, -1 / lmbda, decimal=2) @@ -601,18 +703,17 @@ class TestBoxcox(TestCase): class TestBoxcoxNormmax(TestCase): - def setUp(self): np.random.seed(12345) self.x = stats.loggamma.rvs(5, size=50) + 5 def test_pearsonr(self): maxlog = stats.boxcox_normmax(self.x) - assert_allclose(maxlog, 1.804465325046) + assert_allclose(maxlog, 1.804465, rtol=1e-6) def test_mle(self): maxlog = stats.boxcox_normmax(self.x, method='mle') - assert_allclose(maxlog, 1.758101454114) + assert_allclose(maxlog, 1.758101, rtol=1e-6) # Check that boxcox() uses 'mle' _, maxlog_boxcox = stats.boxcox(self.x) @@ -620,11 +721,10 @@ class TestBoxcoxNormmax(TestCase): def test_all(self): maxlog_all = stats.boxcox_normmax(self.x, method='all') - assert_allclose(maxlog_all, [1.804465325046, 1.758101454114]) + assert_allclose(maxlog_all, [1.804465, 1.758101], rtol=1e-6) class TestBoxcoxNormplot(TestCase): - def setUp(self): np.random.seed(7654321) self.x = stats.loggamma.rvs(5, size=500) + 5 @@ -662,9 +762,8 @@ class TestBoxcoxNormplot(TestCase): class TestCircFuncs(TestCase): - def test_circfuncs(self): - x = np.array([355, 5, 2, 359, 10, 350]) + x = np.array([355,5,2,359,10,350]) M = stats.circmean(x, high=360) Mval = 0.167690146 assert_allclose(M, Mval, rtol=1e-7) @@ -678,7 +777,7 @@ class TestCircFuncs(TestCase): assert_allclose(S, Sval, rtol=1e-7) def test_circfuncs_small(self): - x = np.array([20, 21, 22, 18, 19, 20.5, 19.2]) + x = np.array([20,21,22,18,19,20.5,19.2]) M1 = x.mean() M2 = stats.circmean(x, high=360) assert_allclose(M2, M1, rtol=1e-5) @@ -692,9 +791,9 @@ class TestCircFuncs(TestCase): assert_allclose(S2, S1, rtol=1e-4) def test_circmean_axis(self): - x = np.array([[355, 5, 2, 359, 10, 350], - [351, 7, 4, 352, 9, 349], - [357, 9, 8, 358, 4, 356]]) + x = np.array([[355,5,2,359,10,350], + [351,7,4,352,9,349], + [357,9,8,358,4,356]]) M1 = stats.circmean(x, high=360) M2 = stats.circmean(x.ravel(), high=360) assert_allclose(M1, M2, rtol=1e-14) @@ -704,13 +803,13 @@ class TestCircFuncs(TestCase): assert_allclose(M1, M2, rtol=1e-14) M1 = stats.circmean(x, high=360, axis=0) - M2 = [stats.circmean(x[:, i], high=360) for i in range(x.shape[1])] + M2 = [stats.circmean(x[:,i], high=360) for i in range(x.shape[1])] assert_allclose(M1, M2, rtol=1e-14) def test_circvar_axis(self): - x = np.array([[355, 5, 2, 359, 10, 350], - [351, 7, 4, 352, 9, 349], - [357, 9, 8, 358, 4, 356]]) + x = np.array([[355,5,2,359,10,350], + [351,7,4,352,9,349], + [357,9,8,358,4,356]]) V1 = stats.circvar(x, high=360) V2 = stats.circvar(x.ravel(), high=360) @@ -721,13 +820,13 @@ class TestCircFuncs(TestCase): assert_allclose(V1, V2, rtol=1e-11) V1 = stats.circvar(x, high=360, axis=0) - V2 = [stats.circvar(x[:, i], high=360) for i in range(x.shape[1])] + V2 = [stats.circvar(x[:,i], high=360) for i in range(x.shape[1])] assert_allclose(V1, V2, rtol=1e-11) def test_circstd_axis(self): - x = np.array([[355, 5, 2, 359, 10, 350], - [351, 7, 4, 352, 9, 349], - [357, 9, 8, 358, 4, 356]]) + x = np.array([[355,5,2,359,10,350], + [351,7,4,352,9,349], + [357,9,8,358,4,356]]) S1 = stats.circstd(x, high=360) S2 = stats.circstd(x.ravel(), high=360) @@ -738,11 +837,11 @@ class TestCircFuncs(TestCase): assert_allclose(S1, S2, rtol=1e-11) S1 = stats.circstd(x, high=360, axis=0) - S2 = [stats.circstd(x[:, i], high=360) for i in range(x.shape[1])] + S2 = [stats.circstd(x[:,i], high=360) for i in range(x.shape[1])] assert_allclose(S1, S2, rtol=1e-11) def test_circfuncs_array_like(self): - x = [355, 5, 2, 359, 10, 350] + x = [355,5,2,359,10,350] assert_allclose(stats.circmean(x, high=360), 0.167690146, rtol=1e-7) assert_allclose(stats.circvar(x, high=360), 42.51955609, rtol=1e-7) assert_allclose(stats.circstd(x, high=360), 6.520702116, rtol=1e-7) @@ -803,5 +902,108 @@ def test_wilcoxon_tie(): assert_allclose(p, expected_p, rtol=1e-6) +class TestMedianTest(TestCase): + + def test_bad_n_samples(self): + # median_test requires at least two samples. + assert_raises(ValueError, stats.median_test, [1, 2, 3]) + + def test_empty_sample(self): + # Each sample must contain at least one value. + assert_raises(ValueError, stats.median_test, [], [1, 2, 3]) + + def test_empty_when_ties_ignored(self): + # The grand median is 1, and all values in the first argument are + # equal to the grand median. With ties="ignore", those values are + # ignored, which results in the first sample being (in effect) empty. + # This should raise a ValueError. + assert_raises(ValueError, stats.median_test, + [1, 1, 1, 1], [2, 0, 1], [2, 0], ties="ignore") + + def test_empty_contingency_row(self): + # The grand median is 1, and with the default ties="below", all the + # values in the samples are counted as being below the grand median. + # This would result a row of zeros in the contingency table, which is + # an error. + assert_raises(ValueError, stats.median_test, [1, 1, 1], [1, 1, 1]) + + # With ties="above", all the values are counted as above the + # grand median. + assert_raises(ValueError, stats.median_test, [1, 1, 1], [1, 1, 1], + ties="above") + + def test_bad_ties(self): + assert_raises(ValueError, stats.median_test, [1, 2, 3], [4, 5], ties="foo") + + def test_bad_keyword(self): + assert_raises(TypeError, stats.median_test, [1, 2, 3], [4, 5], foo="foo") + + def test_simple(self): + x = [1, 2, 3] + y = [1, 2, 3] + stat, p, med, tbl = stats.median_test(x, y) + + # The median is floating point, but this equality test should be safe. + assert_equal(med, 2.0) + + assert_array_equal(tbl, [[1, 1], [2, 2]]) + + # The expected values of the contingency table equal the contingency table, + # so the statistic should be 0 and the p-value should be 1. + assert_equal(stat, 0) + assert_equal(p, 1) + + def test_ties_options(self): + # Test the contingency table calculation. + x = [1, 2, 3, 4] + y = [5, 6] + z = [7, 8, 9] + # grand median is 5. + + # Default 'ties' option is "below". + stat, p, m, tbl = stats.median_test(x, y, z) + assert_equal(m, 5) + assert_equal(tbl, [[0, 1, 3], [4, 1, 0]]) + + stat, p, m, tbl = stats.median_test(x, y, z, ties="ignore") + assert_equal(m, 5) + assert_equal(tbl, [[0, 1, 3], [4, 0, 0]]) + + stat, p, m, tbl = stats.median_test(x, y, z, ties="above") + assert_equal(m, 5) + assert_equal(tbl, [[0, 2, 3], [4, 0, 0]]) + + def test_basic(self): + # median_test calls chi2_contingency to compute the test statistic + # and p-value. Make sure it hasn't screwed up the call... + + x = [1, 2, 3, 4, 5] + y = [2, 4, 6, 8] + + stat, p, m, tbl = stats.median_test(x, y) + assert_equal(m, 4) + assert_equal(tbl, [[1, 2], [4, 2]]) + + exp_stat, exp_p, dof, e = stats.chi2_contingency(tbl) + assert_allclose(stat, exp_stat) + assert_allclose(p, exp_p) + + stat, p, m, tbl = stats.median_test(x, y, lambda_=0) + assert_equal(m, 4) + assert_equal(tbl, [[1, 2], [4, 2]]) + + exp_stat, exp_p, dof, e = stats.chi2_contingency(tbl, lambda_=0) + assert_allclose(stat, exp_stat) + assert_allclose(p, exp_p) + + stat, p, m, tbl = stats.median_test(x, y, correction=False) + assert_equal(m, 4) + assert_equal(tbl, [[1, 2], [4, 2]]) + + exp_stat, exp_p, dof, e = stats.chi2_contingency(tbl, correction=False) + assert_allclose(stat, exp_stat) + assert_allclose(p, exp_p) + + if __name__ == "__main__": run_module_suite() diff --git a/pywafo/src/wafo/stats/tests/test_mstats_basic.py b/pywafo/src/wafo/stats/tests/test_mstats_basic.py index a49fdad..1f242e4 100644 --- a/pywafo/src/wafo/stats/tests/test_mstats_basic.py +++ b/pywafo/src/wafo/stats/tests/test_mstats_basic.py @@ -1,5 +1,5 @@ """ -Tests for the stats.mstats module (support for maskd arrays) +Tests for the stats.mstats module (support for masked arrays) """ from __future__ import division, print_function, absolute_import @@ -13,14 +13,13 @@ from numpy.ma import masked, nomask import wafo.stats.mstats as mstats from wafo import stats from numpy.testing import TestCase, run_module_suite +from numpy.testing.decorators import skipif from numpy.ma.testutils import (assert_equal, assert_almost_equal, - assert_array_almost_equal, - assert_array_almost_equal_nulp, assert_, - assert_allclose, assert_raises) + assert_array_almost_equal, assert_array_almost_equal_nulp, assert_, + assert_allclose, assert_raises) class TestMquantiles(TestCase): - def test_mquantiles_limit_keyword(self): # Regression test for Trac ticket #867 data = np.array([[6., 7., 1.], @@ -42,102 +41,115 @@ class TestMquantiles(TestCase): class TestGMean(TestCase): - def test_1D(self): - a = (1, 2, 3, 4) + a = (1,2,3,4) actual = mstats.gmean(a) - desired = np.power(1 * 2 * 3 * 4, 1. / 4.) + desired = np.power(1*2*3*4,1./4.) assert_almost_equal(actual, desired, decimal=14) - desired1 = mstats.gmean(a, axis=-1) + desired1 = mstats.gmean(a,axis=-1) assert_almost_equal(actual, desired1, decimal=14) assert_(not isinstance(desired1, ma.MaskedArray)) - a = ma.array((1, 2, 3, 4), mask=(0, 0, 0, 1)) + a = ma.array((1,2,3,4),mask=(0,0,0,1)) actual = mstats.gmean(a) - desired = np.power(1 * 2 * 3, 1. / 3.) - assert_almost_equal(actual, desired, decimal=14) + desired = np.power(1*2*3,1./3.) + assert_almost_equal(actual, desired,decimal=14) - desired1 = mstats.gmean(a, axis=-1) + desired1 = mstats.gmean(a,axis=-1) assert_almost_equal(actual, desired1, decimal=14) + @skipif(not hasattr(np, 'float96'), 'cannot find float96 so skipping') + def test_1D_float96(self): + a = ma.array((1,2,3,4), mask=(0,0,0,1)) + actual_dt = mstats.gmean(a, dtype=np.float96) + desired_dt = np.power(1 * 2 * 3, 1. / 3.).astype(np.float96) + assert_almost_equal(actual_dt, desired_dt, decimal=14) + assert_(actual_dt.dtype == desired_dt.dtype) + def test_2D(self): a = ma.array(((1, 2, 3, 4), (1, 2, 3, 4), (1, 2, 3, 4)), mask=((0, 0, 0, 0), (1, 0, 0, 1), (0, 1, 1, 0))) actual = mstats.gmean(a) - desired = np.array((1, 2, 3, 4)) + desired = np.array((1,2,3,4)) assert_array_almost_equal(actual, desired, decimal=14) - desired1 = mstats.gmean(a, axis=0) + desired1 = mstats.gmean(a,axis=0) assert_array_almost_equal(actual, desired1, decimal=14) actual = mstats.gmean(a, -1) - desired = ma.array((np.power(1 * 2 * 3 * 4, 1. / 4.), - np.power(2 * 3, 1. / 2.), - np.power(1 * 4, 1. / 2.))) + desired = ma.array((np.power(1*2*3*4,1./4.), + np.power(2*3,1./2.), + np.power(1*4,1./2.))) assert_array_almost_equal(actual, desired, decimal=14) class TestHMean(TestCase): - def test_1D(self): - a = (1, 2, 3, 4) + a = (1,2,3,4) actual = mstats.hmean(a) - desired = 4. / (1. / 1 + 1. / 2 + 1. / 3 + 1. / 4) + desired = 4. / (1./1 + 1./2 + 1./3 + 1./4) assert_almost_equal(actual, desired, decimal=14) - desired1 = mstats.hmean(ma.array(a), axis=-1) + desired1 = mstats.hmean(ma.array(a),axis=-1) assert_almost_equal(actual, desired1, decimal=14) - a = ma.array((1, 2, 3, 4), mask=(0, 0, 0, 1)) + a = ma.array((1,2,3,4),mask=(0,0,0,1)) actual = mstats.hmean(a) - desired = 3. / (1. / 1 + 1. / 2 + 1. / 3) - assert_almost_equal(actual, desired, decimal=14) - desired1 = mstats.hmean(a, axis=-1) + desired = 3. / (1./1 + 1./2 + 1./3) + assert_almost_equal(actual, desired,decimal=14) + desired1 = mstats.hmean(a,axis=-1) assert_almost_equal(actual, desired1, decimal=14) + @skipif(not hasattr(np, 'float96'), 'cannot find float96 so skipping') + def test_1D_float96(self): + a = ma.array((1,2,3,4), mask=(0,0,0,1)) + actual_dt = mstats.hmean(a, dtype=np.float96) + desired_dt = np.asarray(3. / (1./1 + 1./2 + 1./3), + dtype=np.float96) + assert_almost_equal(actual_dt, desired_dt, decimal=14) + assert_(actual_dt.dtype == desired_dt.dtype) + def test_2D(self): - a = ma.array(((1, 2, 3, 4), (1, 2, 3, 4), (1, 2, 3, 4)), - mask=((0, 0, 0, 0), (1, 0, 0, 1), (0, 1, 1, 0))) + a = ma.array(((1,2,3,4),(1,2,3,4),(1,2,3,4)), + mask=((0,0,0,0),(1,0,0,1),(0,1,1,0))) actual = mstats.hmean(a) - desired = ma.array((1, 2, 3, 4)) + desired = ma.array((1,2,3,4)) assert_array_almost_equal(actual, desired, decimal=14) - actual1 = mstats.hmean(a, axis=-1) - desired = (4. / (1 / 1. + 1 / 2. + 1 / 3. + 1 / 4.), - 2. / (1 / 2. + 1 / 3.), - 2. / (1 / 1. + 1 / 4.) + actual1 = mstats.hmean(a,axis=-1) + desired = (4./(1/1.+1/2.+1/3.+1/4.), + 2./(1/2.+1/3.), + 2./(1/1.+1/4.) ) assert_array_almost_equal(actual1, desired, decimal=14) class TestRanking(TestCase): - def __init__(self, *args, **kwargs): TestCase.__init__(self, *args, **kwargs) def test_ranking(self): - x = ma.array([0, 1, 1, 1, 2, 3, 4, 5, 5, 6, ]) - assert_almost_equal( - mstats.rankdata(x), [1, 3, 3, 3, 5, 6, 7, 8.5, 8.5, 10]) - x[[3, 4]] = masked - assert_almost_equal( - mstats.rankdata(x), [1, 2.5, 2.5, 0, 0, 4, 5, 6.5, 6.5, 8]) + x = ma.array([0,1,1,1,2,3,4,5,5,6,]) + assert_almost_equal(mstats.rankdata(x), + [1,3,3,3,5,6,7,8.5,8.5,10]) + x[[3,4]] = masked + assert_almost_equal(mstats.rankdata(x), + [1,2.5,2.5,0,0,4,5,6.5,6.5,8]) assert_almost_equal(mstats.rankdata(x, use_missing=True), - [1, 2.5, 2.5, 4.5, 4.5, 4, 5, 6.5, 6.5, 8]) - x = ma.array([0, 1, 5, 1, 2, 4, 3, 5, 1, 6, ]) - assert_almost_equal( - mstats.rankdata(x), [1, 3, 8.5, 3, 5, 7, 6, 8.5, 3, 10]) - x = ma.array([[0, 1, 1, 1, 2], [3, 4, 5, 5, 6, ]]) - assert_almost_equal( - mstats.rankdata(x), [[1, 3, 3, 3, 5], [6, 7, 8.5, 8.5, 10]]) - assert_almost_equal( - mstats.rankdata(x, axis=1), [[1, 3, 3, 3, 5], [1, 2, 3.5, 3.5, 5]]) - assert_almost_equal( - mstats.rankdata(x, axis=0), [[1, 1, 1, 1, 1], [2, 2, 2, 2, 2, ]]) + [1,2.5,2.5,4.5,4.5,4,5,6.5,6.5,8]) + x = ma.array([0,1,5,1,2,4,3,5,1,6,]) + assert_almost_equal(mstats.rankdata(x), + [1,3,8.5,3,5,7,6,8.5,3,10]) + x = ma.array([[0,1,1,1,2], [3,4,5,5,6,]]) + assert_almost_equal(mstats.rankdata(x), + [[1,3,3,3,5], [6,7,8.5,8.5,10]]) + assert_almost_equal(mstats.rankdata(x, axis=1), + [[1,3,3,3,5], [1,2,3.5,3.5,5]]) + assert_almost_equal(mstats.rankdata(x,axis=0), + [[1,1,1,1,1], [2,2,2,2,2,]]) class TestCorr(TestCase): - def test_pearsonr(self): # Tests some computations of Pearson's r x = ma.arange(10) @@ -158,73 +170,72 @@ class TestCorr(TestCase): x1 = ma.array([-1.0, 0.0, 1.0]) y1 = ma.array([0, 0, 3]) r, p = mstats.pearsonr(x1, y1) - assert_almost_equal(r, np.sqrt(3) / 2) - assert_almost_equal(p, 1.0 / 3) + assert_almost_equal(r, np.sqrt(3)/2) + assert_almost_equal(p, 1.0/3) # (x2, y2) have the same unmasked data as (x1, y1). mask = [False, False, False, True] x2 = ma.array([-1.0, 0.0, 1.0, 99.0], mask=mask) y2 = ma.array([0, 0, 3, -1], mask=mask) r, p = mstats.pearsonr(x2, y2) - assert_almost_equal(r, np.sqrt(3) / 2) - assert_almost_equal(p, 1.0 / 3) + assert_almost_equal(r, np.sqrt(3)/2) + assert_almost_equal(p, 1.0/3) def test_spearmanr(self): # Tests some computations of Spearman's rho - (x, y) = ([5.05, 6.75, 3.21, 2.66], [1.65, 2.64, 2.64, 6.95]) - assert_almost_equal(mstats.spearmanr(x, y)[0], -0.6324555) - (x, y) = ([5.05, 6.75, 3.21, 2.66, np.nan], - [1.65, 2.64, 2.64, 6.95, np.nan]) + (x, y) = ([5.05,6.75,3.21,2.66],[1.65,2.64,2.64,6.95]) + assert_almost_equal(mstats.spearmanr(x,y)[0], -0.6324555) + (x, y) = ([5.05,6.75,3.21,2.66,np.nan],[1.65,2.64,2.64,6.95,np.nan]) (x, y) = (ma.fix_invalid(x), ma.fix_invalid(y)) - assert_almost_equal(mstats.spearmanr(x, y)[0], -0.6324555) + assert_almost_equal(mstats.spearmanr(x,y)[0], -0.6324555) x = [2.0, 47.4, 42.0, 10.8, 60.1, 1.7, 64.0, 63.1, - 1.0, 1.4, 7.9, 0.3, 3.9, 0.3, 6.7] + 1.0, 1.4, 7.9, 0.3, 3.9, 0.3, 6.7] y = [22.6, 8.3, 44.4, 11.9, 24.6, 0.6, 5.7, 41.6, - 0.0, 0.6, 6.7, 3.8, 1.0, 1.2, 1.4] - assert_almost_equal(mstats.spearmanr(x, y)[0], 0.6887299) + 0.0, 0.6, 6.7, 3.8, 1.0, 1.2, 1.4] + assert_almost_equal(mstats.spearmanr(x,y)[0], 0.6887299) x = [2.0, 47.4, 42.0, 10.8, 60.1, 1.7, 64.0, 63.1, - 1.0, 1.4, 7.9, 0.3, 3.9, 0.3, 6.7, np.nan] + 1.0, 1.4, 7.9, 0.3, 3.9, 0.3, 6.7, np.nan] y = [22.6, 8.3, 44.4, 11.9, 24.6, 0.6, 5.7, 41.6, - 0.0, 0.6, 6.7, 3.8, 1.0, 1.2, 1.4, np.nan] + 0.0, 0.6, 6.7, 3.8, 1.0, 1.2, 1.4, np.nan] (x, y) = (ma.fix_invalid(x), ma.fix_invalid(y)) - assert_almost_equal(mstats.spearmanr(x, y)[0], 0.6887299) + assert_almost_equal(mstats.spearmanr(x,y)[0], 0.6887299) def test_kendalltau(self): # Tests some computations of Kendall's tau - x = ma.fix_invalid([5.05, 6.75, 3.21, 2.66, np.nan]) + x = ma.fix_invalid([5.05, 6.75, 3.21, 2.66,np.nan]) y = ma.fix_invalid([1.65, 26.5, -5.93, 7.96, np.nan]) z = ma.fix_invalid([1.65, 2.64, 2.64, 6.95, np.nan]) - assert_almost_equal(np.asarray(mstats.kendalltau(x, y)), - [+0.3333333, 0.4969059]) - assert_almost_equal(np.asarray(mstats.kendalltau(x, z)), - [-0.5477226, 0.2785987]) + assert_almost_equal(np.asarray(mstats.kendalltau(x,y)), + [+0.3333333,0.4969059]) + assert_almost_equal(np.asarray(mstats.kendalltau(x,z)), + [-0.5477226,0.2785987]) # - x = ma.fix_invalid([0, 0, 0, 0, 20, 20, 0, 60, 0, 20, - 10, 10, 0, 40, 0, 20, 0, 0, 0, 0, 0, np.nan]) - y = ma.fix_invalid([0, 80, 80, 80, 10, 33, 60, 0, 67, 27, - 25, 80, 80, 80, 80, 80, 80, 0, 10, 45, np.nan, 0]) - result = mstats.kendalltau(x, y) + x = ma.fix_invalid([0, 0, 0, 0,20,20, 0,60, 0,20, + 10,10, 0,40, 0,20, 0, 0, 0, 0, 0, np.nan]) + y = ma.fix_invalid([0,80,80,80,10,33,60, 0,67,27, + 25,80,80,80,80,80,80, 0,10,45, np.nan, 0]) + result = mstats.kendalltau(x,y) assert_almost_equal(np.asarray(result), [-0.1585188, 0.4128009]) def test_kendalltau_seasonal(self): # Tests the seasonal Kendall tau. - x = [[nan, nan, 4, 2, 16, 26, 5, 1, 5, 1, 2, 3, 1], + x = [[nan,nan, 4, 2, 16, 26, 5, 1, 5, 1, 2, 3, 1], [4, 3, 5, 3, 2, 7, 3, 1, 1, 2, 3, 5, 3], - [3, 2, 5, 6, 18, 4, 9, 1, 1, nan, 1, 1, nan], - [nan, 6, 11, 4, 17, nan, 6, 1, 1, 2, 5, 1, 1]] + [3, 2, 5, 6, 18, 4, 9, 1, 1,nan, 1, 1,nan], + [nan, 6, 11, 4, 17,nan, 6, 1, 1, 2, 5, 1, 1]] x = ma.fix_invalid(x).T output = mstats.kendalltau_seasonal(x) assert_almost_equal(output['global p-value (indep)'], 0.008, 3) assert_almost_equal(output['seasonal p-value'].round(2), - [0.18, 0.53, 0.20, 0.04]) + [0.18,0.53,0.20,0.04]) def test_pointbiserial(self): - x = [1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, -1] - y = [14.8, 13.8, 12.4, 10.1, 7.1, 6.1, 5.8, 4.6, 4.3, 3.5, 3.3, 3.2, 3.0, - 2.8, 2.8, 2.5, 2.4, 2.3, 2.1, 1.7, 1.7, 1.5, 1.3, 1.3, 1.2, 1.2, 1.1, - 0.8, 0.7, 0.6, 0.5, 0.2, 0.2, 0.1, np.nan] + x = [1,0,1,1,1,1,0,1,0,0,0,1,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,1,0, + 0,0,0,0,1,-1] + y = [14.8,13.8,12.4,10.1,7.1,6.1,5.8,4.6,4.3,3.5,3.3,3.2,3.0, + 2.8,2.8,2.5,2.4,2.3,2.1,1.7,1.7,1.5,1.3,1.3,1.2,1.2,1.1, + 0.8,0.7,0.6,0.5,0.2,0.2,0.1,np.nan] assert_almost_equal(mstats.pointbiserialr(x, y)[0], 0.36149, 5) @@ -232,70 +243,71 @@ class TestTrimming(TestCase): def test_trim(self): a = ma.arange(10) - assert_equal(mstats.trim(a), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + assert_equal(mstats.trim(a), [0,1,2,3,4,5,6,7,8,9]) a = ma.arange(10) - assert_equal( - mstats.trim(a, (2, 8)), [None, None, 2, 3, 4, 5, 6, 7, 8, None]) + assert_equal(mstats.trim(a,(2,8)), [None,None,2,3,4,5,6,7,8,None]) a = ma.arange(10) - assert_equal(mstats.trim(a, limits=(2, 8), inclusive=(False, False)), - [None, None, None, 3, 4, 5, 6, 7, None, None]) + assert_equal(mstats.trim(a,limits=(2,8),inclusive=(False,False)), + [None,None,None,3,4,5,6,7,None,None]) a = ma.arange(10) - assert_equal(mstats.trim(a, limits=(0.1, 0.2), relative=True), - [None, 1, 2, 3, 4, 5, 6, 7, None, None]) + assert_equal(mstats.trim(a,limits=(0.1,0.2),relative=True), + [None,1,2,3,4,5,6,7,None,None]) a = ma.arange(12) - a[[0, -1]] = a[5] = masked - assert_equal(mstats.trim(a, (2, 8)), + a[[0,-1]] = a[5] = masked + assert_equal(mstats.trim(a, (2,8)), [None, None, 2, 3, 4, None, 6, 7, 8, None, None, None]) x = ma.arange(100).reshape(10, 10) - trimx = mstats.trim(x, (0.1, 0.2), relative=True, axis=None) - assert_equal(trimx._mask.ravel(), [1] * 10 + [0] * 70 + [1] * 20) - trimx = mstats.trim(x, (0.1, 0.2), relative=True, axis=0) - assert_equal(trimx._mask.ravel(), [1] * 10 + [0] * 70 + [1] * 20) - trimx = mstats.trim(x, (0.1, 0.2), relative=True, axis=-1) - assert_equal(trimx._mask.T.ravel(), [1] * 10 + [0] * 70 + [1] * 20) - + expected = [1]*10 + [0]*70 + [1]*20 + trimx = mstats.trim(x, (0.1,0.2), relative=True, axis=None) + assert_equal(trimx._mask.ravel(), expected) + trimx = mstats.trim(x, (0.1,0.2), relative=True, axis=0) + assert_equal(trimx._mask.ravel(), expected) + trimx = mstats.trim(x, (0.1,0.2), relative=True, axis=-1) + assert_equal(trimx._mask.T.ravel(), expected) + + # same as above, but with an extra masked row inserted x = ma.arange(110).reshape(11, 10) x[1] = masked - trimx = mstats.trim(x, (0.1, 0.2), relative=True, axis=None) - assert_equal(trimx._mask.ravel(), [1] * 20 + [0] * 70 + [1] * 20) - trimx = mstats.trim(x, (0.1, 0.2), relative=True, axis=0) - assert_equal(trimx._mask.ravel(), [1] * 20 + [0] * 70 + [1] * 20) - trimx = mstats.trim(x.T, (0.1, 0.2), relative=True, axis=-1) - assert_equal(trimx.T._mask.ravel(), [1] * 20 + [0] * 70 + [1] * 20) + expected = [1]*20 + [0]*70 + [1]*20 + trimx = mstats.trim(x, (0.1,0.2), relative=True, axis=None) + assert_equal(trimx._mask.ravel(), expected) + trimx = mstats.trim(x, (0.1,0.2), relative=True, axis=0) + assert_equal(trimx._mask.ravel(), expected) + trimx = mstats.trim(x.T, (0.1,0.2), relative=True, axis=-1) + assert_equal(trimx.T._mask.ravel(), expected) def test_trim_old(self): x = ma.arange(100) assert_equal(mstats.trimboth(x).count(), 60) - assert_equal(mstats.trimtail(x, tail='r').count(), 80) + assert_equal(mstats.trimtail(x,tail='r').count(), 80) x[50:70] = masked trimx = mstats.trimboth(x) assert_equal(trimx.count(), 48) - assert_equal( - trimx._mask, [1] * 16 + [0] * 34 + [1] * 20 + [0] * 14 + [1] * 16) + assert_equal(trimx._mask, [1]*16 + [0]*34 + [1]*20 + [0]*14 + [1]*16) x._mask = nomask - x.shape = (10, 10) + x.shape = (10,10) assert_equal(mstats.trimboth(x).count(), 60) assert_equal(mstats.trimtail(x).count(), 80) def test_trimmedmean(self): - data = ma.array([77, 87, 88, 114, 151, 210, 219, 246, 253, 262, - 296, 299, 306, 376, 428, 515, 666, 1310, 2611]) - assert_almost_equal(mstats.trimmed_mean(data, 0.1), 343, 0) - assert_almost_equal(mstats.trimmed_mean(data, (0.1, 0.1)), 343, 0) - assert_almost_equal(mstats.trimmed_mean(data, (0.2, 0.2)), 283, 0) + data = ma.array([77, 87, 88,114,151,210,219,246,253,262, + 296,299,306,376,428,515,666,1310,2611]) + assert_almost_equal(mstats.trimmed_mean(data,0.1), 343, 0) + assert_almost_equal(mstats.trimmed_mean(data,(0.1,0.1)), 343, 0) + assert_almost_equal(mstats.trimmed_mean(data,(0.2,0.2)), 283, 0) def test_trimmed_stde(self): - data = ma.array([77, 87, 88, 114, 151, 210, 219, 246, 253, 262, - 296, 299, 306, 376, 428, 515, 666, 1310, 2611]) - assert_almost_equal(mstats.trimmed_stde(data, (0.2, 0.2)), 56.13193, 5) - assert_almost_equal(mstats.trimmed_stde(data, 0.2), 56.13193, 5) + data = ma.array([77, 87, 88,114,151,210,219,246,253,262, + 296,299,306,376,428,515,666,1310,2611]) + assert_almost_equal(mstats.trimmed_stde(data,(0.2,0.2)), 56.13193, 5) + assert_almost_equal(mstats.trimmed_stde(data,0.2), 56.13193, 5) def test_winsorization(self): - data = ma.array([77, 87, 88, 114, 151, 210, 219, 246, 253, 262, - 296, 299, 306, 376, 428, 515, 666, 1310, 2611]) - assert_almost_equal(mstats.winsorize(data, (0.2, 0.2)).var(ddof=1), + data = ma.array([77, 87, 88,114,151,210,219,246,253,262, + 296,299,306,376,428,515,666,1310,2611]) + assert_almost_equal(mstats.winsorize(data,(0.2,0.2)).var(ddof=1), 21551.4, 1) data[5] = masked winsorized = mstats.winsorize(data) @@ -310,60 +322,60 @@ class TestMoments(TestCase): # http://www.mathworks.com/access/helpdesk/help/toolbox/stats/kurtosis.shtml # http://www.mathworks.com/access/helpdesk/help/toolbox/stats/skewness.shtml # Note that both test cases came from here. - testcase = [1, 2, 3, 4] + testcase = [1,2,3,4] testmathworks = ma.fix_invalid([1.165, 0.6268, 0.0751, 0.3516, -0.6965, np.nan]) testcase_2d = ma.array( - np.array([[0.05245846, 0.50344235, 0.86589117, 0.36936353, 0.46961149], - [0.11574073, 0.31299969, 0.45925772, 0.72618805, 0.75194407], - [0.67696689, 0.91878127, 0.09769044, 0.04645137, 0.37615733], - [0.05903624, 0.29908861, 0.34088298, 0.66216337, 0.83160998], - [0.64619526, 0.94894632, 0.27855892, 0.0706151, 0.39962917]]), - mask=np.array([[True, False, False, True, False], - [True, True, True, False, True], - [False, False, False, False, False], - [True, True, True, True, True], - [False, False, True, False, False]], dtype=np.bool)) + np.array([[0.05245846, 0.50344235, 0.86589117, 0.36936353, 0.46961149], + [0.11574073, 0.31299969, 0.45925772, 0.72618805, 0.75194407], + [0.67696689, 0.91878127, 0.09769044, 0.04645137, 0.37615733], + [0.05903624, 0.29908861, 0.34088298, 0.66216337, 0.83160998], + [0.64619526, 0.94894632, 0.27855892, 0.0706151, 0.39962917]]), + mask=np.array([[True, False, False, True, False], + [True, True, True, False, True], + [False, False, False, False, False], + [True, True, True, True, True], + [False, False, True, False, False]], dtype=np.bool)) def test_moment(self): - y = mstats.moment(self.testcase, 1) - assert_almost_equal(y, 0.0, 10) - y = mstats.moment(self.testcase, 2) - assert_almost_equal(y, 1.25) - y = mstats.moment(self.testcase, 3) - assert_almost_equal(y, 0.0) - y = mstats.moment(self.testcase, 4) - assert_almost_equal(y, 2.5625) + y = mstats.moment(self.testcase,1) + assert_almost_equal(y,0.0,10) + y = mstats.moment(self.testcase,2) + assert_almost_equal(y,1.25) + y = mstats.moment(self.testcase,3) + assert_almost_equal(y,0.0) + y = mstats.moment(self.testcase,4) + assert_almost_equal(y,2.5625) def test_variation(self): y = mstats.variation(self.testcase) - assert_almost_equal(y, 0.44721359549996, 10) + assert_almost_equal(y,0.44721359549996, 10) def test_skewness(self): y = mstats.skew(self.testmathworks) - assert_almost_equal(y, -0.29322304336607, 10) - y = mstats.skew(self.testmathworks, bias=0) - assert_almost_equal(y, -0.437111105023940, 10) + assert_almost_equal(y,-0.29322304336607,10) + y = mstats.skew(self.testmathworks,bias=0) + assert_almost_equal(y,-0.437111105023940,10) y = mstats.skew(self.testcase) - assert_almost_equal(y, 0.0, 10) + assert_almost_equal(y,0.0,10) def test_kurtosis(self): # Set flags for axis = 0 and fisher=0 (Pearson's definition of kurtosis # for compatibility with Matlab) - y = mstats.kurtosis(self.testmathworks, 0, fisher=0, bias=1) - assert_almost_equal(y, 2.1658856802973, 10) + y = mstats.kurtosis(self.testmathworks,0,fisher=0,bias=1) + assert_almost_equal(y, 2.1658856802973,10) # Note that MATLAB has confusing docs for the following case # kurtosis(x,0) gives an unbiased estimate of Pearson's skewness # kurtosis(x) gives a biased estimate of Fisher's skewness (Pearson-3) # The MATLAB docs imply that both should give Fisher's - y = mstats.kurtosis(self.testmathworks, fisher=0, bias=0) - assert_almost_equal(y, 3.663542721189047, 10) - y = mstats.kurtosis(self.testcase, 0, 0) - assert_almost_equal(y, 1.64) + y = mstats.kurtosis(self.testmathworks,fisher=0, bias=0) + assert_almost_equal(y, 3.663542721189047,10) + y = mstats.kurtosis(self.testcase,0,0) + assert_almost_equal(y,1.64) # test that kurtosis works on multidimensional masked arrays correct_2d = ma.array(np.array([-1.5, -3., -1.47247052385, 0., - - 1.26979517952]), + -1.26979517952]), mask=np.array([False, False, False, True, False], dtype=np.bool)) assert_array_almost_equal(mstats.kurtosis(self.testcase_2d, 1), @@ -386,40 +398,34 @@ class TestMoments(TestCase): stats.kurtosis(self.testcase_2d[2, :])) def test_mode(self): - a1 = [0, 0, 0, 1, 1, 1, 2, 3, 3, 3, 3, 4, 5, 6, 7] - a2 = np.reshape(a1, (3, 5)) - a3 = np.array([1, 2, 3, 4, 5, 6]) - a4 = np.reshape(a3, (3, 2)) + a1 = [0,0,0,1,1,1,2,3,3,3,3,4,5,6,7] + a2 = np.reshape(a1, (3,5)) + a3 = np.array([1,2,3,4,5,6]) + a4 = np.reshape(a3, (3,2)) ma1 = ma.masked_where(ma.array(a1) > 2, a1) ma2 = ma.masked_where(a2 > 2, a2) ma3 = ma.masked_where(a3 < 2, a3) ma4 = ma.masked_where(ma.array(a4) < 2, a4) - assert_equal(mstats.mode(a1, axis=None), (3, 4)) - assert_equal(mstats.mode(a1, axis=0), (3, 4)) - assert_equal(mstats.mode(ma1, axis=None), (0, 3)) - assert_equal(mstats.mode(a2, axis=None), (3, 4)) - assert_equal(mstats.mode(ma2, axis=None), (0, 3)) - assert_equal(mstats.mode(a3, axis=None), (1, 1)) - assert_equal(mstats.mode(ma3, axis=None), (2, 1)) - assert_equal( - mstats.mode(a2, axis=0), ([[0, 0, 0, 1, 1]], [[1, 1, 1, 1, 1]])) - assert_equal( - mstats.mode(ma2, axis=0), ([[0, 0, 0, 1, 1]], [[1, 1, 1, 1, 1]])) - assert_equal( - mstats.mode(a2, axis=-1), ([[0], [3], [3]], [[3], [3], [1]])) - assert_equal( - mstats.mode(ma2, axis=-1), ([[0], [1], [0]], [[3], [1], [0]])) - assert_equal(mstats.mode(ma4, axis=0), ([[3, 2]], [[1, 1]])) - assert_equal( - mstats.mode(ma4, axis=-1), ([[2], [3], [5]], [[1], [1], [1]])) + assert_equal(mstats.mode(a1, axis=None), (3,4)) + assert_equal(mstats.mode(a1, axis=0), (3,4)) + assert_equal(mstats.mode(ma1, axis=None), (0,3)) + assert_equal(mstats.mode(a2, axis=None), (3,4)) + assert_equal(mstats.mode(ma2, axis=None), (0,3)) + assert_equal(mstats.mode(a3, axis=None), (1,1)) + assert_equal(mstats.mode(ma3, axis=None), (2,1)) + assert_equal(mstats.mode(a2, axis=0), ([[0,0,0,1,1]], [[1,1,1,1,1]])) + assert_equal(mstats.mode(ma2, axis=0), ([[0,0,0,1,1]], [[1,1,1,1,1]])) + assert_equal(mstats.mode(a2, axis=-1), ([[0],[3],[3]], [[3],[3],[1]])) + assert_equal(mstats.mode(ma2, axis=-1), ([[0],[1],[0]], [[3],[1],[0]])) + assert_equal(mstats.mode(ma4, axis=0), ([[3,2]], [[1,1]])) + assert_equal(mstats.mode(ma4, axis=-1), ([[2],[3],[5]], [[1],[1],[1]])) class TestPercentile(TestCase): - def setUp(self): - self.a1 = [3, 4, 5, 10, -3, -5, 6] - self.a2 = [3, -6, -2, 8, 7, 4, 2, 1] - self.a3 = [3., 4, 5, 10, -3, -5, -6, 7.0] + self.a1 = [3,4,5,10,-3,-5,6] + self.a2 = [3,-6,-2,8,7,4,2,1] + self.a3 = [3.,4,5,10,-3,-5,-6,7.0] def test_percentile(self): x = np.arange(8) * 0.5 @@ -433,15 +439,14 @@ class TestPercentile(TestCase): [4, 4, 3], [1, 1, 1], [1, 1, 1]]) - assert_equal(mstats.scoreatpercentile(x, 50), [1, 1, 1]) + assert_equal(mstats.scoreatpercentile(x,50), [1,1,1]) class TestVariability(TestCase): - """ Comparison numbers are found using R v.1.5.1 note that length(testcase) = 4 """ - testcase = ma.fix_invalid([1, 2, 3, 4, np.nan]) + testcase = ma.fix_invalid([1,2,3,4,np.nan]) def test_signaltonoise(self): # This is not in R, so used: @@ -454,7 +459,7 @@ class TestVariability(TestCase): y = mstats.sem(self.testcase) assert_almost_equal(y, 0.6454972244) n = self.testcase.count() - assert_allclose(mstats.sem(self.testcase, ddof=0) * np.sqrt(n / (n - 2)), + assert_allclose(mstats.sem(self.testcase, ddof=0) * np.sqrt(n/(n-2)), mstats.sem(self.testcase, ddof=2)) def test_zmap(self): @@ -478,41 +483,41 @@ class TestVariability(TestCase): class TestMisc(TestCase): def test_obrientransform(self): - args = [[5] * 5 + [6] * 11 + [7] * 9 + [8] * 3 + [9] * 2 + [10] * 2, - [6] + [7] * 2 + [8] * 4 + [9] * 9 + [10] * 16] - result = [5 * [3.1828] + 11 * [0.5591] + 9 * [0.0344] + 3 * [1.6086] + 2 * [5.2817] + 2 * [11.0538], - [10.4352] + 2 * [4.8599] + 4 * [1.3836] + 9 * [0.0061] + 16 * [0.7277]] - assert_almost_equal(np.round(mstats.obrientransform(*args).T, 4), - result, 4) + args = [[5]*5+[6]*11+[7]*9+[8]*3+[9]*2+[10]*2, + [6]+[7]*2+[8]*4+[9]*9+[10]*16] + result = [5*[3.1828]+11*[0.5591]+9*[0.0344]+3*[1.6086]+2*[5.2817]+2*[11.0538], + [10.4352]+2*[4.8599]+4*[1.3836]+9*[0.0061]+16*[0.7277]] + assert_almost_equal(np.round(mstats.obrientransform(*args).T,4), + result,4) def test_kstwosamp(self): - x = [[nan, nan, 4, 2, 16, 26, 5, 1, 5, 1, 2, 3, 1], + x = [[nan,nan, 4, 2, 16, 26, 5, 1, 5, 1, 2, 3, 1], [4, 3, 5, 3, 2, 7, 3, 1, 1, 2, 3, 5, 3], - [3, 2, 5, 6, 18, 4, 9, 1, 1, nan, 1, 1, nan], - [nan, 6, 11, 4, 17, nan, 6, 1, 1, 2, 5, 1, 1]] + [3, 2, 5, 6, 18, 4, 9, 1, 1,nan, 1, 1,nan], + [nan, 6, 11, 4, 17,nan, 6, 1, 1, 2, 5, 1, 1]] x = ma.fix_invalid(x).T - (winter, spring, summer, fall) = x.T + (winter,spring,summer,fall) = x.T - assert_almost_equal(np.round(mstats.ks_twosamp(winter, spring), 4), - (0.1818, 0.9892)) - assert_almost_equal(np.round(mstats.ks_twosamp(winter, spring, 'g'), 4), - (0.1469, 0.7734)) - assert_almost_equal(np.round(mstats.ks_twosamp(winter, spring, 'l'), 4), - (0.1818, 0.6744)) + assert_almost_equal(np.round(mstats.ks_twosamp(winter,spring),4), + (0.1818,0.9892)) + assert_almost_equal(np.round(mstats.ks_twosamp(winter,spring,'g'),4), + (0.1469,0.7734)) + assert_almost_equal(np.round(mstats.ks_twosamp(winter,spring,'l'),4), + (0.1818,0.6744)) def test_friedmanchisq(self): # No missing values - args = ([9.0, 9.5, 5.0, 7.5, 9.5, 7.5, 8.0, 7.0, 8.5, 6.0], - [7.0, 6.5, 7.0, 7.5, 5.0, 8.0, 6.0, 6.5, 7.0, 7.0], - [6.0, 8.0, 4.0, 6.0, 7.0, 6.5, 6.0, 4.0, 6.5, 3.0]) + args = ([9.0,9.5,5.0,7.5,9.5,7.5,8.0,7.0,8.5,6.0], + [7.0,6.5,7.0,7.5,5.0,8.0,6.0,6.5,7.0,7.0], + [6.0,8.0,4.0,6.0,7.0,6.5,6.0,4.0,6.5,3.0]) result = mstats.friedmanchisquare(*args) assert_almost_equal(result[0], 10.4737, 4) assert_almost_equal(result[1], 0.005317, 6) # Missing values - x = [[nan, nan, 4, 2, 16, 26, 5, 1, 5, 1, 2, 3, 1], + x = [[nan,nan, 4, 2, 16, 26, 5, 1, 5, 1, 2, 3, 1], [4, 3, 5, 3, 2, 7, 3, 1, 1, 2, 3, 5, 3], - [3, 2, 5, 6, 18, 4, 9, 1, 1, nan, 1, 1, nan], - [nan, 6, 11, 4, 17, nan, 6, 1, 1, 2, 5, 1, 1]] + [3, 2, 5, 6, 18, 4, 9, 1, 1,nan, 1, 1,nan], + [nan, 6, 11, 4, 17,nan, 6, 1, 1, 2, 5, 1, 1]] x = ma.fix_invalid(x) result = mstats.friedmanchisquare(*x) assert_almost_equal(result[0], 2.0156, 4) @@ -530,6 +535,27 @@ def test_regress_simple(): assert_almost_equal(intercept, 10.211269918932341) +def test_theilslopes(): + # Test for basic slope and intercept. + slope, intercept, lower, upper = mstats.theilslopes([0,1,1]) + assert_almost_equal(slope, 0.5) + assert_almost_equal(intercept, 0.5) + + # Test for correct masking. + y = np.ma.array([0,1,100,1], mask=[False, False, True, False]) + slope, intercept, lower, upper = mstats.theilslopes(y) + assert_almost_equal(slope, 1./3) + assert_almost_equal(intercept, 2./3) + + # Test of confidence intervals from example in Sen (1968). + x = [1, 2, 3, 4, 10, 12, 18] + y = [9, 15, 19, 20, 45, 55, 78] + slope, intercept, lower, upper = mstats.theilslopes(y, x, 0.07) + assert_almost_equal(slope, 4) + assert_almost_equal(upper, 4.38, decimal=2) + assert_almost_equal(lower, 3.71, decimal=2) + + def test_plotting_positions(): # Regression test for #1256 pos = mstats.plotting_positions(np.arange(3), 0, 0) @@ -539,9 +565,11 @@ def test_plotting_positions(): class TestNormalitytests(): def test_vs_nonmasked(self): - x = np.array((-2, -1, 0, 1, 2, 3) * 4) ** 2 - assert_array_almost_equal(mstats.normaltest(x), stats.normaltest(x)) - assert_array_almost_equal(mstats.skewtest(x), stats.skewtest(x)) + x = np.array((-2,-1,0,1,2,3)*4)**2 + assert_array_almost_equal(mstats.normaltest(x), + stats.normaltest(x)) + assert_array_almost_equal(mstats.skewtest(x), + stats.skewtest(x)) assert_array_almost_equal(mstats.kurtosistest(x), stats.kurtosistest(x)) @@ -554,7 +582,7 @@ class TestNormalitytests(): def test_axis_None(self): # Test axis=None (equal to axis=0 for 1-D input) - x = np.array((-2, -1, 0, 1, 2, 3) * 4) ** 2 + x = np.array((-2,-1,0,1,2,3)*4)**2 assert_allclose(mstats.normaltest(x, axis=None), mstats.normaltest(x)) assert_allclose(mstats.skewtest(x, axis=None), mstats.skewtest(x)) assert_allclose(mstats.kurtosistest(x, axis=None), @@ -562,7 +590,7 @@ class TestNormalitytests(): def test_maskedarray_input(self): # Add some masked values, test result doesn't change - x = np.array((-2, -1, 0, 1, 2, 3) * 4) ** 2 + x = np.array((-2,-1,0,1,2,3)*4)**2 xm = np.ma.array(np.r_[np.inf, x, 10], mask=np.r_[True, [False] * x.size, True]) assert_allclose(mstats.normaltest(xm), stats.normaltest(x)) @@ -570,7 +598,7 @@ class TestNormalitytests(): assert_allclose(mstats.kurtosistest(xm), stats.kurtosistest(x)) def test_nd_input(self): - x = np.array((-2, -1, 0, 1, 2, 3) * 4) ** 2 + x = np.array((-2,-1,0,1,2,3)*4)**2 x_2d = np.vstack([x] * 2).T for func in [mstats.normaltest, mstats.skewtest, mstats.kurtosistest]: res_1d = func(x) @@ -579,9 +607,8 @@ class TestNormalitytests(): assert_allclose(res_2d[1], [res_1d[1]] * 2) -# TODO: for all ttest functions, add tests with masked array inputs +#TODO: for all ttest functions, add tests with masked array inputs class TestTtest_rel(): - def test_vs_nonmasked(self): np.random.seed(1234567) outcome = np.random.randn(20, 4) + [0, 0, 1, 2] @@ -618,7 +645,6 @@ class TestTtest_rel(): class TestTtest_ind(): - def test_vs_nonmasked(self): np.random.seed(1234567) outcome = np.random.randn(20, 4) + [0, 0, 1, 2] @@ -646,7 +672,6 @@ class TestTtest_ind(): class TestTtest_1samp(): - def test_vs_nonmasked(self): np.random.seed(1234567) outcome = np.random.randn(20, 4) + [0, 0, 1, 2] @@ -673,5 +698,358 @@ class TestTtest_1samp(): assert_(np.all(np.isnan(res1))) +class TestCompareWithStats(TestCase): + """ + Class to compare mstats results with stats results. + + It is in general assumed that scipy.stats is at a more mature stage than + stats.mstats. If a routine in mstats results in similar results like in + scipy.stats, this is considered also as a proper validation of scipy.mstats + routine. + + Different sample sizes are used for testing, as some problems between stats + and mstats are dependent on sample size. + + Author: Alexander Loew + + NOTE that some tests fail. This might be caused by + a) actual differences or bugs between stats and mstats + b) numerical inaccuracies + c) different definitions of routine interfaces + + These failures need to be checked. Current workaround is to have disabled these tests, + but issuing reports on scipy-dev + + """ + def get_n(self): + """ Returns list of sample sizes to be used for comparison. """ + return [1000, 100, 10, 5] + + def generate_xy_sample(self, n): + # This routine generates numpy arrays and corresponding masked arrays + # with the same data, but additional masked values + np.random.seed(1234567) + x = np.random.randn(n) + y = x + np.random.randn(n) + xm = np.ones(len(x) + 5) * 1e16 + ym = np.ones(len(y) + 5) * 1e16 + xm[0:len(x)] = x + ym[0:len(y)] = y + mask = xm > 9e15 + xm = np.ma.array(xm, mask=mask) + ym = np.ma.array(ym, mask=mask) + return x, y, xm, ym + + def generate_xy_sample2D(self, n, nx): + x = np.ones((n, nx)) * np.nan + y = np.ones((n, nx)) * np.nan + xm = np.ones((n+5, nx)) * np.nan + ym = np.ones((n+5, nx)) * np.nan + + for i in range(nx): + x[:,i], y[:,i], dx, dy = self.generate_xy_sample(n) + + xm[0:n, :] = x[0:n] + ym[0:n, :] = y[0:n] + xm = np.ma.array(xm, mask=np.isnan(xm)) + ym = np.ma.array(ym, mask=np.isnan(ym)) + return x, y, xm, ym + + def test_linregress(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + res1 = stats.linregress(x, y) + res2 = stats.mstats.linregress(xm, ym) + assert_allclose(np.asarray(res1), np.asarray(res2)) + + def test_pearsonr(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + r, p = stats.pearsonr(x, y) + rm, pm = stats.mstats.pearsonr(xm, ym) + + assert_almost_equal(r, rm, decimal=14) + assert_almost_equal(p, pm, decimal=14) + + def test_spearmanr(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + r, p = stats.spearmanr(x, y) + rm, pm = stats.mstats.spearmanr(xm, ym) + assert_almost_equal(r, rm, 14) + assert_almost_equal(p, pm, 14) + + def test_gmean(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + r = stats.gmean(abs(x)) + rm = stats.mstats.gmean(abs(xm)) + assert_allclose(r, rm, rtol=1e-13) + + r = stats.gmean(abs(y)) + rm = stats.mstats.gmean(abs(ym)) + assert_allclose(r, rm, rtol=1e-13) + + def test_hmean(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + + r = stats.hmean(abs(x)) + rm = stats.mstats.hmean(abs(xm)) + assert_almost_equal(r, rm, 10) + + r = stats.hmean(abs(y)) + rm = stats.mstats.hmean(abs(ym)) + assert_almost_equal(r, rm, 10) + + def test_skew(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + + r = stats.skew(x) + rm = stats.mstats.skew(xm) + assert_almost_equal(r, rm, 10) + + r = stats.skew(y) + rm = stats.mstats.skew(ym) + assert_almost_equal(r, rm, 10) + + def test_moment(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + + r = stats.moment(x) + rm = stats.mstats.moment(xm) + assert_almost_equal(r, rm, 10) + + r = stats.moment(y) + rm = stats.mstats.moment(ym) + assert_almost_equal(r, rm, 10) + + def test_signaltonoise(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + + r = stats.signaltonoise(x) + rm = stats.mstats.signaltonoise(xm) + assert_almost_equal(r, rm, 10) + + r = stats.signaltonoise(y) + rm = stats.mstats.signaltonoise(ym) + assert_almost_equal(r, rm, 10) + + def test_betai(self): + np.random.seed(12345) + for i in range(10): + a = np.random.rand() * 5. + b = np.random.rand() * 200. + assert_equal(stats.betai(a, b, 0.), 0.) + assert_equal(stats.betai(a, b, 1.), 1.) + assert_equal(stats.mstats.betai(a, b, 0.), 0.) + assert_equal(stats.mstats.betai(a, b, 1.), 1.) + x = np.random.rand() + assert_almost_equal(stats.betai(a, b, x), + stats.mstats.betai(a, b, x), decimal=13) + + def test_zscore(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + + #reference solution + zx = (x - x.mean()) / x.std() + zy = (y - y.mean()) / y.std() + + #validate stats + assert_allclose(stats.zscore(x), zx, rtol=1e-10) + assert_allclose(stats.zscore(y), zy, rtol=1e-10) + + #compare stats and mstats + assert_allclose(stats.zscore(x), stats.mstats.zscore(xm[0:len(x)]), + rtol=1e-10) + assert_allclose(stats.zscore(y), stats.mstats.zscore(ym[0:len(y)]), + rtol=1e-10) + + def test_kurtosis(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + r = stats.kurtosis(x) + rm = stats.mstats.kurtosis(xm) + assert_almost_equal(r, rm, 10) + + r = stats.kurtosis(y) + rm = stats.mstats.kurtosis(ym) + assert_almost_equal(r, rm, 10) + + def test_sem(self): + # example from stats.sem doc + a = np.arange(20).reshape(5,4) + am = np.ma.array(a) + r = stats.sem(a,ddof=1) + rm = stats.mstats.sem(am, ddof=1) + + assert_allclose(r, 2.82842712, atol=1e-5) + assert_allclose(rm, 2.82842712, atol=1e-5) + + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + assert_almost_equal(stats.mstats.sem(xm, axis=None, ddof=0), + stats.sem(x, axis=None, ddof=0), decimal=13) + assert_almost_equal(stats.mstats.sem(ym, axis=None, ddof=0), + stats.sem(y, axis=None, ddof=0), decimal=13) + assert_almost_equal(stats.mstats.sem(xm, axis=None, ddof=1), + stats.sem(x, axis=None, ddof=1), decimal=13) + assert_almost_equal(stats.mstats.sem(ym, axis=None, ddof=1), + stats.sem(y, axis=None, ddof=1), decimal=13) + + def test_describe(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + r = stats.describe(x, ddof=1) + rm = stats.mstats.describe(xm, ddof=1) + for ii in range(6): + assert_almost_equal(np.asarray(r[ii]), + np.asarray(rm[ii]), + decimal=12) + + def test_rankdata(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + r = stats.rankdata(x) + rm = stats.mstats.rankdata(x) + assert_allclose(r, rm) + + def test_tmean(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + assert_almost_equal(stats.tmean(x),stats.mstats.tmean(xm), 14) + assert_almost_equal(stats.tmean(y),stats.mstats.tmean(ym), 14) + + def test_tmax(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + assert_almost_equal(stats.tmax(x,2.), + stats.mstats.tmax(xm,2.), 10) + assert_almost_equal(stats.tmax(y,2.), + stats.mstats.tmax(ym,2.), 10) + + def test_tmin(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + assert_equal(stats.tmin(x),stats.mstats.tmin(xm)) + assert_equal(stats.tmin(y),stats.mstats.tmin(ym)) + + assert_almost_equal(stats.tmin(x,lowerlimit=-1.), + stats.mstats.tmin(xm,lowerlimit=-1.), 10) + assert_almost_equal(stats.tmin(y,lowerlimit=-1.), + stats.mstats.tmin(ym,lowerlimit=-1.), 10) + + def test_zmap(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + z = stats.zmap(x,y) + zm = stats.mstats.zmap(xm,ym) + assert_allclose(z, zm[0:len(z)], atol=1e-10) + + def test_variation(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + assert_almost_equal(stats.variation(x), stats.mstats.variation(xm), + decimal=12) + assert_almost_equal(stats.variation(y), stats.mstats.variation(ym), + decimal=12) + + def test_tvar(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + assert_almost_equal(stats.tvar(x), stats.mstats.tvar(xm), + decimal=12) + assert_almost_equal(stats.tvar(y), stats.mstats.tvar(ym), + decimal=12) + + def test_trimboth(self): + a = np.arange(20) + b = stats.trimboth(a, 0.1) + bm = stats.mstats.trimboth(a, 0.1) + assert_allclose(b, bm.data[~bm.mask]) + + def test_tsem(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + assert_almost_equal(stats.tsem(x),stats.mstats.tsem(xm), decimal=14) + assert_almost_equal(stats.tsem(y),stats.mstats.tsem(ym), decimal=14) + assert_almost_equal(stats.tsem(x,limits=(-2.,2.)), + stats.mstats.tsem(xm,limits=(-2.,2.)), + decimal=14) + + def test_skewtest(self): + # this test is for 1D data + for n in self.get_n(): + if n > 8: + x, y, xm, ym = self.generate_xy_sample(n) + r = stats.skewtest(x) + rm = stats.mstats.skewtest(xm) + assert_equal(r[0], rm[0]) + # TODO this test is not performed as it is a known issue that + # mstats returns a slightly different p-value what is a bit + # strange is that other tests like test_maskedarray_input don't + # fail! + #~ assert_almost_equal(r[1], rm[1]) + + def test_skewtest_2D_notmasked(self): + # a normal ndarray is passed to the masked function + x = np.random.random((20, 2)) * 20. + r = stats.skewtest(x) + rm = stats.mstats.skewtest(x) + assert_allclose(np.asarray(r), np.asarray(rm)) + + def test_skewtest_2D_WithMask(self): + nx = 2 + for n in self.get_n(): + if n > 8: + x, y, xm, ym = self.generate_xy_sample2D(n, nx) + r = stats.skewtest(x) + rm = stats.mstats.skewtest(xm) + + assert_equal(r[0][0],rm[0][0]) + assert_equal(r[0][1],rm[0][1]) + + def test_normaltest(self): + np.seterr(over='raise') + for n in self.get_n(): + if n > 8: + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', category=UserWarning) + x, y, xm, ym = self.generate_xy_sample(n) + r = stats.normaltest(x) + rm = stats.mstats.normaltest(xm) + assert_allclose(np.asarray(r), np.asarray(rm)) + + def test_find_repeats(self): + x = np.asarray([1,1,2,2,3,3,3,4,4,4,4]).astype('float') + tmp = np.asarray([1,1,2,2,3,3,3,4,4,4,4,5,5,5,5]).astype('float') + mask = (tmp == 5.) + xm = np.ma.array(tmp, mask=mask) + + r = stats.find_repeats(x) + rm = stats.mstats.find_repeats(xm) + + assert_equal(r,rm) + + def test_kendalltau(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + r = stats.kendalltau(x, y) + rm = stats.mstats.kendalltau(xm, ym) + assert_almost_equal(r[0], rm[0], decimal=10) + assert_almost_equal(r[1], rm[1], decimal=7) + + def test_obrientransform(self): + for n in self.get_n(): + x, y, xm, ym = self.generate_xy_sample(n) + r = stats.obrientransform(x) + rm = stats.mstats.obrientransform(xm) + assert_almost_equal(r.T, rm[0:len(x)]) + + if __name__ == "__main__": run_module_suite() diff --git a/pywafo/src/wafo/stats/tests/test_multivariate.py b/pywafo/src/wafo/stats/tests/test_multivariate.py index 5d39d1c..63d2a8c 100644 --- a/pywafo/src/wafo/stats/tests/test_multivariate.py +++ b/pywafo/src/wafo/stats/tests/test_multivariate.py @@ -4,22 +4,34 @@ Test functions for multivariate normal distributions. """ from __future__ import division, print_function, absolute_import -from numpy.testing import (assert_almost_equal, - run_module_suite, assert_allclose, assert_equal, assert_raises) +from numpy.testing import ( + assert_allclose, + assert_almost_equal, + assert_array_almost_equal, + assert_equal, + assert_raises, + run_module_suite, +) import numpy import numpy as np import scipy.linalg -#import wafo.stats._multivariate +from wafo.stats._multivariate import _PSD, _lnB from wafo.stats import multivariate_normal +from wafo.stats import dirichlet, beta from wafo.stats import norm -from wafo.stats._multivariate import _psd_pinv_decomposed_log_pdet - from scipy.integrate import romb +def test_input_shape(): + mu = np.arange(3) + cov = np.identity(2) + assert_raises(ValueError, multivariate_normal.pdf, (0, 1), mu, cov) + assert_raises(ValueError, multivariate_normal.pdf, (0, 1, 2), mu, cov) + + def test_scalar_values(): np.random.seed(1234) @@ -47,6 +59,63 @@ def test_logpdf(): assert_allclose(d1, np.log(d2)) +def test_rank(): + # Check that the rank is detected correctly. + np.random.seed(1234) + n = 4 + mean = np.random.randn(n) + for expected_rank in range(1, n + 1): + s = np.random.randn(n, expected_rank) + cov = np.dot(s, s.T) + distn = multivariate_normal(mean, cov, allow_singular=True) + assert_equal(distn.cov_info.rank, expected_rank) + + +def _sample_orthonormal_matrix(n): + M = np.random.randn(n, n) + u, s, v = scipy.linalg.svd(M) + return u + + +def test_degenerate_distributions(): + for n in range(1, 5): + x = np.random.randn(n) + for k in range(1, n + 1): + # Sample a small covariance matrix. + s = np.random.randn(k, k) + cov_kk = np.dot(s, s.T) + + # Embed the small covariance matrix into a larger low rank matrix. + cov_nn = np.zeros((n, n)) + cov_nn[:k, :k] = cov_kk + + # Define a rotation of the larger low rank matrix. + u = _sample_orthonormal_matrix(n) + cov_rr = np.dot(u, np.dot(cov_nn, u.T)) + y = np.dot(u, x) + + # Check some identities. + distn_kk = multivariate_normal(np.zeros(k), cov_kk, + allow_singular=True) + distn_nn = multivariate_normal(np.zeros(n), cov_nn, + allow_singular=True) + distn_rr = multivariate_normal(np.zeros(n), cov_rr, + allow_singular=True) + assert_equal(distn_kk.cov_info.rank, k) + assert_equal(distn_nn.cov_info.rank, k) + assert_equal(distn_rr.cov_info.rank, k) + pdf_kk = distn_kk.pdf(x[:k]) + pdf_nn = distn_nn.pdf(x) + pdf_rr = distn_rr.pdf(y) + assert_allclose(pdf_kk, pdf_nn) + assert_allclose(pdf_kk, pdf_rr) + logpdf_kk = distn_kk.logpdf(x[:k]) + logpdf_nn = distn_nn.logpdf(x) + logpdf_rr = distn_rr.logpdf(y) + assert_allclose(logpdf_kk, logpdf_nn) + assert_allclose(logpdf_kk, logpdf_rr) + + def test_large_pseudo_determinant(): # Check that large pseudo-determinants are handled appropriately. @@ -67,11 +136,12 @@ def test_large_pseudo_determinant(): # np.linalg.slogdet is only available in numpy 1.6+ # but scipy currently supports numpy 1.5.1. - #assert_allclose(np.linalg.slogdet(cov[:npos, :npos]), (1, large_total_log)) + # assert_allclose(np.linalg.slogdet(cov[:npos, :npos]), + # (1, large_total_log)) # Check the pseudo-determinant. - U, log_pdet = _psd_pinv_decomposed_log_pdet(cov) - assert_allclose(log_pdet, large_total_log) + psd = _PSD(cov) + assert_allclose(psd.log_pdet, large_total_log) def test_broadcasting(): @@ -111,7 +181,7 @@ def test_marginalization(): # yield a 1D Gaussian mean = np.array([2.5, 3.5]) cov = np.array([[.5, 0.2], [0.2, .6]]) - n = 2**8 + 1 # Number of samples + n = 2 ** 8 + 1 # Number of samples delta = 6 / (n - 1) # Grid spacing v = np.linspace(0, 6, n) @@ -126,8 +196,8 @@ def test_marginalization(): margin_y = romb(pdf, delta, axis=1) # Compare with standard normal distribution - gauss_x = norm.pdf(v, loc=mean[0], scale=cov[0, 0]**0.5) - gauss_y = norm.pdf(v, loc=mean[1], scale=cov[1, 1]**0.5) + gauss_x = norm.pdf(v, loc=mean[0], scale=cov[0, 0] ** 0.5) + gauss_y = norm.pdf(v, loc=mean[1], scale=cov[1, 1] ** 0.5) assert_allclose(margin_x, gauss_x, rtol=1e-2, atol=1e-2) assert_allclose(margin_y, gauss_y, rtol=1e-2, atol=1e-2) @@ -160,33 +230,43 @@ def test_pseudodet_pinv(): # Set cond so that the lowest eigenvalue is below the cutoff cond = 1e-5 - U, log_pdet = _psd_pinv_decomposed_log_pdet(cov, cond) - pinv = np.dot(U, U.T) - _, log_pdet_pinv = _psd_pinv_decomposed_log_pdet(pinv, cond) + psd = _PSD(cov, cond=cond) + psd_pinv = _PSD(psd.pinv, cond=cond) # Check that the log pseudo-determinant agrees with the sum # of the logs of all but the smallest eigenvalue - assert_allclose(log_pdet, np.sum(np.log(s[:-1]))) + assert_allclose(psd.log_pdet, np.sum(np.log(s[:-1]))) # Check that the pseudo-determinant of the pseudo-inverse # agrees with 1 / pseudo-determinant - assert_allclose(-log_pdet, log_pdet_pinv) + assert_allclose(-psd.log_pdet, psd_pinv.log_pdet) def test_exception_nonsquare_cov(): cov = [[1, 2, 3], [4, 5, 6]] - assert_raises(ValueError, _psd_pinv_decomposed_log_pdet, cov) + assert_raises(ValueError, _PSD, cov) def test_exception_nonfinite_cov(): cov_nan = [[1, 0], [0, np.nan]] - assert_raises(ValueError, _psd_pinv_decomposed_log_pdet, cov_nan) + assert_raises(ValueError, _PSD, cov_nan) cov_inf = [[1, 0], [0, np.inf]] - assert_raises(ValueError, _psd_pinv_decomposed_log_pdet, cov_inf) + assert_raises(ValueError, _PSD, cov_inf) def test_exception_non_psd_cov(): cov = [[1, 0], [0, -1]] - assert_raises(ValueError, _psd_pinv_decomposed_log_pdet, cov) + assert_raises(ValueError, _PSD, cov) + + +def test_exception_singular_cov(): + np.random.seed(1234) + x = np.random.randn(5) + mean = np.random.randn(5) + cov = np.ones((5, 5)) + e = np.linalg.LinAlgError + assert_raises(e, multivariate_normal, mean, cov) + assert_raises(e, multivariate_normal.pdf, x, mean, cov) + assert_raises(e, multivariate_normal.logpdf, x, mean, cov) def test_R_values(): @@ -216,6 +296,14 @@ def test_R_values(): assert_allclose(pdf, r_pdf, atol=1e-10) +def test_multivariate_normal_rvs_zero_covariance(): + mean = np.zeros(2) + covariance = np.zeros((2, 2)) + model = multivariate_normal(mean, covariance, allow_singular=True) + sample = model.rvs() + assert_equal(sample, [0, 0]) + + def test_rvs_shape(): # Check that rvs parses the mean and covariance correctly, and returns # an array of the right shape @@ -267,9 +355,131 @@ def test_entropy(): # Compare entropy with manually computed expression involving # the sum of the logs of the eigenvalues of the covariance matrix eigs = np.linalg.eig(cov)[0] - desired = 1/2 * (n * (np.log(2*np.pi) + 1) + np.sum(np.log(eigs))) + desired = 1 / 2 * (n * (np.log(2 * np.pi) + 1) + np.sum(np.log(eigs))) assert_almost_equal(desired, rv.entropy()) +def test_lnB(): + alpha = np.array([1, 1, 1]) + desired = .5 # e^lnB = 1/2 for [1, 1, 1] + + assert_almost_equal(np.exp(_lnB(alpha)), desired) + + +def test_frozen_dirichlet(): + np.random.seed(2846) + + n = np.random.randint(1, 32) + alpha = np.random.uniform(10e-10, 100, n) + + d = dirichlet(alpha) + + assert_equal(d.var(), dirichlet.var(alpha)) + assert_equal(d.mean(), dirichlet.mean(alpha)) + assert_equal(d.entropy(), dirichlet.entropy(alpha)) + num_tests = 10 + for i in range(num_tests): + x = np.random.uniform(10e-10, 100, n) + x /= np.sum(x) + assert_equal(d.pdf(x[:-1]), dirichlet.pdf(x[:-1], alpha)) + assert_equal(d.logpdf(x[:-1]), dirichlet.logpdf(x[:-1], alpha)) + + +def test_simple_values(): + alpha = np.array([1, 1]) + d = dirichlet(alpha) + + assert_almost_equal(d.mean(), 0.5) + assert_almost_equal(d.var(), 1. / 12.) + + b = beta(1, 1) + assert_almost_equal(d.mean(), b.mean()) + assert_almost_equal(d.var(), b.var()) + + +def test_K_and_K_minus_1_calls_equal(): + # Test that calls with K and K-1 entries yield the same results. + + np.random.seed(2846) + + n = np.random.randint(1, 32) + alpha = np.random.uniform(10e-10, 100, n) + + d = dirichlet(alpha) + num_tests = 10 + for i in range(num_tests): + x = np.random.uniform(10e-10, 100, n) + x /= np.sum(x) + assert_almost_equal(d.pdf(x[:-1]), d.pdf(x)) + + +def test_multiple_entry_calls(): + # Test that calls with multiple x vectors as matrix work + + np.random.seed(2846) + + n = np.random.randint(1, 32) + alpha = np.random.uniform(10e-10, 100, n) + d = dirichlet(alpha) + + num_tests = 10 + num_multiple = 5 + xm = None + for i in range(num_tests): + for m in range(num_multiple): + x = np.random.uniform(10e-10, 100, n) + x /= np.sum(x) + if xm is not None: + xm = np.vstack((xm, x)) + else: + xm = x + rm = d.pdf(xm.T) + rs = None + for xs in xm: + r = d.pdf(xs) + if rs is not None: + rs = np.append(rs, r) + else: + rs = r + assert_array_almost_equal(rm, rs) + + +def test_2D_dirichlet_is_beta(): + np.random.seed(2846) + + alpha = np.random.uniform(10e-10, 100, 2) + d = dirichlet(alpha) + b = beta(alpha[0], alpha[1]) + + num_tests = 10 + for i in range(num_tests): + x = np.random.uniform(10e-10, 100, 2) + x /= np.sum(x) + assert_almost_equal(b.pdf(x), d.pdf([x])) + + assert_almost_equal(b.mean(), d.mean()[0]) + assert_almost_equal(b.var(), d.var()[0]) + + +def test_dimensions_mismatch(): + # Regression test for GH #3493. Check that setting up a PDF with a mean of + # length M and a covariance matrix of size (N, N), where M != N, raises a + # ValueError with an informative error message. + + mu = np.array([0.0, 0.0]) + sigma = np.array([[1.0]]) + + assert_raises(ValueError, multivariate_normal, mu, sigma) + + # A simple check that the right error message was passed along. Checking + # that the entire message is there, word for word, would be somewhat + # fragile, so we just check for the leading part. + try: + multivariate_normal(mu, sigma) + except ValueError as e: + msg = "Dimension mismatch" + assert_equal(str(e)[:len(msg)], msg) + + if __name__ == "__main__": run_module_suite() diff --git a/pywafo/src/wafo/stats/tests/test_stats.py b/pywafo/src/wafo/stats/tests/test_stats.py index 5ecfe91..8f87fbb 100644 --- a/pywafo/src/wafo/stats/tests/test_stats.py +++ b/pywafo/src/wafo/stats/tests/test_stats.py @@ -8,13 +8,15 @@ """ from __future__ import division, print_function, absolute_import +import sys import warnings from collections import namedtuple -from numpy.testing import TestCase, assert_, assert_equal, \ - assert_almost_equal, assert_array_almost_equal, assert_array_equal, \ - assert_approx_equal, assert_raises, run_module_suite, \ - assert_allclose, dec +from numpy.testing import (TestCase, assert_, assert_equal, + assert_almost_equal, assert_array_almost_equal, + assert_array_equal, assert_approx_equal, + assert_raises, run_module_suite, assert_allclose, + dec) import numpy.ma.testutils as mat from numpy import array, arange, float32, float64, power import numpy as np @@ -170,6 +172,14 @@ class TestNanFunc(TestCase): m = stats.nanmedian(self.X) assert_approx_equal(m, np.median(self.X)) + def test_nanmedian_axis(self): + # Check nanmedian with axis + X = self.X.reshape(3,3) + m = stats.nanmedian(X, axis=0) + assert_equal(m, np.median(X, axis=0)) + m = stats.nanmedian(X, axis=1) + assert_equal(m, np.median(X, axis=1)) + def test_nanmedian_some(self): # Check nanmedian when some values only are nan. m = stats.nanmedian(self.Xsome) @@ -177,8 +187,21 @@ class TestNanFunc(TestCase): def test_nanmedian_all(self): # Check nanmedian when all values are nan. - m = stats.nanmedian(self.Xall) - assert_(np.isnan(m)) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + m = stats.nanmedian(self.Xall) + assert_(np.isnan(m)) + assert_equal(len(w), 1) + assert_(issubclass(w[0].category, RuntimeWarning)) + + def test_nanmedian_all_axis(self): + # Check nanmedian when all values are nan. + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + m = stats.nanmedian(self.Xall.reshape(3,3), axis=1) + assert_(np.isnan(m).all()) + assert_equal(len(w), 3) + assert_(issubclass(w[0].category, RuntimeWarning)) def test_nanmedian_scalars(self): # Check nanmedian for scalar inputs. See ticket #1098. @@ -449,6 +472,11 @@ class TestFisherExact(TestCase): res.append(stats.fisher_exact(table, alternative="greater")[1]) assert_allclose(res, pval, atol=0, rtol=1e-7) + def test_gh3014(self): + # check if issue #3014 has been fixed. + # before, this would have risen a ValueError + odds, pvalue = stats.fisher_exact([[1, 2], [9, 84419233]]) + class TestCorrSpearmanr(TestCase): """ W.II.D. Compute a correlation matrix on all the variables. @@ -601,9 +629,12 @@ def test_kendalltau(): assert_approx_equal(res[1], expected[1]) # with only ties in one or both inputs - assert_(np.all(np.isnan(stats.kendalltau([2,2,2], [2,2,2])))) - assert_(np.all(np.isnan(stats.kendalltau([2,0,2], [2,2,2])))) - assert_(np.all(np.isnan(stats.kendalltau([2,2,2], [2,0,2])))) + assert_equal(stats.kendalltau([2,2,2], [2,2,2]), (np.nan, np.nan)) + assert_equal(stats.kendalltau([2,0,2], [2,2,2]), (np.nan, np.nan)) + assert_equal(stats.kendalltau([2,2,2], [2,0,2]), (np.nan, np.nan)) + + # empty arrays provided as input + assert_equal(stats.kendalltau([], []), (np.nan, np.nan)) # check two different sort methods assert_approx_equal(stats.kendalltau(x1, x2, initial_lexsort=False)[1], @@ -718,6 +749,21 @@ class TestRegression(TestCase): assert_(not np.isnan(res[4])) # stderr should stay finite +def test_theilslopes(): + # Basic slope test. + slope, intercept, lower, upper = stats.theilslopes([0,1,1]) + assert_almost_equal(slope, 0.5) + assert_almost_equal(intercept, 0.5) + + # Test of confidence intervals. + x = [1, 2, 3, 4, 10, 12, 18] + y = [9, 15, 19, 20, 45, 55, 78] + slope, intercept, lower, upper = stats.theilslopes(y, x, 0.07) + assert_almost_equal(slope, 4) + assert_almost_equal(upper, 4.38, decimal=2) + assert_almost_equal(lower, 3.71, decimal=2) + + class TestHistogram(TestCase): # Tests that histogram works as it should, and keeps old behaviour # @@ -1032,9 +1078,23 @@ class TestScoreatpercentile(TestCase): assert_equal(scoreatperc(np.array([1, 10, 100]), 50, limit=(1, 10), interpolation_method='higher'), 10) - def test_sequence(self): + def test_sequence_per(self): x = arange(8) * 0.5 - assert_equal(stats.scoreatpercentile(x, [0, 100, 50]), [0, 3.5, 1.75]) + expected = np.array([0, 3.5, 1.75]) + res = stats.scoreatpercentile(x, [0, 100, 50]) + assert_allclose(res, expected) + assert_(isinstance(res, np.ndarray)) + # Test with ndarray. Regression test for gh-2861 + assert_allclose(stats.scoreatpercentile(x, np.array([0, 100, 50])), + expected) + # Also test combination of 2-D array, axis not None and array-like per + res2 = stats.scoreatpercentile(np.arange(12).reshape((3,4)), + np.array([0, 1, 100, 100]), axis=1) + expected2 = array([[0, 4, 8], + [0.03, 4.03, 8.03], + [3, 7, 11], + [3, 7, 11]]) + assert_allclose(res2, expected2) def test_axis(self): scoreatperc = stats.scoreatpercentile @@ -1054,6 +1114,11 @@ class TestScoreatpercentile(TestCase): assert_raises(ValueError, stats.scoreatpercentile, [1], 101) assert_raises(ValueError, stats.scoreatpercentile, [1], -1) + def test_empty(self): + assert_equal(stats.scoreatpercentile([], 50), np.nan) + assert_equal(stats.scoreatpercentile(np.array([[], []]), 50), np.nan) + assert_equal(stats.scoreatpercentile([], [50, 99]), [np.nan, np.nan]) + class TestItemfreq(object): a = [5, 7, 1, 2, 1, 5, 7] * 10 @@ -1089,7 +1154,7 @@ class TestItemfreq(object): bb = np.array(list(zip(b, b)), dt) v = stats.itemfreq(aa) # Arrays don't compare equal because v[:,0] is object array - assert_equal(v[2, 0], bb[2]) + assert_equal(tuple(v[2, 0]), tuple(bb[2])) class TestMode(TestCase): @@ -1099,6 +1164,71 @@ class TestMode(TestCase): assert_almost_equal(vals[0][0],6) assert_almost_equal(vals[1][0],3) + def test_axes(self): + data1 = [10,10,30,40] + data2 = [10,10,10,10] + data3 = [20,10,20,20] + data4 = [30,30,30,30] + data5 = [40,30,30,30] + arr = np.array([data1, data2, data3, data4, data5]) + + vals = stats.mode(arr, axis=None) + assert_almost_equal(vals[0],np.array([30])) + assert_almost_equal(vals[1],np.array([8])) + + vals = stats.mode(arr, axis=0) + assert_almost_equal(vals[0],np.array([[10,10,30,30]])) + assert_almost_equal(vals[1],np.array([[2,3,3,2]])) + + vals = stats.mode(arr, axis=1) + assert_almost_equal(vals[0],np.array([[10],[10],[20],[30],[30]])) + assert_almost_equal(vals[1],np.array([[2],[4],[3],[4],[3]])) + + def test_strings(self): + data1 = ['rain', 'showers', 'showers'] + vals = stats.mode(data1) + expected = ['showers'] + assert_equal(vals[0][0], 'showers') + assert_equal(vals[1][0], 2) + + @dec.knownfailureif(sys.version_info > (3,), 'numpy github issue 641') + def test_mixed_objects(self): + objects = [10, True, np.nan, 'hello', 10] + arr = np.empty((5,), dtype=object) + arr[:] = objects + vals = stats.mode(arr) + assert_equal(vals[0][0], 10) + assert_equal(vals[1][0], 2) + + def test_objects(self): + """Python objects must be sortable (le + eq) and have ne defined + for np.unique to work. hash is for set. + """ + class Point(object): + def __init__(self, x): + self.x = x + + def __eq__(self, other): + return self.x == other.x + + def __ne__(self, other): + return self.x != other.x + + def __lt__(self, other): + return self.x < other.x + + def __hash__(self): + return hash(self.x) + + points = [Point(x) for x in [1,2,3,4,3,2,2,2]] + arr = np.empty((8,), dtype=object) + arr[:] = points + assert len(set(points)) == 4 + assert_equal(np.unique(arr).shape, (4,)) + vals = stats.mode(arr) + assert_equal(vals[0][0], Point(2)) + assert_equal(vals[1][0], 4) + class TestVariability(TestCase): @@ -1120,7 +1250,7 @@ class TestVariability(TestCase): # y = stats.sem(self.shoes[0]) # assert_approx_equal(y,0.775177399) y = stats.sem(self.testcase) - assert_approx_equal(y,0.6454972244) + assert_approx_equal(y, 0.6454972244) n = len(self.testcase) assert_allclose(stats.sem(self.testcase, ddof=0) * np.sqrt(n/(n-2)), stats.sem(self.testcase, ddof=2)) @@ -1660,7 +1790,8 @@ def test_chisquare_masked_arrays(): # Empty arrays: # A data set with length 0 returns a masked scalar. - chisq, p = stats.chisquare(np.ma.array([])) + with np.errstate(invalid='ignore'): + chisq, p = stats.chisquare(np.ma.array([])) assert_(isinstance(chisq, np.ma.MaskedArray)) assert_equal(chisq.shape, ()) assert_(chisq.mask) @@ -1675,7 +1806,8 @@ def test_chisquare_masked_arrays(): # empty3.T is an array containing 3 data sets, each with length 0, # so an array of size (3,) is returned, with all values masked. - chisq, p = stats.chisquare(empty3.T) + with np.errstate(invalid='ignore'): + chisq, p = stats.chisquare(empty3.T) assert_(isinstance(chisq, np.ma.MaskedArray)) assert_equal(chisq.shape, (3,)) assert_(np.all(chisq.mask)) @@ -1694,39 +1826,39 @@ def test_power_divergence_against_cressie_read_data(): 11, 13.952, 14, 12.831, 17, 11.800, - 5, 10.852, + 5, 10.852, 11, 9.9796, 10, 9.1777, - 4, 8.4402, - 8, 7.7620, + 4, 8.4402, + 8, 7.7620, 10, 7.1383, - 7, 6.5647, - 9, 6.0371, + 7, 6.5647, + 9, 6.0371, 11, 5.5520, - 3, 5.1059, - 6, 4.6956, - 1, 4.3183, - 1, 3.9713, - 4, 3.6522, + 3, 5.1059, + 6, 4.6956, + 1, 4.3183, + 1, 3.9713, + 4, 3.6522, ]).reshape(-1, 2) table5 = np.array([ # lambda, statistic -10.0, 72.2e3, - -5.0, 28.9e1, - -3.0, 65.6, - -2.0, 40.6, - -1.5, 34.0, - -1.0, 29.5, - -0.5, 26.5, - 0.0, 24.6, - 0.5, 23.4, - 0.67, 23.1, - 1.0, 22.7, - 1.5, 22.6, - 2.0, 22.9, - 3.0, 24.8, - 5.0, 35.5, - 10.0, 21.4e1, + -5.0, 28.9e1, + -3.0, 65.6, + -2.0, 40.6, + -1.5, 34.0, + -1.0, 29.5, + -0.5, 26.5, + 0.0, 24.6, + 0.5, 23.4, + 0.67, 23.1, + 1.0, 22.7, + 1.5, 22.6, + 2.0, 22.9, + 3.0, 24.8, + 5.0, 35.5, + 10.0, 21.4e1, ]).reshape(-1, 2) for lambda_, expected_stat in table5: @@ -2622,22 +2754,25 @@ class TestSigamClip(object): class TestFOneWay(TestCase): - def test_trivial(self): # A trivial test of stats.f_oneway, with F=0. F, p = stats.f_oneway([0,2], [0,2]) assert_equal(F, 0.0) def test_basic(self): - # A test of stats.f_oneway, with F=2. - F, p = stats.f_oneway([0,2], [2,4]) # Despite being a floating point calculation, this data should # result in F being exactly 2.0. + F, p = stats.f_oneway([0,2], [2,4]) assert_equal(F, 2.0) + def test_large_integer_array(self): + a = np.array([655, 788], dtype=np.uint16) + b = np.array([789, 772], dtype=np.uint16) + F, p = stats.f_oneway(a, b) + assert_almost_equal(F, 0.77450216931805538) -class TestKruskal(TestCase): +class TestKruskal(TestCase): def test_simple(self): x = [1] y = [2] diff --git a/pywafo/src/wafo/stats/tests/test_tukeylambda_stats.py b/pywafo/src/wafo/stats/tests/test_tukeylambda_stats.py index b1e74a1..9d3d654 100644 --- a/pywafo/src/wafo/stats/tests/test_tukeylambda_stats.py +++ b/pywafo/src/wafo/stats/tests/test_tukeylambda_stats.py @@ -18,7 +18,7 @@ def test_tukeylambda_stats_known_exact(): # lambda = 0 var = tukeylambda_variance(0) - assert_allclose(var, np.pi ** 2 / 3, atol=1e-12) + assert_allclose(var, np.pi**2 / 3, atol=1e-12) kurt = tukeylambda_kurtosis(0) assert_allclose(kurt, 1.2, atol=1e-10) @@ -26,7 +26,7 @@ def test_tukeylambda_stats_known_exact(): var = tukeylambda_variance(0.5) assert_allclose(var, 4 - np.pi, atol=1e-12) kurt = tukeylambda_kurtosis(0.5) - desired = (5. / 3 - np.pi / 2) / (np.pi / 4 - 1) ** 2 - 3 + desired = (5./3 - np.pi/2) / (np.pi/4 - 1)**2 - 3 assert_allclose(kurt, desired, atol=1e-10) # lambda = 1 diff --git a/pywafo/src/wafo/test/test_misc.py b/pywafo/src/wafo/test/test_misc.py index 9052dc6..1eb5638 100644 --- a/pywafo/src/wafo/test/test_misc.py +++ b/pywafo/src/wafo/test/test_misc.py @@ -297,9 +297,11 @@ def test_hygfz(): assert_almost_equal(1.0464328112173522, hygfz(0.1, 0.2, 0.3, 0.5)) assert_almost_equal(1.2027034401166194, hygfz(0.1, 0.2, 0.3, 0.95)) #assert_equal(1.661006238211309e-07, hygfz(5, -300, 10, 0.5)) - assert_equal(0.118311386286, hygfz(0.5, -99.0, 1.5, 0.5625)) - assert_equal(0.0965606007742, hygfz(0.5, -149.0, 1.5, 0.5625)) - assert_equal(0.49234384000963544+0.60513406166123973j, hygfz(1, 1, 4, 3+4j)) + #assert_equal(0.118311386286, hygfz(0.5, -99.0, 1.5, 0.5625)) + #assert_equal(0.0965606007742, hygfz(0.5, -149.0, 1.5, 0.5625)) + #assert_equal(0.49234384000963544 + 0.60513406166123973j, + # hygfz(1, 1, 4, 3 + 4j)) + def test_common_shape(): A = np.ones((4, 1)) diff --git a/pywafo/src/wafo/transform/__init__.py b/pywafo/src/wafo/transform/__init__.py index c89bb56..7108026 100644 --- a/pywafo/src/wafo/transform/__init__.py +++ b/pywafo/src/wafo/transform/__init__.py @@ -2,5 +2,6 @@ Transform package in WAFO Toolbox. """ -from core import * -import models +from .core import * +from . import models +from . import estimation diff --git a/pywafo/src/wafo/transform/core.py b/pywafo/src/wafo/transform/core.py index 6ad8b71..d260431 100644 --- a/pywafo/src/wafo/transform/core.py +++ b/pywafo/src/wafo/transform/core.py @@ -1,209 +1,221 @@ -''' -''' -from __future__ import division -#import numpy as np -from numpy import trapz, sqrt, linspace #@UnresolvedImport - -from wafo.wafodata import PlotData -from wafo.misc import tranproc #, trangood - -__all__ = ['TrData', 'TrCommon'] - -class TrCommon(object): - """ - transformation model, g. - - Information about the moments of the process can be obtained by site - specific data, laboratory measurements or by resort to theoretical models. - - Assumption - ---------- - The Gaussian process, Y, distributed N(0,1) is related to the - non-Gaussian process, X, by Y = g(X). - - Methods - ------- - dist2gauss : Returns a measure of departure from the Gaussian model, i.e., - int (g(x)-xn)**2 dx where int. limits are given by X. - dat2gauss : Transform non-linear data to Gaussian scale - gauss2dat : Transform Gaussian data to non-linear scale - - Member variables - ---------------- - mean, sigma, skew, kurt : real, scalar - mean, standard-deviation, skewness and kurtosis, respectively, of the - non-Gaussian process. Default mean=0, sigma=1, skew=0.16, kurt=3.04. - skew=kurt-3=0 for a Gaussian process. - """ - - def __init__(self, mean=0.0, var=1.0, skew=0.16, kurt=3.04, *args, **kwds): - sigma = kwds.get('sigma',None) - if sigma is None: - sigma = sqrt(var) - self.mean = mean - self.sigma = sigma - self.skew = skew - self.kurt = kurt - # Mean and std in the Gaussian world: - self.ymean = kwds.get('ymean', 0e0) - self.ysigma = kwds.get('ysigma', 1e0) - - def __call__(self, x, *xi): - return self._dat2gauss(x, *xi) - - def dist2gauss(self, x=None, xnmin=-5, xnmax=5, n=513): - """ - Return a measure of departure from the Gaussian model. - - Parameters - ---------- - x : vector (default sigma*linspace(xnmin,xnmax,n)+mean) - xnmin : real, scalar - minimum on normalized scale - xnmax : real, scalar - maximum on normalized scale - n : integer, scalar - number of evaluation points - - - Returns - ------- - t0 : real, scalar - a measure of departure from the Gaussian model calculated as - trapz((xn-g(x))**2., xn) where int. limits is given by X. - """ - if x is None: - xn = linspace(xnmin, xnmax, n) - x = self.sigma*xn+self.mean - else: - xn = (x-self.mean)/self.sigma - - yn = (self._dat2gauss(x)-self.ymean)/self.ysigma - t0 = trapz((xn-yn)**2., xn) - return t0 - - def gauss2dat(self, y, *yi): - """ - Transforms Gaussian data, y, to non-linear scale. - - Parameters - ---------- - y, y1,..., yn : array-like - input vectors with Gaussian data values, where yi is the i'th time - derivative of y. (n<=4) - Returns - ------- - x, x1,...,xn : array-like - transformed data to a non-linear scale - - See also - -------- - dat2gauss - tranproc - """ - return self._gauss2dat(y, *yi) - def _gauss2dat(self, y, *yi): - pass - def dat2gauss(self, x, *xi): - """ - Transforms non-linear data, x, to Gaussian scale. - - Parameters - ---------- - x, x1,...,xn : array-like - input vectors with non-linear data values, where xi is the i'th time - derivative of x. (n<=4) - Returns - ------- - y, y1,...,yn : array-like - transformed data to a Gaussian scale - - See also - -------- - gauss2dat - tranproc. - """ - return self._dat2gauss(x, *xi) - def _dat2gauss(self, x, *xi): - pass - -class TrData(PlotData, TrCommon): - __doc__ = TrCommon.__doc__.split('mean')[0].replace('','Data' #@ReservedAssignment - ) + """ - data : array-like - Gaussian values, Y - args : array-like - non-Gaussian values, X - ymean, ysigma : real, scalars (default ymean=0, ysigma=1) - mean and standard-deviation, respectively, of the process in Gaussian world. - mean, sigma : real, scalars - mean and standard-deviation, respectively, of the non-Gaussian process. - Default: - mean = self.gauss2dat(ymean), - sigma = (self.gauss2dat(ysigma)-self.gauss2dat(-ysigma))/2 - - Example - ------- - Construct a linear transformation model - >>> import numpy as np - >>> import wafo.transform as wt - >>> sigma = 5; mean = 1 - >>> u = np.linspace(-5,5); x = sigma*u+mean; y = u - >>> g = wt.TrData(y,x) - >>> g.mean - array([ 1.]) - >>> g.sigma - array([ 5.]) - - >>> g = wt.TrData(y,x,mean=1,sigma=5) - >>> g.mean - 1 - >>> g.sigma - 5 - >>> g.dat2gauss(1,2,3) - [array([ 0.]), array([ 0.4]), array([ 0.6])] - - Check that the departure from a Gaussian model is zero - >>> g.dist2gauss() < 1e-16 - True - """ - def __init__(self, *args, **kwds): - options = dict(title='Transform', - xlab='x', ylab='g(x)', - plot_args=['r'], - plot_args_children=['g--'],) - options.update(**kwds) - super(TrData, self).__init__(*args, **options) - self.ymean = kwds.get('ymean', 0e0) - self.ysigma = kwds.get('ysigma', 1e0) - self.mean = kwds.get('mean', None) - self.sigma = kwds.get('sigma', None) - - if self.mean is None: - #self.mean = np.mean(self.args) # - self.mean = self.gauss2dat(self.ymean) - if self.sigma is None: - yp = self.ymean+self.ysigma - ym = self.ymean-self.ysigma - self.sigma = (self.gauss2dat(yp)-self.gauss2dat(ym))/2. - - self.children = [PlotData((self.args-self.mean)/self.sigma, self.args)] - - def trdata(self): - return self - - def _gauss2dat(self, y, *yi): - return tranproc(self.data, self.args, y, *yi) - - def _dat2gauss(self, x, *xi): - return tranproc(self.args, self.data, x, *xi) - -def main(): - pass - -if __name__ == '__main__': - if True: #False : # - import doctest - doctest.testmod() - else: - main() \ No newline at end of file +''' +''' +from __future__ import division +#import numpy as np +from numpy import trapz, sqrt, linspace # @UnresolvedImport + +from wafo.containers import PlotData +from wafo.misc import tranproc # , trangood + +__all__ = ['TrData', 'TrCommon'] + + +class TrCommon(object): + + """ + transformation model, g. + + Information about the moments of the process can be obtained by site + specific data, laboratory measurements or by resort to theoretical models. + + Assumption + ---------- + The Gaussian process, Y, distributed N(0,1) is related to the + non-Gaussian process, X, by Y = g(X). + + Methods + ------- + dist2gauss : Returns a measure of departure from the Gaussian model, i.e., + int (g(x)-xn)**2 dx where int. limits are given by X. + dat2gauss : Transform non-linear data to Gaussian scale + gauss2dat : Transform Gaussian data to non-linear scale + + Member variables + ---------------- + mean, sigma, skew, kurt : real, scalar + mean, standard-deviation, skewness and kurtosis, respectively, of the + non-Gaussian process. Default mean=0, sigma=1, skew=0.16, kurt=3.04. + skew=kurt-3=0 for a Gaussian process. + """ + + def __init__(self, mean=0.0, var=1.0, skew=0.16, kurt=3.04, *args, **kwds): + sigma = kwds.get('sigma', None) + if sigma is None: + sigma = sqrt(var) + self.mean = mean + self.sigma = sigma + self.skew = skew + self.kurt = kurt + # Mean and std in the Gaussian world: + self.ymean = kwds.get('ymean', 0e0) + self.ysigma = kwds.get('ysigma', 1e0) + + def __call__(self, x, *xi): + return self._dat2gauss(x, *xi) + + def dist2gauss(self, x=None, xnmin=-5, xnmax=5, n=513): + """ + Return a measure of departure from the Gaussian model. + + Parameters + ---------- + x : vector (default sigma*linspace(xnmin,xnmax,n)+mean) + xnmin : real, scalar + minimum on normalized scale + xnmax : real, scalar + maximum on normalized scale + n : integer, scalar + number of evaluation points + + + Returns + ------- + t0 : real, scalar + a measure of departure from the Gaussian model calculated as + trapz((xn-g(x))**2., xn) where int. limits is given by X. + """ + if x is None: + xn = linspace(xnmin, xnmax, n) + x = self.sigma * xn + self.mean + else: + xn = (x - self.mean) / self.sigma + + yn = (self._dat2gauss(x) - self.ymean) / self.ysigma + t0 = trapz((xn - yn) ** 2., xn) + return t0 + + def gauss2dat(self, y, *yi): + """ + Transforms Gaussian data, y, to non-linear scale. + + Parameters + ---------- + y, y1,..., yn : array-like + input vectors with Gaussian data values, where yi is the i'th time + derivative of y. (n<=4) + Returns + ------- + x, x1,...,xn : array-like + transformed data to a non-linear scale + + See also + -------- + dat2gauss + tranproc + """ + return self._gauss2dat(y, *yi) + + def _gauss2dat(self, y, *yi): + pass + + def dat2gauss(self, x, *xi): + """ + Transforms non-linear data, x, to Gaussian scale. + + Parameters + ---------- + x, x1,...,xn : array-like + input vectors with non-linear data values, where xi is the i'th + time derivative of x. (n<=4) + Returns + ------- + y, y1,...,yn : array-like + transformed data to a Gaussian scale + + See also + -------- + gauss2dat + tranproc. + """ + return self._dat2gauss(x, *xi) + + def _dat2gauss(self, x, *xi): + pass + + +class TrData(PlotData, TrCommon): + __doc__ = TrCommon.__doc__.split('mean')[0].replace('', + 'Data') + """ + data : array-like + Gaussian values, Y + args : array-like + non-Gaussian values, X + ymean, ysigma : real, scalars (default ymean=0, ysigma=1) + mean and standard-deviation, respectively, of the process in Gaussian + world. + mean, sigma : real, scalars + mean and standard-deviation, respectively, of the non-Gaussian process. + Default: + mean = self.gauss2dat(ymean), + sigma = (self.gauss2dat(ysigma)-self.gauss2dat(-ysigma))/2 + + Example + ------- + Construct a linear transformation model + >>> import numpy as np + >>> import wafo.transform as wt + >>> sigma = 5; mean = 1 + >>> u = np.linspace(-5,5); x = sigma*u+mean; y = u + >>> g = wt.TrData(y,x) + >>> g.mean + array([ 1.]) + >>> g.sigma + array([ 5.]) + + >>> g = wt.TrData(y,x,mean=1,sigma=5) + >>> g.mean + 1 + >>> g.sigma + 5 + >>> g.dat2gauss(1,2,3) + [array([ 0.]), array([ 0.4]), array([ 0.6])] + + Check that the departure from a Gaussian model is zero + >>> g.dist2gauss() < 1e-16 + True + """ + + def __init__(self, *args, **kwds): + options = dict(title='Transform', + xlab='x', ylab='g(x)', + plot_args=['r'], + plot_args_children=['g--'],) + options.update(**kwds) + super(TrData, self).__init__(*args, **options) + self.ymean = kwds.get('ymean', 0e0) + self.ysigma = kwds.get('ysigma', 1e0) + self.mean = kwds.get('mean', None) + self.sigma = kwds.get('sigma', None) + + if self.mean is None: + #self.mean = np.mean(self.args) # + self.mean = self.gauss2dat(self.ymean) + if self.sigma is None: + yp = self.ymean + self.ysigma + ym = self.ymean - self.ysigma + self.sigma = (self.gauss2dat(yp) - self.gauss2dat(ym)) / 2. + + self.children = [ + PlotData((self.args - self.mean) / self.sigma, self.args)] + + def trdata(self): + return self + + def _gauss2dat(self, y, *yi): + return tranproc(self.data, self.args, y, *yi) + + def _dat2gauss(self, x, *xi): + return tranproc(self.args, self.data, x, *xi) + +class EstimateTransform(object): + pass + +def main(): + pass + +if __name__ == '__main__': + if True: # False : # + import doctest + doctest.testmod() + else: + main() diff --git a/pywafo/src/wafo/transform/models.py b/pywafo/src/wafo/transform/models.py index 1f79c25..44b5809 100644 --- a/pywafo/src/wafo/transform/models.py +++ b/pywafo/src/wafo/transform/models.py @@ -1,559 +1,555 @@ -''' -Transform Gaussian models -------------------------- -TrHermite -TrOchi -TrLinear -''' -#------------------------------------------------------------------------------- -# Name: transform.models -# Purpose: -# -# Author: pab -# -# Created: 24.11.2008 -# Copyright: (c) pab 2008 -# Licence: -#------------------------------------------------------------------------------- -#!/usr/bin/env python -from __future__ import division -from scipy.optimize import brentq -from numpy import (sqrt, atleast_1d, abs, imag, sign, where, cos, arccos, ceil, #@UnresolvedImport - expm1, log1p, pi) #@UnresolvedImport -import numpy as np -import warnings -from core import TrCommon, TrData -__all__ = ['TrHermite', 'TrLinear', 'TrOchi'] - -_example = ''' - >>> import numpy as np - >>> import wafo.spectrum.models as sm - >>> import wafo.transform.models as tm - >>> std = 7./4 - >>> g = tm.(sigma=std, ysigma=std) - - Simulate a Transformed Gaussian process: - >>> Sj = sm.Jonswap(Hm0=4*std, Tp=11) - >>> w = np.linspace(0,4,256) - >>> S = Sj.tospecdata(w) # Make spectrum object from numerical values - >>> ys = S.sim(ns=15000) # Simulated in the Gaussian world - - >>> me, va, sk, ku = S.stats_nl(moments='mvsk') - >>> g2 = tm.(mean=me, var=va, skew=sk, kurt=ku, ysigma=std) - >>> xs = g2.gauss2dat(ys[:,1:]) # Transformed to the real world - ''' -class TrCommon2(TrCommon): - __doc__ = TrCommon.__doc__ #@ReservedAssignment - def trdata(self, x=None, xnmin= -5, xnmax=5, n=513): - """ - Return a discretized transformation model. - - Parameters - ---------- - x : vector (default sigma*linspace(xnmin,xnmax,n)+mean) - xnmin : real, scalar - minimum on normalized scale - xnmax : real, scalar - maximum on normalized scale - n : integer, scalar - number of evaluation points - - Returns - ------- - t0 : real, scalar - a measure of departure from the Gaussian model calculated as - trapz((xn-g(x))**2., xn) where int. limits is given by X. - """ - if x is None: - xn = np.linspace(xnmin, xnmax, n) - x = self.sigma * xn + self.mean - else: - xn = (x - self.mean) / self.sigma - - yn = (self._dat2gauss(x) - self.ymean) / self.ysigma - - return TrData(yn, x, mean=self.mean, sigma=self.sigma) - -class TrHermite(TrCommon2): - __doc__ = TrCommon2.__doc__.replace('', 'Hermite' #@ReservedAssignment - ) + """ - pardef : scalar, integer - 1 Winterstein et. al. (1994) parametrization [1]_ (default) - 2 Winterstein (1988) parametrization [2]_ - - Description - ----------- - The hermite transformation model is monotonic cubic polynomial, calibrated - such that the first 4 moments of the transformed model G(y)=g^-1(y) match - the moments of the true process. The model is given as: - - g(x) = xn - c3(xn**2-1) - c4*(xn**3-3*xn) - - for kurt<3 (hardening model) where - xn = (x-mean)/sigma - c3 = skew/6 - c4 = (kurt-3)/24. - - or - G(y) = mean + K*sigma*[ y + c3(y**2-1) + c4*(y**3-3*y) ] - - for kurt>=3 (softening model) where - y = g(x) = G**-1(x) - K = 1/sqrt(1+2*c3^2+6*c4^2) - If pardef = 1 : - c3 = skew/6*(1-0.015*abs(skew)+0.3*skew^2)/(1+0.2*(kurt-3)) - c4 = 0.1*((1+1.25*(kurt-3))^(1/3)-1)*c41 - c41 = (1-1.43*skew^2/(kurt-3))^(1-0.1*(kurt)^0.8) - If pardef = 2 : - c3 = skew/(6*(1+6*c4)) - c4 = [sqrt(1+1.5*(kurt-3))-1]/18 - - - Example: - -------- - """ + _example.replace('', 'TrHermite') + """ - >>> g.dist2gauss() - 0.88230868748851499 - >>> g2.dist2gauss() - 1.1411663205144991 - - See also - -------- - SpecData1d.stats_nl - wafo.transform.TrOchi - wafo.objects.LevelCrossings.trdata - wafo.objects.TimeSeries.trdata - - References - ---------- - .. [1] Winterstein, S.R, Ude, T.C. and Kleiven, G. (1994) - "Springing and slow drift responses: - predicted extremes and fatigue vs. simulation" - In Proc. 7th International behaviour of Offshore structures, (BOSS) - Vol. 3, pp.1-15 - .. [2] Winterstein, S.R. (1988) - 'Nonlinear vibration models for extremes and fatigue.' - J. Engng. Mech., ASCE, Vol 114, No 10, pp 1772-1790 - """ - def __init__(self, *args, **kwds): - super(TrHermite, self).__init__(*args, **kwds) - self.pardef = kwds.get('pardef', 1) - self._c3 = None - self._c4 = None - self._forward = None - self._backward = None - self._x_limit = None - self.set_poly() - - def _poly_par_from_stats(self): - skew = self.skew - ga2 = self.kurt - 3.0 - if ga2 <= 0: - self._c4 = ga2 / 24. - self._c3 = skew / 6. - elif self.pardef == 2: - # Winterstein 1988 parametrization - if skew ** 2 > 8 * (ga2 + 3.) / 9.: - warnings.warn('Kurtosis too low compared to the skewness') - - self._c4 = (sqrt(1. + 1.5 * ga2) - 1.) / 18. - self._c3 = skew / (6. * (1 + 6. * self._c4)) - else: - # Winterstein et. al. 1994 parametrization intended to - # apply for the range: 0 <= ga2 < 12 and 0<= skew^2 < 2*ga2/3 - if skew ** 2 > 2 * (ga2) / 3: - warnings.warn('Kurtosis too low compared to the skewness') - - if (ga2 < 0) or (12 < ga2): - warnings.warn('Kurtosis must be between 0 and 12') - - self._c3 = skew / 6 * (1 - 0.015 * abs(skew) + 0.3 * skew ** 2) / (1 + 0.2 * ga2) - if ga2 == 0.: - self._c4 = 0.0 - else: - c41 = (1. - 1.43 * skew ** 2. / ga2) ** (1. - 0.1 * (ga2 + 3.) ** 0.8) - self._c4 = 0.1 * ((1. + 1.25 * ga2) ** (1. / 3.) - 1.) * c41 - - if not np.isfinite(self._c3) or not np.isfinite(self._c4): - raise ValueError('Unable to calculate the polynomial') - - def set_poly(self): - ''' - Set poly function from stats (i.e., mean, sigma, skew and kurt) - ''' - - if self._c3 is None: - self._poly_par_from_stats() - eps = np.finfo(float).eps - c3 = self._c3 - c4 = self._c4 - ma = self.mean - sa = self.sigma - if abs(c4) < sqrt(eps): - c4 = 0.0 - - #gdef = self.kurt-3.0 - if self.kurt < 3.0: - p = np.poly1d([-c4, -c3, 1. + 3. * c4, c3]) # forward, g - self._forward = p - self._backward = None - else: - Km1 = np.sqrt(1. + 2. * c3 ** 2 + 6 * c4 ** 2) - p = np.poly1d(np.r_[c4, c3, 1. - 3. * c4, -c3] / Km1) # backward G - self._forward = None - self._backward = p - - #% Check if it is a strictly increasing function. - dp = p.deriv(m=1) #% Derivative - r = dp.r #% Find roots of the derivative - r = r[where(abs(imag(r)) < eps)] # Keep only real roots - - if r.size > 0: - # Compute where it is possible to invert the polynomial - if self.kurt < 3.: - self._x_limit = r - else: - self._x_limit = sa * p(r) + ma - txt1 = ''' - The polynomial is not a strictly increasing function. - The derivative of g(x) is infinite at x = %g''' % self._x_limit - warnings.warn(txt1) - return - def check_forward(self, x): - if not (self._x_limit is None): - x00 = self._x_limit - txt2 = 'for the given interval x = [%g, %g]' % (x[0], x[-1]) - - if any(np.logical_and(x[0] <= x00, x00 <= x[-1])): - cdef = 1 - else: - cdef = sum(np.logical_xor(x00 <= x[0] , x00 <= x[-1])) - - if np.mod(cdef, 2): - errtxt = 'Unable to invert the polynomial \n %s' % txt2 - raise ValueError(errtxt) - np.disp('However, successfully inverted the polynomial\n %s' % txt2) - - - def _dat2gauss(self, x, *xi): - if len(xi) > 0: - raise ValueError('Transforming derivatives is not implemented!') - xn = atleast_1d(x) - self.check_forward(xn) - - xn = (xn - self.mean) / self.sigma - - if self._forward is None: - #Inverting the polynomial - yn = self._poly_inv(self._backward, xn) - else: - yn = self._forward(xn) - return yn * self.ysigma + self.ymean - - def _gauss2dat(self, y, *yi): - if len(yi) > 0: - raise ValueError('Transforming derivatives is not implemented!') - yn = (atleast_1d(y) - self.ymean) / self.ysigma - #self.check_forward(y) - - if self._backward is None: - #% Inverting the polynomial - #%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - xn = self._poly_inv(self._forward, yn) - else: - xn = self._backward(yn) - return self.sigma * xn + self.mean - - def _poly_inv(self, p, xn): - ''' - Invert polynomial - ''' - if p.order < 2: - return xn - elif p.order == 2: - # Quadratic: Solve a*u**2+b*u+c = xn - coefs = p.coeffs - a = coefs[0] - b = coefs[1] - c = coefs[2] - xn - t = 0.5 * (b + sign(b) * sqrt(b ** 2 - 4 * a * c)) - #so1 = t/a # largest solution - so2 = -c / t # smallest solution - return so2 - elif p.order == 3: - # Solve - # K*(c4*u^3+c3*u^2+(1-3*c4)*u-c3) = xn = (x-ma)/sa - # -c4*xn^3-c3*xn^2+(1+3*c4)*xn+c3 = u - coefs = p.coeffs[1::] / p.coeffs[0] - a = coefs[0] - b = coefs[1] - c = coefs[2] - xn / p.coeffs[0] - - x0 = a / 3. - #% substitue xn = z-x0 and divide by c4 => z^3 + 3*p1*z+2*q0 = 0 - p1 = b / 3 - x0 ** 2 - #p1 = (b-a**2/3)/3 - - - #q0 = (c + x0*(2.*x0/3.-b))/2. - #q0 = x0**3 -a*b/6 +c/2 - q0 = x0 * (x0 ** 2 - b / 2) + c / 2 -## # z^3+3*p1*z+2*q0=0 - -## c3 = self._c3 -## c4 = self._c4 -## b1 = 1./(3.*c4) -## #x0 = c3*b1 -## #% substitue u = z-x0 and divide by c4 => z^3 + 3*c*z+2*q0 = 0 -## #p1 = b1-1.-x0**2. -## Km1 = np.sqrt(1.+2.*c3**2+6*c4**2) -## q0 = x0**3-1.5*b1*(x0+xn*Km1) - #q0 = x0**3-1.5*b1*(x0+xn) - if not (self._x_limit is None): # % Three real roots - d = sqrt(-p1) - theta1 = arccos(-q0 / d ** 3) / 3 - th2 = np.r_[0, -2 * pi / 3, 2 * pi / 3] - x1 = abs(2 * d * cos(theta1[ceil(len(xn) / 2)] + th2) - x0) - ix = x1.argmin() # % choose the smallest solution - return 2. * d * cos(theta1 + th2[ix]) - x0 - else: # %Only one real root exist - q1 = sqrt((q0) ** 2 + p1 ** 3) - #% Find the real root of the monic polynomial - A0 = (q1 - q0) ** (1. / 3.) - B0 = -(q1 + q0) ** (1. / 3.) - return A0 + B0 - x0 #% real root - #%% The other complex roots are given by - #%x= -(A0+B0)/2+(A0-B0)*sqrt(3)/2-x0 - #%x=-(A0+B0)/2+(A0-B0)*sqrt(-3)/2-x0 - - - -class TrLinear(TrCommon2): - __doc__ = TrCommon2.__doc__.replace('', 'Linear' #@ReservedAssignment - ) + """ - Description - ----------- - The linear transformation model is monotonic linear polynomial, calibrated - such that the first 2 moments of the transformed model G(y)=g^-1(y) match - the moments of the true process. - - Example: - -------- - """ + _example.replace('', 'TrLinear') + """ - >>> g.dist2gauss() - 0.0 - >>> g2.dist2gauss() - 3.8594770921678001e-31 - - See also - -------- - TrOchi - TrHermite - SpecData1D.stats_nl - LevelCrossings.trdata - TimeSeries.trdata - spec2skew, ochitr, lc2tr, dat2tr - - """ - def _dat2gauss(self, x, *xi): - sratio = atleast_1d(self.ysigma / self.sigma) - y = (atleast_1d(x) - self.mean) * sratio + self.ymean - if len(xi) > 0: - y = [y, ] + [ ix * sratio for ix in xi] - return y - - def _gauss2dat(self, y, *yi): - sratio = atleast_1d(self.sigma / self.ysigma) - x = (atleast_1d(y) - self.ymean) * sratio + self.mean - if len(yi) > 0: - x = [x, ] + [iy * sratio for iy in yi] - return x - - -class TrOchi(TrCommon2): - __doc__ = TrCommon2.__doc__.replace('', 'Ochi' #@ReservedAssignment - ) + """ - - Description - ----------- - The Ochi transformation model is a monotonic exponential function, - calibrated such that the first 3 moments of the transformed model - G(y)=g^-1(y) match the moments of the true process. However, the - skewness is limited by ABS(SKEW)<2.82. According to Ochi it is - appropriate for a process with very strong non-linear characteristics. - The model is given as: - g(x) = ((1-exp(-gamma*(x-mean)/sigma))/gamma-mean2)/sigma2 - where - gamma = 1.28*a for x>=mean - 3*a otherwise - mean, - sigma = standard deviation and mean, respectively, of the process. - mean2, - sigma2 = normalizing parameters in the transformed world, i.e., to - make the gaussian process in the transformed world is N(0,1). - - The unknown parameters a, mean2 and sigma2 are found by solving the - following non-linear equations: - - a*(sigma2^2+mean2^2)+mean2 = 0 - sigma2^2-2*a^2*sigma2^4 = 1 - 2*a*sigma2^4*(3-8*a^2*sigma2^2) = skew - - Note - ---- - Transformation, g, does not have continous derivatives of 2'nd order or higher. - - Example - ------- - """ + _example.replace('', 'TrOchi') + """ - >>> g.dist2gauss() - 1.410698801056657 - >>> g2.dist2gauss() - 1.988807188766706 - - See also - -------- - spec2skew, hermitetr, lc2tr, dat2tr - - References - ---------- - Ochi, M.K. and Ahn, K. (1994) - 'Non-Gaussian probability distribution of coastal waves.' - In Proc. 24th Conf. Coastal Engng, Vol. 1, pp 482-496 - - Michel K. Ochi (1998), - "OCEAN WAVES, The stochastic approach", - OCEAN TECHNOLOGY series 6, Cambridge, pp 255-275. - """ - - def __init__(self, *args, **kwds): - super(TrOchi, self).__init__(*args, **kwds) - self.kurt = None - self._phat = None - self._par_from_stats() - - def _par_from_stats(self): - skew = self.skew - if abs(skew) > 2.82842712474619: - raise ValueError('Skewness must be less than 2.82842') - - mean1 = self.mean - sigma1 = self.sigma - - if skew == 0: - self._phat = [sigma1, mean1, 0, 0, 1, 0] - return - - # Solve the equations to obtain the gamma parameters: - #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # a*(sig2^2+ma2^2)+ma2 = 0 - # sig2^2-2*a^2*sig2^4 = E(y^2) % =1 - # 2*a*sig2^4*(3-8*a^2*sig2^2) = E(y^3) % = skew - - # Let x = [a sig2^2 ] - # Set up the 2D non-linear equations for a and sig2^2: - # g1='[x(2)-2.*x(1).^2.*x(2).^2-P1, 2.*x(1).*x(2).^2.*(3-8.*x(1).^2.*x(2))-P2 ]' - # Or solve the following 1D non-linear equation for sig2^2: - g2 = lambda x:-sqrt(abs(x - 1) * 2) * (3. * x - 4 * abs(x - 1)) + abs(skew) - - - a1 = 1. # % Start interval where sig2^2 is located. - a2 = 2. - - sig22 = brentq(g2, a1, a2) #% smallest solution for sig22 - a = sign(skew) * sqrt(abs(sig22 - 1) / 2) / sig22 - gam_a = 1.28 * a - gam_b = 3 * a - sigma2 = sqrt(sig22) - - #% Solve the following 2nd order equation to obtain ma2 - #% a*(sig2^2+ma2^2)+ma2 = 0 - my2 = (-1. - sqrt(1. - 4. * a ** 2 * sig22)) / a #% Largest mean - mean2 = a * sig22 / my2 #% choose the smallest mean - - self._phat = [sigma1, mean1, gam_a, gam_b, sigma2, mean2] - return - - def _get_par(self): - ''' - Returns ga, gb, sigma2, mean2 - ''' - if (self._phat is None or self.sigma != self._phat[0] - or self.mean != self._phat[1]): - self._par_from_stats() - #sigma1 = self._phat[0] - #mean1 = self._phat[1] - ga = self._phat[2] - gb = self._phat[3] - sigma2 = self._phat[4] - mean2 = self._phat[5] - return ga, gb, sigma2, mean2 - - def _dat2gauss(self, x, *xi): - if len(xi) > 0: - raise ValueError('Transforming derivatives is not implemented!') - ga, gb, sigma2, mean2 = self._get_par() - mean = self.mean - sigma = self.sigma - xn = atleast_1d(x) - shape0 = xn.shape - xn = (xn.ravel() - mean) / sigma - igp, = where(0 <= xn) - igm, = where(xn < 0) - - g = xn.copy() - - if ga != 0: - np.put(g, igp, (-expm1(-ga * xn[igp])) / ga) - - if gb != 0: - np.put(g, igm, (-expm1(-gb * xn[igm])) / gb) - - g.shape = shape0 - return (g - mean2) * self.ysigma / sigma2 + self.ymean - - def _gauss2dat(self, y, *yi): - if len(yi) > 0: - raise ValueError('Transforming derivatives is not implemented!') - - ga, gb, sigma2, mean2 = self._get_par() - mean = self.mean - sigma = self.sigma - - yn = (atleast_1d(y) - self.ymean) / self.ysigma - xn = sigma2 * yn.ravel() + mean2 - - igp, = where(0 <= xn) - igm, = where(xn < 0) - - if ga != 0: - np.put(xn, igp, -log1p(-ga * xn[igp]) / ga) - - if gb != 0: - np.put(xn, igm, -log1p(-gb * xn[igm]) / gb) - - xn.shape = yn.shape - return sigma * xn + mean - -def main(): - import pylab - g = TrHermite(skew=0.1, kurt=3.01) - g.dist2gauss() - #g = TrOchi(skew=0.56) - x = np.linspace(-5, 5) - y = g(x) - pylab.plot(np.abs(x - g.gauss2dat(y))) - #pylab.plot(x,y,x,x,':',g.gauss2dat(y),y,'r') - - pylab.show() - np.disp('finito') - -if __name__ == '__main__': - if True : # False: # - import doctest - doctest.testmod() - else: - main() - - - +''' +Transform Gaussian models +------------------------- +TrHermite +TrOchi +TrLinear +''' +# !/usr/bin/env python +from __future__ import division +from scipy.optimize import brentq +from numpy import (sqrt, atleast_1d, abs, imag, sign, where, cos, arccos, ceil, # @UnresolvedImport + expm1, log1p, pi) # @UnresolvedImport +import numpy as np +import warnings +from core import TrCommon, TrData +__all__ = ['TrHermite', 'TrLinear', 'TrOchi'] + +_example = ''' + >>> import numpy as np + >>> import wafo.spectrum.models as sm + >>> import wafo.transform.models as tm + >>> std = 7./4 + >>> g = tm.(sigma=std, ysigma=std) + + Simulate a Transformed Gaussian process: + >>> Sj = sm.Jonswap(Hm0=4*std, Tp=11) + >>> w = np.linspace(0,4,256) + >>> S = Sj.tospecdata(w) # Make spectrum object from numerical values + >>> ys = S.sim(ns=15000) # Simulated in the Gaussian world + + >>> me, va, sk, ku = S.stats_nl(moments='mvsk') + >>> g2 = tm.(mean=me, var=va, skew=sk, kurt=ku, ysigma=std) + >>> xs = g2.gauss2dat(ys[:,1:]) # Transformed to the real world + ''' + + +class TrCommon2(TrCommon): + __doc__ = TrCommon.__doc__ # @ReservedAssignment + + def trdata(self, x=None, xnmin=-5, xnmax=5, n=513): + """ + Return a discretized transformation model. + + Parameters + ---------- + x : vector (default sigma*linspace(xnmin,xnmax,n)+mean) + xnmin : real, scalar + minimum on normalized scale + xnmax : real, scalar + maximum on normalized scale + n : integer, scalar + number of evaluation points + + Returns + ------- + t0 : real, scalar + a measure of departure from the Gaussian model calculated as + trapz((xn-g(x))**2., xn) where int. limits is given by X. + """ + if x is None: + xn = np.linspace(xnmin, xnmax, n) + x = self.sigma * xn + self.mean + else: + xn = (x - self.mean) / self.sigma + + yn = (self._dat2gauss(x) - self.ymean) / self.ysigma + + return TrData(yn, x, mean=self.mean, sigma=self.sigma) + + +class TrHermite(TrCommon2): + __doc__ = TrCommon2.__doc__.replace('', 'Hermite' # @ReservedAssignment + ) + """ + pardef : scalar, integer + 1 Winterstein et. al. (1994) parametrization [1]_ (default) + 2 Winterstein (1988) parametrization [2]_ + + Description + ----------- + The hermite transformation model is monotonic cubic polynomial, calibrated + such that the first 4 moments of the transformed model G(y)=g^-1(y) match + the moments of the true process. The model is given as: + + g(x) = xn - c3(xn**2-1) - c4*(xn**3-3*xn) + + for kurt<3 (hardening model) where + xn = (x-mean)/sigma + c3 = skew/6 + c4 = (kurt-3)/24. + + or + G(y) = mean + K*sigma*[ y + c3(y**2-1) + c4*(y**3-3*y) ] + + for kurt>=3 (softening model) where + y = g(x) = G**-1(x) + K = 1/sqrt(1+2*c3^2+6*c4^2) + If pardef = 1 : + c3 = skew/6*(1-0.015*abs(skew)+0.3*skew^2)/(1+0.2*(kurt-3)) + c4 = 0.1*((1+1.25*(kurt-3))^(1/3)-1)*c41 + c41 = (1-1.43*skew^2/(kurt-3))^(1-0.1*(kurt)^0.8) + If pardef = 2 : + c3 = skew/(6*(1+6*c4)) + c4 = [sqrt(1+1.5*(kurt-3))-1]/18 + + + Example: + -------- + """ + _example.replace('', 'TrHermite') + """ + >>> g.dist2gauss() + 0.88230868748851499 + >>> g2.dist2gauss() + 1.1411663205144991 + + See also + -------- + SpecData1d.stats_nl + wafo.transform.TrOchi + wafo.objects.LevelCrossings.trdata + wafo.objects.TimeSeries.trdata + + References + ---------- + .. [1] Winterstein, S.R, Ude, T.C. and Kleiven, G. (1994) + "Springing and slow drift responses: + predicted extremes and fatigue vs. simulation" + In Proc. 7th International behaviour of Offshore structures, (BOSS) + Vol. 3, pp.1-15 + .. [2] Winterstein, S.R. (1988) + 'Nonlinear vibration models for extremes and fatigue.' + J. Engng. Mech., ASCE, Vol 114, No 10, pp 1772-1790 + """ + + def __init__(self, *args, **kwds): + super(TrHermite, self).__init__(*args, **kwds) + self.pardef = kwds.get('pardef', 1) + self._c3 = None + self._c4 = None + self._forward = None + self._backward = None + self._x_limit = None + self.set_poly() + + def _poly_par_from_stats(self): + skew = self.skew + ga2 = self.kurt - 3.0 + if ga2 <= 0: + self._c4 = ga2 / 24. + self._c3 = skew / 6. + elif self.pardef == 2: + # Winterstein 1988 parametrization + if skew ** 2 > 8 * (ga2 + 3.) / 9.: + warnings.warn('Kurtosis too low compared to the skewness') + + self._c4 = (sqrt(1. + 1.5 * ga2) - 1.) / 18. + self._c3 = skew / (6. * (1 + 6. * self._c4)) + else: + # Winterstein et. al. 1994 parametrization intended to + # apply for the range: 0 <= ga2 < 12 and 0<= skew^2 < 2*ga2/3 + if skew ** 2 > 2 * (ga2) / 3: + warnings.warn('Kurtosis too low compared to the skewness') + + if (ga2 < 0) or (12 < ga2): + warnings.warn('Kurtosis must be between 0 and 12') + + self._c3 = skew / 6 * \ + (1 - 0.015 * abs(skew) + 0.3 * skew ** 2) / (1 + 0.2 * ga2) + if ga2 == 0.: + self._c4 = 0.0 + else: + expon = 1. - 0.1 * (ga2 + 3.) ** 0.8 + c41 = (1. - 1.43 * skew ** 2. / ga2) ** (expon) + self._c4 = 0.1 * ((1. + 1.25 * ga2) ** (1. / 3.) - 1.) * c41 + + if not np.isfinite(self._c3) or not np.isfinite(self._c4): + raise ValueError('Unable to calculate the polynomial') + + def set_poly(self): + ''' + Set poly function from stats (i.e., mean, sigma, skew and kurt) + ''' + + if self._c3 is None: + self._poly_par_from_stats() + eps = np.finfo(float).eps + c3 = self._c3 + c4 = self._c4 + ma = self.mean + sa = self.sigma + if abs(c4) < sqrt(eps): + c4 = 0.0 + + # gdef = self.kurt-3.0 + if self.kurt < 3.0: + p = np.poly1d([-c4, -c3, 1. + 3. * c4, c3]) # forward, g + self._forward = p + self._backward = None + else: + Km1 = np.sqrt(1. + 2. * c3 ** 2 + 6 * c4 ** 2) + # backward G + p = np.poly1d(np.r_[c4, c3, 1. - 3. * c4, -c3] / Km1) + self._forward = None + self._backward = p + + # Check if it is a strictly increasing function. + dp = p.deriv(m=1) # % Derivative + r = dp.r # % Find roots of the derivative + r = r[where(abs(imag(r)) < eps)] # Keep only real roots + + if r.size > 0: + # Compute where it is possible to invert the polynomial + if self.kurt < 3.: + self._x_limit = r + else: + self._x_limit = sa * p(r) + ma + txt1 = ''' + The polynomial is not a strictly increasing function. + The derivative of g(x) is infinite at x = %g''' % self._x_limit + warnings.warn(txt1) + return + + def check_forward(self, x): + if not (self._x_limit is None): + x00 = self._x_limit + txt2 = 'for the given interval x = [%g, %g]' % (x[0], x[-1]) + + if any(np.logical_and(x[0] <= x00, x00 <= x[-1])): + cdef = 1 + else: + cdef = sum(np.logical_xor(x00 <= x[0], x00 <= x[-1])) + + if np.mod(cdef, 2): + errtxt = 'Unable to invert the polynomial \n %s' % txt2 + raise ValueError(errtxt) + np.disp( + 'However, successfully inverted the polynomial\n %s' % txt2) + + def _dat2gauss(self, x, *xi): + if len(xi) > 0: + raise ValueError('Transforming derivatives is not implemented!') + xn = atleast_1d(x) + self.check_forward(xn) + + xn = (xn - self.mean) / self.sigma + + if self._forward is None: + # Inverting the polynomial + yn = self._poly_inv(self._backward, xn) + else: + yn = self._forward(xn) + return yn * self.ysigma + self.ymean + + def _gauss2dat(self, y, *yi): + if len(yi) > 0: + raise ValueError('Transforming derivatives is not implemented!') + yn = (atleast_1d(y) - self.ymean) / self.ysigma + # self.check_forward(y) + + if self._backward is None: + # Inverting the polynomial + xn = self._poly_inv(self._forward, yn) + else: + xn = self._backward(yn) + return self.sigma * xn + self.mean + + def _poly_inv(self, p, xn): + ''' + Invert polynomial + ''' + if p.order < 2: + return xn + elif p.order == 2: + # Quadratic: Solve a*u**2+b*u+c = xn + coefs = p.coeffs + a = coefs[0] + b = coefs[1] + c = coefs[2] - xn + t = 0.5 * (b + sign(b) * sqrt(b ** 2 - 4 * a * c)) + # so1 = t/a # largest solution + so2 = -c / t # smallest solution + return so2 + elif p.order == 3: + # Solve + # K*(c4*u^3+c3*u^2+(1-3*c4)*u-c3) = xn = (x-ma)/sa + # -c4*xn^3-c3*xn^2+(1+3*c4)*xn+c3 = u + coefs = p.coeffs[1::] / p.coeffs[0] + a = coefs[0] + b = coefs[1] + c = coefs[2] - xn / p.coeffs[0] + + x0 = a / 3. + # substitue xn = z-x0 and divide by c4 => z^3 + 3*p1*z+2*q0 = 0 + p1 = b / 3 - x0 ** 2 + # p1 = (b-a**2/3)/3 + + # q0 = (c + x0*(2.*x0/3.-b))/2. + # q0 = x0**3 -a*b/6 +c/2 + q0 = x0 * (x0 ** 2 - b / 2) + c / 2 +# z^3+3*p1*z+2*q0=0 + +# c3 = self._c3 +# c4 = self._c4 +# b1 = 1./(3.*c4) +# x0 = c3*b1 +# % substitue u = z-x0 and divide by c4 => z^3 + 3*c*z+2*q0 = 0 +# p1 = b1-1.-x0**2. +# Km1 = np.sqrt(1.+2.*c3**2+6*c4**2) +# q0 = x0**3-1.5*b1*(x0+xn*Km1) + # q0 = x0**3-1.5*b1*(x0+xn) + if not (self._x_limit is None): # % Three real roots + d = sqrt(-p1) + theta1 = arccos(-q0 / d ** 3) / 3 + th2 = np.r_[0, -2 * pi / 3, 2 * pi / 3] + x1 = abs(2 * d * cos(theta1[ceil(len(xn) / 2)] + th2) - x0) + ix = x1.argmin() # % choose the smallest solution + return 2. * d * cos(theta1 + th2[ix]) - x0 + else: # %Only one real root exist + q1 = sqrt((q0) ** 2 + p1 ** 3) + # Find the real root of the monic polynomial + A0 = (q1 - q0) ** (1. / 3.) + B0 = -(q1 + q0) ** (1. / 3.) + return A0 + B0 - x0 # % real root + #%% The other complex roots are given by + #%x= -(A0+B0)/2+(A0-B0)*sqrt(3)/2-x0 + #%x=-(A0+B0)/2+(A0-B0)*sqrt(-3)/2-x0 + + +class TrLinear(TrCommon2): + __doc__ = TrCommon2.__doc__.replace('', 'Linear' # @ReservedAssignment + ) + """ + Description + ----------- + The linear transformation model is monotonic linear polynomial, calibrated + such that the first 2 moments of the transformed model G(y)=g^-1(y) match + the moments of the true process. + + Example: + -------- + """ + _example.replace('', 'TrLinear') + """ + >>> g.dist2gauss() + 0.0 + >>> g2.dist2gauss() + 3.8594770921678001e-31 + + See also + -------- + TrOchi + TrHermite + SpecData1D.stats_nl + LevelCrossings.trdata + TimeSeries.trdata + spec2skew, ochitr, lc2tr, dat2tr + + """ + + def _dat2gauss(self, x, *xi): + sratio = atleast_1d(self.ysigma / self.sigma) + y = (atleast_1d(x) - self.mean) * sratio + self.ymean + if len(xi) > 0: + y = [y, ] + [ix * sratio for ix in xi] + return y + + def _gauss2dat(self, y, *yi): + sratio = atleast_1d(self.sigma / self.ysigma) + x = (atleast_1d(y) - self.ymean) * sratio + self.mean + if len(yi) > 0: + x = [x, ] + [iy * sratio for iy in yi] + return x + + +class TrOchi(TrCommon2): + __doc__ = TrCommon2.__doc__.replace('', 'Ochi' # @ReservedAssignment + ) + """ + + Description + ----------- + The Ochi transformation model is a monotonic exponential function, + calibrated such that the first 3 moments of the transformed model + G(y)=g^-1(y) match the moments of the true process. However, the + skewness is limited by ABS(SKEW)<2.82. According to Ochi it is + appropriate for a process with very strong non-linear characteristics. + The model is given as: + g(x) = ((1-exp(-gamma*(x-mean)/sigma))/gamma-mean2)/sigma2 + where + gamma = 1.28*a for x>=mean + 3*a otherwise + mean, + sigma = standard deviation and mean, respectively, of the process. + mean2, + sigma2 = normalizing parameters in the transformed world, i.e., to + make the gaussian process in the transformed world is N(0,1). + + The unknown parameters a, mean2 and sigma2 are found by solving the + following non-linear equations: + + a*(sigma2^2+mean2^2)+mean2 = 0 + sigma2^2-2*a^2*sigma2^4 = 1 + 2*a*sigma2^4*(3-8*a^2*sigma2^2) = skew + + Note + ---- + Transformation, g, does not have continous derivatives of 2'nd order or + higher. + + Example + ------- + """ + _example.replace('', 'TrOchi') + """ + >>> g.dist2gauss() + 1.410698801056657 + >>> g2.dist2gauss() + 1.988807188766706 + + See also + -------- + spec2skew, hermitetr, lc2tr, dat2tr + + References + ---------- + Ochi, M.K. and Ahn, K. (1994) + 'Non-Gaussian probability distribution of coastal waves.' + In Proc. 24th Conf. Coastal Engng, Vol. 1, pp 482-496 + + Michel K. Ochi (1998), + "OCEAN WAVES, The stochastic approach", + OCEAN TECHNOLOGY series 6, Cambridge, pp 255-275. + """ + + def __init__(self, *args, **kwds): + super(TrOchi, self).__init__(*args, **kwds) + self.kurt = None + self._phat = None + self._par_from_stats() + + def _par_from_stats(self): + skew = self.skew + if abs(skew) > 2.82842712474619: + raise ValueError('Skewness must be less than 2.82842') + + mean1 = self.mean + sigma1 = self.sigma + + if skew == 0: + self._phat = [sigma1, mean1, 0, 0, 1, 0] + return + + # Solve the equations to obtain the gamma parameters: + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # a*(sig2^2+ma2^2)+ma2 = 0 + # sig2^2-2*a^2*sig2^4 = E(y^2) % =1 + # 2*a*sig2^4*(3-8*a^2*sig2^2) = E(y^3) % = skew + + # Let x = [a sig2^2 ] + # Set up the 2D non-linear equations for a and sig2^2: + # g1='[x(2)-2.*x(1).^2.*x(2).^2-P1, 2.*x(1).*x(2).^2.*(3-8.*x(1).^2.*x(2))-P2 ]' + # Or solve the following 1D non-linear equation for sig2^2: + g2 = lambda x: -sqrt(abs(x - 1) * 2) * \ + (3. * x - 4 * abs(x - 1)) + abs(skew) + + a1 = 1. # % Start interval where sig2^2 is located. + a2 = 2. + + sig22 = brentq(g2, a1, a2) # % smallest solution for sig22 + a = sign(skew) * sqrt(abs(sig22 - 1) / 2) / sig22 + gam_a = 1.28 * a + gam_b = 3 * a + sigma2 = sqrt(sig22) + + # Solve the following 2nd order equation to obtain ma2 + # a*(sig2^2+ma2^2)+ma2 = 0 + my2 = (-1. - sqrt(1. - 4. * a ** 2 * sig22)) / a # % Largest mean + mean2 = a * sig22 / my2 # % choose the smallest mean + + self._phat = [sigma1, mean1, gam_a, gam_b, sigma2, mean2] + return + + def _get_par(self): + ''' + Returns ga, gb, sigma2, mean2 + ''' + if (self._phat is None or self.sigma != self._phat[0] + or self.mean != self._phat[1]): + self._par_from_stats() + # sigma1 = self._phat[0] + # mean1 = self._phat[1] + ga = self._phat[2] + gb = self._phat[3] + sigma2 = self._phat[4] + mean2 = self._phat[5] + return ga, gb, sigma2, mean2 + + def _dat2gauss(self, x, *xi): + if len(xi) > 0: + raise ValueError('Transforming derivatives is not implemented!') + ga, gb, sigma2, mean2 = self._get_par() + mean = self.mean + sigma = self.sigma + xn = atleast_1d(x) + shape0 = xn.shape + xn = (xn.ravel() - mean) / sigma + igp, = where(0 <= xn) + igm, = where(xn < 0) + + g = xn.copy() + + if ga != 0: + np.put(g, igp, (-expm1(-ga * xn[igp])) / ga) + + if gb != 0: + np.put(g, igm, (-expm1(-gb * xn[igm])) / gb) + + g.shape = shape0 + return (g - mean2) * self.ysigma / sigma2 + self.ymean + + def _gauss2dat(self, y, *yi): + if len(yi) > 0: + raise ValueError('Transforming derivatives is not implemented!') + + ga, gb, sigma2, mean2 = self._get_par() + mean = self.mean + sigma = self.sigma + + yn = (atleast_1d(y) - self.ymean) / self.ysigma + xn = sigma2 * yn.ravel() + mean2 + + igp, = where(0 <= xn) + igm, = where(xn < 0) + + if ga != 0: + np.put(xn, igp, -log1p(-ga * xn[igp]) / ga) + + if gb != 0: + np.put(xn, igm, -log1p(-gb * xn[igm]) / gb) + + xn.shape = yn.shape + return sigma * xn + mean + + +def main(): + import pylab + g = TrHermite(skew=0.1, kurt=3.01) + g.dist2gauss() + # g = TrOchi(skew=0.56) + x = np.linspace(-5, 5) + y = g(x) + pylab.plot(np.abs(x - g.gauss2dat(y))) + # pylab.plot(x,y,x,x,':',g.gauss2dat(y),y,'r') + + pylab.show() + np.disp('finito') + +if __name__ == '__main__': + if True: # False: # + import doctest + doctest.testmod() + else: + main() diff --git a/pywafo/src/wafo/wafodata.py b/pywafo/src/wafo/wafodata.py index bdccbcc..57a2a5e 100644 --- a/pywafo/src/wafo/wafodata.py +++ b/pywafo/src/wafo/wafodata.py @@ -1,515 +1,552 @@ -import warnings -from graphutil import cltext -from plotbackend import plotbackend -from time import gmtime, strftime -import numpy as np -from scipy.integrate.quadrature import cumtrapz #@UnresolvedImport -from scipy.interpolate import griddata -from scipy import integrate - -__all__ = ['PlotData', 'AxisLabels'] - -def empty_copy(obj): - class Empty(obj.__class__): - def __init__(self): - pass - newcopy = Empty() - newcopy.__class__ = obj.__class__ - return newcopy - -def _set_seed(iseed): - if iseed != None: - try: - np.random.set_state(iseed) - except: - np.random.seed(iseed) -def now(): - ''' - Return current date and time as a string - ''' - return strftime("%a, %d %b %Y %H:%M:%S", gmtime()) - -class PlotData(object): - ''' - Container class for data objects in WAFO - - Member variables - ---------------- - data : array_like - args : vector for 1D, list of vectors for 2D, 3D, ... - labels : AxisLabels - children : list of PlotData objects - - Member methods - -------------- - plot : - copy : - - - Example - ------- - >>> import numpy as np - >>> x = np.arange(-2, 2, 0.2) - - # Plot 2 objects in one call - >>> d2 = PlotData(np.sin(x), x, xlab='x', ylab='sin', title='sinus') - >>> h = d2.plot() - - Plot with confidence interval - >>> d3 = PlotData(np.sin(x),x) - >>> d3.children = [PlotData(np.vstack([np.sin(x)*0.9, np.sin(x)*1.2]).T,x)] - >>> d3.plot_args_children=[':r'] - >>> h = d3.plot() - - See also - -------- - specdata, - covdata - ''' - def __init__(self, data=None, args=None, *args2, **kwds): - self.data = data - self.args = args - self.date = now() - self.plotter = kwds.pop('plotter', None) - self.children = None - self.plot_args_children = kwds.pop('plot_args_children', []) - self.plot_kwds_children = kwds.pop('plot_kwds_children', {}) - self.plot_args = kwds.pop('plot_args', []) - self.plot_kwds = kwds.pop('plot_kwds', {}) - - self.labels = AxisLabels(**kwds) - if not self.plotter: - self.setplotter(kwds.get('plotmethod', None)) - - def plot(self, *args, **kwds): - axis = kwds.pop('axis',None) - if axis is None: - axis = plotbackend.gca() - tmp = None - plotflag = kwds.get('plotflag', None) - if not plotflag and self.children != None: - plotbackend.hold('on') - tmp = [] - child_args = kwds.pop('plot_args_children', tuple(self.plot_args_children)) - child_kwds = dict(self.plot_kwds_children).copy() - child_kwds.update(kwds.pop('plot_kwds_children', {})) - child_kwds['axis'] = axis - for child in self.children: - tmp1 = child.plot(*child_args, **child_kwds) - if tmp1 != None: - tmp.append(tmp1) - if len(tmp) == 0: - tmp = None - main_args = args if len(args) else tuple(self.plot_args) - main_kwds = dict(self.plot_kwds).copy() - main_kwds.update(kwds) - main_kwds['axis'] = axis - tmp2 = self.plotter.plot(self, *main_args, **main_kwds) - return tmp2, tmp - - def eval_points(self, *args, **kwds): - ''' - >>> x = np.linspace(0,5,20) - >>> d = PlotData(np.sin(x),x) - >>> xi = np.linspace(0,5,60) - >>> di = PlotData(d.eval_points(xi, method='cubic'),xi) - >>> h = d.plot('.') - >>> hi = di.plot() - - ''' - if isinstance(self.args, (list, tuple)): # Multidimensional data - ndim = len(self.args) - if ndim < 2: - msg = '''Unable to determine plotter-type, because len(self.args)<2. - If the data is 1D, then self.args should be a vector! - If the data is 2D, then length(self.args) should be 2. - If the data is 3D, then length(self.args) should be 3. - Unless you fix this, the plot methods will not work!''' - warnings.warn(msg) - else: - return griddata(self.args, self.data.ravel(), *args,**kwds) - else: #One dimensional data - return griddata((self.args,), self.data, *args,**kwds) - - def integrate(self, a, b, **kwds): - ''' - >>> x = np.linspace(0,5,60) - >>> d = PlotData(np.sin(x), x) - >>> d.integrate(0,np.pi/2) - 0.99940054759302188 - - ''' - method = kwds.pop('method','trapz') - fun = getattr(integrate, method) - if isinstance(self.args, (list, tuple)): # Multidimensional data - ndim = len(self.args) - if ndim < 2: - msg = '''Unable to determine plotter-type, because len(self.args)<2. - If the data is 1D, then self.args should be a vector! - If the data is 2D, then length(self.args) should be 2. - If the data is 3D, then length(self.args) should be 3. - Unless you fix this, the plot methods will not work!''' - warnings.warn(msg) - else: - return griddata(self.args, self.data.ravel(), **kwds) - else: #One dimensional data - - x = self.args - ix = np.flatnonzero((a2') - - else: #One dimensional data - self.plotter = Plotter_1d(plotmethod) - - -class AxisLabels: - def __init__(self, title='', xlab='', ylab='', zlab='', **kwds): - self.title = title - self.xlab = xlab - self.ylab = ylab - self.zlab = zlab - def __repr__(self): - return self.__str__() - def __str__(self): - return '%s\n%s\n%s\n%s\n' % (self.title, self.xlab, self.ylab, self.zlab) - def copy(self): - newcopy = empty_copy(self) - newcopy.__dict__.update(self.__dict__) - return newcopy - - def labelfig(self, axis=None): - if axis is None: - axis = plotbackend.gca() - try: - h1 = axis.set_title(self.title) - h2 = axis.set_xlabel(self.xlab) - h3 = axis.set_ylabel(self.ylab) - #h4 = plotbackend.zlabel(self.zlab) - return h1, h2, h3 - except: - pass - -class Plotter_1d(object): - """ - - Parameters - ---------- - plotmethod : string - defining type of plot. Options are: - bar : bar plot with rectangles - barh : horizontal bar plot with rectangles - loglog : plot with log scaling on the *x* and *y* axis - semilogx : plot with log scaling on the *x* axis - semilogy : plot with log scaling on the *y* axis - plot : Plot lines and/or markers (default) - stem : Stem plot - step : stair-step plot - scatter : scatter plot - """ - def __init__(self, plotmethod='plot'): - self.plotfun = None - if plotmethod is None: - plotmethod = 'plot' - self.plotmethod = plotmethod - self.plotbackend = plotbackend -# try: -# self.plotfun = getattr(plotbackend, plotmethod) -# except: -# pass - - def show(self): - plotbackend.show() - - def plot(self, wdata, *args, **kwds): - axis = kwds.pop('axis',None) - if axis is None: - axis = plotbackend.gca() - plotflag = kwds.pop('plotflag', False) - if plotflag: - h1 = self._plot(axis, plotflag, wdata, *args, **kwds) - else: - if isinstance(wdata.data, (list, tuple)): - vals = tuple(wdata.data) - else: - vals = (wdata.data,) - if isinstance(wdata.args, (list, tuple)): - args1 = tuple((wdata.args)) + vals + args - else: - args1 = tuple((wdata.args,)) + vals + args - plotfun = getattr(axis, self.plotmethod) - h1 = plotfun(*args1, **kwds) - h2 = wdata.labels.labelfig(axis) - return h1, h2 - - def _plot(self, axis, plotflag, wdata, *args, **kwds): - x = wdata.args - data = transformdata(x, wdata.data, plotflag) - dataCI = getattr(wdata, 'dataCI', ()) - h1 = plot1d(axis, x, data, dataCI, plotflag, *args, **kwds) - return h1 - -def plot1d(axis, args, data, dataCI, plotflag, *varargin, **kwds): - - plottype = np.mod(plotflag, 10) - if plottype == 0: # % No plotting - return [] - elif plottype == 1: - H = axis.plot(args, data, *varargin, **kwds) - elif plottype == 2: - H = axis.step(args, data, *varargin, **kwds) - elif plottype == 3: - H = axis.stem(args, data, *varargin, **kwds) - elif plottype == 4: - H = axis.errorbar(args, data, yerr=[dataCI[:,0] - data, dataCI[:,1] - data], *varargin, **kwds) - elif plottype == 5: - H = axis.bar(args, data, *varargin, **kwds) - elif plottype == 6: - level = 0 - if np.isfinite(level): - H = axis.fill_between(args, data, level, *varargin, **kwds); - else: - H = axis.fill_between(args, data, *varargin, **kwds); - elif plottype==7: - H = axis.plot(args, data, *varargin, **kwds) - H = axis.fill_between(args, dataCI[:,0], dataCI[:,1], alpha=0.2, color='r'); - - scale = plotscale(plotflag) - logXscale = 'x' in scale - logYscale = 'y' in scale - logZscale = 'z' in scale - - if logXscale: - axis.set(xscale='log') - if logYscale: - axis.set(yscale='log') - if logZscale: - axis.set(zscale='log') - - transFlag = np.mod(plotflag // 10, 10) - logScale = logXscale or logYscale or logZscale - if logScale or (transFlag == 5 and not logScale): - ax = list(axis.axis()) - fmax1 = data.max() - if transFlag == 5 and not logScale: - ax[3] = 11 * np.log10(fmax1) - ax[2] = ax[3] - 40 - else: - ax[3] = 1.15 * fmax1; - ax[2] = ax[3] * 1e-4; - - axis.axis(ax) - - if np.any(dataCI) and plottype < 3: - axis.hold(True) - plot1d(axis, args, dataCI, (), plotflag, 'r--'); - return H - -def plotscale(plotflag): - ''' - Return plotscale from plotflag - - CALL scale = plotscale(plotflag) - - plotflag = integer defining plotscale. - Let scaleId = floor(plotflag/100). - If scaleId < 8 then: - 0 'linear' : Linear scale on all axes. - 1 'xlog' : Log scale on x-axis. - 2 'ylog' : Log scale on y-axis. - 3 'xylog' : Log scale on xy-axis. - 4 'zlog' : Log scale on z-axis. - 5 'xzlog' : Log scale on xz-axis. - 6 'yzlog' : Log scale on yz-axis. - 7 'xyzlog' : Log scale on xyz-axis. - otherwise - if (mod(scaleId,10)>0) : Log scale on x-axis. - if (mod(floor(scaleId/10),10)>0) : Log scale on y-axis. - if (mod(floor(scaleId/100),10)>0) : Log scale on z-axis. - - scale = string defining plotscale valid options are: - 'linear', 'xlog', 'ylog', 'xylog', 'zlog', 'xzlog', - 'yzlog', 'xyzlog' - - Example - >>> for id in range(100,701,100): - ... plotscale(id) - 'xlog' - 'ylog' - 'xylog' - 'zlog' - 'xzlog' - 'yzlog' - 'xyzlog' - - >>> plotscale(200) - 'ylog' - >>> plotscale(300) - 'xylog' - >>> plotscale(300) - 'xylog' - - See also - -------- - transformdata - ''' - scaleId = plotflag // 100 - if scaleId > 7: - logXscaleId = np.mod(scaleId, 10) > 0 - logYscaleId = (np.mod(scaleId // 10, 10) > 0) * 2 - logZscaleId = (np.mod(scaleId // 100, 10) > 0) * 4 - scaleId = logYscaleId + logXscaleId + logZscaleId - - scales = ['linear', 'xlog', 'ylog', 'xylog', 'zlog', 'xzlog', 'yzlog', 'xyzlog'] - - return scales[scaleId] - -def transformdata(x, f, plotflag): - transFlag = np.mod(plotflag // 10, 10) - if transFlag == 0: - data = f - elif transFlag == 1: - data = 1 - f - elif transFlag == 2: - data = cumtrapz(f, x) - elif transFlag == 3: - data = 1 - cumtrapz(f, x) - if transFlag in (4, 5): - if transFlag == 4: - data = -np.log1p(-cumtrapz(f, x)) - else: - if any(f < 0): - raise ValueError('Invalid plotflag: Data or dataCI is negative, but must be positive') - data = 10 * np.log10(f) - return data - -class Plotter_2d(Plotter_1d): - """ - Parameters - ---------- - plotmethod : string - defining type of plot. Options are: - contour (default) - contourf - mesh - surf - """ - - def __init__(self, plotmethod='contour'): - if plotmethod is None: - plotmethod = 'contour' - super(Plotter_2d, self).__init__(plotmethod) - - def _plot(self, axis, plotflag, wdata, *args, **kwds): - h1 = plot2d(axis, wdata, plotflag, *args, **kwds) - return h1 - -def plot2d(axis, wdata, plotflag, *args, **kwds): - f = wdata - if isinstance(wdata.args, (list, tuple)): - args1 = tuple((wdata.args)) + (wdata.data,) + args - else: - args1 = tuple((wdata.args,)) + (wdata.data,) + args - if plotflag in (1, 6, 7, 8, 9): - isPL = False - if hasattr(f, 'clevels') and len(f.clevels) > 0: # check if contour levels is submitted - CL = f.clevels - isPL = hasattr(f, 'plevels') and f.plevels is not None - if isPL: - PL = f.plevels # levels defines quantile levels? 0=no 1=yes - else: - dmax = np.max(f.data) - dmin = np.min(f.data) - CL = dmax - (dmax - dmin) * (1 - np.r_[0.01, 0.025, 0.05, 0.1, 0.2, 0.4, 0.5, 0.75]) - clvec = np.sort(CL) - - if plotflag in [1, 8, 9]: - h = axis.contour(*args1, levels=CL, **kwds); - #else: - # [cs hcs] = contour3(f.x{:},f.f,CL,sym); - - if plotflag in (1, 6): - ncl = len(clvec) - if ncl > 12: - ncl = 12 - warnings.warn('Only the first 12 levels will be listed in table.') - - clvals = PL[:ncl] if isPL else clvec[:ncl] - unused_axcl = cltext(clvals, percent=isPL) # print contour level text - elif any(plotflag == [7, 9]): - axis.clabel(h) - else: - axis.clabel(h) - elif plotflag == 2: - h = axis.mesh(*args1, **kwds) - elif plotflag == 3: - h = axis.surf(*args1, **kwds) #shading interp % flat, faceted % surfc - elif plotflag == 4: - h = axis.waterfall(*args1, **kwds) - elif plotflag == 5: - h = axis.pcolor(*args1, **kwds) #%shading interp % flat, faceted - elif plotflag == 10: - h = axis.contourf(*args1, **kwds) - axis.clabel(h) - plotbackend.colorbar(h) - else: - raise ValueError('unknown option for plotflag') - #if any(plotflag==(2:5)) - # shading(shad); - #end - # pass - -def test_eval_points(): - plotbackend.ioff() - x = np.linspace(0,5,21) - d = PlotData(np.sin(x),x) - xi = np.linspace(0,5,61) - di = PlotData(d.eval_points(xi,method='cubic'),xi) - d.plot('.') - di.plot() - di.show() -def test_integrate(): - x = np.linspace(0,5,60) - d = PlotData(np.sin(x), x) - print(d.integrate(0,np.pi/2,method='simps')) -def test_docstrings(): - import doctest - doctest.testmod() - -def main(): - pass - -if __name__ == '__main__': - - #test_integrate() - #test_eval_points() - test_docstrings() - #main() +import warnings +from graphutil import cltext +from plotbackend import plotbackend +from time import gmtime, strftime +import numpy as np +from scipy.integrate.quadrature import cumtrapz # @UnresolvedImport +from scipy.interpolate import griddata +from scipy import integrate + +__all__ = ['PlotData', 'AxisLabels'] + + +def empty_copy(obj): + class Empty(obj.__class__): + + def __init__(self): + pass + newcopy = Empty() + newcopy.__class__ = obj.__class__ + return newcopy + + +def _set_seed(iseed): + if iseed != None: + try: + np.random.set_state(iseed) + except: + np.random.seed(iseed) + + +def now(): + ''' + Return current date and time as a string + ''' + return strftime("%a, %d %b %Y %H:%M:%S", gmtime()) + + +class PlotData(object): + + ''' + Container class for data objects in WAFO + + Member variables + ---------------- + data : array_like + args : vector for 1D, list of vectors for 2D, 3D, ... + labels : AxisLabels + children : list of PlotData objects + + Member methods + -------------- + plot : + copy : + + + Example + ------- + >>> import numpy as np + >>> x = np.arange(-2, 2, 0.2) + + # Plot 2 objects in one call + >>> d2 = PlotData(np.sin(x), x, xlab='x', ylab='sin', title='sinus') + >>> h = d2.plot() + + Plot with confidence interval + >>> d3 = PlotData(np.sin(x),x) + >>> d3.children = [PlotData(np.vstack([np.sin(x)*0.9, np.sin(x)*1.2]).T,x)] + >>> d3.plot_args_children=[':r'] + >>> h = d3.plot() + + See also + -------- + specdata, + covdata + ''' + + def __init__(self, data=None, args=None, *args2, **kwds): + self.data = data + self.args = args + self.date = now() + self.plotter = kwds.pop('plotter', None) + self.children = None + self.plot_args_children = kwds.pop('plot_args_children', []) + self.plot_kwds_children = kwds.pop('plot_kwds_children', {}) + self.plot_args = kwds.pop('plot_args', []) + self.plot_kwds = kwds.pop('plot_kwds', {}) + + self.labels = AxisLabels(**kwds) + if not self.plotter: + self.setplotter(kwds.get('plotmethod', None)) + + def plot(self, *args, **kwds): + axis = kwds.pop('axis', None) + if axis is None: + axis = plotbackend.gca() + tmp = None + plotflag = kwds.get('plotflag', None) + if not plotflag and self.children != None: + plotbackend.hold('on') + tmp = [] + child_args = kwds.pop( + 'plot_args_children', tuple(self.plot_args_children)) + child_kwds = dict(self.plot_kwds_children).copy() + child_kwds.update(kwds.pop('plot_kwds_children', {})) + child_kwds['axis'] = axis + for child in self.children: + tmp1 = child.plot(*child_args, **child_kwds) + if tmp1 != None: + tmp.append(tmp1) + if len(tmp) == 0: + tmp = None + main_args = args if len(args) else tuple(self.plot_args) + main_kwds = dict(self.plot_kwds).copy() + main_kwds.update(kwds) + main_kwds['axis'] = axis + tmp2 = self.plotter.plot(self, *main_args, **main_kwds) + return tmp2, tmp + + def eval_points(self, *args, **kwds): + ''' + >>> x = np.linspace(0,5,20) + >>> d = PlotData(np.sin(x),x) + >>> xi = np.linspace(0,5,60) + >>> di = PlotData(d.eval_points(xi, method='cubic'),xi) + >>> h = d.plot('.') + >>> hi = di.plot() + + ''' + if isinstance(self.args, (list, tuple)): # Multidimensional data + ndim = len(self.args) + if ndim < 2: + msg = '''Unable to determine plotter-type, because len(self.args)<2. + If the data is 1D, then self.args should be a vector! + If the data is 2D, then length(self.args) should be 2. + If the data is 3D, then length(self.args) should be 3. + Unless you fix this, the plot methods will not work!''' + warnings.warn(msg) + else: + return griddata(self.args, self.data.ravel(), *args, **kwds) + else: # One dimensional data + return griddata((self.args,), self.data, *args, **kwds) + + def integrate(self, a, b, **kwds): + ''' + >>> x = np.linspace(0,5,60) + >>> d = PlotData(np.sin(x), x) + >>> d.integrate(0,np.pi/2) + 0.99940054759302188 + + ''' + method = kwds.pop('method', 'trapz') + fun = getattr(integrate, method) + if isinstance(self.args, (list, tuple)): # Multidimensional data + ndim = len(self.args) + if ndim < 2: + msg = '''Unable to determine plotter-type, because len(self.args)<2. + If the data is 1D, then self.args should be a vector! + If the data is 2D, then length(self.args) should be 2. + If the data is 3D, then length(self.args) should be 3. + Unless you fix this, the plot methods will not work!''' + warnings.warn(msg) + else: + return griddata(self.args, self.data.ravel(), **kwds) + else: # One dimensional data + + x = self.args + ix = np.flatnonzero((a < x) & (x < b)) + xi = np.hstack((a, x.take(ix), b)) + fi = np.hstack( + (self.eval_points(a), self.data.take(ix), self.eval_points(b))) + return fun(fi, xi, **kwds) + + def show(self): + self.plotter.show() + + def copy(self): + newcopy = empty_copy(self) + newcopy.__dict__.update(self.__dict__) + return newcopy + + def setplotter(self, plotmethod=None): + ''' + Set plotter based on the data type data_1d, data_2d, data_3d or data_nd + ''' + if isinstance(self.args, (list, tuple)): # Multidimensional data + ndim = len(self.args) + if ndim < 2: + msg = '''Unable to determine plotter-type, because len(self.args)<2. + If the data is 1D, then self.args should be a vector! + If the data is 2D, then length(self.args) should be 2. + If the data is 3D, then length(self.args) should be 3. + Unless you fix this, the plot methods will not work!''' + warnings.warn(msg) + elif ndim == 2: + self.plotter = Plotter_2d(plotmethod) + else: + warnings.warn('Plotter method not implemented for ndim>2') + + else: # One dimensional data + self.plotter = Plotter_1d(plotmethod) + + +class AxisLabels: + + def __init__(self, title='', xlab='', ylab='', zlab='', **kwds): + self.title = title + self.xlab = xlab + self.ylab = ylab + self.zlab = zlab + + def __repr__(self): + return self.__str__() + + def __str__(self): + return '%s\n%s\n%s\n%s\n' % (self.title, self.xlab, self.ylab, self.zlab) + + def copy(self): + newcopy = empty_copy(self) + newcopy.__dict__.update(self.__dict__) + return newcopy + + def labelfig(self, axis=None): + if axis is None: + axis = plotbackend.gca() + try: + h1 = axis.set_title(self.title) + h2 = axis.set_xlabel(self.xlab) + h3 = axis.set_ylabel(self.ylab) + #h4 = plotbackend.zlabel(self.zlab) + return h1, h2, h3 + except: + pass + + +class Plotter_1d(object): + + """ + + Parameters + ---------- + plotmethod : string + defining type of plot. Options are: + bar : bar plot with rectangles + barh : horizontal bar plot with rectangles + loglog : plot with log scaling on the *x* and *y* axis + semilogx : plot with log scaling on the *x* axis + semilogy : plot with log scaling on the *y* axis + plot : Plot lines and/or markers (default) + stem : Stem plot + step : stair-step plot + scatter : scatter plot + """ + + def __init__(self, plotmethod='plot'): + self.plotfun = None + if plotmethod is None: + plotmethod = 'plot' + self.plotmethod = plotmethod + self.plotbackend = plotbackend +# try: +# self.plotfun = getattr(plotbackend, plotmethod) +# except: +# pass + + def show(self): + plotbackend.show() + + def plot(self, wdata, *args, **kwds): + axis = kwds.pop('axis', None) + if axis is None: + axis = plotbackend.gca() + plotflag = kwds.pop('plotflag', False) + if plotflag: + h1 = self._plot(axis, plotflag, wdata, *args, **kwds) + else: + if isinstance(wdata.data, (list, tuple)): + vals = tuple(wdata.data) + else: + vals = (wdata.data,) + if isinstance(wdata.args, (list, tuple)): + args1 = tuple((wdata.args)) + vals + args + else: + args1 = tuple((wdata.args,)) + vals + args + plotfun = getattr(axis, self.plotmethod) + h1 = plotfun(*args1, **kwds) + h2 = wdata.labels.labelfig(axis) + return h1, h2 + + def _plot(self, axis, plotflag, wdata, *args, **kwds): + x = wdata.args + data = transformdata(x, wdata.data, plotflag) + dataCI = getattr(wdata, 'dataCI', ()) + h1 = plot1d(axis, x, data, dataCI, plotflag, *args, **kwds) + return h1 + + +def plot1d(axis, args, data, dataCI, plotflag, *varargin, **kwds): + + plottype = np.mod(plotflag, 10) + if plottype == 0: # % No plotting + return [] + elif plottype == 1: + H = axis.plot(args, data, *varargin, **kwds) + elif plottype == 2: + H = axis.step(args, data, *varargin, **kwds) + elif plottype == 3: + H = axis.stem(args, data, *varargin, **kwds) + elif plottype == 4: + H = axis.errorbar( + args, data, yerr=[dataCI[:, 0] - data, dataCI[:, 1] - data], *varargin, **kwds) + elif plottype == 5: + H = axis.bar(args, data, *varargin, **kwds) + elif plottype == 6: + level = 0 + if np.isfinite(level): + H = axis.fill_between(args, data, level, *varargin, **kwds) + else: + H = axis.fill_between(args, data, *varargin, **kwds) + elif plottype == 7: + H = axis.plot(args, data, *varargin, **kwds) + H = axis.fill_between( + args, dataCI[:, 0], dataCI[:, 1], alpha=0.2, color='r') + + scale = plotscale(plotflag) + logXscale = 'x' in scale + logYscale = 'y' in scale + logZscale = 'z' in scale + + if logXscale: + axis.set(xscale='log') + if logYscale: + axis.set(yscale='log') + if logZscale: + axis.set(zscale='log') + + transFlag = np.mod(plotflag // 10, 10) + logScale = logXscale or logYscale or logZscale + if logScale or (transFlag == 5 and not logScale): + ax = list(axis.axis()) + fmax1 = data.max() + if transFlag == 5 and not logScale: + ax[3] = 11 * np.log10(fmax1) + ax[2] = ax[3] - 40 + else: + ax[3] = 1.15 * fmax1 + ax[2] = ax[3] * 1e-4 + + axis.axis(ax) + + if np.any(dataCI) and plottype < 3: + axis.hold(True) + plot1d(axis, args, dataCI, (), plotflag, 'r--') + return H + + +def plotscale(plotflag): + ''' + Return plotscale from plotflag + + CALL scale = plotscale(plotflag) + + plotflag = integer defining plotscale. + Let scaleId = floor(plotflag/100). + If scaleId < 8 then: + 0 'linear' : Linear scale on all axes. + 1 'xlog' : Log scale on x-axis. + 2 'ylog' : Log scale on y-axis. + 3 'xylog' : Log scale on xy-axis. + 4 'zlog' : Log scale on z-axis. + 5 'xzlog' : Log scale on xz-axis. + 6 'yzlog' : Log scale on yz-axis. + 7 'xyzlog' : Log scale on xyz-axis. + otherwise + if (mod(scaleId,10)>0) : Log scale on x-axis. + if (mod(floor(scaleId/10),10)>0) : Log scale on y-axis. + if (mod(floor(scaleId/100),10)>0) : Log scale on z-axis. + + scale = string defining plotscale valid options are: + 'linear', 'xlog', 'ylog', 'xylog', 'zlog', 'xzlog', + 'yzlog', 'xyzlog' + + Example + >>> for id in range(100,701,100): + ... plotscale(id) + 'xlog' + 'ylog' + 'xylog' + 'zlog' + 'xzlog' + 'yzlog' + 'xyzlog' + + >>> plotscale(200) + 'ylog' + >>> plotscale(300) + 'xylog' + >>> plotscale(300) + 'xylog' + + See also + -------- + transformdata + ''' + scaleId = plotflag // 100 + if scaleId > 7: + logXscaleId = np.mod(scaleId, 10) > 0 + logYscaleId = (np.mod(scaleId // 10, 10) > 0) * 2 + logZscaleId = (np.mod(scaleId // 100, 10) > 0) * 4 + scaleId = logYscaleId + logXscaleId + logZscaleId + + scales = ['linear', 'xlog', 'ylog', 'xylog', + 'zlog', 'xzlog', 'yzlog', 'xyzlog'] + + return scales[scaleId] + + +def transformdata(x, f, plotflag): + transFlag = np.mod(plotflag // 10, 10) + if transFlag == 0: + data = f + elif transFlag == 1: + data = 1 - f + elif transFlag == 2: + data = cumtrapz(f, x) + elif transFlag == 3: + data = 1 - cumtrapz(f, x) + if transFlag in (4, 5): + if transFlag == 4: + data = -np.log1p(-cumtrapz(f, x)) + else: + if any(f < 0): + raise ValueError( + 'Invalid plotflag: Data or dataCI is negative, but must be positive') + data = 10 * np.log10(f) + return data + + +class Plotter_2d(Plotter_1d): + + """ + Parameters + ---------- + plotmethod : string + defining type of plot. Options are: + contour (default) + contourf + mesh + surf + """ + + def __init__(self, plotmethod='contour'): + if plotmethod is None: + plotmethod = 'contour' + super(Plotter_2d, self).__init__(plotmethod) + + def _plot(self, axis, plotflag, wdata, *args, **kwds): + h1 = plot2d(axis, wdata, plotflag, *args, **kwds) + return h1 + + +def plot2d(axis, wdata, plotflag, *args, **kwds): + f = wdata + if isinstance(wdata.args, (list, tuple)): + args1 = tuple((wdata.args)) + (wdata.data,) + args + else: + args1 = tuple((wdata.args,)) + (wdata.data,) + args + if plotflag in (1, 6, 7, 8, 9): + isPL = False + # check if contour levels is submitted + if hasattr(f, 'clevels') and len(f.clevels) > 0: + CL = f.clevels + isPL = hasattr(f, 'plevels') and f.plevels is not None + if isPL: + PL = f.plevels # levels defines quantile levels? 0=no 1=yes + else: + dmax = np.max(f.data) + dmin = np.min(f.data) + CL = dmax - (dmax - dmin) * \ + (1 - np.r_[0.01, 0.025, 0.05, 0.1, 0.2, 0.4, 0.5, 0.75]) + clvec = np.sort(CL) + + if plotflag in [1, 8, 9]: + h = axis.contour(*args1, levels=CL, **kwds) + # else: + # [cs hcs] = contour3(f.x{:},f.f,CL,sym); + + if plotflag in (1, 6): + ncl = len(clvec) + if ncl > 12: + ncl = 12 + warnings.warn( + 'Only the first 12 levels will be listed in table.') + + clvals = PL[:ncl] if isPL else clvec[:ncl] + # print contour level text + unused_axcl = cltext(clvals, percent=isPL) + elif any(plotflag == [7, 9]): + axis.clabel(h) + else: + axis.clabel(h) + elif plotflag == 2: + h = axis.mesh(*args1, **kwds) + elif plotflag == 3: + # shading interp % flat, faceted % surfc + h = axis.surf(*args1, **kwds) + elif plotflag == 4: + h = axis.waterfall(*args1, **kwds) + elif plotflag == 5: + h = axis.pcolor(*args1, **kwds) # %shading interp % flat, faceted + elif plotflag == 10: + h = axis.contourf(*args1, **kwds) + axis.clabel(h) + plotbackend.colorbar(h) + else: + raise ValueError('unknown option for plotflag') + # if any(plotflag==(2:5)) + # shading(shad); + # end + # pass + + +def test_eval_points(): + plotbackend.ioff() + x = np.linspace(0, 5, 21) + d = PlotData(np.sin(x), x) + xi = np.linspace(0, 5, 61) + di = PlotData(d.eval_points(xi, method='cubic'), xi) + d.plot('.') + di.plot() + di.show() + + +def test_integrate(): + x = np.linspace(0, 5, 60) + d = PlotData(np.sin(x), x) + print(d.integrate(0, np.pi / 2, method='simps')) + + +def test_docstrings(): + import doctest + doctest.testmod() + + +def main(): + pass + +if __name__ == '__main__': + + # test_integrate() + # test_eval_points() + test_docstrings() + # main() diff --git a/pywafo/src/wafo/wave_theory/core.py b/pywafo/src/wafo/wave_theory/core.py index d26a5e5..3593d58 100644 --- a/pywafo/src/wafo/wave_theory/core.py +++ b/pywafo/src/wafo/wave_theory/core.py @@ -1,565 +1,603 @@ -''' -Created on 3. juni 2011 - -@author: pab -''' -import numpy as np -from numpy import exp, expm1, inf, nan, pi, hstack, where, atleast_1d, cos, sin -from dispersion_relation import w2k, k2w #@UnusedImport - -__all__ =['w2k', 'k2w', 'sensor_typeid', 'sensor_type', 'TransferFunction'] - -def hyperbolic_ratio(a, b, sa, sb): - ''' - Return ratio of hyperbolic functions - to allow extreme variations of arguments. - - Parameters - ---------- - a, b : array-like - arguments vectors of the same size - sa, sb : scalar integers - defining the hyperbolic function used, i.e., f(x,1)=cosh(x), f(x,-1)=sinh(x) - - Returns - ------- - r : ndarray - f(a,sa)/f(b,sb), ratio of hyperbolic functions of same - size as a and b - Examples - -------- - >>> x = [-2,0,2] - >>> hyperbolic_ratio(x,1,1,1) # gives r=cosh(x)/cosh(1) - array([ 2.438107 , 0.64805427, 2.438107 ]) - >>> hyperbolic_ratio(x,1,1,-1) # gives r=cosh(x)/sinh(1) - array([ 3.20132052, 0.85091813, 3.20132052]) - >>> hyperbolic_ratio(x,1,-1,1) # gives r=sinh(x)/cosh(1) - array([-2.35040239, 0. , 2.35040239]) - >>> hyperbolic_ratio(x,1,-1,-1) # gives r=sinh(x)/sinh(1) - array([-3.08616127, 0. , 3.08616127]) - >>> hyperbolic_ratio(1,x,1,1) # gives r=cosh(1)/cosh(x) - array([ 0.41015427, 1.54308063, 0.41015427]) - >>> hyperbolic_ratio(1,x,1,-1) # gives r=cosh(1)/sinh(x) - array([-0.42545906, inf, 0.42545906]) - >>> hyperbolic_ratio(1,x,-1,1) # gives r=sinh(1)/cosh(x) - array([ 0.3123711 , 1.17520119, 0.3123711 ]) - >>> hyperbolic_ratio(1,x,-1,-1) # gives r=sinh(1)/sinh(x) - array([-0.32402714, inf, 0.32402714]) - - See also - -------- - tran - ''' - - ak, bk, sak, sbk = np.atleast_1d(a, b, np.sign(sa), np.sign(sb)) - # old call - #return exp(ak-bk)*(1+sak*exp(-2*ak))/(1+sbk*exp(-2*bk)) - # TODO: Does not always handle division by zero correctly - - signRatio = np.where(sak * ak < 0, sak, 1) - signRatio = np.where(sbk * bk < 0, sbk * signRatio, signRatio) - - bk = np.abs(bk) - ak = np.abs(ak) - - num = np.where(sak < 0, expm1(-2 * ak), 1 + exp(-2 * ak)) - den = np.where(sbk < 0, expm1(-2 * bk), 1 + exp(-2 * bk)) - iden = np.ones(den.shape) * inf - ind = np.flatnonzero(den != 0) - iden.flat[ind] = 1.0 / den[ind] - val = np.where(num == den, 1, num * iden) - return signRatio * exp(ak - bk) * val #((sak+exp(-2*ak))/(sbk+exp(-2*bk))) - -def sensor_typeid(*sensortypes): - ''' Return ID for sensortype name - - Parameter - --------- - sensortypes : list of strings defining the sensortype - - Returns - ------- - sensorids : list of integers defining the sensortype - - Valid senor-ids and -types for time series are as follows: - 0, 'n' : Surface elevation (n=Eta) - 1, 'n_t' : Vertical surface velocity - 2, 'n_tt' : Vertical surface acceleration - 3, 'n_x' : Surface slope in x-direction - 4, 'n_y' : Surface slope in y-direction - 5, 'n_xx' : Surface curvature in x-direction - 6, 'n_yy' : Surface curvature in y-direction - 7, 'n_xy' : Surface curvature in xy-direction - 8, 'P' : Pressure fluctuation about static MWL pressure - 9, 'U' : Water particle velocity in x-direction - 10, 'V' : Water particle velocity in y-direction - 11, 'W' : Water particle velocity in z-direction - 12, 'U_t' : Water particle acceleration in x-direction - 13, 'V_t' : Water particle acceleration in y-direction - 14, 'W_t' : Water particle acceleration in z-direction - 15, 'X_p' : Water particle displacement in x-direction from its mean position - 16, 'Y_p' : Water particle displacement in y-direction from its mean position - 17, 'Z_p' : Water particle displacement in z-direction from its mean position - - Example: - >>> sensor_typeid('W','v') - [11, 10] - >>> sensor_typeid('rubbish') - [nan] - - See also - -------- - sensor_type - ''' - - sensorid_table = dict(n=0, n_t=1, n_tt=2, n_x=3, n_y=4, n_xx=5, - n_yy=6, n_xy=7, p=8, u=9, v=10, w=11, u_t=12, - v_t=13, w_t=14, x_p=15, y_p=16, z_p=17) - try: - return [sensorid_table.get(name.lower(), nan) for name in sensortypes] - except: - raise ValueError('Input must be a string!') - - - -def sensor_type(*sensorids): - ''' - Return sensortype name - - Parameter - --------- - sensorids : vector or list of integers defining the sensortype - - Returns - ------- - sensornames : tuple of strings defining the sensortype - Valid senor-ids and -types for time series are as follows: - 0, 'n' : Surface elevation (n=Eta) - 1, 'n_t' : Vertical surface velocity - 2, 'n_tt' : Vertical surface acceleration - 3, 'n_x' : Surface slope in x-direction - 4, 'n_y' : Surface slope in y-direction - 5, 'n_xx' : Surface curvature in x-direction - 6, 'n_yy' : Surface curvature in y-direction - 7, 'n_xy' : Surface curvature in xy-direction - 8, 'P' : Pressure fluctuation about static MWL pressure - 9, 'U' : Water particle velocity in x-direction - 10, 'V' : Water particle velocity in y-direction - 11, 'W' : Water particle velocity in z-direction - 12, 'U_t' : Water particle acceleration in x-direction - 13, 'V_t' : Water particle acceleration in y-direction - 14, 'W_t' : Water particle acceleration in z-direction - 15, 'X_p' : Water particle displacement in x-direction from its mean position - 16, 'Y_p' : Water particle displacement in y-direction from its mean position - 17, 'Z_p' : Water particle displacement in z-direction from its mean position - - Example: - >>> sensor_type(range(3)) - ('n', 'n_t', 'n_tt') - - See also - -------- - sensor_typeid, tran - ''' - valid_names = ('n', 'n_t', 'n_tt', 'n_x', 'n_y', 'n_xx', 'n_yy', 'n_xy', - 'p', 'u', 'v', 'w', 'u_t', 'v_t', 'w_t', 'x_p', 'y_p', 'z_p', - nan) - ids = atleast_1d(*sensorids) - if isinstance(ids, list): - ids = hstack(ids) - n = len(valid_names) - 1 - ids = where(((ids < 0) | (n < ids)), n , ids) - return tuple(valid_names[i] for i in ids) - -class TransferFunction(object): - ''' - Class for computing transfer functions based on linear wave theory - of the system with input surface elevation, - eta(x0,y0,t) = exp(i*(kx*x0+ky*y0-w*t)), - and output Y determined by sensortype and position of sensor. - - Member methods - -------------- - tran(w, theta, kw) - - Hw = a function of frequency only (not direction) size 1 x Nf - Gwt = a function of frequency and direction size Nt x Nf - w = vector of angular frequencies in Rad/sec. Length Nf - theta = vector of directions in radians Length Nt (default 0) - ( theta = 0 -> positive x axis theta = pi/2 -> positive y axis) - Member variables - ---------------- - pos : [x,y,z] - vector giving coordinate position relative to [x0 y0 z0] (default [0,0,0]) - sensortype = string - defining the sensortype or transfer function in output. - 0, 'n' : Surface elevation (n=Eta) (default) - 1, 'n_t' : Vertical surface velocity - 2, 'n_tt' : Vertical surface acceleration - 3, 'n_x' : Surface slope in x-direction - 4, 'n_y' : Surface slope in y-direction - 5, 'n_xx' : Surface curvature in x-direction - 6, 'n_yy' : Surface curvature in y-direction - 7, 'n_xy' : Surface curvature in xy-direction - 8, 'P' : Pressure fluctuation about static MWL pressure - 9, 'U' : Water particle velocity in x-direction - 10, 'V' : Water particle velocity in y-direction - 11, 'W' : Water particle velocity in z-direction - 12, 'U_t' : Water particle acceleration in x-direction - 13, 'V_t' : Water particle acceleration in y-direction - 14, 'W_t' : Water particle acceleration in z-direction - 15, 'X_p' : Water particle displacement in x-direction from its mean position - 16, 'Y_p' : Water particle displacement in y-direction from its mean position - 17, 'Z_p' : Water particle displacement in z-direction from its mean position - h : real scalar - water depth (default inf) - g : real scalar - acceleration of gravity (default 9.81 m/s**2) - rho : real scalar - water density (default 1028 kg/m**3) - bet : 1 or -1 - 1, theta given in terms of directions toward which waves travel (default) - -1, theta given in terms of directions from which waves come - igam : 1,2 or 3 - 1, if z is measured positive upward from mean water level (default) - 2, if z is measured positive downward from mean water level - 3, if z is measured positive upward from sea floor - thetax, thetay : real scalars - angle in degrees clockwise from true north to positive x-axis and - positive y-axis, respectively. (default theatx=90, thetay=0) - - Example - ------- - >>> import pylab as plt - >>> N=50; f0=0.1; th0=0; h=50; w0 = 2*pi*f0 - >>> t = np.linspace(0,15,N) - >>> eta0 = np.exp(-1j*w0*t) - >>> stypes = ['n', 'n_x', 'n_y']; - >>> tf = TransferFunction(pos=(0, 0, 0), h=50) - >>> vals = [] - >>> fh = plt.plot(t, eta0.real, 'r.') - >>> plt.hold(True) - >>> for i,stype in enumerate(stypes): - ... tf.sensortype = stype - ... Hw, Gwt = tf.tran(w0,th0) - ... vals.append((Hw*Gwt*eta0).real.ravel()) - ... vals[i] - ... fh = plt.plot(t, vals[i]) - >>> plt.show() - - - See also - -------- - dat2dspec, sensor_type, sensor_typeid - - Reference - --------- - Young I.R. (1994) - "On the measurement of directional spectra", - Applied Ocean Research, Vol 16, pp 283-294 - ''' - def __init__(self, pos=(0, 0, 0), sensortype='n', h=inf, g=9.81, rho=1028, - bet=1, igam=1, thetax=90, thetay=0): - self.pos = pos - self.sensortype = sensortype if isinstance(sensortype, str) else sensor_type(sensortype) - self.h = h - self.g = g - self.rho = rho - self.bet = bet - self.igam = igam - self.thetax = thetax - self.thetay = thetay - self._tran_dict = dict(n=self._n, n_t=self._n_t, n_tt=self._n_tt, - n_x=self._n_x, n_y=self._n_y, n_xx=self._n_xx, - n_yy=self._n_yy, n_xy=self._n_xy, - P=self._p, p=self._p, - U=self._u, u=self._u, - V=self._v, v=self._v, - W=self._w, w=self._w, - U_t=self._u_t, u_t=self._u_t, - V_t=self._v_t, v_t=self._v_t, - W_t=self._w_t, w_t=self._w_t, - X_p=self._x_p, x_p=self._x_p, - Y_p=self._y_p, y_p=self._y_p, - Z_p=self._z_p, z_p=self._z_p) - - def tran(self, w, theta=0, kw=None): - ''' - Return transfer functions based on linear wave theory - of the system with input surface elevation, - eta(x0,y0,t) = exp(i*(kx*x0+ky*y0-w*t)), - and output, - Y = Hw*Gwt*eta, determined by sensortype and position of sensor. - - Parameters - ---------- - w : array-like - vector of angular frequencies in Rad/sec. Length Nf - theta : array-like - vector of directions in radians Length Nt (default 0) - ( theta = 0 -> positive x axis theta = pi/2 -> positive y axis) - kw : array-like - vector of wave numbers corresponding to angular frequencies, w. Length Nf - (default calculated with w2k) - - Returns - ------- - Hw = transfer function of frequency only (not direction) size 1 x Nf - Gwt = transfer function of frequency and direction size Nt x Nf - - The complete transfer function Hwt = Hw*Gwt is a function of - w (columns) and theta (rows) size Nt x Nf - ''' - if kw is None: - kw, unusedkw2 = w2k(w, 0, self.h) #wave number as function of angular frequency - - w, theta, kw = np.atleast_1d(w, theta, kw) - # make sure they have the correct orientation - theta.shape = (-1, 1) - kw.shape = (-1,) - w.shape = (-1,) - - tran_fun = self._tran_dict[self.sensortype] - Hw, Gwt = tran_fun(w, theta, kw) - - # New call to avoid singularities. pab 07.11.2000 - # Set Hw to 0 for expressions w*hyperbolic_ratio(z*k,h*k,1,-1)= 0*inf - ind = np.flatnonzero(1 - np.isfinite(Hw)) - Hw.flat[ind] = 0 - - sgn = np.sign(Hw); - k0 = np.flatnonzero(sgn < 0) - if len(k0): # make sure Hw>=0 ie. transfer negative signs to Gwt - Gwt[:, k0] = -Gwt[:, k0] - Hw[:, k0] = -Hw[:, k0] - - if self.igam == 2: - #pab 09 Oct.2002: bug fix - # Changing igam by 2 should affect the directional result in the same way that changing eta by -eta! - Gwt = -Gwt - return Hw, Gwt - __call__ = tran -#---Private member methods - def _get_ee_cthxy(self, theta, kw): - # convert from angle in degrees to radians - bet = self.bet - thxr = self.thetax * pi / 180 - thyr = self.thetay * pi / 180 - - cthx = bet * cos(theta - thxr + pi / 2) - #cthy = cos(theta-thyr-pi/2) - cthy = bet * sin(theta - thyr) - - # Compute location complex exponential - x, y, unused_z = list(self.pos) - ee = exp((1j * (x * cthx + y * cthy)) * kw) # exp(i*k(w)*(x*cos(theta)+y*sin(theta)) size Nt X Nf - return ee, cthx, cthy - - def _get_zk(self, kw): - h = self.h - z = self.pos[2] - if self.igam == 1: - zk = kw * (h + z) # z measured positive upward from mean water level (default) - elif self.igam == 2: - zk = kw * (h - z) # z measured positive downward from mean water level - else: - zk = kw * z # z measured positive upward from sea floor - return zk - - #--- Surface elevation --- - def _n(self, w, theta, kw): - '''n = Eta = wave profile - ''' - ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) - return np.ones_like(w), ee - - #---- Vertical surface velocity and acceleration----- - def _n_t(self, w, theta, kw): - ''' n_t = Eta_t ''' - ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) - return w, -1j * ee; - def _n_tt(self, w, theta, kw): - '''n_tt = Eta_tt''' - ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) - return w ** 2, -ee - - #--- Surface slopes --- - def _n_x(self, w, theta, kw): - ''' n_x = Eta_x = x-slope''' - ee, cthx, unused_cthy = self._get_ee_cthxy(theta, kw) - return kw, 1j * cthx * ee - def _n_y(self, w, theta, kw): - ''' n_y = Eta_y = y-slope''' - ee, unused_cthx, cthy = self._get_ee_cthxy(theta, kw) - return kw, 1j * cthy * ee - - #--- Surface curvatures --- - def _n_xx(self, w, theta, kw): - ''' n_xx = Eta_xx = Surface curvature (x-dir)''' - ee, cthx, unused_cthy = self._get_ee_cthxy(theta, kw) - return kw ** 2, -(cthx ** 2) * ee - def _n_yy(self, w, theta, kw): - ''' n_yy = Eta_yy = Surface curvature (y-dir)''' - ee, unused_cthx, cthy = self._get_ee_cthxy(theta, kw) - return kw ** 2, -cthy ** 2 * ee - def _n_xy(self, w, theta, kw): - ''' n_xy = Eta_xy = Surface curvature (xy-dir)''' - ee, cthx, cthy = self._get_ee_cthxy(theta, kw) - return kw ** 2, -cthx * cthy * ee - - #--- Pressure--- - def _p(self, w, theta, kw): - ''' pressure fluctuations''' - ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) - hk = kw * self.h - zk = self._get_zk(kw) - return self.rho * self.g * hyperbolic_ratio(zk, hk, 1, 1), ee #hyperbolic_ratio = cosh(zk)/cosh(hk) - - #---- Water particle velocities --- - def _u(self, w, theta, kw): - ''' U = x-velocity''' - ee, cthx, unused_cthy = self._get_ee_cthxy(theta, kw) - hk = kw * self.h - zk = self._get_zk(kw) - return w * hyperbolic_ratio(zk, hk, 1, -1), cthx * ee# w*cosh(zk)/sinh(hk), cos(theta)*ee - def _v(self, w, theta, kw): - '''V = y-velocity''' - ee, unused_cthx, cthy = self._get_ee_cthxy(theta, kw) - hk = kw * self.h - zk = self._get_zk(kw) - return w * hyperbolic_ratio(zk, hk, 1, -1), cthy * ee # w*cosh(zk)/sinh(hk), sin(theta)*ee - def _w(self, w, theta, kw): - ''' W = z-velocity''' - ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) - hk = kw * self.h - zk = self._get_zk(kw) - return w * hyperbolic_ratio(zk, hk, -1, -1), -1j * ee # w*sinh(zk)/sinh(hk), -? - - #---- Water particle acceleration --- - def _u_t(self, w, theta, kw): - ''' U_t = x-acceleration''' - ee, cthx, unused_cthy = self._get_ee_cthxy(theta, kw) - hk = kw * self.h - zk = self._get_zk(kw) - return (w ** 2) * hyperbolic_ratio(zk, hk, 1, -1), -1j * cthx * ee # w^2*cosh(zk)/sinh(hk), ? - - def _v_t(self, w, theta, kw): - ''' V_t = y-acceleration''' - ee, unused_cthx, cthy = self._get_ee_cthxy(theta, kw) - hk = kw * self.h - zk = self._get_zk(kw) - return (w ** 2) * hyperbolic_ratio(zk, hk, 1, -1), -1j * cthy * ee # w^2*cosh(zk)/sinh(hk), ? - def _w_t(self, w, theta, kw): - ''' W_t = z-acceleration''' - ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) - hk = kw * self.h - zk = self._get_zk(kw) - return (w ** 2) * hyperbolic_ratio(zk, hk, -1, -1), -ee # w*sinh(zk)/sinh(hk), ? - - #---- Water particle displacement --- - def _x_p(self, w, theta, kw): - ''' X_p = x-displacement''' - ee, cthx, unused_cthy = self._get_ee_cthxy(theta, kw) - hk = kw * self.h - zk = self._get_zk(kw) - return hyperbolic_ratio(zk, hk, 1, -1), 1j * cthx * ee # cosh(zk)./sinh(hk), ? - def _y_p(self, w, theta, kw): - ''' Y_p = y-displacement''' - ee, unused_cthx, cthy = self._get_ee_cthxy(theta, kw) - hk = kw * self.h - zk = self._get_zk(kw) - return hyperbolic_ratio(zk, hk, 1, -1), 1j * cthy * ee # cosh(zk)./sinh(hk), ? - def _z_p(self, w, theta, kw): - ''' Z_p = z-displacement''' - ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) - hk = kw * self.h - zk = self._get_zk(kw) - return hyperbolic_ratio(zk, hk, -1, -1), ee # sinh(zk)./sinh(hk), ee - -#def wave_pressure(z, Hm0, h=10000, g=9.81, rho=1028): -# ''' -# Calculate pressure amplitude due to water waves. -# -# Parameters -# ---------- -# z : array-like -# depth where pressure is calculated [m] -# Hm0 : array-like -# significant wave height (same as the average of the 1/3'rd highest -# waves in a seastate. [m] -# h : real scalar -# waterdepth (default 10000 [m]) -# g : real scalar -# acceleration of gravity (default 9.81 m/s**2) -# rho : real scalar -# water density (default 1028 kg/m**3) -# -# -# Returns -# ------- -# p : ndarray -# pressure amplitude due to water waves at water depth z. [Pa] -# -# PRESSURE calculate pressure amplitude due to water waves according to -# linear theory. -# -# Example -# ----- -# >>> import pylab as plt -# >>> z = -np.linspace(10,20) -# >>> fh = plt.plot(z, wave_pressure(z, Hm0=1, h=20)) -# >>> plt.show() -# -# See also -# -------- -# w2k -# -# -# u = psweep.Fn*sqrt(mgf.length*9.81) -# z = -10; h = inf; -# Hm0 = 1.5;Tp = 4*sqrt(Hm0); -# S = jonswap([],[Hm0,Tp]); -# Hw = tran(S.w,0,[0 0 -z],'P',h) -# Sm = S; -# Sm.S = Hw.'.*S.S; -# x1 = spec2sdat(Sm,1000); -# pwave = pressure(z,Hm0,h) -# -# plot(psweep.x{1}/u, psweep.f) -# hold on -# plot(x1(1:100,1)-30,x1(1:100,2),'r') -# ''' -# -# -# # Assume seastate with jonswap spectrum: -# -# Tp = 4 * np.sqrt(Hm0) -# gam = jonswap_peakfact(Hm0, Tp) -# Tm02 = Tp / (1.30301 - 0.01698 * gam + 0.12102 / gam) -# w = 2 * np.pi / Tm02 -# kw, unused_kw2 = w2k(w, 0, h) -# -# hk = kw * h -# zk1 = kw * z -# zk = hk + zk1 # z measured positive upward from mean water level (default) -# #zk = hk-zk1; % z measured positive downward from mean water level -# #zk1 = -zk1; -# #zk = zk1; % z measured positive upward from sea floor -# -# # cosh(zk)/cosh(hk) approx exp(zk) for large h -# # hyperbolic_ratio(zk,hk,1,1) = cosh(zk)/cosh(hk) -# # pr = np.where(np.pi < hk, np.exp(zk1), hyperbolic_ratio(zk, hk, 1, 1)) -# pr = hyperbolic_ratio(zk, hk, 1, 1) -# pressure = (rho * g * Hm0 / 2) * pr -# -## pos = [np.zeros_like(z),np.zeros_like(z),z] -## tf = TransferFunction(pos=pos, sensortype='p', h=h, rho=rho, g=g) -## Hw, Gwt = tf.tran(w,0) -## pressure2 = np.abs(Hw) * Hm0 / 2 -# -# return pressure - -def main(): - sensor_type(range(21)) -if __name__ == '__main__': - pass \ No newline at end of file +''' +Created on 3. juni 2011 + +@author: pab +''' +import numpy as np +from numpy import exp, expm1, inf, nan, pi, hstack, where, atleast_1d, cos, sin +from dispersion_relation import w2k, k2w # @UnusedImport + +__all__ = ['w2k', 'k2w', 'sensor_typeid', 'sensor_type', 'TransferFunction'] + + +def hyperbolic_ratio(a, b, sa, sb): + ''' + Return ratio of hyperbolic functions + to allow extreme variations of arguments. + + Parameters + ---------- + a, b : array-like + arguments vectors of the same size + sa, sb : scalar integers + defining the hyperbolic function used, i.e., + f(x,1)=cosh(x), f(x,-1)=sinh(x) + + Returns + ------- + r : ndarray + f(a,sa)/f(b,sb), ratio of hyperbolic functions of same + size as a and b + Examples + -------- + >>> x = [-2,0,2] + >>> hyperbolic_ratio(x,1,1,1) # gives r=cosh(x)/cosh(1) + array([ 2.438107 , 0.64805427, 2.438107 ]) + >>> hyperbolic_ratio(x,1,1,-1) # gives r=cosh(x)/sinh(1) + array([ 3.20132052, 0.85091813, 3.20132052]) + >>> hyperbolic_ratio(x,1,-1,1) # gives r=sinh(x)/cosh(1) + array([-2.35040239, 0. , 2.35040239]) + >>> hyperbolic_ratio(x,1,-1,-1) # gives r=sinh(x)/sinh(1) + array([-3.08616127, 0. , 3.08616127]) + >>> hyperbolic_ratio(1,x,1,1) # gives r=cosh(1)/cosh(x) + array([ 0.41015427, 1.54308063, 0.41015427]) + >>> hyperbolic_ratio(1,x,1,-1) # gives r=cosh(1)/sinh(x) + array([-0.42545906, inf, 0.42545906]) + >>> hyperbolic_ratio(1,x,-1,1) # gives r=sinh(1)/cosh(x) + array([ 0.3123711 , 1.17520119, 0.3123711 ]) + >>> hyperbolic_ratio(1,x,-1,-1) # gives r=sinh(1)/sinh(x) + array([-0.32402714, inf, 0.32402714]) + + See also + -------- + tran + ''' + + ak, bk, sak, sbk = np.atleast_1d(a, b, np.sign(sa), np.sign(sb)) + # old call + # return exp(ak-bk)*(1+sak*exp(-2*ak))/(1+sbk*exp(-2*bk)) + # TODO: Does not always handle division by zero correctly + + signRatio = np.where(sak * ak < 0, sak, 1) + signRatio = np.where(sbk * bk < 0, sbk * signRatio, signRatio) + + bk = np.abs(bk) + ak = np.abs(ak) + + num = np.where(sak < 0, expm1(-2 * ak), 1 + exp(-2 * ak)) + den = np.where(sbk < 0, expm1(-2 * bk), 1 + exp(-2 * bk)) + iden = np.ones(den.shape) * inf + ind = np.flatnonzero(den != 0) + iden.flat[ind] = 1.0 / den[ind] + val = np.where(num == den, 1, num * iden) + # ((sak+exp(-2*ak))/(sbk+exp(-2*bk))) + return signRatio * exp(ak - bk) * val + + +def sensor_typeid(*sensortypes): + ''' Return ID for sensortype name + + Parameter + --------- + sensortypes : list of strings defining the sensortype + + Returns + ------- + sensorids : list of integers defining the sensortype + + Valid senor-ids and -types for time series are as follows: + 0, 'n' : Surface elevation (n=Eta) + 1, 'n_t' : Vertical surface velocity + 2, 'n_tt' : Vertical surface acceleration + 3, 'n_x' : Surface slope in x-direction + 4, 'n_y' : Surface slope in y-direction + 5, 'n_xx' : Surface curvature in x-direction + 6, 'n_yy' : Surface curvature in y-direction + 7, 'n_xy' : Surface curvature in xy-direction + 8, 'P' : Pressure fluctuation about static MWL pressure + 9, 'U' : Water particle velocity in x-direction + 10, 'V' : Water particle velocity in y-direction + 11, 'W' : Water particle velocity in z-direction + 12, 'U_t' : Water particle acceleration in x-direction + 13, 'V_t' : Water particle acceleration in y-direction + 14, 'W_t' : Water particle acceleration in z-direction + 15, 'X_p' : Water particle displacement in x-direction from mean pos. + 16, 'Y_p' : Water particle displacement in y-direction from mean pos. + 17, 'Z_p' : Water particle displacement in z-direction from mean pos. + + Example: + >>> sensor_typeid('W','v') + [11, 10] + >>> sensor_typeid('rubbish') + [nan] + + See also + -------- + sensor_type + ''' + + sensorid_table = dict(n=0, n_t=1, n_tt=2, n_x=3, n_y=4, n_xx=5, + n_yy=6, n_xy=7, p=8, u=9, v=10, w=11, u_t=12, + v_t=13, w_t=14, x_p=15, y_p=16, z_p=17) + try: + return [sensorid_table.get(name.lower(), nan) for name in sensortypes] + except: + raise ValueError('Input must be a string!') + + +def sensor_type(*sensorids): + ''' + Return sensortype name + + Parameter + --------- + sensorids : vector or list of integers defining the sensortype + + Returns + ------- + sensornames : tuple of strings defining the sensortype + Valid senor-ids and -types for time series are as follows: + 0, 'n' : Surface elevation (n=Eta) + 1, 'n_t' : Vertical surface velocity + 2, 'n_tt' : Vertical surface acceleration + 3, 'n_x' : Surface slope in x-direction + 4, 'n_y' : Surface slope in y-direction + 5, 'n_xx' : Surface curvature in x-direction + 6, 'n_yy' : Surface curvature in y-direction + 7, 'n_xy' : Surface curvature in xy-direction + 8, 'P' : Pressure fluctuation about static MWL pressure + 9, 'U' : Water particle velocity in x-direction + 10, 'V' : Water particle velocity in y-direction + 11, 'W' : Water particle velocity in z-direction + 12, 'U_t' : Water particle acceleration in x-direction + 13, 'V_t' : Water particle acceleration in y-direction + 14, 'W_t' : Water particle acceleration in z-direction + 15, 'X_p' : Water particle displacement in x-direction from mean pos. + 16, 'Y_p' : Water particle displacement in y-direction from mean pos. + 17, 'Z_p' : Water particle displacement in z-direction from mean pos. + + Example: + >>> sensor_type(range(3)) + ('n', 'n_t', 'n_tt') + + See also + -------- + sensor_typeid, tran + ''' + valid_names = ('n', 'n_t', 'n_tt', 'n_x', 'n_y', 'n_xx', 'n_yy', 'n_xy', + 'p', 'u', 'v', 'w', 'u_t', 'v_t', 'w_t', 'x_p', 'y_p', + 'z_p', nan) + ids = atleast_1d(*sensorids) + if isinstance(ids, list): + ids = hstack(ids) + n = len(valid_names) - 1 + ids = where(((ids < 0) | (n < ids)), n, ids) + return tuple(valid_names[i] for i in ids) + + +class TransferFunction(object): + + ''' + Class for computing transfer functions based on linear wave theory + of the system with input surface elevation, + eta(x0,y0,t) = exp(i*(kx*x0+ky*y0-w*t)), + and output Y determined by sensortype and position of sensor. + + Member methods + -------------- + tran(w, theta, kw) + + Hw = a function of frequency only (not direction) size 1 x Nf + Gwt = a function of frequency and direction size Nt x Nf + w = vector of angular frequencies in Rad/sec. Length Nf + theta = vector of directions in radians Length Nt (default 0) + ( theta = 0 -> positive x axis theta = pi/2 -> positive y axis) + Member variables + ---------------- + pos : [x,y,z], (default [0,0,0]) + vector giving coordinate position relative to [x0 y0 z0] + sensortype = string + defining the sensortype or transfer function in output. + 0, 'n' : Surface elevation (n=Eta) (default) + 1, 'n_t' : Vertical surface velocity + 2, 'n_tt' : Vertical surface acceleration + 3, 'n_x' : Surface slope in x-direction + 4, 'n_y' : Surface slope in y-direction + 5, 'n_xx' : Surface curvature in x-direction + 6, 'n_yy' : Surface curvature in y-direction + 7, 'n_xy' : Surface curvature in xy-direction + 8, 'P' : Pressure fluctuation about static MWL pressure + 9, 'U' : Water particle velocity in x-direction + 10, 'V' : Water particle velocity in y-direction + 11, 'W' : Water particle velocity in z-direction + 12, 'U_t' : Water particle acceleration in x-direction + 13, 'V_t' : Water particle acceleration in y-direction + 14, 'W_t' : Water particle acceleration in z-direction + 15, 'X_p' : Water particle displacement in x-direction from mean pos. + 16, 'Y_p' : Water particle displacement in y-direction from mean pos. + 17, 'Z_p' : Water particle displacement in z-direction from mean pos. + h : real scalar + water depth (default inf) + g : real scalar + acceleration of gravity (default 9.81 m/s**2) + rho : real scalar + water density (default 1028 kg/m**3) + bet : 1 or -1 (default 1) + 1, theta given in terms of directions toward which waves travel + -1, theta given in terms of directions from which waves come + igam : 1,2 or 3 + 1, if z is measured positive upward from mean water level (default) + 2, if z is measured positive downward from mean water level + 3, if z is measured positive upward from sea floor + thetax, thetay : real scalars + angle in degrees clockwise from true north to positive x-axis and + positive y-axis, respectively. (default theatx=90, thetay=0) + + Example + ------- + >>> import pylab as plt + >>> N=50; f0=0.1; th0=0; h=50; w0 = 2*pi*f0 + >>> t = np.linspace(0,15,N) + >>> eta0 = np.exp(-1j*w0*t) + >>> stypes = ['n', 'n_x', 'n_y']; + >>> tf = TransferFunction(pos=(0, 0, 0), h=50) + >>> vals = [] + >>> fh = plt.plot(t, eta0.real, 'r.') + >>> plt.hold(True) + >>> for i,stype in enumerate(stypes): + ... tf.sensortype = stype + ... Hw, Gwt = tf.tran(w0,th0) + ... vals.append((Hw*Gwt*eta0).real.ravel()) + + fh = plt.plot(t, vals[i]) + plt.show() + + + See also + -------- + dat2dspec, sensor_type, sensor_typeid + + Reference + --------- + Young I.R. (1994) + "On the measurement of directional spectra", + Applied Ocean Research, Vol 16, pp 283-294 + ''' + + def __init__(self, pos=(0, 0, 0), sensortype='n', h=inf, g=9.81, rho=1028, + bet=1, igam=1, thetax=90, thetay=0): + self.pos = pos + self.sensortype = sensortype if isinstance( + sensortype, str) else sensor_type(sensortype) + self.h = h + self.g = g + self.rho = rho + self.bet = bet + self.igam = igam + self.thetax = thetax + self.thetay = thetay + self._tran_dict = dict(n=self._n, n_t=self._n_t, n_tt=self._n_tt, + n_x=self._n_x, n_y=self._n_y, n_xx=self._n_xx, + n_yy=self._n_yy, n_xy=self._n_xy, + P=self._p, p=self._p, + U=self._u, u=self._u, + V=self._v, v=self._v, + W=self._w, w=self._w, + U_t=self._u_t, u_t=self._u_t, + V_t=self._v_t, v_t=self._v_t, + W_t=self._w_t, w_t=self._w_t, + X_p=self._x_p, x_p=self._x_p, + Y_p=self._y_p, y_p=self._y_p, + Z_p=self._z_p, z_p=self._z_p) + + def tran(self, w, theta=0, kw=None): + ''' + Return transfer functions based on linear wave theory + of the system with input surface elevation, + eta(x0,y0,t) = exp(i*(kx*x0+ky*y0-w*t)), + and output, + Y = Hw*Gwt*eta, determined by sensortype and position of sensor. + + Parameters + ---------- + w : array-like + vector of angular frequencies in Rad/sec. Length Nf + theta : array-like + vector of directions in radians Length Nt (default 0) + ( theta = 0 -> positive x axis theta = pi/2 -> positive y axis) + kw : array-like + vector of wave numbers corresponding to angular frequencies, w. + Length Nf (default calculated with w2k) + + Returns + ------- + Hw = transfer function of frequency only (not direction) size 1 x Nf + Gwt = transfer function of frequency and direction size Nt x Nf + + The complete transfer function Hwt = Hw*Gwt is a function of + w (columns) and theta (rows) size Nt x Nf + ''' + if kw is None: + # wave number as function of angular frequency + kw, unusedkw2 = w2k(w, 0, self.h) + + w, theta, kw = np.atleast_1d(w, theta, kw) + # make sure they have the correct orientation + theta.shape = (-1, 1) + kw.shape = (-1,) + w.shape = (-1,) + + tran_fun = self._tran_dict[self.sensortype] + Hw, Gwt = tran_fun(w, theta, kw) + + # New call to avoid singularities. pab 07.11.2000 + # Set Hw to 0 for expressions w*hyperbolic_ratio(z*k,h*k,1,-1)= 0*inf + ind = np.flatnonzero(1 - np.isfinite(Hw)) + Hw.flat[ind] = 0 + + sgn = np.sign(Hw) + k0 = np.flatnonzero(sgn < 0) + if len(k0): # make sure Hw>=0 ie. transfer negative signs to Gwt + Gwt[:, k0] = -Gwt[:, k0] + Hw[:, k0] = -Hw[:, k0] + + if self.igam == 2: + # pab 09 Oct.2002: bug fix + # Changing igam by 2 should affect the directional result in the + # same way that changing eta by -eta! + Gwt = -Gwt + return Hw, Gwt + __call__ = tran +#---Private member methods + + def _get_ee_cthxy(self, theta, kw): + # convert from angle in degrees to radians + bet = self.bet + thxr = self.thetax * pi / 180 + thyr = self.thetay * pi / 180 + + cthx = bet * cos(theta - thxr + pi / 2) + #cthy = cos(theta-thyr-pi/2) + cthy = bet * sin(theta - thyr) + + # Compute location complex exponential + x, y, unused_z = list(self.pos) + # exp(i*k(w)*(x*cos(theta)+y*sin(theta)) size Nt X Nf + ee = exp((1j * (x * cthx + y * cthy)) * kw) + return ee, cthx, cthy + + def _get_zk(self, kw): + h = self.h + z = self.pos[2] + if self.igam == 1: + # z measured positive upward from mean water level (default) + zk = kw * (h + z) + elif self.igam == 2: + # z measured positive downward from mean water level + zk = kw * (h - z) + else: + zk = kw * z # z measured positive upward from sea floor + return zk + + #--- Surface elevation --- + def _n(self, w, theta, kw): + '''n = Eta = wave profile + ''' + ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) + return np.ones_like(w), ee + + #---- Vertical surface velocity and acceleration----- + def _n_t(self, w, theta, kw): + ''' n_t = Eta_t ''' + ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) + return w, -1j * ee + + def _n_tt(self, w, theta, kw): + '''n_tt = Eta_tt''' + ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) + return w ** 2, -ee + + #--- Surface slopes --- + def _n_x(self, w, theta, kw): + ''' n_x = Eta_x = x-slope''' + ee, cthx, unused_cthy = self._get_ee_cthxy(theta, kw) + return kw, 1j * cthx * ee + + def _n_y(self, w, theta, kw): + ''' n_y = Eta_y = y-slope''' + ee, unused_cthx, cthy = self._get_ee_cthxy(theta, kw) + return kw, 1j * cthy * ee + + #--- Surface curvatures --- + def _n_xx(self, w, theta, kw): + ''' n_xx = Eta_xx = Surface curvature (x-dir)''' + ee, cthx, unused_cthy = self._get_ee_cthxy(theta, kw) + return kw ** 2, -(cthx ** 2) * ee + + def _n_yy(self, w, theta, kw): + ''' n_yy = Eta_yy = Surface curvature (y-dir)''' + ee, unused_cthx, cthy = self._get_ee_cthxy(theta, kw) + return kw ** 2, -cthy ** 2 * ee + + def _n_xy(self, w, theta, kw): + ''' n_xy = Eta_xy = Surface curvature (xy-dir)''' + ee, cthx, cthy = self._get_ee_cthxy(theta, kw) + return kw ** 2, -cthx * cthy * ee + + #--- Pressure--- + def _p(self, w, theta, kw): + ''' pressure fluctuations''' + ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) + hk = kw * self.h + zk = self._get_zk(kw) + # hyperbolic_ratio = cosh(zk)/cosh(hk) + return self.rho * self.g * hyperbolic_ratio(zk, hk, 1, 1), ee + + #---- Water particle velocities --- + def _u(self, w, theta, kw): + ''' U = x-velocity''' + ee, cthx, unused_cthy = self._get_ee_cthxy(theta, kw) + hk = kw * self.h + zk = self._get_zk(kw) + # w*cosh(zk)/sinh(hk), cos(theta)*ee + return w * hyperbolic_ratio(zk, hk, 1, -1), cthx * ee + + def _v(self, w, theta, kw): + '''V = y-velocity''' + ee, unused_cthx, cthy = self._get_ee_cthxy(theta, kw) + hk = kw * self.h + zk = self._get_zk(kw) + # w*cosh(zk)/sinh(hk), sin(theta)*ee + return w * hyperbolic_ratio(zk, hk, 1, -1), cthy * ee + + def _w(self, w, theta, kw): + ''' W = z-velocity''' + ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) + hk = kw * self.h + zk = self._get_zk(kw) + # w*sinh(zk)/sinh(hk), -? + return w * hyperbolic_ratio(zk, hk, -1, -1), -1j * ee + + #---- Water particle acceleration --- + def _u_t(self, w, theta, kw): + ''' U_t = x-acceleration''' + ee, cthx, unused_cthy = self._get_ee_cthxy(theta, kw) + hk = kw * self.h + zk = self._get_zk(kw) + # w^2*cosh(zk)/sinh(hk), ? + return (w ** 2) * hyperbolic_ratio(zk, hk, 1, -1), -1j * cthx * ee + + def _v_t(self, w, theta, kw): + ''' V_t = y-acceleration''' + ee, unused_cthx, cthy = self._get_ee_cthxy(theta, kw) + hk = kw * self.h + zk = self._get_zk(kw) + # w^2*cosh(zk)/sinh(hk), ? + return (w ** 2) * hyperbolic_ratio(zk, hk, 1, -1), -1j * cthy * ee + + def _w_t(self, w, theta, kw): + ''' W_t = z-acceleration''' + ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) + hk = kw * self.h + zk = self._get_zk(kw) + # w*sinh(zk)/sinh(hk), ? + return (w ** 2) * hyperbolic_ratio(zk, hk, -1, -1), -ee + + #---- Water particle displacement --- + def _x_p(self, w, theta, kw): + ''' X_p = x-displacement''' + ee, cthx, unused_cthy = self._get_ee_cthxy(theta, kw) + hk = kw * self.h + zk = self._get_zk(kw) + # cosh(zk)./sinh(hk), ? + return hyperbolic_ratio(zk, hk, 1, -1), 1j * cthx * ee + + def _y_p(self, w, theta, kw): + ''' Y_p = y-displacement''' + ee, unused_cthx, cthy = self._get_ee_cthxy(theta, kw) + hk = kw * self.h + zk = self._get_zk(kw) + # cosh(zk)./sinh(hk), ? + return hyperbolic_ratio(zk, hk, 1, -1), 1j * cthy * ee + + def _z_p(self, w, theta, kw): + ''' Z_p = z-displacement''' + ee, unused_cthx, unused_cthy = self._get_ee_cthxy(theta, kw) + hk = kw * self.h + zk = self._get_zk(kw) + return hyperbolic_ratio(zk, hk, -1, -1), ee # sinh(zk)./sinh(hk), ee + +# def wave_pressure(z, Hm0, h=10000, g=9.81, rho=1028): +# ''' +# Calculate pressure amplitude due to water waves. +# +# Parameters +# ---------- +# z : array-like +# depth where pressure is calculated [m] +# Hm0 : array-like +# significant wave height (same as the average of the 1/3'rd highest +# waves in a seastate. [m] +# h : real scalar +# waterdepth (default 10000 [m]) +# g : real scalar +# acceleration of gravity (default 9.81 m/s**2) +# rho : real scalar +# water density (default 1028 kg/m**3) +# +# +# Returns +# ------- +# p : ndarray +# pressure amplitude due to water waves at water depth z. [Pa] +# +# PRESSURE calculate pressure amplitude due to water waves according to +# linear theory. +# +# Example +# ----- +# >>> import pylab as plt +# >>> z = -np.linspace(10,20) +# >>> fh = plt.plot(z, wave_pressure(z, Hm0=1, h=20)) +# >>> plt.show() +# +# See also +# -------- +# w2k +# +# +# u = psweep.Fn*sqrt(mgf.length*9.81) +# z = -10; h = inf; +# Hm0 = 1.5;Tp = 4*sqrt(Hm0); +# S = jonswap([],[Hm0,Tp]); +# Hw = tran(S.w,0,[0 0 -z],'P',h) +# Sm = S; +# Sm.S = Hw.'.*S.S; +# x1 = spec2sdat(Sm,1000); +# pwave = pressure(z,Hm0,h) +# +# plot(psweep.x{1}/u, psweep.f) +# hold on +# plot(x1(1:100,1)-30,x1(1:100,2),'r') +# ''' +# +# +# Assume seastate with jonswap spectrum: +# +# Tp = 4 * np.sqrt(Hm0) +# gam = jonswap_peakfact(Hm0, Tp) +# Tm02 = Tp / (1.30301 - 0.01698 * gam + 0.12102 / gam) +# w = 2 * np.pi / Tm02 +# kw, unused_kw2 = w2k(w, 0, h) +# +# hk = kw * h +# zk1 = kw * z +# zk = hk + zk1 # z measured positive upward from mean water level (default) +# zk = hk-zk1; % z measured positive downward from mean water level +# zk1 = -zk1; +# zk = zk1; % z measured positive upward from sea floor +# +# cosh(zk)/cosh(hk) approx exp(zk) for large h +# hyperbolic_ratio(zk,hk,1,1) = cosh(zk)/cosh(hk) +# pr = np.where(np.pi < hk, np.exp(zk1), hyperbolic_ratio(zk, hk, 1, 1)) +# pr = hyperbolic_ratio(zk, hk, 1, 1) +# pressure = (rho * g * Hm0 / 2) * pr +# +## pos = [np.zeros_like(z),np.zeros_like(z),z] +## tf = TransferFunction(pos=pos, sensortype='p', h=h, rho=rho, g=g) +## Hw, Gwt = tf.tran(w,0) +## pressure2 = np.abs(Hw) * Hm0 / 2 +# +# return pressure + + +def test_docstrings(): + import doctest + print('Testing docstrings in %s' % __file__) + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) + + +def main(): + sensor_type(range(21)) +if __name__ == '__main__': + test_docstrings() diff --git a/pywafo/src/wafo/wave_theory/dispersion_relation.py b/pywafo/src/wafo/wave_theory/dispersion_relation.py index d921a4e..f05be49 100644 --- a/pywafo/src/wafo/wave_theory/dispersion_relation.py +++ b/pywafo/src/wafo/wave_theory/dispersion_relation.py @@ -1,206 +1,210 @@ -""" -Dispersion relation module --------------------------- -k2w - Translates from wave number to frequency -w2k - Translates from frequency to wave number -""" -import warnings -#import numpy as np -from numpy import (atleast_1d, sqrt, ones_like, zeros_like, arctan2, where, tanh, any, #@UnresolvedImport - sin, cos, sign, inf, flatnonzero, finfo, cosh, abs) #@UnresolvedImport - -__all__ = ['k2w', 'w2k'] - -def k2w(k1, k2=0e0, h=inf, g=9.81, u1=0e0, u2=0e0): - ''' Translates from wave number to frequency - using the dispersion relation - - Parameters - ---------- - k1 : array-like - wave numbers [rad/m]. - k2 : array-like, optional - second dimension wave number - h : real scalar, optional - water depth [m]. - g : real scalar, optional - acceleration of gravity, see gravity - u1, u2 : real scalars, optional - current velocity [m/s] along dimension 1 and 2. - note: when u1!=0 | u2!=0 then theta is not calculated correctly - - Returns - ------- - w : ndarray - angular frequency [rad/s]. - theta : ndarray - direction [rad]. - - Dispersion relation - ------------------- - w = sqrt(g*K*tanh(K*h)) ( 0 < w < inf) - theta = arctan2(k2,k1) (-pi < theta < pi) - where - K = sqrt(k1**2+k2**2) - - The shape of w and theta is the common shape of k1 and k2 according to the - numpy broadcasting rules. - - See also - -------- - w2k - - Example - ------- - >>> from numpy import arange - >>> import wafo.spectrum.dispersion_relation as wsd - >>> wsd.k2w(arange(0.01,.5,0.2))[0] - array([ 0.3132092 , 1.43530485, 2.00551739]) - >>> wsd.k2w(arange(0.01,.5,0.2),h=20)[0] - array([ 0.13914927, 1.43498213, 2.00551724]) - ''' - - k1i, k2i, hi, gi, u1i, u2i = atleast_1d(k1, k2, h, g, u1, u2) - - if k1i.size == 0: - return zeros_like(k1i) - ku1 = k1i*u1i - ku2 = k2i*u2i - - theta = arctan2(k2, k1) - - k = sqrt(k1i**2+k2i**2) - w = where(k>0, ku1+ku2+sqrt(gi*k*tanh(k*hi)), 0.0) - - cond = (w<0) - if any(cond): - txt0 = ''' - Waves and current are in opposite directions - making some of the frequencies negative. - Here we are forcing the negative frequencies to zero. - ''' - warnings.warn(txt0) - w = where(cond, 0.0, w) # force w to zero - - return w, theta - -def w2k(w, theta=0.0, h=inf, g=9.81, count_limit=100): - ''' - Translates from frequency to wave number - using the dispersion relation - - Parameters - ---------- - w : array-like - angular frequency [rad/s]. - theta : array-like, optional - direction [rad]. - h : real scalar, optional - water depth [m]. - g : real scalar or array-like of size 2. - constant of gravity [m/s**2] or 3D normalizing constant - - Returns - ------- - k1, k2 : ndarray - wave numbers [rad/m] along dimension 1 and 2. - - Description - ----------- - Uses Newton Raphson method to find the wave number k in the dispersion relation - w**2= g*k*tanh(k*h). - The solution k(w) => k1 = k(w)*cos(theta) - k2 = k(w)*sin(theta) - The size of k1,k2 is the common shape of w and theta according to numpy - broadcasting rules. If w or theta is scalar it functions as a constant - matrix of the same shape as the other. - - Example - ------- - >>> import pylab as plb - >>> import wafo.spectrum.dispersion_relation as wsd - >>> w = plb.linspace(0,3); - >>> h = plb.plot(w,w2k(w)[0]) - >>> wsd.w2k(range(4))[0] - array([ 0. , 0.1019368 , 0.4077472 , 0.91743119]) - >>> wsd.w2k(range(4),h=20)[0] - array([ 0. , 0.10503601, 0.40774726, 0.91743119]) - - >>> plb.close('all') - - See also - -------- - k2w - ''' - wi, th, hi, gi = atleast_1d(w, theta, h, g) - - if wi.size == 0: - return zeros_like(wi) - - k = 1.0*sign(wi)*wi**2.0 / gi[0] # deep water - if (hi > 10. ** 25).all(): - k2 = k*sin(th)*gi[0]/gi[-1] #size np x nf - k1 = k*cos(th) - return k1, k2 - - - if gi.size > 1: - txt0 = ''' - Finite depth in combination with 3D normalization (len(g)=2) is not implemented yet. - ''' - raise ValueError(txt0) - - - find = flatnonzero - eps = finfo(float).eps - - oshape = k.shape - wi, k, hi = wi.ravel(), k.ravel(), hi.ravel() - - # Newton's Method - # Permit no more than count_limit iterations. - hi = hi * ones_like(k) - hn = zeros_like(k) - ix = find((wi<0) | (00 and count < count_limit): - ki = k[ix] - kh = ki * hi[ix] - hn[ix] = (ki*tanh(kh)-wi[ix]**2.0/gi)/(tanh(kh)+kh/(cosh(kh)**2.0)) - knew = ki - hn[ix] - # Make sure that the current guess is not zero. - # When Newton's Method suggests steps that lead to zero guesses - # take a step 9/10ths of the way to zero: - ksmall = find(abs(knew)==0) - if ksmall.size>0: - knew[ksmall] = ki[ksmall] / 10.0 - hn[ix[ksmall]] = ki[ksmall]-knew[ksmall] - - k[ix] = knew - # disp(['Iteration ',num2str(count),' Number of points left: ' num2str(length(ix)) ]), - - ix = find((abs(hn) > sqrt(eps)*abs(k)) * abs(hn) > sqrt(eps)) - count += 1 - - if count == count_limit: - txt1 = ''' W2K did not converge. - The maximum error in the last step was: %13.8f''' % max(hn[ix]) - warnings.warn(txt1) - - k.shape = oshape - - k2 = k*sin(th) - k1 = k*cos(th) - return k1, k2 - -def main(): - import doctest - doctest.testmod() - -if __name__ == '__main__': - main() \ No newline at end of file +""" +Dispersion relation module +-------------------------- +k2w - Translates from wave number to frequency +w2k - Translates from frequency to wave number +""" +import warnings +#import numpy as np +from numpy import (atleast_1d, sqrt, ones_like, zeros_like, arctan2, where, + tanh, any, sin, cos, sign, inf, + flatnonzero, finfo, cosh, abs) + +__all__ = ['k2w', 'w2k'] + + +def k2w(k1, k2=0e0, h=inf, g=9.81, u1=0e0, u2=0e0): + ''' Translates from wave number to frequency + using the dispersion relation + + Parameters + ---------- + k1 : array-like + wave numbers [rad/m]. + k2 : array-like, optional + second dimension wave number + h : real scalar, optional + water depth [m]. + g : real scalar, optional + acceleration of gravity, see gravity + u1, u2 : real scalars, optional + current velocity [m/s] along dimension 1 and 2. + note: when u1!=0 | u2!=0 then theta is not calculated correctly + + Returns + ------- + w : ndarray + angular frequency [rad/s]. + theta : ndarray + direction [rad]. + + Dispersion relation + ------------------- + w = sqrt(g*K*tanh(K*h)) ( 0 < w < inf) + theta = arctan2(k2,k1) (-pi < theta < pi) + where + K = sqrt(k1**2+k2**2) + + The shape of w and theta is the common shape of k1 and k2 according to the + numpy broadcasting rules. + + See also + -------- + w2k + + Example + ------- + >>> from numpy import arange + >>> import wafo.wave_theory.dispersion_relation as wsd + >>> wsd.k2w(arange(0.01,.5,0.2))[0] + array([ 0.3132092 , 1.43530485, 2.00551739]) + >>> wsd.k2w(arange(0.01,.5,0.2),h=20)[0] + array([ 0.13914927, 1.43498213, 2.00551724]) + ''' + + k1i, k2i, hi, gi, u1i, u2i = atleast_1d(k1, k2, h, g, u1, u2) + + if k1i.size == 0: + return zeros_like(k1i) + ku1 = k1i * u1i + ku2 = k2i * u2i + + theta = arctan2(k2, k1) + + k = sqrt(k1i ** 2 + k2i ** 2) + w = where(k > 0, ku1 + ku2 + sqrt(gi * k * tanh(k * hi)), 0.0) + + cond = (w < 0) + if any(cond): + txt0 = ''' + Waves and current are in opposite directions + making some of the frequencies negative. + Here we are forcing the negative frequencies to zero. + ''' + warnings.warn(txt0) + w = where(cond, 0.0, w) # force w to zero + + return w, theta + + +def w2k(w, theta=0.0, h=inf, g=9.81, count_limit=100): + ''' + Translates from frequency to wave number + using the dispersion relation + + Parameters + ---------- + w : array-like + angular frequency [rad/s]. + theta : array-like, optional + direction [rad]. + h : real scalar, optional + water depth [m]. + g : real scalar or array-like of size 2. + constant of gravity [m/s**2] or 3D normalizing constant + + Returns + ------- + k1, k2 : ndarray + wave numbers [rad/m] along dimension 1 and 2. + + Description + ----------- + Uses Newton Raphson method to find the wave number k in the dispersion + relation + w**2= g*k*tanh(k*h). + The solution k(w) => k1 = k(w)*cos(theta) + k2 = k(w)*sin(theta) + The size of k1,k2 is the common shape of w and theta according to numpy + broadcasting rules. If w or theta is scalar it functions as a constant + matrix of the same shape as the other. + + Example + ------- + >>> import pylab as plb + >>> import wafo.wave_theory.dispersion_relation as wsd + >>> w = plb.linspace(0,3); + >>> h = plb.plot(w,w2k(w)[0]) + >>> wsd.w2k(range(4))[0] + array([ 0. , 0.1019368 , 0.4077472 , 0.91743119]) + >>> wsd.w2k(range(4),h=20)[0] + array([ 0. , 0.10503601, 0.40774726, 0.91743119]) + + >>> plb.close('all') + + See also + -------- + k2w + ''' + wi, th, hi, gi = atleast_1d(w, theta, h, g) + + if wi.size == 0: + return zeros_like(wi) + + k = 1.0 * sign(wi) * wi ** 2.0 / gi[0] # deep water + if (hi > 10. ** 25).all(): + k2 = k * sin(th) * gi[0] / gi[-1] # size np x nf + k1 = k * cos(th) + return k1, k2 + + if gi.size > 1: + raise ValueError('Finite depth in combination with 3D normalization' + + ' (len(g)=2) is not implemented yet.') + + find = flatnonzero + eps = finfo(float).eps + + oshape = k.shape + wi, k, hi = wi.ravel(), k.ravel(), hi.ravel() + + # Newton's Method + # Permit no more than count_limit iterations. + hi = hi * ones_like(k) + hn = zeros_like(k) + ix = find((wi < 0) | (0 < wi)) + + # Break out of the iteration loop for three reasons: + # 1) the last update is very small (compared to x) + # 2) the last update is very small (compared to sqrt(eps)) + # 3) There are more than 100 iterations. This should NEVER happen. + count = 0 + while (ix.size > 0 and count < count_limit): + ki = k[ix] + kh = ki * hi[ix] + hn[ix] = (ki * tanh(kh) - wi[ix] ** 2.0 / gi) / \ + (tanh(kh) + kh / (cosh(kh) ** 2.0)) + knew = ki - hn[ix] + # Make sure that the current guess is not zero. + # When Newton's Method suggests steps that lead to zero guesses + # take a step 9/10ths of the way to zero: + ksmall = find(abs(knew) == 0) + if ksmall.size > 0: + knew[ksmall] = ki[ksmall] / 10.0 + hn[ix[ksmall]] = ki[ksmall] - knew[ksmall] + + k[ix] = knew + # disp(['Iteration ',num2str(count),' Number of points left: ' + # num2str(length(ix)) ]), + + ix = find((abs(hn) > sqrt(eps) * abs(k)) * abs(hn) > sqrt(eps)) + count += 1 + + if count == count_limit: + warnings.warn('W2K did not converge. The maximum error in the ' + + 'last step was: %13.8f' % max(hn[ix])) + + k.shape = oshape + + k2 = k * sin(th) + k1 = k * cos(th) + return k1, k2 + + +def test_docstrings(): + import doctest + print('Testing docstrings in %s' % __file__) + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) + + +if __name__ == '__main__': + test_docstrings() diff --git a/pywafo/src/wafo/wave_theory/test/test_dispersion_relation.py b/pywafo/src/wafo/wave_theory/test/test_dispersion_relation.py index a5018d4..4a92fa3 100644 --- a/pywafo/src/wafo/wave_theory/test/test_dispersion_relation.py +++ b/pywafo/src/wafo/wave_theory/test/test_dispersion_relation.py @@ -1,31 +1,35 @@ -''' -Created on 19. juli 2010 - -@author: pab -''' -import numpy as np -from wafo.wave_theory.dispersion_relation import w2k,k2w #@UnusedImport - -def test_k2w_infinite_water_depth(): - vals = k2w(np.arange(0.01,.5,0.2))[0] - true_vals = np.array([ 0.3132092 , 1.43530485, 2.00551739]) - assert((np.abs(vals-true_vals)<1e-7).all()) - -def test_k2w_finite_water_depth(): - vals = k2w(np.arange(0.01,.5,0.2),h=20)[0] - true_vals = np.array([ 0.13914927, 1.43498213, 2.00551724]) - assert((np.abs(vals-true_vals)<1e-7).all()) - -def test_w2k_infinite_water_depth(): - vals = w2k(range(4))[0] - true_vals = np.array([ 0. , 0.1019368 , 0.4077472 , 0.91743119]) - assert((np.abs(vals-true_vals)<1e-7).all()) - -def test_w2k_finite_water_depth(): - vals = w2k(range(4),h=20)[0] - true_vals = np.array([ 0. , 0.10503601, 0.40774726, 0.91743119]) - assert((np.abs(vals-true_vals)<1e-7).all()) - -if __name__ == '__main__': - import nose - nose.run() \ No newline at end of file +''' +Created on 19. juli 2010 + +@author: pab +''' +import numpy as np +from wafo.wave_theory.dispersion_relation import w2k, k2w # @UnusedImport + + +def test_k2w_infinite_water_depth(): + vals = k2w(np.arange(0.01, .5, 0.2))[0] + true_vals = np.array([0.3132092, 1.43530485, 2.00551739]) + assert((np.abs(vals - true_vals) < 1e-7).all()) + + +def test_k2w_finite_water_depth(): + vals = k2w(np.arange(0.01, .5, 0.2), h=20)[0] + true_vals = np.array([0.13914927, 1.43498213, 2.00551724]) + assert((np.abs(vals - true_vals) < 1e-7).all()) + + +def test_w2k_infinite_water_depth(): + vals = w2k(range(4))[0] + true_vals = np.array([0., 0.1019368, 0.4077472, 0.91743119]) + assert((np.abs(vals - true_vals) < 1e-7).all()) + + +def test_w2k_finite_water_depth(): + vals = w2k(range(4), h=20)[0] + true_vals = np.array([0., 0.10503601, 0.40774726, 0.91743119]) + assert((np.abs(vals - true_vals) < 1e-7).all()) + +if __name__ == '__main__': + import nose + nose.run()