From e732dd35b0e1dc167b29dbeebee1e0c28b6b96ab Mon Sep 17 00:00:00 2001
From: madsmpedersen <m@madsp.dk>
Date: Mon, 9 Nov 2015 09:47:53 +0100
Subject: [PATCH] reorganized the fatigue module

---
 README.md                                     |   1 +
 wetb/fatigue_tools/fatigue.py                 | 219 ++----------------
 .../rainflowcounting/__init__.py              |   0
 .../fatigue_tools/rainflowcounting/compile.py |   7 +
 .../{ => rainflowcounting}/pair_range.py      |   0
 .../{ => rainflowcounting}/pair_range.pyd     | Bin 39936 -> 39936 bytes
 .../{ => rainflowcounting}/peak_trough.py     |   0
 .../{ => rainflowcounting}/peak_trough.pyd    | Bin 37888 -> 37888 bytes
 .../rainflowcounting/rainflowcount.py         | 126 ++++++++++
 .../rainflowcount_astm.py                     |   0
 .../rainflowcount_astm.pyd                    | Bin 45056 -> 45056 bytes
 .../rainflowcounting/rfc_hist.py              |  49 ++++
 12 files changed, 208 insertions(+), 194 deletions(-)
 create mode 100644 wetb/fatigue_tools/rainflowcounting/__init__.py
 create mode 100644 wetb/fatigue_tools/rainflowcounting/compile.py
 rename wetb/fatigue_tools/{ => rainflowcounting}/pair_range.py (100%)
 rename wetb/fatigue_tools/{ => rainflowcounting}/pair_range.pyd (71%)
 rename wetb/fatigue_tools/{ => rainflowcounting}/peak_trough.py (100%)
 rename wetb/fatigue_tools/{ => rainflowcounting}/peak_trough.pyd (99%)
 create mode 100644 wetb/fatigue_tools/rainflowcounting/rainflowcount.py
 rename wetb/fatigue_tools/{ => rainflowcounting}/rainflowcount_astm.py (100%)
 rename wetb/fatigue_tools/{ => rainflowcounting}/rainflowcount_astm.pyd (99%)
 create mode 100644 wetb/fatigue_tools/rainflowcounting/rfc_hist.py

diff --git a/README.md b/README.md
index 1900083..2365c88 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,7 @@ General Time Series Data Format, a binary hdf5 data format for storing time seri
 
 ### [fatigue_tools](wetb/fatigue_tools)
 - [fatigue](wetb/fatigue_tools/fatigue.py): Rainflow counting, cycle matrix and equvivalent loads
+- [bearing_damage](wetb/fatigue_tools/bearing_damage.py): Calculate a comparable measure of bearing damage
 
 ### [wind](wetb/wind)
 - [shear](wetb/wind/shear.py): Calculate and fit wind shear 
diff --git a/wetb/fatigue_tools/fatigue.py b/wetb/fatigue_tools/fatigue.py
index 0c7425f..a411a16 100644
--- a/wetb/fatigue_tools/fatigue.py
+++ b/wetb/fatigue_tools/fatigue.py
@@ -2,199 +2,23 @@
 Created on 04/03/2013
 @author: mmpe
 
-'rainflow_windap' and 'rainflow_astm' are two different methods to for rainflow counting
+
 'eq_load' calculate equivalent loads using one of the two rain flow counting methods
 'cycle_matrix' calculates a matrix of cycles (binned on amplitude and mean value)
 'eq_load_and_cycles' is used to calculate eq_loads of multiple time series (e.g. life time equivalent load)
 
+The methods uses the rainflow counting routines (See documentation in top of methods):
+- 'rainflow_windap': (Described in "Recommended Practices for Wind Turbine Testing - 3. Fatigue Loads",
+                      2. edition 1990, Appendix A)
+or
+- 'rainflow_astm' (based on the c-implementation by Adam Nieslony found at the MATLAB Central File Exchange
+                   http://www.mathworks.com/matlabcentral/fileexchange/3026)
 '''
-
-#try:
-#    """
-#    The cython_import function compiles modules using cython.
-#    It is found at: https://github.com/madsmpedersen/MMPE/blob/master/cython_compile/cython_compile.py
-#    """
-#    from mmpe.cython_compile.cython_compile import cython_import
-#except ImportError:
-#    cython_import = __import__
 import numpy as np
+from wetb.fatigue_tools.rainflowcounting import rainflowcount
 
-
-def rfc_hist(sig_rf, nrbins=46):
-    """
-    Histogram of rainflow counted cycles
-    ====================================
-
-    hist, bin_edges, bin_avg = rfc_hist(sig, nrbins=46)
-
-    Divide the rainflow counted cycles of a signal into equally spaced bins.
-
-    Created on Wed Feb 16 16:53:18 2011
-    @author: David Verelst
-    Modified 10.10.2011 by Mads M Pedersen to elimintate __copy__ and __eq__
-
-    Parameters
-    ----------
-    sig_rf : array-like
-        As output by rfc_astm or rainflow
-
-    nrbins : int, optional
-        Divide the rainflow counted amplitudes in a number of equally spaced
-        bins.
-
-    Returns
-    -------
-    hist : array-like
-        Counted rainflow cycles per bin, has nrbins elements
-
-    bin_edges : array-like
-        Edges of the bins, has nrbins+1 elements.
-
-    bin_avg : array-like
-        Average rainflow cycle amplitude per bin, has nrbins elements.
-    """
-
-    rf_half = sig_rf
-
-    # the Matlab approach is to divide into 46 bins
-    bin_edges = np.linspace(0, 1, num=nrbins + 1) * rf_half.max()
-    hist = np.histogram(rf_half, bins=bin_edges)[0]
-    # calculate the average per bin
-    hist_sum = np.histogram(rf_half, weights=rf_half, bins=bin_edges)[0]
-    # replace zeros with one, to avoid 0/0
-    hist_ = hist.copy()
-    hist_[(hist == 0).nonzero()] = 1.0
-    # since the sum is also 0, the avg remains zero for those whos hist is zero
-    bin_avg = hist_sum / hist_
-
-    return hist, bin_edges, bin_avg
-
-
-def check_signal(signal):
-    # check input data validity
-    if not type(signal).__name__ == 'ndarray':
-        raise TypeError('signal must be ndarray, not: ' + type(signal).__name__)
-
-    elif len(signal.shape) not in (1, 2):
-        raise TypeError('signal must be 1D or 2D, not: ' + str(len(signal.shape)))
-
-    if len(signal.shape) == 2:
-        if signal.shape[1] > 1:
-            raise TypeError('signal must have one column only, not: ' + str(signal.shape[1]))
-    if np.min(signal) == np.max(signal):
-        raise TypeError("Signal contains no variation")
-
-
-def rainflow_windap(signal, levels=255., thresshold=(255 / 50)):
-    """Windap equivalent rainflow counting
-
-
-    Calculate the amplitude and mean values of half cycles in signal
-
-    This algorithms used by this routine is implemented directly as described in
-    "Recommended Practices for Wind Turbine Testing - 3. Fatigue Loads", 2. edition 1990, Appendix A
-
-    Parameters
-    ----------
-    Signal : array-like
-        The raw signal
-
-    levels : int, optional
-        The signal is discretize into this number of levels.
-        255 is equivalent to the implementation in Windap
-
-    thresshold : int, optional
-        Cycles smaller than this thresshold are ignored
-        255/50 is equivalent to the implementation in Windap
-
-    Returns
-    -------
-    ampl : array-like
-        Peak to peak amplitudes of the half cycles
-
-    mean : array-like
-        Mean values of the half cycles
-
-
-    Example
-    -------
-    >>> signal = np.array([-2.0, 0.0, 1.0, 0.0, -3.0, 0.0, 5.0, 0.0, -1.0, 0.0, 3.0, 0.0, -4.0, 0.0, 4.0, 0.0, -2.0])
-    >>> ampl, mean = rainflow_windap(signal)
-    """
-    check_signal(signal)
-    #type <double> is required by <find_extreme> and <rainflow>
-    signal = signal.astype(np.double)
-
-    offset = np.nanmin(signal)
-    signal -= offset
-    if np.nanmax(signal) > 0:
-        gain = np.nanmax(signal) / levels
-        signal = signal / gain
-        signal = np.round(signal).astype(np.int)
-
-        from wetb.fatigue_tools.peak_trough import peak_trough
-
-
-        #Convert to list of local minima/maxima where difference > thresshold
-        sig_ext = peak_trough(signal, thresshold)
-
-        from wetb.fatigue_tools.pair_range import pair_range_amplitude_mean
-
-        #rainflow count
-        ampl_mean = pair_range_amplitude_mean(sig_ext)
-
-        ampl_mean = np.array(ampl_mean)
-        ampl_mean = np.round(ampl_mean / thresshold) * gain * thresshold
-        ampl_mean[:, 1] += offset
-        return ampl_mean.T
-
-
-
-def rainflow_astm(signal):
-    """Matlab equivalent rainflow counting
-
-    Calculate the amplitude and mean values of half cycles in signal
-
-    This implemementation is based on the c-implementation by Adam Nieslony found at
-    the MATLAB Central File Exchange http://www.mathworks.com/matlabcentral/fileexchange/3026
-
-    Parameters
-    ----------
-    Signal : array-like
-        The raw signal
-
-    Returns
-    -------
-    ampl : array-like
-        peak to peak amplitudes of the half cycles (note that the matlab implementation
-        uses peak amplitude instead of peak to peak)
-
-    mean : array-like
-        Mean values of the half cycles
-
-
-    Examples
-    --------
-    >>> signal = np.array([-2.0, 0.0, 1.0, 0.0, -3.0, 0.0, 5.0, 0.0, -1.0, 0.0, 3.0, 0.0, -4.0, 0.0, 4.0, 0.0, -2.0])
-    >>> ampl, mean = rainflow_astm(signal)
-    """
-    check_signal(signal)
-
-    # type <double> is reuqired by <find_extreme> and <rainflow>
-    signal = signal.astype(np.double)
-
-    # Import find extremes and rainflow.
-    # If possible the module is compiled using cython otherwise the python implementation is used
-    #cython_import('fatigue_tools.rainflowcount_astm')
-    from wetb.fatigue_tools.rainflowcount_astm import find_extremes, rainflowcount
-
-    # Remove points which is not local minimum/maximum
-    sig_ext = find_extremes(signal)
-
-    # rainflow count
-    ampl_mean = np.array(rainflowcount(sig_ext))
-
-    return np.array(ampl_mean).T
+rainflow_windap = rainflowcount.rainflow_windap
+rainflow_astm = rainflowcount.rainflow_astm
 
 
 def eq_load(signals, no_bins=46, m=[3, 4, 6, 8, 10, 12], neq=1, rainflow_func=rainflow_windap):
@@ -239,14 +63,6 @@ def eq_load(signals, no_bins=46, m=[3, 4, 6, 8, 10, 12], neq=1, rainflow_func=ra
     except TypeError:
         return [[np.nan] * len(np.atleast_1d(m))] * len(np.atleast_1d(neq))
 
-#    ampl, _ = rainflow_func(signals)
-#    if ampl is None:
-#        return []
-#    hist_data, x, bin_avg = rfc_hist(ampl, no_bins)
-#
-#    m = np.atleast_1d(m)
-#
-#    return np.array([np.power(np.sum(0.5 * hist_data * np.power(bin_avg, m[i])) / neq, 1. / m[i]) for i in range(len(m))])
 
 
 def eq_load_and_cycles(signals, no_bins=46, m=[3, 4, 6, 8, 10, 12], neq=[10 ** 6, 10 ** 7, 10 ** 8], rainflow_func=rainflow_windap):
@@ -354,3 +170,18 @@ def cycle_matrix(signals, ampl_bins=10, mean_bins=10, rainflow_func=rainflow_win
     return cycles, ampl_bin_mean, ampl_edges, mean_bin_mean, mean_edges
 
 
+if __name__ == "__main__":
+    signal1 = np.array([-2.0, 0.0, 1.0, 0.0, -3.0, 0.0, 5.0, 0.0, -1.0, 0.0, 3.0, 0.0, -4.0, 0.0, 4.0, 0.0, -2.0])
+    signal2 = signal1 * 1.1
+
+    # equivalent load for default wöhler slopes
+    print (eq_load(signal1, no_bins=50, neq=17, rainflow_func=rainflow_windap))
+    print (eq_load(signal1, no_bins=50, neq=17, rainflow_func=rainflow_astm))
+
+    # Cycle matrix with 4 amplitude bins and 4 mean value bins
+    print (cycle_matrix(signal1, 4, 4, rainflow_func=rainflow_windap))
+    print (cycle_matrix(signal1, 4, 4, rainflow_func=rainflow_astm))
+
+    # Cycle matrix where signal1 and signal2 contributes with 50% each
+    print (cycle_matrix([(.5, signal1), (.5, signal2)], 4, 8, rainflow_func=rainflow_astm))
+
diff --git a/wetb/fatigue_tools/rainflowcounting/__init__.py b/wetb/fatigue_tools/rainflowcounting/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/wetb/fatigue_tools/rainflowcounting/compile.py b/wetb/fatigue_tools/rainflowcounting/compile.py
new file mode 100644
index 0000000..2666310
--- /dev/null
+++ b/wetb/fatigue_tools/rainflowcounting/compile.py
@@ -0,0 +1,7 @@
+import sys
+sys.path.append("../../../../MMPE/")
+from mmpe.cython_compile.cython_compile import cython_import
+
+cython_import('pair_range')
+cython_import('peak_trough')
+cython_import('rainflowcount_astm')
diff --git a/wetb/fatigue_tools/pair_range.py b/wetb/fatigue_tools/rainflowcounting/pair_range.py
similarity index 100%
rename from wetb/fatigue_tools/pair_range.py
rename to wetb/fatigue_tools/rainflowcounting/pair_range.py
diff --git a/wetb/fatigue_tools/pair_range.pyd b/wetb/fatigue_tools/rainflowcounting/pair_range.pyd
similarity index 71%
rename from wetb/fatigue_tools/pair_range.pyd
rename to wetb/fatigue_tools/rainflowcounting/pair_range.pyd
index e3e28cb4e3bb7ab0fc866c3ab7d1ec242458abdb..30f8baed7a441394607bf0e5759a496a08e83a6d 100644
GIT binary patch
delta 3984
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

delta 3962
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
Oat56vPTzoR2KgUhb0EI}

diff --git a/wetb/fatigue_tools/peak_trough.py b/wetb/fatigue_tools/rainflowcounting/peak_trough.py
similarity index 100%
rename from wetb/fatigue_tools/peak_trough.py
rename to wetb/fatigue_tools/rainflowcounting/peak_trough.py
diff --git a/wetb/fatigue_tools/peak_trough.pyd b/wetb/fatigue_tools/rainflowcounting/peak_trough.pyd
similarity index 99%
rename from wetb/fatigue_tools/peak_trough.pyd
rename to wetb/fatigue_tools/rainflowcounting/peak_trough.pyd
index db4087cd89878143c3de4f75a60b5026f5dceb1f..250f85f0e8de0b2c2fe387e0175e92e9c8415cf7 100644
GIT binary patch
delta 27
gcmZoz!PKyVX~G8<^Js^#jbA?GfEk-#<VvUk0Hak4bN~PV

delta 27
gcmZoz!PKyVX~GAVW#4T=Hh%e#17>V~kt?AF0KLEsegFUf

diff --git a/wetb/fatigue_tools/rainflowcounting/rainflowcount.py b/wetb/fatigue_tools/rainflowcounting/rainflowcount.py
new file mode 100644
index 0000000..f05a1a0
--- /dev/null
+++ b/wetb/fatigue_tools/rainflowcounting/rainflowcount.py
@@ -0,0 +1,126 @@
+import numpy as np
+def check_signal(signal):
+    # check input data validity
+    if not type(signal).__name__ == 'ndarray':
+        raise TypeError('signal must be ndarray, not: ' + type(signal).__name__)
+
+    elif len(signal.shape) not in (1, 2):
+        raise TypeError('signal must be 1D or 2D, not: ' + str(len(signal.shape)))
+
+    if len(signal.shape) == 2:
+        if signal.shape[1] > 1:
+            raise TypeError('signal must have one column only, not: ' + str(signal.shape[1]))
+    if np.min(signal) == np.max(signal):
+        raise TypeError("Signal contains no variation")
+
+
+def rainflow_windap(signal, levels=255., thresshold=(255 / 50)):
+    """Windap equivalent rainflow counting
+
+
+    Calculate the amplitude and mean values of half cycles in signal
+
+    This algorithms used by this routine is implemented directly as described in
+    "Recommended Practices for Wind Turbine Testing - 3. Fatigue Loads", 2. edition 1990, Appendix A
+
+    Parameters
+    ----------
+    Signal : array-like
+        The raw signal
+
+    levels : int, optional
+        The signal is discretize into this number of levels.
+        255 is equivalent to the implementation in Windap
+
+    thresshold : int, optional
+        Cycles smaller than this thresshold are ignored
+        255/50 is equivalent to the implementation in Windap
+
+    Returns
+    -------
+    ampl : array-like
+        Peak to peak amplitudes of the half cycles
+
+    mean : array-like
+        Mean values of the half cycles
+
+
+    Example
+    -------
+    >>> signal = np.array([-2.0, 0.0, 1.0, 0.0, -3.0, 0.0, 5.0, 0.0, -1.0, 0.0, 3.0, 0.0, -4.0, 0.0, 4.0, 0.0, -2.0])
+    >>> ampl, mean = rainflow_windap(signal)
+    """
+    check_signal(signal)
+    #type <double> is required by <find_extreme> and <rainflow>
+    signal = signal.astype(np.double)
+
+    offset = np.nanmin(signal)
+    signal -= offset
+    if np.nanmax(signal) > 0:
+        gain = np.nanmax(signal) / levels
+        signal = signal / gain
+        signal = np.round(signal).astype(np.int)
+
+        from wetb.fatigue_tools.rainflowcounting.peak_trough import peak_trough
+
+
+        #Convert to list of local minima/maxima where difference > thresshold
+        sig_ext = peak_trough(signal, thresshold)
+
+        from wetb.fatigue_tools.rainflowcounting.pair_range import pair_range_amplitude_mean
+
+        #rainflow count
+        ampl_mean = pair_range_amplitude_mean(sig_ext)
+
+        ampl_mean = np.array(ampl_mean)
+        ampl_mean = np.round(ampl_mean / thresshold) * gain * thresshold
+        ampl_mean[:, 1] += offset
+        return ampl_mean.T
+
+
+
+def rainflow_astm(signal):
+    """Matlab equivalent rainflow counting
+
+    Calculate the amplitude and mean values of half cycles in signal
+
+    This implemementation is based on the c-implementation by Adam Nieslony found at
+    the MATLAB Central File Exchange http://www.mathworks.com/matlabcentral/fileexchange/3026
+
+    Parameters
+    ----------
+    Signal : array-like
+        The raw signal
+
+    Returns
+    -------
+    ampl : array-like
+        peak to peak amplitudes of the half cycles (note that the matlab implementation
+        uses peak amplitude instead of peak to peak)
+
+    mean : array-like
+        Mean values of the half cycles
+
+
+    Examples
+    --------
+    >>> signal = np.array([-2.0, 0.0, 1.0, 0.0, -3.0, 0.0, 5.0, 0.0, -1.0, 0.0, 3.0, 0.0, -4.0, 0.0, 4.0, 0.0, -2.0])
+    >>> ampl, mean = rainflow_astm(signal)
+    """
+    check_signal(signal)
+
+    # type <double> is reuqired by <find_extreme> and <rainflow>
+    signal = signal.astype(np.double)
+
+    # Import find extremes and rainflow.
+    # If possible the module is compiled using cython otherwise the python implementation is used
+    #cython_import('fatigue_tools.rainflowcount_astm')
+    from wetb.fatigue_tools.rainflowcounting.rainflowcount_astm import find_extremes, rainflowcount
+
+    # Remove points which is not local minimum/maximum
+    sig_ext = find_extremes(signal)
+
+    # rainflow count
+    ampl_mean = np.array(rainflowcount(sig_ext))
+
+    return np.array(ampl_mean).T
\ No newline at end of file
diff --git a/wetb/fatigue_tools/rainflowcount_astm.py b/wetb/fatigue_tools/rainflowcounting/rainflowcount_astm.py
similarity index 100%
rename from wetb/fatigue_tools/rainflowcount_astm.py
rename to wetb/fatigue_tools/rainflowcounting/rainflowcount_astm.py
diff --git a/wetb/fatigue_tools/rainflowcount_astm.pyd b/wetb/fatigue_tools/rainflowcounting/rainflowcount_astm.pyd
similarity index 99%
rename from wetb/fatigue_tools/rainflowcount_astm.pyd
rename to wetb/fatigue_tools/rainflowcounting/rainflowcount_astm.pyd
index 6ef6cd427dfb019332137805ebe4db80d90dcbc8..e985824ec83011c3b893870352cdef19cba7a582 100644
GIT binary patch
delta 27
gcmZp8z|`=7X~G8<t7wO?jb9!#gBhD|v}kAp0ItOh4FCWD

delta 27
gcmZp8z|`=7X~G8<hPRO+8^1hg1~WF_XwlFH0J#1P>i_@%

diff --git a/wetb/fatigue_tools/rainflowcounting/rfc_hist.py b/wetb/fatigue_tools/rainflowcounting/rfc_hist.py
new file mode 100644
index 0000000..d4c7fb0
--- /dev/null
+++ b/wetb/fatigue_tools/rainflowcounting/rfc_hist.py
@@ -0,0 +1,49 @@
+import numpy as np
+def rfc_hist(sig_rf, nrbins=46):
+    """
+    Histogram of rainflow counted cycles
+    ====================================
+
+    hist, bin_edges, bin_avg = rfc_hist(sig, nrbins=46)
+
+    Divide the rainflow counted cycles of a signal into equally spaced bins.
+
+    Created on Wed Feb 16 16:53:18 2011
+    @author: David Verelst
+    Modified 10.10.2011 by Mads M Pedersen to elimintate __copy__ and __eq__
+
+    Parameters
+    ----------
+    sig_rf : array-like
+        As output by rfc_astm or rainflow
+
+    nrbins : int, optional
+        Divide the rainflow counted amplitudes in a number of equally spaced
+        bins.
+
+    Returns
+    -------
+    hist : array-like
+        Counted rainflow cycles per bin, has nrbins elements
+
+    bin_edges : array-like
+        Edges of the bins, has nrbins+1 elements.
+
+    bin_avg : array-like
+        Average rainflow cycle amplitude per bin, has nrbins elements.
+    """
+
+    rf_half = sig_rf
+
+    # the Matlab approach is to divide into 46 bins
+    bin_edges = np.linspace(0, 1, num=nrbins + 1) * rf_half.max()
+    hist = np.histogram(rf_half, bins=bin_edges)[0]
+    # calculate the average per bin
+    hist_sum = np.histogram(rf_half, weights=rf_half, bins=bin_edges)[0]
+    # replace zeros with one, to avoid 0/0
+    hist_ = hist.copy()
+    hist_[(hist == 0).nonzero()] = 1.0
+    # since the sum is also 0, the avg remains zero for those whos hist is zero
+    bin_avg = hist_sum / hist_
+
+    return hist, bin_edges, bin_avg
\ No newline at end of file
-- 
GitLab