Hi everyone, I am trying to improve my codeβs efficiency. I locate a line that might be too expensive, but I failed to figure out how to improve it.
In brief, I need to save the inverse of an N by N covariance matrix of N realizations of a Gaussian process with Matern covariogram. In the following code chunk, I define a function Maternlu to compute the Matern covariance between two points separated by distance x. coords records the coordinates of all realizations and matrix M stores what I need.
using Distributions
using LinearAlgebra
using Distances
using SpecialFunctions
function Maternlu(x; Ξ½=1.0, Ο = 6.0, Ο2 = 1.0)
# Matern function
if x == 0.0
Ο2
else
a = Ο * x;
Ο2 * 2^(1 - Ξ½) / gamma(Ξ½) * (a)^Ξ½ * besselk(Ξ½, a)
end
end
N = 400;
coords = rand(2, N); #coordinates of observations
M = inv(cholesky(Symmetric(Maternlu.(
UpperTriangular(pairwise(Euclidean(), coords, dims = 2)),
Ξ½ = 1.0, Ο = 6.0))));
The last line requires around 6MB but the matrix M itself only requires 1.2MB. Moreover, I only need the upper-triangular part of M .
@benchmark M = inv(cholesky(Symmetric(Maternlu.(
UpperTriangular(pairwise(Euclidean(), coords, dims = 2)),
Ξ½ = 1.0, Ο = 6.0))))
BenchmarkTools.Trial: 63 samples with 1 evaluation.
Range (min β¦ max): 39.158 ms β¦ 83.481 ms β GC (min β¦ max): 0.00% β¦ 3.10%
Time (median): 80.759 ms β GC (median): 0.00%
Time (mean Β± Ο): 80.414 ms Β± 5.400 ms β GC (mean Β± Ο): 0.27% Β± 0.73%
βββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
39.2 ms Histogram: frequency by time 83.3 ms <
Memory estimate: 6.10 MiB, allocs estimate: 79819.
I think the ideal code will be like
preallocate an N by N upper-triangular matrix
overwrite it with the upper-triangular part of the distance matrix among the N locations
overwrite it with the upper-triangular part of the Matern covariance
use function like cholesky! to overwrite this matrix with the Cholesky decomposition
overwrite it with the upper-triangular part of the inverse of the Matern covariance
But I donβt know how to preallocate an upper-triangular matrix and it seems to me that the current cholesky! doesnβt support an upper-triangular matrix input. Any thoughts about what I can do to improve my code? Many thanks!!!
I see. No wonder I can hardly improve my code by using a triangular matrix. Thanks a lot!
I wrote a function MaternluU!
function MaternluU!(A::AbstractMatrix; Ξ½=1.0, Ο = 6.0, Ο2 = 1.0)
# Matern function
m,n = size(A)
@boundscheck m == n || throw(BoundsError())
a = 0.0;
@inbounds for j in 1:n
for i in 1:j
if A[i,j] == 0.0
A[i,j] = Ο2
else
a = A[i,j] * Ο;
A[i,j] = Ο2 * 2^(1 - Ξ½) / gamma(Ξ½) * (a)^Ξ½ * besselk(Ξ½, a)
end
end
end
return A
end
It slightly improves the memory cost.
function computeM(coords)
M = pairwise(Euclidean(), coords, dims = 2);
MaternluU!(M);
M = Symmetric(M, :U);
M = cholesky(M);
M = inv(M);
end
@benchmark computeM(coords)
BenchmarkTools.Trial: 62 samples with 1 evaluation.
Range (min β¦ max): 38.149 ms β¦ 86.843 ms β GC (min β¦ max): 0.00% β¦ 1.96%
Time (median): 82.426 ms β GC (median): 0.00%
Time (mean Β± Ο): 81.841 ms Β± 5.785 ms β GC (mean Β± Ο): 0.21% Β± 0.64%
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ βββββ β
38.1 ms Histogram: frequency by time 85.9 ms <
Memory estimate: 4.88 MiB, allocs estimate: 79809.
M = pairwise(Euclidean(), coords, dims = 2);
MaternluU!(M);
M = Symmetric(M, :U);
cholesky!(M)
I can overwrite the upper triangular part of M with the Cholesky decomposition of the covariance matrix. But how can I compute the inverse of the covariance matrix efficiently with the last matrix? Many thanks!
Do you actually need the inverse?
Note that you can do X / M or M \ Y, which are equivalent to X * inv(M) and inv(M) * Y, respectively, but much faster and more numerically stable.
Also, see ldiv! and rdiv! for inplace versions.
Also, logdet should work as well, and be far faster.
EDIT:
I really donβt reccomend it, but LinearAlgebra.inv!(cholesky!(M)) should work.
Also, if you have an Intel CPU (or even an AMD CPU), using MKL is likely to speed these operations up by quite a bit.
Yes, I need it to construct a sequence of matrices in my algorithm. Here is part of my code where I need this inverse. The inverse matrix is invR_nk and I need it to construct the chol_inv_M in a for loop.
Terrific! Thank you so much. I think that is what I want. Now the memory cost drops from 6.1MB to 2.44MB.
function computeM(coords)
M = pairwise(Euclidean(), coords, dims = 2);
MaternluU!(M);
M = Symmetric(M, :U);
LinearAlgebra.inv!(cholesky!(M))
end
@benchmark computeM(coords)
BenchmarkTools.Trial: 60 samples with 1 evaluation.
Range (min β¦ max): 80.800 ms β¦ 87.584 ms β GC (min β¦ max): 0.00% β¦ 3.06%
Time (median): 84.594 ms β GC (median): 0.00%
Time (mean Β± Ο): 84.614 ms Β± 1.068 ms β GC (mean Β± Ο): 0.14% Β± 0.59%
ββββ ββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
80.8 ms Histogram: frequency by time 86.8 ms <
Memory estimate: 2.44 MiB, allocs estimate: 79805.