yawErrorAnalyst.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. import os
  2. import pandas as pd
  3. import numpy as np
  4. import plotly.graph_objects as go
  5. from algorithmContract.confBusiness import *
  6. from algorithmContract.contract import Contract
  7. from behavior.analystWithGoodPoint import AnalystWithGoodPoint
  8. from plotly.subplots import make_subplots
  9. from scipy.optimize import curve_fit
  10. class YawErrorAnalyst(AnalystWithGoodPoint):
  11. """
  12. 风电机组静态偏航误差分析
  13. """
  14. fieldWindDirFloor = 'wind_dir_floor'
  15. fieldPower = 'power'
  16. fieldPowerMean = 'mean_power'
  17. fieldPowerMax = 'max_power'
  18. fieldPowerMin = 'min_power'
  19. fieldPowerMedian = 'median_power'
  20. fieldYawError = 'yaw_error'
  21. fieldStep = 'wsstep'
  22. fieldK = 'k'
  23. fieldWindSpeed = 'mean_WindSpeed'
  24. fieldcount = 'point_num'
  25. def typeAnalyst(self):
  26. return "yaw_error"
  27. def selectColumns(self):
  28. return [Field_DeviceCode,Field_Time,Field_WindSpeed,Field_ActiverPower,Field_AngleIncluded,Field_PitchAngel1,Field_RotorSpeed, Field_GeneratorSpeed]
  29. def filterCommon(self, dataFrame: pd.DataFrame,conf:Contract):
  30. #-------------------1.物理筛选--------
  31. # 使用 loc 方法获取 Field_RatedPower 列的值
  32. fullpower = self.turbineInfo[Field_RatedPower].iloc[0]
  33. # rated_power = self.turbineInfo.loc[self.turbineInfo[Field_CodeOfTurbine].isin(dataFrame[Field_CodeOfTurbine]), Field_RatedPower]
  34. # fullpower=rated_power.iloc[0]
  35. # rated_power=self.turbineInfo[Field_RatedPower]
  36. # 删除小于0的有功功率
  37. dataFrame = dataFrame[~(dataFrame[Field_ActiverPower] < 0)]
  38. # 删除小于2.5的风速
  39. dataFrame = dataFrame[~(dataFrame[Field_WindSpeed] < 2.5)]
  40. # 删除有功小于额定功率-100,变桨角度大于0.2的数据
  41. dataFrame = dataFrame[~((dataFrame[Field_ActiverPower] < fullpower - 100) & (dataFrame[Field_PitchAngel1] > 3))]
  42. # 删除有功小于额定功率-40,变桨角度大于1的数据
  43. dataFrame = dataFrame[~((dataFrame[Field_ActiverPower] < fullpower - 40) & (dataFrame[Field_PitchAngel1] > 5))]
  44. # 删除对风角度小于-30,大于30的数据
  45. dataFrame = dataFrame[~((dataFrame[Field_AngleIncluded].abs() > 30))]
  46. #-------------------2.数学筛选--------
  47. wsstep = 0.5
  48. stdup = 6
  49. stddown = 2.2
  50. for wscol in np.arange(3, 15.25, wsstep/2):
  51. dataFrame_lv = dataFrame[(dataFrame[Field_WindSpeed] >= (wscol-wsstep/4)) & (dataFrame[Field_WindSpeed] < (wscol+wsstep/4))]
  52. meanpowr = np.mean(dataFrame_lv[Field_ActiverPower])
  53. stddataws = np.std(dataFrame_lv[Field_ActiverPower])
  54. index1 = dataFrame[(dataFrame[Field_WindSpeed] >= (wscol-wsstep/4)) & (dataFrame[Field_WindSpeed] < (wscol+wsstep/4))
  55. & ((dataFrame_lv[Field_ActiverPower] - meanpowr) > stdup * stddataws) | ((meanpowr - dataFrame_lv
  56. [Field_ActiverPower]) > stddown * stddataws)].index
  57. dataFrame.drop(index1, inplace=True)
  58. return dataFrame
  59. def calculateYawError(self, dataFrame: pd.DataFrame, fieldAngleInclude,fieldActivePower, fieldWindSpeed):
  60. dataFrame = dataFrame.dropna(
  61. subset=[Field_NameOfTurbine, fieldAngleInclude, fieldActivePower,fieldWindSpeed])
  62. df_math_yaw = dataFrame.copy()
  63. # 偏航误差角度计算
  64. degree_length = 20 # 对风角度最大值
  65. yaw_data_out = np.empty((0, 4), float)
  66. wsstep = 0.5
  67. # 初始化一个字典来存储每 k 次循环的平均功率结果
  68. power_dict = {}
  69. for k in np.arange(3, 15.5, wsstep):
  70. yaw_data_value_co = np.empty((0, 4), float)
  71. yaw_data = df_math_yaw[(df_math_yaw[fieldWindSpeed] >= (k - 0.25)) & (df_math_yaw[fieldWindSpeed] < (k + 0.25))]
  72. # print(yaw_data)
  73. for m in np.arange((0 - degree_length), degree_length + 1, 0.5):
  74. wd = yaw_data[(yaw_data[fieldAngleInclude] >= (m - 0.5)) & (yaw_data[fieldAngleInclude] < (m + 0.5))]
  75. wd_row = wd.shape[0]
  76. if wd_row < 10:
  77. continue
  78. if wd_row >= 10:
  79. max_value_row = wd[fieldActivePower].idxmax()
  80. min_value_row = wd[fieldActivePower].idxmin()
  81. wd = wd.drop(max_value_row)
  82. wd = wd.drop(min_value_row)
  83. wd_row = wd.shape[0]
  84. mean_wd = wd[fieldAngleInclude].mean()
  85. mean_power = wd[fieldActivePower].mean()
  86. mean_ws = wd[fieldWindSpeed].mean()
  87. yaw_data_value = [mean_power, mean_wd, mean_ws, wd_row]
  88. yaw_data_value_co = np.vstack((yaw_data_value_co, yaw_data_value))
  89. # 存储当前 k 和 m 对应的 mean_power
  90. if k not in power_dict:
  91. power_dict[k] = {}
  92. power_dict[k][m] = mean_power
  93. # 检查 yaw_data_value_co 是否为空
  94. if yaw_data_value_co.size == 0:
  95. print(f"yaw_data_value_co k={k}是空的,无法计算argmax")
  96. continue
  97. else:
  98. max_row = np.argmax(yaw_data_value_co[:, 0])
  99. yaw_data_out = np.vstack((yaw_data_out, yaw_data_value_co[max_row, :]))
  100. # 将结果转换为 DataFrame
  101. result_df = pd.DataFrame(yaw_data_out, columns=[self.fieldPowerMean, self.fieldYawError,self.fieldWindSpeed,self.fieldcount])
  102. # result_df.to_csv('D:\\dashengkeji\\project\\WTOAAM\\debug_tset_20241008\\result_df_JHS_.csv',index=False)
  103. # 构建最终画图的 DataFrame
  104. final_df = pd.DataFrame(columns=[self.fieldWindDirFloor] + [f'{k}-{k+wsstep}' for k in np.arange(4, 9, wsstep)])
  105. rows = []
  106. for m in np.arange((0 - degree_length), degree_length + 1, 1):
  107. row = {self.fieldWindDirFloor: m}
  108. for k in np.arange(4, 9, wsstep):
  109. if k in power_dict and power_dict[k]: # 检查 power_dict[k] 是否存在且不为空
  110. row[f'{k}-{k+wsstep}'] = power_dict[k].get(m, np.nan)
  111. else:
  112. row[f'{k}-{k+wsstep}'] = np.nan # 如果 power_dict[k] 为空,则填充 np.nan
  113. rows.append(row)
  114. final_df = pd.concat([final_df, pd.DataFrame(rows)], ignore_index=True)
  115. return result_df,final_df
  116. def turbinesAnalysis(self, outputAnalysisDir, conf: Contract, turbineCodes):
  117. dictionary = self.processTurbineData(turbineCodes,conf,self.selectColumns())
  118. dataFrameMerge = self.userDataFrame(dictionary,conf.dataContract.configAnalysis,self)
  119. # dataFrameMerge.to_csv('D:\dashengkeji\project\WTOAAM\debug_tset_20241008\dataFrameJHS_WOG00935.csv',index=False)
  120. return self.yawErrorAnalysis(dataFrameMerge, outputAnalysisDir, conf)
  121. def yawErrorAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, conf: Contract):
  122. # 检查所需列是否存在
  123. required_columns = {Field_ActiverPower, Field_YawError,Field_WindSpeed,Field_PitchAngel1,Field_Cp,Field_TSR}
  124. if not required_columns.issubset(dataFrameMerge.columns):
  125. raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
  126. results = []
  127. result_rows = []
  128. grouped = dataFrameMerge.groupby(
  129. [Field_NameOfTurbine, Field_CodeOfTurbine])
  130. for name, group in grouped:
  131. group = self.filterCommon(group,conf)
  132. df,final_df = self.calculateYawError(
  133. group, Field_YawError, Field_ActiverPower,Field_WindSpeed)
  134. df.dropna(inplace=True)
  135. # final_df.dropna(inplace=True)
  136. """
  137. 自动化选择tsr平稳段的风速段作为风速最后的筛选区间
  138. """
  139. # 滑动窗口size
  140. window_size=5
  141. # 差分阈值
  142. threshold=2
  143. diff = group[Field_TSR].diff().abs()
  144. stable_mask = diff.rolling(window=window_size).mean() < threshold
  145. # print("diff的索引:", diff.index)
  146. # print("stable_mask的索引:", stable_mask.index)
  147. # 找到最长的平稳段
  148. longest_stable_segment = None
  149. max_length = 0
  150. current_segment = None
  151. for i in range(len(stable_mask)):
  152. if i in stable_mask.index:
  153. if stable_mask[i]:
  154. if current_segment is None:
  155. current_segment = [i]
  156. else:
  157. current_segment.append(i)
  158. else:
  159. if current_segment is not None:
  160. if len(current_segment) > max_length:
  161. longest_stable_segment = current_segment
  162. max_length = len(current_segment)
  163. current_segment = None
  164. if current_segment is not None and len(current_segment) > max_length:
  165. longest_stable_segment = current_segment
  166. if longest_stable_segment is not None:
  167. start_idx = longest_stable_segment[0]
  168. end_idx = longest_stable_segment[-1]
  169. # 获取tsr平稳区间对应的风俗区间
  170. if start_idx is not None and end_idx is not None:
  171. # 提取对应的windspeed区间
  172. stable_windspeed_segment = group.loc[start_idx:end_idx, Field_WindSpeed]
  173. # print(f"平稳段的起始索引: {start_idx}, 结束索引: {end_idx}")
  174. # print("对应的windspeed区间:")
  175. # print(stable_windspeed_segment)
  176. # 计算风速平稳段的最大值和最小值
  177. max_windspeed = stable_windspeed_segment.max()
  178. min_windspeed = stable_windspeed_segment.min()
  179. # print(f"风速平稳段的最大值: {max_windspeed}")
  180. # print(f"风速平稳段的最小值: {min_windspeed}")
  181. else:
  182. print("未找到平稳段")
  183. # 筛选出 self.fieldWindSpeed 列的数值处于 4.5 到 8 区间内的行
  184. filtered_df = df[(df[self.fieldWindSpeed] >= 4.5) & (df[self.fieldWindSpeed] <= 8)]
  185. # 查找 df 中 self.fieldYawError列的平均值,并向下取整作为偏航误差
  186. max_yaw_error = filtered_df[self.fieldYawError].mean()
  187. max_yaw_error_floor = np.floor(max_yaw_error)
  188. # 存储结果
  189. results.append({
  190. Field_NameOfTurbine: name[0],
  191. Field_YawError: max_yaw_error_floor
  192. })
  193. #对 final_df 中的数据进行可视化
  194. fig = go.Figure()
  195. for col in final_df.columns[1:]: # 跳过第一列 self.fieldWindDirFloor
  196. fig.add_trace(go.Scatter(
  197. x=final_df[self.fieldWindDirFloor],
  198. y=final_df[col],
  199. mode='lines+markers',
  200. name=col
  201. ))
  202. fig.update_layout(
  203. title=f'偏航误差分析 {name[0]} ',
  204. xaxis_title='偏航角度',
  205. yaxis_title='平均功率',
  206. legend_title='风速区间',
  207. showlegend=True
  208. )
  209. # Save to file
  210. filePathOfImage = os.path.join(outputAnalysisDir, f"{name[0]}.png")
  211. fig.write_image(filePathOfImage, scale=3)
  212. filePathOfHtml = os.path.join(outputAnalysisDir, f"{name[0]}.html")
  213. fig.write_html(filePathOfHtml)
  214. result_rows.append({
  215. Field_Return_TypeAnalyst: self.typeAnalyst(),
  216. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  217. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  218. Field_CodeOfTurbine: name[1],
  219. Field_Return_FilePath: filePathOfImage,
  220. Field_Return_IsSaveDatabase: False
  221. })
  222. result_rows.append({
  223. Field_Return_TypeAnalyst: self.typeAnalyst(),
  224. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  225. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  226. Field_CodeOfTurbine: name[1],
  227. Field_Return_FilePath: filePathOfHtml,
  228. Field_Return_IsSaveDatabase: False
  229. })
  230. # 初始化一个空的DataFrame,指定列名
  231. columns = [Field_NameOfTurbine, Field_YawError]
  232. dataFrameResult = pd.DataFrame(results, columns=columns)
  233. filePathOfYawErrorResult = os.path.join(
  234. outputAnalysisDir, f"{name[0]}yaw_error_result{CSVSuffix}")
  235. df.to_csv(filePathOfYawErrorResult, index=False)
  236. result_rows.append({
  237. Field_Return_TypeAnalyst: self.typeAnalyst(),
  238. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  239. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  240. Field_CodeOfTurbine: name[1],
  241. Field_Return_FilePath: filePathOfYawErrorResult,
  242. Field_Return_IsSaveDatabase: True
  243. })
  244. # 初始化一个空的DataFrame,指定列名
  245. columns = [Field_NameOfTurbine, Field_YawError]
  246. dataFrameResult = pd.DataFrame(results, columns=columns)
  247. filePathOfYawError = os.path.join(
  248. outputAnalysisDir, f"yaw_error_result{CSVSuffix}")
  249. dataFrameResult.to_csv(filePathOfYawError, index=False)
  250. result_rows.append({
  251. Field_Return_TypeAnalyst: self.typeAnalyst(),
  252. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  253. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  254. Field_CodeOfTurbine: "total",
  255. Field_Return_FilePath: filePathOfYawError,
  256. Field_Return_IsSaveDatabase: True
  257. })
  258. result_df = pd.DataFrame(result_rows)
  259. return result_df