yawErrorAnalyst.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import os
  2. import numpy as np
  3. from plotly.subplots import make_subplots
  4. import plotly.graph_objects as go
  5. from scipy.optimize import curve_fit
  6. import matplotlib.pyplot as plt
  7. import pandas as pd
  8. from .analyst import Analyst
  9. from .utils.directoryUtil import DirectoryUtil as dir
  10. from confBusiness import ConfBusiness
  11. class YawErrorAnalyst(Analyst):
  12. """
  13. 风电机组静态偏航误差分析
  14. """
  15. def typeAnalyst(self):
  16. return "yaw_error"
  17. def turbineAnalysis(self,
  18. dataFrame,
  19. outputAnalysisDir,
  20. outputFilePath,
  21. confData: ConfBusiness,
  22. turbineName):
  23. self.yaw_error(dataFrame, outputFilePath,
  24. confData.field_turbine_time, confData.field_angle_included, confData.field_power, confData.field_pitch_angle1)
  25. def yaw_error(self, dataFrame, output_path, field_time, field_angle_included_wind_dir, field_power_active, pitch_col):
  26. # Calculate floor values and other transformations
  27. dataFrame['wind_dir_floor'] = np.floor(dataFrame[field_angle_included_wind_dir]).astype(int)
  28. # dataFrame['wind_dir_floor'] = dataFrame[field_angle_included_wind_dir].round().astype(int)
  29. dataFrame['power'] = dataFrame[field_power_active].astype(float)
  30. dataFrame['time'] = pd.to_datetime(dataFrame[field_time])
  31. # Calculate aggregated metrics for power
  32. grouped = dataFrame.groupby('wind_dir_floor').agg({
  33. 'power': ['mean', 'max', 'min', 'median', lambda x: (x > 0).sum(), lambda x: (x > x.median()).sum()]
  34. }).reset_index()
  35. # Rename columns for clarity
  36. grouped.columns = ['wind_dir_floor', 'mean_power', 'max_power',
  37. 'min_power', 'median_power', 'power_gt_0', 'power_gt_80p']
  38. # Calculate total sums for conditions
  39. power_gt_0_sum = grouped['power_gt_0'].sum()
  40. power_gt_80p_sum = grouped['power_gt_80p'].sum()
  41. # Calculate ratios
  42. grouped['ratio_0'] = grouped['power_gt_0'] / power_gt_0_sum
  43. grouped['ratio_80p'] = grouped['power_gt_80p'] / power_gt_80p_sum
  44. # Filter out zero ratios and calculate slope
  45. grouped = grouped[grouped['ratio_0'] > 0]
  46. grouped['slop'] = grouped['ratio_80p'] / grouped['ratio_0']
  47. # Sort by wind direction floor
  48. grouped.sort_values('wind_dir_floor', inplace=True)
  49. # Write to CSV
  50. grouped.to_csv(output_path, index=False)
  51. def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
  52. self.yaw_result(outputAnalysisDir)
  53. def poly_func(self, x, a, b, c, d, e):
  54. return a * x**4 + b * x ** 3 + c * x ** 2 + d * x + e
  55. def yaw_result(self, csvFileDir):
  56. files = os.listdir(csvFileDir)
  57. for file in files:
  58. old_name = os.path.join(csvFileDir, file)
  59. print(file)
  60. # if os.path.isdir(old_name):
  61. # data_stat(old_name)
  62. # continue
  63. # print(old_name)
  64. if not file.endswith(".csv"):
  65. continue
  66. data_path = old_name
  67. # limit_path = data_path+".limit"
  68. df = pd.read_csv(data_path)
  69. # 不具备普适性,视静态偏航误差输出数据结果调整,经2024-3-13验证输出结果不可信
  70. # df = df[df["wind_dir_floor"] > -12]
  71. # df = df[df["wind_dir_floor"] < 12]
  72. df.dropna(inplace=True)
  73. # drop extreme value, the 3 largest and 3 smallest
  74. df_ = df[df["ratio_80p"] > 0.000]
  75. xdata = df_['wind_dir_floor']
  76. ydata = df_['slop']
  77. # make ydata smooth
  78. ydata = ydata.rolling(7).median()[6:]
  79. xdata = xdata[3:-3]
  80. # Curve fitting
  81. popt, pcov = curve_fit(self.poly_func, xdata, ydata)
  82. # popt contains the optimized parameters a, b, and c
  83. # Generate fitted y-dataFrame using the optimized parameters
  84. fitted_ydata = self.poly_func(xdata, *popt)
  85. # get the max value of fitted_ydata and its index
  86. max_pos = fitted_ydata.idxmax()
  87. # subplots
  88. plt.figure(figsize=(10, 6))
  89. fig = plt.subplot(211)
  90. plt.title(file)
  91. plt.scatter(x=df["wind_dir_floor"], y=df["slop"],
  92. s=5, label="energy gain")
  93. plt.plot(xdata, fitted_ydata, 'r-', label="fit")
  94. plt.scatter(xdata[max_pos], fitted_ydata[max_pos], s=20,
  95. label="max pos:"+str(df["wind_dir_floor"][max_pos]))
  96. plt.legend()
  97. plt.grid(True)
  98. fig = plt.subplot(212)
  99. plt.scatter(x=df["wind_dir_floor"],
  100. y=df["ratio_0"], s=5, label="base energy")
  101. plt.scatter(x=df["wind_dir_floor"],
  102. y=df["ratio_80p"], s=5, label="slope energy")
  103. plt.legend()
  104. plt.grid(True)
  105. plt.savefig(data_path+".png")
  106. plt.close()
  107. # calc squear error of fitted_ydata and ydata
  108. print(file, df["wind_dir_floor"][max_pos])
  109. # def yaw_result(self, csvFileDir):
  110. # files = os.listdir(csvFileDir)
  111. # for file in files:
  112. # if not file.endswith(".csv"):
  113. # continue
  114. # data_path = os.path.join(csvFileDir, file)
  115. # df = pd.read_csv(data_path)
  116. # df.dropna(inplace=True)
  117. # # Filter and smooth data
  118. # df_ = df[df["ratio_80p"] > 0.000]
  119. # xdata = df_['wind_dir_floor']
  120. # ydata = df_['slop'].rolling(7).median()[6:]
  121. # xdata = xdata[3:-3].reset_index(drop=True)
  122. # ydata = ydata.reset_index(drop=True)
  123. # # Curve fitting
  124. # popt, _ = curve_fit(self.poly_func, xdata, ydata)
  125. # fitted_ydata = self.poly_func(xdata, *popt)
  126. # max_pos = fitted_ydata.argmax()
  127. # # Plotting
  128. # fig = make_subplots(rows=2, cols=1)
  129. # # Scatter plot of energy gain
  130. # fig.add_trace(go.Scatter(x=df["wind_dir_floor"], y=df["slop"], mode='markers', name="energy gain", marker=dict(size=5)), row=1, col=1)
  131. # # Line plot of fit
  132. # fig.add_trace(go.Scatter(x=xdata, y=fitted_ydata, mode='lines', name="fit", line=dict(color='red')), row=1, col=1)
  133. # # Marker for max position
  134. # fig.add_trace(go.Scatter(x=[xdata[max_pos]], y=[fitted_ydata[max_pos]], mode='markers', name=f"max pos: {xdata[max_pos]}", marker=dict(size=10, color='orange')), row=1, col=1)
  135. # # Scatter plot of base and slope energy
  136. # fig.add_trace(go.Scatter(x=df["wind_dir_floor"], y=df["ratio_0"], mode='markers', name="base energy", marker=dict(size=5)), row=2, col=1)
  137. # fig.add_trace(go.Scatter(x=df["wind_dir_floor"], y=df["ratio_80p"], mode='markers', name="slope energy", marker=dict(size=5)), row=2, col=1)
  138. # fig.update_layout(height=600, width=800, title_text=file)
  139. # fig.write_image(data_path+".png")
  140. # print(file, xdata[max_pos])