4. 最大单响药量折算与PPV样本配对
为什么“最大单响药量”不能直接取总药量
逐孔起爆网路中,各孔按毫秒延时间隔依次起爆,爆破振动响应主要由同一时刻(同一延时窗口内)真正共同作用的那部分药量决定,而不是整次爆破的总药量。若把总药量直接代入萨道夫斯基公式,会系统性高估药量、压低拟合的 K 值,导致后续 PPV 预测偏保守甚至失真。因此本节的第一个任务是把网路参数折算成最大单响药量 Qmax。
工程上常用的折算判据是“同段(同延时窗口)药量求和”:把延时差小于振动波形主频对应半周期的孔归为同一响应段,段内药量相加,取各段之和的最大值作为 Qmax。简化做法是按设计分段(同段雷管)直接求和,本案例采用这一可复核的口径,并在样本表中保留分段依据。
折算示例:一个 5 段逐孔网路
设某爆次共 24 个炮孔,单孔药量 3.0 kg,采用 5 个延时段,各段孔数分别为 6、5、5、4、4。按同段求和:
- 第 1 段:6 × 3.0 = 18.0 kg
- 第 2 段:5 × 3.0 = 15.0 kg
- 第 3 段:5 × 3.0 = 15.0 kg
- 第 4 段:4 × 3.0 = 12.0 kg
- 第 5 段:4 × 3.0 = 12.0 kg
则 Qmax = 18.0 kg,而总药量为 72.0 kg。两者相差 4 倍,若混用会直接改变回归结果。注意:若某段内单孔药量不同(如掏槽孔装药更多),需按孔逐一累加,不能简单用“孔数 × 平均单孔药量”。
药量—距离—PPV 三元样本的配对逻辑
折算得到的 Qmax 属于“爆次级”属性,而 PPV 属于“测点级”属性。配对的关键是以爆次编号为唯一连接键:同一爆次下的所有测点共享同一个 Qmax,但各自拥有独立的爆心距 R 和实测 PPV。配对错误通常来自三类原因:爆次编号录入不一致(如“2024-03-05-1”与“20240305-1”)、同一爆次多次爆破未区分、测点记录缺少爆次字段而按时间就近匹配。
用脚本完成折算与配对
下面给出可直接运行的 Python 实现:输入为网路分段表(爆次、段号、孔数、单孔药量)和测点表(爆次、测点、R、PPV),输出为三元样本集,并对未匹配、重复爆次、药量缺失等情况给出显式告警,避免静默错配。
import pandas as pd
# 1) 网路分段表:每段一行,单孔药量可逐段不同
net = pd.DataFrame([
{"blast_id": "B-0305-1", "seg": 1, "holes": 6, "charge_per_hole": 3.0},
{"blast_id": "B-0305-1", "seg": 2, "holes": 5, "charge_per_hole": 3.0},
{"blast_id": "B-0305-1", "seg": 3, "holes": 5, "charge_per_hole": 3.0},
{"blast_id": "B-0305-1", "seg": 4, "holes": 4, "charge_per_hole": 3.0},
{"blast_id": "B-0305-1", "seg": 5, "holes": 4, "charge_per_hole": 3.0},
])
# 2) 测点表:R 已校核,PPV 已做异常值处理
pts = pd.DataFrame([
{"blast_id": "B-0305-1", "point": "P1", "R": 85, "PPV": 1.42},
{"blast_id": "B-0305-1", "point": "P2", "R": 132, "PPV": 0.86},
{"blast_id": "B-0305-1", "point": "P3", "R": 210, "PPV": 0.47},
{"blast_id": "B-0305-1", "point": "P4", "R": 305, "PPV": 0.29},
])
# 3) 折算最大单响药量:段内药量求和后取最大
net["seg_charge"] = net["holes"] * net["charge_per_hole"]
qmax = (net.groupby("blast_id")["seg_charge"].max()
.rename("Q_max").reset_index())
# 4) 以爆次编号为键做多对一配对
samples = pts.merge(qmax, on="blast_id", how="left", validate="many_to_one")
# 5) 配对质量检查
missing = samples["Q_max"].isna()
if missing.any():
print("未匹配到药量的爆次:", samples.loc[missing, "blast_id"].unique())
dup = net.groupby(["blast_id", "seg"]).size()
if (dup > 1).any():
print("存在重复段记录:", dup[dup > 1])
samples = samples.dropna(subset=["Q_max"])
samples = samples[["blast_id", "point", "Q_max", "R", "PPV"]]
print(samples.to_string(index=False))
预期输出(由上述输入直接推出,非实测):
blast_id point Q_max R PPV
0 B-0305-1 P1 18.0 85 1.42
1 B-0305-1 P2 18.0 132 0.86
2 B-0305-1 P3 18.0 210 0.47
3 B-0305-1 P4 18.0 305 0.29
配对结果的自检要点
- 行数守恒:配对后样本行数应等于有效测点行数;若变多,说明药量表存在重复爆次记录,merge 产生了笛卡尔扩张。
- Q_max 唯一性:同一爆次内所有样本的 Q_max 必须完全相同,出现两个值即为折算或配对错误。
- 量级合理性:Q_max 应小于总药量且不小于单段最大药量;若 Q_max 等于总药量,多半是误把总药量当单响。
- 键格式统一:爆次编号在两张表中必须同格式,建议在入库阶段统一为“日期-序号”并去除空格。
可直接套用的样本表字段
| 字段 | 含义 | 来源 | 校验规则 |
|---|---|---|---|
| blast_id | 爆次唯一编号 | 爆破记录 | 非空、格式统一、两表一致 |
| point | 测点编号 | 测点记录 | 同一爆次内不重复 |
| Q_max | 最大单响药量 (kg) | 分段表折算 | 0 < Q_max ≤ 总药量 |
| R | 爆心距 (m) | 坐标计算(例3) | > 0,与坐标一致 |
| PPV | 实测峰值振速 (cm/s) | 监测仪(例2处理后) | > 0,已剔除异常 |
| seg_basis | 折算口径说明 | 人工填写 | 全批统一 |
完成上述折算与配对后,每个测点都对应一组 (Q_max, R, PPV),且 Q_max 与 R 的量纲、口径在全批样本中一致。这份三元样本集即为后续比例距离计算与回归拟合的唯一输入,任何字段口径的变动都必须回到本步重新生成,而不是在回归阶段临时修补。