石油勘探射线追踪(Ray Tracing)MATLAB 实现

面向反射/折射初至走时的 2D 射线追踪,对标井下/地面石油勘探场景:多层介质 + 层速度模型 + 射线方程(程函/哈密顿形式)+ 界面 Snell 透射/反射 + 初至走时。


一、问题建模(先对齐术语)

石油勘探语境下,射线追踪通常是速度建模 / 走时层析 / Kirchhoff 偏移的前置:

要素 含义
速度模型 v(x,z)v(x,z)v(x,z) 层状 / 线性梯度 / 网格
射线方程 drds=p,  dpds=∇(ln⁡v)  ⇒\frac{d\boldsymbol{r}}{ds}=\boldsymbol{p},\; \frac{d\boldsymbol{p}}{ds}=\nabla(\ln v)\;\Rightarrowdsdr=p,dsdp=∇(lnv)⇒ 哈密顿形式
界面条件 Snell: sin⁡θ1v1=sin⁡θ2v2\frac{\sin\theta_1}{v_1}=\frac{\sin\theta_2}{v_2}v1sinθ1=v2sinθ2(透射),反射同理
目标 炮-检走时 ttt,射线路径,覆盖分析

两套实现:

  • A 版:层状模型 + 解析 Snell
  • B 版:网格速度模型 + 哈密顿 ODE + RK4

二、A 版:层状模型 + Snell 透射/反射

2.1 速度模型定义 velocity_model.m

matlab 复制代码
%% 层状速度模型(石油勘探常用)
% 每层: [顶深(m), 底深(m), v_top(m/s), v_bottom(m/s)]
% 层内速度线性插值 v(z) = v_top + (v_bottom-v_top)*(z-top)/(bottom-top)

function v = layered_velocity(z, layers)
% z: 深度(m),可标量或向量
% layers: N×4 矩阵
v = zeros(size(z));
for i = 1:size(layers,1)
    top = layers(i,1); bottom = layers(i,2);
    v_top = layers(i,3); v_bottom = layers(i,4);
    
    mask = (z >= top) & (z < bottom);
    if i == size(layers,1)
        mask = (z >= top);  % 最底层无下底
    end
    v(mask) = v_top + (v_bottom-v_top) * (z(mask)-top) / (bottom-top+eps);
end
end

%% 示例模型(浅层沉积+基底)
% 地表 z=0,深度+
layers = [ ...
    0,   800,  1800, 2200;   % 表层+泥岩
    800, 1800, 2200, 2800;   % 砂岩
    1800, 3000, 2800, 3500;  % 灰岩
];

2.2 单层内射线传播(常梯度,解析近似)

matlab 复制代码
function [x2, z2, t12, pz2] = ray_segment_layer(z1, z2_target, x1, pz1, vtop, vbot, layer_top, layer_bottom)
% 单层内射线追踪(线性梯度 v(z)=v0 + g*z)
% 输入: 起点(z1,x1), 目标深度 z2_target, 入射慢度 pz1, 层参数
% 输出: 出点(x2,z2), 走时 t12, 出射慢度 pz2

g = (vbot - vtop) / (layer_bottom - layer_top);  % 梯度
v0 = vtop - g*layer_top;  % v(z)=v0+g*z

% 慢度 p = sinθ/v, 水平慢度 px 守恒
% 由 Snell: px = pz1 * (??) ------ 更稳的做法是用 Hamilton:
% dz/ds = pz*v, dpz/ds = -dv/dz = -g
% 常梯度层内可解析:
% 令 pz(z) = pz1 - g*(z-z1)/v(z)? 不,直接用慢度形式:

% === 改用慢度 u=1/v 的形式(更稳)===
% 程函: |∇τ|² = u², 射线: dx/dt = u*p 之类
% 工程上石油界常用"梯形法则+Snell",这里给数值段(通用)

px = pz1 * 0; % 水平慢度------等等,得从入射角来

% 重新入参: 改成给入射角更直观
end

上面这个函数写得太"教科书",实际石油界层内常用两段做法:

  • 层内常速 → 直线 + Snell
  • 层内线性梯度 → 圆弧形射线(解析),x(z)x(z)x(z) 是 sin⁡\sinsin / cos⁡\coscos 形式

2.3 主追踪器(层状 + 透射/反射)raytrace_2d.m

matlab 复制代码
%% 2D 射线追踪 ------ 层状模型,shooting 法
% 炮 (xs, zs), 检波器 (xr, zr),多层常速层,界面 Snell 透射
% 这里做: 直达 + 折射(临界) + 反射 三类

function [rays] = raytrace_2d_shot(xs, zs, xr_arr, zr_arr, layers)
% xs,zs: 炮坐标
% xr_arr,zr_arr: 检波器数组
% layers: N×4 层定义

nc = length(xr_arr);
rays = cell(nc,1);

for ir = 1:nc
    xr = xr_arr(ir); zr = zr_arr(ir);
    
    % ===== 先试: 直达波(同一层内)=====
    if floor(zs/layers(1,2)) == floor(zr/layers(1,2))  % 粗糙同层判
        v = layered_velocity((zs+zr)/2, layers);
        L = sqrt((xr-xs)^2 + (zr-zs)^2);
        t_direct = L / v;
        
        ray.type = 'direct';
        ray.path = [[xs,zs]; [xr,zr]];
        ray.tt = t_direct;
        rays{ir} = ray;
        continue;
    end
    
    % ===== 透射波(射线穿过界面)=====
    % shooting: 从炮出发,猜入射角,追到检波器深度,看水平偏差
    % 这里给简化版: 假设垂直层状,射线只在界面处偏折(层内直线)
    
    % 找出炮→检经过哪些界面
    z_interface = [layers(:,1); layers(end,2)];  % 所有界面深度
    z_pts = unique(sort([zs; zr; z_interface]));
    z_pts = z_pts(z_pts >= min(zs,zr) & z_pts <= max(zs,zr));
    
    % 每段: 常速, Snell 在界面处
    % 入射角 θ1: sinθ1/v1 = sinθ2/v2 = p (射线参数, 守恒)
    
    % shooting: 搜 p (水平慢度)
    p_candidates = linspace(1e-6, 1/min(layers(:,3))*0.99, 200);  % p < 1/v_max
    
    best_err = inf; best_p = NaN;
    best_path = []; best_tt = inf;
    
    for ip = 1:length(p_candidates)
        p = p_candidates(ip);  % 水平慢度, 守恒
        
        % 从炮出发
        x_cur = xs; z_cur = zs;
        tt = 0;
        path = [x_cur, z_cur];
        ok = true;
        
        % 向下追界面
        for iz = 2:length(z_pts)
            z_next = z_pts(iz);
            % 当前所在层
            ilayer = find(z_cur >= layers(:,1) & z_cur < [layers(2:end,1); inf], 1);
            if isempty(ilayer), ilayer = size(layers,1); end
            
            v_layer = layered_velocity((z_cur+z_next)/2, layers);
            
            % 由 p 求垂直慢度: pz = sqrt(u² - p²), u=1/v
            u = 1/v_layer;
            if p >= u
                ok = false; break;  % 全反射/临界
            end
            pz = sqrt(u^2 - p^2);
            
            % 层内 dz, dx
            dz = abs(z_next - z_cur);
            ds = dz / (v_layer * pz * v_layer); % 错------ds = dz/(v*cosθ), cosθ = pz*u? 重算:
            % 慢度矢量 p⃗ = (px, pz) = (p, pz)
            % dx/ds = v * px = v*p, dz/ds = v*pz
            % 所以 dz = v*pz * ds → ds = dz/(v*pz)
            % 但 pz = √(u²-p²)
            ds = dz / (v_layer * pz);
            dx = v_layer * p * ds;
            
            if z_next > z_cur, x_cur = x_cur + dx; else x_cur = x_cur - dx; end
            z_cur = z_next;
            tt = tt + ds;
            path = [path; x_cur, z_cur];
        end
        
        if ~ok, continue; end
        
        % 看水平偏差
        err = abs(x_cur - xr);
        if err < best_err
            best_err = err;
            best_p = p;
            best_path = path;
            best_tt = tt;
        end
    end
    
    % 收敛判据
    if best_err < 5  % 5m 以内算追到
        ray.type = 'transmitted';
        ray.path = best_path;
        ray.tt = best_tt;
        ray.p = best_p;
        rays{ir} = ray;
    else
        rays{ir} = [];  % 没追上,可能是反射/临界,这里略
    end
end
end

2.4 主脚本 seismic_raytrace_demo.m

matlab 复制代码
%% 石油勘探 2D 射线追踪 demo
clear; clc; close all;

%% 1. 速度模型
layers = [ ...
    0,   800,  1800, 2200;   % 表层
    800, 1800, 2200, 2800;   % 砂岩
    1800, 3000, 2800, 3500;  % 灰岩
];

%% 2. 观测系统
xs = 0; zs = 0;                    % 地表炮
xr = 0:50:2000; zr = zeros(size(xr));  % 地表检波器
% 也可以加井下: zr = 1000*ones(size(xr)); 

%% 3. 追踪
rays = raytrace_2d_shot(xs, zs, xr, zr, layers);

%% 4. 走时曲线
tt = zeros(size(xr));
for i = 1:length(xr)
    if ~isempty(rays{i})
        tt(i) = rays{i}.tt;
    else
        tt(i) = NaN;
    end
end

%% 5. 可视化
figure('Color','w','Position',[100 100 1200 500]);

% 速度剖面
subplot(1,2,1); hold on; grid on; box on;
% 画层
for i = 1:size(layers,1)
    plot([0 2500], [layers(i,1) layers(i,1)], 'k-', 'LineWidth',1.5);
    plot([0 2500], [layers(i,2) layers(i,2)], 'k--', 'LineWidth',1);
    % 速度填充(示意)
    zz = linspace(layers(i,1), layers(i,2), 100);
    vv = layered_velocity(zz, layers);
    yy = vv*ones(size(zz'))? 不对------换思路:用 contourf
end
% 画几条射线
colors = parula(length(rays));
cnt = 0;
for i = 1:5:length(rays)  % 稀疏画
    if ~isempty(rays{i})
        cnt = cnt+1;
        r = rays{i};
        plot(r.path(:,1), r.path(:,2), '-', 'Color', colors(cnt,:), 'LineWidth',1);
        plot(r.path(:,1), r.path(:,2), '.k', 'MarkerSize',3);
    end
end
% 炮检
plot(xs, zs, 'r*', 'MarkerSize',14, 'LineWidth',2);
plot(xr, zr, 'kv', 'MarkerSize',8, 'LineWidth',1);
xlabel('x (m)'); ylabel('z (m)'); title('射线追踪(层状模型)');
axis ij; ylim([3000 0]); xlim([-100 2100]);

% 走时曲线
subplot(1,2,2); hold on; grid on;
plot(xr, tt, 'bo-', 'LineWidth',1.5);
xlabel('偏移距 (m)'); ylabel('走时 (s)');
title('初至走时曲线 (t-x)');
% 画 v(z) 示意
axes('Position',[0.7 0.15 0.1 0.6]); hold on;
zz = linspace(0,3000,200);
vv = layered_velocity(zz, layers);
plot(vv, zz, 'r-', 'LineWidth',2);
set(gca,'YDir','reverse'); ylim([3000 0]); xlabel('v (m/s)'); ylabel('');
title('v(z)');

sgtitle('石油勘探 2D 射线追踪 Demo', 'FontSize',14, 'FontWeight','bold');

参考代码 raytrace石油勘探的射线追踪程序 www.youwenfan.com/contentcsw/82208.html

三、B 版:网格速度模型 + 哈密顿 ODE

A 版够教学,但真做走时层析 一般用网格 v(x,z)v(x,z)v(x,z) + 最短路径法(SPM)或 Runge-Kutta 追射线。给你 RK 版骨架:

matlab 复制代码
%% 哈密顿射线追踪(网格速度模型)
% 射线方程(慢度形式):
%   dx/dt = v² * px
%   dz/dt = v² * pz
%   dpx/dt = -v * ∂v/∂x
%   dpz/dt = -v * ∂v/∂z
% 其中 p = ∇τ(慢度矢量), v = 1/u

function [x_ray, z_ray, t_ray] = hamilton_raytrace(x0, z0, px0, pz0, v_grid, xvec, zvec, dt)
% v_grid: 网格速度 (Nz×Nx)
% xvec, zvec: 网格坐标
% dt: RK 步长

% 初始
x = x0; z = z0;
px = px0; pz = pz0;
t = 0;

x_ray = x; z_ray = z; t_ray = t;

while t < 1e4  % 追到检波器逻辑外面包
    
    % 插值 v, dv/dx, dv/dz
    v = interp2(xvec, zvec, v_grid, x, z, 'linear', 2000);
    [dvdx, dvdz] = gradient(v_grid, mean(diff(xvec)), mean(diff(zvec)));
    dvdx_i = interp2(xvec, zvec, dvdx, x, z, 'linear', 0);
    dvdz_i = interp2(xvec, zvec, dvdz, x, z, 'linear', 0);
    
    % RK4
    % k1
    k1x = v^2 * px; k1z = v^2 * pz;
    k1px = -v * dvdx_i; k1pz = -v * dvdz_i;
    
    % k2, k3, k4 略(模板化写)...
    % 这里给欧拉示意(想稳就用 RK4)
    x = x + dt * k1x; z = z + dt * k1z;
    px = px + dt * k1px; pz = pz + dt * k1pz;
    t = t + dt;
    
    x_ray = [x_ray; x]; z_ray = [z_ray; z]; t_ray = [t_ray; t];
    
    % 出口判(追到检波器)
    if z > zvec(end)-10, break; end
end
end

工程上更稳的是最短路径法(SPM)/ Fast Marching,网格上直接解程函方程得 τ(x,z),再插射线(∇τ 方向)。石油界商业 code(Hampson-Russell, Omega)底层都是这个思路。