def get_life(self, C, k, nr_load_classes=512, Su=False, range=False): """Calculate fatigue life with parameters C, k, as defined in [2]. :param C: [int,float] Fatigue strength coefficient [MPa**k]. :param k : [int,float] Fatigue strength exponent [/]. :param nr_load_classes: int The number of intervals to divide the min-max range of the dataseries into. Defaults to 512. :param Su: [int,float] Ultimate tensile strength [MPa]. If specified, Goodman equivalent stress is used for fatigue life estimation. Defaults to False. :param range: bool If True, ranges instead of amplitudes are used for fatigue life estimation. Defaults to False. :return T: float Estimated fatigue life in seconds. """ t = self.spectral_data.t ranges, means = fatpack.find_rainflow_ranges(self.spectral_data.data, k=nr_load_classes, return_means=True) if range == True: pass elif range == False: ranges *= 0.5 else: raise Exception('Unrecognized Input Error') if Su: ranges = ranges / (1. - means / Su) d = np.sum(ranges**k / C) T = t / d return T
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
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
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
def rainflow_histogram(self): r, sm = fatpack.find_rainflow_ranges(self.y, return_means=True, k=self.nbins) # Next create the bins to divide the stress ranges and means into bins_r = np.linspace(min(r), max(r), self.nbins) bins_sm = np.linspace(min(sm), max(sm), self.nbins) # and establish the data array. Note that other datavectors vectors, e.g. # Smin and Smax, may also be used to create other data arrays and # resulting rainflow matrices. data_array = np.array([sm, r]).T # Finally, establish the rainflow matrix from the data array and the # specified row and column bins. n = fatpack.find_rainflow_matrix(data_array, bins_sm, bins_r) n = n.sum(axis=0) r = bins_r[0:-1] S = (r/2)/(1-np.mean(self.y)/self.sigmaf) # r = [] # n = [] # for i in range(len(cc)): # r.append(cc[i][0]/2) # n.append(cc[i][1]) # # print(r[i], n[i]) # O código abaixo acho que está certo, porém preciso dar um jeito de fazê-lo ser mais rápido para obter os ranges e means de rainflow rangemax = max(S) - min(S) nclass = self.nbins tns = sum(n) bw = (min(S) + (rangemax/nclass)*(nclass+1)) - (min(S) + (rangemax/nclass)*(nclass)) p = [] appendp = p.append summ = 0 for i in range(len(n)): appendp((n[i]/tns)/bw) summ += p[i]*bw #Finish here!!!! return S, n, p
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