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

世界杯球童 2026-08-07 09:35:31 2330

面向反射/折射初至走时的 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)底层都是这个思路。

站点统计