The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Generalized eigenvalue problems with eigencore

A generalized eigenvalue problem asks for nonzero vectors \(x\) and scalars \(\lambda\) satisfying

\[ A x = \lambda B x. \]

This form shows up whenever a problem needs a metric other than the standard inner product: whitened PCA and canonical correlation analysis weight by a covariance matrix, linear discriminant analysis weights by a within-class scatter matrix, and normalized graph Laplacians weight by a degree matrix. In each case B is not incidental — solving the plain eigenproblem A x = lambda x and ignoring B gives you the wrong answer.

The second matrix \(B\) may define a positive-definite metric, or it may be part of a more general matrix pencil. eigencore supports both cases, along with partial solves when you need only a few eigenpairs and QZ decompositions when you need the full structure of a dense pencil.

Start with a positive-definite metric

When A is symmetric (or Hermitian) and B is symmetric positive definite, pass B directly to eig_full(). The returned vectors are normalized in the \(B\) metric.

A <- diag(c(2, 8, 18))
B <- diag(c(1, 2, 3))

fit <- eig_full(A, B = B)
values(fit)
#> [1] 2 4 6
certificate(fit)$passed
#> [1] TRUE

Here the generalized eigenvalues are 2, 4, and 6: each diagonal entry of A is divided by the corresponding entry of B.

Compute only part of the spectrum

Use eig_partial() when you need only a few eigenpairs. A target such as smallest() states which part of the spectrum to compute. Scale the same idea up to a diagonal metric problem with 150 pairs, and ask for the 5 smallest:

n <- 150
A_wide <- diag(seq(1, 150, length.out = n))
B_wide <- diag(seq(1, 2, length.out = n))

part <- eig_partial(A_wide, B = B_wide, k = 5, target = smallest())
sort(Re(values(part)))
#> [1] 1.000000 1.986667 2.960265 3.921053 4.869281
part$method
#> [1] "native dense generalized SPD LAPACK fallback"
certificate(part)$passed
#> [1] TRUE
Scatter plot of 150 generalized eigenvalues sorted descending in grey, with the five smallest highlighted in blue at the bottom-right.

The five smallest generalized eigenvalues (blue) against the full 150-pair spectrum of the pencil (A, B) (grey).

With n = 150 this computed 5 of 150 pairs. A whitened-PCA or normalized-Laplacian problem built from real data routinely has n in the tens or hundreds of thousands, where a full generalized eigendecomposition is often not practical and a certified partial slice is the useful result you can afford.

The main choice is the structure of the problem and how much of its spectrum you need:

Problem Function
Symmetric/Hermitian A with positive-definite B, full spectrum eig_full(A, B = B)
Symmetric/Hermitian A with positive-definite B, partial spectrum eig_partial(A, B = B, k = ...)
Dense general pencil eig_full(A, B = B, structure = general())
Dense generalized Schur decomposition generalized_schur(A, B)

Handle a dense general pencil

Use structure = general() when B is indefinite, singular, nonsymmetric, or when the pair does not satisfy the symmetric/Hermitian positive-definite contract. This path represents eigenvalues by homogeneous coordinates \((\alpha, \beta)\), where a finite eigenvalue is \(\alpha / \beta\).

A_general <- matrix(c(1, 4, 2, 3), 2, 2)
B_general <- matrix(c(2, 1, 0, -1), 2, 2)

pencil <- eig_full(A_general, B = B_general, structure = general())
values(pencil)
#> [1] -0.75+1.391941i -0.75-1.391941i
alpha_beta(pencil)$classification
#> [1] "finite" "finite"
certificate(pencil)$passed
#> [1] TRUE

Homogeneous coordinates are especially useful when B is singular. eigencore classifies finite, infinite, and undefined eigenvalues instead of forcing every pair into an ordinary numeric ratio.

singular <- eig_full(
  diag(c(2, 3, 0)),
  B = diag(c(1, 0, 0)),
  structure = general()
)

alpha_beta(singular)$classification
#> [1] "finite"    "infinite"  "undefined"
certificate(singular)$failed_indices
#> [1] 2 3

Use QZ when you need the Schur form

generalized_schur() computes a dense generalized Schur, or QZ, decomposition. Use values() for the finite ratios and alpha_beta() when you need the homogeneous coordinates or finite/infinite classification.

qz <- generalized_schur(A_general, B_general)
values(qz)
#> [1] -0.75+1.391941i -0.75-1.391941i
alpha_beta(qz)$classification
#> [1] "finite" "finite"
qz$method
#> [1] "native dense generalized Schur QZ LAPACK full"

For pencils with singular B, sort = "finite" or sort = "infinite" moves the requested class to the leading part of the decomposition.

qz_singular <- generalized_schur(
  diag(c(2, 3, 0)),
  diag(c(1, 0, 0)),
  sort = "infinite"
)
alpha_beta(qz_singular)$classification
#> [1] "infinite"  "finite"    "undefined"

Keep sparse partial problems sparse

Sparse symmetric/Hermitian problems with a positive-definite metric use eig_partial(). Set allow_dense_fallback = "never" when preserving sparsity is a hard requirement.

A_sparse <- Diagonal(x = c(1, 4, 9, 16, 25, 36))
B_sparse <- Diagonal(x = c(1, 2, 3, 4, 5, 6))

sparse_fit <- eig_partial(
  A_sparse,
  B = B_sparse,
  k = 3,
  target = smallest(),
  method = lanczos(max_subspace = 6),
  allow_dense_fallback = "never"
)

values(sparse_fit)
#> [1] 1 2 3
sparse_fit$method
#> [1] "native transformed generalized SPD B-orthogonal Lanczos"
certificate(sparse_fit)$passed
#> [1] TRUE

Full-spectrum functions accept base dense matrices and reject sparse or operator inputs rather than silently converting them to dense storage.

A note for older code

The retired geigen package exposed related operations, but eigencore is not a namespace-compatible replacement. When maintaining older code, translate the mathematical operation: use eig_full() for a full generalized eigensolve, generalized_schur() for QZ, and alpha_beta() to inspect homogeneous eigenvalue coordinates.

Where to go next

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.