下面对目前所有相位恢复的代码进行整理,详细说明每段代码的原理和功能。

使用 PhaseRetrival 工具包进行相位恢复

验证传播函数正确性

下面是使用 PhaseRetrival 工具包进行相位恢复前的准备工作,使用FECO导出的数据(幅值+相位)模拟衍射,以验证衍射部分代码的正确性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
%% 导入使用的相位恢复包
addpath('E:\组会\其他\实验室历史文件\code\phasepack-matlab-master\solvers');
addpath('E:\组会\其他\实验室历史文件\code\phasepack-matlab-master\util');
addpath('E:\组会\其他\实验室历史文件\code\phasepack-matlab-master\initializers');
global numrows numcols num_fourier_masks masks padding lamda dx;

%% 参数初始化
f = 1.65e9;
lamda = physconst('lightspeed') / f;
k0 = 2 * pi / lamda;
dtheta = pi / 180;
dphi = pi / 180;
theta = -pi / 2:dtheta:pi / 2;
phi = 0:dphi:pi;

%% 导入FECO仿真得到的电场原始数据作为真值
NF_data1 = 'E:\组会\其他\实验室历史文件\code\phase retrival\NFdata\DATAyyq\l_6lamb_n_97_2-5lamb.efe';
NF_data2 = 'E:\组会\其他\实验室历史文件\code\phase retrival\NFdata\DATAyyq\l_6lamb_n_97_3-0lamb.efe';
NF_data3 = 'E:\组会\其他\实验室历史文件\code\phase retrival\NFdata\DATAyyq\l_6lamb_n_97_3-5lamb.efe';

%% 根据导入的数据,自动处理,只保留电场的主方向(y方向),因为工具包只能输入一维向量,所以这里将电场排成一列,并取得采样面上的点样点数和采样间隔
[Ex1, Ey1, xd1, yd1] = load_NF(NF_data1);
[Ex2, Ey2, xd2, yd2] = load_NF(NF_data2);
[Ex3, Ey3, xd3, yd3] = load_NF(NF_data3);
Ey1_1D = Ey1(:);
Ey2_1D = Ey2(:);
Ey3_1D = Ey3(:);

M = length(xd2); % x方向采样点数
N = length(yd2); % y方向采样点数
dx = (xd2(2) - xd2(1)); % x方向采样间隔
dy = (yd2(2) - yd2(1)); % y方向采样间隔
image-20250407101206002
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
%% 对原始数据进行扩展,在电场外围补0,增加谱域的分辨率,边长扩展为原来的两倍
padding = 2;
MI = padding * M;
NI = padding * N;
m = -MI / 2:1:MI / 2 - 1;
n = -NI / 2:1:NI / 2 - 1;

k_X_Rectangular = 2 * pi * m / (MI * dx);
k_Y_Rectangular = 2 * pi * n / (NI * dy);
dk_X = k_X_Rectangular(2) - k_X_Rectangular(1);
dk_Y = k_Y_Rectangular(2) - k_Y_Rectangular(1);
[k_Y_Rectangular_Grid, k_X_Rectangular_Grid] = meshgrid(k_Y_Rectangular, k_X_Rectangular);

%% 计算第一个面电场(幅值+相位)的角谱,并绘制
fy1 = fftshift(ifft2(Ey2, MI, NI) * MI * NI) * dx * dy;
fy_Magnitude = 20 * log10(abs(fy1));
image-20250407102315210
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
%% 频谱域滤波
kz = sqrt(k0 ^ 2 - k_X_Rectangular_Grid .^ 2 - k_Y_Rectangular_Grid .^ 2);

%% 导入天线口径尺寸、测量面尺寸、测量面距离
W = 0.55;
H = 0.428; %天线孔径尺寸

w = dx * (M - 1); % x方向扫描区域的长度[m]
h = dy * (N - 1); % y方向扫描区域的长度[m]

z0 = 2.5 * lamda; % 扫描面位置
z1 = 3.0 * lamda; % 参考平面位置
z2 = 3.5 * lamda;

%% 设置可信比例,需要同时实验测试出最佳数值,不同案例的数值不同,一般在0.8~1.0之间
Reliable_rate = 1;

%% 计算可信角度
thetax_sure = Reliable_rate * atan(0.5 * (w - W) / z1);
thetay_sure = Reliable_rate * atan(0.5 * (h - H) / z1);

%% 计算频谱域滤波函数
UR = zeros(MI, NI);

for i = 1:MI
for j = 1:NI
if ((k_X_Rectangular(i) * k_X_Rectangular(i) / ((k0 * sin(thetax_sure)) ^ 2)) + (k_Y_Rectangular(j) * k_Y_Rectangular(j) / (k0 ^ 2)) < 1.0 && (k_X_Rectangular(i) * k_X_Rectangular(i) / (k0 ^ 2)) + (k_Y_Rectangular(j) * k_Y_Rectangular(j) / ((k0 * sin(thetay_sure)) ^ 2)) < 1.0)
UR(i, j) = 1;
else
UR(i, j) = 0;
end
end
end

%% 计算天线的可信波谱(频谱域滤波函数与电场角谱相乘),并绘制
fy_H_Magnitude = 20 * log10(abs(fy1 .* UR));
image-20250407102657234
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
%% 创建‘num_fourier_mask ’傅里叶掩码,即矩阵A,实现物理衍射过程。
[numrows, numcols] = size(k_Y_Rectangular_Grid); % 设置掩码尺寸

num_fourier_masks = 2; % 设置衍射面数(原始面、衍射面)
masks = zeros(numrows, numcols, num_fourier_masks);

dz = [0 0.5];

isim = imag(kz) ~= 0; % k圆的外部
kz(isim) = -1 * kz(isim); % yyq - 修正k圆的外部衍射公式 -1i 改为 -1

for i = 1:num_fourier_masks
% i 为 1 时,mask 为 全 1 矩阵,即不改变原始面
% i 为 2 时,mask 为 衍射矩阵,即通过第一个面的角谱计算得到第二个面
masks(:, :, i) = UR .* exp(-1i * kz * dz(i) * lamda);
end

%% 验证计算结果,使用原始数据信息直接获取幅相
fy0 = fftshift(ifft2(Ey2, MI, NI) * MI * NI) * dx * dy;
x = fy0(:); % 将 角谱信号 转换为一维向量,以便PhasePack可以处理它

b_cal = abs(A(x)); % 函数A —— 将 谱域x 使用 傅里叶变换 转换成 场
b_real = [abs(Ey2_1D); abs(Ey3_1D)];
image-20250407103208504
1
2
3
4
5
6
7
% 计算两个面分别与真实值之间的误差
b_err_1 = sqrt(sum(abs(abs(b_real) - b_cal) .^ 2, 'all') / sum(b_cal .^ 2, 'all'));
b_err_2 = abs((b_real - b_cal) ./ b_cal);

b_err = reshape(b_err_2, [numrows / padding, numcols / padding, num_fourier_masks]);
E1_err = (squeeze(b_err(:, :, 1)));
E2_err = (squeeze(b_err(:, :, 2)));
image-20250407103324787 image-20250407103342730

PhaseRetrival 工具包进行相位恢复

上面已经验证了衍射过程的正确,下面开始使用 PhaseRetrival 工具包进行相位恢复,这个过程是已经集成好的,只需要设置少量参数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
%% 设置PhasePack选项
opts = struct; % 创建一个空结构体来存储选项
opts.algorithm = 'RAF'; % 求解方法 RAF
opts.initMethod = 'Weighted'; % 求解器生成初始起点方法 Weighted
opts.tol = 1e-6; % 容差 - 将其变小以获得更精确的解决方案,或将其增大以获得更快的运行时间
opts.verbose = 1; % 求解器运行时输出大量信息(将此设置为1或0以减少输出)
opts.maxIters = 1000;
% 这里还有更多的选项,比如:opts.searchMethod = 'LBFGS'、initSpectral、initOrthogonal、initWeighted、initOptimalSpectral,具体内容还未了解

%% 运行相位恢复算法
fprintf('Running.....\n 算法: %s \n 初始化: %s \n 误差: %s \n 迭代次数: %d \n', opts.algorithm, opts.initMethod, opts.tol, opts.maxIters);
% 使用测量算子“A”、伴随算子“At”、测量值“b”、要恢复的信号长度和选项来调用求解器。
% 注意,此方法可以接受函数句柄或矩阵作为测量操作符。这里,我们使用函数句柄,因为我们依赖FFT来快速完成任务。如果A是函数句柄,则必须提供At和n
[x, outs, opts] = solvePhaseRetrieval(@A, @At, b_real, numel(k_Y_Rectangular_Grid), opts);

% 将矢量输出转换回二维图像
recovered_image = reshape(x, numrows, numcols);

%打印一些有用的信息到控制台
fprintf('Image recovery required %d iterations (%f secs)\n', outs.iterationCount, outs.solveTimes(end));

by_retrival = A(recovered_image); % 函数A —— 将 谱域x 使用 傅里叶变换 转换成 场
by_retrival = reshape(by_retrival, [numrows / padding, numcols / padding, num_fourier_masks]);
E2 = (squeeze(by_retrival(:, :, 1))); % yyq - 更正远场变量命名方式,E1 改为 E2
image-20250407104118383 image-20250407104318928

对比远场数据

为验证 PhaseRetrival 工具包的计算结果,使用其计算出的远场方向图与FECO导出的远场进行对比。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
% 计算远场方向图
[EPLANE, HPLANE, Etot] = computerFF(theta, phi, Ex2, E2, k0, M, N, dx); % yyq - 更正计算远场的电场平面,E1 改为 E2
[EPLANE2, HPLANE2, Etot2] = computerFF(theta, phi, Ex2, Ey2, k0, M, N, dx);

% 远场误差计算
Etot_Size = size(Etot);

ffH_err = 20 * log10(abs((Etot(1, :) - Etot2(1, :)) / max(Etot2(1, :))));
ffE_err = 20 * log10(abs((Etot(floor(Etot_Size(2) / 2), :)) - Etot2(floor(Etot_Size(2) / 2), :)) ./ max(Etot2(floor(Etot_Size(2) / 2), :)));

ENL_H = 20 * log10(mean(abs((Etot(1, :) - Etot2(1, :)) / max(Etot2(1, :)))));
ENL_E = 20 * log10(mean(abs((Etot(floor(Etot_Size(2) / 2), :)) - Etot2(floor(Etot_Size(2) / 2), :)) ./ max(Etot2(floor(Etot_Size(2) / 2), :))));
ENL = 20 * log10(mean(abs((Etot - Etot2) / max(max(Etot2))), "all"));

% 读取仿真软件仿真的远场方向图
far_data = importdata("E:\组会\其他\实验室历史文件\code\phase retrival\NFdata\ffdata.dat");
xn1 = far_data.data(:, 1);
yn1 = far_data.data(:, 2);
yn2 = far_data.data(:, 3);
image-20250407104225163

辅助函数

下面是 PhaseRetrival 工具包在计算两个面衍射的时候,使用的辅助函数A 和 A的逆函数At。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
% 函数A —— 将 谱域x 使用 傅里叶变换 转换成 场
function measurements = A(pixels)
global numrows numcols num_fourier_masks masks padding dx;

dk_X = 2 * pi / (numrows * dx);

% 重构方法将迭代存储为向量,因此该函数需要接受一个向量作为输入,恢复为2维图像
im = reshape(pixels, [numrows, numcols]);

measurements = zeros(numrows / padding, numcols / padding, num_fourier_masks);
% 将 输入 进行 傅里叶变换 转换成 场
for m = 1:num_fourier_masks
this_mask = squeeze(masks(:, :, m));
measurements_Padded = (fft2(ifftshift(im .* this_mask))) * dk_X * dk_X / (4 * pi * pi);
measurements(:, :, m) = measurements_Padded(1:numrows / padding, 1:numcols / padding);
end

measurements = measurements(:);
end

function pixels = At(measurements)
global numrows numcols num_fourier_masks masks padding dx;

dk_X = 2 * pi / (numrows * dx);

im_reconstructed = zeros(numrows, numcols);
measurements = reshape(measurements, [numrows / padding, numcols / padding, num_fourier_masks]);

for m = 1:num_fourier_masks
this_mask = squeeze(masks(:, :, m));
this_measurements = squeeze(measurements(:, :, m));
im_reconstructed = im_reconstructed + conj(this_mask) .* fftshift(ifft2(this_measurements, numrows, numcols)) * numrows * numcols * dk_X * dk_X / (4 * pi * pi);
end

pixels = im_reconstructed(:);
end

使用U-Net 网络直接进行相位恢复

下面是最初直接使用tensorflow 搭建 U-Net 网络进行相位恢复的代码

数据读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 读取 FECO 导出的电场数据,与上面 PhaseRetrival 不同的是,这里输入和输出均为二维图像,不需要变为一维进行处理
class PlaneData:
def __init__(self, file_name):
abs_temp = []
E_temp = []
phase_temp = []
f = open(file_name)
E = f.read()
if file_name.split('.')[-1] == 'txt':
E = E.split('\n')[2:-1]
elif file_name.split('.')[-1] == 'efe':
E = E.split('\n')[16:-2]

x = int(np.sqrt(len(E)))
y = int(np.sqrt(len(E)))

index_dot = file_name.find('-')
distance_plane = int(file_name[index_dot - 1]) + float(file_name[index_dot + 1]) * 0.1

main_direction = 'y'

for k in E:
E_xp, E_yp, E_zp, E_xr, E_xi, E_yr, E_yi, E_zr, E_zi = k.split()
E_x = complex(float(E_xr), float(E_xi))
E_y = complex(float(E_yr), float(E_yi))
E_z = complex(float(E_zr), float(E_zi))

if main_direction == 'y':
abs_temp.append(np.abs(E_y))
E_temp.append(E_y)
phase = np.angle(E_y)
phase_temp.append(phase)
elif main_direction == 'x':
abs_temp.append(np.abs(E_x))
E_temp.append(E_x)
phase = np.angle(E_x)
phase_temp.append(phase)
elif main_direction == 'z':
abs_temp.append(np.abs(E_z))
E_temp.append(E_z)
phase = np.angle(E_z)
phase_temp.append(phase)

abs_pro = np.array(abs_temp).reshape((x, y))
E_temp = np.array(E_temp).reshape((x, y))
phase_pro = np.array(phase_temp).reshape((x, y))

self.abs = abs_pro[0:96, 0:96] / np.max(abs_pro[0:96, 0:96])
self.phase = phase_pro[0:96, 0:96]
self.E = E_temp[0:96, 0:96] / np.max(np.abs(E_temp[0:96, 0:96]))
self.N = x - 1
self.distance = distance_plane

plane_1 = PlaneData(r'DATA\l_6lamb_n_97_2-5lamb.efe')
plane_2 = PlaneData(r'DATA\l_6lamb_n_97_3-0lamb.efe')
plane_3 = PlaneData(r'DATA\l_6lamb_n_97_3-2lamb.efe')

定义变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 定义变量
dim = plane_1.N # 设置电场平面维度为256
jqx = 0 # 截取位置的横坐标
jqy = 0 # 截取位置的纵坐标
plane_W = dim # 图像宽度
plane_H = dim # 图像高度
batch_size = 1 # 批处理大小为1

# 定义截取平面参数
lamb = 299792458 / 1.65e9 # 波长(单位:米)
pixelsize = lamb / 8 # 像素尺寸(单位:米)
N = dim # 像素数量
L = N * pixelsize # 物体平面和图像平面的长度(单位:米)

# 定义机器学习参数
Steps = 5000 # 迭代步数
LR = 0.01 # 学习率
noise_level = 0 # 噪声水平

定义网络结构

1
2
3
4
5
6
7
8
9
10
11
# 定义输入层,用于接收噪声和未测量数据
with tf.compat.v1.variable_scope('input'):
rand = tf.placeholder(shape=(dim, dim), dtype=tf.float32, name='noise')
true_measure = tf.constant(plane_2.abs, dtype=tf.float32, name='diffraction')
inpt = true_measure + rand

# 定义推断层,用于推断未测量数据
with tf.compat.v1.variable_scope('inference'):
out = model_Unet.inference(inpt, plane_H, plane_W, batch_size)
out = tf.reshape(out, [plane_H, plane_W])
# 上面代码中 model_Unet.inference 即为网络结构

引入物理衍射

1
2
3
# 定义物理模型,用于计算测量数据
out_calculate = Calculate_step.Phy(out, lamb, L, (plane_2.distance - plane_1.distance) * lamb,
plane_1.abs, jqx, jqy, plane_H, plane_W)

上面 Calculate_step.Phy 函数用来模拟物理传播过程,与之前使用 PhaseRetrival 工具包进行相位恢复时的物理传播过程保持一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def Phy(fai_1, lamb, L, Z, E_1_abs, jqx, jqy, plane_H, plane_W):
M = int(fai_1.shape[1])
E_abs_1 = E_1_abs
E_abs_1 = E_abs_1[jqx:jqx + plane_H, jqy:jqy + plane_W]
E_abs_1 = E_abs_1 / np.max(E_abs_1)

# 将'fai_1'转换为复数数据类型
fai_1 = tf.cast(fai_1, dtype=tf.complex128)
# 将'fai_1'乘以虚数单位'j'
fai_1 = 1j * fai_1
# 对'fai_1'进行指数运算
U_in0 = tf.exp(fai_1)
# 对'fai_1'进行幅值加权运算
U_in = tf.multiply(U_in0, E_abs_1)
# 使用'Myfftshift'模块中的自定义函数应用FFT和shift操作
U_in_fft = Myfftshift.fftshift(tf.signal.fft2d(U_in))
U_in_fft = tf.cast(U_in_fft, dtype=tf.complex128)

# 计算空间频率
fx = 1 / L
# 生成一个均匀间隔的数字数组'x'
x = np.linspace(-M / 2, M / 2 - 1, M)
# 将'fx'与'x'相乘
fx = (fx * x)
# 使用'fx'创建一个二维坐标网格
[Fx, Fy] = np.meshgrid(fx, fx)
# 计算一个常数'k'
k = 2 * math.pi / lamb
# k = 2 * math.pi / lamb * 2

# 计算传递函数'G'
# Ax\Ay是口面场的长和宽,Lx\Ly是测量面的长和宽
Ax = 548
Ay = 426
Lx = L * 1000
Ly = L * 1000
# 设置可信比例
Reliable_rate = 1

thea_x = Reliable_rate * np.arctan(((Lx - Ax) / 2 / 2.5 / lamb / 1000))
thea_y = Reliable_rate * np.arctan(((Ly - Ay) / 2 / 2.5 / lamb / 1000))

G1 = (1 - lamb * lamb * (Fx * Fx / np.sin(thea_x) / np.sin(thea_x) + Fy * Fy))
G2 = (1 - lamb * lamb * (Fx * Fx + Fy * Fy / np.sin(thea_y) / np.sin(thea_y)))
G = (1 - lamb * lamb * (Fx * Fx + Fy * Fy))
H = np.ones_like(G) * 1j

for i in range(H.shape[0]):
for j in range(H.shape[1]):
if G[i, j] >= 0:
temp = k * (-1 * Z) * np.sqrt(G[i, j])
H[i, j] = np.exp(1j * temp)
else:
temp = k * (1j * Z) * np.sqrt(1j * G[i, j] * 1j)
H[i, j] = np.exp(-1 * temp)

H = tf.cast(H, dtype=tf.complex128)
U_out_fft = tf.multiply(U_in_fft, H)
# 应用逆FFT和shift操作
U_out = tf.signal.ifft2d(Myfftshift.ifftshift(U_out_fft))
# 对'U_out'进行归一化
U_out_abs = tf.abs(U_out)
temp = tf.reduce_max(tf.reduce_max(U_out_abs))
U_out = tf.abs(U_out) / temp
# 返回最终的电场强度
return U_out

神经网络补充

1
2
3
4
5
6
# 定义损失函数,用于计算均方误差
loss_m = tf.compat.v1.losses.mean_squared_error(out_calculate, true_measure) # 代价函数(不依赖于标记数据)
optimizer = tf.compat.v1.train.AdamOptimizer(learning_rate=LR)

# 获取更新操作集合
update_ops = tf.compat.v1.get_collection(tf.compat.v1.GraphKeys.UPDATE_OPS)

神经网络初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
# 确保在训练操作之前先执行更新操作
with tf.control_dependencies(update_ops):
train_op = optimizer.minimize(loss_m)

# 初始化saver
saver = tf.compat.v1.train.Saver()

with tf.compat.v1.Session() as sess:
# 初始化全局变量
sess.run(tf.global_variables_initializer())
# 初始化损失值
m_loss = np.zeros([Steps, 1])
temp_m_loss = []

网络迭代优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 开始优化
for step in range(Steps):

# 生成随机数
new_rand = np.random.uniform(0, noise_level, size=(dim, dim)) # noise_level
sess.run([train_op], feed_dict={rand: new_rand})

loss_measure = sess.run(loss_m, feed_dict={rand: new_rand})
m_loss[step] = loss_measure
print('Step:%d Measure loss:%f' % (step, loss_measure))

# 这行代码创建了一个名为 new_rand 的元素全为 0 的 Numpy 数组,形状为 (dim, dim)。
new_rand = np.random.uniform(0, 0.0, size=(dim, dim))
pha_out = sess.run(out, feed_dict={rand: new_rand}).reshape(dim, dim)
out_calculate_temp = sess.run(out_calculate, feed_dict={rand: new_rand}).reshape(dim, dim)

使用U-Net 网络源重构进行相位恢复

下面是通过源重构进行相位恢复,把上面物理衍射的物理过程改为格林公式,模拟等效源在空间中的辐射过程。

模型差异

其机器学习相关的大量代码——网络结构、初始化、迭代过程——与上一个保持一致,只是物理过程部分的代码不同,下面只展示不同部分。

1
2
3
4
5
6
7
8
9
# 定义物理模型,用于计算测量数据
sample1_cords = Calculate_step.set_cords(-1 * int(N / 2) * dx, int(N / 2) * dy, dx, dy,
plane_RS.distance * lamda)
source_cords = Calculate_step.set_cords(-1 * int(N / 2) * dx, int(N / 2) * dy, dx, dy,
plane_RS.distance * lamda * 0.8)
g = Calculate_step.green_function(source_cords, sample1_cords, k, dx, dy)

out_calculate = tf.abs(g @ out)
out_calculate = tf.reshape(out_calculate, [dim, dim])
1
2
3
4
5
6
7
8
9
10
11
12
13
# Calculate_step.set_cords 函数是设置并保存 等效源与测量平面 的坐标的函数,后续传给计算出格林函数的模块。
def set_cords(begin, end, dx, dy, Z):
x_d = np.arange(begin, end, dx)
y_d = np.arange(begin, end, dy)
[x_grid, y_grid] = np.meshgrid(x_d, y_d)
z_grid = Z * np.ones_like(x_grid)

x_cords = x_grid.reshape(-1, 1)
y_cords = y_grid.reshape(-1, 1)
z_cords = z_grid.reshape(-1, 1)

return_cords = np.hstack((y_cords, x_cords, z_cords))
return return_cords
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Calculate_step.green_function 函数是生成等效源到测量平面的格林函数g,进而通过神经网络得到的等效源模拟辐射,计算得到测量面的电场。
def green_function(source_xy, sample_xy, k, dx, dy):
x_source = source_xy[:, 0]
y_source = source_xy[:, 1]
z_source = source_xy[:, 2]

x_sample = sample_xy[:, 0]
y_sample = sample_xy[:, 1]
z_sample = sample_xy[:, 2]

g = np.zeros((len(x_sample), len(x_source)), dtype=complex)

# 矢量化计算格林函数
for m in range(len(x_sample)):
R_ij = np.sqrt((x_sample[m] - x_source) ** 2 + (y_sample[m] - y_source) ** 2 + (z_sample[m] - z_source) ** 2)
g[m, :] = 1 / (4 * np.pi) * (np.exp(-1j * k * R_ij) / R_ij ** 2) * ((z_sample[m] - z_source) * (1j * k + 1 / R_ij)) * dx * dy

return g

使用全连接神经网络进行相位恢复

论文复现

首先,下面是对 PhaseRetrival 工具包中相位恢复相关论文中提到的原理复现。这一部分并不涉及机器学习,是传统的相位恢复算法——使用加权最大相关初始化,并用加权梯度流进行迭代。

按照给定的形状随机生成实数信号(y、A、x)

1
2
3
4
5
6
7
8
9
def generate_data(n, m):
# 生成真实信号 x (n维向量)
x = np.random.randn(n)
x = x / np.linalg.norm(x) # 归一化
# 生成随机高斯测量矩阵 A (m x n)
A = np.random.randn(m, n)
# 生成测量值 y = |A x|
y = np.abs(A @ x)
return A, y, x

定义初始化方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def weighted_max_correlation_init(A, y, S_size):
m, n = A.shape
S = np.argsort(y)[-S_size:] # 选择最大的 S_size 个测量值对应的索引
A_S = A[S, :]
y_S = y[S]
# 计算加权矩阵 Y
gamma = 0.5 # 论文中默认的 gamma 值
weights = y_S ** gamma
Y = (A_S.T * weights) @ A_S / S_size

# 计算 Y 的最大特征向量作为初始估计
_, _, Vt = svds(Y, k=1)
z0 = Vt[0]
# 估计信号范数
norm_estimate = np.sqrt(np.mean(y ** 2))
z0 = z0 * norm_estimate
return z0

定义优化方法——加权幅流法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def reweighted_amplitude_flow(A, y, z0, max_iter=3000, mu=0.5, beta=5):
m, n = A.shape
z = z0.copy()
for t in range(max_iter):
# 计算当前估计的幅度
Az = A @ z
Az_abs = np.abs(Az)
# 计算权重
ratios = Az_abs / y
weights = 1 / (1 + beta / ratios)
# 计算梯度
gradient = (A.T @ (weights * (Az - y * (Az / Az_abs)))) / m
# 更新估计
z = z - mu * gradient
# 检查收敛条件
if np.linalg.norm(gradient) < 1e-6:
break
return z

主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def main():
n = 1000 # 信号维度
m = 2 * n - 1 # 测量数量,接近信息论极限
S_size = int(3 * m / 13) # 初始化时选择的测量数量
# 生成数据
A, y, x = generate_data(n, m)

# 加权最大相关初始化
z0 = weighted_max_correlation_init(A, y, S_size)

# 迭代加权梯度流
z_hat = reweighted_amplitude_flow(A, y, z0)

# 计算恢复误差
error = np.linalg.norm(z_hat - x) / np.linalg.norm(x)
print(f"Recovery error: {error}")

引入神经网络实现

下面把神经网络引入,根据之前同学提醒,打算使用端对端的神经网络,通过输入A与y,直接得到待求的x。

1
2
3
4
5
6
# 生成数据的函数保持不变
def generate_data(n, m):
x = np.random.randn(n)
A = np.random.randn(m, n)
y = np.abs(A @ x)
return A, y, x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 定义神经网络模型
class SignalRecoveryModel(nn.Module):
def __init__(self, m, n):
super(SignalRecoveryModel, self).__init__()
# 输入是 A 和 y 的组合,A 是 m x n 矩阵,y 是 m 维向量
# 将 A 和 y 展平并拼接为一个输入向量
self.input_size = m * n + m # A 的大小是 m * n,y 的大小是 m
self.fc1 = nn.Linear(self.input_size, 512)
self.fc2 = nn.Linear(512, 256)
self.fc3 = nn.Linear(256, n) # 输出是 n 维信号 x
self.relu = nn.ReLU()

def forward(self, A, y):
# 将 A 和 y 展平并拼接
A_flat = A.view(-1) # 将 A 展平为 (m * n) 向量
y_flat = y.view(-1) # 将 y 展平为 m 向量
x_input = torch.cat((A_flat, y_flat), dim=0) # 拼接为 (m * n + m) 向量
x_input = x_input.unsqueeze(0) # 添加 batch 维度
x = self.relu(self.fc1(x_input))
x = self.relu(self.fc2(x))
x = self.fc3(x)
return x.squeeze(0) # 去掉 batch 维度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def main():
n = 97 * 2 # 信号维度
m = 2 * n # 测量数量,接近信息论极限

# 生成数据
A, y, x = generate_data(n, m)

# 将数据转换为 PyTorch 张量
A_tensor = torch.FloatTensor(A)
y_tensor = torch.FloatTensor(y)
x_tensor = torch.FloatTensor(x)

# 初始化模型
model = SignalRecoveryModel(m, n)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 训练模型
num_epochs = 1000
for epoch in range(num_epochs):
model.train()
optimizer.zero_grad()
outputs = model(A_tensor, y_tensor)
loss = criterion(outputs, x_tensor)
loss.backward()
optimizer.step()

if (epoch + 1) % 100 == 0:
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')

# 测试模型
model.eval()
with torch.no_grad():
z_hat = model(A_tensor, y_tensor).numpy()

# 计算恢复误差
error = np.linalg.norm(z_hat - x) / np.linalg.norm(x)
print(f"Recovery error: {error}")

复数改进

上面的代码只针对实数的情况,但在相位恢复中的数据都是复数,目前的神经网络只支持实数,所以需要对其进行改进,以满足复数的需要,下面是改进后的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 生成复数数据
def generate_data(n, m):
# 生成复数信号 x (n维向量)
x = np.random.randn(n) + 1j * np.random.randn(n)
x = x / np.linalg.norm(x) # 归一化

# 生成复数高斯测量矩阵 A (m x n)
A = np.random.randn(m, n) + 1j * np.random.randn(m, n)

# 生成测量值 y = |A x|
y = np.abs(A @ x) # y 是实数向量

return A, y, x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 定义复数神经网络模型
class ComplexSignalRecoveryModel(nn.Module):
def __init__(self, m, n):
super(ComplexSignalRecoveryModel, self).__init__()
# 输入维度: A的实部(m*n) + A的虚部(m*n) + y(m) = 2*m*n + m
self.input_size = 2 * m * n + m
# 输出维度: x的实部(n) + x的虚部(n) = 2n
self.output_size = 2 * n

# 定义网络结构
self.fc1 = nn.Linear(self.input_size, 2048)
self.fc2 = nn.Linear(2048, 1024)
self.fc3 = nn.Linear(1024, self.output_size)
self.relu = nn.ReLU()

def forward(self, A_real_flat, A_imag_flat, y_flat):
# 拼接输入: [A_real, A_imag, y]
x_input = torch.cat([A_real_flat, A_imag_flat, y_flat], dim=0)
x_input = x_input.unsqueeze(0) # 添加batch维度

# 前向传播
x = self.relu(self.fc1(x_input))
x = self.relu(self.fc2(x))
x = self.fc3(x)

# 输出拆分为实部和虚部
x_out = x.squeeze(0)
x_real = x_out[:self.output_size // 2]
x_imag = x_out[self.output_size // 2:]
return x_real, x_imag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def main():
n = 25 # 信号维度
m = 2 * n - 1 # 测量数量

# 生成复数数据
A, y, x = generate_data(n, m)
x_real_true = np.real(x)
x_imag_true = np.imag(x)

# 将A拆分为实部和虚部,并展平
A_real = np.real(A).flatten()
A_imag = np.imag(A).flatten()
y_flat = y.flatten()

# 转换为PyTorch张量
A_real_tensor = torch.FloatTensor(A_real)
A_imag_tensor = torch.FloatTensor(A_imag)
y_tensor = torch.FloatTensor(y_flat)
x_real_tensor = torch.FloatTensor(x_real_true)
x_imag_tensor = torch.FloatTensor(x_imag_true)

# 初始化模型
model = ComplexSignalRecoveryModel(m, n)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 训练模型
num_epochs = 1000
for epoch in range(num_epochs):
model.train()
optimizer.zero_grad()
x_real_pred, x_imag_pred = model(A_real_tensor, A_imag_tensor, y_tensor)

# 计算实部和虚部的损失
loss_real = criterion(x_real_pred, x_real_tensor)
loss_imag = criterion(x_imag_pred, x_imag_tensor)
total_loss = loss_real + loss_imag

total_loss.backward()
optimizer.step()

if (epoch + 1) % 100 == 0:
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {total_loss.item():.4f}')

# 测试模型
model.eval()
with torch.no_grad():
x_real_pred, x_imag_pred = model(A_real_tensor, A_imag_tensor, y_tensor)
z_hat = x_real_pred.numpy() + 1j * x_imag_pred.numpy()

# 计算恢复误差
error = np.linalg.norm(z_hat - x) / np.linalg.norm(x)
print(f"Recovery error: {error}")

验证传播函数的正确性

这一部分用来验证传播函数部分代码的正确性,共有两个部分。第一部分是与之前 PhaseRetrival 工具包一样,验证衍射过程的正确性;第二部分是因为在神经网络中需要测量矩阵A的显示表达,所以还需验证其正确性。

首先是衍射过程中函数A和A的逆函数At的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 函数A的实现
def A_function(pixels, masks, padding, dx):
numrows, numcols = masks.shape[:2]
im = pixels.reshape(numrows, numcols)
measurements = np.zeros((numrows // padding, numcols // padding, masks.shape[2]), dtype=np.complex128)

dk_X = 2 * np.pi / (numrows * dx)
for m in range(masks.shape[2]):
masked = im * masks[:, :, m]
ft = np.fft.fft2(np.fft.ifftshift(masked))
measurements[:, :, m] = ft[:numrows // padding, :numcols // padding] * (dk_X ** 2) / (4 * np.pi ** 2)

return measurements.ravel(order='F')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 函数At的实现
def At_function(measurements, numrows, numcols, num_fourier_masks, masks, padding, dx):
dk_X = 2 * np.pi / (numrows * dx)

im_reconstructed = np.zeros((numrows, numcols), dtype=complex)
measurements = np.reshape(measurements, (numrows // padding, numcols // padding, num_fourier_masks), order='F')

for m in range(num_fourier_masks):
this_mask = np.squeeze(masks[:, :, m])
this_measurements = np.squeeze(measurements[:, :, m])
im_reconstructed += np.conj(this_mask) * np.fft.fftshift(np.fft.ifft2(this_measurements, s=(numrows, numcols))) * numrows * numcols * dk_X * dk_X / (4 * np.pi * np.pi)

pixels = im_reconstructed.ravel(order='F')
return pixels

下面是测量矩阵A的显示表达函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
def At_function(measurements, numrows, numcols, num_fourier_masks, masks, padding, dx):
dk_X = 2 * np.pi / (numrows * dx)

im_reconstructed = np.zeros((numrows, numcols), dtype=complex)
measurements = np.reshape(measurements, (numrows // padding, numcols // padding, num_fourier_masks), order='F')

for m in range(num_fourier_masks):
this_mask = np.squeeze(masks[:, :, m])
this_measurements = np.squeeze(measurements[:, :, m])
im_reconstructed += np.conj(this_mask) * np.fft.fftshift(np.fft.ifft2(this_measurements, s=(numrows, numcols))) * numrows * numcols * dk_X * dk_X / (4 * np.pi * np.pi)

pixels = im_reconstructed.ravel(order='F')
return pixels
image-20250407190451927

功能整合

整合上面提到的功能,在下面的代码中,可以完成三个主要的功能:

1
process = ['train', 'test', 'trained_test']
  • 'train':使用随机生成的数据训练模型
  • 'test':不使用神经网络的部分,仅仅验证传播函数的正确性
  • 'trained_test':对训练好的模型进行测试,目前是使用重新随机生成的数据进行测试,接下来还会补充使用FECO导出的电场数据进行测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
if 'train' in process:
# 检查是否有GPU可用
device = torch.device('cuda' if (torch.cuda.is_available() and FLAG_GPU) else 'cpu')

# 生成复数数据
A, y, x = generate_data(n, m)

# 保存测试 A矩阵
file_path = r"E:\PhysenNet_inversion\DATA\process_DATA\test_A.npy"
np.save(file_path, A)
print("已创建并保存test_A.npy数据")

# 初始化模型
model = ComplexSignalRecoveryModel(m, n).to(device)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 训练模型
num_epochs = 1000
for epoch in range(num_epochs):
model.train()
optimizer.zero_grad()

# 生成复数数据
A, y, x = generate_data(n, m, A)
x_real_true = np.real(x)
x_imag_true = np.imag(x)

# 将A拆分为实部和虚部,并展平
A_real = np.real(A).ravel(order='F')
A_imag = np.imag(A).ravel(order='F')
y_flat = y.flatten()

# 转换为PyTorch张量
A_real_tensor = torch.FloatTensor(A_real).to(device)
A_imag_tensor = torch.FloatTensor(A_imag).to(device)
y_tensor = torch.FloatTensor(y_flat).to(device)
x_real_tensor = torch.FloatTensor(x_real_true).to(device)
x_imag_tensor = torch.FloatTensor(x_imag_true).to(device)

x_real_pred, x_imag_pred = model(A_real_tensor, A_imag_tensor, y_tensor)

# 计算实部和虚部的损失
loss_real = criterion(x_real_pred, x_real_tensor)
loss_imag = criterion(x_imag_pred, x_imag_tensor)
total_loss = loss_real + loss_imag

total_loss.backward()
optimizer.step()

if (epoch + 1) % 100 == 0:
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {total_loss.item():.4f}')

# 测试模型
model.eval()
with torch.no_grad():
x_real_pred, x_imag_pred = model(A_real_tensor, A_imag_tensor, y_tensor)
z_hat = x_real_pred.cpu().numpy() + 1j * x_imag_pred.cpu().numpy()

# 计算恢复误差
error = np.linalg.norm(z_hat - x) / np.linalg.norm(x)
error = mean_squared_error(np.real(z_hat), np.real(x)) + mean_squared_error(np.imag(z_hat), np.imag(x))
print(f"Recovery error: {error}")

model_path = r"E:\PhysenNet_inversion\DATA\process_DATA\complex_signal_model.pth"

# 训练完成后保存模型
torch.save(model, model_path)
print(f"模型已保存到 {model_path}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
if 'test' in process:
# 参数初始化
f = 1.65e9
lamda = constants.speed_of_light / f
k0 = 2 * np.pi / lamda
dtheta = np.pi / 180
dphi = np.pi / 180
theta = np.arange(-np.pi / 2, np.pi / 2 + dtheta, dtheta)
phi = np.arange(0, np.pi + dphi, dphi)

NF_data1 = r'E:\组会\其他\实验室历史文件\code\phase retrival\NFdata\NearField3_10X10P25_0P25.efe'
NF_data2 = r'E:\组会\其他\实验室历史文件\code\phase retrival\NFdata\NearField4_10X10P25_0P25.efe'
NF_data3 = r'E:\组会\其他\实验室历史文件\code\phase retrival\NFdata\NearField5_10X10P25_0P25.efe'
dz_values = [0, 1] # 衍射面间距数组
z0, z1, z2 = 3.0 * lamda, 4.0 * lamda, 5.0 * lamda
Reliable_rate = 1

# 加载近场数据
Ex1, Ey1, xd1, yd1, z = load_NF(NF_data1) # Ex_matrix, Ey_matrix, xd, yd, z
Ex2, Ey2, xd2, yd2, z = load_NF(NF_data2)
Ex3, Ey3, xd3, yd3, z = load_NF(NF_data3)

# 转换为1D向量 (考虑numpy存储顺序)
Ey1_1D = Ey1.flatten(order='F') # 使用Fortran列优先顺序匹配MATLAB
Ey2_1D = Ey2.flatten(order='F')
Ey3_1D = Ey3.flatten(order='F')

# 计算采样参数 (添加类型转换确保整数)
M = int(len(xd2)) # x方向采样点数
N = int(len(yd2)) # y方向采样点数
dx = float(xd2[1] - xd2[0]) # x方向采样间隔
dy = float(yd2[1] - yd2[0]) # y方向采样间隔

# 谱域滤波参数
padding = 1
MI = padding * M
NI = padding * N

# 生成波数网格
m = np.arange(-MI // 2, MI // 2)
n = np.arange(-NI // 2, NI // 2)
k_X_Rectangular = 2 * np.pi * m / (MI * dx)
k_Y_Rectangular = 2 * np.pi * n / (NI * dy)

# 计算波数间隔
dk_X = k_X_Rectangular[1] - k_X_Rectangular[0]
dk_Y = k_Y_Rectangular[1] - k_Y_Rectangular[0]

# 生成波数网格
k_Y_Rectangular_Grid, k_X_Rectangular_Grid = np.meshgrid(k_Y_Rectangular, k_X_Rectangular)

# 计算kz
kz = np.sqrt(k0 ** 2 - k_X_Rectangular_Grid ** 2 - k_Y_Rectangular_Grid ** 2 + 0j)

# 生成可靠性区域
W = 0.55
H = 0.428

# 扫描区域尺寸
w = dx * (M - 1)
h = dy * (N - 1)

# 计算可靠性角度
thetax_sure = Reliable_rate * np.arctan(0.5 * (w - W) / z1)
thetay_sure = Reliable_rate * np.arctan(0.5 * (h - H) / z1)

# 定义文件路径
file_path = r"E:\PhysenNet_inversion\DATA\process_DATA\UR.npy"
# 检查文件是否存在
if os.path.isfile(file_path):
print("UR.npy 文件存在")
# 加载文件
UR = np.load(file_path)
print("成功加载 UR.npy 文件")
else:
# 生成滤波矩阵UR
print("UR.npy 文件不存在")
UR = np.zeros((MI, NI), dtype=int)
for i in range(MI):
for j in range(NI):
condition1 = (k_X_Rectangular[i] ** 2 / (k0 * np.sin(thetax_sure)) ** 2 +
k_Y_Rectangular[j] ** 2 / k0 ** 2) < 1.0
condition2 = (k_X_Rectangular[i] ** 2 / k0 ** 2 +
k_Y_Rectangular[j] ** 2 / (k0 * np.sin(thetay_sure)) ** 2) < 1.0
if condition1 and condition2:
UR[i, j] = 1

# 保存文件
np.save(file_path, UR)
print("已创建并保存UR.npy数据")

# 计算 fy1
fy1 = np.fft.fftshift(np.fft.ifft2(Ey2, s=(MI, NI))) * MI * NI * dx * dy
# 计算 fy_Magnitude
fy_Magnitude = 20 * np.log10(np.abs(fy1))
# 计算 fy_H_Magnitude
fy_H_Magnitude = 20 * np.log10(np.abs(fy1 * UR))

# ================== 创建傅里叶掩码 ==================
# 获取网格尺寸
numrows, numcols = k_Y_Rectangular_Grid.shape # 假设k_Y_Rectangular_Grid已定义
num_fourier_masks = 2

isim = np.imag(kz) != 0
kz[isim] = -1 * kz[isim]

# 定义文件路径
file_path = r"E:\PhysenNet_inversion\DATA\process_DATA\masks.npy"
# 检查文件是否存在
if os.path.isfile(file_path):
print("masks.npy 文件存在")
# 加载文件
masks = np.load(file_path)
print("成功加载 masks.npy 文件")
else:
print("masks.npy 文件不存在")
masks = np.zeros((numrows, numcols, num_fourier_masks), dtype=np.complex128)

# ================== 生成衍射掩码 ==================
for i in range(num_fourier_masks):
phase = -1j * kz * dz_values[i] * lamda
masks[:, :, i] = UR * np.exp(phase)

# 保存文件
np.save(file_path, masks)
print("已创建并保存masks.npy数据")

# ================== 验证数据计算 ==================
# 傅里叶变换和预处理
fy2 = np.fft.fftshift(np.fft.ifft2(Ey2, s=(MI, NI))) * MI * NI * dx * dy
x = fy2.flatten() # 展平为一维向量 .ravel(order='F') .flatten()

# b_cal = np.abs(A_function(x, masks, padding, dx)) # 矩阵乘法

# 定义文件路径
file_path = r"E:\PhysenNet_inversion\DATA\process_DATA\A.npy"
# 检查文件是否存在
if os.path.isfile(file_path):
print("A.npy 文件存在")
# 加载文件
A = np.load(file_path)
print("成功加载 A.npy 文件")
else:
print("A.npy 文件不存在")
A = construct_A_matrix(masks, padding, dx)
# 保存文件
np.save(file_path, A)
print("已创建并保存A.npy数据")

b_cal = np.abs(A @ x)

b_real = np.concatenate([np.abs(Ey2_1D.ravel(order='F')), np.abs(Ey3_1D.ravel(order='F'))])

# 误差计算
b_err_1 = np.sqrt(np.sum((np.abs(b_real) - b_cal) ** 2) / np.sum(b_cal ** 2))
b_err_2 = np.abs((b_real - b_cal) / b_cal)

# 重塑误差矩阵
b_err = b_err_2.reshape((numrows // padding, numcols // padding, num_fourier_masks))
E1_err = np.squeeze(b_err[:, :, 0])
E2_err = np.squeeze(b_err[:, :, 1])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
if 'trained_test' in process:
# 检查是否有GPU可用
device = torch.device('cuda' if (torch.cuda.is_available() and FLAG_GPU) else 'cpu')

model_path = r"E:\PhysenNet_inversion\DATA\process_DATA\complex_signal_model.pth"

if os.path.exists(model_path):
print("发现训练模型,加载模型中...")
model = torch.load(model_path).to(device)

# 生成测试数据
A, y, x = generate_data(n, m)

file_path = r"E:\PhysenNet_inversion\DATA\process_DATA\test_A.npy"
if os.path.isfile(file_path):
print("test_A.npy 文件存在")
# 加载文件
A = np.load(file_path)
print("成功加载 test_A.npy 文件")
else:
pass

# 将A拆分为实部和虚部,并展平
A_real = np.real(A).ravel(order='F')
A_imag = np.imag(A).ravel(order='F')
y_flat = y.flatten()

# 转换为PyTorch张量
A_real_tensor = torch.FloatTensor(A_real).to(device)
A_imag_tensor = torch.FloatTensor(A_imag).to(device)
y_tensor = torch.FloatTensor(y_flat).to(device)

# 测试模型
model.eval()
with torch.no_grad():
x_real_pred, x_imag_pred = model(A_real_tensor, A_imag_tensor, y_tensor)
z_hat = x_real_pred.cpu().numpy() + 1j * x_imag_pred.cpu().numpy()

# 计算恢复误差
error = np.linalg.norm(z_hat - x) / np.linalg.norm(x)
error = mean_squared_error(np.real(z_hat), np.real(x)) + mean_squared_error(np.imag(z_hat), np.imag(x))
print(f"Recovery error: {error}")

使用 initWeighted 初始化

使用 Python 对 PhaseRetrival 工具包进行改写,并验证改写代码的正确性,验证思路如下:

  • 使用相同的初始数据,得到初始化的结果,与 PhaseRetrival 工具包计算得到的结果进行对比
  • 把Python计算得到的结果带入到 PhaseRetrival 工具包,使用相同的迭代方法,看是否可以收敛到真实解

代码部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
import warnings
import matplotlib.pyplot as plt
import numpy as np
from scipy.sparse.linalg import LinearOperator, eigs
import os
from scipy import constants

warnings.filterwarnings("ignore")

plt.rcParams['font.sans-serif'] = ['SimHei'] # 显示中文
plt.rcParams['axes.unicode_minus'] = False # 显示负号


# ================== 数据加载 ==================
def load_NF(file_path):
near_Efield = np.loadtxt(file_path)
xn = near_Efield[:, 0]
yn = near_Efield[:, 1]
zn = near_Efield[:, 2]
z = zn[0]

# 提取复数场量
Ex = near_Efield[:, 3] + 1j * near_Efield[:, 4]
Ey = near_Efield[:, 5] + 1j * near_Efield[:, 6]

# 确定网格尺寸
LM = np.where(yn == yn[0])[0]
M = len(LM) # x方向采样点数
N = len(xn) // M # y方向采样点数

# 生成网格坐标
xd = np.linspace(xn.min(), xn.max(), M)
yd = np.linspace(yn.min(), yn.max(), N)

# 重塑场矩阵
Ex_matrix = Ex.reshape(M, N).T # 转置以匹配MATLAB的列优先存储
Ey_matrix = Ey.reshape(M, N).T

return Ex_matrix, Ey_matrix, xd, yd, z


# ================== 参数初始化 ==================
def setup_parameters():
"""初始化所有物理参数和路径"""
params = {
# 物理参数
'f': 1.65e9, # 频率
'dz_values': [0, 1], # 衍射面间距
'z0': 3.0 * constants.speed_of_light / 1.65e9, # 以波长为单位的距离
'Reliable_rate': 0.8,

# 文件路径
'NF_paths': [
r'E:\组会\其他\实验室历史文件\code\phase retrival\NFdata\NearField3_10X10P25_0P25.efe',
r'E:\组会\其他\实验室历史文件\code\phase retrival\NFdata\NearField4_10X10P25_0P25.efe',
r'E:\组会\其他\实验室历史文件\code\phase retrival\NFdata\NearField5_10X10P25_0P25.efe'
],
'UR_path': r"E:\PhysenNet_inversion\DATA\process_DATA\UR.npy",
'masks_path': r"E:\PhysenNet_inversion\DATA\process_DATA\masks.npy",

# 计算参数
'padding': 2,
'num_fourier_masks': 2,
'W': 0.55,
'H': 0.428
}

# 计算波长相关参数
lamda = constants.speed_of_light / params['f']
params.update({
'lamda': lamda,
'k0': 2 * np.pi / lamda,
'dtheta': np.pi / 180,
'dphi': np.pi / 180
})

# 加载近场数据
Ey_list = []
for path in params['NF_paths']:
Ex_matrix, Ey_matrix, xd, yd, z = load_NF(path) # 假设已实现load_NF函数
Ey_list.append(Ey_matrix)

# 设置采样参数
params.update({
'M': len(xd),
'N': len(yd),
'dx': float(xd[1] - xd[0]),
'dy': float(yd[1] - yd[0])
})
params.update({
'MI': params['M'] * params['padding'],
'NI': params['N'] * params['padding'],
'Ey_data': Ey_list
})

# 生成波数网格
m = np.arange(-params['MI'] // 2, params['MI'] // 2)
n = np.arange(-params['NI'] // 2, params['NI'] // 2)
k_X = 2 * np.pi * m / (params['MI'] * params['dx'])
k_Y = 2 * np.pi * n / (params['NI'] * params['dy'])
k_Y_grid, k_X_grid = np.meshgrid(k_Y, k_X)

# 计算kz并处理虚部
kz = np.sqrt(params['k0'] ** 2 - k_X_grid ** 2 - k_Y_grid ** 2 + 0j)
kz[np.imag(kz) != 0] *= -1

# 生成/加载 UR矩阵
if not os.path.exists(params['UR_path']):
generate_UR_matrix(params, k_X, k_Y)
UR = np.load(params['UR_path'])

# 生成/加载 masks
if not os.path.exists(params['masks_path']):
generate_masks(params, UR, kz)
masks = np.load(params['masks_path'])

params.update({
'masks': masks
})

return params


# ================== 辅助函数 UR ==================
def generate_UR_matrix(params, k_X, k_Y):
"""生成可靠性区域矩阵"""
print("生成UR矩阵...")
UR = np.zeros((params['MI'], params['NI']), dtype=int)

# 计算可靠性角度
w = params['dx'] * (params['M'] - 1)
h = params['dy'] * (params['N'] - 1)
thetax = params['Reliable_rate'] * np.arctan(0.5 * (w - params['W']) / params['z0'])
thetay = params['Reliable_rate'] * np.arctan(0.5 * (h - params['H']) / params['z0'])

# 并行化生成 UR
for i in range(params['MI']):
for j in range(params['NI']):
condition1 = (k_X[i] ** 2 / (params['k0'] * np.sin(thetax)) ** 2 +
k_Y[j] ** 2 / params['k0'] ** 2) < 1.0
condition2 = (k_X[i] ** 2 / params['k0'] ** 2 +
k_Y[j] ** 2 / (params['k0'] * np.sin(thetay)) ** 2) < 1.0
if condition1 and condition2:
UR[i, j] = 1

np.save(params['UR_path'], UR)


# ================== 辅助函数 mask ==================
def generate_masks(params, UR, kz):
"""生成傅里叶掩码"""
print("生成masks矩阵...")
masks = np.zeros((params['MI'], params['NI'], params['num_fourier_masks']),
dtype=np.complex128)

for m in range(params['num_fourier_masks']):
phase = -1j * kz * params['dz_values'][m] * params['lamda']
masks[:, :, m] = UR * np.exp(phase)

np.save(params['masks_path'], masks)


# ================== 正向物理传播函数 ==================
def A_function(pixels, masks, padding, dx):
"""A操作符:空间域 -> 截断傅里叶测量值"""
numrows, numcols = masks.shape[:2]
im = pixels.reshape(numrows, numcols)
measurements = np.zeros((numrows // padding, numcols // padding, masks.shape[2]), dtype=np.complex128)

dk_X = 2 * np.pi / (numrows * dx)
scale = (dk_X ** 2) / (4 * np.pi ** 2)

for m in range(masks.shape[2]):
masked = im * masks[:, :, m]
ft = np.fft.fft2(np.fft.ifftshift(masked))
measurements[:, :, m] = ft[:numrows // padding, :numcols // padding] * scale

return measurements.ravel(order='F')


# ================== 反向物理传播函数 ==================
def At_function(measurements, numrows, numcols, num_fourier_masks, masks, padding, dx):
"""At操作符:测量值 -> 空间域重建"""
dk_X = 2 * np.pi / (numrows * dx)
scale = numrows * numcols * (dk_X ** 2) / (4 * np.pi ** 2)

im_recon = np.zeros((numrows, numcols), dtype=complex)
measurements = measurements.reshape(
(numrows // padding, numcols // padding, num_fourier_masks), order='F')

for m in range(num_fourier_masks):
meas_slice = measurements[:, :, m]
full_ft = np.zeros((numrows, numcols), dtype=np.complex128)
full_ft[:numrows // padding, :numcols // padding] = meas_slice
recon = np.fft.fftshift(np.fft.ifft2(full_ft)) * scale
im_recon += np.conj(masks[:, :, m]) * recon

return im_recon.ravel(order='F')


# ================== 验证重建结果 ==================
def validate_results(x2_true, x1_true, params):
# 计算幅值误差
b_cal = np.abs(A_function(x1_true, np.load(params['masks_path']),
params['padding'], params['dx']))
b_real = np.concatenate([np.abs(params['Ey_data'][1]).flatten(order='F'),
np.abs(params['Ey_data'][2]).flatten(order='F')])

err = np.sqrt(np.sum((np.abs(b_real) - b_cal) ** 2) / np.sum(b_cal ** 2))

print(f"物理衍射相对误差: {err:.4e}")


# ================== 初始化函数 ==================
def initWeighted(A, At, b0, n, params, verbose=True):
"""
重加权振幅流(RAF)算法的加权初始化器

参数:
A: function/np.ndarray - 数据矩阵A的函数或矩阵(测量矩阵)
At: function/np.ndarray - A转置的函数或矩阵
b0: np.ndarray - 振幅测量数据向量(|A*x|)
n: int - 待恢复信号长度
verbose: bool - 是否打印过程信息

返回:
x0: np.ndarray - 初始估计向量
"""
psi = b0.copy()
m = len(psi)

# 处理数值矩阵的情况
if isinstance(A, np.ndarray):
n = A.shape[1]
At = lambda x: A.T @ x.reshape(-1, 1)
A = lambda x: A @ x

if verbose:
print(f"Estimating signal of length {n} using a weighted initializer with {m} measurements...")

# 算法参数
gamma = 0.5
card_I = int((3 * m) // 13) # 截取基数

# 步骤1:构建索引集合I
sorted_indices = np.argsort(-psi) # 降序排序的索引
ind = sorted_indices[:card_I]

# 步骤2:构造矩阵Y
R = np.zeros(m)
R[ind] = 1
W = (R * psi) ** gamma

# 定义Y的矩阵向量乘积函数
def Y_matvec(x):
x = x.ravel()
Ax = A(x) # A操作
W_Ax = W * Ax # 应用权重
At_WAx = At(W_Ax) # At操作
return At_WAx.ravel()

# 创建LinearOperator时传入复数类型
Y_operator = LinearOperator((n, n), matvec=Y_matvec, dtype=np.complex128)

# 步骤3:计算主特征向量
vals, vecs = eigs(Y_operator, k=1, which='LR') # 最大实部特征值
# vals, vecs = eigs(At(W * A(psi)), k=1, which='LR') # 最大实部特征值
V = vecs[:, 0]

# 计算缩放系数 alpha
def alpha(x):
Ax = A(x).ravel()
abs_Ax = np.abs(Ax)
numerator = np.dot(abs_Ax, psi)
denominator = np.dot(abs_Ax, abs_Ax)
return numerator / denominator

x0 = V * alpha(V)

if verbose:
print("Initialization finished.")

return x0


# ================== 初始化整流程函数 ==================
def run_full_initialization(params):
x1_true = ((np.fft.fftshift(np.fft.ifft2(params['Ey_data'][1], s=(params['MI'], params['NI'])))
* params['MI'] * params['NI']) * params['dx'] * params['dy']).flatten(order='F')
x2_true = ((np.fft.fftshift(np.fft.ifft2(params['Ey_data'][2], s=(params['MI'], params['NI'])))
* params['MI'] * params['NI']) * params['dx'] * params['dy']).flatten(order='F')

# 设置A/At函数
A = lambda x: A_function(x, params['masks'], params['padding'], params['dx'])
At = lambda y: At_function(y, params['masks'].shape[0], params['masks'].shape[1],
params['num_fourier_masks'], params['masks'],
params['padding'], params['dx'])

# 生成测量数据
x_true = np.hstack((x1_true, x2_true))

# 运行初始化
b0 = np.hstack((np.abs(params['Ey_data'][1]).flatten(order='F'),
np.abs(params['Ey_data'][2]).flatten(order='F')))
x0 = initWeighted(A, At, b0, len(x_true) // params['padding'], params)

# 存储初始化得到的频谱
np.savetxt(r'E:\PhysenNet_inversion\DATA\process_DATA\x0.txt',
np.column_stack((x0.real, x0.imag)), # 将复数拆分为实部虚部两列
fmt='%.18e', # 保持双精度精度
delimiter=' ') # 用空格分隔
np.save(r'E:\PhysenNet_inversion\DATA\process_DATA\x0.npy', x0)

# 计算幅值误差
b_cal = A_function(x0, np.load(params['masks_path']),
params['padding'], params['dx'])
b_real = np.concatenate([params['Ey_data'][1].flatten(order='F'),
params['Ey_data'][2].flatten(order='F')])

err = np.sqrt(np.sum((np.abs(b_real) - np.abs(b_cal)) ** 2) / np.sum(np.abs(b_real) ** 2))

print(f"初始值相对误差: {err:.4e}")

np.save(r'E:\PhysenNet_inversion\DATA\process_DATA\b_real.npy',
b_cal.reshape(params['M'] * params['padding'], params['N']))

# 验证误差
validate_results(x2_true, x1_true, params)


# ================== 测试用例 ==================
if __name__ == "__main__":
# 初始化参数
params = setup_parameters()

# 执行完整流程
run_full_initialization(params)

初始化结果对比

下面的是Python代码得到的初始电场幅值和相位:

image-20250430133803183

下面的是Python代码得到的初始电场幅值和相位:

image-20250430133659004

使用初始化结果进行迭代

image-20250430133548113 image-20250430133522591

image-20250430133611533

初始化方法的适配性

使用相同的初始化方法,对得到的初始值使用 PhaseRetrival 工具包里面的14种迭代算法进行处理。

AF / RAF 算法流程:

image-20250507100804386

Amplitude flow

Iter = 1000 | IterationTime = 19.554 | Resid = 9.727e-05 | Stepsize = 8.659e-02 Image recovery required 1000 iterations (19.554327 secs)

image-20250430183458803 image-20250430183508297

coordinatedescent

Iteration = 1000 | IterationTime = 22.650226 | Residual = 1.109819e-02 Image recovery required 1000 iterations (22.650226 secs)

image-20250430183529361 image-20250430183540599

fienup

Iteration = 1000 | IterationTime = 257.996386 | Residual = 2.458595e-03 Image recovery required 1000 iterations (257.996386 secs)

image-20250430183551555 image-20250430183602660

Gerchberg saxton

Iteration = 1000 | IterationTime = 267.052254 | Residual = 2.397953e-03 Image recovery required 1000 iterations (267.052254 secs)

image-20250430183632560 image-20250430183644294

kaczmarz

Iteration = 1000 | IterationTime = 3.808584 | Residual = 2.201110e-01 Image recovery required 1000 iterations (3.808584 secs)

image-20250430183654697 image-20250430183705731

phasemax

Iteration = 1000 | IterationTime = 9.705762 | Residual = 6.121450e-03 Image recovery required 1000 iterations (9.705762 secs)

image-20250430183717241 image-20250430183727131

phaselamp

Iteration = 1000 | IterationTime = 15.793961 | Residual = 2.648620e+02 Image recovery required 1000 iterations (15.793961 secs)

image-20250430183735448 image-20250430183745592

raf

Iter = 1000 | IterationTime = 14.745 | Resid = 4.072e-05 | Stepsize = 8.634e+02 Image recovery required 1000 iterations (14.745055 secs)

image-20250430183758653 image-20250430183811738

rwf

Iter = 1000 | IterationTime = 14.577 | Resid = 1.713e-04 | Stepsize = 4.141e-01 Image recovery required 1000 iterations (14.576618 secs)

image-20250430183821135 image-20250430183832328

sketchycgm

Iteration = 1000 | IterationTime = 454.735320 | Residual = 9.969303e-01 Image recovery required 1000 iterations (454.735320 secs)

image-20250430183854796 image-20250430183903871

taf

Iter = 1000 | IterationTime = 64.018 | Resid = 4.227e-04 | Stepsize = 9.676e+02 Image recovery required 1000 iterations (64.018318 secs)

image-20250430183913704 image-20250430183922517

twf

有限值不足,无法进行插值。

wirtflow

Iter = 1000 | IterationTime = 13.834 | Resid = 6.174e-05 | Stepsize = 1.598e-06 Image recovery required 1000 iterations (13.833875 secs)

image-20250430183939810 image-20250430183951983

PINN - 物理信息神经网络

PINN - PHYSICS-INFORMED NEURAL NETWORKS

与标准深度学习方法不同,PINN 通过**强制执行控制问题实际物理的偏微分方程 (PDE) **模型的有效性来限制可接受解的空间。

PINN 仅使用一个训练数据集来实现所需的解决方案,从而减轻了替代(即不受物理约束)深度学习方法中所需的大量训练数据集通常带来的负担。

PINN 不需要它预测的逆参数的任何数据,因此它属于无监督学习。PINN 的这些独特功能对于使用测量场数据或正向模拟生成的合成数据解决逆散射问题非常有益。

PINN 在与前向问题相同的基础上求解高度非线性和色散逆问题,方法是简单地在整体损失函数中增加一个额外的损失项,以最小化偏微分方程及其边界条件的残差 [9]。这些问题通常难以用可用的数学公式来解决。

PINN算法

在这里插入图片描述

首先构造一个神经将网络的输出 $\hat{u}(x ; \theta)$ 作为偏微分方程(PDE)解 $u(x)$ 的替代模型。在这里,我们采用前馈神经网络(FNN),它相对简单,但足以应对大多数偏微分方程问题。$\theta$ 是一个包含神经网络中所有需要训练的权重和偏差的向量。接下来的关键步骤是约束神经网络的输出 $\hat{u}$ 使其满足偏微分方程以及数据观测值 $u$。

这是通过构建损失函数来实现的,该损失函数考虑了对应于偏微分方程、边界条件和初始条件以及场观测值的项。具体来说,我们考虑了以下一个定义在区域 $\Omega \subset \mathbb{R}^d$ 上具有未知参数 $\lambda$ 的偏微分方程问题,其中 $x = (x_1, \ldots, x_d)$。

我们考虑的 PDE 问题,实际物理的偏微分方程(PDE)模型:
$$
f\left(\mathbf{x}; \frac{\partial \hat{u}}{\partial x_1}, \ldots, \frac{\partial \hat{u}}{\partial x_d}, \frac{\partial^2 \hat{u}}{\partial x_1 \partial x_1}, \ldots, \frac{\partial^2 \hat{u}}{\partial x_1 \partial x_d}; \ldots; \lambda \right) = 0, \quad \mathbf{x} \in \Omega. \tag{1}
$$

我们将 $\mathcal{T}_i \subset \Omega$ 定义为已知 PDE 解 $u(\mathbf{x})$ 的点集。损失函数定义:

$$
\mathcal{L}(\theta, \lambda) = w_f \mathcal{L}_f(\theta, \lambda; \mathcal{T}_f) + w_i \mathcal{L}_i(\theta, \lambda; \mathcal{T}_i) + w_b \mathcal{L}_b(\theta, \lambda; \mathcal{T}_b), \tag{2}
$$

其中 $w_f$、$w_b$ 和 $w_i$ 为权重,$\mathcal{T}_f$、$\mathcal{T}_i$ 和 $\mathcal{T}_b$ 分别表示来自偏微分方程训练数据集初始条件和边界条件的残差点集合。这里,$\mathcal{T}_f \subset \Omega$ 是一组预定义点,用于衡量神经网络输出 $\hat{u}$ 与 PDE 的匹配程度。$\mathcal{T}_f$ 可以选择为网格点或随机点。

物理约束项:

$$
\mathcal{L}f(\theta, \lambda; \mathcal{T}_f) = \frac{1}{|\mathcal{T}_f|} \sum{\mathbf{x} \in \mathcal{T}_f} \left| f\left( \mathbf{x}; \frac{\partial \hat{u}}{\partial x_1}, \ldots, \frac{\partial \hat{u}}{\partial x_d}, \frac{\partial^2 \hat{u}}{\partial x_1 \partial x_1}, \ldots, \frac{\partial^2 \hat{u}}{\partial x_1 \partial x_d}; \ldots; \lambda \right) \right|_2^2, \tag{3}
$$

数据匹配项(内部点):

$$
\mathcal{L}i(\theta, \lambda; \mathcal{T}_i) = \frac{1}{|\mathcal{T}_i|} \sum{\mathbf{x} \in \mathcal{T}_i} | \hat{u}(\mathbf{x}) - u(\mathbf{x}) |_2^2, \tag{4}
$$

边界条件项:
$$
\mathcal{L}b(\theta, \lambda; \mathcal{T}_b) = \frac{1}{|\mathcal{T}_b|} \sum{\mathbf{x} \in \mathcal{T}_b} | \mathcal{B}(\hat{u}, \mathbf{x}) |_2^2. \tag{5}
$$

我们通过最小化损失函数 $\mathcal{L}(\theta, \lambda)$ 来优化 $\theta$ 和 $\lambda$。需要注意的是,使用 PINNs 的正问题与反问题之间的唯一差别在于,在公式 (2) 中添加了一个额外的损失项 $\mathcal{L}_i$。

模型训练时,不仅要最小化数据误差,还要最小化物理信息误差,确保预测结果符合物理定律。

维度 正向问题 逆向问题
已知信息 完整方程+边界/初始条件 部分方程+观测数据
求解目标 物理量场分布 参数、边界或方程形式
驱动因素 物理方程残差主导 数据拟合误差主导
复杂度 相对较低(单优化网络) 较高(联合优化参数与网络)
架构统一性 物理方程残差项占主导(如80%权重) 数据匹配项权重提升(如50%数据损失+50%物理残差)

PINN通过将物理机制显式嵌入神经网络,为正反问题提供了统一的框架,尤其在数据稀疏或高维场景中展现潜力,但其成功依赖于物理方程的合理建模与优化算法的有效性。

案例说明(热传导方程)
假设真实物理过程服从:
$$
\frac{\partial u}{\partial t} = \alpha \nabla^2 u + f(x,t)
$$

  • 正问题:已知 $\alpha$ 和 $f(x,t)$
    • 网络输出: $u(x,t)$
    • 损失项:PDE残差$ |\frac{\partial u}{\partial t} - \alpha \nabla^2 u - f|^2$ + 边界条件
  • 反问题:未知 $\alpha$ 或$ f(x,t)$
    • 网络输出: $u(x,t)$ + $ \alpha$ (设为可训练标量)
    • 损失项:数据项$ |u_{\text{pred}}-u_{\text{obs}}|^2$​ + 同上的PDE残差

Physics-informed neural networks for inverse problems in nano-optics and metamaterials

《用于纳米光学和超材料中逆问题的物理信息神经网络》、Opt Express、2020.4、陈余瑶,卢璐……

image-20250507101935908

采用物理信息神经网络 (PINN) 范式来解决光子超材料和纳米光学技术中代表性的逆散射问题,超越了传统有效介质理论的限制。

物理信息神经网络 (PINN)是最近开发的一个通用框架,用于求解偏微分方程的正向和逆问题。在本文中,我们提出并开发了 PINN,用于解决与纳米光学和超材料技术直接相关的不同逆散射电磁问题。特别是,我们通过将强大的 PINNs 框架应用于以周期性和非周期性几何形状排列的有限尺寸的散射纳米圆柱体簇,解决了有效介质确定和参数检索的难题。

为了实现目标,使用亥姆霍兹方程约束了物理信息神经网络(PINNs),该方程适用于在 TM 极化激发下具有弱非均匀性的二维(2D)介质:
$$
\nabla^2 E_z(x, y) + \varepsilon_r(x, y) , k_0^2 E_z = 0
$$
其中,$E_z$ 是电场的 $z$ 分量,$\varepsilon_r(x, y)$ 是空间相关的相对介电常数,$k_0 = \frac{2\pi}{\lambda_0}$ 是自由空间中的波数。$\lambda_0$ 是真空中波长。

如图 1(b)示意图所示,手头的问题是通过在有限元方法(FEM)求解散射问题的前向解 $u(x, y)$ 获得的合成数据上训练 PINNs,来反演函数 $\varepsilon(x, y)$,这对应于图 1(a)中的未知参数 $\lambda$。使用在前向散射有限元模拟中使用 PINNs 预测的介电常数空间轮廓所获得的 $\varepsilon(x, y)$验证 PINNs 对介电常数空间轮廓的预测。将得到的电场分布与用于生成训练(合成)散射数据的原始前向有限元模拟的电场分布进行比较。通过计算对应的总场分布的 $L^2$ 误差范数来量化这两个数据集之间的差异。

在本节中,我们训练 PINNs 以预测整个计算域上的介电常数轮廓,因此在这种情况下,我们不需要显式地考虑边界条件的损失项 $\mathcal{T}_b$​。

在所有仿真中,我们使用 150 × 150 的空间分辨率对电场进行采样。所使用的 PINN 架构由一个前馈神经网络组成,该网络具有 4 个隐藏层,每个隐藏层中有 64 个神经元。我们使用了双曲正切激活函数,并选择了 Glorot uniform 方法进行权重初始化。训练的学习率设置为 $10^{-3}$。最后,我们使用 Adam 方法训练神经网络,并训练 150000 次迭代(epoch),损失值达到 $10^{-2}$​ 。

多体与多层结构确定性电磁散射理论及其在通信和遥感中的应用

浙江大学、2025.4、P-ANN 算法反演(物理内嵌人工神经网络)、基于相干电磁散射模型的分层土壤物理神经网络反演算法

结合精确散射模型的物理神经网络反演算法(P-ANN)。这也为确定性散射模型在实际物理应用问题,特别是涉及电磁逆问题。分层土壤辐射亮温建模主要通过基于麦克斯韦方程和分层媒质并矢格林函数以及涨落耗散定理推导的相干模型表达。

分层土壤被动遥感的物理神经网络模型

本文所提出的物理内嵌神经网络模型的核心在于,将物理亮温模型引入到全连接前馈神经网络的损失函数中,以物理约束结果约束双重作用到网络训练和参数反演。本章所有的神经网络构建,训练和测试,均基于MATLAB2023a的深度学习开发库,并遵循与示例类似的风格。P-ANN的框架详细说明如下:

image-20250510230644125

输入层和输出层

多频率(L波段、C波段和X波段)、多极化(水平极化和垂直极化)以及 多角度(30:2:50度) 的亮温作为神经网络的输入层。观测角度从30度到50度以2度为间隔选取,因此输入层总计共66个节点。

如前所述的双层土壤的五个土壤参数(上层湿度,下层湿度,厚度,上层温度,下层温度)作为输出层。

损失函数

由土壤参数预测值与真实值之间的差异($\text{Loss}_0$) 以及其对应的辐射亮温差异($\text{Loss}_p$​)共同构成:
$$
\text{Loss} = \text{Loss}_0 + \text{Loss}_p
$$

$$
\text{Loss}_0 = \frac{1}{N_0} \sum_{i=1}^{N_0} \left( \left| \overline{S}_i^0 - \widetilde{\overline{S}}_i^0 \right| \right),
$$

$$
\text{Loss}p = \frac{1}{N_0} \sum_{i=1}^{N_0} \left( \left| \overline{T}_{bi}^0 - \widetilde{\overline{T}}{bi}^0 \right| \right)
$$

其中,$|\cdot|$ 表示 L2 范数(欧几里得范数)的平方,$N_0$ 为训练数据的样本数。$\overline{S}i^0$ 和 $\tilde{\overline{S}}i^0$ 分别表示第 $i$ 个采样的真实和预测土壤参数向量,维度均为 5。$\overline{T}{bi}^0$ 和 $\tilde{\overline{T}}{bi}^0$ 分别表示与 $\overline{S}_i^0$ 和 $\tilde{\overline{S}}_i^0$ 对应的亮温向量,维度均为 66。需要说明的是,此处使用的变量均为归一化值,以平衡它们在损失函数中的贡献。

训练集,归一化和网络训练

在该范围内随机生成5000组(N0=5000)土壤参数作为训练数据集。随后,通过物理模型计算多频率、多极化和多角度下的对应亮温,并将其作为神经网络的输入用于训练过程。

各参数输入网络训练时,需先对输入和输出值进行归一化处理,以消除数据单位和尺度不一致的影响,归一化公式如下:
$$
T_b^0 = \frac{T_b - 200}{100},
$$

$$
s^0 = \frac{s - s_{low}}{s_{up} - s_{low}}
$$

其中,$s^0$和$T_b^0$分别为归一化后的土壤参数和亮温值,$s_{up}$和$s_{low}$​分别为土壤参数的上限和下限。

综合考虑精度和效率,本文在训练过程中采用 L-BFGS 算法(Limited-memory Broyden-Fletcher-Goldfarb-Shanno 算法)作为网络训练的优化算法。

P-ANN在训练过程中的损失函数收敛曲线整体上训练在150,000次迭代后收敛,步长范数(两次连续迭代之间的步长)和一阶最优性(损失函数梯度的无穷范数)均小于10−5。在训练过程中,本文还附加验证过程用于评估训练过程中是否出现过拟合现象。验证集的选取为,每个土壤参数均匀采样11个数据点,组合形成115组的数据。验证集每200次迭代评估一次,从图中可以看出,验证集的损失函数值持续下降,未出现过拟合问题。

测试结果

训练完成后,在土壤范围内,每个土壤参数均匀采样21个点,将所有可能的参数组合形成总计215组数据作为测试土壤。对于每组测试土壤参数,使用物理模型计算辐射亮温,并将其输入P-ANN模型以预测土壤参数,从而形成测试集。

image-20250511131054875

P-ANN 预测结果与真实值比较如图 7.5 (b)-(d) 所示,结果表明各参数的 P-ANN 反演结果与真实值高度吻合。各反演参数的均方根误差(RMSE)如图中所示。相对而言,与 P-ANN 方法相比,未将物理模型纳入损失函数的传统神经网络的反演性能较差。仅使用预测值与真实值之间的差异(即 Loss0,见公式 7-63)作为损失函数进行训练的网络,经过相同次数的迭代后,最终损失函数值为 0.00229,劣于 P-ANN 方法,如图 7.5 (a) 所示。此外,对于相同的测试集,其预测值与真实值的偏差也较大。特别是下层土壤湿度和土壤厚度的偏差更为明显,如图 7.5 (c) 和 (d) 所示。这两个参数本身由于微波穿透能力问题,就是较为难反演的参数。然而本文提出的 P-ANN 方法由于结合了物理模型在损失函数中,可以有效约束其反演结果

image-20250511133941380

所提出的P-ANN模型在土壤参数范围较小的情况下获得了好的效果。为了处理更大范围土壤参数的反演问题,一种潜在的策略是采用“分而治之”的方法,即将大土壤参数范围划分为较小范围,并为每个范围训练专用的神经网络,以平衡反演精度和训练成本。例如,本节中分别在五个不同的参数范围(如表7.6中的ran1-ran5所示)训练了P-ANN模型,并使用相同数量的测试数据评估其反演效果。土壤湿度范围的设定基于现有广泛报道的全国土壤湿度数据集,土壤厚度设定基于电磁波穿透深度。表7.7展示了每个范围的测试结果。P-ANN模型在ran1-ran3范围内表现较好,而在ran4-ran5范围内表现相对较差,尤其是在下层土壤湿度和厚度的反演结果上。这主要是由于高土壤湿度将对应于更高的介电常数,从而限制电磁波的穿透能力。因此在高湿度场景,本质上是难以反演下层相关参数的。这一结论也与其他文献的研究报道一致。需说明,这并非算法本身的问题,而是电磁物理过程固有约束的结果

image-20250511135455080

物理神经网络初始化的局部优化算法 - P-ANN 初始化的局部优化算法

当考虑采用优化算法解决上述土壤反演问题时,其目标是找到满足以下条件的最优土壤参数$ ilde{s}$:

$$
\chi(\tilde{s}) = \min_{s} \chi(s)
$$
其中,$\chi(s)$是代价函数,定义为:
$$
\chi(s) = \frac{1}{N} \left{ \sum_{\gamma} \left[ \sigma(\gamma) \cdot \left( T_b^{calc}(\gamma,s) - T_b^{ex}(\gamma) \right) \right]^2 \right}
$$

其中,$N$ 是观测通道的数量,在本文多通道观测策略下其值为 66(1 个角度 ×2 种极化 ×3 个频率)。$\gamma$ 代表每个观测通道,$s$ 是土壤参数向量,本文中长度为 5。

$T_b^{calc}(\gamma,s)$ 是通道 $\gamma$ 中对应于土壤参数向量 $s$ 的计算亮温,而 $T_b^{ex}(\gamma)$ 是通道 $\gamma$ 中的实测亮温,在本文中通过假设的真实土壤 $s$,使用物理模型仿真获得。$\sigma(\gamma)$ 是权重系数,用于加权不同通道的贡献。在本文中,每个通道的权重系数设为 1。归一化土壤参数 $\tilde{s}^n$ 与归一化真实值 $s^n$​ 之间的 RMSE 值被用来衡量反演性能。

考虑到大面积数据反演的高效性,局部优化算法是常用的快速优化算法。但在使用局部优化算法时,初始值的选取是至关重要的,算法从该初始值,采用梯度下降等技术(如本文中使用的信赖域算法)进行优化。如在上文表 7.4 所展示的两种不同初始值对应的局部优化反演结果,反演过程的最终结果显著依赖于初始值的选取随机的初始值选取通常会导致不准确的反演结果,而相对精确的初始值通常能促进优化的快速收敛并产生高精度的反演结果

image-20250511141308617

正如人脑通常也提供不精确的推断,我们在上一节的测试也表明,P-ANN的输出事实上也并不是完全准确的,如图7.5所示。然而,这种不精确的结果是相对接近真实值的。因此,通过将P-ANN的结果与局部优化算法结合,将P-ANN的部分准确的输出作为局部优化算法的初始值输入,就可以促使局部优化算法在快速梯度下降过程中获得更精确的解。基于这一考虑,我们进一步提出了一种以P-ANN输出为初始值的局部优化方法,其框架如图7.6所示。首先将观测辐射亮温(本文采用仿真获得)输入P-ANN以获得相对准确的土壤参数$s^p$反演结果,然后将其作为信赖域算法的初始值$s^0$进行局部优化反演,以获得更精确的最终反演输出$s^0$。

物理神经网络的可靠性评估

如何确定反演结果的可能误差,或者说反演结果的可靠性,也是在反演中的一个关键的问题。传统的ANN模型通常无法提供此类特征,可以想象,当使用传统ANN反演得到土壤分层参数后,该结果与真实分层参数间误差是无从考证和评估的。然而,物理内嵌的神经网络可以自然地解决这一问题。因为有清晰确定性物理模型后,反演得到的土壤参数可以进一步被计算为对应的微波亮温观测值,其与输入的微波亮温观测之间是可以计算误差的。只需要我们事先拥有土壤参数误差和微波亮温误差之间的统计模型关系,那么依照得到的微波亮温误差就可以评判反演土壤参数结果的误差和可靠性。而这一误差关系统计模型是可以在P-ANN的测试过程中统计获得的。

如第7.3.3节所述,当使用测试数据测试训练好的神经网络时,可以估计每组反演土壤参数与真实值之间的差异;同时由于引入了物理模型,可以进一步计算反演土壤对应的辐射亮温,从而得到与亮温观测值相比的亮温误差,进而形成土壤参数误差和亮温误差的对应统计关系。这也正是P-ANN相较于传统ANN的另一独特优势。基于大量测试数据,最终可以统计土壤参数误差与对应亮温误差之间的关系。从而,如上所述,在实际反演过程中,亮温误差易于获取,可以根据统计误差关系以一定的置信水平从亮温误差估计土壤参数反演结果的可靠性。

Physics-informed neural networks: A deep learning framework for solving forward and inverse problems involving nonlinear partial differential equations

《物理信息神经网络:用于求解涉及非线性偏微分方程的正向和逆向问题的深度学习框架》、Phys、M. Raissi, P. Perdikaris, and G. E. Karniadakis、2019

介绍了物理信息神经网络 – 经过训练以解决监督学习任务的神经网络,同时遵守由一般非线性偏微分方程描述的任何给定物理定律。在这项工作中,我们展示了我们在解决两大类问题的背景下的发展:数据驱动的解决方案和数据驱动的偏微分方程发现。通过流体、量子力学、反应扩散系统和非线性浅水波传播中的一系列经典问题证明了所提出的框架的有效性。

在这种小数据制度中,绝大多数最先进的机器学习技术(例如,深度/卷积/递归神经网络)都缺乏稳健性,无法提供任何收敛性保证。乍一看,训练深度学习算法以从几个(可能非常高维的)输入和输出数据对中准确识别非线性映射的任务似乎充其量是天真的。

对于许多与物理和生物系统建模有关的情况,存在大量先验知识,这些知识目前尚未在现代机器学习实践中使用。无论是支配系统随时间变化的动力学的原则性物理定律,还是一些经过经验验证的规则或其他领域的专业知识,这些先验信息可以充当正则化代理,将可接受解的空间限制在可管理的大小(例如,在可压缩流体动力学问题中,通过丢弃任何违反质量守恒原则的非现实流动解)。反过来,将此类结构化信息编码到学习算法中会导致算法看到的数据的信息内容放大,使其能够快速将自己引导到正确的解决方案,即使只有几个训练示例可用时也能很好地泛化。

定义 $f(t, x)$ 由方程的左侧给出:

$$
f := u_t + N[u]
$$
通过深度神经网络近似 $u(t, x)$ 来。这个假设与方程 $(3)$ 一起产生了一个物理学神经网络 $f(t, x)$ 。这个网络可以通过应用链式规则来使用自动微分来区分函数的组成,并且具有与表示 $u(t, x)$ 的网络相同的参数,尽管由于微分算子 $N$ 的作用而具有不同的激活函数。神经网络 $u(t, x)$ 和 $f(t, x)$ 之间的共享参数可以通过最小化均方误差损失来学习
$$
MSE = MSE_u + MSE_f
$$

$$
MSE_u = 1/N_u ∑_{i=1}^{N_u} |u(t_i^u, x_i^u) - u^i|^2
$$

$$
MSE_f = 1/N_f ∑_{i=1}^{N_f} |f(t_i^f, x_i^f)|^2.
$$

这里,${(t_i^u, x_i^u, u^i)}_{i=1}^{N_u}$ 表示 $u(t, x)$ 上的初始边界训练数据, ${t_i^f, x_i^f}_{i=1}^{N_f}$ 指定 $f(t, x)$ 的搭配点。损失 $MSE_u$ 对应于初始和边界数据,而 $MSE_f$ 在一组有限的配置点处强制执行方程 $(2)$ 施加的结构。尽管在以前的研究中已经探索了使用物理定律约束神经网络的类似想法,但在这里,我们使用现代计算工具重新审视它们,并将它们应用于由瞬态非线性偏微分方程描述的更具挑战性的动力学问题。

Quantifying total uncertainty in physics-informed neural networks for solving forward and inverse stochastic problems

《量化物理信息神经网络中的总不确定性,以解决正向和逆向随机问题》、Dongkun Zhang, Lu Lu、Journal of Computational Physics、2019

物理信息神经网络 (PINN) 最近成为一种数值求解偏微分方程 (PDE) 的替代方法,无需构建复杂的网格,而是使用简单的实现。特别是,除了解的深度神经网络 (DNN) 之外,还考虑了一个辅助 DNN,它表示 PDE 的残差。然后将残差与给定解数据中的失配相结合,以公式化损失函数。

但由于数据中固有的随机性或 DNN 架构的近似限制,缺乏解决方案的不确定性量化。这里提出了一种新方法,目的是为 DNN 赋予不确定性的两个来源的不确定性量化,即参数不确定性和近似不确定性。

当微分方程中的参数表示为随机过程时,我们首先考虑参数不确定性。多个 DNN 旨在通过使用来自稀疏传感器的随机数据来学习其解的任意多项式混沌 (aPC) 扩展的模态函数。然后,我们可以使用经过训练的 DNN 非常有效地从新的传感器测量值中做出预测。

频谱域滤波的作用

Spectral Domain Filtering(频谱域滤波) 是一种在频域中对信号进行处理的技术,通过傅里叶变换将信号从时域/空域转换到频域,然后针对特定频率成分进行增强或抑制。常见的操作包括低通滤波(保留低频)、高通滤波(保留高频)或带通滤波(保留特定频段)。

主要作用

  • 去噪与平滑:抑制高频噪声(如传感器噪声或干扰)。
  • 特征提取:增强信号中的关键频率成分(如边缘检测中的高频信号)。
  • 带宽约束:限制信号的频率范围以符合物理系统的实际带宽(如光学系统的衍射极限)。
  • 先验知识嵌入:在迭代优化中引入物理约束(如支持域、非负性等)。

在电磁场相位恢复中的必要性

相位恢复的目标是从仅有的强度测量(如光强或电场模值)中重建丢失的相位信息(如相干衍射成像、全息术)。其核心挑战在于该问题是病态(ill-posed)的,即解不唯一或不稳定。频谱域滤波在此过程中至关重要,原因如下:

抑制噪声与伪影

  • 实际测量中噪声通常存在于高频部分(如散粒噪声、热噪声)。
  • 迭代恢复过程中,噪声会被反复放大,导致解偏离真实值。
  • 频谱域滤波(如低通滤波)可有效抑制高频噪声,提升重建质量。

施加物理约束

  • 电磁场传播受限于物理系统带宽(如光学系统的数值孔径限制)。
  • 超出系统截止频率的成分可能是非物理的,需通过滤波去除(如带通滤波匹配系统传递函数)。

稳定迭代算法

  • 相位恢复算法(如Gerchberg-Saxton、Fienup算法)需在空域和频域交替迭代,施加约束。
  • 频谱域滤波作为频域约束的一部分,可防止算法发散,加速收敛。

解决病态性问题

  • 相位恢复的数学本质是求解非线性方程,需额外约束(如稀疏性、平滑性)。
  • 频谱域滤波通过限制解的频域特性,缩小解空间,使问题适定(well-posed)。

典型应用示例

在相干衍射成像(CDI)中:

  1. 实验测得衍射图样(频域强度)。

  2. 通过傅里叶变换迭代更新相位,每次迭代中:

    频域滤波:限制频率范围与系统带宽一致,去除超出截止频率的成分。

  3. 未滤波的迭代可能导致高频噪声主导,最终重建失败。

在相位恢复算法中频谱域滤波的作用

在相位恢复算法(如 Gerchberg-Saxton(GS)算法Fienup算法)中,“空域和频域交替迭代,施加约束”是核心操作。这一过程通过反复在两个域(空域和频域)之间传递信号,并施加不同的物理约束,逐步逼近真实的相位解。

角域(角谱)的基本概念:

角域,通常称为角谱,是指通过傅里叶变换将空间分布的电场或光场分解为不同传播方向的平面波的集合。每个平面波的传播方向由对应的空间频率决定,这些方向的角度分布即构成角谱。

平面波的波矢 $k=(k_x,k_y,k_z)$决定了波的传播方向。根据几何关系,传播方向的角度 $θ$(例如相对于z轴的倾斜角)满足:

$$\sin\theta = \frac{\sqrt{k_x^2 + k_y^2}}{k} \quad$$​

因此,空间频率 (kx,ky)直接对应传播角度。傅里叶变换后的每个点 (kx,ky) 对应一个特定方向的平面波分量,其幅度和相位由傅里叶系数给出。

通过傅里叶变换,复杂的波场被分解为无数个不同方向的平面波叠加。角谱即表示这些平面波的权重分布,反映了波场中各个传播方向的重要性。

角域的物理意义

  • 平面波分解
    任何复杂的波(如通过狭缝的光波)都可视为不同方向传播的平面波的叠加。角谱描述了这些平面波的组成,例如:

    • 一束平行光对应角谱中单一方向的峰值;
    • 经过小孔衍射的光波,角谱会扩展,显示多方向的传播分量。
  • 传播特性分析
    角谱可以解释波的传播行为。例如:

    • 远场(夫琅禾费衍射):远场衍射图样直接对应角谱的强度分布,即不同方向的光强;
    • 近场(菲涅尔衍射):角谱通过 相位修正 仍能描述波的传播演化。
  • 实际应用

    • 光学成像:透镜的聚焦效果可通过角谱中 高频成分(大角度传播的波)的截断来解释(如数值孔径限制);
    • 无线通信:天线辐射方向图的分析本质上是角谱的体现。

示例说明

假设一束光垂直照射一个狭缝:

  • 空间域:狭缝处的电场分布$ E(x) $是矩形函数;
  • 傅里叶变换:得到频域的$ sinc $函数$\tilde{E}(k_x)$,对应角谱;
  • 物理意义:$sinc$ 函数的零点间隔表明哪些传播角度(空间频率)被抑制,对应衍射图样的暗条纹。

关键公式

$$ \text{角谱} \propto \tilde{E}(k_x, k_y), \quad \text{传播角度} \propto \arctan\left(\frac{\sqrt{k_x^2 + k_y^2}}{k_z}\right).$$

模态函数失去正交性

从近场测量中获得远场方向图的经典方法是采用模态展开方法,该方法基于以下事实:适当表面上的场可以表示为一组正交函数的线性组合。

在这种方法中,在测量截断表面上的电场或磁场的切向分量后,利用模态函数的正交性获得模态系数。但是,这些函数在原始表面上是正交的,但在截断表面上不是正交的,因此这些系数的计算是错误的。

这个误差在计算的远场模式中被转化为两种不同的效应。一方面,在某个光谱区域之外,结果完全不可靠。另一方面,由于截断表面边缘处的测量场不连续,该区域内会出现错误的波纹。

因此,整个计算模式始终受到误差的影响,并且无法定义误差完全为零的区域。但是,光谱可靠区域的概念通常用于指代误差不可忽略但较低的区域。其中使用几何光学来计算用于定义可靠区域的最大有效角。

在截断表面上,模态函数失去正交性的原因可以从数学和物理两个层面来理解:

1. 正交性的数学本质

正交性是指两个函数在某个定义域(如无限大表面)上的内积(积分)为零。对于无限大表面上的模态函数(如平面波、球面谐波等),其正交性成立的条件是:
$$
\int_{-\infty}^{\infty} f_m(\mathbf{r}) f_n^*(\mathbf{r}) , d\mathbf{r} = \delta_{mn}
$$
其中 $\delta_{mn}$ 是克罗内克函数。然而,当表面被截断为有限区域时:

  • 积分范围受限:积分仅在有限区域 $S_{\text{truncated}}$ 内进行,即:
    $$
    \int_{S_{\text{truncated}}} f_m(\mathbf{r}) f_n^*(\mathbf{r}) , d\mathbf{r} \neq \delta_{mn}.
    $$
  • 函数截断后的交叠:截断导致模态函数的“尾部”被切除,原本在无限域中正交的函数可能在有限域内部分重叠,导致内积不为零。

2. 物理机制:场的截断效应

  • 无限表面与有限表面的对比
    在无限大表面上,模态函数(如平面波)的传播方向覆盖全部空间,不同模态之间通过相位差异自然正交。但在有限截断表面上:
    • 边缘不连续性:截断会导致场在边缘处突然截止(类似信号处理中的矩形窗截断),产生高频分量(吉布斯现象)。
    • 能量泄漏:截断后的场无法完整包含原模态的全部能量,不同模态的泄漏能量在有限区域内交叠,破坏了正交性。

3. 具体例子:平面波展开

以平面波展开为例:

  • 无限表面上的正交性
    不同方向传播的平面波 $e^{j\mathbf{k}_m \cdot \mathbf{r}} $和 $e^{j\mathbf{k}n \cdot \mathbf{r}}$ 在无限域内正交:
    $$
    \int
    {-\infty}^{\infty} e^{j(\mathbf{k}_m - \mathbf{k}_n) \cdot \mathbf{r}} , d\mathbf{r} = \delta(\mathbf{k}_m - \mathbf{k}_n).
    $$
  • 截断表面上的正交性破坏
    若积分限制在有限区域 $[-L/2, L/2]$,结果变为:
    $$
    \int_{-L/2}^{L/2} e^{j(k_m - k_n)x} dx = L \cdot \text{sinc}\left( \frac{(k_m - k_n)L}{2} \right),
    $$
    当 $k_m \neq k_n $ 时,积分值不为零,导致正交性丢失。

4. 实际影响

  • 模态系数计算错误
    正交性是求解线性组合系数的关键工具。若函数在截断表面不正交,通过内积计算的系数将包含其他模态的“污染”分量。
  • 远场方向图误差
    错误的系数会引入虚假波纹(截断噪声)和不可靠的高频分量,最终导致远场方向图失真。

总结
截断表面上模态函数失去正交性的根本原因在于:

  1. 数学层面:有限积分域破坏了无限域正交性的严格条件。
  2. 物理层面:场的截断导致能量泄漏和模态交叠,类似于信号处理中的频谱泄漏现象。

这一现象是近场测量中误差的主要来源,也是定义“光谱可靠区域”的理论基础。