generatorSpeedPowerAnalyst.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. import os
  2. from datetime import datetime
  3. import numpy as np
  4. import pandas as pd
  5. import plotly.express as px
  6. import plotly.graph_objects as go
  7. from algorithmContract.confBusiness import *
  8. from algorithmContract.contract import Contract
  9. from behavior.analystWithGoodPoint import AnalystWithGoodPoint
  10. class GeneratorSpeedPowerAnalyst(AnalystWithGoodPoint):
  11. """
  12. 风电机组发电机转速-有功功率分析
  13. """
  14. def typeAnalyst(self):
  15. return "speed_power"
  16. def turbinesAnalysis(self, outputAnalysisDir, conf: Contract, turbineCodes):
  17. dictionary = self.processTurbineData(turbineCodes, conf, [
  18. Field_DeviceCode, Field_Time,Field_RotorSpeed,Field_GeneratorSpeed, Field_WindSpeed, Field_ActiverPower])
  19. dataFrameOfTurbines = self.userDataFrame(
  20. dictionary, conf.dataContract.configAnalysis, self)
  21. # 检查所需列是否存在
  22. required_columns = {Field_CodeOfTurbine,Field_RotorSpeed,Field_GeneratorSpeed,Field_ActiverPower}
  23. if not required_columns.issubset(dataFrameOfTurbines.columns):
  24. raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
  25. turbrineInfos = self.common.getTurbineInfos(
  26. conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo)
  27. groupedOfTurbineModel = turbrineInfos.groupby(Field_MillTypeCode)
  28. returnDatas = []
  29. for turbineModelCode, group in groupedOfTurbineModel:
  30. currTurbineCodes = group[Field_CodeOfTurbine].unique().tolist()
  31. currTurbineModeInfo = self.common.getTurbineModelByCode(
  32. turbineModelCode, self.turbineModelInfo)
  33. currDataFrameOfTurbines = dataFrameOfTurbines[dataFrameOfTurbines[Field_CodeOfTurbine].isin(
  34. currTurbineCodes)]
  35. # 将 currTurbineInfos 转换为字典
  36. currTurbineInfos_dict = turbrineInfos.set_index(Field_CodeOfTurbine)[Field_NameOfTurbine].to_dict()
  37. # 使用 map 函数来填充 Field_NameOfTurbine 列
  38. currDataFrameOfTurbines[Field_NameOfTurbine] = currDataFrameOfTurbines[Field_CodeOfTurbine].map(currTurbineInfos_dict).fillna("")
  39. result2D = self.drawScatter2DMonthly(
  40. currDataFrameOfTurbines, outputAnalysisDir, conf,currTurbineModeInfo)
  41. returnDatas.extend(result2D)
  42. result3D = self.drawScatterGraph(
  43. currDataFrameOfTurbines, outputAnalysisDir, conf,currTurbineModeInfo)
  44. returnDatas.extend(result3D)
  45. resultTotal = self.drawScatterGraphForTurbines(
  46. currDataFrameOfTurbines, outputAnalysisDir, conf, currTurbineModeInfo)
  47. returnDatas.extend(resultTotal)
  48. returnDataFrame = pd.DataFrame(returnDatas)
  49. return returnDataFrame
  50. def drawScatter2DMonthlyOfTurbine(self, dataFrame: pd.DataFrame, outputAnalysisDir: str, conf: Contract, name: str,turbineModelInfo: pd.Series):
  51. # 设置颜色条参数
  52. dataFrame = dataFrame.sort_values(by=Field_YearMonth)
  53. # 绘制 Plotly 散点图
  54. fig = go.Figure(data=go.Scatter(
  55. x=dataFrame[Field_GeneratorSpeed],
  56. y=dataFrame[Field_ActiverPower],
  57. mode='markers',
  58. marker=dict(
  59. color=dataFrame[Field_UnixYearMonth],
  60. colorscale='Rainbow',
  61. size=3,
  62. opacity=0.7,
  63. colorbar=dict(
  64. tickvals=np.linspace(
  65. dataFrame[Field_UnixYearMonth].min(), dataFrame[Field_UnixYearMonth].max(), 6),
  66. ticktext=[datetime.fromtimestamp(ts).strftime('%Y-%m') for ts in np.linspace(
  67. dataFrame[Field_UnixYearMonth].min(), dataFrame[Field_UnixYearMonth].max(), 6)],
  68. thickness=18,
  69. len=1, # 设置颜色条的长度,使其占据整个图的高度
  70. outlinecolor='rgba(255,255,255,0)'
  71. ),
  72. showscale=True
  73. ),
  74. showlegend=False
  75. ))
  76. # # 设置固定散点大小
  77. # fig.update_traces(marker=dict(size=3))
  78. # 如果需要颜色轴的刻度和标签
  79. # 以下是以比例方式进行色彩的可视化处理
  80. fig.update_layout(
  81. title={
  82. "text": f'月度发电机转速功率散点图: {name[0]}',
  83. # "x": 0.5
  84. },
  85. xaxis=dict(
  86. title='发电机转速',
  87. dtick=self.axisStepGeneratorSpeed,
  88. range=[self.axisLowerLimitGeneratorSpeed,
  89. self.axisUpperLimitGeneratorSpeed],
  90. tickangle=-45
  91. ),
  92. yaxis=dict(
  93. title='功率',
  94. dtick=self.axisStepActivePower,
  95. range=[self.axisLowerLimitActivePower,
  96. self.axisUpperLimitActivePower],
  97. ),
  98. coloraxis=dict(
  99. colorbar=dict(
  100. title="时间",
  101. ticks="outside",
  102. len=1, # 设置颜色条的长度,使其占据整个图的高度
  103. thickness=20, # 调整颜色条的宽度
  104. orientation='v', # 设置颜色条为垂直方向
  105. tickmode='array', # 确保刻度按顺序排列
  106. tickvals=dataFrame[Field_YearMonth].unique(
  107. ).tolist(), # 确保刻度为唯一的年月
  108. ticktext=dataFrame[Field_YearMonth].unique(
  109. ).tolist() # 以%Y-%m格式显示标签
  110. )
  111. )
  112. )
  113. # 确保从 Series 中提取的是具体的值
  114. engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "")
  115. if isinstance(engineTypeCode, pd.Series):
  116. engineTypeCode = engineTypeCode.iloc[0]
  117. engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "")
  118. if isinstance(engineTypeName, pd.Series):
  119. engineTypeName = engineTypeName.iloc[0]
  120. # 构建最终的JSON对象
  121. json_output = {
  122. "analysisTypeCode": "发电机转速和有功功率分析",
  123. "engineCode": engineTypeCode,
  124. "engineTypeName": engineTypeName,
  125. "xaixs": "发电机转速(r/min)",
  126. "yaixs": "功率(kw)",
  127. "data": [{
  128. "engineName": name[0],
  129. "engineCode": name[1],
  130. "title":f' 月度发电机转速功率散点图:{name[0]}',
  131. "xData": dataFrame[Field_GeneratorSpeed].tolist(),
  132. "yData":dataFrame[Field_ActiverPower].tolist(),
  133. "color": dataFrame[Field_UnixYearMonth].tolist(),
  134. "colorbartitle": "时间",
  135. "mode":'markers'
  136. }]
  137. }
  138. # 保存图片
  139. outputFilePathPNG = os.path.join(
  140. outputAnalysisDir, f"{name[0]}.png")
  141. fig.write_image(outputFilePathPNG, width=800, height=600, scale=3)
  142. # # 保存html
  143. # outputFileHtml = os.path.join(outputAnalysisDir, f"{name[0]}.html")
  144. # fig.write_html(outputFileHtml)
  145. # 将JSON对象保存到文件
  146. output_json_path = os.path.join(outputAnalysisDir, f"speed_power{name[0]}.json")
  147. with open(output_json_path, 'w', encoding='utf-8') as f:
  148. import json
  149. json.dump(json_output, f, ensure_ascii=False, indent=4)
  150. result = []
  151. # 如果需要返回DataFrame,可以包含文件路径
  152. result.append({
  153. Field_Return_TypeAnalyst: self.typeAnalyst(),
  154. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  155. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  156. Field_CodeOfTurbine: dataFrame[Field_CodeOfTurbine].iloc[0],
  157. Field_Return_FilePath: output_json_path,
  158. Field_Return_IsSaveDatabase: True
  159. })
  160. result.append({
  161. Field_Return_TypeAnalyst: self.typeAnalyst(),
  162. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  163. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  164. Field_CodeOfTurbine: dataFrame[Field_CodeOfTurbine].iloc[0],
  165. Field_Return_FilePath: outputFilePathPNG,
  166. Field_Return_IsSaveDatabase: False
  167. })
  168. # result.append({
  169. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  170. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  171. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  172. # Field_CodeOfTurbine: dataFrame[Field_CodeOfTurbine].iloc[0],
  173. # Field_Return_FilePath: outputFileHtml,
  174. # Field_Return_IsSaveDatabase: True
  175. # })
  176. return result
  177. def drawScatterGraphOfTurbine(self, dataFrame: pd.DataFrame, outputAnalysisDir: str, conf: Contract, name: str,turbineModelInfo: pd.Series):
  178. # 创建3D散点图
  179. fig = px.scatter_3d(dataFrame,
  180. x=Field_GeneratorSpeed,
  181. y=Field_YearMonth,
  182. z=Field_ActiverPower,
  183. color=Field_YearMonth,
  184. labels={Field_GeneratorSpeed: '发电机转速',
  185. Field_YearMonth: '时间', Field_ActiverPower: '功率'}
  186. )
  187. # 设置固定散点大小
  188. fig.update_traces(marker=dict(size=1.5))
  189. # 更新图形的布局
  190. fig.update_layout(
  191. title={
  192. "text": f'月度发电机转速功率3D散点图: {name[0]}',
  193. # "x": 0.5
  194. },
  195. scene=dict(
  196. xaxis=dict(
  197. title='发电机转速',
  198. dtick=self.axisStepGeneratorSpeed, # 设置y轴刻度间隔
  199. range=[self.axisLowerLimitGeneratorSpeed,
  200. self.axisUpperLimitGeneratorSpeed], # 设置y轴的范围
  201. showgrid=True, # 显示网格线
  202. ),
  203. yaxis=dict(
  204. title='时间',
  205. tickformat='%Y-%m', # 日期格式,
  206. dtick='M1', # 每月一个刻度
  207. showgrid=True, # 显示网格线
  208. ),
  209. zaxis=dict(
  210. title='功率',
  211. dtick=self.axisStepActivePower,
  212. range=[self.axisLowerLimitActivePower,
  213. self.axisUpperLimitActivePower],
  214. showgrid=True, # 显示网格线
  215. )
  216. ),
  217. scene_camera=dict(
  218. up=dict(x=0, y=0, z=1), # 保持相机向上方向不变
  219. center=dict(x=0, y=0, z=0), # 保持相机中心位置不变
  220. eye=dict(x=-1.8, y=-1.8, z=1.2) # 调整eye属性以实现水平旋转180°
  221. ),
  222. # 设置图例标题
  223. # legend_title_text='Time',
  224. legend=dict(
  225. orientation="h",
  226. itemsizing="constant", # Use constant size for legend items
  227. itemwidth=80 # Set the width of legend items to 50 pixels
  228. )
  229. )
  230. # 确保从 Series 中提取的是具体的值
  231. engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "")
  232. if isinstance(engineTypeCode, pd.Series):
  233. engineTypeCode = engineTypeCode.iloc[0]
  234. engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "")
  235. if isinstance(engineTypeName, pd.Series):
  236. engineTypeName = engineTypeName.iloc[0]
  237. # 构建最终的JSON对象
  238. json_output = {
  239. "analysisTypeCode": "发电机转速和有功功率分析",
  240. "engineCode": engineTypeCode,
  241. "engineTypeName": engineTypeName,
  242. "xaixs": "发电机转速(r/min)",
  243. "yaixs": "时间",
  244. "zaixs": "有功功率(kw)",
  245. "data": [{
  246. "engineName": name[0],
  247. "engineCode": name[1],
  248. "title":f' 月度发电机转速功率3D散点图:{name[0]}',
  249. "xData": dataFrame[Field_GeneratorSpeed].tolist(),
  250. "yData":dataFrame[Field_YearMonth].tolist(),
  251. "zData":dataFrame[Field_ActiverPower].tolist(),
  252. "color": dataFrame[Field_YearMonth].tolist(),
  253. "mode":'markers'
  254. }]
  255. }
  256. # # 保存图像
  257. # outputFileHtml = os.path.join(
  258. # outputAnalysisDir, "{}_3D.html".format(name[0]))
  259. # fig.write_html(outputFileHtml)
  260. result = []
  261. # 将JSON对象保存到文件
  262. output_json_path = os.path.join(outputAnalysisDir, f"3D_{name[0]}.json")
  263. with open(output_json_path, 'w', encoding='utf-8') as f:
  264. import json
  265. json.dump(json_output, f, ensure_ascii=False, indent=4)
  266. # 如果需要返回DataFrame,可以包含文件路径
  267. result.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: output_json_path,
  273. Field_Return_IsSaveDatabase: True
  274. })
  275. # result.append({
  276. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  277. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  278. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  279. # Field_CodeOfTurbine: dataFrame[Field_CodeOfTurbine].iloc[0],
  280. # Field_Return_FilePath: outputFileHtml,
  281. # Field_Return_IsSaveDatabase: True
  282. # })
  283. return result
  284. def drawScatter2DMonthly(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, conf: Contract,turbineModelInfo: pd.Series):
  285. """
  286. 生成每台风电机组二维有功功率、发电机转速散点图表
  287. """
  288. results = []
  289. grouped = dataFrameMerge.groupby(
  290. [Field_NameOfTurbine, Field_CodeOfTurbine])
  291. for name, group in grouped:
  292. result = self.drawScatter2DMonthlyOfTurbine(
  293. group, outputAnalysisDir, conf, name,turbineModelInfo)
  294. results.extend(result)
  295. return results
  296. def drawScatterGraph(self, dataFrame: pd.DataFrame, outputAnalysisDir: str, conf: Contract,turbineModelInfo: pd.Series):
  297. """
  298. 绘制风速-功率分布图并保存为文件。
  299. 参数:
  300. dataFrameMerge (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。
  301. outputAnalysisDir (str): 分析输出目录。
  302. confData (Contract): 配置
  303. """
  304. results = []
  305. dataFrame = dataFrame[(dataFrame[Field_ActiverPower] > 0)].sort_values(
  306. by=Field_YearMonth)
  307. grouped = dataFrame.groupby(
  308. [Field_NameOfTurbine, Field_CodeOfTurbine])
  309. # 遍历每个设备的数据
  310. for name, group in grouped:
  311. if len(group[Field_YearMonth].unique()) > 1:
  312. result = self.drawScatterGraphOfTurbine(
  313. group, outputAnalysisDir, conf, name,turbineModelInfo)
  314. results.extend(result)
  315. return results
  316. def drawScatterGraphForTurbines(self, dataFrame: pd.DataFrame, outputAnalysisDir, conf: Contract, turbineModelInfo: pd.Series):
  317. """
  318. 绘制风速-功率分布图并保存为文件。 (须按照机型分组)
  319. 参数:
  320. dataFrameMerge (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。
  321. outputAnalysisDir (str): 分析输出目录。
  322. confData (Contract): 配置
  323. """
  324. dataFrame = dataFrame[(dataFrame[Field_ActiverPower] > 0)].sort_values(by=Field_NameOfTurbine)
  325. # 创建3D散点图
  326. fig = px.scatter_3d(dataFrame,
  327. x=Field_GeneratorSpeed,
  328. y=Field_NameOfTurbine,
  329. z=Field_ActiverPower,
  330. color=Field_NameOfTurbine,
  331. labels={Field_GeneratorSpeed: '发电机转速',
  332. Field_NameOfTurbine: '风机', Field_ActiverPower: '功率'},
  333. )
  334. # 设置固定散点大小
  335. fig.update_traces(marker=dict(size=1.5))
  336. # 更新图形的布局
  337. fig.update_layout(
  338. title={
  339. "text": f'风机发电机转速功率3D散点图-{turbineModelInfo[Field_MachineTypeCode]}',
  340. "x": 0.5
  341. },
  342. scene=dict(
  343. xaxis=dict(
  344. title='发电机转速',
  345. dtick=self.axisStepGeneratorSpeed, # 设置y轴刻度间隔
  346. range=[self.axisLowerLimitGeneratorSpeed,
  347. self.axisUpperLimitGeneratorSpeed], # 设置y轴的范围
  348. showgrid=True, # 显示网格线
  349. ),
  350. yaxis=dict(
  351. title='机组',
  352. showgrid=True, # 显示网格线
  353. ),
  354. zaxis=dict(
  355. title='功率',
  356. dtick=self.axisStepActivePower,
  357. range=[self.axisLowerLimitActivePower,
  358. self.axisUpperLimitActivePower],
  359. showgrid=True, # 显示网格线
  360. )
  361. ),
  362. scene_camera=dict(
  363. up=dict(x=0, y=0, z=1), # 保持相机向上方向不变
  364. center=dict(x=0, y=0, z=0), # 保持相机中心位置不变
  365. eye=dict(x=-1.8, y=-1.8, z=1.2) # 调整eye属性以实现水平旋转180°
  366. ),
  367. # 设置图例标题
  368. # legend_title_text='Turbine'
  369. legend=dict(
  370. orientation="h",
  371. itemsizing="constant", # Use constant size for legend items
  372. itemwidth=80 # Set the width of legend items to 50 pixels
  373. )
  374. )
  375. # 确保从 Series 中提取的是具体的值
  376. engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "")
  377. if isinstance(engineTypeCode, pd.Series):
  378. engineTypeCode = engineTypeCode.iloc[0]
  379. engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "")
  380. if isinstance(engineTypeName, pd.Series):
  381. engineTypeName = engineTypeName.iloc[0]
  382. # 构建最终的JSON对象
  383. json_output = {
  384. "analysisTypeCode": "发电机转速和有功功率分析",
  385. "engineCode": engineTypeCode,
  386. "engineTypeName": engineTypeName,
  387. "xaixs": "发电机转速(r/min)",
  388. "yaixs": "机组",
  389. "zaixs": "有功功率(kw)",
  390. "data": [{
  391. "title":f'风机发电机转速功率3D散点图-{turbineModelInfo[Field_MachineTypeCode]}',
  392. "xData": dataFrame[Field_GeneratorSpeed].tolist(),
  393. "yData":dataFrame[Field_NameOfTurbine].tolist(),
  394. "zData":dataFrame[Field_ActiverPower].tolist(),
  395. "color": dataFrame[Field_NameOfTurbine].tolist(),
  396. "mode":'markers'
  397. }]
  398. }
  399. # # 保存图像
  400. # outputFileHtml = os.path.join(
  401. # outputAnalysisDir, "{}-{}.html".format(self.typeAnalyst(),turbineModelInfo[Field_MillTypeCode]))
  402. # fig.write_html(outputFileHtml)
  403. result = []
  404. # 将JSON对象保存到文件
  405. output_json_path = os.path.join(outputAnalysisDir, f"total_3D_{turbineModelInfo[Field_MillTypeCode]}.json")
  406. with open(output_json_path, 'w', encoding='utf-8') as f:
  407. import json
  408. json.dump(json_output, f, ensure_ascii=False, indent=4)
  409. # 如果需要返回DataFrame,可以包含文件路径
  410. result.append({
  411. Field_Return_TypeAnalyst: self.typeAnalyst(),
  412. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  413. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  414. Field_CodeOfTurbine:Const_Output_Total,
  415. Field_MillTypeCode:turbineModelInfo[Field_MillTypeCode],
  416. Field_Return_FilePath: output_json_path,
  417. Field_Return_IsSaveDatabase: True
  418. })
  419. # result.append({
  420. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  421. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  422. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  423. # Field_CodeOfTurbine: Const_Output_Total,
  424. # Field_Return_FilePath: outputFileHtml,
  425. # Field_Return_IsSaveDatabase: True
  426. # })
  427. return result