yawErrorAnalyst.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  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. import math
  11. class YawErrorAnalyst(AnalystWithGoodPoint):
  12. """
  13. 风电机组静态偏航误差分析
  14. """
  15. fieldWindDirFloor = 'wind_dir_floor'
  16. fieldPower = 'power'
  17. fieldPowerMean = 'mean_power'
  18. fieldPowerMax = 'max_power'
  19. fieldPowerMin = 'min_power'
  20. fieldPowerMedian = 'median_power'
  21. fieldYawError = 'yaw_error'
  22. fieldStep = 'wsstep'
  23. fieldK = 'k'
  24. fieldWindSpeed = 'mean_WindSpeed'
  25. fieldcount = 'point_num'
  26. def typeAnalyst(self):
  27. return "yaw_error"
  28. def selectColumns(self):
  29. return [Field_DeviceCode,Field_Time,Field_WindSpeed,Field_ActiverPower,Field_AngleIncluded,Field_PitchAngel1,Field_RotorSpeed, Field_GeneratorSpeed]
  30. def filterCommon(self, dataFrame: pd.DataFrame,conf:Contract):
  31. #-------------------1.物理筛选--------
  32. # 使用 loc 方法获取 Field_RatedPower 列的值
  33. fullpower = self.turbineInfo[Field_RatedPower].iloc[0]
  34. # rated_power = self.turbineInfo.loc[self.turbineInfo[Field_CodeOfTurbine].isin(dataFrame[Field_CodeOfTurbine]), Field_RatedPower]
  35. # fullpower=rated_power.iloc[0]
  36. # rated_power=self.turbineInfo[Field_RatedPower]
  37. # 删除小于0的有功功率
  38. dataFrame = dataFrame[~(dataFrame[Field_ActiverPower] < 0)]
  39. # 删除小于2.5的风速
  40. dataFrame = dataFrame[~(dataFrame[Field_WindSpeed] < 2.5)]
  41. # 删除有功小于额定功率-100,变桨角度大于0.2的数据
  42. dataFrame = dataFrame[~((dataFrame[Field_ActiverPower] < fullpower - 100) & (dataFrame[Field_PitchAngel1] > 3))]
  43. # 删除有功小于额定功率-40,变桨角度大于1的数据
  44. dataFrame = dataFrame[~((dataFrame[Field_ActiverPower] < fullpower - 40) & (dataFrame[Field_PitchAngel1] > 5))]
  45. # 删除对风角度小于-30,大于30的数据
  46. dataFrame = dataFrame[~((dataFrame[Field_AngleIncluded].abs() > 30))]
  47. #-------------------2.数学筛选--------
  48. wsstep = 0.5
  49. stdup = 6
  50. stddown = 2.2
  51. for wscol in np.arange(3, 15.25, wsstep/2):
  52. dataFrame_lv = dataFrame[(dataFrame[Field_WindSpeed] >= (wscol-wsstep/4)) & (dataFrame[Field_WindSpeed] < (wscol+wsstep/4))]
  53. meanpowr = np.mean(dataFrame_lv[Field_ActiverPower])
  54. stddataws = np.std(dataFrame_lv[Field_ActiverPower])
  55. index1 = dataFrame[(dataFrame[Field_WindSpeed] >= (wscol-wsstep/4)) & (dataFrame[Field_WindSpeed] < (wscol+wsstep/4))
  56. & ((dataFrame_lv[Field_ActiverPower] - meanpowr) > stdup * stddataws) | ((meanpowr - dataFrame_lv
  57. [Field_ActiverPower]) > stddown * stddataws)].index
  58. dataFrame.drop(index1, inplace=True)
  59. return dataFrame
  60. def calculateYawError(self, dataFrame: pd.DataFrame, fieldAngleInclude,fieldActivePower, fieldWindSpeed):
  61. dataFrame = dataFrame.dropna(
  62. subset=[Field_NameOfTurbine, fieldAngleInclude, fieldActivePower,fieldWindSpeed])
  63. df_math_yaw = dataFrame.copy()
  64. # 偏航误差角度计算
  65. degree_length = 20 # 对风角度最大值
  66. yaw_data_out = np.empty((0, 4), float)
  67. wsstep = 0.5
  68. # 初始化一个字典来存储每 k 次循环的平均功率结果
  69. power_dict = {}
  70. for k in np.arange(3, 15.5, wsstep):
  71. yaw_data_value_co = np.empty((0, 4), float)
  72. yaw_data = df_math_yaw[(df_math_yaw[fieldWindSpeed] >= (k - 0.25)) & (df_math_yaw[fieldWindSpeed] < (k + 0.25))]
  73. # print(yaw_data)
  74. for m in np.arange((0 - degree_length), degree_length + 1, 0.5):
  75. wd = yaw_data[(yaw_data[fieldAngleInclude] >= (m - 0.5)) & (yaw_data[fieldAngleInclude] < (m + 0.5))]
  76. wd_row = wd.shape[0]
  77. if wd_row < 10:
  78. continue
  79. if wd_row >= 10:
  80. max_value_row = wd[fieldActivePower].idxmax()
  81. min_value_row = wd[fieldActivePower].idxmin()
  82. wd = wd.drop(max_value_row)
  83. wd = wd.drop(min_value_row)
  84. wd_row = wd.shape[0]
  85. mean_wd = wd[fieldAngleInclude].mean()
  86. mean_power = wd[fieldActivePower].mean()
  87. mean_ws = wd[fieldWindSpeed].mean()
  88. yaw_data_value = [mean_power, mean_wd, mean_ws, wd_row]
  89. yaw_data_value_co = np.vstack((yaw_data_value_co, yaw_data_value))
  90. # 存储当前 k 和 m 对应的 mean_power
  91. if k not in power_dict:
  92. power_dict[k] = {}
  93. power_dict[k][m] = mean_power
  94. # 检查 yaw_data_value_co 是否为空
  95. if yaw_data_value_co.size == 0:
  96. # print(f"yaw_data_value_co k={k}是空的,无法计算argmax")
  97. continue
  98. else:
  99. max_row = np.argmax(yaw_data_value_co[:, 0])
  100. yaw_data_out = np.vstack((yaw_data_out, yaw_data_value_co[max_row, :]))
  101. # 将结果转换为 DataFrame
  102. result_df = pd.DataFrame(yaw_data_out, columns=[self.fieldPowerMean, self.fieldYawError,self.fieldWindSpeed,self.fieldcount])
  103. # result_df.to_csv('D:\\dashengkeji\\project\\WTOAAM\\debug_tset_20241008\\result_df_JHS_.csv',index=False)
  104. # 构建最终画图的 DataFrame
  105. final_df = pd.DataFrame(columns=[self.fieldWindDirFloor] + [f'{k}-{k+wsstep}' for k in np.arange(4, 9, wsstep)])
  106. rows = []
  107. for m in np.arange((0 - degree_length), degree_length + 1, 1):
  108. row = {self.fieldWindDirFloor: m}
  109. for k in np.arange(4, 9, wsstep):
  110. if k in power_dict and power_dict[k]: # 检查 power_dict[k] 是否存在且不为空
  111. row[f'{k}-{k+wsstep}'] = power_dict[k].get(m, np.nan)
  112. else:
  113. row[f'{k}-{k+wsstep}'] = np.nan # 如果 power_dict[k] 为空,则填充 np.nan
  114. rows.append(row)
  115. final_df = pd.concat([final_df, pd.DataFrame(rows)], ignore_index=True)
  116. return result_df,final_df
  117. def turbinesAnalysis(self, outputAnalysisDir, conf: Contract, turbineCodes):
  118. dictionary = self.processTurbineData(turbineCodes,conf,self.selectColumns())
  119. dataFrameMerge = self.userDataFrame(dictionary,conf.dataContract.configAnalysis,self)
  120. turbineInfos = self.common.getTurbineInfos(conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo)
  121. return self.yawErrorAnalysis(dataFrameMerge,turbineInfos, outputAnalysisDir, conf)
  122. def yawErrorAnalysis(self, dataFrameMerge: pd.DataFrame,turbineModelInfo: pd.Series, outputAnalysisDir, conf: Contract):
  123. # 检查所需列是否存在
  124. required_columns = {Field_ActiverPower, Field_YawError,Field_WindSpeed,Field_PitchAngel1,Field_Cp,Field_TSR}
  125. if not required_columns.issubset(dataFrameMerge.columns):
  126. raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
  127. results = []
  128. result_rows = []
  129. grouped = dataFrameMerge.groupby(
  130. [Field_NameOfTurbine, Field_CodeOfTurbine])
  131. for name, group in grouped:
  132. group = self.filterCommon(group,conf)
  133. df,final_df = self.calculateYawError(
  134. group, Field_YawError, Field_ActiverPower,Field_WindSpeed)
  135. df.dropna(inplace=True)
  136. # final_df.dropna(inplace=True)
  137. """
  138. 自动化选择tsr平稳段的风速段作为风速最后的筛选区间
  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. """
  184. # 筛选出 self.fieldWindSpeed 列的数值处于 4.5 到 8 区间内的行
  185. filtered_df = df[(df[self.fieldWindSpeed] >= 4.5) & (df[self.fieldWindSpeed] <= 8)]
  186. # 查找 df 中 self.fieldYawError列的平均值,并向下取整作为偏航误差
  187. max_yaw_error = filtered_df[self.fieldYawError].mean()
  188. max_yaw_error_floor = np.floor(max_yaw_error)
  189. # 存储结果
  190. results.append({
  191. Field_NameOfTurbine: name[0],
  192. Field_YawError: max_yaw_error_floor
  193. })
  194. #对 final_df 中的数据进行可视化
  195. fig = go.Figure()
  196. for col in final_df.columns[1:]: # 跳过第一列 self.fieldWindDirFloor
  197. fig.add_trace(go.Scatter(
  198. x=final_df[self.fieldWindDirFloor],
  199. y=final_df[col],
  200. mode='lines+markers',
  201. name=col
  202. ))
  203. fig.update_layout(
  204. title=f'偏航误差分析 {name[0]} ',
  205. xaxis_title='偏航角度',
  206. yaxis_title='平均功率',
  207. legend_title='风速区间',
  208. showlegend=True
  209. )
  210. # 将NAN转化为空字符串
  211. yData = final_df[col].astype(str).replace(["None", "nan", "null", ""], "")
  212. print("yData:", yData)
  213. # 确保从 Series 中提取的是具体的值
  214. engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "")
  215. if isinstance(engineTypeCode, pd.Series):
  216. engineTypeCode = engineTypeCode.iloc[0]
  217. engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "")
  218. if isinstance(engineTypeName, pd.Series):
  219. engineTypeName = engineTypeName.iloc[0]
  220. # 构建最终的JSON对象
  221. json_output = {
  222. "analysisTypeCode": "偏航误差分析",
  223. "engineCode": engineTypeCode,
  224. "engineTypeName": engineTypeName,
  225. "xaixs": "偏航角度(°)",
  226. "yaixs": "有功功率(kw)",
  227. "data": [
  228. {
  229. "engineName": name[0],
  230. "engineCode": name[1],
  231. "title": f'静态偏航误差分析 {name[0]}',
  232. "xData": final_df[self.fieldWindDirFloor].tolist(),
  233. "yData": yData.tolist(),
  234. "legend": col,
  235. "type": "lines+markers",
  236. }
  237. for col in final_df.columns[1:] # 跳过第一列 self.fieldWindDirFloor
  238. ]
  239. }
  240. # Save to file
  241. filePathOfImage = os.path.join(outputAnalysisDir, f"{name[0]}.png")
  242. fig.write_image(filePathOfImage, scale=3)
  243. # filePathOfHtml = os.path.join(outputAnalysisDir, f"{name[0]}.html")
  244. # fig.write_html(filePathOfHtml)
  245. # 将JSON对象保存到文件
  246. output_json_path = os.path.join(outputAnalysisDir, f"{name[0]}.json")
  247. with open(output_json_path, 'w', encoding='utf-8') as f:
  248. import json
  249. json.dump(json_output, f, ensure_ascii=False, indent=4)
  250. # 如果需要返回DataFrame,可以包含文件路径
  251. result_rows.append({
  252. Field_Return_TypeAnalyst: self.typeAnalyst(),
  253. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  254. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  255. Field_CodeOfTurbine: name[1],
  256. Field_Return_FilePath: output_json_path,
  257. Field_Return_IsSaveDatabase: True
  258. })
  259. result_rows.append({
  260. Field_Return_TypeAnalyst: self.typeAnalyst(),
  261. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  262. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  263. Field_CodeOfTurbine: name[1],
  264. Field_Return_FilePath: filePathOfImage,
  265. Field_Return_IsSaveDatabase: False
  266. })
  267. # result_rows.append({
  268. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  269. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  270. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  271. # Field_CodeOfTurbine: name[1],
  272. # Field_Return_FilePath: filePathOfHtml,
  273. # Field_Return_IsSaveDatabase: True
  274. # })
  275. # # 初始化一个空的DataFrame,指定列名
  276. # columns = [Field_NameOfTurbine, Field_YawError]
  277. # dataFrameResult = pd.DataFrame(results, columns=columns)
  278. # filePathOfYawErrorResult = os.path.join(
  279. # outputAnalysisDir, f"{name[0]}yaw_error_result{CSVSuffix}")
  280. # df.to_csv(filePathOfYawErrorResult, index=False)
  281. # result_rows.append({
  282. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  283. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  284. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  285. # Field_CodeOfTurbine: name[1],
  286. # Field_Return_FilePath: filePathOfYawErrorResult,
  287. # Field_Return_IsSaveDatabase: True
  288. # })
  289. # 初始化一个空的DataFrame,指定列名
  290. columns = [Field_NameOfTurbine, Field_YawError]
  291. dataFrameResult = pd.DataFrame(results, columns=columns)
  292. # 新增三列,分别表示 [0,3], (3,5], (5, 正无穷)
  293. dataFrameResult['[0,3]'] = np.where((abs(dataFrameResult[Field_YawError]) >= 0) & (abs(dataFrameResult[Field_YawError]) <= 3), dataFrameResult[Field_YawError], 0)
  294. dataFrameResult['(3,5]'] = np.where((abs(dataFrameResult[Field_YawError]) > 3) & (abs(dataFrameResult[Field_YawError]) <= 5), dataFrameResult[Field_YawError], 0)
  295. dataFrameResult['(5, )'] = np.where(abs(dataFrameResult[Field_YawError]) > 5, dataFrameResult[Field_YawError], 0)
  296. dataFrameResult["powerloss"]=((1 - np.cos(np.radians(dataFrameResult[Field_YawError])) ** 2) * 100).round(2)
  297. print("损失电量:",dataFrameResult["powerloss"])
  298. filePathOfYawError = os.path.join(
  299. outputAnalysisDir, f"yaw_error_result{CSVSuffix}")
  300. dataFrameResult.to_csv(filePathOfYawError, index=False)
  301. result_rows.append({
  302. Field_Return_TypeAnalyst: self.typeAnalyst(),
  303. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  304. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  305. Field_CodeOfTurbine: "total",
  306. Field_MillTypeCode:"total",
  307. Field_Return_FilePath: filePathOfYawError,
  308. Field_Return_IsSaveDatabase: True
  309. })
  310. result_df = pd.DataFrame(result_rows)
  311. return result_df