import os import pandas as pd import plotly.graph_objects as go import seaborn as sns from algorithmContract.confBusiness import * from algorithmContract.contract import Contract from behavior.analystWithGoodPoint import AnalystWithGoodPoint class TSRWindSpeedAnalyst(AnalystWithGoodPoint): """ 风电机组叶尖速比分析 """ def typeAnalyst(self): return "tsr_windspeed" def selectColumns(self): return [Field_DeviceCode,Field_Time,Field_WindSpeed,Field_ActiverPower,Field_RotorSpeed,Field_GeneratorSpeed] # def turbineAnalysis(self, dataFrame, outputAnalysisDir, outputFilePath, conf: Contract, Field_NameOfTurbine): # # self.tsr(dataFrame, outputFilePath, Field_WindSpeed, Field_RotorSpeed, # Field_ActiverPower) def tsr(self, dataFrame : pd.DataFrame): #Add column field_name dataFrame[Field_PowerFarmName] = self.currPowerFarmInfo.loc[Field_PowerFarmName] # Alias the power column # dataFrame[Field_Power] = dataFrame[Field_ActiverPower] # Calculate 'wind_speed_floor' # dataFrame[Field_WindSpeedFloor] = (dataFrame[Field_WindSpeed] / 1).astype(int) + 0.5 # Ensure the necessary columns are of float type # dataFrame['wind_speed'] = dataFrame[Field_WindSpeed].astype(float) dataFrame[Field_RotorSpeed] = dataFrame[Field_RotorSpeed].astype(float) dataFrame[Field_GeneratorSpeed] = dataFrame[Field_GeneratorSpeed].astype(float) # rotor_diameter = pd.to_numeric(rotor_diameter, errors='coerce') # # Calculate TSR # dataFrame['tsr'] = (dataFrame['rotor_speed'] * 0.104667 * # (rotor_diameter / 2)) / dataFrame['wind_speed'] # Group by 'wind_speed_floor' and calculate median, max, and min of TSR grouped = dataFrame.groupby([Field_WindSpeedFloor,Field_CodeOfTurbine,Field_NameOfTurbine]).agg({ Field_ActiverPower: 'median', Field_PowerFloor: 'median', Field_RotorSpeed : 'median', Field_GeneratorSpeed: 'median', Field_TSR : ['median', 'max', 'min'], Field_PowerFarmName: 'max' }).reset_index() # Rename columns for clarity post aggregation grouped.columns = [Field_WindSpeedFloor, Field_CodeOfTurbine, Field_NameOfTurbine,Field_Power,Field_PowerFloor, Field_RotorSpeed, Field_GeneratorSpeed, Field_TSR, Field_TSRMax, Field_TSRMin, Field_PowerFarmName] # Sort by 'wind_speed_floor' grouped.sort_values(by=[Field_NameOfTurbine, Field_PowerFloor]) return grouped def turbinesAnalysis(self, outputAnalysisDir, conf: Contract, turbineCodes): dictionary = self.processTurbineData(turbineCodes,conf,self.selectColumns()) dataFrameOfTurbines = self.userDataFrame(dictionary,conf.dataContract.configAnalysis,self) # 检查所需列是否存在 required_columns = {Field_WindSpeed, Field_RotorSpeed,Field_PowerFloor,Field_GeneratorSpeed,Field_TSR} if not required_columns.issubset(dataFrameOfTurbines.columns): raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}") turbrineInfos = self.common.getTurbineInfos( conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo) groupedOfTurbineModel = turbrineInfos.groupby(Field_MillTypeCode) returnDatas = [] for turbineModelCode, group in groupedOfTurbineModel: currTurbineCodes = group[Field_CodeOfTurbine].unique().tolist() currTurbineModeInfo = self.common.getTurbineModelByCode( turbineModelCode, self.turbineModelInfo) currDataFrameOfTurbines = dataFrameOfTurbines[dataFrameOfTurbines[Field_CodeOfTurbine].isin( currTurbineCodes)] #创建一个与dataFrameOfTurbines相同的dataFrame dataFrame=currDataFrameOfTurbines.copy() returnData = self.plot_tsr_distribution( self.tsr(dataFrame), outputAnalysisDir, conf, currTurbineModeInfo) returnDatas.append(returnData) returnResult = pd.concat(returnDatas, ignore_index=True) return returnResult def plot_tsr_distribution(self, dataFrame: pd.DataFrame, outputAnalysisDir, conf: Contract,turbineModelInfo: pd.Series): """ Generates tsr distribution plots for turbines in a wind farm. Parameters: - csvFileDirOfCp: str, path to the directory containing input CSV files. - farm_name: str, name of the wind farm. - encoding: str, encoding of the input CSV files. Defaults to 'utf-8'. """ x_name = 'wind_speed_floor' y_name = 'tsr' # upLimitOfTSR = 20 # 设置绘图样式 sns.set_palette('deep') # 初始化结果DataFrame # res = pd.DataFrame() # # # 遍历输入目录中的所有文件 # for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp): # for file_name in file_names: # # if not file_name.endswith(CSVSuffix): # continue # # file_path = os.path.join(root, file_name) # # # 读取CSV文件 # frame = pd.read_csv(file_path, encoding=encoding) # frame = frame[(frame[x_name] > 0)] # # # 选择需要的列并合并到结果DataFrame中 # res = pd.concat([res, frame], axis=0) # # # 重置索引 # ress = res.reset_index() # dataFrame[Field_NameOfTurbine] = dataFrame[Field_NameOfTurbine].astype(str) # dataFrame = dataFrame.sort_values(by=[Field_NameOfTurbine, x_name]) # 绘制全场TSR分布图 fig = go.Figure() # colors = px.colors.sequential.Turbo # 创建一个列表来存储各个风电机组的数据 turbine_data_list = [] # 遍历不同的turbine来添加线条 for turbine in dataFrame[Field_NameOfTurbine].unique(): turbine_data = dataFrame[dataFrame[Field_NameOfTurbine] == turbine] fig.add_trace(go.Scatter(x=turbine_data[x_name], y=turbine_data[y_name], mode='lines', # line=dict(color=colors[idx % len(colors)]), name=turbine)) # 提取数据 turbine_data_total = { "engineName": turbine, "engineCode": turbine_data[Field_CodeOfTurbine].iloc[0], "xData": turbine_data[x_name].tolist(), "yData": turbine_data[y_name].tolist(), } turbine_data_list.append(turbine_data_total) fig.update_layout( title={ "text": f'叶尖速比分布图-{turbineModelInfo[Field_MachineTypeCode]}', 'x': 0.5 }, # margin=dict( # t=35, # 顶部 margin,减小这个值可以使标题更靠近图形 # l=60, # 左侧 margin # r=60, # 右侧 margin # b=40, # 底部 margin # ), # legend=dict(title='Turbine', # x=1.02, # y=0.5, # orientation='v', # traceorder='normal', # font=dict(size=12), # bgcolor='rgba(255, 255, 255, 0)', # bordercolor='rgba(255, 255, 255, 0)'), legend=dict( orientation="h", # Horizontal orientation xanchor="center", # Anchor the legend to the center x=0.5, # Position legend at the center of the x-axis y=-0.2, # Position legend below the x-axis # itemsizing='constant', # Keep the size of the legend entries constant # itemwidth=50 ), xaxis=dict( title='风速', dtick=1, tickangle=-45, range=[0, 26]), yaxis=dict( title='叶尖风速比', dtick=self.axisStepTSR, range=[self.axisLowerLimitTSR, self.axisUpperLimitTSR] ) ) # 设置x轴标签旋转 fig.update_xaxes(tickangle=-45) # 保存图形 # fig.write_image(csvFileDirOfCp + r"/{}-TSR-Distibute.png".format(confData.farm_name),format='png',width=800, height=500,scale=3) # fig.show() engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "") if isinstance(engineTypeCode, pd.Series): engineTypeCode = engineTypeCode.iloc[0] engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "") if isinstance(engineTypeName, pd.Series): engineTypeName = engineTypeName.iloc[0] # 构建最终的JSON对象 json_output = { "analysisTypeCode": "风电机组叶尖速比和风速分析", "typecode": turbineModelInfo[Field_MillTypeCode], "engineCode": engineTypeCode, "engineTypeName": engineTypeName, "title": f'叶尖速比分布图-{turbineModelInfo[Field_MachineTypeCode]}', "xaixs": "风速", "yaixs": "叶尖风速比", "data": turbine_data_list } # 保存HTML # htmlFileName = f"{dataFrame[Field_PowerFarmName].iloc[0]}-TSR-Distribution-{turbineModelInfo[Field_MillTypeCode]}.html" # htmlFilePath = os.path.join(outputAnalysisDir, htmlFileName) # fig.write_html(htmlFilePath) result_rows = [] # result_rows.append({ # Field_Return_TypeAnalyst: self.typeAnalyst(), # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID, # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum, # Field_CodeOfTurbine: 'total', # Field_Return_FilePath: htmlFilePath, # Field_Return_IsSaveDatabase: True # }) # 将JSON对象保存到文件 output_json_path = os.path.join(outputAnalysisDir, f"{turbineModelInfo[Field_MillTypeCode]}.json") with open(output_json_path, 'w', encoding='utf-8') as f: import json json.dump(json_output, f, ensure_ascii=False, indent=4) # 如果需要返回DataFrame,可以包含文件路径 result_rows.append({ Field_Return_TypeAnalyst: self.typeAnalyst(), Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID, Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum, Field_CodeOfTurbine: 'total', Field_MillTypeCode: turbineModelInfo[Field_MillTypeCode], Field_Return_FilePath: output_json_path, Field_Return_IsSaveDatabase: True }) # 绘制每个设备的TSR分布图 for name, group in dataFrame.groupby([Field_NameOfTurbine, Field_CodeOfTurbine]): fig = go.Figure() # 创建一个列表来存储各个风电机组的数据 turbine_data_list_each = [] # 循环绘制turbine的线条 for turbine in dataFrame[Field_NameOfTurbine].unique(): turbine_data = dataFrame[dataFrame[Field_NameOfTurbine] == turbine] fig.add_trace(go.Scatter(x=turbine_data[x_name], y=turbine_data[y_name], mode='lines', line=dict(color='lightgrey'), showlegend=False)) # 提取数据 turbine_data_each = { "engineName": turbine, "engineCode": turbine_data[Field_CodeOfTurbine].iloc[0], "xData": turbine_data[x_name].tolist(), "yData": turbine_data[y_name].tolist(), } turbine_data_list_each.append(turbine_data_each) fig.add_trace(go.Scatter(x=group[x_name], y=group[y_name], mode='lines', line=dict(color='darkblue'), showlegend=False)) fig.update_layout( title={"text": '机组: {}'.format(name[0])}, # margin=dict( # t=35, # 顶部 margin,减小这个值可以使标题更靠近图形 # l=60, # 左侧 margin # r=60, # 右侧 margin # b=40, # 底部 margin # ), xaxis=dict( title='风速最低阈值 ', dtick=1, tickangle=-45, range=[0, 26]), yaxis=dict( title='叶尖速比', dtick=self.axisStepTSR, range=[self.axisLowerLimitTSR, self.axisUpperLimitTSR] ) ) fig.update_xaxes(tickangle=-45) engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "") if isinstance(engineTypeCode, pd.Series): engineTypeCode = engineTypeCode.iloc[0] engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "") if isinstance(engineTypeName, pd.Series): engineTypeName = engineTypeName.iloc[0] # 构建最终的JSON对象 json_output = { "analysisTypeCode": "风电机组叶尖速比和风速分析", "typecode": turbineModelInfo[Field_MillTypeCode], "engineCode": engineTypeCode, "engineTypeName": engineTypeName, "title": f'机组:{format(name[0])}', "xaixs": "功率(kW)", "yaixs": "叶尖速比", "data": turbine_data_list_each } # 将JSON对象保存到文件 output_json_path_each = os.path.join(outputAnalysisDir, f"{name[0]}.json") with open(output_json_path_each, 'w', encoding='utf-8') as f: import json json.dump(json_output, f, ensure_ascii=False, indent=4) # 如果需要返回DataFrame,可以包含文件路径 result_rows.append({ Field_Return_TypeAnalyst: self.typeAnalyst(), Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID, Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum, Field_CodeOfTurbine: name[1], Field_Return_FilePath: output_json_path_each, Field_Return_IsSaveDatabase: True }) # 保存图像 pngFileName = f"{name[0]}.png" pngFilePath = os.path.join(outputAnalysisDir, pngFileName) fig.write_image(pngFilePath, scale=3) # 保存HTML # htmlFileName = f"{name[0]}.html" # htmlFilePath = os.path.join(outputAnalysisDir, htmlFileName) # fig.write_html(htmlFilePath) result_rows.append({ Field_Return_TypeAnalyst: self.typeAnalyst(), Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID, Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum, Field_CodeOfTurbine: name[1], Field_Return_FilePath: pngFilePath, Field_Return_IsSaveDatabase: False }) # result_rows.append({ # Field_Return_TypeAnalyst: self.typeAnalyst(), # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID, # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum, # Field_CodeOfTurbine: name[1], # Field_Return_FilePath: htmlFilePath, # Field_Return_IsSaveDatabase: True # }) result_df = pd.DataFrame(result_rows) return result_df