Introduction

The objective of this notebook is the simulation of second order isotropic random fields defined on the three dimensional sphere using the spectral algorithm proposed in Ch. Lantuéjoul et al. (2019).

Any point on the sphere \(\mathbb{S}_2\) can be defined by two spherical coordinates, its colatitude \(\theta \in [0, \pi]\) and its longitude \(\varphi \in [0, 2\pi]\). If we consider the points of the sphere as points of \(\mathbb{R}^3\) the relationship between its Cartesian coordinates and its spherical coordinates is: \[ \left\{ \begin{array}{ccl} x & = & \sin (\theta) \, \cos (\varphi) \\ y & = & \sin (\theta) \, \sin (\varphi) \\ z & = & \cos (\theta) \end{array} \right. \]

The ingredients of the simulation method are:

\[ C(t) = \sum_{n = 0}^{+\infty} a_n P_n(\cos t) \text{ with } t \in [0, \pi]. \]

In the previous equations,

The Legendre polynomials

The Legendre polynomials are defined by the Rodrigues’formula:

\[ P_n(x) = \frac{(-1)^n}{2^n n!} \frac{d^n}{dx^n}(1-x^2)^n. \] They can be computed using the recursion formula:

\[ (n+1) P_{n+1}(x) = (2n+1)\,x\, P_{n}(x) - n P_{n-1}(x), \] with the initial values \(P_0(x) = 1\) and \(P_1(x) = x\).

The Legendre polynomials are orthogonal but not normalized:

\[ \int_{-1}^{+1} P_m(u)P_n(u)du = \frac{2}{2n+1}\delta_{m,n}. \]

For numerical stability, we use normalized polynomials \(\tilde{P}_l(x) = P_l(x)\times \sqrt{2n+1}\). In this case, the recursion formula is: \[ \tilde{P}_{n+1}(x) = \frac{\sqrt{(2n+3)(2n+1)}}{n+1}\,x\, \tilde{P}_{n}(x) - \frac{n}{n+1}\sqrt{\frac{2n+3}{2n-1}} \tilde{P}_{n-1}(x), \] with the initial values \(\tilde{P}_0(x) = 1\) and \(\tilde{P}_1(x) = x\sqrt{3}\).

The associated Legendre functions

The associated Legendre functions denoted \(P_l^m(x)\) with \(x \in [-1,1]\) and \(-l \leq m \leq l\) are defined by the equation: \[ P_l^m(x) = (-1)^m \, (1 - x^2)^{m/2} \frac{d^m}{dx^m}(P_l(x)), \] where \(P_l\) is the Legendre polynomial of degree \(l\) (\(P_l = P_l^0\)), and \(m\) its order.

We consider also normalized Legendre functions as,

\[ \tilde{P}_l^m(x) = P_l^m(x) \times \sqrt{(2l+1)\frac{(l-m)!}{(l+m)!}}, \] which can be compute using the two recursion formulae:

\[ \tilde{P}_{l+1}^{l+1}(x) = -\sqrt{\frac{2l+3}{2l+2}} \, (1-x^2)^{1/2} \, \tilde{P}_{l}^{l}(x) \] and

\[ \tilde{P}_{l+1}^{m}(x) = \sqrt{\frac{(2l+3)(2l+1)}{(l+m+1)(l-m+1)}} \, x \, \tilde{P}_{l}^{m}(x) - \sqrt{\frac{(l+m)(l-m)(2l+3)}{(l+m+1)(l-m+1)(2l-1)}} \, \tilde{P}_{l-1}^{m}(x) \] with the initial conditions, \(\tilde{P}_0^0 = 1\) and \(\tilde{P}_1^0 = x \sqrt{3}\).

Note: The recursion for the Legendre functions boils down to the recursion for the Legendre polynomials with \(l = 0\).

Spherical harmonics

The spherical harmonics act on the sphere \(\mathbb{S}_2\) exactly like trigonometric functions on the unit circle, they are defined using the normalized Legendre functions:

\[ Y_{n, k}(\theta, \varphi) = \frac{1}{\sqrt{4\pi}} \tilde{P}_n^l (\cos \theta) e^{ik\varphi}. \] In addition, the value for negative order can is (\(k\geq0\)):

\[ Y_{n, -k}(\theta, \varphi) = (-1)^k \, \bar{Y}_{n, k}(\theta, \varphi). \]

Simulation algorithm

The simulation of the isotropic random function on the sphere with a spectrum \(a_n\) is:

  1. simulate \(P\) i.i.d. spectral components and random phases \((N_p, K_p, \Phi_p)_{p \in 1:P}\)
  2. compute the simulation values \(Z(x) = \frac{1}{\sqrt{P}} \sum_{p \in 1:P} 2 \sqrt{2\pi} \, \Re(Y_{N_p, K_p}(\theta, \varphi)\, e^{i\Phi_p})\)

Auxiliary functions

## Warning in rgl.init(initValue, onlyNULL): RGL: unable to open X11 display
## Warning: 'rgl.init' failed, will use the null device.
## See '?rgl.useNULL' for ways to avoid this warning.

Defining the sphere

Initialisation of the sphere

defineDefaultSpace(ESpaceType_SN(),param=6371)
## NULL
nx = c(360, 180)
dx = nx/(nx-1)*pi/180
x0 = c(0,0)
grd = DbGrid_create(nx = nx, x0 = x0, dx = dx)
err = grd$setName("x1", "phi")
err = grd$setName("x2", "theta")

Display

Initialization of S2 for 3d viewer

viz_S2_in_3D = IniView_S2_in_3D(grd)

# display of the real part of the spherical harmonics using rgl
val = ut_sphericalHarmonicVec(n = 15, k = 5, theta = grd["theta"], phi = grd["phi"])
err = viz_S2_in_3D$display(val = Re(val), ncol = 50, palette = "rainbow")
rglwidget()

Spectral simulation on the sphere

# defining the parameters for covariance and spectrum
rho = 0.9    # for the geometric model
lambda = 10  # for the Poisson model
nu = 5.0     # for the exponential model
mu = 1.0; kappa = 2.0 # for the SPDE model
ns = 100
err = defineDefaultSpace(ESpaceType_SN())
# modelSph = Model_createFromParam(ECov_LINEARSPH())
# modelSph = Model_createFromParam(ECov_GEOMETRIC(), rho)
modelSph = Model_createFromParam(ECov_POISSON(), 1., 1., lambda)
# modelSph = Model_createFromParam(ECov_EXPONENTIAL(), nu, 1., 0., flagRange=TRUE)
# modelSph = Model_createFromParam(ECov_MATERN(), 1./kappa, 1., mu, flagRange=FALSE)

cova = modelSph$getCovAniso(0)
covname = cova$getCovName()

Variogram of the model

alpha   = seq(from = 0, to = pi, length.out = 100)

mode = CovCalcMode()
mode$setAsVario(TRUE)
## NULL
covvec = cova$evalCovOnSphereVec(alpha,50, FALSE, mode=mode)
plot.init() + 
  plot.XY(alpha, covvec) +
  plot.decoration(title=paste0(covname, " model on S2"),
                  xlab = "Geodetic distance (rad)", ylab = "Variogram") 

Spectrum of the model

nd = 100
spevec = cova$evalSpectrumOnSphere(nd, flagCumul=TRUE)
plot.init() + 
  plot.XY(seq(nd+1), spevec) +
  plot.decoration(title=paste0(covname, " spectrum on S2"),
                  xlab = "Degrees", ylab = "Cumulated spectrum") + 
  plot.geometry(ylim=c(0,NA))

Simulation

seed = 54251
err  = grd$deleteColumns(names = paste0(covname, "*"))
simu = SimuSpectralS2(cova = cova)
err  = simu$simulate(ns = ns, seed = seed, verbose = TRUE, nd = 100)
## Simulation of the spectrum
## - Space dimension   = 2
## - Number of variables  = 1
## - Number of spectral components = 100
## Simulation of the spectrum
## - Space dimension   = 2
## - Number of variables  = 1
## - Number of spectral components = 100
## 
## List of Orders
## --------------
## Component  0 ( 5 /  0)
##  Key= 4  1 (+)
##  Key= 5  1 (+)
##  Key= 6  1 (+)
##  Key=10  2 (+)
## Component  1 ( 6 /  4)
##  Key= 4  1 (+)
##  Key= 5  1 (+)
##  Key= 8  1 (-)
##  Key=10  1 (-)  2 (+)
##  Key=11  1 (-)
##  Key=12  1 (-)  1 (+)
##  Key=14  1 (+)
## Component  2 ( 3 /  2)
##  Key= 7  1 (-)  1 (+)
##  Key= 8  1 (-)  1 (+)
##  Key=11  1 (+)
## Component  3 ( 4 /  8)
##  Key= 5  1 (-)
##  Key= 6  1 (-)  1 (+)
##  Key= 8  2 (-)  1 (+)
##  Key= 9  1 (-)  1 (+)
##  Key=10  2 (-)  1 (+)
##  Key=13  1 (-)
## Component  4 ( 8 /  6)
##  Key= 4  1 (+)
##  Key= 5  1 (+)
##  Key= 7  1 (-)  3 (+)
##  Key= 9  1 (-)
##  Key=10  1 (-)
##  Key=12  3 (-)  2 (+)
##  Key=15  1 (+)
## Component  5 ( 3 /  3)
##  Key= 7  1 (-)
##  Key= 9  1 (-)  1 (+)
##  Key=10  1 (+)
##  Key=11  1 (-)
##  Key=12  1 (+)
## Component  6 ( 6 /  6)
##  Key= 7  1 (-)  1 (+)
##  Key= 8  1 (+)
##  Key= 9  1 (+)
##  Key=10  2 (+)
##  Key=11  1 (-)  1 (+)
##  Key=14  2 (-)
##  Key=15  1 (-)
##  Key=17  1 (-)
## Component  7 ( 5 /  5)
##  Key= 7  1 (-)
##  Key= 9  1 (+)
##  Key=10  2 (-)  1 (+)
##  Key=11  1 (+)
##  Key=12  1 (-)  1 (+)
##  Key=13  1 (-)  1 (+)
## Component  8 ( 2 /  3)
##  Key= 8  1 (+)
##  Key= 9  2 (-)
##  Key=14  1 (-)
##  Key=16  1 (+)
## Component  9 ( 3 /  3)
##  Key= 9  1 (-)  1 (+)
##  Key=10  1 (-)
##  Key=11  1 (-)  1 (+)
##  Key=16  1 (+)
## Component 10 ( 1 /  5)
##  Key=10  2 (-)  1 (+)
##  Key=11  3 (-)
## Component 11 ( 3 /  2)
##  Key=11  1 (-)
##  Key=12  2 (+)
##  Key=13  1 (-)  1 (+)
## Component 12 ( 1 /  0)
##  Key=16  1 (+)
## Component 13 ( 1 /  1)
##  Key=14  1 (-)  1 (+)
## Component 14 ( 1 /  0)
##  Key=16  1 (+)
## 
## Summary:
## - Number of Orders         = 15
## - Number of components (+) = 52
## - Number of components (-) = 48
err  = simu$compute(dbout = grd, verbose = TRUE, namconv = NamingConvention(covname))
## Spectral Simulation on a set of Isolated Points
## - Number of samples = 64800
## - Space dimension   = 2
## - Number of variables = 1
## 
## >>> simulation on Sphere
## ------------------------
## >>> point number    : 64800
## >>> component number: 100
## >>> Maximum order   : 14
## >>> Maximum degree  : 17
## >>> Simulating order K = 0: component number = 5
## K = 0 and N = 4 : 1 / 1  jk = 0
## K = 0 and N = 5 : 1 / 2  jk = 1
## K = 0 and N = 6 : 1 / 3  jk = 2
## K = 0 and N = 10 : 2 / 5  jk = 3
## >>> Simulating order K = 1: component number = 10
## K = 1 and N = 4 : 1 / 6  jk = 5
## K = 1 and N = 5 : 1 / 7  jk = 6
## K = 1 and N = 8 : 1 / 8  jk = 7
## K = 1 and N = 10 : 3 / 11  jk = 8
## K = 1 and N = 11 : 1 / 12  jk = 11
## K = 1 and N = 12 : 2 / 14  jk = 12
## K = 1 and N = 14 : 1 / 15  jk = 14
## >>> Simulating order K = 2: component number = 5
## K = 2 and N = 7 : 2 / 17  jk = 15
## K = 2 and N = 8 : 2 / 19  jk = 17
## K = 2 and N = 11 : 1 / 20  jk = 19
## >>> Simulating order K = 3: component number = 12
## K = 3 and N = 5 : 1 / 21  jk = 20
## K = 3 and N = 6 : 2 / 23  jk = 21
## K = 3 and N = 8 : 3 / 26  jk = 23
## K = 3 and N = 9 : 2 / 28  jk = 26
## K = 3 and N = 10 : 3 / 31  jk = 28
## K = 3 and N = 13 : 1 / 32  jk = 31
## >>> Simulating order K = 4: component number = 14
## K = 4 and N = 4 : 1 / 33  jk = 32
## K = 4 and N = 5 : 1 / 34  jk = 33
## K = 4 and N = 7 : 4 / 38  jk = 34
## K = 4 and N = 9 : 1 / 39  jk = 38
## K = 4 and N = 10 : 1 / 40  jk = 39
## K = 4 and N = 12 : 5 / 45  jk = 40
## K = 4 and N = 15 : 1 / 46  jk = 45
## >>> Simulating order K = 5: component number = 6
## K = 5 and N = 7 : 1 / 47  jk = 46
## K = 5 and N = 9 : 2 / 49  jk = 47
## K = 5 and N = 10 : 1 / 50  jk = 49
## K = 5 and N = 11 : 1 / 51  jk = 50
## K = 5 and N = 12 : 1 / 52  jk = 51
## >>> Simulating order K = 6: component number = 12
## K = 6 and N = 7 : 2 / 54  jk = 52
## K = 6 and N = 8 : 1 / 55  jk = 54
## K = 6 and N = 9 : 1 / 56  jk = 55
## K = 6 and N = 10 : 2 / 58  jk = 56
## K = 6 and N = 11 : 2 / 60  jk = 58
## K = 6 and N = 14 : 2 / 62  jk = 60
## K = 6 and N = 15 : 1 / 63  jk = 62
## K = 6 and N = 17 : 1 / 64  jk = 63
## >>> Simulating order K = 7: component number = 10
## K = 7 and N = 7 : 1 / 65  jk = 64
## K = 7 and N = 9 : 1 / 66  jk = 65
## K = 7 and N = 10 : 3 / 69  jk = 66
## K = 7 and N = 11 : 1 / 70  jk = 69
## K = 7 and N = 12 : 2 / 72  jk = 70
## K = 7 and N = 13 : 2 / 74  jk = 72
## >>> Simulating order K = 8: component number = 5
## K = 8 and N = 8 : 1 / 75  jk = 74
## K = 8 and N = 9 : 2 / 77  jk = 75
## K = 8 and N = 14 : 1 / 78  jk = 77
## K = 8 and N = 16 : 1 / 79  jk = 78
## >>> Simulating order K = 9: component number = 6
## K = 9 and N = 9 : 2 / 81  jk = 79
## K = 9 and N = 10 : 1 / 82  jk = 81
## K = 9 and N = 11 : 2 / 84  jk = 82
## K = 9 and N = 16 : 1 / 85  jk = 84
## >>> Simulating order K = 10: component number = 6
## K = 10 and N = 10 : 3 / 88  jk = 85
## K = 10 and N = 11 : 3 / 91  jk = 88
## >>> Simulating order K = 11: component number = 5
## K = 11 and N = 11 : 1 / 92  jk = 91
## K = 11 and N = 12 : 2 / 94  jk = 92
## K = 11 and N = 13 : 2 / 96  jk = 94
## >>> Simulating order K = 12: component number = 1
## K = 12 and N = 16 : 1 / 97  jk = 96
## >>> Simulating order K = 13: component number = 2
## K = 13 and N = 14 : 2 / 99  jk = 97
## >>> Simulating order K = 14: component number = 1
## K = 14 and N = 16 : 1 / 100  jk = 99

QC

Statistics

opers = EStatOption_fromKeys(c("NUM", "MINI", "MAXI", "MEAN", "STDV"))
tab = dbStatisticsMono(grd, names = paste0(covname, "*"), opers = opers)$toTL()
knitr::kable(tab, digits = 6, caption = paste0("Statistics for simulation of ", 
                                               covname, " model on S2"))
Statistics for simulation of Poisson model on S2
Number Minimum Maximum Mean St. Dev.
Poisson.V1.simu 64800 -3.54206 3.19619 -0.02458 0.970365

Visualization 2D

simu_name = paste0(covname, ".V1.simu")
p = plot.init(asp=1) +
  plot.raster(grd, name = simu_name, flagLegend = TRUE, legendName = "Y", 
            palette = "Spectral") +
  plot.decoration(xlab = "Longitude", ylab = "Colatitutde", 
                  title = paste0(covname, " model on S2"))
plot.end(p) 

Visualization 3D

err = viz_S2_in_3D$display(val = grd[simu_name], ncol = 25, palette = "terrain")
rglwidget()

Experimental variogram

# defining the variogram directions
dir_list = list()
dir_list[[1+length(dir_list)]] = list(lag = c(1,0))
dir_list[[1+length(dir_list)]] = list(lag = c(0,1))
dir_list[[1+length(dir_list)]] = list(lag = c(1,1))
dir_col = c("orange", "skyblue", "red")
ndisc   = 30

alpha   = seq(from = 0, to = pi, length.out = 100)
# computing the variograms
var_list = list()
for (idir in 1:length(dir_list)) {
  var_list[[1+length(var_list)]] = vario_on_S2(grd = grd, nm_var = simu_name, 
         lag = dir_list[[idir]]$lag, nlag = 90, ndisc = ndisc, dmax = pi)
}

# covariance model
mode = CovCalcMode()
mode$setAsVario(TRUE)
## NULL
mod_vario = cova$evalCovOnSphereVec(alpha, mode = mode)
plot(NULL, NULL, xlim = range(alpha), ylim = 1.1 * range(c(0, max(mod_vario))), 
     xlab = "Geodetic distance (rad)", ylab = "Variogram",
     main = paste0(covname, " model on S2"))
abline(h = c(0, 1), col = "gray", lty = 2)
abline(v = c(0), col = "gray", lty = 2)
lines(alpha, mod_vario , col = "black", lw = 2, lty = 1)

leg = c("model")
for (ivar in 1:length(var_list)) {
  lines(var_list[[ivar]]$hh, var_list[[ivar]]$gg, col = dir_col[ivar], lw = 1, lty = 3)
  points(var_list[[ivar]]$hh, var_list[[ivar]]$gg, col = dir_col[ivar], pch = 1)
  leg = c(leg, paste0("[", dir_list[[ivar]]$lag[1], ",", dir_list[[ivar]]$lag[2],"]"))
}
  
legend("bottomright", 
       legend = leg, 
       col = c("black", dir_col), 
       pch = c(NA_integer_, rep(1, length(dir_col))), 
       lty = c(1, rep(3, length(dir_col))),
       lw  = c(2, rep(1, length(dir_col)))
)

Quantification of the fluctuations on the variogram

err = grd$deleteColumns(names = paste0(covname, "*"))
nsim = 20 # number of simulation
err = simuSpectral(dbin = NULL, dbout = grd, cova = cova, seed = 2584, ns=ns, nbsimu=nsim, namconv = NamingConvention(covname))

# simulation and experimental variograms
varios <- matrix(NaN, nrow = nsim, ncol = ndisc)
for (s in 1:nsim) {
  nm  = paste0(covname, ".V1.S", s)
  vs = vario_on_S2(grd = grd, nm_var = nm, lag = c(1,0), nlag = 90, ndisc = ndisc, dmax = pi)
  varios[s, 1:length(vs$hh)] <- vs$gg
}
dist = vs$hh

# plot
plot(NULL, NULL, xlim = range(dist, na.rm = TRUE), ylim = range(varios, na.rm = TRUE),
     xlab = "Geodetic distance (rad)", ylab = "Variogram", 
     main = paste0(covname, " model on S2"))
abline(h = c(0), lty = 3, col = "gray")
abline(v = c(0, pi/2, pi), lty = 3, col = "gray")

# model
abline(h = c(1), lw = 1, lty = 2)
lines(alpha, mod_vario , col = "black", lw = 2, lty = 1)

# experimental variograms
for(s in 1:nsim) {
  lines(dist, varios[s,1:length(dist)], col = "gray", lw = 1, lty = 2 )
}

# mean variogam and quantiles
v_moy = apply(X = varios, MARGIN = 2, FUN = mean, na.rm = TRUE)
v_Q10 = apply(X = varios, MARGIN = 2, FUN = quantile, probs = 0.10, na.rm = TRUE)
v_Q90 = apply(X = varios, MARGIN = 2, FUN = quantile, probs = 0.90, na.rm = TRUE)

lines(dist, v_moy[1:length(dist)], col = "orange", lw = 2, lty = 2)
lines(dist, v_Q10[1:length(dist)], col = "green", lw = 1, lty = 1)
lines(dist, v_Q90[1:length(dist)], col = "blue", lw = 1, lty = 1)

# position of the legend to be adapted?
legend("topright", 
       legend = c("model", "experiment", "mean", "Q10", "Q90"),
       col = c("black", "gray", "orange", "green", "blue"),
       lw = c(2, 1, 2, 1, 1),
       lty = c(1, 2, 2, 1, 1)
       )