cpAnalyst.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import os
  2. import pandas as pd
  3. import plotly.graph_objects as go
  4. from algorithmContract.confBusiness import *
  5. from algorithmContract.contract import Contract
  6. from behavior.analystWithGoodPoint import AnalystWithGoodPoint
  7. from plotly.subplots import make_subplots
  8. class CpAnalyst(AnalystWithGoodPoint):
  9. """
  10. 风电机组风能利用系数分析
  11. """
  12. def typeAnalyst(self):
  13. return "cp"
  14. def turbinesAnalysis(self, outputAnalysisDir, conf: Contract, turbineCodes):
  15. dictionary = self.processTurbineData(turbineCodes, conf, [
  16. Field_DeviceCode, Field_Time, Field_WindSpeed, Field_ActiverPower])
  17. dataFrameOfTurbines = self.userDataFrame(
  18. dictionary, conf.dataContract.configAnalysis, self)
  19. # 检查所需列是否存在
  20. required_columns = {Field_WindSpeed,
  21. Field_Cp, Field_PowerFloor}
  22. if not required_columns.issubset(dataFrameOfTurbines.columns):
  23. raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
  24. turbrineInfos = self.common.getTurbineInfos(
  25. conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo)
  26. groupedOfTurbineModel = turbrineInfos.groupby(Field_MillTypeCode)
  27. returnDatas = []
  28. for turbineModelCode, group in groupedOfTurbineModel:
  29. currTurbineCodes = group[Field_CodeOfTurbine].unique().tolist()
  30. currTurbineModeInfo = self.common.getTurbineModelByCode(
  31. turbineModelCode, self.turbineModelInfo)
  32. dataFrameOfContractPowerCurve = self.dataFrameContractOfTurbine[
  33. self.dataFrameContractOfTurbine[Field_MillTypeCode] == turbineModelCode]
  34. currDataFrameOfTurbines = dataFrameOfTurbines[dataFrameOfTurbines[Field_CodeOfTurbine].isin(
  35. currTurbineCodes)]
  36. returnData = self.drawLineGraphForTurbine(
  37. currDataFrameOfTurbines, outputAnalysisDir, conf, currTurbineModeInfo,dataFrameOfContractPowerCurve)
  38. returnDatas.append(returnData)
  39. returnResult = pd.concat(returnDatas, ignore_index=True)
  40. return returnResult
  41. def drawLineGraphForTurbine(self, dataFrameOfTurbines: pd.DataFrame, outputAnalysisDir, conf: Contract, turbineModelInfo: pd.Series,dataFrameOfContractPowerCurve:pd.DataFrame):
  42. upLimitOfPower = self.turbineInfo[Field_RatedPower].max() * 0.9
  43. grouped = dataFrameOfTurbines.groupby([Field_CodeOfTurbine, Field_PowerFloor]).agg(
  44. cp=('cp', 'median'),
  45. cp_max=('cp', 'max'),
  46. cp_min=('cp', 'min'),
  47. ).reset_index()
  48. # Rename columns post aggregation for clarity
  49. grouped.columns = [Field_CodeOfTurbine,
  50. Field_PowerFloor, Field_CpMedian, 'cp_max', 'cp_min']
  51. # Sort by power_floor
  52. grouped = grouped.sort_values(
  53. by=[Field_CodeOfTurbine, Field_PowerFloor])
  54. # Create Subplots
  55. fig = make_subplots(specs=[[{"secondary_y": False}]])
  56. # colors = px.colors.sequential.Turbo
  57. # Plotting the turbine lines
  58. for turbineCode in grouped[Field_CodeOfTurbine].unique():
  59. turbine_data = grouped[grouped[Field_CodeOfTurbine] == turbineCode]
  60. currTurbineInfo = self.common.getTurbineInfo(
  61. conf.dataContract.dataFilter.powerFarmID, turbineCode, self.turbineInfo)
  62. fig.add_trace(
  63. go.Scatter(x=turbine_data[Field_PowerFloor],
  64. y=turbine_data[Field_CpMedian],
  65. mode='lines',
  66. # line=dict(color=colors[idx % len(colors)]),
  67. name=currTurbineInfo[Field_NameOfTurbine])
  68. )
  69. # Plotting the contract guarantee Cp curve
  70. fig.add_trace(
  71. go.Scatter(x=dataFrameOfContractPowerCurve[Field_PowerFloor],
  72. y=dataFrameOfContractPowerCurve[Field_Cp],
  73. # mode='lines',
  74. # line=dict(color='red', dash='dash'),
  75. mode='lines+markers',
  76. line=dict(color='red'),
  77. marker=dict(color='red', size=5),
  78. name='合同功率曲线'
  79. ),
  80. secondary_y=False,
  81. )
  82. # Update layout
  83. fig.update_layout(
  84. title={
  85. 'text': f'风能利用系数分布-{turbineModelInfo[Field_MachineTypeCode]}',
  86. 'x': 0.5, # 标题位置居中
  87. },
  88. xaxis_title='功率',
  89. yaxis_title='风能利用系数',
  90. # legend_title='Turbine',
  91. xaxis=dict(range=[0, upLimitOfPower], tickangle=-45),
  92. yaxis=dict(
  93. dtick=self.axisStepCp,
  94. range=[self.axisLowerLimitCp,
  95. self.axisUpperLimitCp]
  96. ),
  97. legend=dict(
  98. orientation="h", # Horizontal orientation
  99. xanchor="center", # Anchor the legend to the center
  100. x=0.5, # Position legend at the center of the x-axis
  101. y=-0.2, # Position legend below the x-axis
  102. # itemsizing='constant', # Keep the size of the legend entries constant
  103. # itemwidth=50
  104. )
  105. )
  106. # 保存html
  107. htmlFileName = f"{self.powerFarmInfo[Field_PowerFarmName].iloc[0]}-{turbineModelInfo[Field_MillTypeCode]}-Cp-Distribution.html"
  108. htmlFilePath = os.path.join(outputAnalysisDir, htmlFileName)
  109. fig.write_html(htmlFilePath)
  110. result_rows = []
  111. result_rows.append({
  112. Field_Return_TypeAnalyst: self.typeAnalyst(),
  113. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  114. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  115. Field_CodeOfTurbine: Const_Output_Total,
  116. Field_Return_FilePath: htmlFilePath,
  117. Field_Return_IsSaveDatabase: True
  118. })
  119. # Individual turbine graphs
  120. for turbineCode, group in grouped.groupby(Field_CodeOfTurbine):
  121. fig = go.Figure()
  122. currTurbineInfo = self.common.getTurbineInfo(
  123. conf.dataContract.dataFilter.powerFarmID, turbineCode, self.turbineInfo)
  124. # Flag to add legend only once
  125. # add_legend = True
  126. # Plot other turbines data
  127. for other_name, other_group in grouped[grouped[Field_CodeOfTurbine] != turbineCode].groupby(Field_CodeOfTurbine):
  128. fig.add_trace(
  129. go.Scatter(
  130. x=other_group[Field_PowerFloor],
  131. y=other_group[Field_CpMedian],
  132. mode='lines',
  133. # name='Other Turbines' if add_legend else '',
  134. line=dict(color='lightgray', width=1),
  135. showlegend=False
  136. )
  137. )
  138. add_legend = False # Only add legend item for the first other turbine
  139. # Add trace for the current turbine
  140. fig.add_trace(
  141. go.Scatter(x=group[Field_PowerFloor], y=group[Field_CpMedian],
  142. mode='lines', name=currTurbineInfo[Field_NameOfTurbine], line=dict(color='darkblue'))
  143. )
  144. fig.add_trace(
  145. go.Scatter(x=dataFrameOfContractPowerCurve[Field_PowerFloor],
  146. y=dataFrameOfContractPowerCurve[Field_Cp],
  147. mode='lines+markers',
  148. name='合同功率曲线',
  149. marker=dict(color='red', size=5),
  150. line=dict(color='red'))
  151. )
  152. fig.update_layout(
  153. title={
  154. 'text': f'机组: {currTurbineInfo[Field_NameOfTurbine]}',
  155. # 'x': 0.5, # 标题位置居中
  156. },
  157. xaxis_title='功率',
  158. yaxis_title='风能利用系数',
  159. xaxis=dict(range=[0, upLimitOfPower], tickangle=-45),
  160. yaxis=dict(
  161. dtick=self.axisStepCp,
  162. range=[self.axisLowerLimitCp,
  163. self.axisUpperLimitCp] # axisStepCp
  164. ),
  165. legend=dict(x=1.05, y=0.5)
  166. )
  167. # 保存图像
  168. pngFileName = f"{currTurbineInfo[Field_NameOfTurbine]}.png"
  169. pngFilePath = os.path.join(outputAnalysisDir, pngFileName)
  170. fig.write_image(pngFilePath, scale=3)
  171. # 保存HTML
  172. htmlFileName = f"{currTurbineInfo[Field_NameOfTurbine]}.html"
  173. htmlFilePath = os.path.join(outputAnalysisDir, htmlFileName)
  174. fig.write_html(htmlFilePath)
  175. result_rows.append({
  176. Field_Return_TypeAnalyst: self.typeAnalyst(),
  177. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  178. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  179. Field_CodeOfTurbine: turbineCode,
  180. Field_Return_FilePath: pngFilePath,
  181. Field_Return_IsSaveDatabase: False
  182. })
  183. result_rows.append({
  184. Field_Return_TypeAnalyst: self.typeAnalyst(),
  185. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  186. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  187. Field_CodeOfTurbine: turbineCode,
  188. Field_Return_FilePath: htmlFilePath,
  189. Field_Return_IsSaveDatabase: True
  190. })
  191. result_df = pd.DataFrame(result_rows)
  192. return result_df