generatorSpeedTorqueAnalyst.py 21 KB

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