cpAnalyst.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. import os
  2. import math
  3. import pandas as pd
  4. import plotly.graph_objects as go
  5. from algorithmContract.confBusiness import *
  6. from algorithmContract.contract import Contract
  7. from behavior.analystWithGoodPoint import AnalystWithGoodPoint
  8. from plotly.subplots import make_subplots
  9. class CpAnalyst(AnalystWithGoodPoint):
  10. """
  11. 风电机组风能利用系数分析
  12. """
  13. def typeAnalyst(self):
  14. return "cp"
  15. def turbinesAnalysis(self, outputAnalysisDir, conf: Contract, turbineCodes):
  16. dictionary = self.processTurbineData(turbineCodes, conf, [
  17. Field_DeviceCode, Field_Time, Field_WindSpeed, Field_ActiverPower])
  18. dataFrameOfTurbines = self.userDataFrame(
  19. dictionary, conf.dataContract.configAnalysis, self)
  20. # 检查所需列是否存在
  21. required_columns = {Field_WindSpeed,
  22. Field_Cp, Field_PowerFloor}
  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. dataFrameOfContractPowerCurve = self.dataFrameContractOfTurbine[
  34. self.dataFrameContractOfTurbine[Field_MillTypeCode] == turbineModelCode]
  35. currDataFrameOfTurbines = dataFrameOfTurbines[dataFrameOfTurbines[Field_CodeOfTurbine].isin(
  36. currTurbineCodes)]
  37. returnData = self.drawLineGraphForTurbine(
  38. currDataFrameOfTurbines, outputAnalysisDir, conf, currTurbineModeInfo,dataFrameOfContractPowerCurve)
  39. returnDatas.append(returnData)
  40. returnResult = pd.concat(returnDatas, ignore_index=True)
  41. return returnResult
  42. def drawLineGraphForTurbine(self, dataFrameOfTurbines: pd.DataFrame, outputAnalysisDir, conf: Contract, turbineModelInfo: pd.Series,dataFrameOfContractPowerCurve:pd.DataFrame):
  43. upLimitOfPower = self.turbineInfo[Field_RatedPower].max() * 0.9
  44. grouped = dataFrameOfTurbines.groupby([Field_CodeOfTurbine, Field_PowerFloor]).agg(
  45. cp=('cp', 'mean'),
  46. cp_max=('cp', 'max'),
  47. cp_min=('cp', 'min'),
  48. ).reset_index()
  49. # Rename columns post aggregation for clarity
  50. grouped.columns = [Field_CodeOfTurbine,
  51. Field_PowerFloor, Field_CpMedian, 'cp_max', 'cp_min']
  52. # Sort by power_floor
  53. grouped = grouped.sort_values(
  54. by=[Field_CodeOfTurbine, Field_PowerFloor])
  55. # Create Subplots
  56. fig = make_subplots(specs=[[{"secondary_y": False}]])
  57. # 创建一个列表来存储各个风电机组的数据
  58. turbine_data_list = []
  59. # colors = px.colors.sequential.Turbo
  60. # Plotting the turbine lines
  61. for turbineCode in grouped[Field_CodeOfTurbine].unique():
  62. turbine_data = grouped[grouped[Field_CodeOfTurbine] == turbineCode]
  63. currTurbineInfo = self.common.getTurbineInfo(
  64. conf.dataContract.dataFilter.powerFarmID, turbineCode, self.turbineInfo)
  65. fig.add_trace(
  66. go.Scatter(x=turbine_data[Field_PowerFloor],
  67. y=turbine_data[Field_CpMedian],
  68. mode='lines',
  69. # line=dict(color=colors[idx % len(colors)]),
  70. name=currTurbineInfo[Field_NameOfTurbine])
  71. )
  72. # 提取数据
  73. turbine_data_total = {
  74. "engineName": currTurbineInfo[Field_NameOfTurbine],
  75. "engineCode": turbineCode,
  76. "xData": turbine_data[Field_PowerFloor].tolist(),
  77. # "yData": turbine_data[Field_CpMedian].tolist(),
  78. "yData": [None if math.isinf(val) else val for val in turbine_data[Field_CpMedian].tolist()],
  79. }
  80. turbine_data_list.append(turbine_data_total)
  81. # Plotting the contract guarantee Cp curve
  82. fig.add_trace(
  83. go.Scatter(x=dataFrameOfContractPowerCurve[Field_PowerFloor],
  84. y=dataFrameOfContractPowerCurve[Field_Cp],
  85. # mode='lines',
  86. # line=dict(color='red', dash='dash'),
  87. mode='lines+markers',
  88. line=dict(color='red'),
  89. marker=dict(color='red', size=5),
  90. name='合同功率曲线'
  91. ),
  92. secondary_y=False,
  93. )
  94. # Update layout
  95. fig.update_layout(
  96. title={
  97. 'text': f'风能利用系数分布-{turbineModelInfo[Field_MachineTypeCode]}',
  98. 'x': 0.5, # 标题位置居中
  99. },
  100. xaxis_title='功率',
  101. yaxis_title='风能利用系数',
  102. # legend_title='Turbine',
  103. xaxis=dict(range=[0, upLimitOfPower], tickangle=-45),
  104. yaxis=dict(
  105. dtick=self.axisStepCp,
  106. range=[self.axisLowerLimitCp,
  107. self.axisUpperLimitCp]
  108. ),
  109. legend=dict(
  110. orientation="h", # Horizontal orientation
  111. xanchor="center", # Anchor the legend to the center
  112. x=0.5, # Position legend at the center of the x-axis
  113. y=-0.2, # Position legend below the x-axis
  114. # itemsizing='constant', # Keep the size of the legend entries constant
  115. # itemwidth=50
  116. )
  117. )
  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. # 构建最终的JSON对象
  125. json_output = {
  126. "analysisTypeCode": "风电机组风能利用系数分析",
  127. "typecode": turbineModelInfo[Field_MillTypeCode],
  128. "engineCode": engineTypeCode,
  129. "engineTypeName": engineTypeName,
  130. "title": f'风能利用系数分布-{turbineModelInfo[Field_MachineTypeCode]}',
  131. "xaixs": "功率(kW)",
  132. "yaixs": "风能利用系数",
  133. "contract_Cp_curve_xData": dataFrameOfContractPowerCurve[Field_PowerFloor].tolist(),
  134. "contract_Cp_curve_yData": dataFrameOfContractPowerCurve[Field_Cp].tolist(),
  135. "data": turbine_data_list
  136. }
  137. # 将JSON对象保存到文件
  138. output_json_path = os.path.join(outputAnalysisDir, f"{turbineModelInfo[Field_MillTypeCode]}.json")
  139. with open(output_json_path, 'w', encoding='utf-8') as f:
  140. import json
  141. json.dump(json_output, f, ensure_ascii=False, indent=4)
  142. # 保存html
  143. # htmlFileName = f"{self.powerFarmInfo[Field_PowerFarmName].iloc[0]}-{turbineModelInfo[Field_MillTypeCode]}-Cp-Distribution.html"
  144. # htmlFilePath = os.path.join(outputAnalysisDir, htmlFileName)
  145. # fig.write_html(htmlFilePath)
  146. result_rows = []
  147. # result_rows.append({
  148. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  149. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  150. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  151. # Field_CodeOfTurbine: Const_Output_Total,
  152. # Field_Return_FilePath: htmlFilePath,
  153. # Field_Return_IsSaveDatabase: True
  154. # })
  155. # 如果需要返回DataFrame,可以包含文件路径
  156. result_rows.append({
  157. Field_Return_TypeAnalyst: self.typeAnalyst(),
  158. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  159. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  160. Field_CodeOfTurbine: 'total',
  161. Field_MillTypeCode: turbineModelInfo[Field_MillTypeCode],
  162. Field_Return_FilePath: output_json_path,
  163. Field_Return_IsSaveDatabase: True
  164. })
  165. # Individual turbine graphs
  166. for turbineCode, group in grouped.groupby(Field_CodeOfTurbine):
  167. fig = go.Figure()
  168. currTurbineInfo = self.common.getTurbineInfo(
  169. conf.dataContract.dataFilter.powerFarmID, turbineCode, self.turbineInfo)
  170. # 创建一个列表来存储各个风电机组的数据
  171. turbine_data_list_each = []
  172. # Flag to add legend only once
  173. # add_legend = True
  174. # Plot other turbines data
  175. for other_name, other_group in grouped[grouped[Field_CodeOfTurbine] != turbineCode].groupby(Field_CodeOfTurbine):
  176. tempTurbineInfo = self.common.getTurbineInfo(
  177. conf.dataContract.dataFilter.powerFarmID, other_name, self.turbineInfo)
  178. fig.add_trace(
  179. go.Scatter(
  180. x=other_group[Field_PowerFloor],
  181. y=other_group[Field_CpMedian],
  182. mode='lines',
  183. # name='Other Turbines' if add_legend else '',
  184. line=dict(color='lightgray', width=1),
  185. showlegend=False
  186. )
  187. )
  188. # 提取数据
  189. turbine_data_other_each = {
  190. "engineName": tempTurbineInfo[Field_NameOfTurbine],
  191. "engineCode": other_name,
  192. "xData": other_group[Field_PowerFloor].tolist(),
  193. # "yData": other_group[Field_CpMedian].tolist(),
  194. "yData": [None if math.isinf(val) else val for val in other_group[Field_CpMedian].tolist()],
  195. }
  196. turbine_data_list_each.append(turbine_data_other_each)
  197. add_legend = False # Only add legend item for the first other turbine
  198. # Add trace for the current turbine
  199. fig.add_trace(
  200. go.Scatter(x=group[Field_PowerFloor], y=group[Field_CpMedian],
  201. mode='lines', name=currTurbineInfo[Field_NameOfTurbine], line=dict(color='darkblue'))
  202. )
  203. turbine_data_curr = {
  204. "engineName": currTurbineInfo[Field_NameOfTurbine],
  205. "engineCode": currTurbineInfo[Field_CodeOfTurbine],
  206. "xData": group[Field_PowerFloor].tolist(),
  207. # "yData": group[Field_CpMedian].tolist(),
  208. "yData": [None if math.isinf(val) else val for val in group[Field_CpMedian].tolist()],
  209. }
  210. turbine_data_list_each.append(turbine_data_curr)
  211. fig.add_trace(
  212. go.Scatter(x=dataFrameOfContractPowerCurve[Field_PowerFloor],
  213. y=dataFrameOfContractPowerCurve[Field_Cp],
  214. mode='lines+markers',
  215. name='合同功率曲线',
  216. marker=dict(color='red', size=5),
  217. line=dict(color='red'))
  218. )
  219. fig.update_layout(
  220. title={
  221. 'text': f'机组: {currTurbineInfo[Field_NameOfTurbine]}',
  222. # 'x': 0.5, # 标题位置居中
  223. },
  224. xaxis_title='功率',
  225. yaxis_title='风能利用系数',
  226. xaxis=dict(range=[0, upLimitOfPower], tickangle=-45),
  227. yaxis=dict(
  228. dtick=self.axisStepCp,
  229. range=[self.axisLowerLimitCp,
  230. self.axisUpperLimitCp] # axisStepCp
  231. ),
  232. legend=dict(x=1.05, y=0.5)
  233. )
  234. engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "")
  235. if isinstance(engineTypeCode, pd.Series):
  236. engineTypeCode = engineTypeCode.iloc[0]
  237. engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "")
  238. if isinstance(engineTypeName, pd.Series):
  239. engineTypeName = engineTypeName.iloc[0]
  240. # 构建最终的JSON对象
  241. json_output = {
  242. "analysisTypeCode": "风电机组风能利用系数分析",
  243. "typecode": turbineModelInfo[Field_MillTypeCode],
  244. "engineCode": engineTypeCode,
  245. "engineTypeName": engineTypeName,
  246. "title": f'机组: {currTurbineInfo[Field_NameOfTurbine]}',
  247. "xaixs": "功率(kW)",
  248. "yaixs": "风能利用系数",
  249. "contract_Cp_curve_xData": dataFrameOfContractPowerCurve[Field_PowerFloor].tolist(),
  250. "contract_Cp_curve_yData": dataFrameOfContractPowerCurve[Field_Cp].tolist(),
  251. "data": turbine_data_list_each
  252. }
  253. # 将JSON对象保存到文件
  254. output_json_path_each = os.path.join(outputAnalysisDir,
  255. f"{currTurbineInfo[Field_NameOfTurbine]}.json")
  256. with open(output_json_path_each, 'w', encoding='utf-8') as f:
  257. import json
  258. json.dump(json_output, f, ensure_ascii=False, indent=4)
  259. # 保存图像
  260. # pngFileName = f"{currTurbineInfo[Field_NameOfTurbine]}.png"
  261. # pngFilePath = os.path.join(outputAnalysisDir, pngFileName)
  262. # fig.write_image(pngFilePath, scale=3)
  263. # 保存HTML
  264. # htmlFileName = f"{currTurbineInfo[Field_NameOfTurbine]}.html"
  265. # htmlFilePath = os.path.join(outputAnalysisDir, htmlFileName)
  266. # fig.write_html(htmlFilePath)
  267. # result_rows.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: turbineCode,
  272. # Field_Return_FilePath: pngFilePath,
  273. # Field_Return_IsSaveDatabase: False
  274. # })
  275. # 如果需要返回DataFrame,可以包含文件路径
  276. result_rows.append({
  277. Field_Return_TypeAnalyst: self.typeAnalyst(),
  278. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  279. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  280. Field_CodeOfTurbine: turbineCode,
  281. Field_Return_FilePath: output_json_path_each,
  282. Field_Return_IsSaveDatabase: True
  283. })
  284. # result_rows.append({
  285. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  286. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  287. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  288. # Field_CodeOfTurbine: turbineCode,
  289. # Field_Return_FilePath: htmlFilePath,
  290. # Field_Return_IsSaveDatabase: True
  291. # })
  292. result_df = pd.DataFrame(result_rows)
  293. return result_df