简介:这份资源是2023年全国大学生数学建模竞赛A题优秀论文的完整PDF,面向备战数模国赛的本科生、研究生及指导教师,聚焦定日镜场输出热功率优化这一光热发电核心问题。论文围绕三个递进问题展开:先建立镜场与镜面坐标系,借助坐标转换矩阵与反射定律确定镜面姿态,并设计阴影遮挡效率与集热器截断效率的计算方法;再提出吸收塔选址策略与分区域同心圆规划策略,以变步长搜索算法求解最优排布;最后引入定日镜规格分区域规划,结合二分法进一步优化参数。文中给出年平均输出热功率、光学效率、吸收塔坐标、定日镜尺寸与总面数等具体求解结果,并附有完整建模思路与算法设计。资源包为1个PDF文件,大小约11.06MB,结构清晰便于检索。目前已有383人学习,适合需要参考优秀论文建模框架、算法实现与结果表述的参赛者研读。
1. 从一份国赛优秀论文拆解定日镜场输出功率优化的完整实现路径
如果你正在准备数学建模竞赛,或者需要快速复现一套光热发电定日镜场的效率计算方案,这份 2023 数模国赛优秀论文 A127 值得仔细拆。它解决的核心问题是:给定定日镜场的几何布局和气象条件,如何计算年平均光学效率和输出热功率,并在此基础上反向优化镜场参数使功率最大化。论文覆盖了从坐标系建立、光线向量推导、阴影遮挡判断到变步长搜索优化的全链路,三个问题层层递进,最终给出了 60MW 额定功率下的具体规划方案。适合正在备战数模竞赛、做新能源方向课程设计、或者需要一套可运行的光热电站仿真代码的读者。下面我按“模型怎么建 → 代码怎么写 → 坑在哪 → 怎么验证”的顺序,把这份论文的工程落地路径拆开讲。
2. 坐标系与光线向量:定日镜姿态计算的数学底座
2.1 为什么必须建两套坐标系
定日镜场优化的第一步不是写代码,而是把几何关系理清楚。论文建立了镜场坐标系 XYZ 和镜面坐标系 xyz 两套系统。镜场坐标系以圆形镜场中心为原点,正东为 X 轴,正北为 Y 轴,垂直向上为 Z 轴。镜面坐标系以每面定日镜中心为原点,镜面左右方向为 x 轴,上下方向为 y 轴,法线方向为 z 轴。
这么做的原因很直接:太阳入射光线是在镜场坐标系下描述的,而定日镜的反射特性是在镜面坐标系下定义的。两套坐标系之间通过坐标转换矩阵 T 关联,论文中给出的转换关系是 $\vec{R}_g = \vec{R}_a \cdot T$。实际写代码时,这个 T 矩阵由定日镜的俯仰角和方位角决定,本质上是两个旋转矩阵的乘积。
我一般会先把转换矩阵写成独立函数,方便后续调试:
import numpy as np def build_transform_matrix(alpha_p, gamma_p): """ 构建镜面坐标系到镜场坐标系的转换矩阵 alpha_p: 俯仰角(弧度),镜面绕水平轴的旋转 gamma_p: 方位角(弧度),镜面绕垂直轴的旋转 返回: 3x3 转换矩阵 T """ # 绕 z 轴旋转 gamma_p(方位) Rz = np.array([ [np.cos(gamma_p), -np.sin(gamma_p), 0], [np.sin(gamma_p), np.cos(gamma_p), 0], [0, 0, 1] ]) # 绕 x 轴旋转 alpha_p(俯仰) Rx = np.array([ [1, 0, 0], [0, np.cos(alpha_p), -np.sin(alpha_p)], [0, np.sin(alpha_p), np.cos(alpha_p)] ]) return Rz @ Rx这段代码的逻辑是:先绕 Z 轴旋转方位角,再绕 X 轴旋转俯仰角,得到完整的姿态变换。参数说明:alpha_p和gamma_p的单位必须是弧度,如果你从题目附件读到的是角度值,记得先做np.deg2rad()转换。这个矩阵后续会反复用到——计算法向量、判断阴影、算截断效率都依赖它。
2.2 入射光线与反射光线的向量推导
入射光线方向向量由太阳高度角 $\alpha_s$ 和太阳方位角 $\gamma_s$ 决定:
$$\vec{I}_i = (-\cos\alpha_s \sin\gamma_s, -\cos\alpha_s \cos\gamma_s, -\sin\alpha_s)$$
其中太阳高度角和方位角又由纬度 $\phi$、太阳赤纬角 $\delta$、太阳时角 $\omega$ 计算。论文中给出的公式是:
$$\sin\alpha_s = \cos\delta \cos\phi \cos\omega + \sin\delta \sin\phi$$
$$\cos\gamma_s = \frac{\sin\delta - \sin\alpha_s \sin\phi}{\cos\alpha_s \cos\phi}$$
太阳赤纬角和时角的计算:
$$\sin\delta = \sin\frac{2\pi D}{365} \sin\frac{2\pi \cdot 23.45}{360}$$
$$\omega = \frac{\pi}{12}(ST - 12)$$
这里 D 是距离春分的天数,ST 是当地时间(小时)。这几个公式是整个模型的时间基准,写代码时建议封装成一个函数,输入日期和时刻,输出入射向量。
反射光线方向向量则始终由定日镜中心指向集热器中心:
$$\vec{R}_i = \frac{I - P_i}{|I - P_i|} = \frac{(-X_i, -Y_i, h_0 - h_i)}{\sqrt{X_i^2 + Y_i^2 + (h_0 - h_i)^2}}$$
其中 $I(0, 0, h_0)$ 是集热器中心坐标,$P_i(X_i, Y_i, h_i)$ 是第 i 面定日镜中心坐标。
有了入射和反射向量,法向量就由反射定律直接给出:
$$\vec{n}_i = \frac{\vec{R}_i - \vec{I}_i}{|\vec{R}_i - \vec{I}_i|}$$
这一步是整个模型的基石。法向量算错,后面所有效率计算全部崩盘。我踩过的坑是:入射向量和反射向量的方向约定必须一致,都指向镜面或者都背离镜面,否则法向量会反向。
2.3 光学效率的五个分量拆解
论文把光学效率拆成五个分量相乘:
$$\eta = \eta_{sb} \cdot \eta_{cos} \cdot \eta_{at} \cdot \eta_{trunc} \cdot \eta_{ref}$$
| 效率分量 | 物理含义 | 计算难度 | 关键依赖 |
|---|---|---|---|
| $\eta_{ref}$ 镜面反射率 | 镜面材质固有属性 | 低(常数) | 材质参数 |
| $\eta_{at}$ 大气透射率 | 光线传播中的大气吸收 | 低(二次函数) | 镜面到集热器距离 |
| $\eta_{cos}$ 余弦效率 | 非垂直入射的能量损失 | 中(向量点积) | 入射向量、法向量 |
| $\eta_{sb}$ 阴影遮挡效率 | 塔挡、阴影、挡光损失 | 高(离散化判断) | 镜面离散精度 |
| $\eta_{trunc}$ 截断效率 | 光线未命中集热器 | 高(光线追迹) | 锥形光线离散数 |
余弦效率的计算最直接:$\eta_{cos,i} = -\vec{I}_i \cdot \vec{n}i$(两个单位向量的点积取负)。大气透射率是距离的二次函数:$\eta{at,i} = 1.97 \times 10^{-8} d^2 - 0.0001176 d + 0.99321$。这两个分量代码里几行就能搞定。
真正花时间的是阴影遮挡效率和截断效率。论文对阴影遮挡做了细分:塔挡损失通过判断定日镜中心是否在塔的阴影区域内来简化计算;阴影损失和挡光损失则需要对镜面做离散化处理,提出“无效点判断策略”。截断效率则是对锥形光线离散化后,逐条判断光线与集热器圆柱面是否有交点。
3. 阴影遮挡与截断效率:离散化策略的代码实现
3.1 镜面离散化与无效点判断
阴影遮挡效率的核心思路是:把每面定日镜的镜面划分成网格,逐点判断该点是否被遮挡。论文提出的“无效点判断策略”逻辑是这样的——对镜面上每个离散点,沿入射光线方向反向追踪,看是否会与其他定日镜或吸收塔相交。如果相交,该点标记为无效。
def compute_shadow_blocking_efficiency(heliostat, all_heliostats, tower, sun_vec, grid_n=10): """ 计算单面定日镜的阴影遮挡效率 heliostat: 当前定日镜对象(含中心坐标、法向量、尺寸) all_heliostats: 所有定日镜列表 tower: 吸收塔参数(中心坐标、集热器半径、高度) sun_vec: 入射光线方向向量(单位向量) grid_n: 镜面离散网格数(每边) 返回: eta_sb (0~1) """ valid_count = 0 total_count = grid_n * grid_n # 生成镜面上的离散点(镜面坐标系下的局部坐标) half_w = heliostat.width / 2 half_h = heliostat.height / 2 xs = np.linspace(-half_w, half_w, grid_n) ys = np.linspace(-half_h, half_h, grid_n) for x_local in xs: for y_local in ys: # 局部坐标转镜场坐标 point_local = np.array([x_local, y_local, 0]) point_global = heliostat.center + heliostat.T @ point_local # 沿入射光线反向追踪 ray_origin = point_global ray_dir = -sun_vec # 反向 blocked = False # 检查是否被其他定日镜遮挡 for other in all_heliostats: if other.id == heliostat.id: continue if ray_intersects_rect(ray_origin, ray_dir, other): blocked = True break # 检查是否被吸收塔遮挡 if not blocked and ray_intersects_cylinder(ray_origin, ray_dir, tower): blocked = True if not blocked: valid_count += 1 return valid_count / total_count这段代码的关键参数是grid_n——镜面离散精度。论文中没有明确给出网格数,但根据经验,10×10 到 20×20 是比较合理的范围。网格太粗,阴影边界判断误差大;网格太细,计算量指数增长。我一般先用 10×10 跑通流程,确认结果合理后再提高到 20×20 做最终计算。
ray_intersects_rect和ray_intersects_cylinder是两个几何判断函数,分别判断射线是否与矩形镜面和圆柱面相交。这两个函数的实现涉及空间解析几何,核心是求解射线方程与平面/柱面方程的交点,再判断交点是否在有效范围内。
3.2 锥形光线离散化与截断效率
截断效率的计算比阴影遮挡更复杂。太阳光不是严格的平行光,而是以一定锥角发散的锥形光束。论文的做法是把锥形光线离散成若干条独立光线,逐条判断是否能入射到集热器上。
def compute_truncation_efficiency(heliostat, tower, sun_vec, cone_angle=0.00465, n_rays=50): """ 计算单面定日镜的集热器截断效率 cone_angle: 太阳锥角(弧度),典型值约 4.65mrad n_rays: 离散光线数量 返回: eta_trunc (0~1) """ # 在锥形范围内生成离散方向 ray_dirs = generate_cone_directions(sun_vec, cone_angle, n_rays) hit_count = 0 for direction in ray_dirs: # 从定日镜中心出发,沿反射方向追踪 reflected_dir = reflect_direction(direction, heliostat.normal) ray_origin = heliostat.center # 判断是否与集热器圆柱面相交 if ray_intersects_cylinder(ray_origin, reflected_dir, tower): hit_count += 1 return hit_count / n_raysgenerate_cone_directions函数在给定中心方向和锥角的前提下,生成均匀分布在锥面上的方向向量。n_rays的取值直接影响精度和速度——论文中可能用了 50 到 100 条光线,实际测试下来,50 条光线在大多数场景下已经能给出稳定结果。
这里有个容易翻车的地方:锥形光线的离散方式。如果只是在锥面上均匀取点,可能会在中心区域采样不足。更稳妥的做法是在锥形立体角内做均匀采样,或者用同心圆环的方式分层采样。
3.3 输出热功率的汇总计算
所有效率分量算完之后,单面定日镜的光学效率就是五项相乘,整个镜场的输出热功率为:
$$E_{field} = DNI \cdot \sum_{i=1}^{N} A_i \eta_i$$
其中 DNI 是法向直接辐射照度,由海拔和太阳高度角决定:
$$DNI = G_0 \left( a + b \exp\left(-\frac{c}{\sin\alpha_s}\right) \right)$$
$G_0 = 1.366$ kW/m² 是太阳常数,a、b、c 是关于海拔 H 的函数,由题目附录给出。
def compute_field_power(heliostats, tower, D, ST, phi, H, grid_n=10, n_rays=50): """ 计算整个定日镜场的年平均输出热功率 D: 距离春分的天数 ST: 当地时间(小时) phi: 纬度(弧度) H: 海拔(米) """ # 计算太阳位置 alpha_s, gamma_s = compute_sun_position(D, ST, phi) sun_vec = compute_incident_vector(alpha_s, gamma_s) # 计算 DNI dni = compute_dni(alpha_s, H) total_power = 0.0 for h in heliostats: # 更新法向量 h.normal = compute_normal(h.center, tower.center, sun_vec) # 计算各项效率 eta_cos = compute_cosine_efficiency(sun_vec, h.normal) eta_at = compute_atmospheric_transmittance(h.center, tower.center) eta_sb = compute_shadow_blocking_efficiency(h, heliostats, tower, sun_vec, grid_n) eta_trunc = compute_truncation_efficiency(h, tower, sun_vec, n_rays=n_rays) eta_ref = 0.92 # 镜面反射率常数 eta_total = eta_sb * eta_cos * eta_at * eta_trunc * eta_ref total_power += dni * h.area * eta_total return total_power这段代码把前面所有模块串起来了。实际跑的时候,需要对全年多个时间点做循环,再取平均。论文中“年均”指标的计算时间范围是每月 21 日的 9:00、10:30、12:00、13:30、15:00 共五个时刻,全年 60 个时间点。
4. 参数优化:变步长搜索与分区域同心圆规划
4.1 问题二的优化模型与变步长搜索
问题二的核心是反问题:给定 60MW 额定功率,求最优的镜场参数。论文提出的分区域同心圆规划策略是:以吸收塔为中心画同心圆,把镜场分成若干区域,每个区域内的定日镜按不同规则排布。
需要优化的参数包括:镜面宽度 $W_a$、镜面高度 $W_b$、安装高度 $h_i$、定日镜底座间距 $L$、吸收塔位置。论文采用变步长搜索算法来求解。
def variable_step_search(param_ranges, objective_func, initial_step, min_step, max_iter=1000): """ 变步长搜索算法 param_ranges: 每个参数的搜索范围 [(min, max), ...] objective_func: 目标函数,输入参数列表,返回目标值 initial_step: 初始步长列表 min_step: 最小步长列表 """ # 从范围中点开始 current = [(lo + hi) / 2 for lo, hi in param_ranges] step = initial_step.copy() best_val = objective_func(current) best_params = current.copy() for iteration in range(max_iter): improved = False for i in range(len(current)): # 正向试探 trial = current.copy() trial[i] = min(trial[i] + step[i], param_ranges[i][1]) val = objective_func(trial) if val > best_val: best_val = val best_params = trial.copy() current = trial improved = True continue # 反向试探 trial = current.copy() trial[i] = max(trial[i] - step[i], param_ranges[i][0]) val = objective_func(trial) if val > best_val: best_val = val best_params = trial.copy() current = trial improved = True if not improved: # 所有方向都没有改进,缩小步长 step = [s * 0.5 for s in step] if all(s < ms for s, ms in zip(step, min_step)): break return best_params, best_val变步长搜索的核心逻辑是:先大步长快速逼近最优区域,再逐步缩小步长精细搜索。参数说明:initial_step一般取搜索范围的 10%~20%,min_step根据精度要求设定,比如镜面宽度精确到 0.1m 就设 0.1。
论文中问题二的求解结果:吸收塔位置 (0, -79),定日镜尺寸 5.5×5.5m,安装高度 2.75m,总面数 3385,年平均输出热功率 60.1189MW,年平均光学效率 0.6028。
4.2 问题三的分区域规格优化与二分法
问题三放宽了约束:不同区域的定日镜可以有不同的尺寸和安装高度。论文提出“定日镜规格分区域规划策略”——同一区域内规格相同,不同区域根据与吸收塔的距离确定规格。
优化算法上,论文在变步长搜索的基础上引入了二分法。二分法主要用于处理定日镜总数的整数约束——当功率接近 60MW 时,需要精确控制定日镜数量。
def binary_search_heliostat_count(target_power, power_func, low, high, tol=0.01): """ 二分法搜索满足额定功率的定日镜数量 target_power: 目标功率 (MW) power_func: 输入定日镜数量,返回实际功率 low, high: 定日镜数量的搜索范围 tol: 功率容差 """ while high - low > 1: mid = (low + high) // 2 power = power_func(mid) if abs(power - target_power) < tol: return mid elif power < target_power: low = mid else: high = mid # 返回更接近目标功率的数量 if abs(power_func(low) - target_power) < abs(power_func(high) - target_power): return low return high二分法在这里的作用是快速定位满足功率约束的定日镜数量区间,再在这个区间内用变步长搜索找最优规格参数。问题三的求解结果:年平均输出热功率 61.8593MW,年平均光学效率 0.5446,共采用 7 种不同的安装高度,总面数 2846。
注意一个反直觉的结果:问题三的输出热功率比问题二更高(61.86 vs 60.12),但光学效率反而更低(0.5446 vs 0.6028)。原因是问题三通过增加定日镜规格的多样性,让更多定日镜工作在非最优但能贡献更多总功率的状态。这说明“效率最高”和“总功率最大”并不总是一回事。
5. 避坑与排查:复现这套模型时最容易翻车的五个地方
5.1 坐标系转换矩阵方向搞反
现象:法向量计算结果与预期方向相反,余弦效率出现负值。
原因:镜面坐标系到镜场坐标系的转换矩阵 T 有方向性。论文中写的是 $\vec{R}_g = \vec{R}_a \cdot T$,即镜面坐标系向量右乘 T 得到镜场坐标系向量。如果写成 $T \cdot \vec{R}_a$,结果完全不同。
解决:统一约定所有向量为列向量,转换关系写成 $\vec{R}_g = T^T \vec{R}_a$ 或 $\vec{R}_g = \vec{R}_a T$,全篇保持一致。建议写一个单元测试:取一个已知姿态的定日镜,手动验算法向量。
5.2 阴影判断中的自遮挡问题
现象:阴影遮挡效率异常低,大量镜面点被标记为无效。
原因:在遍历所有定日镜做遮挡判断时,没有排除当前定日镜自身。射线从镜面上某点出发,自然会与自身所在平面相交。
解决:在循环中加if other.id == heliostat.id: continue。另外,对于相邻很近的定日镜,还要考虑射线起点是否在另一面镜子的背面——如果射线与镜面平面的交点在该镜面的背面,不应算作遮挡。
5.3 锥形光线离散数不足导致截断效率震荡
现象:截断效率在不同运行中波动较大,结果不稳定。
原因:锥形光线的离散数n_rays太少,采样不均匀。特别是当锥角较小而集热器距离较远时,少量光线的命中率估计偏差很大。
解决:把n_rays提高到 100 以上,或者改用分层采样策略——在锥形立体角内按同心圆环分层,每层均匀取点。实测下来,100 条光线在大多数场景下能把截断效率的波动控制在 1% 以内。
5.4 年均计算的时间点选取不当
现象:年平均效率与论文结果偏差超过 5%。
原因:年均指标需要对全年多个时间点做平均。如果只取少数几天或者时间点分布不均匀,结果会有系统性偏差。
解决:严格按照论文的时间方案——每月 21 日,每天 5 个时刻(9:00、10:30、12:00、13:30、15:00),全年 60 个时间点。如果计算资源允许,可以加密到每月 3 天(上旬、中旬、下旬各一天),结果会更稳定。
5.5 变步长搜索陷入局部最优
现象:优化结果明显差于论文给出的参考值,且换一个初始点结果变化很大。
原因:变步长搜索本质上是贪心算法,对初始点敏感。如果初始点落在局部最优附近,步长缩小后就跳不出来了。
解决:用多起点策略——从搜索空间的不同位置出发跑多次,取最优结果。另外,在步长缩小到一定程度后,可以加一个随机扰动项,帮助跳出局部最优。我一般会跑 5 到 10 个不同初始点,取最好的那个。
6. 验证与进阶:如何确认你的复现结果是对的
6.1 分阶段验证策略
不要一上来就跑完整模型。按以下顺序逐步验证:
| 验证阶段 | 验证内容 | 预期结果 | 检查方法 |
|---|---|---|---|
| 第一阶段 | 太阳位置计算 | 与天文年历对比 | 取春分日正午,高度角应接近 90°-纬度 |
| 第二阶段 | 单面定日镜法向量 | 入射角=反射角 | 手动计算向量夹角 |
| 第三阶段 | 余弦效率 | 0 < η_cos < 1 | 正午时对应定日镜应接近 1 |
| 第四阶段 | 阴影遮挡效率 | 边缘定日镜 > 中心定日镜 | 物理直觉判断 |
| 第五阶段 | 全场输出功率 | 与论文偏差 < 3% | 对比 36.99MW |
第一阶段最关键。如果太阳位置算错了,后面全错。我一般会先拿几个已知日期和地点做验证——比如北纬 40° 春分日正午,太阳高度角应该是 50° 左右。
6.2 结果合理性检查
论文给出的问题一结果:年平均输出热功率 36.9945MW,年平均光学效率 0.6051,单位面积年平均输出热功率 0.5888kW/m²。这三个数之间有一致性关系:
$$0.5888 \text{ kW/m}^2 = \frac{36.9945 \text{ MW}}{N \cdot A_i}$$
如果你算出的单位面积功率和总功率对不上,说明采光面积或者定日镜数量用错了。
另一个检查点:光学效率 0.6051 意味着约 60% 的太阳辐射能最终被集热器吸收。这个值在光热电站中属于正常范围——实际运行的塔式电站光学效率通常在 0.55 到 0.65 之间。如果你算出 0.9 以上,大概率是某个效率分量漏乘了。
6.3 从复现到进阶的几个方向
跑通论文模型之后,有几个方向可以继续深入。一是提高离散精度——把镜面网格从 10×10 提高到 30×30,锥形光线从 50 条增加到 200 条,看结果收敛到什么值。二是替换优化算法——把变步长搜索换成遗传算法或者粒子群优化,对比收敛速度和最终解的质量。三是加入论文中忽略的因素——比如定日镜面的平整度误差、跟踪系统的角度偏差、风速对镜面姿态的影响。
我自己在复现这类论文时有个习惯:每改一个参数,都把前后结果记在表格里。这样不仅能看出参数敏感性,还能在结果异常时快速定位是哪一步出的问题。从那以后我每次跑优化模型都强制走一遍分阶段验证,再也没出现过“跑了一晚上发现坐标系建反了”这种血泪教训。
希望帮到你。
本文还有配套的精品资源,点击获取