123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- import os
- import pandas as pd
- import numpy as np
- import plotly.graph_objects as go
- from algorithmContract.confBusiness import *
- from algorithmContract.contract import Contract
- from behavior.analystWithGoodPoint import AnalystWithGoodPoint
- from plotly.subplots import make_subplots
- from scipy.optimize import curve_fit
- import math
- class YawErrorAnalyst(AnalystWithGoodPoint):
- """
- 风电机组静态偏航误差分析
- """
- fieldWindDirFloor = 'wind_dir_floor'
- fieldPower = 'power'
- fieldPowerMean = 'mean_power'
- fieldPowerMax = 'max_power'
- fieldPowerMin = 'min_power'
- fieldPowerMedian = 'median_power'
- fieldYawError = 'yaw_error'
- fieldStep = 'wsstep'
- fieldK = 'k'
- fieldWindSpeed = 'mean_WindSpeed'
- fieldcount = 'point_num'
- def typeAnalyst(self):
- return "yaw_error"
- def selectColumns(self):
- return [Field_DeviceCode,Field_Time,Field_WindSpeed,Field_ActiverPower,Field_AngleIncluded,Field_PitchAngel1,Field_RotorSpeed, Field_GeneratorSpeed]
- def filterCommon(self, dataFrame: pd.DataFrame,conf:Contract):
- #-------------------1.物理筛选--------
- # 使用 loc 方法获取 Field_RatedPower 列的值
- fullpower = self.turbineInfo[Field_RatedPower].iloc[0]
- # rated_power = self.turbineInfo.loc[self.turbineInfo[Field_CodeOfTurbine].isin(dataFrame[Field_CodeOfTurbine]), Field_RatedPower]
- # fullpower=rated_power.iloc[0]
- # rated_power=self.turbineInfo[Field_RatedPower]
- # 删除小于0的有功功率
- dataFrame = dataFrame[~(dataFrame[Field_ActiverPower] < 0)]
- # 删除小于2.5的风速
- dataFrame = dataFrame[~(dataFrame[Field_WindSpeed] < 2.5)]
- # 删除有功小于额定功率-100,变桨角度大于0.2的数据
- dataFrame = dataFrame[~((dataFrame[Field_ActiverPower] < fullpower - 100) & (dataFrame[Field_PitchAngel1] > 3))]
- # 删除有功小于额定功率-40,变桨角度大于1的数据
- dataFrame = dataFrame[~((dataFrame[Field_ActiverPower] < fullpower - 40) & (dataFrame[Field_PitchAngel1] > 5))]
- # 删除对风角度小于-30,大于30的数据
- dataFrame = dataFrame[~((dataFrame[Field_AngleIncluded].abs() > 30))]
- #-------------------2.数学筛选--------
- wsstep = 0.5
- stdup = 6
- stddown = 2.2
- for wscol in np.arange(3, 15.25, wsstep/2):
- dataFrame_lv = dataFrame[(dataFrame[Field_WindSpeed] >= (wscol-wsstep/4)) & (dataFrame[Field_WindSpeed] < (wscol+wsstep/4))]
- meanpowr = np.mean(dataFrame_lv[Field_ActiverPower])
- stddataws = np.std(dataFrame_lv[Field_ActiverPower])
- index1 = dataFrame[(dataFrame[Field_WindSpeed] >= (wscol-wsstep/4)) & (dataFrame[Field_WindSpeed] < (wscol+wsstep/4))
- & ((dataFrame_lv[Field_ActiverPower] - meanpowr) > stdup * stddataws) | ((meanpowr - dataFrame_lv
- [Field_ActiverPower]) > stddown * stddataws)].index
- dataFrame.drop(index1, inplace=True)
- return dataFrame
- def calculateYawError(self, dataFrame: pd.DataFrame, fieldAngleInclude,fieldActivePower, fieldWindSpeed):
-
- dataFrame = dataFrame.dropna(
- subset=[Field_NameOfTurbine, fieldAngleInclude, fieldActivePower,fieldWindSpeed])
- df_math_yaw = dataFrame.copy()
- # 偏航误差角度计算
- degree_length = 20 # 对风角度最大值
- yaw_data_out = np.empty((0, 4), float)
- wsstep = 0.5
- # 初始化一个字典来存储每 k 次循环的平均功率结果
- power_dict = {}
- for k in np.arange(3, 15.5, wsstep):
- yaw_data_value_co = np.empty((0, 4), float)
- yaw_data = df_math_yaw[(df_math_yaw[fieldWindSpeed] >= (k - 0.25)) & (df_math_yaw[fieldWindSpeed] < (k + 0.25))]
- # print(yaw_data)
- for m in np.arange((0 - degree_length), degree_length + 1, 0.5):
- wd = yaw_data[(yaw_data[fieldAngleInclude] >= (m - 0.5)) & (yaw_data[fieldAngleInclude] < (m + 0.5))]
- wd_row = wd.shape[0]
- if wd_row < 10:
- continue
- if wd_row >= 10:
- max_value_row = wd[fieldActivePower].idxmax()
- min_value_row = wd[fieldActivePower].idxmin()
- wd = wd.drop(max_value_row)
- wd = wd.drop(min_value_row)
- wd_row = wd.shape[0]
- mean_wd = wd[fieldAngleInclude].mean()
- mean_power = wd[fieldActivePower].mean()
- mean_ws = wd[fieldWindSpeed].mean()
- yaw_data_value = [mean_power, mean_wd, mean_ws, wd_row]
- yaw_data_value_co = np.vstack((yaw_data_value_co, yaw_data_value))
- # 存储当前 k 和 m 对应的 mean_power
- if k not in power_dict:
- power_dict[k] = {}
- power_dict[k][m] = mean_power
- # 检查 yaw_data_value_co 是否为空
- if yaw_data_value_co.size == 0:
- # print(f"yaw_data_value_co k={k}是空的,无法计算argmax")
- continue
- else:
- max_row = np.argmax(yaw_data_value_co[:, 0])
- yaw_data_out = np.vstack((yaw_data_out, yaw_data_value_co[max_row, :]))
- # 将结果转换为 DataFrame
- result_df = pd.DataFrame(yaw_data_out, columns=[self.fieldPowerMean, self.fieldYawError,self.fieldWindSpeed,self.fieldcount])
- # result_df.to_csv('D:\\dashengkeji\\project\\WTOAAM\\debug_tset_20241008\\result_df_JHS_.csv',index=False)
- # 构建最终画图的 DataFrame
- final_df = pd.DataFrame(columns=[self.fieldWindDirFloor] + [f'{k}-{k+wsstep}' for k in np.arange(4, 9, wsstep)])
- rows = []
- for m in np.arange((0 - degree_length), degree_length + 1, 1):
- row = {self.fieldWindDirFloor: m}
- for k in np.arange(4, 9, wsstep):
- if k in power_dict and power_dict[k]: # 检查 power_dict[k] 是否存在且不为空
- row[f'{k}-{k+wsstep}'] = power_dict[k].get(m, np.nan)
- else:
- row[f'{k}-{k+wsstep}'] = np.nan # 如果 power_dict[k] 为空,则填充 np.nan
- rows.append(row)
- final_df = pd.concat([final_df, pd.DataFrame(rows)], ignore_index=True)
- return result_df,final_df
- def turbinesAnalysis(self, outputAnalysisDir, conf: Contract, turbineCodes):
- dictionary = self.processTurbineData(turbineCodes,conf,self.selectColumns())
- dataFrameMerge = self.userDataFrame(dictionary,conf.dataContract.configAnalysis,self)
- turbineInfos = self.common.getTurbineInfos(conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo)
- return self.yawErrorAnalysis(dataFrameMerge,turbineInfos, outputAnalysisDir, conf)
- def yawErrorAnalysis(self, dataFrameMerge: pd.DataFrame,turbineModelInfo: pd.Series, outputAnalysisDir, conf: Contract):
- # 检查所需列是否存在
- required_columns = {Field_ActiverPower, Field_YawError,Field_WindSpeed,Field_PitchAngel1,Field_Cp,Field_TSR}
- if not required_columns.issubset(dataFrameMerge.columns):
- raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
-
- results = []
- result_rows = []
- grouped = dataFrameMerge.groupby(
- [Field_NameOfTurbine, Field_CodeOfTurbine])
-
- for name, group in grouped:
- group = self.filterCommon(group,conf)
- df,final_df = self.calculateYawError(
- group, Field_YawError, Field_ActiverPower,Field_WindSpeed)
-
- df.dropna(inplace=True)
-
- # final_df.dropna(inplace=True)
- """
- 自动化选择tsr平稳段的风速段作为风速最后的筛选区间
-
- # 滑动窗口size
- window_size=5
- # 差分阈值
- threshold=2
- diff = group[Field_TSR].diff().abs()
- stable_mask = diff.rolling(window=window_size).mean() < threshold
- # print("diff的索引:", diff.index)
- # print("stable_mask的索引:", stable_mask.index)
- # 找到最长的平稳段
- longest_stable_segment = None
- max_length = 0
- current_segment = None
- for i in range(len(stable_mask)):
- if i in stable_mask.index:
- if stable_mask[i]:
- if current_segment is None:
- current_segment = [i]
- else:
- current_segment.append(i)
- else:
- if current_segment is not None:
- if len(current_segment) > max_length:
- longest_stable_segment = current_segment
- max_length = len(current_segment)
- current_segment = None
- if current_segment is not None and len(current_segment) > max_length:
- longest_stable_segment = current_segment
- if longest_stable_segment is not None:
- start_idx = longest_stable_segment[0]
- end_idx = longest_stable_segment[-1]
- # 获取tsr平稳区间对应的风俗区间
- if start_idx is not None and end_idx is not None:
- # 提取对应的windspeed区间
- stable_windspeed_segment = group.loc[start_idx:end_idx, Field_WindSpeed]
- # print(f"平稳段的起始索引: {start_idx}, 结束索引: {end_idx}")
- # print("对应的windspeed区间:")
- # print(stable_windspeed_segment)
- # 计算风速平稳段的最大值和最小值
- max_windspeed = stable_windspeed_segment.max()
- min_windspeed = stable_windspeed_segment.min()
- # print(f"风速平稳段的最大值: {max_windspeed}")
- # print(f"风速平稳段的最小值: {min_windspeed}")
- else:
- print("未找到平稳段")
- """
- # 筛选出 self.fieldWindSpeed 列的数值处于 4.5 到 8 区间内的行
- filtered_df = df[(df[self.fieldWindSpeed] >= 4.5) & (df[self.fieldWindSpeed] <= 8)]
- # 查找 df 中 self.fieldYawError列的平均值,并向下取整作为偏航误差
- max_yaw_error = filtered_df[self.fieldYawError].mean()
- max_yaw_error_floor = np.floor(max_yaw_error)
- # 存储结果
- results.append({
- Field_NameOfTurbine: name[0],
- Field_YawError: max_yaw_error_floor
- })
- #对 final_df 中的数据进行可视化
- fig = go.Figure()
- for col in final_df.columns[1:]: # 跳过第一列 self.fieldWindDirFloor
- fig.add_trace(go.Scatter(
- x=final_df[self.fieldWindDirFloor],
- y=final_df[col],
- mode='lines+markers',
- name=col
- ))
-
- fig.update_layout(
- title=f'偏航误差分析 {name[0]} ',
- xaxis_title='偏航角度',
- yaxis_title='平均功率',
- legend_title='风速区间',
- showlegend=True
- )
- # 将NAN转化为空字符串
- yData = final_df[col].astype(str).replace(["None", "nan", "null", ""], "")
- print("yData:", yData)
- # 确保从 Series 中提取的是具体的值
- 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": "偏航误差分析",
- "engineCode": engineTypeCode,
- "engineTypeName": engineTypeName,
- "xaixs": "偏航角度(°)",
- "yaixs": "有功功率(kw)",
- "data": [
- {
- "engineName": name[0],
- "engineCode": name[1],
- "title": f'静态偏航误差分析 {name[0]}',
- "xData": final_df[self.fieldWindDirFloor].tolist(),
- "yData": yData.tolist(),
- "legend": col,
- "type": "lines+markers",
-
- }
- for col in final_df.columns[1:] # 跳过第一列 self.fieldWindDirFloor
- ]
- }
-
- # Save to file
- filePathOfImage = os.path.join(outputAnalysisDir, f"{name[0]}.png")
- fig.write_image(filePathOfImage, scale=3)
- # filePathOfHtml = os.path.join(outputAnalysisDir, f"{name[0]}.html")
- # fig.write_html(filePathOfHtml)
- # 将JSON对象保存到文件
- output_json_path = os.path.join(outputAnalysisDir, f"{name[0]}.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: name[1],
- Field_Return_FilePath: output_json_path,
- Field_Return_IsSaveDatabase: True
- })
- 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: filePathOfImage,
- 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: filePathOfHtml,
- # Field_Return_IsSaveDatabase: True
-
-
- # })
- # # 初始化一个空的DataFrame,指定列名
- # columns = [Field_NameOfTurbine, Field_YawError]
- # dataFrameResult = pd.DataFrame(results, columns=columns)
- # filePathOfYawErrorResult = os.path.join(
- # outputAnalysisDir, f"{name[0]}yaw_error_result{CSVSuffix}")
- # df.to_csv(filePathOfYawErrorResult, index=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: filePathOfYawErrorResult,
- # Field_Return_IsSaveDatabase: True
- # })
- # 初始化一个空的DataFrame,指定列名
- columns = [Field_NameOfTurbine, Field_YawError]
- dataFrameResult = pd.DataFrame(results, columns=columns)
- # 新增三列,分别表示 [0,3], (3,5], (5, 正无穷)
- dataFrameResult['[0,3]'] = np.where((abs(dataFrameResult[Field_YawError]) >= 0) & (abs(dataFrameResult[Field_YawError]) <= 3), dataFrameResult[Field_YawError], 0)
- dataFrameResult['(3,5]'] = np.where((abs(dataFrameResult[Field_YawError]) > 3) & (abs(dataFrameResult[Field_YawError]) <= 5), dataFrameResult[Field_YawError], 0)
- dataFrameResult['(5, )'] = np.where(abs(dataFrameResult[Field_YawError]) > 5, dataFrameResult[Field_YawError], 0)
- dataFrameResult["powerloss"]=((1 - np.cos(np.radians(dataFrameResult[Field_YawError])) ** 2) * 100).round(2)
- print("损失电量:",dataFrameResult["powerloss"])
- filePathOfYawError = os.path.join(
- outputAnalysisDir, f"yaw_error_result{CSVSuffix}")
- dataFrameResult.to_csv(filePathOfYawError, index=False)
- 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:"total",
- Field_Return_FilePath: filePathOfYawError,
- Field_Return_IsSaveDatabase: True
- })
- result_df = pd.DataFrame(result_rows)
- return result_df
|