资讯动态

火灾报警系统建模:探测器布点优化与模拟退火求解

发布时间:2026/10/22 2:36:22 来源:尧图企业网站定制
简介这是一份2022年五一数学建模联赛C题火灾报警系统问题的成品论文面向参赛选手及需要课程作业参考的建模学习者。资料内容以竞赛论文为主体围绕火灾报警系统问题展开依次完成真实火灾次数的判定、探测器可靠性与故障率评估、智能研判与模糊综合评价、消防大队管理水平排序及改进建议。PDF正文包含摘要、问题背景与重述、问题分析、模型假设、符号说明及模型求解等完整结构文中重点使用了熵权Topsis模型、灰色关联度模型和层次分析法等内容并附代码与数据便于读者对照复现、作业参考或借鉴论文写法。压缩包为1个PDF文件大小仅532KB轻量易取目前已有2739人学习/下载。下载后即可获得该赛题的一次完整参赛解决方案适合赛前训练、课程作业和算法实践使用。1. 火灾报警系统第一问先把“报警”这件事量化再谈建模拿到2022年五一赛C题火灾报警系统问题-01很多队伍的第一反应是找火灾数据集或者在网格上先做一个温度场动画。真正动手以后会发现第一问难的不是火势怎么蔓延而是把“报警”这个事件定义清楚探测器布在哪、阈值设多少、从起火到报警允许延迟多久。这三件事没有写进数学表达式后面算出来的覆盖率、漏报率都只是自说自话。本文按处理这类赛题的常规做法把第一问拆成覆盖建模、网格离散化、模拟退火求解和避坑验证四步代码可以直接改参数跑。适合还没把报警过程量化的参赛者也适合传感器布点这类覆盖类工程场景。2. 从题目到模型把火灾报警问题拆成可计算的指标2.1 报警系统要回答的四个问题探测器、位置、阈值、时间火灾报警系统类赛题无论场景描述怎么包装第一问最终都要回答四个问题装多少个探测器、探测器放在哪、检测值到多少算报警、报警最多可以延迟多久。四个问题互相耦合阈值设得低报警响应快但误报概率升高探测器布得密覆盖好但成本上升。所以不能单独优化一个变量必须先把约束的优先级定下来。参赛论文里最常犯的错是把“覆盖率”当成一个不需要定义的概念直接用。评审追问一句你说的覆盖是半径覆盖还是触发覆盖探测器在房间角落温度确实超过阈值但烟雾浓度不够算覆盖还是不算我一般用“距离响应”双条件来定义火源点落在探测器的有效感知半径内并且该处的模拟测量值在规定时间内超过报警阈值。两个条件同时满足才算一次有效覆盖。这样定义的好处是让不确定性前置。火源位置、探测器位置、阈值、时间全部变成可以被代码逐个检查的量而不是靠感觉拍板。后续任何一次覆盖判断都能回溯到具体的几何距离和阈值计算答辩时也讲得清楚。2.2 用覆盖面与响应时间建立目标函数把 2.1 的判断标准写成数学形式第一问就变成标准的集合覆盖问题。记平面被离散后的网格为候选点集合火源点集合为 F候选探测器位置集合为 C。定义覆盖矩阵 AA_ij 1 表示火源点 i 能被候选位置 j 有效覆盖。设决策变量 x_j ∈ {0,1}x_j1 表示在候选位置 j 安装探测器目标函数是安装数量最小化约束是每个火源点至少被 r_i 个探测器有效覆盖min Σ x_js.t. 对每个火源点 iΣ_j A_ij·x_j ≥ r_ix_j ∈ {0,1}这里的 r_i 是冗余度。默认取 1说明任意一个探测器触发就报警如果题目强调系统可靠性可以取 2要求两个探测器同时判定起火才确认报警。冗余度多一档探测器数量通常会上涨 30% 到 60%这个变化本身可以作为可靠性分析的论据。为什么用集合覆盖而不是直接套目标检测模型因为第一问的输入是坐标和阈值输出是安装方案这是典型的组合优化任务火焰识别模型处理的是图像任务类型完全不同。在没有商业求解器授权的场景里自己写模拟退火是稳定且可解释的选择后面第四章的代码可以直接用。2.3 参数表物理量、单位与建模默认值下面是一组我在模拟项目X里常用的默认参数。赛题给了数据就从题目数据里换算没给就作为假设写进论文两个来源都要明确说明。参数建议默认值说明网格尺寸1 m小于 0.5 m 计算量翻倍覆盖精度提升有限有效感知半径 R_eff8 m由传感器灵敏度换算普通感烟感温探测器可取此范围报警阈值57 ℃感温探测器常见阈值需减去环境温度后与温升比较最大允许报警延迟60 s题目没给就按这个做默认约束火源点数量网格数/100 或 60 个左右均匀抽样边缘和角落要手动补充覆盖冗余 r_i1双覆盖可靠性分析时改为 2这里特别提醒一句参数要集中在代码开头不要散落在各个函数里。后面做灵敏度分析时只需要在这一段改三个数就能重新跑全流程节省大量时间。3. 数据准备与离散化网格划分和火灾场景生成3.1 原始数据常见形态点位、温度曲线与烟雾浓度这类赛题的数据有三种常见形态。第一种给建筑平面图坐标可能是多边形顶点也可能是栅格点位第二种给历史温度或烟雾浓度曲线用来反推探测器的响应特性第三种只给文字描述需要自己构造仿真场景。第三种最麻烦常见做法是先假设一个矩形平面例如 30 m × 20 m再在内部均匀取样。拿到第二种数据时我的习惯是先画一条温度曲线看从起始温度到报警阈值的上升时间用这个时间反推探测器的有效感知半径。如果曲线显示 45 秒内温升到达 32 ℃而火源距离探测器 6 米那有效感知半径至少可以按 6 米取。这个换算过程要写进论文不能只给一个突兀的默认值。3.2 对建筑平面做网格离散化的 Python 实现无论原始数据是矩形还是多边形第一步都是把连续平面变成离散网格。下面代码生成网格中心点坐标并处理非矩形区域的掩码问题。import numpy as np # 网格参数集中在文件开头方便调参 GRID_SIZE 1.0 # 网格尺寸m W, H 30.0, 20.0 # 建筑平面尺寸m NX, NY int(W / GRID_SIZE), int(H / GRID_SIZE) # 网格数量 # 生成所有网格中心点坐标 xs np.arange(GRID_SIZE / 2, W, GRID_SIZE) ys np.arange(GRID_SIZE / 2, H, GRID_SIZE) grid_x, grid_y np.meshgrid(xs, ys) points np.stack([grid_x.ravel(), grid_y.ravel()], axis1) print(f网格总数: {len(points)}) # 非矩形区域掩码示例假设右半部分是矩形区域内的一块禁入区 in_region np.ones(len(points), dtypebool) in_region ~((points[:, 0] 20.0) (points[:, 1] 5.0)) points points[in_region] print(f剔除禁入区后网格总数: {len(points)})逻辑说明用网格中心点代替连续平面是覆盖类建模的标准离散化方式。GRID_SIZE决定计算规模1 m 在 30 m×20 m 场景下是 600 个点后面模拟退火算距离矩阵也很快。arange从GRID_SIZE/2出发保证每个网格内有一个中心点而不是落在边界上。非矩形区域用布尔掩码剔除这是处理多边形房间的通用手法只需要把掩码条件换成实际的多边形判断函数。参数说明W和H是矩形尺寸改成不规则建筑时掩码条件要换成点在多边形内部的判定GRID_SIZE从 1 m 改到 0.5 m网格数会变成原来的四倍建议先用 1 m 跑通最后再加密做收敛性验证。3.3 给每个网格赋值热源模型与传播简化报警判断依赖温度或烟雾浓度这里用简化高斯热源模型模拟单个火源点对周围网格的温升贡献。真实火灾涉及热辐射、对流传热和烟气流但第一问需要的是“探测器能否在规定时间触发”单调且可解释的近似模型足够支撑这个判断。def temperature_rise(src, pts, Q500.0, sigma3.0): 简化热源模型: 返回每个点相对火源的温升(℃) Q: 火源热释放量折算的温度系数 sigma: 热扩散等效半径越大影响范围越广 d np.linalg.norm(pts - src, axis1) return Q * np.exp(-(d ** 2) / (2 * sigma ** 2)) # 示例模拟一个起火点并查看该场景下哪些网格超过阈值 fire_src np.array([15.0, 10.0]) rise temperature_rise(fire_src, points) over_threshold rise 57.0 - 25.0 # 报警阈值57℃ 减去环境温度25℃ print(f超过报警阈值的网格数: {over_threshold.sum()})逻辑说明实际火灾传播是非线性过程但覆盖问题只需要知道“一定距离内温升是否达到报警条件”。高斯衰减保证了单调性距离越近温升越高便于覆盖判断。57.0 - 25.0是把绝对温度阈值换算成相对温升因为环境温度 25 ℃ 时探测器测到的是环境温度加温升。参数说明Q和sigma需要跟场景匹配。Q500、sigma3.0时10 m 外温升约 3.9 ℃基本不触发报警5 m 处约 34.5 ℃可以被探测到。如果全网格都超过阈值说明sigma或Q偏大覆盖优化会失去区分度反过来全都没超阈值先检查环境温度是否减错。3.4 参数说明网格尺寸、时间步长和场景数量怎么定网格尺寸和时间步长在这个模型里不是独立参数。时间被吸收进最大允许报警延迟T_allowed不需要做真实的时间迭代网格尺寸影响的是覆盖判断的精度和计算量。我的经验是先用 1 m 跑通流程最后用 0.5 m 复核一遍如果探测器数量和布点位置变化很大说明结果对网格敏感论文里要把两组结果都列出来。火源点抽样数量和分布直接决定覆盖率结论。如果只抽 10 个点任何方案都能轻松做到 100% 覆盖论文没有说服力。常见做法是抽 60 到 100 个点并且手动补充四角和边界rng np.random.default_rng(42) # 固定随机种子保证结果可复现 n_sources 60 source_idx rng.choice(len(points), sizen_sources, replaceFalse) sources points[source_idx] # 手动补充角落防止边界成为覆盖盲区 corners np.array([ [0.5, 0.5], [0.5, H - 0.5], [W - 0.5, 0.5], [W - 0.5, H - 0.5] ]) sources np.vstack([sources, corners]) print(f火源点总数: {len(sources)})逻辑说明rng.choice随机抽样覆盖了平面中部但四角这类极端位置很难被抽中。火灾报警系统最怕的恰恰是角落起火所以手动补齐四角是工程习惯也让“覆盖率 100%”这个结论在困难位置上依然成立。4. 第一问的建模与求解从遍历到优化算法4.1 最小探测器数先用下界和贪心验证问题规模跑优化之前先算一个理想下界单个探测器有效覆盖面积近似 π·R_eff²平面面积 A 除以它再向上取整就是理想最小探测器数。以 30 m×20 m、R_eff8 m 为例下界是 ceil(600 / 201) 3 个。这个下界非常乐观因为圆形不能无缝平铺边界和冗余会把数量推高。它的价值在于校验代码如果优化结果小于下界说明覆盖矩阵或坐标系写错了如果结果比下界大很多可能是感知半径设置不合理。贪心算法为模拟退火提供一个可行初解避免从空集开始盲目搜索。贪心逻辑很简单每轮选择能覆盖最多未覆盖火源点的候选探测器位置直到全部覆盖。在 600 个网格点上贪心几乎瞬间完成且它的结果一定可行这给后续模拟退火省掉了大量处理不可行解的麻烦。4.2 用模拟退火求解探测器布点通用框架代码模拟退火适用于几千个候选点的集合覆盖问题实现简单且不容易陷入明显的局部最优。下面是完整代码覆盖矩阵在循环外预先算好迭代过程中只做行逻辑运算跑起来很快。import numpy as np def build_coverage(sources, candidates, R_eff): 构建覆盖矩阵: 形状 (n_sources, n_candidates) 欧氏距离在有效感知半径内视为可覆盖。 d np.linalg.norm(sources[:, None, :] - candidates[None, :, :], axis2) return d R_eff def greedy_init(cover): 贪心初始解: 反复选择覆盖最多未覆盖源点的候选位置 covered np.zeros(cover.shape[0], dtypebool) chosen [] while not covered.all(): gain (~covered).astype(int) cover.astype(int) idx int(np.argmax(gain)) chosen.append(idx) covered | cover[:, idx] return chosen def sa_solve(cover, init_sol, max_iter3000, t0100.0, alpha0.995): 模拟退火: 通过交换候选探测器搜索更小的覆盖集合。 保持完全覆盖的前提下优先接受探测器数量更少的方案。 cur set(init_sol) best set(cur) rng np.random.default_rng(7) # 检查初始解完全覆盖 covered_all np.zeros(cover.shape[0], dtypebool) for cand in cur: covered_all | cover[:, cand] assert covered_all.all(), 初始解未完全覆盖检查覆盖矩阵或贪心逻辑 T t0 for it in range(max_iter): nxt set(cur) # 扰动: 去掉一个探测器或去掉一个再换入一个候选点 drop rng.choice(sorted(nxt)) nxt.remove(drop) if rng.random() 0.7: add int(rng.choice(cover.shape[1])) if add not in nxt: nxt.add(add) # 检查新方案是否完全覆盖 ok np.zeros(cover.shape[0], dtypebool) for cand in nxt: ok | cover[:, cand] if ok.all() and len(nxt) len(cur): cur nxt if len(nxt) len(best): best set(nxt) elif ok.all() and len(nxt) len(cur): cur nxt # 同数量交换保持种群多样性 else: # 不完全覆盖时按 Metropolis 准则接受避免困在局部最优 cost_old len(cur) 100 * (1 - covered_all.mean()) cost_new len(nxt) 100 * (1 - ok.mean()) if np.exp(-(cost_new - cost_old) / T) rng.random(): cur nxt covered_all ok T * alpha if T 1e-3: break return sorted(best), len(best) # 集成: 使用前面准备好的网格和火源点 cover build_coverage(sources, points, R_eff8.0) init_sol greedy_init(cover) best_sol, num_need sa_solve(cover, init_sol) print(f需要探测器: {num_need} 个) print(f布点索引(前10): {best_sol[:10]})逻辑说明build_coverage把几何判断转成布尔矩阵后续所有优化都在矩阵上进行以后想换传感器模型只需改这一处。greedy_init提供可行解SA 从可行域内部开始搜索比随机初始化快得多。扰动操作每次“去掉一个探测器”并以 0.7 概率“换入一个未选候选点”既能减少数量也能改变位置组合。Metropolis 准则让不完全覆盖的方案有小概率被接受避免一遇到不可行解就回退、导致搜索范围过窄。参数说明max_iter3000在 600 个候选点场景几秒跑完点数上千时建议调到 8000 到 15000t0100控制初始接受概率alpha0.995是经典降温系数。运行三遍如果结果一致说明迭代次数够如果每次都不同且数量偏大优先加大max_iter而不是调高t0。4.3 参数说明温度阈值、迭代次数、初温怎么调调整参数时遵循一个流程。第一步固定随机种子跑三遍记录探测器数量。三次数值一致说明收敛稳定波动大说明迭代不足把max_iter翻倍再跑。第二步看探测器数量是否接近下界。如果明显偏高先检查R_eff是不是设小了而不是怀疑算法写错。温度阈值的影响最直接阈值越高温升超过阈值的网格越少每个探测器能覆盖的火源点越少需要的探测器数量越多。反过来阈值低到接近环境温度时所有网格都触发覆盖矩阵接近全 1优化结果会给出一个离谱的小数量。这两种情况都要通过灵敏度分析来验证设置的合理性而不是默默接受第一个结果。4.4 输出结果布点坐标、覆盖矩阵与报警延迟求解完成后要把结果落到可交付的形式。布点坐标存成 CSV 是最基本的同时还要输出每个火源点的报警延迟让“最大报警延迟”成为一个算出来的数字而不是拍脑袋的估计。# 输出布点坐标 best_points points[best_sol] np.savetxt(detectors.csv, best_points, delimiter,, headerx,y, comments) # 计算每个火源点的报警延迟用简化的距离/速度关系估算 def alarm_delay(src, detectors, v_ref1.5, R_eff8.0): d np.min(np.linalg.norm(detectors - src, axis1)) if d R_eff: return np.inf return d / v_ref delays np.array([alarm_delay(s, best_points) for s in sources]) print(f最大报警延迟: {delays.max():.1f} s, 最短: {delays.min():.1f} s) print(f未覆盖源点数: {int(np.isinf(delays).sum())})逻辑说明alarm_delay用直线距离除以参考传播速度估算报警延迟v_ref1.5 m/s是简化假设必须写进论文的模型假设部分。如果未覆盖源点数大于 0说明覆盖判断和求解环节存在不一致直接回溯查build_coverage的R_eff和火源点采样过程。5. 报警系统建模避坑数据、阈值与验证的常见问题5.1 现象漏报率高得没法写进论文在模拟项目X里我第一次跑完打印出的覆盖率为 87%漏报的源点全是远离探测器的角落。原因很快找到报警阈值按 57 ℃ 设但热源模型在这个距离上的温升只有 18 ℃探测器根本没有触发条件。解决办法是先统计所有火源点温升的分布看最大值和最小值再决定阈值。如果最大温升都不超过报警阈值说明Q或sigma与阈值不匹配要么调高热源强度要么把阈值写成相对环境温度的温升比如“环境温度 25 ℃ 以上升温 32 ℃ 报警”。阈值数值本身不是重点重点是要和热源模型在同一量纲下比较。5.2 现象网格太粗角落盲区被掩盖把GRID_SIZE从 1 m 改成 5 m 后探测器数量从 8 个降到 5 个看起来很美好但实际上 5 m 网格把两个探测器之间的盲区直接“平均”掉了。网格中心有覆盖不代表整个网格面都有覆盖角落区域的微小区块被吞并。解决方法是做网格收敛性检验用 1 m 和 0.5 m 各跑一遍比较探测器数量。如果数量一致说明结果对网格不敏感如果明显变化论文里同时报告两组数据并说明最终采用了更细的网格。这个检验要放在覆盖判断之后、结果分析之前位置不能颠倒。5.3 现象随机种子没固定两次结果不一样第一次跑 SA 得到 8 个探测器第二次变成 9 个布点位置也完全不同。原因显然是随机种子没固定每次rng.choice的抽样和扰动路径都不一样。解决方法是所有随机过程都使用同一个随机数生成器并且把种子值写进代码注释和论文附录。具体做法是项目开头写rng np.random.default_rng(42)后续抽样、SA 扰动、重新采样验证全部使用这个rng。这样评审复跑时能得到完全一致的结果省掉很多解释成本。5.4 现象仿真时间过长参数扫描根本跑不动候选点从 600 个涨到 6000 个时如果每次迭代都调用np.linalg.norm重算距离跑一次 SA 需要十几分钟做三档灵敏度分析基本不可能。原因是几何距离计算被重复做了几千次。解决方法是把距离和覆盖矩阵的计算放到 SA 循环之前一次算好存成布尔矩阵循环里只做行逻辑或运算。6000 点时覆盖矩阵是 60×6000 的布尔矩阵内存占用不到几 MB整个 SA 跑完只要十几秒。这个优化必须在最开始就做否则参数扫描环节只能放弃论文的灵敏度分析部分也会缺一块。5.5 现象只抽样网格中心边界成了覆盖盲区火源点通过rng.choice从网格中心点中抽样四角和边界的极端位置几乎不可能被抽中。结果覆盖率达到 100%但实际检查发现墙边 0.5 m 范围内没有探测器能覆盖到。原因是采样过程忽略了边界点。解决方法是手动补充四角坐标以及沿着每面墙的中点补几个边界源点再单独统计“边界源点覆盖率”。这个指标在论文里很有说服力边界覆盖率比整体覆盖率低 10% 以内说明布点方案对角落也稳健低很多就要考虑是不是 R_eff 太小或者边界附近缺少候选位置。6. 把第一问的答案变成能交付的结果一致性验证与灵敏度分析6.1 用重新采样做覆盖一致性验证模拟退火求出的布点方案不能直接写进论文。常规做法是生成另一组不同规模的火源点例如 240 个重新算一遍覆盖率和报警延迟。如果第二组抽样下的覆盖率仍然接近 100%说明方案不依赖特定采样。具体判断标准覆盖率不低于 98%且最大报警延迟不超过设定允许值的 1.1 倍就算通过。6.2 灵敏度分析表与热力图输出对有效感知半径和报警阈值各取三档生成一张探测器数量变化表R_eff (m)阈值 50 ℃阈值 57 ℃阈值 64 ℃6.01112148.0781010.0567这张表直接回答评审最常问的问题探测器数量对参数敏感吗数据说明感知半径从 8 m 降到 6 m探测器数量增加一半阈值从 57 ℃ 升到 64 ℃数量增加两到三个。敏感的方向和幅度都与物理预期一致设置也就站得住脚。再配合一张覆盖热力图背景网格用颜色表示被多少个探测器覆盖点位标出探测器位置和火源点位置一眼能看出覆盖薄弱区在哪个方向。图比表格更容易看出问题我习惯把热力图放在灵敏度表前面先看图有了直观印象再看表核对数字。以前做题时我把网格从 1 m 改成 0.5 m 后最优探测器数量从 8 变成 9当时以为是代码 bug后来才发现是边界盲区被更细的网格暴露出来了。从那以后养成了习惯任何布点方案交出去之前先跑一遍网格收敛性和重新采样验证再写进论文。第一问不追求火势模型多复杂追求的是关键指标可复现、阈值有解释、边界没有盲区做到这三点方案就能站得住。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑