yawErrorAnalyst.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  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. turbineInfos = self.common.getTurbineInfos(conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo)
  120. return self.yawErrorAnalysis(dataFrameMerge,turbineInfos, outputAnalysisDir, conf)
  121. def yawErrorAnalysis(self, dataFrameMerge: pd.DataFrame,turbineModelInfo: pd.Series, 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. # 滑动窗口size
  139. window_size=5
  140. # 差分阈值
  141. threshold=2
  142. diff = group[Field_TSR].diff().abs()
  143. stable_mask = diff.rolling(window=window_size).mean() < threshold
  144. # print("diff的索引:", diff.index)
  145. # print("stable_mask的索引:", stable_mask.index)
  146. # 找到最长的平稳段
  147. longest_stable_segment = None
  148. max_length = 0
  149. current_segment = None
  150. for i in range(len(stable_mask)):
  151. if i in stable_mask.index:
  152. if stable_mask[i]:
  153. if current_segment is None:
  154. current_segment = [i]
  155. else:
  156. current_segment.append(i)
  157. else:
  158. if current_segment is not None:
  159. if len(current_segment) > max_length:
  160. longest_stable_segment = current_segment
  161. max_length = len(current_segment)
  162. current_segment = None
  163. if current_segment is not None and len(current_segment) > max_length:
  164. longest_stable_segment = current_segment
  165. if longest_stable_segment is not None:
  166. start_idx = longest_stable_segment[0]
  167. end_idx = longest_stable_segment[-1]
  168. # 获取tsr平稳区间对应的风俗区间
  169. if start_idx is not None and end_idx is not None:
  170. # 提取对应的windspeed区间
  171. stable_windspeed_segment = group.loc[start_idx:end_idx, Field_WindSpeed]
  172. # print(f"平稳段的起始索引: {start_idx}, 结束索引: {end_idx}")
  173. # print("对应的windspeed区间:")
  174. # print(stable_windspeed_segment)
  175. # 计算风速平稳段的最大值和最小值
  176. max_windspeed = stable_windspeed_segment.max()
  177. min_windspeed = stable_windspeed_segment.min()
  178. # print(f"风速平稳段的最大值: {max_windspeed}")
  179. # print(f"风速平稳段的最小值: {min_windspeed}")
  180. else:
  181. print("未找到平稳段")
  182. """
  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. # 确保从 Series 中提取的是具体的值
  210. engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "")
  211. if isinstance(engineTypeCode, pd.Series):
  212. engineTypeCode = engineTypeCode.iloc[0]
  213. engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "")
  214. if isinstance(engineTypeName, pd.Series):
  215. engineTypeName = engineTypeName.iloc[0]
  216. # 构建最终的JSON对象
  217. json_output = {
  218. "analysisTypeCode": "偏航误差分析",
  219. "engineCode": engineTypeCode,
  220. "engineTypeName": engineTypeName,
  221. "xaixs": "偏航角度",
  222. "yaixs": "有功功率(kw)",
  223. "data": [
  224. {
  225. "engineName": name[0],
  226. "engineCode": name[1],
  227. "title": f'静态偏航误差分析 {name[0]}',
  228. "xData": final_df[self.fieldWindDirFloor].tolist(),
  229. "yData": final_df[col].tolist(),
  230. "legend": col,
  231. "type": "lines+markers",
  232. }
  233. for col in final_df.columns[1:] # 跳过第一列 self.fieldWindDirFloor
  234. ]
  235. }
  236. # Save to file
  237. filePathOfImage = os.path.join(outputAnalysisDir, f"{name[0]}.png")
  238. fig.write_image(filePathOfImage, scale=3)
  239. # filePathOfHtml = os.path.join(outputAnalysisDir, f"{name[0]}.html")
  240. # fig.write_html(filePathOfHtml)
  241. # 将JSON对象保存到文件
  242. output_json_path = os.path.join(outputAnalysisDir, f"{name[0]}.json")
  243. with open(output_json_path, 'w', encoding='utf-8') as f:
  244. import json
  245. json.dump(json_output, f, ensure_ascii=False, indent=4)
  246. # 如果需要返回DataFrame,可以包含文件路径
  247. result_rows.append({
  248. Field_Return_TypeAnalyst: self.typeAnalyst(),
  249. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  250. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  251. Field_CodeOfTurbine: name[1],
  252. Field_Return_FilePath: output_json_path,
  253. Field_Return_IsSaveDatabase: True
  254. })
  255. result_rows.append({
  256. Field_Return_TypeAnalyst: self.typeAnalyst(),
  257. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  258. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  259. Field_CodeOfTurbine: name[1],
  260. Field_Return_FilePath: filePathOfImage,
  261. Field_Return_IsSaveDatabase: False
  262. })
  263. # result_rows.append({
  264. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  265. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  266. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  267. # Field_CodeOfTurbine: name[1],
  268. # Field_Return_FilePath: filePathOfHtml,
  269. # Field_Return_IsSaveDatabase: True
  270. # })
  271. # # 初始化一个空的DataFrame,指定列名
  272. # columns = [Field_NameOfTurbine, Field_YawError]
  273. # dataFrameResult = pd.DataFrame(results, columns=columns)
  274. # filePathOfYawErrorResult = os.path.join(
  275. # outputAnalysisDir, f"{name[0]}yaw_error_result{CSVSuffix}")
  276. # df.to_csv(filePathOfYawErrorResult, index=False)
  277. # result_rows.append({
  278. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  279. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  280. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  281. # Field_CodeOfTurbine: name[1],
  282. # Field_Return_FilePath: filePathOfYawErrorResult,
  283. # Field_Return_IsSaveDatabase: True
  284. # })
  285. # 初始化一个空的DataFrame,指定列名
  286. columns = [Field_NameOfTurbine, Field_YawError]
  287. dataFrameResult = pd.DataFrame(results, columns=columns)
  288. filePathOfYawError = os.path.join(
  289. outputAnalysisDir, f"yaw_error_result{CSVSuffix}")
  290. dataFrameResult.to_csv(filePathOfYawError, index=False)
  291. result_rows.append({
  292. Field_Return_TypeAnalyst: self.typeAnalyst(),
  293. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  294. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  295. Field_CodeOfTurbine: "total",
  296. Field_Return_FilePath: filePathOfYawError,
  297. Field_Return_IsSaveDatabase: True
  298. })
  299. result_df = pd.DataFrame(result_rows)
  300. return result_df