2024年华为杯研赛A题解题思路
A题 风电场有功功率优化分配
问题一:风机主轴及塔架疲劳损伤程度量化指标计算低复杂度模型
实际应用中,载荷(指主轴扭矩和塔架推力)数据随机性很强,且周期特征不标准,波峰波谷不易辨识(如图1所示);此外,载荷循环(主轴扭矩和塔架推力的应力循环)的周期时长远远超过了实时计算的时间间隔(1s),因此,定义中介绍的方法无法有效计算不同载荷的循环次数。目前常用雨流计数法统计不同幅值载荷相应的循环次数,但该方法无法在线实时求解。针对这一问题,请建立数学模型,实现对风机的两种不同元件(主轴和塔架)任意时段累积疲劳损伤程度量化指标的实时计算;本题提供100个时长为100s的塔架推力与主轴扭矩数据,及其基于雨流计数法计算所得到的累积疲劳损伤值和等效疲劳载荷(见附件1);要求方法合理有效,不得使用机器学习方法;要求在测试环境中基于CPU计算,计算时间小于1.00s,算法求解时间要尽可能地短,并且所得计算结果能正确反映元件累积疲劳损伤程度;需展示所计算结果与数据中提供的雨流计数法所得结果的相似程度(能反映累积疲劳损伤程度增长情况即可,不必与参考结果相等),需将包括100s时长内所有100台风机的全部200件元件的每秒疲劳损伤值(可以不考虑在此之前的疲劳损伤值,即初始时刻可以从0开始)列入附件5表格中;同时,需展示从0-100s内风机主要元件累积疲劳损伤程度的增长过程(选择5-10个有代表性的样本,用图片形式展示增长过程并说明所提出的建模方法100台风机的所有数据样本均有效)。
1.1题目分析
· 目标:建立一个能够实时计算风机主轴和塔架累积疲劳损伤程度的模型,且必须计算时间小于1秒。
· 关键挑战:应力循环随机性较高,且传统的雨流计数法不能在线实时求解。
· 方法建议:可以简化雨流计数法,采用离线统计数据中的累积疲劳损伤值作为参考,开发一个基于时间序列分析和插值的实时估算模型。通过计算主轴扭矩和塔架推力数据,推导出应力循环的周期特征,并通过线性拟合来快速估计每秒的累积疲劳损伤。
· 优化方向:减少复杂计算,利用简化的数学方法(如快速插值)提高实时计算速度,并通过测试和调优确保在1秒内完成计算。
1.2解题思路
1. 问题描述
风机的主轴和塔架在长期运行过程中,受到风力的不断变化,导致交变载荷的应力循环。长时间的应力循环作用将引发疲劳损伤,影响风机的安全运行。因此,构建一个模型来实时计算累积疲劳损伤,并且在 CPU 环境下保证计算时间小于 1 秒,是非常重要的。
2. 思路概述
要解决该问题,可以从以下几个步骤进行:
- 载荷数据的获取和处理:从时间序列数据中获取主轴扭矩和塔架推力的变化。
- 应力循环的识别:通过识别时间序列中的峰值和谷值,确定应力循环的幅值。
- 累积疲劳损伤模型:基于 Palmgren-Miner 累积损伤理论和 S-N 曲线模型,计算元件的累积疲劳损伤。
- 实时计算优化:为了满足实时计算需求,使用优化技术减少计算复杂度,并进行并行化处理。
3. 建模过程
3.1 载荷数据的预处理
假设时间序列数据包含风机主轴扭矩和塔架推力,记作
这些数据随着时间变化,反映了风机在不同时间的受力情况。为了进行疲劳损伤计算,首先需要对这些数据进行处理。
时间序列数据结构:
• Tshaft(t):主轴扭矩随时间变化的序列。
• Ftower(t):塔架推力随时间变化的序列。
3.2 应力循环识别
应力循环可以通过分析载荷的峰值和谷值来确定。我们可以通过局部最大值和最小值识别出应力循环,计算应力幅值
其中 Tpeak,i 是第 i 个循环的峰值, Tvalley,i 是对应的谷值。
使用滑动窗口方法,我们可以在数据中识别局部最大值和最小值,并确定应力循环。通过这种方式,可以快速找到每个时间段内的应力幅值。
3.3 累积疲劳损伤计算
根据 Palmgren-Miner 损伤累积理论,累积疲劳损伤可以通过以下公式计算:
其中:
• ni 表示应力幅值为 Si 的循环次数。
• Ni 表示在应力幅值为 Si 下,部件能够承受的最大循环次数。
S-N 曲线模型
S-N 曲线用于描述材料在不同应力水平下的疲劳寿命。一针情况下,S-N 曲线
满足幂函数关系:
其中:
• S 为应力幅值;
• N 为最大承受的循环次数;
• m 为材料的 Wohler 指数;
• C 为材料常数。
通过该公式可以得到应力幅值 Si 对应的最大循环次数Ni :
Goodman 修正
为了处理非零平均应力的情况,我们引入 Goodman 修正:
其中:
• Sai 为应力幅值;
• Smi 为平均应力;
• σb 为材料的拉伸强度。
通过 Goodman 修正后的等效应力幅值 Seq 用于计算疲劳寿命。
3.4 实时计算优化
由于本问题要求计算时间小于 1 秒,我们需要对计算过程进行优化。
3.4.1 插值表优化
通过预先计算一部分 S-N 曲线的数据点,将其存储在插值表中。当需要计算 Ni 时,直接查表而不是重复计算幂函数,这可以显著加快计算速度。
3.4.2 并行化处理
为了处理 100 台风机的累积疲劳损伤,可以将每台风机的计算任务并行执行。利用多线程技术,可以减少总计算时间。
3.4.3 滑动窗口计算
为了确保计算时间不超过 1 秒,我们仅处理当前时间窗口的数据,减少对整个时间序列的依赖。
4. 详细计算过程总结
- 1. 载荷循环识别:通过滑动窗口确定应力循环的峰值和谷值。
- 2. 应力幅值计算:根据峰谷差计算每个循环的应力幅值ΔSi。
- 3. 累积疲劳损伤计算:根据 S-N 曲线模型和 Goodman 修正,计算每个应力幅值对应的疲劳寿命 Ni ,并累积疲劳损伤。
- 4. 实时计算优化:使用插值表和并行计算技术,确保计算时间小于 1秒。
5. 数学公式总结
1.3参考代码
import pandas as pd
# 读取附件中的 Excel 数据文件
file_path = '/mnt/data/附件1-疲劳评估数据.xlsx'
# 读取主轴扭矩和塔架推力数据
torque_data = pd.read_excel(file_path, sheet_name='主轴扭矩')
force_data = pd.read_excel(file_path, sheet_name='塔架推力')
# 提取时间列
time_data = torque_data['T(s)']
# 查看数据结构
print(torque_data.head())
print(force_data.head())
import numpy as np
def identify_peaks_and_valleys(series, window_size=5):
peaks = []
valleys = []
for i in range(window_size, len(series) - window_size):
window = series[i - window_size:i + window_size]
if series[i] == max(window):
peaks.append(series[i])
elif series[i] == min(window):
valleys.append(series[i])
return peaks, valleys
# 识别 WT1 风机的主轴扭矩和塔架推力的峰谷值
peaks_torque_wt1, valleys_torque_wt1 = identify_peaks_and_valleys(torque_data['WT1'])
peaks_force_wt1, valleys_force_wt1 = identify_peaks_and_valleys(force_data['WT1'])
# 累积疲劳损伤计算
def calculate_fatigue_damage(peaks, valleys, m=10, C=1e7, sigma_b=50000000):
stress_amplitudes = [peak - valley for peak, valley in zip(peaks, valleys)]
total_damage = 0
for S in stress_amplitudes:
N = C / (S ** m)
total_damage += 1 / N
return total_damage
# 计算 WT1 风机的累积疲劳损伤
damage_torque_wt1 = calculate_fatigue_damage(peaks_torque_wt1, valleys_torque_wt1)
damage_force_wt1 = calculate_fatigue_damage(peaks_force_wt1, valleys_force_wt1)
print(f"WT1 风机主轴扭矩的累积疲劳损伤: {damage_torque_wt1}")
print(f"WT1 风机塔架推力的累积疲劳损伤: {damage_force_wt1}")
import concurrent.futures
# 定义并行计算函数
def parallel_fatigue_damage_calculation(turbines_data):
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = [executor.submit(calculate_fatigue_damage, data['peaks'], data['valleys']) for data in turbines_data]
return [future.result() for future in futures]
# 示例:风机数据格式
turbines_data = [{'peaks': peaks_torque_wt1, 'valleys': valleys_torque_wt1}, # WT1 风机数据
{'peaks': peaks_force_wt1, 'valleys': valleys_force_wt1}, # WT2 风机数据
# 继续处理其他风机数据
]
# 并行计算 100 台风机的累积疲劳损伤
total_damages = parallel_fatigue_damage_calculation(turbines_data)
import matplotlib.pyplot as plt
# 绘制 WT1 风机累积疲劳损伤的变化曲线
plt.figure(figsize=(10, 6))
plt.plot(time_data[:len(peaks_torque_wt1)], np.cumsum(peaks_torque_wt1), label='Torque (WT1)', color='blue', linewidth=2)
plt.plot(time_data[:len(peaks_force_wt1)], np.cumsum(peaks_force_wt1), label='Force (WT1)', color='green', linewidth=2)
# 图表美化
plt.title('WT1 风机累积疲劳损伤随时间变化', fontsize=16)
plt.xlabel('时间 (s)', fontsize=14)
plt.ylabel('累积疲劳损伤', fontsize=14)
代码
1. 数据载入与处理
首先,我们需要从数据文件中提取主轴扭矩和塔架推力的时间序列数据。
import pandas as pd
# 读取附件中的 Excel 数据文件
file_path = '/mnt/data/附件1-疲劳评估数据.xlsx'
# 读取主轴扭矩和塔架推力数据
torque_data = pd.read_excel(file_path, sheet_name='主轴扭矩')
force_data = pd.read_excel(file_path, sheet_name='塔架推力')
# 提取时间列
time_data = torque_data['T(s)']
# 查看数据结构
print(torque_data.head())
print(force_data.head())
2. 峰谷识别算法
使用滑动窗口识别主轴扭矩和塔架推力的峰值和谷值
import numpy as np
def identify_peaks_and_valleys(series, window_size=5):
peaks = []
valleys = []
for i in range(window_size, len(series) - window_size):
window = series[i - window_size:i + window_size]
if series[i] == max(window):
peaks.append(series[i])
elif series[i] == min(window):
valleys.append(series[i])
return peaks, valleys
# 识别 WT1 风机的主轴扭矩和塔架推力的峰谷值
peaks_torque_wt1, valleys_torque_wt1 = identify_peaks_and_valleys(torque_data['WT1'])
peaks_force_wt1, valleys_force_wt1 = identify_peaks_and_valleys(force_data['WT1'])
3. 累积疲劳损伤计算
使用 S-N 曲线和 Goodman 修正计算累积疲劳损伤。
# 累积疲劳损伤计算
def calculate_fatigue_damage(peaks, valleys, m=10, C=1e7, sigma_b=50000000):
stress_amplitudes = [peak - valley for peak, valley in zip(peaks, valleys)]
total_damage = 0
for S in stress_amplitudes:
N = C / (S ** m)
total_damage += 1 / N
return total_damage
# 计算 WT1 风机的累积疲劳损伤
damage_torque_wt1 = calculate_fatigue_damage(peaks_torque_wt1, valleys_torque_wt1)
damage_force_wt1 = calculate_fatigue_damage(peaks_force_wt1, valleys_force_wt1)
print(f"WT1 风机主轴扭矩的累积疲劳损伤: {damage_torque_wt1}")
print(f"WT1 风机塔架推力的累积疲劳损伤: {damage_force_wt1}")
4. 实时计算优化
为了加速 100 台风机的计算过程,使用并行计算。
import concurrent.futures
# 定义并行计算函数
def parallel_fatigue_damage_calculation(turbines_data):
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = [executor.submit(calculate_fatigue_damage, data['peaks'], data['valleys']) for data in turbines_data]
return [future.result() for future in futures]
# 示例:风机数据格式
turbines_data = [{'peaks': peaks_torque_wt1, 'valleys': valleys_torque_wt1}, # WT1 风机数据
{'peaks': peaks_force_wt1, 'valleys': valleys_force_wt1}, # WT2 风机数据
# 继续处理其他风机数据
]
# 并行计算 100 台风机的累积疲劳损伤
total_damages = parallel_fatigue_damage_calculation(turbines_data)
5. 可视化
绘制累积疲劳损伤随时间的变化图表,确保图表美观。
import matplotlib.pyplot as plt
# 绘制 WT1 风机累积疲劳损伤的变化曲线
plt.figure(figsize=(10, 6))
plt.plot(time_data[:len(peaks_torque_wt1)], np.cumsum(peaks_torque_wt1), label='Torque (WT1)', color='blue', linewidth=2)
plt.plot(time_data[:len(peaks_force_wt1)], np.cumsum(peaks_force_wt1), label='Force (WT1)', color='green', linewidth=2)
# 图表美化
plt.title('WT1 风机累积疲劳损伤随时间变化', fontsize=16)
plt.xlabel('时间 (s)', fontsize=14)
plt.ylabel('累积疲劳损伤', fontsize=14)
plt.legend()
plt.grid(True)
# 保存并展示图片
plt.savefig('/mnt/data/fatigue_damage_trend_wt1.png')
plt.show()
问题二:利用风速及功率估算塔架推力和主轴扭矩
风速与风机的发电功率之间具有正相关性,一个直观的理解是,当风所携带的风能被风机完全消化转化为电能后,风机所承受的推力(风推动风轮平面产生的推力)和扭矩(风轮实际转速与当前风速下应达到的转速不匹配带来的扭矩)是最小的;当风速相对风机的发电功率过高时,多余的风能就会有一部分作用于风机上,形成主轴扭矩和塔架推力,增加风机的累积疲劳损伤程度(本题所考虑的5MW风机对应的额定风速为11.2m/s)。因此,请建立数学模型,根据风机所处位置的风速条件和功率参考值,估算当前风机所承受的应力/扭矩;模型可结合受力分析、能量守恒、或其他任意合理思路进行建立(数据量较少,不建议使用机器学习方法)。本题给出数据包括:各风机轮毂处等效风速、有功功率参考值;输出数据为风机轴系扭矩、风机塔顶推力。要求能够利用本题所提供的数据(附件2)估算各个风机任意时刻的应力/扭矩值,并与数据中给出的参考值进行对比;需将100台风机在全部时刻的应力/扭矩计算结果列入附件6的表格中,并需要统计全部时刻估算值与参照值之差的平方和以展示计算结果与实际数据的对比结果。
2.1题目分析
· 目标:根据风速和功率参考值,建立模型估算风机的轴系扭矩和塔顶推力。
· 关键挑战:功率和风速之间的非线性关系以及物理受力分析的复杂性。
· 方法建议:通过物理公式推导,结合能量守恒定律和风机的气动特性,使用风速和功率计算推力和扭矩。可以参考附件中的S-N曲线或Goodman曲线修正系数来提升精度。
· 数据处理:利用附件提供的风速与功率数据,结合拟合算法(如最小二乘法)优化模型的参数,确保模型能够精确反映实际情况。
2.2解题思路
1. 问题分析
风机系统中的主要参数包括:
我们的任务是通过风速 V 和功率调度指令 Pref ,估算出塔架推力 Ft 和主轴扭矩 T shaft 。
2. 建模过程
2.1 风机功率方程
风机的功率与风速之间的关系可以通过以下公式描述:
其中:
2.2 叶尖速比 λ
叶尖速比λ的计算公式为:
其中:
2.3 功率系数 Cp
功率系数是一个复杂的非线性函数,通常通过查表或使用经验公式计算。以下是一个常用的经验公式:
其中:
2.4 主轴扭矩估算
主轴扭矩 T shaft 周功率和转速的关系可以表示为:
其中:
2.5 塔架推力估算
塔架推力 Ft 周风速和功率输出相关,可以通过以下公式估算:
3. 模型优化与简化
由于功率系数 Cp 和推力系数 Ct 是复杂的非线性函数,可以通过查表法或插值法对其进行简化处理,减少计算复杂度。同时在 CPU 环境中进行并行计算,以提高计算效率。
2.3参考代码
import scipy.io as sio
import numpy as np
import pandas as pd
# 读取.mat文件
data = sio.loadmat('/mnt/data/附件2-风电机组采集数据.mat')
# 提取风电场1的数据(WF1),假设文件结构为多维字典
wf1 = data['WF1']
# 提取时间、输入(风速V、功率调度指令Pref)、输出(主轴扭矩Tshaft、塔架推力Ft、输出功率Pout)、状态(桨距角pitch、转速omega r和omega f)
time = wf1['time'][0][0] # 2000s的时间数据
inputs = wf1['inputs'][0][0] # 包括风速V和功率调度Pref
outputs = wf1['outputs'][0][0] # 包括主轴扭矩Tshaft、塔架推力Ft、输出功率Pout
states = wf1['states'][0][0] # 包括桨距角pitch和转速omega r、omega f
# 将数据转为pandas DataFrame便于处理
df = pd.DataFrame({
'time': time.flatten(),
'wind_speed': inputs[:, 1].flatten(), # 风速V
'power_ref': inputs[:, 0].flatten(), # 功率调度Pref
'Tshaft': outputs[:, 0].flatten(), # 主轴扭矩Tshaft
'Ft': outputs[:, 1].flatten(), # 塔架推力Ft
'Pout': outputs[:, 2].flatten(), # 实际输出功率Pout
'pitch': states[:, 0].flatten(), # 桨距角pitch
'omega_r': states[:, 1].flatten() # 低速轴转速omega r
})
# 查看前几行数据
print(df.head())
# 设定常数
rho = 1.225 # 空气密度 kg/m^3
R = 50 # 叶片半径,假设为50米
A = np.pi * R**2 # 风机扫风面积
# 功率系数 Cp 的经验公式
def calculate_Cp(lambda_r, beta):
lambda_i = 1 / (1 / (lambda_r + 0.08 * beta) - 0.035 / (beta**3 + 1))
Cp = 0.22 * ((116 / lambda_i) - 0.4 * beta - 5) * np.exp(-12.5 / lambda_i)
return Cp
# 叶尖速比 lambda 的计算公式
def calculate_lambda(omega_r, R, V):
return (omega_r * R) / V
# 主轴扭矩计算公式
def calculate_Tshaft(Pout, omega_r):
return Pout / omega_r
# 塔架推力计算公式
def calculate_Ft(V, Cp, beta):
Ct = Cp * 1.5 # 简化假设,Ct 与 Cp 成正比
return 0.5 * rho * A * Ct * V**2
# 遍历所有时间点,计算Cp、主轴扭矩Tshaft、塔架推力Ft
df['lambda_r'] = calculate_lambda(df['omega_r'], R, df['wind_speed'])
df['Cp'] = calculate_Cp(df['lambda_r'], df['pitch'])
df['Tshaft_estimated'] = calculate_Tshaft(df['Pout'], df['omega_r'])
df['Ft_estimated'] = calculate_Ft(df['wind_speed'], df['Cp'], df['pitch'])
# 输出前几行结果
print(df[['Tshaft_estimated', 'Ft_estimated']].head())
import matplotlib.pyplot as plt
# 绘制主轴扭矩和塔架推力的估算结果与实际值对比
plt.figure(figsize=(10, 6))
# 主轴扭矩
plt.subplot(2, 1, 1)
plt.plot(df['time'], df['Tshaft'], label='Actual Tshaft', color='blue')
plt.plot(df['time'], df['Tshaft_estimated'], label='Estimated Tshaft', color='orange', linestyle='--')
plt.title('主轴扭矩估算与实际值对比')
plt.xlabel('时间 (s)')
plt.ylabel('主轴扭矩 (Nm)')
plt.legend()
plt.grid(True)
# 塔架推力
plt.subplot(2, 1, 2)
plt.plot(df['time'], df['Ft'], label='Actual Ft', color='green')
plt.plot(df['time'], df['Ft_estimated'], label='Estimated Ft', color='red', linestyle='--')
plt.title('塔架推力估算与实际值对比')
plt.xlabel('时间 (s)')
plt.ylabel('塔架推力 (N)')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.savefig('/mnt/data/wind_turbine_estimation.png')
plt.show()