我画大饼说这个周末就要改完本科论文的,结果 pyart
的线性规划部分的函数越看越迷惑,写一篇帮自己梳理一下。
pyart 中的线性规划计算逻辑 所有相关的函数都在 pyart.correct.phase_proc
中,直接使用的线性规划函数有两个,phase_proc_lp
和 phase_proc_lp_gf
。这两个似乎差不多,先解析第一个,再细说差异。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 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 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): if slc.stop - slc.start < ncpts or slc.start < ncpts: x_ma.mask[slc.start - 1 : slc.stop + 1 ] = True c = 0 except TypeError: c = 1 x_ma.mask = True except AttributeError: c = 1 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_matrix
和 construct_B_vectors
分别构造了进行线性规划处理需要的两个参数矩阵,最后依照给定的参数带入不同的 solver
进行求解。这里需要注意的是,进行线性规划时其中求解 KDP 用到的 5 点差分卷积滤波器为 [0.2, 0.1, 0, -0.1, -0.2]
,该滤波器对二次函数可以精确求出其导数,但是三次和四次的不行,五次及以上我没有验证。
1 2 3 4 5 6 7 8 9 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 来。
Comments