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”)、同一爆次多次爆破未区分、测点记录缺少爆次字段而按时间就近匹配。

逐孔网路 → 最大单响药量 → 三元样本配对 网路参数(爆次级) 爆次编号:B-0305-1 总孔数:24 单孔药量:3.0 kg 延时段数:5 各段孔数:6 / 5 / 5 / 4 / 4 总药量:72.0 kg (总药量不直接进入回归) 口径:同段药量求和 同段药量折算 段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 Q_max = 18.0 kg 测点记录(测点级) 爆次:B-0305-1 测点 P1:R=85 m,PPV=1.42 cm/s 测点 P2:R=132 m,PPV=0.86 cm/s 测点 P3:R=210 m,PPV=0.47 cm/s 测点 P4:R=305 m,PPV=0.29 cm/s R 已由例3坐标校核后确定 PPV 已由例2异常值处理 连接键:爆次编号 B-0305-1 一致才允许配对 三元样本集(爆次 B-0305-1,Q_max = 18.0 kg) 样本 Q_max (kg) R (m) PPV (cm/s) 配对状态 S1 18.0 85 1.42 ✓ 已配对 S2 18.0 132 0.86 ✓ 已配对 S3 18.0 210 0.47 ✓ 已配对

用脚本完成折算与配对

下面给出可直接运行的 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 等于总药量,多半是误把总药量当单响。
  • 键格式统一:爆次编号在两张表中必须同格式,建议在入库阶段统一为“日期-序号”并去除空格。
不要用“时间最接近”的方式把测点匹配到爆次。同一日内多次爆破间隔可能只有几十分钟,按时间就近匹配极易把 A 爆次的测点挂到 B 爆次的药量上,且这种错误在样本表中完全看不出来,只会在回归残差上表现为异常离散。

可直接套用的样本表字段

字段含义来源校验规则
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 的量纲、口径在全批样本中一致。这份三元样本集即为后续比例距离计算与回归拟合的唯一输入,任何字段口径的变动都必须回到本步重新生成,而不是在回归阶段临时修补。