pyart中关于线性规划部分代码的疑问

Python

This article was last updated on <span id="expire-date"></span> days ago, the information described in the article may be outdated.

我画大饼说这个周末就要改完本科论文的,结果 pyart 的线性规划部分的函数越看越迷惑,写一篇帮自己梳理一下。

pyart 中的线性规划计算逻辑

所有相关的函数都在 pyart.correct.phase_proc 中,直接使用的线性规划函数有两个,phase_proc_lpphase_proc_lp_gf。这两个似乎差不多,先解析第一个,再细说差异。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 第一部分
# parse the field parameters
if refl_field is None:
refl_field = get_field_name("reflectivity")
if ncp_field is None:
ncp_field = get_field_name("normalized_coherent_power")
if rhv_field is None:
rhv_field = get_field_name("cross_correlation_ratio")
if phidp_field is None:
phidp_field = get_field_name("differential_phase")
if kdp_field is None:
kdp_field = get_field_name("specific_differential_phase")
if unf_field is None:
unf_field = get_field_name("unfolded_differential_phase")

函数的第一部分,根据函数的里参数的设置,取得后面会用到的数据在其 Radar 类中存储的名称。

1
2
3
4
5
6
7
8
# 第二部分
# prepare reflectivity field
refl = copy.deepcopy(radar.fields[refl_field]["data"]) + offset
is_low_z = (refl) < low_z
is_high_z = (refl) > high_z
refl[np.where(is_high_z)] = high_z
refl[np.where(is_low_z)] = low_z
z_mod = refl

函数的第二部分,取出反射率因子 Z,然后给其加上一个 offset,这个 offset 似乎是对反射率因子的修正。需要注意的是,后面 pyart 会通过判断高度来决定哪些数据会进行线性规划处理,哪些不会,因此这个 offset 需要谨慎设置 (pyart 认为高于某个高度后降水粒子会变成冰晶为主,可以对数据进行线性规划的前提就是降水区的粒子要成椭球型,即雨滴)。将超出一定范围的值设置为边界值 (默认最高54),这是由于 pyart 将高于 54 的粒子视为冰雹。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 第三部分
# unfold Phi_DP
if debug:
print("Unfolding")
my_unf = get_phidp_unf(
radar,
ncp_lev=min_ncp,
rhohv_lev=min_rhv,
debug=debug,
ncpts=2,
doc=None,
sys_phase=sys_phase,
nowrap=nowrap,
overide_sys_phase=overide_sys_phase,
refl_field=refl_field,
ncp_field=ncp_field,
rhv_field=rhv_field,
phidp_field=phidp_field,
)
my_new_ph = copy.deepcopy(radar.fields[phidp_field])
my_unf[:, -1] = my_unf[:, -2]
my_new_ph["data"] = my_unf
radar.fields.update({unf_field: my_new_ph})

函数的第三部分理论上对 $\phi_{DP}$ 进行了退折叠,其实并没有。深究一下这个 get_phidp_unf 函数就会发现,里面的处理包括:系统相位 (system phase) 的计算、非气象回波的剔除、小回波的剔除以及无效数据的插值。并且这里的小回波剔除有着严重的漏洞,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 小回波的剔除
x_ma = ma.masked_where(notmeteo, my_phidp[radial, :])
try:
ma.notmasked_contiguous(x_ma)
for slc in ma.notmasked_contiguous(x_ma):
# so trying to get rid of clutter and small things that
# should not add to phidp anyway
if slc.stop - slc.start < ncpts or slc.start < ncpts:
x_ma.mask[slc.start - 1 : slc.stop + 1] = True
c = 0
except TypeError: # non sequence, no valid regions
c = 1 # ie do nothing
x_ma.mask = True
except AttributeError:
# sys.stderr.write('No Valid Regions, ATTERR \n ')
# sys.stderr.write(myfile.times['time_end'].strftime('%Y-%m-%dT%H:%M:%SZ') + '\n')
# print x_ma
# print x_ma.mask
c = 1 # also do nothing
x_ma.mask = True

这里注意第 5 行的循环,里面利用 np.ma.notmasked_contiguous 函数寻找连续的数据序列,并判断每个数据序列是起始位置及其长度,若其起始位置和长度的其中一项小于 ncpts,则将其剔除。这里的漏洞是,其并未判断数据序列之间的距离,若出现连续小回波的情况 (每个数据序列之间间隔1个单位) 则会将这些小回波全部删除 (当然这是我理论上的看法,我并没有拿到足够多类型的雷达资料进行验证)。

所以整个 get_phidp_unf 函数并没有涉及到 $\phi_{DP}$ 的退折叠。

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
# 第四部分
for sweep in range(len(radar.sweep_start_ray_index["data"])):
if debug:
print("Doing ", sweep)
end_gate, start_ray, end_ray = det_process_range(radar, sweep, fzl, doc=15)
start_gate = 0

A_Matrix = construct_A_matrix(
len(radar.range["data"][start_gate:end_gate]), St_Gorlv_differential_5pts
)

B_vectors = construct_B_vectors(
phidp_mod[start_ray:end_ray, start_gate:end_gate],
z_mod[start_ray:end_ray, start_gate:end_gate],
St_Gorlv_differential_5pts,
dweight=self_const,
coef=coef,
)

weights = np.ones(phidp_mod[start_ray:end_ray, start_gate:end_gate].shape)

nw = np.bmat([weights, np.zeros(weights.shape)])

if LP_solver == "pyglpk":
mysoln = LP_solver_pyglpk(
A_Matrix, B_vectors, nw, really_verbose=really_verbose
)
elif LP_solver == "cvxopt":
mysoln = LP_solver_cvxopt(A_Matrix, B_vectors, nw)
elif LP_solver == "cylp":
mysoln = LP_solver_cylp(
A_Matrix, B_vectors, nw, really_verbose=really_verbose
)
elif LP_solver == "cylp_mp":
mysoln = LP_solver_cylp_mp(
A_Matrix, B_vectors, nw, really_verbose=really_verbose, proc=proc
)
else:
raise ValueError("unknown LP_solver:" + LP_solver)

proc_ph["data"][start_ray:end_ray, start_gate:end_gate] = mysoln

函数的第四部分开始对每一个 sweep 进行循环进行线性规划处理。construct_A_matrixconstruct_B_vectors 分别构造了进行线性规划处理需要的两个参数矩阵,最后依照给定的参数带入不同的 solver 进行求解。这里需要注意的是,进行线性规划时其中求解 KDP 用到的 5 点差分卷积滤波器为 [0.2, 0.1, 0, -0.1, -0.2],该滤波器对二次函数可以精确求出其导数,但是三次和四次的不行,五次及以上我没有验证。

1
2
3
4
5
6
7
8
9
# 第五部分
# prepare output
sobel = 2.0 * np.arange(window_len) / (window_len - 1.0) - 1.0
sobel = sobel / (abs(sobel).sum())
sobel = sobel[::-1]
gate_spacing = (radar.range["data"][1] - radar.range["data"][0]) / 1000.0
kdp = scipy.ndimage.convolve1d(proc_ph["data"], sobel, axis=1) / (
(window_len / 3.0) * 2.0 * gate_spacing
)

函数的第五部分使用另外一个变长的滤波器来求 KDP,该滤波器取决于用户输入的窗口长度,默认为 35。但是经实测生成的滤波器计算出的结果并不是原函数的导数,到目前为止,我也不清楚为什么其可以算出 KDP 来。

Author: Syize

Permalink: https://blog.syize.cn/2023/03/22/pyart-lp/

本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Syizeのblog

Comments