def _compute_del(ts, slope, elapsed, **kwargs):
        """
        Computes damage equivalent load of input `ts`.

        Parameters
        ----------
        ts : np.array
            Time series to calculate DEL for.
        slope : int | float
            Slope of the fatigue curve.
        elapsed : int | float
            Elapsed time of the time series.
        rainflow_bins : int
            Number of bins used in rainflow analysis.
            Default: 100
        """

        bins = kwargs.get("rainflow_bins", 100)

        ranges = fatpack.find_rainflow_ranges(ts)
        Nrf, Srf = fatpack.find_range_count(ranges, 100)
        DELs = Srf**slope * Nrf / elapsed
        DEL = DELs.sum()**(1 / slope)

        return DEL
Beispiel #2
0
def fatigueLoad(foil_load_vertical, FN_interpolated, k=None, foilmodule=None, outputfolder=None, n_years=None, V=None):
    
    # Set global variables:
    global curve, category, S, fatigue_damage, path_main, y, df, li_img, li_fd

    # System paths
    # path_main = db_location +  "\Teknisk utvikling\\" + foilmodule[0:2] + ' ' + foilmodule[2:] + "\FEM ANALYSE\RAPPORT"
    # os.chdir(path_main)
    # print(path_main)

    # df = pd.read_excel(path_main + '\\Input til FATIGUE.xlsx', sheet_name=foilmodule)
    # print (df.iloc[:, [0,1]])
 
     # Generates overview of the loads acting on the foil:
    #%%
    reversals, reversals_ix = fatpack.find_reversals(foil_load_vertical)#, k)
    cycles, residue = fatpack.find_rainflow_cycles(reversals)
    processed_residue = fatpack.concatenate_reversals(residue, residue)
    cycles_residue, _ = fatpack.find_rainflow_cycles(processed_residue)
    cycles_total = np.concatenate((cycles, cycles_residue))
    
    # Find the rainflow ranges from the cycles
    force_range = fatpack.find_rainflow_ranges(foil_load_vertical)#, k)
    
    figsize = np.array([140., 140.]) / 10
    # fig = plt.figure(dpi=180, figsize=figsize)
    # fig.suptitle("Foil load data:", fontsize=16)
    
    # Plotting signal with reversals.
    # ax_signal = plt.subplot2grid((3, 2), (0, 0))
    # ax_signal.plot(foil_load_vertical/1000)
    # ax_signal.plot(reversals_ix, foil_load_vertical[reversals_ix]/1000, 'ro', fillstyle='none',
    #                 label='reversal')
    # # ax_signal.legend()
    # ax_signal.set(title="Signal", ylabel="Foil normal force [kN]", xlabel="Index", xlim=[0, 5000])
    
    # Plotting the cumulative distribution of the cycle count
    # ax_cumdist = plt.subplot2grid((3, 2), (1, 0))
    N, force_distribution = fatpack.find_range_count(force_range, 25)
    Ncum = N.sum() - np.cumsum(N)
    # ax_cumdist.semilogx(Ncum, force_distribution/1000)
    # ax_cumdist.set(title="Cumulative distribution, rainflow ranges",
    #                xlabel="Count, N", ylabel="Foil normal force range [kN]")
    
    # Interpolate for a fixed range of forces
    df_FN_interpolated = DataFrame (FN_interpolated,columns=['Foil normal force range [N]'])   
    f = interpolate.interp1d(force_distribution, Ncum, fill_value=0, bounds_error=False)
    Ncum_normalized = np.round(f(FN_interpolated))     
    df_Ncum = DataFrame (Ncum_normalized,columns=['Number of cycles simulated [-]'])   

    # Save distribution to excel file
    filename= outputfolder + '\FOIL FORCE STATISTICS ' + str(int(V/0.5144)) + ' knots.xlsx'
    append2excel.append_df_to_excel(filename, df_FN_interpolated, sheet_name='Sheet1', startcol=1, startrow = 5, truncate_sheet=None, index=False)

    append2excel.append_df_to_excel(filename, df_Ncum, sheet_name='Sheet1', startcol=0, startrow = 5,  truncate_sheet=None, index=False)
    append2excel.append_to_cell(filename, n_years, col=1, row = 0,  truncate_sheet=None, index=False)

    return force_range, force_distribution
Beispiel #3
0
def damage_equivalent_load(data_signal, m, bin_num=100, data_length=600):
    """
    Calculates the damage equivalent load of a single data signal (or channel) 
    based on IEC TS 62600-3:2020 ED1. 4-point rainflow counting algorithm from 
    fatpack module is based on the following resources:
        
    - `C. Amzallag et. al. Standardization of the rainflow counting method for
      fatigue analysis. International Journal of Fatigue, 16 (1994) 287-293`
      `ISO 12110-2, Metallic materials - Fatigue testing - Variable amplitude
      fatigue testing.`
    - `G. Marsh et. al. Review and application of Rainflow residue processing
      techniques for accurate fatigue damage estimation. International Journal
      of Fatigue, 82 (2016) 757-765`
    
    Parameters
    -----------
    data_signal : array
        Data signal being analyzed
    
    m : float/int
        Fatigue slope factor of material
    
    bin_num : int
        Number of bins for rainflow counting method (minimum=100)
    
    data_length : float/int
        Length of measured data (seconds)
    
    Returns
    -----------
    DEL : float
        Damage equivalent load of single data signal
    """
    # check data types
    try:
        data_signal = np.array(data_signal)
    except:
        pass
    assert isinstance(data_signal, np.ndarray), 'data_signal must be of type np.ndarray'
    assert isinstance(m, (float,int)), 'm must be of type float or int'
    assert isinstance(bin_num, (float,int)), 'bin_num must be of type float or int'
    assert isinstance(data_length, (float,int)), 'data_length must be of type float or int'

    # find rainflow ranges
    ranges = fatpack.find_rainflow_ranges(data_signal)

    # find range count and bin
    Nrf, Srf = fatpack.find_range_count(ranges, bin_num)

    # get DEL
    DELs = Srf**m * Nrf / data_length
    DEL = DELs.sum() ** (1/m)

    return DEL
Beispiel #4
0
fig = plt.figure(dpi=300, figsize=figsize)

# Plotting signal with reversals.
ax_signal = plt.subplot2grid((3, 2), (0, 0))
ax_signal.plot(y)
ax_signal.plot(reversals_ix,
               y[reversals_ix],
               'ro',
               fillstyle='none',
               label='reversal')
ax_signal.legend()
ax_signal.set(title="Signal", ylabel="y", xlabel="Index", xlim=[400, 500])

# Plotting the cumulative distribution of the cycle count
ax_cumdist = plt.subplot2grid((3, 2), (1, 0))
N, S = fatpack.find_range_count(ranges, 64)
Ncum = N.sum() - np.cumsum(N)
ax_cumdist.semilogx(Ncum, S)
ax_cumdist.set(title="Cumulative distribution, rainflow ranges",
               xlabel="Count, N",
               ylabel="Range, S")

# Plotting the rainflow matrix of the total cycle count
ax_rfcmat = plt.subplot2grid((3, 2), (0, 1), rowspan=2, aspect='equal')
bins = np.linspace(cycles_total.min(), cycles_total.max(), 64)
rfcmat = fatpack.find_rainflow_matrix(cycles_total, bins, bins)
X, Y = np.meshgrid(bins, bins, indexing='ij')
C = ax_rfcmat.pcolormesh(X, Y, rfcmat, cmap='magma')
fig.colorbar(C)
ax_rfcmat.set(title="Rainflow matrix",
              xlabel="Starting point",
Beispiel #5
0
    def get_DEL(self, fast_data, chan_dict, binNum=100, t=600):
        """ Calculates the short-term damage equivalent load of multiple variables
        
        Parameters: 
        -----------
        fast_data: list
            List of dictionaries containing openfast output data (returned from ROSCO_toolbox.FAST_IO.load_output)
        
        chan_dict : list, tuple
            tuple/list containing channel names to be analyzed and corresponding fatigue slope factor "m"
            ie. ('TwrBsFxt',4)
        
        binNum : int
            number of bins for rainflow counting method (minimum=100)
        
        t : float/int
            Used to control DEL frequency. Default for 1Hz is 600 seconds for 10min data
        
        Outputs:
        -----------
        dfDEL : pd.DataFrame
            Damage equivalent load of each specified variable for one fast output file  
        """
        # check data types
        assert isinstance(fast_data, (list)), 'fast_data must be of type list'
        assert isinstance(
            chan_dict,
            (list, tuple)), 'chan_dict must be of type list or tuple'
        assert isinstance(binNum,
                          (float, int)), 'binNum must be of type float or int'
        assert isinstance(t, (float, int)), 't must be of type float or int'

        # create dictionary from chan_dict
        dic = dict(chan_dict)

        # pre-allocate list
        dflist = []

        for fd in fast_data:
            if self.verbose:
                print('Processing data for {}'.format(fd['meta']['name']))
            dlist = []  # initiate blank list every loop
            # loop through channels and apply corresponding fatigue slope
            for var in dic.keys():
                # find rainflow ranges
                ranges = fatpack.find_rainflow_ranges(fd[var])

                # find range count and bin
                Nrf, Srf = fatpack.find_range_count(ranges, binNum)

                # get DEL
                DELs = Srf**dic[var] * Nrf / t
                DEL = DELs.sum()**(1 / dic[var])
                dlist.append(DEL)
            # append DEL values for each channel to master list
            dflist.append(dlist)

            # create dataframe to return
            dfDEL = pd.DataFrame(np.transpose(dflist))
            dfDEL = dfDEL.T
            dfDEL.columns = dic.keys()

        return dfDEL