1. Res2DInv 工程数据导入与测线拓扑检查

为什么先做拓扑检查再反演

Res2DInv 的反演网格是按“测线位置—电极间距—排列类型”三者共同建立的。如果导入的数据里电极间距不一致、测点序号跳号,或者某条排列的电极坐标与相邻排列对不上,反演程序不会报错,而是把这些点当作真实的地电异常去拟合,最终在剖面上形成假的“高阻/低阻竖条”。因此导入后的第一件事不是点反演,而是把测线拓扑核对清楚。

本例使用一条 24 电极、电极间距 5 m 的温纳剖面作为教学示例数据(示例数据,非实测),测线沿 x 方向布设,电极编号 1~24,对应桩号 0~115 m。数据文件采用 Res2DInv 通用 DAT 格式:第一行为排列类型与电极数,随后每行是“测点位置序号、电极距系数 n、视电阻率”。

导入前的数据准备与格式约定

Res2DInv 的 DAT 文件对字段位置敏感,导入前先确认三件事:排列类型代码、电极总数、每行的四个字段顺序。温纳排列的通用写法如下(示例数据):

1
24
1  1  0.0  128.4
2  1  5.0  131.7
3  1  10.0 126.9
...
1  2  2.5  142.3
2  2  7.5  139.8
...

字段含义依次为:测点序号、电极距系数 n、测点中心桩号(m)、视电阻率(Ω·m)。注意第三列是“测点中心位置”,温纳排列中它等于当前四电极组的中心桩号,而不是第一个电极的桩号。这一点在检查拓扑连续性时最容易出错。

测线拓扑检查的三个判据

  • 电极间距一致性:同一 n 值下,相邻测点中心桩号之差应恒等于电极间距 a(本例 5 m)。若出现 4.8、5.2 之类的偏差,说明野外电极实际位置与记录不符。
  • 测点连续性:同一 n 值下的测点序号应连续,缺号意味着该位置数据缺失,需在 DAT 中补占位行或明确标记。
  • 排列覆盖范围:n 越大,参与电极跨度越大,可用的测点中心越少。检查每个 n 的测点数量是否符合 (N - 3n) 的预期,本例 N=24,n=1 应有 21 个测点,n=2 应有 18 个,依此类推。
温纳排列测线拓扑检查(24 电极,a = 5 m) 电极桩号(m) 0 10 20 30 40 50 60 70 80 90 100 110 1 24 n = 1 测点中心 2.5 ~ 107.5 m,共 21 点,间距 5 m n = 2 测点中心 7.5 ~ 102.5 m,共 18 点,间距 5 m n = 3 测点中心 12.5 ~ 97.5 m,共 15 点,间距 5 m 检查中发现的异常(示例) ① n=1 第 9 点中心桩号记录为 42.0 m,应为 42.5 m → 电极间距偏差 0.5 m,需回查野外记录 ② n=2 第 12 点视电阻率缺失(空值)→ 在 DAT 中补占位行并标记,避免反演网格错位

用脚本完成批量核对

手工核对 24 电极、3 个 n 层的数据容易漏项,下面这段 Python 脚本直接读取 DAT 文件,按 n 分组检查间距、序号连续性和测点数量,并输出异常清单。脚本只依赖标准库,可直接运行。

import csv

A = 5.0          # 电极间距 (m)
N_ELECTRODES = 24

def load_dat(path):
    rows = []
    with open(path, newline='') as f:
        lines = [ln.strip() for ln in f if ln.strip()]
    # 第 1 行排列类型,第 2 行电极数,之后为数据
    for ln in lines[2:]:
        parts = ln.split()
        if len(parts) < 4:
            continue
        idx, n, center, rho = int(parts[0]), int(parts[1]), float(parts[2]), parts[3]
        rows.append({"idx": idx, "n": n, "center": center, "rho": rho})
    return rows

def check_topology(rows):
    issues = []
    by_n = {}
    for r in rows:
        by_n.setdefault(r["n"], []).append(r)

    for n, pts in sorted(by_n.items()):
        pts.sort(key=lambda p: p["idx"])
        expected = N_ELECTRODES - 3 * n
        if len(pts) != expected:
            issues.append(f"n={n}: 测点数 {len(pts)},预期 {expected}")

        # 序号连续性
        for i in range(1, len(pts)):
            if pts[i]["idx"] != pts[i-1]["idx"] + 1:
                issues.append(f"n={n}: 序号跳号 {pts[i-1]['idx']} -> {pts[i]['idx']}")

        # 间距一致性
        for i in range(1, len(pts)):
            d = pts[i]["center"] - pts[i-1]["center"]
            if abs(d - A) > 1e-6:
                issues.append(f"n={n}: 第 {pts[i]['idx']} 点间距 {d:.2f} m,应为 {A} m")

        # 空值
        for p in pts:
            if p["rho"] in ("", "-", "NaN"):
                issues.append(f"n={n}: 第 {p['idx']} 点视电阻率缺失")

    return issues

if __name__ == "__main__":
    data = load_dat("line01.dat")
    problems = check_topology(data)
    if problems:
        print("发现异常:")
        for p in problems:
            print(" -", p)
    else:
        print("拓扑检查通过,可进入反演。")

脚本输出的异常清单就是“异常测点记录表”的原始内容。对每条异常,按下面的处理原则决定是修正、补占位还是剔除:

异常类型判断依据处理方式
间距偏差 ≤ 0.5 m野外电极实际位置与记录不符按实测桩号修正 center 字段,保留数据
间距偏差 > 0.5 m可能电极被移动或记录错误标记为可疑点,反演前剔除或单独复核
序号跳号该位置无观测补占位行(rho 留空),保持网格对齐
视电阻率缺失仪器未记录或读数无效补占位行并标记,不参与反演拟合

输出可反演的数据文件

修正完成后,把处理结果写回 DAT 文件。注意 Res2DInv 对空值的容忍度有限,缺失点建议用占位行保留位置,而不是直接删行——删行会让后续测点序号整体前移,反演网格随之错位。下面是在同一脚本中追加的写出函数:

def write_dat(path, rows, arr_type=1, n_electrodes=24):
    with open(path, "w") as f:
        f.write(f"{arr_type}\n{n_electrodes}\n")
        for r in sorted(rows, key=lambda x: (x["n"], x["idx"])):
            rho = r["rho"] if r["rho"] not in ("", "-", "NaN") else ""
            f.write(f"{r['idx']}  {r['n']}  {r['center']:.1f}  {rho}\n")

写出后重新运行 check_topology,确认异常清单为空或只剩已标记的可疑点,即可把该 DAT 文件作为反演输入。同时把异常测点整理成一张记录表,随数据一起归档,便于后续反演结果出现异常时回溯。

拓扑检查通过的标准不是“脚本不报错”,而是:每个 n 层的测点数量等于 N−3n、相邻测点中心间距恒等于 a、序号连续无跳号、缺失点已用占位行保留位置。四项同时满足,才把文件交给反演。
不要用“删除异常行”来让检查通过。删除会改变测点序号与网格的对应关系,反演剖面会出现整体偏移,且这种偏移在剖面上看起来很像真实的地质边界,很难事后发现。