dataMarkerOfScada3.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. import numpy as np
  2. import pandas as pd
  3. import os
  4. import plotly.graph_objects as go
  5. # 声明全局变量
  6. fieldActivePower = "功率"
  7. fieldWindSpeed = "风速"
  8. fieldPitchAngle = "叶片角度"
  9. PRated = 1500 # 风机额定功率
  10. VCutOut = 25 # 切出风速
  11. VCutIn = 3 # 切入风速
  12. VRated = 10 # 额定风速
  13. VNum = int(VCutOut / 0.25) # 风速分区数量
  14. # 读取数据函数
  15. def read_data():
  16. # 读取 CSV 文件
  17. March809 = pd.read_csv(
  18. "./data/DataClassificationIdentification/A01-G.csv", encoding="utf-8")
  19. IdealCurve = pd.read_csv(
  20. "./data/DataClassificationIdentification/A型风机设计功率曲线.csv", encoding="utf-8")
  21. return March809, IdealCurve
  22. # 计算统计信息函数
  23. def calculate_statistics(March809, IdealCurve):
  24. AA = len(March809)
  25. BB = len(IdealCurve)
  26. PowerMax = March809[fieldActivePower].max()
  27. PowerRated = int(np.ceil(PowerMax / 100) * 100)
  28. PNum = PowerRated // 25 # 功率分区数量
  29. # 计算实际发电量
  30. EPActualTotal = March809[March809[fieldActivePower]
  31. >= 0][fieldActivePower].sum() / 6
  32. WindSpeedAvr = March809[fieldWindSpeed].mean()
  33. # 计算风机可利用率
  34. nShouldGP = np.sum(March809[fieldWindSpeed] >= VCutIn)
  35. nRealGP = np.sum((March809[fieldWindSpeed] >= VCutIn)
  36. & (March809[fieldActivePower] > 0))
  37. TurbineRunRate = (nRealGP / nShouldGP * 100) if nShouldGP > 0 else 0
  38. # 计算理论发电量
  39. EPIdealTotalAAA = 0
  40. for i in range(AA):
  41. nWhichBin = 0
  42. for m in range(BB - 1):
  43. if IdealCurve.iloc[m][fieldWindSpeed] < March809.iloc[i][fieldWindSpeed] <= IdealCurve.iloc[m + 1][fieldWindSpeed]:
  44. nWhichBin = m
  45. break
  46. if nWhichBin > 0:
  47. IdealPower = (March809.iloc[i][fieldWindSpeed] - IdealCurve.iloc[nWhichBin][fieldWindSpeed]) / (IdealCurve.iloc[nWhichBin + 1][fieldWindSpeed] - IdealCurve.iloc[nWhichBin]
  48. [fieldWindSpeed]) * (IdealCurve.iloc[nWhichBin + 1][fieldActivePower] - IdealCurve.iloc[nWhichBin][fieldActivePower]) + IdealCurve.iloc[nWhichBin][fieldActivePower]
  49. EPIdealTotalAAA += IdealPower / 6
  50. return AA, BB, PNum, EPActualTotal, WindSpeedAvr, TurbineRunRate, EPIdealTotalAAA
  51. # 分类数据函数
  52. def classify_data(March809, PNum, VNum):
  53. DzMarch809 = March809[March809[fieldActivePower] > 0]
  54. nCounter1 = len(DzMarch809)
  55. XBoxNumber = np.ones((PNum, VNum), dtype=int)
  56. for i in range(nCounter1):
  57. nWhichP = np.digitize(
  58. DzMarch809.iloc[i][fieldActivePower], np.arange(0, PNum * 25, 25)) - 1
  59. nWhichV = np.digitize(
  60. DzMarch809.iloc[i][fieldWindSpeed], np.arange(0.125, VNum * 0.25, 0.25))
  61. if nWhichP < PNum and nWhichV < VNum:
  62. XBoxNumber[nWhichP, nWhichV] += 1
  63. XBoxNumber -= 1
  64. return DzMarch809, XBoxNumber
  65. # 计算百分比函数
  66. def compute_percentages(XBoxNumber, PNum, VNum):
  67. PBoxPercent = np.zeros((PNum, VNum))
  68. PBinSum = XBoxNumber.sum(axis=1)
  69. for i in range(PNum):
  70. if PBinSum[i] > 0:
  71. PBoxPercent[i, :] = XBoxNumber[i, :] / PBinSum[i] * 100
  72. VBoxPercent = np.zeros((PNum, VNum))
  73. VBinSum = XBoxNumber.sum(axis=0)
  74. for i in range(VNum):
  75. if VBinSum[i] > 0:
  76. VBoxPercent[:, i] = XBoxNumber[:, i] / VBinSum[i] * 100
  77. return PBoxPercent, VBoxPercent
  78. # 查找主带函数
  79. def find_main_band(PBoxPercent, PNum, VNum, XBoxNumber):
  80. PBoxMaxIndex = np.argmax(PBoxPercent, axis=1)
  81. PBoxMaxP = np.max(PBoxPercent, axis=1)
  82. DotDense = np.zeros(PNum)
  83. DotDenseLeftRight = np.zeros((PNum, 2), dtype=int)
  84. DotValve = 90
  85. for i in range(PNum - 6):
  86. PDotDenseSum = PBoxMaxP[i]
  87. iSpreadRight = iSpreadLeft = 1
  88. while PDotDenseSum < DotValve:
  89. if (PBoxMaxIndex[i] + iSpreadRight) < VNum - 1:
  90. PDotDenseSum += PBoxPercent[i, PBoxMaxIndex[i] + iSpreadRight]
  91. iSpreadRight += 1
  92. if (PBoxMaxIndex[i] + iSpreadRight) > VNum - 1:
  93. break
  94. if (PBoxMaxIndex[i] - iSpreadLeft) > 0:
  95. PDotDenseSum += PBoxPercent[i, PBoxMaxIndex[i] - iSpreadLeft]
  96. iSpreadLeft += 1
  97. if (PBoxMaxIndex[i] - iSpreadLeft) <= 0:
  98. break
  99. iSpreadRight -= 1
  100. iSpreadLeft -= 1
  101. DotDenseLeftRight[i, :] = [iSpreadLeft, iSpreadRight]
  102. DotDense[i] = iSpreadLeft + iSpreadRight + 1
  103. DotDenseWidthLeft = DotDenseLeftRight[:, 1]
  104. MainBandRight = np.median(DotDenseWidthLeft)
  105. PowerLimitValve = int(np.ceil(MainBandRight)) + 3
  106. PowerLimit = np.zeros(PNum)
  107. nCounterLimit = nCounter = 0
  108. WidthAverage = 0
  109. for i in range(PNum - 6):
  110. if DotDenseLeftRight[i, 1] > PowerLimitValve and XBoxNumber[i, :].sum() > 20:
  111. PowerLimit[i] = 1
  112. nCounterLimit += 1
  113. else:
  114. WidthAverage += DotDenseLeftRight[i, 1]
  115. nCounter += 1
  116. WidthAverage /= nCounter
  117. WidthVar = np.sqrt(np.mean((DotDenseLeftRight[:, 1] - WidthAverage) ** 2))
  118. PowerBandWidth = WidthAverage * 2 * 0.25
  119. return DotDense, DotDenseLeftRight, PowerLimit, WidthAverage, WidthVar, PowerBandWidth, PBoxMaxIndex
  120. # 标记坏点函数
  121. def mark_bad_points(DzMarch809, DotDenseLeftRight, PBoxMaxIndex, PowerLimit, PNum, VNum, XBoxNumber, PBoxPercent):
  122. CurveWidthR = int(np.ceil(DotDenseLeftRight[:, 1].mean())) + 2
  123. CurveWidthL = CurveWidthR
  124. BBoxLimit = np.zeros((PNum, VNum))
  125. for i in range(3, PNum - 6):
  126. if PowerLimit[i] == 1:
  127. BBoxLimit[i, PBoxMaxIndex[i] + CurveWidthR + 1: VNum] = 1
  128. BBoxRemove = np.zeros((PNum, VNum))
  129. for m in range(PNum - 6):
  130. BBoxRemove[m, PBoxMaxIndex[m] + CurveWidthR: VNum] = 1
  131. BBoxRemove[m, :PBoxMaxIndex[m] - CurveWidthL + 1] = 2
  132. CurveTop = [0, 0]
  133. CurveTopValve = 3
  134. BTopFind = False
  135. for m in range(PNum - 4, 0, -1):
  136. for n in range(VNum):
  137. if PBoxPercent[m, n] > CurveTopValve and XBoxNumber[m, n] >= 10:
  138. CurveTop = [m, n]
  139. BTopFind = True
  140. break
  141. if BTopFind:
  142. break
  143. IsolateValve = 3
  144. for m in range(PNum - 6):
  145. for n in range(PBoxMaxIndex[m] + CurveWidthR, VNum):
  146. if PBoxPercent[m, n] < IsolateValve:
  147. BBoxRemove[m, n] = 1
  148. for m in range(PNum - 6, PNum):
  149. BBoxRemove[m, :] = 3
  150. for m in range(PNum - 5, PNum):
  151. BBoxRemove[m, :CurveTop[1] - 2] = 2
  152. DzMarch809Sel = np.zeros(len(DzMarch809), dtype=int)
  153. for i in range(len(DzMarch809)):
  154. nWhichP = np.digitize(
  155. DzMarch809.iloc[i][fieldActivePower], np.arange(0, PNum * 25, 25)) - 1
  156. nWhichV = np.digitize(
  157. DzMarch809.iloc[i][fieldWindSpeed], np.arange(0.125, VNum * 0.25, 0.25))
  158. # if (
  159. # (DzMarch809.iloc[i][fieldActivePower] < PRated * 0.75) and
  160. # (DzMarch809.iloc[i][fieldPitchAngle] > 0.5) or
  161. # (DzMarch809.iloc[i][fieldActivePower] < PRated * 0.85) and
  162. # (DzMarch809.iloc[i][fieldPitchAngle] > 1.5) or
  163. # (DzMarch809.iloc[i][fieldActivePower] < PRated * 0.9) and
  164. # (DzMarch809.iloc[i][fieldPitchAngle] > 2.5)
  165. # ):
  166. # continue
  167. if nWhichP < PNum and nWhichV < VNum:
  168. if BBoxRemove[nWhichP, nWhichV] == 1:
  169. DzMarch809Sel[i] = 1
  170. elif BBoxRemove[nWhichP, nWhichV] == 2:
  171. DzMarch809Sel[i] = 2
  172. elif BBoxRemove[nWhichP, nWhichV] == 3:
  173. DzMarch809Sel[i] = 0
  174. return DzMarch809Sel
  175. def identify_limit_load_data(DzMarch809: pd.DataFrame, DzMarch809Sel, PRated):
  176. nCounter1 = len(DzMarch809)
  177. PVLimit = np.zeros((nCounter1, 2))
  178. nLimitTotal = 0
  179. nWindowLength = 3
  180. PowerStd = 15 # 功率波动方差
  181. PowerLimitUp = PRated - 300
  182. PowerLimitLow = 5 # 200kW
  183. nWindowNum = nCounter1 // nWindowLength
  184. for i in range(nWindowNum):
  185. LimitWindow = DzMarch809.iloc[i * nWindowLength:(i + 1) * nWindowLength][fieldActivePower].values
  186. # 检查所有数据是否在 PowerLimitLow值~PowerLimitUp值范围内
  187. if not ((LimitWindow >= PowerLimitLow) & (LimitWindow <= PowerLimitUp)).all():
  188. continue
  189. """
  190. 限功率识别策略(参考):
  191. 1. 功率<额定功率(PRated)*0.75 and 叶片角度>0.5 ; 2. 功率<额定功率*0.85 and 叶片角度>1.5 ; 3. 功率<额定功率*0.9 and 叶片角度>2.5;
  192. 示例:
  193. 1. 功率<1100,and 叶片角度>0.5 ; 2. 功率<1250 and 叶片角度>1.5 ; 3. 功率<1400 and 叶片角度>2.5 ;
  194. """
  195. # 额外的限功率识别策略
  196. pitch_angle_window = DzMarch809.iloc[i * nWindowLength:(i + 1) * nWindowLength][fieldPitchAngle].values
  197. if (
  198. (DzMarch809.iloc[i * nWindowLength:(i + 1) * nWindowLength][fieldActivePower] < PRated * 0.75).any() and
  199. (pitch_angle_window > 0.5).any() or
  200. (DzMarch809.iloc[i * nWindowLength:(i + 1) * nWindowLength][fieldActivePower] < PRated * 0.85).any() and
  201. (pitch_angle_window > 1.5).any() or
  202. (DzMarch809.iloc[i * nWindowLength:(i + 1) * nWindowLength][fieldActivePower] < PRated * 0.9).any() and
  203. (pitch_angle_window > 2.5).any()
  204. ):
  205. # 标识限负荷数据
  206. DzMarch809Sel[i * nWindowLength:(i + 1) * nWindowLength] = 4
  207. UpLimit = LimitWindow[0] + PowerStd
  208. LowLimit = LimitWindow[0] - PowerStd
  209. # 检查所有数据是否在方差范围内
  210. if not ((LimitWindow[1:] >= LowLimit) & (LimitWindow[1:] <= UpLimit)).all():
  211. continue
  212. # 标识限负荷数据
  213. DzMarch809Sel[i * nWindowLength:(i + 1) * nWindowLength] = 4
  214. for j in range(nWindowLength):
  215. # 只提取功率和风速数据
  216. PVLimit[nLimitTotal] = DzMarch809.iloc[i * nWindowLength + j][[fieldWindSpeed, fieldActivePower]]
  217. nLimitTotal += 1
  218. PVLimit = PVLimit[:nLimitTotal] # 截取实际数据部分
  219. return PVLimit, DzMarch809Sel
  220. # 计算能量损失函数
  221. def calculate_energy_loss(DzMarch809Sel, DzMarch809, IdealCurve, PNum, BB, PRated, EPIdealTotalAAA, EPActualTotal):
  222. EPLostStopTotal = 0
  223. nStopTotal = 0
  224. for i in range(len(DzMarch809)):
  225. if DzMarch809.iloc[i][fieldActivePower] <= 0:
  226. nWhichBin = 0
  227. for m in range(BB - 1):
  228. if IdealCurve.iloc[m][fieldWindSpeed] < DzMarch809.iloc[i][fieldWindSpeed] <= IdealCurve.iloc[m + 1][fieldWindSpeed]:
  229. nWhichBin = m
  230. break
  231. if nWhichBin > 0:
  232. IdealPower = (DzMarch809.iloc[i][fieldWindSpeed] - IdealCurve.iloc[nWhichBin][fieldWindSpeed]) / (IdealCurve.iloc[nWhichBin + 1][fieldWindSpeed] - IdealCurve.iloc[nWhichBin]
  233. [fieldWindSpeed]) * (IdealCurve.iloc[nWhichBin + 1][fieldActivePower] - IdealCurve.iloc[nWhichBin][fieldActivePower]) + IdealCurve.iloc[nWhichBin][fieldActivePower]
  234. EPLostStopTotal += IdealPower / 6
  235. nStopTotal += 1
  236. EPLostBadTotal = 0
  237. EPLost = 0
  238. nBadTotal = 0
  239. for i in range(len(DzMarch809)):
  240. if DzMarch809Sel[i] == 1:
  241. nWhichBin = 0
  242. for m in range(BB - 1):
  243. if IdealCurve.iloc[m][fieldWindSpeed] < DzMarch809.iloc[i][fieldWindSpeed] <= IdealCurve.iloc[m + 1][fieldWindSpeed]:
  244. nWhichBin = m
  245. break
  246. if nWhichBin > 0:
  247. IdealPower = (DzMarch809.iloc[i][fieldWindSpeed] - IdealCurve.iloc[nWhichBin][fieldWindSpeed]) / (IdealCurve.iloc[nWhichBin + 1][fieldWindSpeed] - IdealCurve.iloc[nWhichBin]
  248. [fieldWindSpeed]) * (IdealCurve.iloc[nWhichBin + 1][fieldActivePower] - IdealCurve.iloc[nWhichBin][fieldActivePower]) + IdealCurve.iloc[nWhichBin][fieldActivePower]
  249. EPLost += abs(IdealPower -
  250. DzMarch809.iloc[i][fieldActivePower]) / 6
  251. EPLostBadTotal += EPLost
  252. nBadTotal += 1
  253. EPOverTotal = 0
  254. nOverTotal = 0
  255. for i in range(len(DzMarch809)):
  256. if DzMarch809Sel[i] == 3:
  257. EPOver = (DzMarch809.iloc[i][fieldActivePower] - PRated) / 6
  258. EPOverTotal += EPOver
  259. nOverTotal += 1
  260. EPLostPerformTotal = 0
  261. for i in range(len(DzMarch809)):
  262. nWhichBinI = 0
  263. for m in range(BB - 1):
  264. if IdealCurve.iloc[m][fieldWindSpeed] < DzMarch809.iloc[i][fieldWindSpeed] <= IdealCurve.iloc[m + 1][fieldWindSpeed]:
  265. nWhichBinI = m
  266. break
  267. if nWhichBinI > 0:
  268. IdealPower = (DzMarch809.iloc[i][fieldWindSpeed] - IdealCurve.iloc[nWhichBinI][fieldWindSpeed]) / (IdealCurve.iloc[nWhichBinI + 1][fieldWindSpeed] - IdealCurve.iloc[nWhichBinI]
  269. [fieldWindSpeed]) * (IdealCurve.iloc[nWhichBinI + 1][fieldActivePower] - IdealCurve.iloc[nWhichBinI][fieldActivePower]) + IdealCurve.iloc[nWhichBinI][fieldActivePower]
  270. EPLostPerformTotal += (IdealPower -
  271. DzMarch809.iloc[i][fieldActivePower]) / 6
  272. EPIdealTotal = EPActualTotal + EPLostStopTotal + EPLostBadTotal + \
  273. EPLostPerformTotal if EPLostPerformTotal >= 0 else EPActualTotal + \
  274. EPLostStopTotal + EPLostBadTotal
  275. RemoveOverEP = 0
  276. for i in range(len(DzMarch809)):
  277. if DzMarch809Sel[i] == 2:
  278. nWhichBin = 0
  279. for m in range(BB - 1):
  280. if IdealCurve.iloc[m][fieldWindSpeed] < DzMarch809.iloc[i][fieldWindSpeed] <= IdealCurve.iloc[m + 1][fieldWindSpeed]:
  281. nWhichBin = m
  282. break
  283. if nWhichBin > 0:
  284. IdealPower = (DzMarch809.iloc[i][fieldWindSpeed] - IdealCurve.iloc[nWhichBin][fieldWindSpeed]) / (IdealCurve.iloc[nWhichBin + 1][fieldWindSpeed] - IdealCurve.iloc[nWhichBin]
  285. [fieldWindSpeed]) * (IdealCurve.iloc[nWhichBin + 1][fieldActivePower] - IdealCurve.iloc[nWhichBin][fieldActivePower]) + IdealCurve.iloc[nWhichBin][fieldActivePower]
  286. RemoveOverEP += (DzMarch809.iloc[i]
  287. [fieldActivePower] - IdealPower) / 6
  288. for i in range(len(DzMarch809)):
  289. if DzMarch809.iloc[i][fieldActivePower] > PRated:
  290. RemoveOverEP += (DzMarch809.iloc[i][fieldActivePower] - PRated) / 6
  291. return EPLostStopTotal, EPLostBadTotal, EPOverTotal, EPLostPerformTotal, EPIdealTotal - RemoveOverEP
  292. # 计算实测功率曲线函数
  293. def calculate_measured_power_curve(PVDot, VRated, PRated):
  294. XBinNumber = np.ones(50)
  295. PCurve = np.zeros((50, 2))
  296. PCurve[:, 0] = np.arange(0.5, 25.5, 0.5)
  297. XBinSum = np.zeros((50, 2))
  298. for i in range(len(PVDot)):
  299. nWhichBin = 0
  300. for b in range(50):
  301. if (b * 0.5 - 0.25) < PVDot.iloc[i][fieldWindSpeed] <= (b * 0.5 + 0.25):
  302. nWhichBin = b
  303. break
  304. if nWhichBin > 0:
  305. XBinSum[nWhichBin, 0] += PVDot.iloc[i][fieldWindSpeed]
  306. XBinSum[nWhichBin, 1] += PVDot.iloc[i][fieldActivePower]
  307. XBinNumber[nWhichBin] += 1
  308. XBinNumber -= 1
  309. for b in range(50):
  310. if XBinNumber[b] > 0:
  311. PCurve[b, 0] = XBinSum[b, 0] / XBinNumber[b]
  312. PCurve[b, 1] = XBinSum[b, 1] / XBinNumber[b]
  313. VRatedNum = int(VRated / 0.5)
  314. for m in range(VRatedNum, 50):
  315. if PCurve[m, 1] == 0:
  316. PCurve[m, 1] = PRated
  317. return PCurve
  318. # 计算标准正则功率曲线函数
  319. def calculate_normalized_power_curve(IdealCurve, VRated, PRated):
  320. PCurveNorm = np.zeros((50, 2))
  321. VRatedNum = int(VRated / 0.5)
  322. # 15m/s以上为额定功率
  323. high_wind_speeds = np.arange(15, 25.5, 0.5)
  324. PCurveNorm[VRatedNum:VRatedNum+len(high_wind_speeds), 0] = high_wind_speeds
  325. PCurveNorm[VRatedNum:VRatedNum+len(high_wind_speeds), 1] = PRated
  326. # 15m/s以下正则功率曲线
  327. VSpeed = np.arange(0.5, 15.5, 0.5)
  328. CurveData = IdealCurve[IdealCurve[fieldWindSpeed]
  329. <= 15].to_numpy() # 提取风速<=15的数据
  330. for i, v in enumerate(VSpeed):
  331. if i < len(CurveData) - 1:
  332. # 插值计算
  333. x0, y0 = CurveData[i]
  334. x1, y1 = CurveData[i + 1]
  335. PCurveNorm[i, 0] = v
  336. PCurveNorm[i, 1] = y0 + (v - x0) * (y1 - y0) / (x1 - x0)
  337. else:
  338. PCurveNorm[i, 0] = v
  339. PCurveNorm[i, 1] = PRated # 防止超出范围的情况
  340. return PCurveNorm
  341. # 保存结果函数
  342. def save_results(PCurve, PCurveNorm, EPKW, EPPer, outputDir='./output/A01'):
  343. if not os.path.exists(outputDir):
  344. os.makedirs(outputDir)
  345. pd.DataFrame(PCurve, columns=[fieldWindSpeed, fieldActivePower]).to_csv(
  346. os.path.join(outputDir, 'PCurve.csv'), index=False)
  347. pd.DataFrame(PCurveNorm, columns=[fieldWindSpeed, fieldActivePower]).to_csv(
  348. os.path.join(outputDir, 'PCurveNorm.csv'), index=False)
  349. pd.DataFrame([EPKW], columns=['EPIdealTotal', 'EPActualTotal', 'EPLostStopTotal', 'EPLostBadLimitTotal', 'EPLostPerformTotal', 'EPLostBadTotal',
  350. 'EPLostLimitTotal', 'EPOverTotal', 'WindSpeedAvr', 'TurbineRunRate']).to_csv(os.path.join(outputDir, 'EPKW.csv'), index=False)
  351. pd.DataFrame([EPPer], columns=['Percent1', 'Percent2', 'Percent3', 'Percent4', 'Percent5', 'Percent6', 'Percent7',
  352. 'Percent8', 'WindSpeedAvr', 'TurbineRunRate']).to_csv(os.path.join(outputDir, 'EPPer.csv'), index=False)
  353. # 绘制结果函数,使用 Plotly
  354. def plot_results(PVBad, PVLimit, PVDot, PCurve, IdealCurve):
  355. fig = go.Figure()
  356. # 添加坏点数据
  357. if not PVBad.empty:
  358. fig.add_trace(go.Scatter(x=PVBad[fieldWindSpeed], y=PVBad[fieldActivePower],
  359. mode='markers', name='坏点', marker=dict(color='red')))
  360. # 添加限电点数据
  361. if not PVLimit.empty:
  362. fig.add_trace(go.Scatter(x=PVLimit[fieldWindSpeed], y=PVLimit[fieldActivePower],
  363. mode='markers', name='限功率', marker=dict(color='blue')))
  364. # 添加正常点数据
  365. if not PVDot.empty:
  366. fig.add_trace(go.Scatter(x=PVDot[fieldWindSpeed], y=PVDot[fieldActivePower],
  367. mode='markers', name='好点', marker=dict(color='black')))
  368. # 添加实测功率曲线
  369. if PCurve.shape[0] > 0:
  370. fig.add_trace(go.Scatter(
  371. x=PCurve[:, 0], y=PCurve[:, 1], mode='lines+markers', name='实测功率曲线', line=dict(color='green')))
  372. # 添加设计功率曲线
  373. if IdealCurve.shape[0] > 0:
  374. fig.add_trace(go.Scatter(x=IdealCurve[:, 0], y=IdealCurve[:, 1],
  375. mode='lines+markers', name='合同功率曲线', line=dict(color='yellow')))
  376. # 更新布局
  377. fig.update_layout(
  378. title={'text':'风力机功率散点数据分类标识','x':0.5},
  379. xaxis_title=fieldWindSpeed,
  380. yaxis_title=fieldActivePower,
  381. legend=dict(x=0.01, y=0.99),
  382. template='plotly_white'
  383. )
  384. # fig.show()
  385. outputDir = './output/A01'
  386. filePath = os.path.join(outputDir, f'power_scatter.html')
  387. fig.write_html(filePath)
  388. # 主函数
  389. def main():
  390. # 读取数据
  391. March809, IdealCurve = read_data()
  392. # 计算统计信息
  393. AA, BB, PNum, EPActualTotal, WindSpeedAvr, TurbineRunRate, EPIdealTotalAAA = calculate_statistics(
  394. March809, IdealCurve)
  395. # 分类数据
  396. DzMarch809, XBoxNumber = classify_data(March809, PNum, VNum)
  397. # 计算百分比
  398. PBoxPercent, VBoxPercent = compute_percentages(XBoxNumber, PNum, VNum)
  399. # 查找主带
  400. DotDense, DotDenseLeftRight, PowerLimit, WidthAverage, WidthVar, PowerBandWidth, PBoxMaxIndex = find_main_band(
  401. PBoxPercent, PNum, VNum, XBoxNumber)
  402. # 标记坏点
  403. DzMarch809Sel = mark_bad_points(
  404. DzMarch809, DotDenseLeftRight, PBoxMaxIndex, PowerLimit, PNum, VNum, XBoxNumber, PBoxPercent)
  405. # 标识限负荷数据点
  406. PVLimitArray, DzMarch809Sel = identify_limit_load_data(
  407. DzMarch809, DzMarch809Sel, PRated)
  408. # 初始化存储好点和坏点的 DataFrame
  409. PVBad = pd.DataFrame(columns=DzMarch809.columns)
  410. PVLimit = pd.DataFrame(columns=DzMarch809.columns)
  411. PVDot = pd.DataFrame(columns=DzMarch809.columns)
  412. # 存储好点和坏点
  413. for i in range(len(DzMarch809)):
  414. if DzMarch809Sel[i] in [1, 2, 3]:
  415. if not DzMarch809.iloc[[i]].isna().all().all():
  416. PVBad = pd.concat(
  417. [PVBad, DzMarch809.iloc[[i]]], ignore_index=True)
  418. elif DzMarch809Sel[i] == 4:
  419. if not DzMarch809.iloc[[i]].isna().all().all():
  420. PVLimit = pd.concat(
  421. [PVLimit, DzMarch809.iloc[[i]]], ignore_index=True)
  422. else:
  423. if not DzMarch809.iloc[[i]].isna().all().all():
  424. PVDot = pd.concat(
  425. [PVDot, DzMarch809.iloc[[i]]], ignore_index=True)
  426. # 计算能量损失
  427. EPLostStopTotal, EPLostBadTotal, EPOverTotal, EPLostPerformTotal, EPIdealTotal = calculate_energy_loss(
  428. DzMarch809Sel, DzMarch809, IdealCurve, PNum, BB, PRated, EPIdealTotalAAA, EPActualTotal)
  429. EPKW = [EPIdealTotal, EPActualTotal, EPLostStopTotal, EPLostBadTotal,
  430. EPLostPerformTotal, EPLostBadTotal, 0, EPOverTotal, WindSpeedAvr, TurbineRunRate]
  431. EPPer = [100, EPActualTotal / EPIdealTotal * 100, EPLostStopTotal / EPIdealTotal * 100, EPLostBadTotal / EPIdealTotal * 100,
  432. EPLostPerformTotal / EPIdealTotal * 100, EPLostBadTotal / EPIdealTotal * 100, 0, EPOverTotal / EPActualTotal * 100, WindSpeedAvr, TurbineRunRate]
  433. # 计算实测功率曲线
  434. PCurve = calculate_measured_power_curve(PVDot, VRated, PRated)
  435. # 计算标准正则功率曲线
  436. PCurveNorm = calculate_normalized_power_curve(IdealCurve, VRated, PRated)
  437. # 保存结果
  438. save_results(PCurve, PCurveNorm, EPKW, EPPer)
  439. # 绘制结果
  440. plot_results(PVBad, PVLimit, PVDot, PCurve, IdealCurve.to_numpy())
  441. if __name__ == "__main__":
  442. main()