ClassIdentifier_4.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. import os
  2. import numpy as np
  3. from pandas import DataFrame
  4. from utils.draw.draw_file import scatter
  5. from utils.file.trans_methods import read_file_to_df
  6. class ClassIdentifier(object):
  7. def __init__(self, wind_turbine_number, file_path: str = None, origin_df: DataFrame = None,
  8. wind_velocity='wind_velocity',
  9. active_power='active_power',
  10. pitch_angle_blade='pitch_angle_blade_1',
  11. rated_power=1500):
  12. """
  13. :param wind_turbine_number: The wind turbine number.
  14. :param file_path: The file path of the input data.
  15. :param origin_df: The pandas DataFrame containing the input data.
  16. :param wind_velocity: 风速字段
  17. :param active_power: 有功功率字段
  18. :param pitch_angle_blade: 桨距角
  19. :param rated_power: 额定功率
  20. """
  21. self.wind_turbine_number = wind_turbine_number
  22. self.wind_velocity = wind_velocity
  23. self.active_power = active_power
  24. self.pitch_angle_blade = pitch_angle_blade
  25. self.rated_power = rated_power # 额定功率1500kw,可改为2000kw
  26. if file_path is None and origin_df is None:
  27. raise ValueError("Either file_path or origin_df should be provided.")
  28. if file_path:
  29. self.df = read_file_to_df(file_path)
  30. else:
  31. self.df = origin_df
  32. def identifier(self):
  33. # 风速 和 有功功率 df
  34. wind_and_power_df = self.df[[self.wind_velocity, self.active_power, "pitch_angle_blade_1"]]
  35. wind_and_power_df.reset_index(inplace=True)
  36. wind_and_power_df_count = wind_and_power_df.shape[0]
  37. power_max = wind_and_power_df[self.active_power].max()
  38. power_rated = np.ceil(power_max / 100) * 100
  39. v_cut_out = 25
  40. # 网格法确定风速风向分区数量,功率方向分区数量,
  41. p_num = int(np.ceil(power_rated / 25)) # 功率分区间隔25kW
  42. v_num = int(np.ceil(v_cut_out / 0.25)) # 风速分区间隔0.25m/s
  43. # 存储功率大于零的运行数据
  44. dz_march = np.zeros([wind_and_power_df_count, 2], dtype=float)
  45. n_counter1 = 0
  46. for i in range(wind_and_power_df_count):
  47. if wind_and_power_df.loc[i, self.active_power] > 0:
  48. dz_march[n_counter1, 0] = wind_and_power_df.loc[i, self.wind_velocity]
  49. dz_march[n_counter1, 1] = wind_and_power_df.loc[i, self.active_power]
  50. n_counter1 = n_counter1 + 1
  51. # 统计各网格落入的散点个数
  52. if v_num == 1:
  53. x_box_number = np.ones([p_num], dtype=int)
  54. else:
  55. x_box_number = np.ones([p_num, v_num], dtype=int)
  56. n_which_p = -1
  57. n_which_v = -1
  58. for i in range(n_counter1):
  59. for m in range(p_num):
  60. if m * 25 < dz_march[i, 1] <= (m + 1) * 25:
  61. n_which_p = m
  62. break
  63. for n in range(v_num):
  64. if ((n + 1) * 0.25 - 0.125) < dz_march[i, 0] <= ((n + 1) * 0.25 + 0.125):
  65. n_which_v = n
  66. break
  67. if n_which_p > -1 and n_which_v > -1:
  68. x_box_number[n_which_p, n_which_v] = x_box_number[n_which_p, n_which_v] + 1
  69. for m in range(p_num):
  70. for n in range(v_num):
  71. x_box_number[m, n] = x_box_number[m, n] - 1
  72. # 在功率方向将网格内散点绝对个数转换为相对百分比,备用
  73. p_box_percent = np.zeros([p_num, v_num], dtype=float)
  74. p_bin_sum = np.zeros(p_num, dtype=int)
  75. for i in range(p_num):
  76. for m in range(v_num):
  77. p_bin_sum[i] = p_bin_sum[i] + x_box_number[i, m]
  78. for m in range(v_num):
  79. if p_bin_sum[i] > 0:
  80. p_box_percent[i, m] = x_box_number[i, m] / p_bin_sum[i] * 100
  81. # 在风速方向将网格内散点绝对个数转换为相对百分比,备用
  82. v_box_percent = np.zeros([p_num, v_num], dtype=float)
  83. v_bin_sum = np.zeros(v_num, dtype=int)
  84. for i in range(v_num):
  85. for m in range(p_num):
  86. v_bin_sum[i] = v_bin_sum[i] + x_box_number[m, i]
  87. for m in range(p_num):
  88. if v_bin_sum[i] > 0:
  89. v_box_percent[m, i] = x_box_number[m, i] / v_bin_sum[i] * 100
  90. # 以水平功率带方向为准,分析每个水平功率带中,功率主带中心,即找百分比最大的网格位置。
  91. p_box_max_index = np.zeros(p_num, dtype=int) # 水平功率带最大网格位置索引
  92. p_box_max_p = np.zeros(p_num, dtype=int) # 水平功率带最大网格百分比
  93. for m in range(p_num):
  94. # 确定每一水平功率带的最大网格位置索引即百分比值
  95. p_box_max_p[m], p_box_max_index[m] = p_box_percent[m, :].max(), p_box_percent[m, :].argmax()
  96. # 以垂直风速方向为准,分析每个垂直风速带中,功率主带中心,即找百分比最大的网格位置。
  97. v_box_max_index = np.zeros(v_num, dtype=int)
  98. v_box_max_v = np.zeros(v_num, dtype=int)
  99. for m in range(v_num):
  100. [v_box_max_v[m], v_box_max_index[m]] = v_box_percent[:, m].max(), v_box_percent[:, m].argmax()
  101. # 切入风速特殊处理,如果切入风速过于偏右,向左拉回
  102. if p_box_max_index[0] > 14:
  103. p_box_max_index[0] = 9
  104. # 以水平功率带方向为基准,进行分析
  105. dot_dense = np.zeros(p_num, dtype=int) # 每一水平功率带的功率主带包含的网格数
  106. dot_dense_left_right = np.zeros([p_num, 2], dtype=int) # 存储每一水平功率带的功率主带以最大网格为中心,向向左,向右扩展的网格数
  107. dot_valve = 90 # 从中心向左右对称扩展网格的散点百分比和的阈值。
  108. for i in range(p_num - 6): # 从最下层水平功率带1开始,向上到第PNum-6个水平功率带(额定功率一下水平功率带),逐一分析
  109. p_dot_dense_sum = p_box_max_p[i] # 以中心最大水平功率带为基准,向左向右对称扩展网格,累加各网格散点百分比
  110. i_spread_right = 1
  111. i_spread_left = 1
  112. while p_dot_dense_sum < dot_valve:
  113. if (p_box_max_index[i] + i_spread_right) < v_num - 1:
  114. p_dot_dense_sum = p_dot_dense_sum + p_box_percent[i, p_box_max_index[i] + i_spread_right] # 向右侧扩展
  115. i_spread_right = i_spread_right + 1
  116. if (p_box_max_index[i] + i_spread_right) > v_num - 1:
  117. break
  118. if (p_box_max_index[i] - i_spread_left) > 0:
  119. p_dot_dense_sum = p_dot_dense_sum + p_box_percent[i, p_box_max_index[i] - i_spread_left] # 向左侧扩展
  120. i_spread_left = i_spread_left + 1
  121. if (p_box_max_index[i] - i_spread_left) <= 0:
  122. break
  123. i_spread_right = i_spread_right - 1
  124. i_spread_left = i_spread_left - 1
  125. # 向左右对称扩展完毕
  126. dot_dense_left_right[i, 0] = i_spread_left
  127. dot_dense_left_right[i, 1] = i_spread_right
  128. dot_dense[i] = i_spread_left + i_spread_right + 1
  129. # 各行功率主带右侧宽度的中位数最具有代表性
  130. dot_dense_width_left = np.zeros([p_num - 6, 1], dtype=int)
  131. for i in range(p_num - 6):
  132. dot_dense_width_left[i] = dot_dense_left_right[i, 1]
  133. main_band_right = np.median(dot_dense_width_left)
  134. # 散点向右显著延展分布的水平功率带为限功率水平带
  135. power_limit = np.zeros([p_num, 1], dtype=int) # 各水平功率带是否为限功率标识,==1:是;==0:不是
  136. width_average = 0 # 功率主带平均宽度
  137. width_var = 0 # 功率主带方差
  138. # power_limit_valve = 6 #限功率主带判别阈值
  139. power_limit_valve = np.ceil(main_band_right) + 3 # 限功率主带判别阈值
  140. n_counter_limit = 0
  141. n_counter = 0
  142. for i in range(p_num - 6):
  143. if dot_dense_left_right[i, 1] > power_limit_valve and p_bin_sum[i] > 20: # 如果向右扩展网格数大于阈值,且该水平功率带点总数>20,是
  144. power_limit[i] = 1
  145. n_counter_limit = n_counter_limit + 1
  146. if dot_dense_left_right[i, 1] <= power_limit_valve:
  147. width_average = width_average + dot_dense_left_right[i, 1] # 统计正常水平功率带右侧宽度
  148. n_counter = n_counter + 1
  149. width_average = width_average / n_counter # 功率主带平均宽度
  150. # 各水平功率带的功率主带宽度的方差,反映从下到上宽度是否一致,或是否下宽上窄等异常情况
  151. for i in range(p_num - 6):
  152. if dot_dense_left_right[i, 1] <= power_limit_valve:
  153. width_var = width_var + (dot_dense_left_right[i, 1] - width_average) * (
  154. dot_dense_left_right[i, 1] - width_average)
  155. # 对限负荷水平功率带的最大网格较下面相邻层显著偏右,拉回
  156. for i in range(1, p_num - 6):
  157. if power_limit[i] == 1 and abs(p_box_max_index[i] - p_box_max_index[i - 1]) > 5:
  158. p_box_max_index[i] = p_box_max_index[i - 1] + 1
  159. # 输出各层功率主带的左右边界网格索引
  160. dot_dense_inverse = np.zeros([p_num, 2], dtype=int)
  161. for i in range(p_num):
  162. dot_dense_inverse[i, :] = dot_dense_left_right[p_num - i - 1, :]
  163. # 功率主带的右边界
  164. curve_width_r = int(np.ceil(width_average) + 2)
  165. # curve_width_l = 6 #功率主带的左边界
  166. curve_width_l = curve_width_r
  167. b_box_limit = np.zeros([p_num, v_num], dtype=int) # 网格是否为限功率网格的标识,如果为限功率水平功率带,从功率主带右侧边缘向右的网格为限功率网格
  168. for i in range(2, p_num - 6):
  169. if power_limit[i] == 1:
  170. for j in range(p_box_max_index[i] + curve_width_r, v_num):
  171. b_box_limit[i, j] = 1
  172. b_box_remove = np.zeros([p_num, v_num], dtype=int) # 数据异常需要剔除的网格标识,标识==1:功率主带右侧的欠发网格;==2:功率主带左侧的超发网格
  173. for m in range(p_num - 6):
  174. for n in range(p_box_max_index[m] + curve_width_r, v_num):
  175. b_box_remove[m, n] = 1
  176. for n in range(p_box_max_index[m] - curve_width_l, -1, -1):
  177. b_box_remove[m, n] = 2
  178. # 确定功率主带的左上拐点,即额定风速位置的网格索引
  179. curve_top = np.zeros(2, dtype=int)
  180. curve_top_valve = 3 # 网格的百分比阈值
  181. b_top_find = 0
  182. for m in range(p_num - 4 - 1, -1, -1):
  183. for n in range(v_num):
  184. if v_box_percent[m, n] > curve_top_valve and x_box_number[m, n] >= 10: # 如左上角网格的百分比和散点个数大于阈值。
  185. curve_top[0] = m
  186. curve_top[1] = n
  187. b_top_find = 1
  188. break
  189. if b_top_find == 1:
  190. break
  191. isolate_valve = 3
  192. for m in range(p_num - 6):
  193. for n in range(p_box_max_index[m] + curve_width_r, v_num):
  194. if p_box_percent[m, n] < isolate_valve:
  195. b_box_remove[m, n] = 1
  196. # 功率主带顶部宽度
  197. curve_width_t = 2
  198. for m in range(p_num - curve_width_t - 1, p_num):
  199. for n in range(v_num):
  200. b_box_remove[m, n] = 3 # 网格为额定功率以上的超发点
  201. # 功率主带拐点左侧的欠发网格标识
  202. for m in range(p_num - 5 - 1, p_num):
  203. for n in range(curve_top[1] - 1):
  204. b_box_remove[m, n] = 2
  205. # 以网格的标识,决定该网格内数据的标识。dzwind_and_power_sel。散点在哪个网格,此网格的标识即为该点的标识
  206. dzwind_and_power_sel = np.zeros(n_counter1, dtype=int) # -1:停机 0:好点 1:欠发功率点;2:超发功率点;3:额定风速以上的超发功率点 4: 限电
  207. n_which_p = -1
  208. n_which_v = -1
  209. n_bad_a = 0
  210. for i in range(n_counter1):
  211. for m in range(p_num):
  212. if m * 25 < dz_march[i, 1] <= (m + 1) * 25:
  213. n_which_p = m
  214. break
  215. for n in range(v_num):
  216. if ((n + 1) * 0.25 - 0.125) < dz_march[i, 0] <= ((n + 1) * 0.25 + 0.125):
  217. n_which_v = n
  218. break
  219. if n_which_p > -1 and n_which_v > -1:
  220. if b_box_remove[n_which_p, n_which_v] == 1:
  221. dzwind_and_power_sel[i] = 1
  222. n_bad_a = n_bad_a + 1
  223. if b_box_remove[n_which_p, n_which_v] == 2:
  224. dzwind_and_power_sel[i] = 2
  225. if b_box_remove[n_which_p, n_which_v] == 3:
  226. dzwind_and_power_sel[i] = 0 # 3 # 额定风速以上的超发功率点认为是正常点,不再标识。
  227. # 限负荷数据标识方法2:把数据切割为若干个窗口。对每一窗口,以第一个点为基准,连续nWindowLength个数据的功率在方差范围内,呈现显著水平分布的点
  228. n_window_length = 3
  229. limit_window = np.zeros(n_window_length, dtype=float)
  230. power_std = 15 # 功率波动方差
  231. n_window_num = int(np.floor(n_counter1 / n_window_length))
  232. power_limit_up = self.rated_power - 300
  233. power_limit_low = 200
  234. for i in range(n_window_num):
  235. for j in range(n_window_length):
  236. limit_window[j] = dz_march[i * n_window_length + j, 1]
  237. b_all_in_areas = 1
  238. for j in range(n_window_length):
  239. if limit_window[j] < power_limit_low or limit_window[j] > power_limit_up:
  240. b_all_in_areas = 0
  241. if b_all_in_areas == 0:
  242. continue
  243. up_limit = limit_window[0] + power_std
  244. low_limit = limit_window[0] - power_std
  245. b_all_in_up_low = 1
  246. for j in range(1, n_window_length):
  247. if limit_window[j] < low_limit or limit_window[j] > up_limit:
  248. b_all_in_up_low = 0
  249. if b_all_in_up_low == 1:
  250. for j in range(n_window_length):
  251. dzwind_and_power_sel[i * n_window_length + j] = 4 # 标识窗口内的数据为限负荷数据
  252. for i in range(p_num - 6):
  253. pv_left_down = np.zeros(2, dtype=float)
  254. pv_right_up = np.zeros(2, dtype=float)
  255. if (p_box_max_index[i + 1] - p_box_max_index[i]) >= 1:
  256. pv_left_down[0] = (p_box_max_index[i] + 1 + curve_width_r) * 0.25 - 0.125
  257. pv_left_down[1] = i * 25
  258. pv_right_up[0] = (p_box_max_index[i + 1] + 1 + curve_width_r) * 0.25 - 0.125
  259. pv_right_up[1] = (i + 1) * 25
  260. for m in range(n_counter1):
  261. if pv_left_down[0] < dz_march[m, 0] < pv_right_up[0] and pv_left_down[1] < \
  262. dz_march[m, 1] < pv_right_up[1]: # 在该锯齿中
  263. if (dz_march[m, 1] - pv_left_down[1]) / (dz_march[m, 0] - pv_left_down[0]) > (
  264. pv_right_up[1] - pv_left_down[1]) / (
  265. pv_right_up[0] - pv_left_down[0]): # 斜率大于对角连线,则在锯齿左上三角形中,选中
  266. dzwind_and_power_sel[m] = 0
  267. wind_and_power_df.loc[:, 'marker'] = -1
  268. wind_and_power_df.loc[
  269. wind_and_power_df[wind_and_power_df[self.active_power] > 0].index, 'marker'] = dzwind_and_power_sel
  270. # 把部分欠发的优化为限电
  271. # 构建条件表达式
  272. cond1 = (wind_and_power_df['marker'] == 1) & (
  273. (wind_and_power_df[self.active_power] < self.rated_power * 0.75) &
  274. (wind_and_power_df[self.pitch_angle_blade] > 0.5)
  275. )
  276. cond2 = (wind_and_power_df['marker'] == 1) & (
  277. (wind_and_power_df[self.active_power] < self.rated_power * 0.85) &
  278. (wind_and_power_df[self.pitch_angle_blade] > 1.5)
  279. )
  280. cond3 = (wind_and_power_df['marker'] == 1) & (
  281. (wind_and_power_df[self.active_power] < self.rated_power * 0.9) &
  282. (wind_and_power_df[self.pitch_angle_blade] > 2.5)
  283. )
  284. # 使用逻辑或操作符|合并条件
  285. combined_condition = cond1 | cond2 | cond3
  286. wind_and_power_df.loc[combined_condition, 'marker'] = 4
  287. return wind_and_power_df
  288. def run(self):
  289. # Implement your class identification logic here
  290. return self.identifier()
  291. if __name__ == '__main__':
  292. test = ClassIdentifier('test', r"D:\中能智能\matlib计算相关\好点坏点matlib计算\WOG00436.csv",
  293. wind_velocity='wind_velocity',
  294. active_power='active_power',
  295. pitch_angle_blade='pitch_angle_blade_1',
  296. rated_power=1500
  297. )
  298. df = test.run()
  299. df.to_csv("tet.csv", encoding="utf8")
  300. color_map = {-1: 'red', 0: 'green', 1: 'blue', 2: 'black', 3: 'orange', 4: 'magenta'}
  301. c = df['marker'].map(color_map)
  302. # -1:停机 0:好点 1:欠发功率点;2:超发功率点;3:额定风速以上的超发功率点 4: 限电
  303. legend_map = {"停机": 'red', "好点": 'green', "欠发": 'blue', "超发": 'black', "额定风速以上的超发": 'orange', "限电": 'magenta'}
  304. scatter("测试matlab结果", x_label='风速', y_label='有功功率', x_values=df[test.wind_velocity].values,
  305. y_values=df[test.active_power].values, color=c, col_map=legend_map,
  306. save_file_path=os.path.dirname(__file__) + os.sep + '元梁山测试matlab结果均值.png')