
Envirotype-informed Kernels for Multi-Environment Genomic Prediction
Source:R/env_prediction.R
get_kernel.RdBuilds the genomic, envirotypic and genotype-by-environment kernels required
by kernel_model. Given a genomic relationship matrix
(p x p genotypes) and optionally an environmental relationship matrix
(q x q environments), it returns one kernel per model term, already
spectrally decomposed and ready to sample from.
Two computational engines are available and produce numerically identical results:
engine = "factored"(default)Never allocates the
n x nkernel. Each kernel is represented by its eigenvectorsU(n x r) and eigenvaluess, obtained from the smallp x p/q x qinputs. Memory is O(n*r) instead of O(n^2), which is what makes large multi-environment trials feasible.engine = "dense"Builds the explicit
n x nmatrix and attaches it as$Kernel. Slower and quadratic in memory, but needed if you want to inspect or post-process the kernel itself.
Set verbose = TRUE to trace each step with timings;
verbose = FALSE (default) is entirely silent.
Usage
get_kernel(
K_G = NULL,
K_E = NULL,
data = NULL,
model = "RNMM",
intercept.random = FALSE,
engine = c("factored", "dense"),
env = "env",
gid = "gid",
y = "value",
tol = 1e-10,
keep_var = 1,
scale_kernels = TRUE,
verbose = FALSE
)Arguments
- K_G
list of genomic relationship matrices (
p x p), named. IfNULL, an identity genotype kernel is used.- K_E
list of environmental relationship matrices (
q x q), named. IfNULL, benchmark genomic-only models are built andmodelis restricted toMM/MDs.- data
data.frame with the environment, genotype and phenotype columns.
- model
character. One of
"MM","MDs","EMM","EMDs","RNMM","RNMDs". See Model structures.- intercept.random
logical. Append an identity genotype kernel
KG_Gi, separating marker-predictable genetic variance from genotype-specific deviation the markers do not capture. Requires replication (a genotype seen in more than one environment) to be identifiable; with one observation per genotype it is confounded with the residual and a warning is issued. DefaultFALSE.- engine
character.
"factored"(default) or"dense".- env, gid, y
character. Column names in
data.- tol
numeric. Eigenvalue cutoff, relative to the largest eigenvalue. Must match
kernel_model(tol =). Default1e-10.- keep_var
numeric in (0, 1]. Retain only the leading eigenvalues carrying this proportion of each kernel's trace. Sampler cost scales with total rank, so e.g.
0.99is often a large speedup for negligible change in predictions. Default1(keep everything).- scale_kernels
logical. Normalise each kernel to
mean(diag(K)) = 1before decomposing. Must matchkernel_model(scale_kernels =). DefaultTRUE.- verbose
logical. Narrate each step with timings. Default
FALSE.
Value
A named list of kernels, class "kernel_set". Each element is a
list with:
Type"D"(dense) or"BD"(block-diagonal).decomplist of blocks, each
list(s, U, nr, deltav, type, pos, scaled, tol).diag_meanmean(diag(K)), computed in O(n*r).Kernelthe
n x nmatrix – only whenengine = "dense".
Attributes: "ne" (observations per environment), "kd_meta"
(decomposition settings, so kernel_model() can validate the cache),
and "obs" (index vectors and level names).
Model structures
| model | terms | kernels |
MM | y = mu + G | KG_G |
MDs | y = mu + G + GE | KG_G, KGE_GE |
EMM | y = mu + E + G | KE_W, KG_G |
EMDs | y = mu + E + G + GE | KE_W, KG_G, KGE_GE |
RNMM | y = mu + E + G + GxW | KE_W, KG_G, KGE_GW |
RNMDs | y = mu + E + G + GE + GxW | KE_W, KG_G, KGE_GE, KGE_GW |
GE is a block-diagonal (within-environment) interaction; GxW is
the reaction-norm interaction built from the environmental kernel.
Why the factored engine is exact
Every kernel here is a congruence transform of a small matrix. With
ig mapping observations to genotypes and ie to environments,
$$K_G = Z_g K_g Z_g' = K_g[ig, ig], \quad K_E = K_e[ie, ie]$$
so the eigendecomposition of the n x n kernel follows from the small
one in O(p^3) rather than O(n^3). Writing \(K = A A'\) with
\(A = V[ig,]\sqrt{D}\), the nonzero eigenpairs come from the small Gram
matrix \(A'A\), giving an orthonormal U and exact eigenvalues.
The interaction kernel is an elementwise product of two expansions, which is the expansion of a Kronecker product: with \(j = (ie-1) n_g + ig\), \(K_{GE} = (K_e \otimes K_g)[j, j]\). On a complete balanced grid its eigenpairs are closed-form (eigenvalues \(d_e \otimes d_g\), eigenvectors a Khatri-Rao product) so no large eigendecomposition is ever required.
Reusing kernels
Kernels depend only on genotypes and environments, never on the phenotype.
Masking y for a cross-validation fold does not invalidate
them, so build once and reuse across folds, chains and models.
References
Costa-Neto G, Galli G, Carvalho HF, Crossa J, Fritsche-Neto R (2021). EnvRtype: a software to interplay enviromics and quantitative genomics in agriculture. G3, 11(4), jkab040.
Examples
# \donttest{
data("maizeYield"); data("maizeG"); data("maizeWTH")
ECs <- W_matrix(env.data = maizeWTH[maizeWTH$daysFromStart < 100, ],
var.id = c("FRUE", "PETP", "SRAD", "T2M_MAX"),
statistic = "mean")
#> ---------------------------------------------------------------
#> W_matrix -- builds the environmental covariable (W) matrix
#> Last updated at 09/27/2026
#> ---------------------------------------------------------------
#> - summarising weather data into environmental covariables
#> - centring, scaling and quality-controlling W
KE <- list(W = env_kernel(env.data = ECs)[[2]])
#> ---------------------------------------------------------------
#> env_kernel -- builds environmental relatedness kernels
#> Last updated at 09/27/2026
#> ---------------------------------------------------------------
#> - computing a single environmental kernel
KG <- list(G = maizeG)
## factored engine (default): no n x n matrix is ever allocated
K <- get_kernel(K_G = KG, K_E = KE, data = maizeYield, model = "RNMM",
env = "env", gid = "gid", y = "value", verbose = TRUE)
#>
#> =========================================================
#> get_kernel: envirotype-informed kernels for multi-
#> environment genomic prediction. Builds the G, E and
#> GxE relationship kernels for the chosen model and
#> returns them spectrally decomposed, ready for
#> kernel_model().
#> Original Version: Costa-Neto et al (2021)
#> EnvRtype v.0.1.3, Sep 2026
#> =========================================================
#> model = RNMM engine = factored
#> ---------------------------------------------------------
#> [1/4] Validating inputs ... 0.00s 750 obs | 150 gid | 5 env
#> [2/4] Aligning relationship matrices ... 0.00s K_G 150 x 150, K_E 5 x 5
#> [3/4] Factorising kernels ... 0.01s 3 kernels, total rank 807
#> [4/4] Assembling output ... 0.01s no n x n matrix built
#> ---------------------------------------------------------
#> Kernels: KE_W, KG_G, KGE_GW
#> Total rank 807 / 750 observations (107.6%)
#> Memory: ~4.8 MB factored vs 13.5 MB dense (3x less)
#> Total running time: 0.02s
#> =========================================================
#>
fit <- kernel_model(y = "value", data = maizeYield, random = K,
env = "env", gid = "gid",
iterations = 5000, burnin = 1000, verbose = TRUE)
#>
#> =========================================================
#> kernel_model: Bayesian genomic prediction for multi-
#> environment trials. Fits y = 1mu + Xb + sum_j u_j + e
#> with u_j ~ N(0, K_j sigma2_j) by Gibbs sampling in the
#> spectral basis of each kernel, returning predictions,
#> variance components and heritabilities.
#> Original Version: Costa-Neto et al (2021)
#> EnvRtype v.0.1.3, Sep 2026
#> =========================================================
#> Gibbs sampler after Granato et al (2018) G3
#> ---------------------------------------------------------
#> [1/6] Validating inputs ... 0.00s 750 obs, 3 kernels
#> [2/6] Assembling response and design ... 0.00s 5 env, 150 gid
#> [3/6] Spectral decomposition ... 0.00s all kernels pre-factored; eigen skipped
#> [4/6] Computing prior scales ... 0.00s R2 = 0.50 split 'per_kernel', prior_scale 'eigen'
#> [5/6] Gibbs sampling (5000 iter) ... 2.56s 400 draws kept
#> [6/6] Variance components and heritability ... 0.00s method 'gibbs', h2 = 0.073 [postmean needs keep_effects = TRUE]
#> ---------------------------------------------------------
#> Total running time: 2.56s (sampler 2.56s, 100%)
#> Residual variance : 0.2949
#> =========================================================
#>
## trim the eigenvalue tail: usually a large speedup, tiny accuracy cost
K99 <- get_kernel(K_G = KG, K_E = KE, data = maizeYield, model = "RNMM",
keep_var = 0.99)
## separate marker-predictable from residual genetic variance
Ki <- get_kernel(K_G = KG, K_E = KE, data = maizeYield, model = "RNMM",
intercept.random = TRUE)
# }