目录
矢量化运算
通过循环遍历所有指定点来解决kriging系统
使用了移动窗口技术来减少计算
Ordinary Kriging"的插值方法
def _exec_vector(self, a, bd, mask):
"""Solves the kriging system as a vectorized operation. This method
can take a lot of memory for large grids and/or large datasets."""
npt = bd.shape[0]
n = self.X_ADJUSTED.shape[0]
zero_index = None
zero_value = False
# use the desired method to invert the kriging matrix
if self.pseudo_inv:
a_inv = P_INV[self.pseudo_inv_type](a)
else:
a_inv = scipy.linalg.inv(a)
if np.any(np.absolute(bd) <= self.eps):
zero_value = True
zero_index = np.where(np.absolute(bd) <= self.eps)
b = np.zeros((npt, n + 1, 1))
b[:, :n, 0] = -self.variogram_function(self.variogram_model_parameters, bd)
if zero_value and self.exact_values:
b[zero_index[0], zero_index[1], 0] = 0.0
b[:, n, 0] = 1.0
if (~mask).any():
mask_b = np.repeat(mask[:, np.newaxis, np.newaxis], n + 1, axis=1)
b = np.ma.array(b, mask=mask_b)
x = np.dot(a_inv, b.reshape((npt, n + 1)).T).reshape((1, n + 1, npt)).T
zvalues = np.sum(x[:, :n, 0] * self.Z, axis=1)
sigmasq = np.sum(x[:, :, 0] * -b[:, :, 0], axis=1)
return zvalues, sigmasq
方法_exec_vector
将kriging系统作为矢量化操作进行求解。它接收三个参数:a
,bd
和mask
。
变量a
表示kriging矩阵,它是一个大小为n
的正方形矩阵,表示n
个数据点之间的协方差。变量bd
是一个数组,其形状为(npt,)
,其中包含npt
个预测点与构建kriging系统所使用的n
个数据点之间的距离。变量mask
是一个形状为(npt,)
的布尔值数组,指示每个预测点是否被屏蔽。
该方法首先使用由self.pseudo_inv
和self.pseudo_inv_type
指定的所需方法计算kriging矩阵a
的逆。然后,它构造了kriging系统的右侧向量b
。如果bd
中任何距离小于或等于self.eps
,则相应的b
值将设置为0(除非self.exact_values
为False,在这种情况下该值将保持不变)。最后,该方法通过将a
的逆与b
相乘来计算kriging系统的解solution
,并返回每个预测点的预测值zvalues
和方差sigmasq
。
请注意,由于使用了矢量化操作,因此该方法在处理大网格和/或大数据集时可能会使用大量内存。
def _exec_loop(self, a, bd_all, mask):
"""Solves the kriging system by looping over all specified points.
Less memory-intensive, but involves a Python-level loop."""
npt = bd_all.shape[0]
n = self.X_ADJUSTED.shape[0]
zvalues = np.zeros(npt)
sigmasq = np.zeros(npt)
# use the desired method to invert the kriging matrix
if self.pseudo_inv:
a_inv = P_INV[self.pseudo_inv_type](a)
else:
a_inv = scipy.linalg.inv(a)
for j in np.nonzero(~mask)[
0
]: # Note that this is the same thing as range(npt) if mask is not defined,
bd = bd_all[j] # otherwise it takes the non-masked elements.
if np.any(np.absolute(bd) <= self.eps):
zero_value = True
zero_index = np.where(np.absolute(bd) <= self.eps)
else:
zero_index = None
zero_value = False
b = np.zeros((n + 1, 1))
b[:n, 0] = -self.variogram_function(self.variogram_model_parameters, bd)
if zero_value and self.exact_values:
b[zero_index[0], 0] = 0.0
b[n, 0] = 1.0
x = np.dot(a_inv, b)
zvalues[j] = np.sum(x[:n, 0] * self.Z)
sigmasq[j] = np.sum(x[:, 0] * -b[:, 0])
return zvalues, sigmasq
该方法_exec_loop
通过循环遍历所有指定点来解决kriging系统。这种方法对内存的需求较低,但涉及Python级别的循环。
变量a
表示kriging矩阵,它是一个大小为n
的正方形矩阵,表示n
个数据点之间的协方差。变量bd_all
是一个数组,其形状为(npt, n)
,其中包含了npt
个预测点与构建kriging系统所使用的所有n
个数据点之间的距离。变量mask
是一个形状为(npt,)
的布尔值数组,指示每个预测点是否被屏蔽。
该方法首先初始化一个具有零值的zvalues
和sigmasq
数组,分别用于存储每个预测点的预测值和方差。然后,它使用由self.pseudo_inv
和self.pseudo_inv_type
指定的所需方法计算kriging矩阵a
的逆。接下来,对于没有被屏蔽的每个预测点,该方法计算右侧向量b
并使用a
的逆来计算kriging系统的解solution
。最后,该方法将预测值和方差存储在相应的数组中,并返回这些数组。
请注意,由于使用循环遍历所有点,因此该方法对内存的需求较低。但是,由于具有Python级别的循环,因此它可能比_exec_vector
方法慢得多。
def _exec_loop_moving_window(self, a_all, bd_all, mask, bd_idx):
"""Solves the kriging system by looping over all specified points.
Less memory-intensive, but involves a Python-level loop."""
import scipy.linalg.lapack
npt = bd_all.shape[0]
n = bd_idx.shape[1]
zvalues = np.zeros(npt)
sigmasq = np.zeros(npt)
for i in np.nonzero(~mask)[
0
]: # Note that this is the same thing as range(npt) if mask is not defined,
b_selector = bd_idx[i] # otherwise it takes the non-masked elements.
bd = bd_all[i]
a_selector = np.concatenate((b_selector, np.array([a_all.shape[0] - 1])))
a = a_all[a_selector[:, None], a_selector]
if np.any(np.absolute(bd) <= self.eps):
zero_value = True
zero_index = np.where(np.absolute(bd) <= self.eps)
else:
zero_index = None
zero_value = False
b = np.zeros((n + 1, 1))
b[:n, 0] = -self.variogram_function(self.variogram_model_parameters, bd)
if zero_value and self.exact_values:
b[zero_index[0], 0] = 0.0
b[n, 0] = 1.0
x = scipy.linalg.solve(a, b)
zvalues[i] = x[:n, 0].dot(self.Z[b_selector])
sigmasq[i] = -x[:, 0].dot(b[:, 0])
return zvalues, sigmasq
该方法_exec_loop_moving_window
通过循环遍历所有指定点来解决kriging系统。这种方法对内存的需求较低,但涉及Python级别的循环。与_exec_loop
方法不同,该方法使用了移动窗口技术来减少计算量。
变量a_all
是一个大小为(n + 1, n + 1)
的矩阵数组,其中包含所有数据点之间的协方差和距离。变量bd_all
是一个数组,其形状为(npt, n)
,其中包含了npt
个预测点与构建kriging系统所使用的所有n
个数据点之间的距离。变量mask
是一个形状为(npt,)
的布尔值数组,指示每个预测点是否被屏蔽。变量bd_idx
是一个形状为(npt, m)
的整数数组,其中包含了每个预测点的最近邻索引。
该方法首先初始化一个具有零值的zvalues
和sigmasq
数组,分别用于存储每个预测点的预测值和方差。然后,对于没有被屏蔽的每个预测点,该方法使用移动窗口技术选择与该点最近邻的数据点,并构造kriging系统的矩阵和右侧向量。最后,该方法使用LAPACK库函数来求解kriging系统,并将预测值和方差存储在相应的数组中,并返回这些数组。
请注意,由于使用循环遍历所有点,因此该方法对内存的需求较低。但是,由于具有Python级别的循环,因此它可能比_exec_vector
方法慢得多。
def execute(
self,
style,
xpoints,
ypoints,
mask=None,
backend="vectorized",
n_closest_points=None,
):
if self.verbose:
print("Executing Ordinary Kriging...\n")
if style != "grid" and style != "masked" and style != "points":
raise ValueError("style argument must be 'grid', 'points', or 'masked'")
if n_closest_points is not None and n_closest_points <= 1:
# If this is not checked, nondescriptive errors emerge
# later in the code.
raise ValueError("n_closest_points has to be at least two!")
xpts = np.atleast_1d(np.squeeze(np.array(xpoints, copy=True)))
ypts = np.atleast_1d(np.squeeze(np.array(ypoints, copy=True)))
n = self.X_ADJUSTED.shape[0]
nx = xpts.size
ny = ypts.size
a = self._get_kriging_matrix(n)
这是一个Python函数的定义,名为execute
,它包含6个参数:style
、 xpoints
、 ypoints
、 mask
、 backend
和 n_closest_points
。函数主要用于执行一种叫做"Ordinary Kriging"的插值方法。
函数中进行了一些参数合法性检查,以确保传入的参数正确。其中,如果传入的style
参数既不是"grid"也不是"masked"也不是"points",则会raise一个ValueError异常。如果传入的n_closest_points
小于等于1,则也会raise一个ValueError异常。
接下来,对xpoints
和ypoints
进行了转换和处理,然后调用了一个名为_get_kriging_matrix
的方法,返回一个矩阵a,该矩阵将在接下来的计算中使用。
if style in ["grid", "masked"]:
if style == "masked":
if mask is None:
raise IOError(
"Must specify boolean masking array when style is 'masked'."
)
if mask.shape[0] != ny or mask.shape[1] != nx:
if mask.shape[0] == nx and mask.shape[1] == ny:
mask = mask.T
else:
raise ValueError(
"Mask dimensions do not match specified grid dimensions."
)
mask = mask.flatten()
npt = ny * nx
grid_x, grid_y = np.meshgrid(xpts, ypts)
xpts = grid_x.flatten()
ypts = grid_y.flatten()
elif style == "points":
if xpts.size != ypts.size:
raise ValueError(
"xpoints and ypoints must have "
"same dimensions when treated as "
"listing discrete points."
)
npt = nx
else:
raise ValueError("style argument must be 'grid', 'points', or 'masked'")
这段代码主要是根据传入的style
参数不同,对一些变量进行初始化。如果style
为"grid"或者"masked",则会进行下面的处理:
style
是"masked",则需要传入一个boolean类型的遮罩数组mask
,否则就会raise一个IOError异常。mask
的行数和列数与ny
和nx
不相等,则需要对mask
进行转置。最终将mask
按行展平为一个一维数组。xpoints
和ypoints
生成网格点坐标,将xpoints
和ypoints
按行展平为两个一维数组。如果style
为"points",则需要确保xpoints
和ypoints
的大小一致,即必须表示相同数量的离散点。
如果style
既不是"grid"也不是"masked"也不是"points",则会raise一个ValueError异常。
if self.coordinates_type == "euclidean":
xpts, ypts = _adjust_for_anisotropy(
np.vstack((xpts, ypts)).T,
[self.XCENTER, self.YCENTER],
[self.anisotropy_scaling],
[self.anisotropy_angle],
).T
# Prepare for cdist:
xy_data = np.concatenate(
(self.X_ADJUSTED[:, np.newaxis], self.Y_ADJUSTED[:, np.newaxis]), axis=1
)
xy_points = np.concatenate(
(xpts[:, np.newaxis], ypts[:, np.newaxis]), axis=1
)
elif self.coordinates_type == "geographic":
# In spherical coordinates, we do not correct for anisotropy.
# Also, we don't use scipy.spatial.cdist, so we do not have to
# format the input data accordingly.
pass
这段代码主要是对输入的xpts
和ypts
进行坐标系变换。如果原始数据的坐标系为欧几里得坐标系(self.coordinates_type == "euclidean"
),则调用一个名为_adjust_for_anisotropy
的函数,对xpts
和ypts
进行修正, 使他们适应各向异性(anisotropy)。修正后的xpts
,ypts
会被赋值回去,同时将训练数据和待插值的点的坐标都拼接到一个数组中。
如果该数据的坐标系为地理坐标系(self.coordinates_type == "geographic"
),则略过这一步骤。
if style != "masked":
mask = np.zeros(npt, dtype="bool")
c_pars = None
if backend == "C":
try:
from .lib.cok import _c_exec_loop, _c_exec_loop_moving_window
except ImportError:
print(
"Warning: failed to load Cython extensions.\n"
" See https://github.com/GeoStat-Framework/PyKrige/issues/8 \n"
" Falling back to a pure python backend..."
)
backend = "loop"
except:
raise RuntimeError("Unknown error in trying to load Cython extension.")
c_pars = {
key: getattr(self, key)
for key in [
"Z",
"eps",
"variogram_model_parameters",
"variogram_function",
"exact_values",
"pseudo_inv",
"pseudo_inv_type",
]
}
这段代码首先初始化一个一维布尔数组mask
,在后面的计算中将被用于确定哪些点需要进行插值。
接下来,如果指定了使用Cython后端(backend == "C"
),则会尝试导入名为_c_exec_loop
和_c_exec_loop_moving_window
的函数。如果成功,则将变量c_pars
初始化为一个字典,包含了模型参数、半方差函数等信息。这些变量将被传递给Cython扩展模块以加速计算。若无法加载Cython扩展,将打印警告,并使用纯Python后端(backend = "loop"
)。如果出现其他错误,则会raise一个RuntimeError异常。
if n_closest_points is not None:
if self.coordinates_type == "geographic":
# To make use of the KDTree, we have to convert the
# spherical coordinates into three dimensional Euclidean
# coordinates, since the standard KDTree cannot handle
# the periodicity.
# Do the conversion just for the step involving the KDTree:
lon_d = self.X_ADJUSTED[:, np.newaxis] * np.pi / 180.0
lat_d = self.Y_ADJUSTED[:, np.newaxis] * np.pi / 180.0
xy_data = np.concatenate(
(
np.cos(lon_d) * np.cos(lat_d),
np.sin(lon_d) * np.cos(lat_d),
np.sin(lat_d),
),
axis=1,
)
lon_p = xpts[:, np.newaxis] * np.pi / 180.0
lat_p = ypts[:, np.newaxis] * np.pi / 180.0
xy_points = np.concatenate(
(
np.cos(lon_p) * np.cos(lat_p),
np.sin(lon_p) * np.cos(lat_p),
np.sin(lat_p),
),
axis=1,
)
from scipy.spatial import cKDTree
tree = cKDTree(xy_data)
bd, bd_idx = tree.query(xy_points, k=n_closest_points, eps=0.0)
这段代码根据传入的n_closest_points
对数据进行剪裁,只保留最近的一些点进行计算。如果n_closest_points
不为空,则首先判断坐标系是否为地理坐标系。如果是,则需要将地理坐标转换成三维欧几里得坐标来使用KDTree,进而找到距离待插值点最近的一些训练点。如果不是,则直接使用原始数据中的欧几里得坐标。
接下来,使用scipy库中的cKDTree函数构建一个KDTree,并使用query方法找到距离待插值点最近的k个训练点。其中bd表示距离,bd_idx表示最近的k个点在原始数据中的索引。
if self.coordinates_type == "geographic":
# Between the nearest neighbours from Euclidean search,
# calculate the great circle distance using the standard method:
x_points = np.tile(xpts[:, np.newaxis], (1, n_closest_points))
y_points = np.tile(ypts[:, np.newaxis], (1, n_closest_points))
bd = core.great_circle_distance(
x_points, y_points, self.X_ADJUSTED[bd_idx], self.Y_ADJUSTED[bd_idx]
)
if backend == "loop":
zvalues, sigmasq = self._exec_loop_moving_window(a, bd, mask, bd_idx)
elif backend == "C":
zvalues, sigmasq = _c_exec_loop_moving_window(
a,
bd,
mask.astype("int8"),
bd_idx.astype(int),
self.X_ADJUSTED.shape[0],
c_pars,
)
else:
raise ValueError(
"Specified backend {} for a moving window "
"is not supported.".format(backend)
)
else:
if self.coordinates_type == "euclidean":
bd = cdist(xy_points, xy_data, "euclidean")
elif self.coordinates_type == "geographic":
bd = core.great_circle_distance(
xpts[:, np.newaxis],
ypts[:, np.newaxis],
self.X_ADJUSTED,
self.Y_ADJUSTED,
)
这段代码根据前面找到的距离最近的k个训练点,通过移动窗口计算插值结果。如果坐标系为地球坐标系(self.coordinates_type == "geographic"
),则需要计算两点之间的大圆距离(即地球上两个点间的最短弧线长度),然后将其转换为欧几里得距离(在投影坐标系下)。如果使用纯Python后端(backend == "loop"
),则调用_exec_loop_moving_window
函数计算插值结果。如果使用Cython后端(backend == "C"
),则调用名为_c_exec_loop_moving_window
的函数进行计算。如果指定的后端不支持,则会raise一个ValueError异常。
如果数据的坐标系为欧几里得坐标系(self.coordinates_type == "euclidean"
),则直接使用cdist函数计算待插值点与训练点之间的欧几里得距离,并传给移动窗口函数进行计算。
if backend == "vectorized":
zvalues, sigmasq = self._exec_vector(a, bd, mask)
elif backend == "loop":
zvalues, sigmasq = self._exec_loop(a, bd, mask)
elif backend == "C":
zvalues, sigmasq = _c_exec_loop(
a, bd, mask.astype("int8"), self.X_ADJUSTED.shape[0], c_pars
)
else:
raise ValueError(
"Specified backend {} is not supported for "
"2D ordinary kriging.".format(backend)
)
这是Python中的一个条件语句,它检查变量“backend”的值。根据其值,调用不同的函数来执行某些计算。
如果“backend”等于“vectorized”,则使用参数“a”、“bd”和“mask”调用“_exec_vector”函数。
如果“backend”等于“loop”,则使用相同的参数调用“_exec_loop”函数。
如果“backend”等于“C”,则使用其他参数(例如“self.X_ADJUSTED.shape [0]”和“c_pars”)调用“_c_exec_loop”函数。
如果“backend”具有任何其他值,则引发ValueError,指示不支持指定的后端进行2D普通克里金插值。
if style == "masked":
zvalues = np.ma.array(zvalues, mask=mask)
sigmasq = np.ma.array(sigmasq, mask=mask)
if style in ["masked", "grid"]:
zvalues = zvalues.reshape((ny, nx))
sigmasq = sigmasq.reshape((ny, nx))
return zvalues, sigmasq
这是一个Python函数,它有三个参数: zvalues, sigmasq和style。
首先,它检查变量style的值。如果style等于"masked",则使用NumPy中的np.ma.array函数创建带有掩码的数组,并将其分配给zvalues和sigmasq。
接下来,如果style的值是"masked"或"grid",则通过reshape函数将zvalues和sigmasq重新构造为ny行、nx列的二维数组。
最后,函数返回重构后的zvalues和sigmasq作为元组。