Incorrect answer from Monte Carlo integration - r

How can I approximate the integral of [x^4 * sin(x)]/ [exp(1)^(x/5)] (0 to +Inf) with Monte Carlo method in R?
What I tried to do is
set.seed(666)
func1 <- function(x)
{
(x^4 * sin(x))/exp(1)^(x/5)
}
n <- 1000000
x <- rexp(n, 0.2)
f <- func1(x)
E <- mean(f)
but the result of E is not right

If you're going to sample from exponential, it shouldn't be used again in the function.
From code
set.seed(32345)
func <- function(x) { (x^4 * sin(x)) }
n <- 10000000
x <- rexp(n, 0.2)
f <- func(x)
E <- mean(f)
I'm getting the answer
[1] 13.06643
UPDATE
It fluctuates, and fluctuates badly.
Lest first start with the right answer which according to Mathematica is equal to
4453125/371293 = 11.9936.
I transformed integral from
I = ∫ dx exp(-x/5) x4 sin(x)
using substitution y=x/5 to
I = 55 Γ(5) ∫ dy exp(-y) y5-1 / Γ(5) sin(5*y)
Everything but sin(5*y) is normalized gamma distribution, which we will use to sample, and sin(5*y) will be our function to compute mean value.
And used following trick together with large number of samples: I split calculation of positive values and negative values. It helps if you have fluctuating answer with values canceling each other. I did calculation in batches as well. Gamma function of 5 is just 4! (factorial)
Code
set.seed(32345)
N <- 10000000 # number of samples per batch
NN <- 640 # number of batches
pos <- rep(0, NN) # positive values
neg <- rep(0, NN) # negative values
for(k in 1:NN) { # loop over batches
y <- rgamma(N, shape=5, scale=1)
f <- sin(5.0 * y)
pnf <- ifelse(f > 0.0, f, 0.0)
pos[k] <- mean(pnf)
pnf <- ifelse(f < 0.0, -f, 0.0)
neg[k] <- mean(pnf)
print(k)
}
mean(pos)
sd(pos)/sqrt(NN)
mean(neg)
sd(neg)/sqrt(NN)
5*5*5*5*5*4*3*2*(mean(pos) - mean(neg))
Output
> mean(pos)
[1] 0.3183912
> sd(pos)/sqrt(NN)
[1] 4.749269e-06
>
> mean(neg)
[1] 0.3182223
> sd(neg)/sqrt(NN)
[1] 5.087734e-06
>
> 5*5*5*5*5*4*3*2*(mean(pos) - mean(neg))
[1] 12.67078
You could see that we really compute difference of two very close values, this is why it is hard to get convergence. It took a bit over 20 minutes to compute on my Xeon workstation.
And with different seed=12345
> mean(pos)
[1] 0.3183917
> sd(pos)/sqrt(NN)
[1] 4.835424e-06
>
> mean(neg)
[1] 0.3182268
> sd(neg)/sqrt(NN)
[1] 4.633129e-06
>
> 5*5*5*5*5*4*3*2*(mean(pos) - mean(neg))
[1] 12.36735

In the following I deliberately don't set a random seed.
As I mentioned in my comments, there are at least two introductory Q & A on Monte Carlo integration on Stack Overflow:
Wrong result when doing simple Monte Carlo integration in R
Monte Carlo integration using importance sampling given a proposal function
Both explained how to get Monte Carlo estimate, but forgot about the standard error of the estimate. It just turns out that Monte Carlo estimation has extremely slow convergence rate on your function.
It is commonly known that Monte Carlo integration has an O(1 / sqrt(N)) convergence rate, where N is the sample size and O() is the big O notation. However, the constant behind that big O can be very large for some functions, so the realistic convergence rate may be much much slower.
Your functions could be defined in at least two ways:
## direct definition
f <- function (x) x^4 * sin(x) * exp(-x/5)
## using gamma distribution; see ?rgamma
g <- function (x) sin(x) * 5 ^ 5 * gamma(5) * dgamma(x, 5, 1/5)
curve(f, from = 0, to = 100)
curve(g, add = TRUE, col = 2)
The 1st Q & A explained how to compute Monte Carlo integration using uniformly distributed samples. Your function f or g is almost zero beyond x = 200, so integration on [0, +Inf) is effectively on [0, 200]. The following function would return you the integration and its standard error:
MCI1 <- function (n) {
x <- runif(n, 0, 200)
y <- 200 * f(x)
c(mean.default(y), sqrt(var(y) / n))
}
Another way is via importance sampling, as explained in the 2nd Q & A. Here gamma distribution is used as proposal distribution (as Ben Bolker suggested).
MCI2 <- function (n) {
x <- rgamma(n, 5, 0.2)
y <- sin(x) * 75000
c(mean.default(y), sqrt(var(y) / n))
}
Now let's check the convergence rate.
n <- seq(1000, by = 5000, length = 100)
tail(n)
#[1] 471000 476000 481000 486000 491000 496000
b1 <- sapply(n, MCI1)
b2 <- sapply(n, MCI2)
For uniform sampling, we have
par(mfrow = c(1, 2))
plot(b1[1, ], main = "estimate")
plot(b1[2, ], main = "standard error")
b1[, (ncol(b1) - 5):ncol(b1)]
# [,1] [,2] [,3] [,4] [,5] [,6]
#[1,] 115.1243 239.9631 55.57149 -325.8631 -140.3745 78.61126
#[2,] 181.0025 179.9988 178.99367 178.2152 177.2193 175.31446
For gamma sampling, we have
par(mfrow = c(1, 2))
plot(b2[1, ], main = "estimate")
plot(b2[2, ], main = "standard error")
b2[, (ncol(b2) - 5):ncol(b2)]
# [,1] [,2] [,3] [,4] [,5] [,6]
#[1,] -100.70344 -150.71536 24.40841 -49.58032 169.85385 122.81731
#[2,] 77.22445 76.85013 76.53198 76.03692 75.69819 75.25755
Whatever method it is, note how big the standard error is (compared with the estimate itself), and how slow it reduces.
It is much easier to use numerical integration (not surprising for integrating univariate functions):
integrate(f, 0, 200)
#11.99356 with absolute error < 0.0012
## trapezoidal rule
200 * mean.default(f(seq(0, 200, length = 10000)))
#[1] 11.99236
In the trapezoidal rule, even if only 1e+4 evenly spaced sampling points are taken, the integration is close enough to the truth.
Remark
Monte Carlo integration would have a less struggle if we do integration on a more restricted domain. From the figure of f or g, we see that this is an oscillating function. And actually, it crosses x-axis with a period of pi. Let's consider an integration on [lower, upper].
MCI3 <- function (n, lower, upper) {
x <- runif(n, lower, upper)
y <- (upper - lower) * f(x)
c(mean.default(y), sqrt(var(y) / n))
}
a1 <- sapply(n, MCI3, lower = 0, upper = pi)
a2 <- sapply(n, MCI3, lower = pi, upper = 2 * pi)
a3 <- sapply(n, MCI3, lower = 2 * pi, upper = 3 * pi)
a4 <- sapply(n, MCI3, lower = 3 * pi, upper = 4 * pi)
a1[, (ncol(a1) - 5):ncol(a1)]
# [,1] [,2] [,3] [,4] [,5] [,6]
#[1,] 17.04658711 16.97935808 17.01094302 17.02117843 16.96935285 16.99552898
#[2,] 0.02407643 0.02390894 0.02379678 0.02368683 0.02354298 0.02342799
a2[, (ncol(a2) - 5):ncol(a2)]
# [,1] [,2] [,3] [,4] [,5]
#[1,] -406.5646843 -404.9633321 -405.4300941 -405.4799659 -405.8337416
#[2,] 0.3476975 0.3463621 0.3442497 0.3425202 0.3409073
# [,6]
#[1,] -405.8628741
#[2,] 0.3390045
a3[, (ncol(a3) - 5):ncol(a3)]
# [,1] [,2] [,3] [,4] [,5] [,6]
#[1,] 1591.539911 1592.280780 1594.307951 1591.375340 1593.171500 1591.648529
#[2,] 1.197469 1.190251 1.183095 1.177079 1.172049 1.165667
a4[, (ncol(a4) - 5):ncol(a4)]
# [,1] [,2] [,3] [,4] [,5]
#[1,] -3235.561677 -3239.147235 -3241.532097 -3238.421556 -3238.667702
#[2,] 2.336684 2.321283 2.311647 2.300856 2.286624
# [,6]
#[1,] -3237.043068
#[2,] 2.279032

Related

R: how to compute the mean and covariance of a truncated normal distribution

I'm interested in finding the mean and covariance of a truncated normal random vector. Suppose Y is a vector containing [Y1 Y2 Y3]. Y follows a multivariate normal distribution with the following mean and covariance:
mu <- c(0.5, 0.5, 0.5)
sigma <- matrix(c( 1, 0.6, 0.3,
0.6, 1, 0.2,
0.3, 0.2, 2), 3, 3)
The truncation region is the set of Ys such that AY >= 0. For instance,
A <- matrix(c(1, -2, -0.5, 1.5, -2, 0, 3, -1, -1, 4, 0, -2), byrow = TRUE, nrow = 4)
> A
[,1] [,2] [,3]
[1,] 1.0 -2 -0.5
[2,] 1.5 -2 0.0
[3,] 3.0 -1 -1.0
[4,] 4.0 0 -2.0
For the following draw of Y, it does not satisfy AY >= 0:
set.seed(3)
Y <- rmvnorm(n = 1, mean = mu, sigma = sigma)
> all(A %*% as.matrix(t(Y)) >= 0)
[1] FALSE
But for other draws of Y, they will satisfy AY >= 0, and I want to find the mean and covariance of those Ys that satisfy AY >= 0.
There are existing packages in R that compute the mean and covariance of a truncated normal distribution. For example, mtmvnorm from the tmvtnorm package:
library(tmvtnorm)
mtmvnorm(mu, sigma, lower = ???, upper = ???)
However, the truncation set that I have, i.e, set of Ys that satisfy AY >= 0, cannot be described by just lower and upper bounds. Is there another way to R to compute the mean and covariance of a truncated normal?
You had correct understanding (or maybe noticed) that this is NOT truncated multivariate normal distribution. You have AY>=0 as a linear constraint over Y, rather than simple element-wise lower/upper bounds.
If you are not a math guy, i.e., pursuing explicit solutions of mean and covariance, I guess a straightforward and efficient way is using Monte Carlo simulation.
More specifically, you can presume a sufficient large N to generate big enough set of samples Y and then filter out the samples that satisfy the constraint AY>=0. In turn, you can compute mean and covariance over the selected samples. An attempt is given as below
N <- 1e7
Y <- rmvnorm(n = N, mean = mu, sigma = sigma)
Y_h <- subset(Y, colSums(tcrossprod(A, Y) >= 0) == nrow(A))
mu_h <- colMeans(Y_h)
sigma_h <- cov(Y_h)
and you will see
> mu_h
[1] 0.8614791 -0.1365222 -0.3456582
> sigma_h
[,1] [,2] [,3]
[1,] 0.5669915 0.29392671 0.37487421
[2,] 0.2939267 0.36318397 0.07193513
[3,] 0.3748742 0.07193513 1.37194669
Another way follows the similar idea, but we can presume the set size of selected samples, i.e., N samples Y all should make AY>=0 stand. Then we can use while loop to do this
N <- 1e6
Y_h <- list()
nl <- 0
while (nl < N) {
Y <- rmvnorm(n = N, mean = mu, sigma = sigma)
v <- subset(Y, colSums(tcrossprod(A, Y) >= 0) == nrow(A))
nl <- nl + nrow(v)
Y_h[[length(Y_h) + 1]] <- v
}
Y_h <- head(do.call(rbind, Y_h), N)
mu_h <- colMeans(Y_h)
sigma_h <- cov(Y_h)
and you will see
> mu_h
[1] 0.8604944 -0.1364895 -0.3463887
> sigma_h
[,1] [,2] [,3]
[1,] 0.5683498 0.29492573 0.37524248
[2,] 0.2949257 0.36352022 0.07252898
[3,] 0.3752425 0.07252898 1.37427521
Note: The advantage of the second option is that, it gives you the sufficient large number of selected Y_h as you want.

R: How to sample from the geometric space outside a 4D hyper ellipse?

Original title (in-precise): How to find all vectors that satisfy a 4-unknown non-linear inequalities?
The question is to find a space C, so that any 4x1 vector x in C satisfies:
t(x) %*% t(Q) %*% Q %*% x > a,
in which Q is a 100 x 4 matrix I already know, and a is a positive constant.
I tried to find the solution from packages such as ellipse, rootSolve, and bvpSolve. But I can't reach a suitable solution.
Any idea or solution will be sincerely appreciated.
Remark: the algorithm below can be used to sample from the surface / manifold of a 4D hyper ellipse, or its inner space, too.
I have changed your question title. It is impossible to list all solutions, although such space has simple mathematical representation. We can at our best sample from such space.
Transformation from Ellipse to Sphere
Here is some mathematics based on Cholesky factorization. Alternatively, consider symmetric Eigen decomposition, and I have a demonstration / comparison between these two at Obtain vertices of the ellipse on an ellipse covariance plot (created by car::ellipse), with nice figures for the geometry.
Since Q is known, R is known. The following R code gets R:
R <- chol(crossprod(Q))
y is from the hypersphere with radius greater than sqrt(a). If we can sample y from such space, we can then map it to x by solving a triangular system:
x <- backsolve(R, y)
Sampling of y
We can use n-sphere coordinates to parametrize such space. For 4D space, we have:
The following R function samples n y-vectors from such space. Because of finite representation of floating point numbers, we can't have infinity radius but .Machine$double.xmax at the best. But we also use an optional argument rmax, if we want a more restricted radius.
ry <- function (n, rmin, rmax = NA) {
if (is.na(rmax)) rmax <- .Machine$double.xmax
if (rmin > rmax) stop("larger `rmax` expected!")
r <- runif(n, rmin, rmax)
phi1 <- runif(n, 0, pi)
phi2 <- runif(n, 0, 2 * pi)
phi3 <- runif(n, 0, 2 * pi)
matrix(c(r * cos(phi1),
r * sin(phi1) * cos(phi2),
r * sin(phi1) * sin(phi2) * cos(phi3),
r * sin(phi1) * sin(phi2) * sin(phi3)),
nrow = 4L, byrow = TRUE, dimnames = list(paste0("y", 1:4), NULL))
}
Try some examples:
## radius between 4 and 10
set.seed(0); ry(5, 4, 10)
# [,1] [,2] [,3] [,4] [,5]
#y1 7.5594886 -5.31049687 -6.1388372 -3.5991830 -3.728597
#y2 5.1402945 0.47936481 0.4799181 -2.5085948 -6.480402
#y3 0.2614002 -1.68833263 -0.1950092 -5.9975328 -4.213166
#y4 -2.0859078 0.02440839 -0.9452077 0.3052708 3.954674
## radius between 4 and "inf"
set.seed(0); ry(5, 4)
# [,1] [,2] [,3] [,4] [,5]
#y1 1.299100e+308 -4.531902e+307 -6.588856e+307 -4.983772e+307 -6.442420e+307
#y2 8.833607e+307 4.090831e+306 5.150993e+306 -3.473640e+307 -1.119710e+308
#y3 4.492167e+306 -1.440799e+307 -2.093047e+306 -8.304756e+307 -7.279678e+307
#y4 -3.584637e+307 2.082977e+305 -1.014498e+307 4.227070e+306 6.833046e+307
I have chosen to use each column than each row as a sample, to make matrix computation easier later.
Transforming y to x
Now assume we have
set.seed(0); Q <- matrix(runif(10 * 4), 10L, 4L)
We get R
R <- chol(crossprod(Q))
# [,1] [,2] [,3] [,4]
#[1,] 2.176848 1.420882 1.2517326 1.4481875
#[2,] 0.000000 1.077816 0.1045581 0.4646328
#[3,] 0.000000 0.000000 1.2284251 0.3961126
#[4,] 0.000000 0.000000 0.0000000 0.9019771
Suppose you have a = 4, then we map y to x:
a <- 4
set.seed(0); y <- ry(5, sqrt(a), 10) ## we set an `rmax` here
x <- backsolve(R, y) ## each column is a sample of `x`
# [,1] [,2] [,3] [,4] [,5]
#[1,] 0.7403534 -1.49866534 -2.2359350 2.0269516 2.948561
#[2,] 5.5481682 0.41827601 0.7024109 -1.7606720 -7.288339
#[3,] 0.9373905 -1.01984708 0.1430660 -4.4180688 -4.749419
#[4,] -2.2616584 0.01995357 -0.8367956 0.2995693 4.299268
Checking
We can check that the above x does satisfy our requirement.
z <- Q %*% x
ax <- colSums(x ^ 2) ## value of `diag(x'Q'Qx)`
#[1] 84.15453 17.00795 24.77044 43.33361 85.85250
They are all greater than 4.

R ginv and Matlab pinv produce different results

ginv() function from MASS package in R produce totally different values compared to MATLAB pinv() function. They both claim to produce Moore-Penrose generalized inverse of a matrix.
I tried to set the same tolerance for the R implementation but the difference persists.
MATLAB default tol : max(size(A)) * norm(A) * eps(class(A))
R default tol : sqrt(.Machine$double.eps)
Reproduction:
R:
library(MASS)
A <- matrix(c(47,94032,149, 94032, 217179406,313679,149,313679,499),3,3)
ginv(A)
outputs:
[,1] [,2] [,3]
[1,] 1.675667e-03 -8.735203e-06 5.545605e-03
[2,] -8.735203e-06 5.014084e-08 -2.890907e-05
[3,] 5.545605e-03 -2.890907e-05 1.835313e-02
svd(A)
outputs:
$d
[1] 2.171799e+08 4.992800e+01 2.302544e+00
$u
[,1] [,2] [,3]
[1,] -0.0004329688 0.289245088 -9.572550e-01
[2,] -0.9999988632 -0.001507826 -3.304234e-06
[3,] -0.0014443299 0.957253888 2.892454e-01
$v
[,1] [,2] [,3]
[1,] -0.0004329688 0.289245088 -9.572550e-01
[2,] -0.9999988632 -0.001507826 -3.304234e-06
[3,] -0.0014443299 0.957253888 2.892454e-01
MATLAB:
A = [47 94032 149; 94032 217179406 313679; 149 313679 499]
pinv(A)
outputs:
ans =
0.3996 -0.0000 -0.1147
-0.0000 0.0000 -0.0000
-0.1147 -0.0000 0.0547
svd:
[U, S, V] = svd(A)
U =
-0.0004 0.2892 -0.9573
-1.0000 -0.0015 -0.0000
-0.0014 0.9573 0.2892
S =
1.0e+008 *
2.1718 0 0
0 0.0000 0
0 0 0.0000
V =
-0.0004 0.2892 -0.9573
-1.0000 -0.0015 -0.0000
-0.0014 0.9573 0.2892
Solution:
to make R ginv like MATLAB pinv use this function:
#' Pseudo-Inverse of Matrix
#' #description
#' This is the modified version of ginv function in MASS package.
#' It produces MATLAB like pseudo-inverse of a matrix
#' #param X The matrix to compute the pseudo-inverse
#' #param tol The default is the same as MATLAB pinv function
#'
#' #return The pseudo inverse of the matrix
#' #export
#'
#' #examples
#' A <- matrix(1:6,3,2)
#' pinv(A)
pinv <- function (X, tol = max(dim(X)) * max(X) * .Machine$double.eps)
{
if (length(dim(X)) > 2L || !(is.numeric(X) || is.complex(X)))
stop("'X' must be a numeric or complex matrix")
if (!is.matrix(X))
X <- as.matrix(X)
Xsvd <- svd(X)
if (is.complex(X))
Xsvd$u <- Conj(Xsvd$u)
Positive <- any(Xsvd$d > max(tol * Xsvd$d[1L], 0))
if (Positive)
Xsvd$v %*% (1 / Xsvd$d * t(Xsvd$u))
else
array(0, dim(X)[2L:1L])
}
Running debugonce(MASS::ginv), we see that the difference lies in what is done with the singular value decomposition.
Specifically, R checks the following:
Xsvd <- svd(A)
Positive <- Xsvd$d > max(tol * Xsvd$d[1L], 0)
Positive
# [1] TRUE TRUE FALSE
If the third element were true (which we can force by setting tol = 0, as suggested by #nicola), MASS::ginv would return:
Xsvd$v %*% (1/Xsvd$d * t(Xsvd$u))
# [,1] [,2] [,3]
# [1,] 3.996430e-01 -7.361507e-06 -1.147047e-01
# [2,] -7.361507e-06 5.014558e-08 -2.932415e-05
# [3,] -1.147047e-01 -2.932415e-05 5.468812e-02
(i.e., the same as MATLAB).
Instead, it returns:
Xsvd$v[, Positive, drop = FALSE] %*% ((1/Xsvd$d[Positive]) *
t(Xsvd$u[, Positive, drop = FALSE]))
# [,1] [,2] [,3]
# [1,] 1.675667e-03 -8.735203e-06 5.545605e-03
# [2,] -8.735203e-06 5.014084e-08 -2.890907e-05
# [3,] 5.545605e-03 -2.890907e-05 1.835313e-02
Thanks to #FaridCher for pointing out the source code of pinv.
I'm not sure I 100% understand the MATLAB code, but I think it comes down to a difference in how tol is used. The MATLAB correspondence to Positive in R is:
`r = sum(s>tol)`
Where tol is what's supplied by the user; if none is supplied, we get:
m = 0;
% I don't get the point of this for loop -- why not just `m = max(size(A))`?
for i = 1:n
m = max(m,length(A(:,i)));
end
% contrast with simply `tol * Xsvd$d[1L]` in R
% (note: i believe the elements of d are sorted largest to smallest)
tol = m*eps(max(s));

Generation of random variables

I have a problem about the generation of random variables with R .
I have to generate random variables
$X_{ij}$ (i=1,...,25, j=1,...,5 ) knowing that
each X_ij follows a binomial distribution
$X_{ij} \sim Bin(n_{ij}, p_{ij})
$and I know already
$n_{ij}$ and $p_{ij}$
for each index. How to generate these random variable?
I don't know if it could be useful, but I have generated $p_{ij}$ knowing that they are also random variable which follow a beta distribution (hence actually $X_{ij}$ follow a beta binomial)
Let's say you had the following matrices for n and p:
(n <- matrix(4:7, nrow=2))
# [,1] [,2]
# [1,] 4 6
# [2,] 5 7
set.seed(144)
(p <- matrix(rbeta(4, 1, 2), nrow=2))
# [,1] [,2]
# [1,] 0.1582904 0.2794913
# [2,] 0.5176909 0.2889718
Now you can draw samples X_{ij} with something like:
set.seed(144)
matrix(apply(cbind(as.vector(n), as.vector(p)), 1, function(x) rbinom(1, x[1], x[2])), nrow=2)
# [,1] [,2]
# [1,] 0 2
# [2,] 2 2
The cbind part of this expression builds a 2-column matrix containing each (n, p) pairing and the apply part draws a single binomially distributed sample for each (n, p) pair, with the matrix part converting the resulting vector to a matrix.

Euclidean distance between data sets in R using rdist from "fields" package

I am using rdist to compute Euclidean distances between a matrix and itself:
> m = matrix(c(1,1,1,2,2,2,3,4,3),nrow=3, ncol=3)
> m
[,1] [,2] [,3]
[1,] 1 2 3
[2,] 1 2 4
[3,] 1 2 3
library(fields)
> rdist(m)
[,1] [,2] [,3]
[1,] 1e-10 1e+00 1e-10
[2,] 1e+00 1e-10 1e+00
[3,] 1e-10 1e+00 1e-10
What confuses me is that I think it should have 0s on the diagonal (surely the distance of a vector to itself is 0?), and for the same reason it should have 0s where it compares the first and third row. The value that I see instead (1e-10) looks way to big to be numerical noise. What's going wrong?
EDIT: rdist is from the package fields.
First of all 1e-10 is simply 1*10^-10 which is 0.0000000001, so numericaly very close to 0 (as it is a result of square rooting, so the actual error in the computation is of row of magnitude 1e-20). Is it "too big"? Well, library is written in fortran, and is focused on speed, so it is quite acceptable. If you analyze the exact code, you will find out how it is computed:
# fields, Tools for spatial data
# Copyright 2004-2011, Institute for Mathematics Applied Geosciences
# University Corporation for Atmospheric Research
# Licensed under the GPL -- www.gpl.org/licenses/gpl.html
"rdist" <- function(x1, x2) {
if (!is.matrix(x1))
x1 <- as.matrix(x1)
if (missing(x2))
x2 <- x1
if (!is.matrix(x2))
x2 <- as.matrix(x2)
d <- ncol(x1)
n1 <- nrow(x1)
n2 <- nrow(x2)
par <- c(1/2, 0)
temp <- .Fortran("radbas", nd = as.integer(d), x1 = as.double(x1),
n1 = as.integer(n1), x2 = as.double(x2), n2 = as.integer(n2),
par = as.double(par), k = as.double(rep(0, n1 * n2)))$k
return(matrix(temp, ncol = n2, nrow = n1))
}
And the exact answer is hidden in the fortran files (in radfun.f called from radbas.f), where you can find the line
if( dtemp.lt.1e-20) dtemp =1e-20
which treats small (even 0) values as 1e-20, which after taking square root results in 1e-10. It seems that the motivation was to speed up the process by using logarithm of the value (as a result, square rooting is simply dividing by 2), which of course is not defined for 0.

Resources