frequency_filter.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. import pandas as pd
  2. import matplotlib.pyplot as plt
  3. import warnings
  4. from pandas.errors import SettingWithCopyWarning
  5. from scipy.signal import butter, filtfilt
  6. from scipy.signal import welch
  7. from scipy.fft import fft, fftfreq
  8. import numpy as np
  9. from sympy import symbols, solve, Eq, simplify, nsolve
  10. import itertools
  11. import multiprocessing
  12. from concurrent.futures import ThreadPoolExecutor
  13. warnings.filterwarnings("ignore", category=SettingWithCopyWarning)
  14. plt.rcParams['font.sans-serif'] = ['SimHei']
  15. plt.rcParams['axes.unicode_minus'] = False
  16. def butter_lowpass_filter(data, cutoff, fs, order=5):
  17. nyq = 0.5 * fs # Nyquist频率
  18. normal_cutoff = cutoff / nyq
  19. b, a = butter(order, normal_cutoff, btype='low', analog=False)
  20. y = filtfilt(b, a, data)
  21. return y
  22. def butter_bandpass_filter(data, cutoff_low, cutoff_high, fs, order=5):
  23. nyq = 0.5 * fs # Nyquist频率
  24. high_cutoff = cutoff_high / nyq
  25. low_cutoff = cutoff_low / nyq
  26. b, a = butter(order, [low_cutoff, high_cutoff], btype='band', analog=False)
  27. y = filtfilt(b, a, data)
  28. return y
  29. def apply_fft(x, fs):
  30. n = len(x)
  31. t = 1.0 / fs
  32. fft_cof = fft(x - np.mean(x)) # Obtain FFT coefficients after removing the mean (DC component) from the signal.
  33. xf = fftfreq(n, t)[:n // 2] # Taking positive spectrum only.
  34. # Multiply abs(FFT coefficients) by 2 to compensate for positive spectrum and normalize by signal length.
  35. fft_positive = 2.0 / n * np.abs(fft_cof[0:n // 2])
  36. return xf, fft_positive
  37. # Function to compute PSD
  38. def compute_psd(signal, length, fs):
  39. seg_length = length / 10000 # Segment length.
  40. overlap = seg_length / 20 # overlap between segments (in number of samples)
  41. nfft_length = 2 ** 14 # FFT length
  42. frequencies, psd = welch(signal, fs=fs, window='han', nperseg=seg_length, noverlap=overlap, nfft=nfft_length)
  43. return frequencies, psd
  44. def tower_cal(data, start_points, end_points, fs):
  45. """
  46. 计算测量叶片数据的塔筒测量距离
  47. :param data: cycle_calculate计算完成后的数据。
  48. :param start_points: 所有每个周期开始点,叶片前缘突变点。
  49. :param end_points: 叶片后缘突变点。
  50. :param fs: 采样频率。
  51. """
  52. cutoff_low = 0.01 * fs # 设置低通滤波截止频率
  53. combined_df_sorted = pd.concat([start_points, end_points]).sort_values(by='time') # 合并DataFrame并按 'time' 列排序
  54. # 检查排序后的数据从end开始,start结束
  55. if combined_df_sorted.iloc[0].equals(start_points.iloc[0]):
  56. combined_df_sorted = combined_df_sorted.iloc[1:]
  57. if combined_df_sorted.iloc[-1].equals(end_points.iloc[-1]):
  58. combined_df_sorted = combined_df_sorted.iloc[:-1]
  59. combined_df_sorted.reset_index(drop=True, inplace=True)
  60. # 将 start_points 中的时间点转换为列表
  61. start_times = combined_df_sorted['time'].tolist()
  62. fft_positive_filters = []
  63. tower_dists = []
  64. # 遍历所有起始时间点
  65. for i in range(0, len(start_times), 2):
  66. # 获取当前起始和结束时间点
  67. start_time = start_times[i] + 0.01 * (start_times[i + 1] - start_times[i])
  68. end_time = start_times[i + 1] - 0.01 * (start_times[i + 1] - start_times[i])
  69. # 根据当前起始时间点和结束时间点对数据进行分段
  70. segment = data[(data['time'] > start_time) & (data['time'] <= end_time)]
  71. mean_point = segment['distance'].mean()
  72. tower_dists.append(mean_point)
  73. tower_dist = np.mean(tower_dists)
  74. return tower_dist
  75. def process_fft(data, fs):
  76. """
  77. 塔筒数据每组低通滤波,求平均得振动分析;
  78. 叶片数据分组高通滤波
  79. :param data: tower_cal计算完成后的数据。
  80. :param fs: 采样频率。
  81. """
  82. cutoff_low = 0.001 * fs # 设置低通滤波截止频率
  83. segment = data.head(int(len(data) * 0.95))
  84. # 确保segment的长度是2的n次幂
  85. desired_length = 2**((len(segment) - 1).bit_length() - 1)
  86. segment = segment.head(desired_length)
  87. segment.loc[:, 'distance_filtered'] = butter_lowpass_filter(segment['distance'], cutoff_low, fs)
  88. # 提取时间序列数据
  89. time_series_filter = segment['distance_filtered'].values # 使用滤波后的距离数据
  90. xf_filter, fft_positive_filter = apply_fft(time_series_filter, fs)
  91. fft_positive_filters_truncated = fft_positive_filter[0:1000]
  92. xf_filter_truncated = xf_filter[0:1000]
  93. # 将 NumPy 数组转换为 Python 列表
  94. fft_y = fft_positive_filters_truncated.tolist()
  95. fft_y_scaled = [x * 1000 for x in fft_y]
  96. fft_y_scaled = [0] + fft_y_scaled
  97. fft_x = xf_filter_truncated.tolist()
  98. fft_x = [0] + fft_x
  99. fft_y_scaled = [0 if a_val < 0.1 else b_val for a_val, b_val in zip(fft_x, fft_y_scaled)]
  100. max_value = max(fft_y_scaled)
  101. max_index = fft_y_scaled.index(max_value)
  102. '''
  103. plt.plot(fft_x, fft_y_scaled, label='Filtered Signal')
  104. plt.xlabel('频率 (Hz)', fontsize=8)
  105. plt.ylabel('振幅 (m)', fontsize=8)
  106. plt.tick_params('x', labelsize=8)
  107. plt.tick_params('y', labelsize=8)
  108. plt.title('Frequency Spectrum', fontsize=12)
  109. plt.legend(fontsize=7)
  110. plt.grid(True)
  111. plt.savefig(f"filter_fft1.png", dpi=110, pil_kwargs={"icc_profile": False})
  112. plt.close()
  113. plt.plot(segment['time'], segment['distance_filtered'], label='Filtered Signal')
  114. plt.xlabel('时间 (s)', fontsize=8)
  115. plt.ylabel('振幅 (m)', fontsize=8)
  116. plt.tick_params('x', labelsize=8)
  117. plt.tick_params('y', labelsize=8)
  118. plt.title('Time-Domain Waveform', fontsize=12)
  119. plt.legend(fontsize=7)
  120. plt.grid(True)
  121. plt.savefig(f"filter_fft2.png", dpi=110, pil_kwargs={"icc_profile": False})
  122. plt.close()
  123. '''
  124. return segment, fft_x, fft_y_scaled, round(fft_x[max_index], 2), round(max_value, 2)
  125. def process_equations(list_param):
  126. """
  127. 处理方程组并求解
  128. 参数:
  129. a, b, p: 坐标点列表,P是轮毂中心
  130. d, e, f: 轴向倾角、相位角、锥角
  131. 返回:
  132. solutions: 包含所有有效解的列表
  133. positive_angles: 包含所有正角度值的列表
  134. """
  135. a, b, p, d, e, f = [list_param[i] for i in range(6)]
  136. # 定义符号
  137. ya, yb, za, zb, x, y = symbols('ya yb za zb x y', real=True)
  138. # 计算全局表达式
  139. za_expr = solve(Eq(a[0] - p[0] + ya * (a[1] - p[1]) + za * (a[2] - p[2]), 0), za)[0]
  140. zb_expr = solve(Eq(b[0] - p[0] + yb * (b[1] - p[1]) + zb * (b[2] - p[2]), 0), zb)[0]
  141. # 解线性方程组得到 x_expr, y_expr
  142. sol_xy = solve([Eq(x + ya * y + za_expr * d, 0), Eq(x + yb * y + zb_expr * d, 0)], [x, y], dict=True)
  143. if not sol_xy:
  144. raise RuntimeError("解线性方程组求 x,y 失败(意外),请检查输入符号。")
  145. x_expr = simplify(sol_xy[0][x])
  146. y_expr = simplify(sol_xy[0][y])
  147. # 最终方程
  148. f1 = simplify(Eq(1 + ya * yb + za_expr * zb_expr + e * (1 + ya ** 2 + za_expr ** 2) ** 0.5 * (
  149. 1 + yb ** 2 + zb_expr ** 2) ** 0.5, 0).lhs)
  150. f2 = simplify(Eq(x_expr ** 2 + y_expr ** 2 + d ** 2 - 1, 0).lhs)
  151. # 存储正角度值的列表
  152. positive_angles = []
  153. # 处理单个解的函数
  154. def process_solution(args):
  155. guess, index, total, solutions, positive_angles = args
  156. try:
  157. # 尝试求解
  158. solution = nsolve([f1, f2], [ya, yb], guess, dict=True, prec=6)
  159. # 获取解的值
  160. ya_val = solution[0][ya]
  161. yb_val = solution[0][yb]
  162. # 计算方程残差(误差)
  163. f1_error = abs(f1.subs({ya: ya_val, yb: yb_val}))
  164. f2_error = abs(f2.subs({ya: ya_val, yb: yb_val}))
  165. err_tol = 1e-1
  166. is_new = True
  167. if f1_error > err_tol or f2_error > err_tol:
  168. is_new = False
  169. # 检查解是否已经存在(允许小的数值误差)
  170. for existing_sol in solutions:
  171. if (abs(solution[0][ya] - existing_sol[ya]) < 1e-3 and
  172. abs(solution[0][yb] - existing_sol[yb]) < 1e-3):
  173. is_new = False
  174. break
  175. if is_new:
  176. solution_dict = solution[0]
  177. solutions.append(solution_dict)
  178. # 计算其他值
  179. ya_val = simplify(solution_dict[ya])
  180. yb_val = simplify(solution_dict[yb])
  181. x_val = simplify(x_expr.subs({ya: ya_val, yb: yb_val}))
  182. y_val = simplify(y_expr.subs({ya: ya_val, yb: yb_val}))
  183. za_val = simplify(za_expr.subs({ya: ya_val}))
  184. zb_val = simplify(zb_expr.subs({yb: yb_val}))
  185. angle = np.degrees(np.arctan(float(y_val) / float(x_val)))
  186. # 检查角度是否为正
  187. if angle > 0:
  188. positive_angles.append(angle)
  189. print(f"初始值 {guess} 的解:")
  190. print(f" x = {x_val}")
  191. print(f" y = {y_val}")
  192. print(f" 方程 f1 残差: {f1_error}")
  193. print(f" 方程 f2 残差: {f2_error}")
  194. print(f" angle = {angle}°")
  195. print("-" * 50)
  196. return 1
  197. else:
  198. return 0
  199. except Exception as e:
  200. if index % 1000 == 0:
  201. print(f"已完成 {index} 个")
  202. return 0
  203. # 设置Windows下的多进程启动方法
  204. multiprocessing.freeze_support()
  205. # 存储结果的列表
  206. manager = multiprocessing.Manager()
  207. solutions = manager.list()
  208. positive_angles = manager.list()
  209. # 初始猜测值
  210. initial_values = np.arange(-10, 10, 0.1)
  211. initial_guesses = list(itertools.product(initial_values, repeat=2))
  212. # 使用进程池并行处理
  213. print(f"总共尝试 {len(initial_guesses)} 个初始组合...")
  214. num_processes = int(multiprocessing.cpu_count() - 1)
  215. with ThreadPoolExecutor(max_workers=num_processes) as executor:
  216. args_list = [(guess, i, len(initial_guesses), solutions, positive_angles)
  217. for i, guess in enumerate(initial_guesses)]
  218. results = list(executor.map(process_solution, args_list))
  219. # 统计结果
  220. success_count = sum(results)
  221. print(f"\n计算完成:")
  222. print(f"- 成功找到解: {success_count} 个")
  223. print(f"- 总共找到 {len(solutions)} 个不同的解")
  224. print(f"- 找到 {len(positive_angles)} 个正角度值")
  225. # 打印符号求解得到的 (ya,yb) 解集
  226. if solutions:
  227. print("\n符号求解得到的 (ya,yb) 解集:")
  228. for idx, sol in enumerate(solutions, 1):
  229. ya_val = simplify(sol[ya])
  230. yb_val = simplify(sol[yb])
  231. x_val = simplify(x_expr.subs({ya: ya_val, yb: yb_val}))
  232. y_val = simplify(y_expr.subs({ya: ya_val, yb: yb_val}))
  233. za_val = simplify(za_expr.subs({ya: ya_val}))
  234. zb_val = simplify(zb_expr.subs({yb: yb_val}))
  235. angle = np.degrees(np.arctan(float(y_val) / float(x_val)))
  236. print(f"解 {idx}:")
  237. print(f" x = {x_val}")
  238. print(f" y = {y_val}")
  239. print(f" angle = {angle}°")
  240. print("-" * 50)
  241. return list(positive_angles)