---
title: Get started with eigencore
output:
  rmarkdown::html_vignette:
    toc: yes
    toc_depth: 2
    css: albers.css
    includes:
      in_header: albers-header.html
params:
  family: lapis
  preset: homage
resource_files:
- albers.css
- albers.js
- albers-header.html
- fonts

vignette: |
  %\VignetteIndexEntry{Get started with eigencore}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup-opts, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  message = FALSE,
  warning = FALSE,
  fig.width = 7,
  fig.height = 4.1,
  fig.align = "center",
  out.width = "92%",
  dpi = 100
)
```

```{r albers-classes, echo=FALSE, results='asis'}
cat(sprintf(
  paste0(
    '<script>document.addEventListener("DOMContentLoaded",function(){',
    'document.body.classList.remove("palette-red","palette-lapis","palette-ochre","palette-teal","palette-green","palette-violet","preset-homage","preset-interaction","preset-study","preset-structural","preset-adobe","preset-midnight");',
    'document.body.classList.add("palette-%s","preset-%s");',
    '});</script>'
  ),
  params$family,
  params$preset
))
```

```{r helpers, include = FALSE}
# Shared plotting helpers. Kept out of the reader's view: the reader sees
# the API call and the picture, never the plotting boilerplate.
ec_blue <- "#2166ac"; ec_grey <- "grey78"

# A computed slice (blue) drawn on top of the full dense spectrum (grey).
plot_spectrum <- function(all_vals, computed, ylab = "value", main = NULL) {
  s <- sort(all_vals, decreasing = TRUE)
  k <- length(computed)
  op <- par(mar = c(4, 4.6, if (is.null(main)) 1 else 2.4, 1)); on.exit(par(op))
  plot(seq_along(s), s, pch = 19, cex = 0.4, col = ec_grey, bty = "n",
       xlab = "rank (largest to smallest)", ylab = ylab, main = main)
  points(seq_len(k), sort(computed, decreasing = TRUE),
         pch = 19, cex = 1.2, col = ec_blue)
  legend("topright", c("full spectrum (dense reference)", "computed by eigencore"),
         pch = 19, pt.cex = c(0.7, 1.2), col = c(ec_grey, ec_blue),
         bty = "n", cex = 0.9)
}
```

A matrix too large to fully diagonalize still has a handful of eigenvalues
or singular values you actually need — the top few directions of variance,
the leading modes of a graph, the dominant singular triplets behind a
low-rank approximation. eigencore computes exactly that partial answer, and
every result carries a certificate: residuals, a backward-error bound, and
a pass/fail verdict, so you don't have to recompute the check yourself to
trust it.

```{r setup}
library(eigencore)
```

## Compute a certified partial eigendecomposition

Build a symmetric matrix and ask for its five largest eigenvalues:

```{r make-A}
set.seed(1)
n <- 200
A <- crossprod(matrix(rnorm(n * n), n, n)) / n + diag(n)
fit <- eig_partial(A, k = 5, target = largest())
fit
```

The certificate is not a side note — `fit$certificate$passed` is the
answer to "can I trust these five numbers?"

```{r first-certificate}
fit$certificate$passed
```

```{r spectrum, echo = FALSE, fig.cap = "The five largest eigenvalues (blue) located within the full spectrum of A (grey). eigencore computes only the requested slice, then certifies it.", fig.alt = "Scatter plot of all 200 eigenvalues sorted from largest to smallest in grey, with the five largest highlighted in blue at the top-left."}
all_vals <- eigen(A, symmetric = TRUE, only.values = TRUE)$values
plot_spectrum(all_vals, fit$values, ylab = "eigenvalue")
```

With `n = 200` this computed 5 of 200 pairs. In a production problem with
`n = 1e6`, storing all eigenvectors and computing the full spectrum is usually
impractical; a certified partial result is often the useful result you can
afford.

## Generalized SPD eigenproblem (`A v = lambda B v`)

Pass a metric `B` to `eig_partial()` when the problem is `A v = lambda B v`
rather than the standard `A v = lambda v`.

```{r generalized}
B <- diag(seq(1, 5, length.out = n))
fit_gen <- eig_partial(A, k = 5, target = largest(), B = B,
                       method = lobpcg(maxit = 200))
fit_gen
```

The absolute residual is `||A v - lambda B v||`. The certificate divides that
quantity by `||A|| + |lambda| ||B||` (and the vector norm, when needed) to form
the backward error used for convergence. Orthogonality is measured in the
`B`-inner product where appropriate. See
`vignette("generalized-eigenproblems")` for dense pencils, singular `B`, and
the QZ decomposition.

## Partial SVD

For rectangular problems use `svd_partial()`:

```{r svd}
M <- matrix(rnorm(400 * 50), 400, 50)
svd_fit <- svd_partial(M, rank = 5, target = largest())
svd_fit
```

The same mental model applies to singular values: you compute the leading
few and leave the tail untouched.

```{r svd-scree, echo = FALSE, fig.cap = "The five leading singular values (blue) computed by eigencore, shown against the full singular-value spectrum of M (grey).", fig.alt = "Scatter plot of all 50 singular values of M sorted descending in grey, with the top five highlighted in blue."}
all_sv <- svd(M, nu = 0, nv = 0)$d
plot_spectrum(all_sv, svd_fit$d, ylab = "singular value")
```

The reported `method` identifies the path — for very small or near-square
problems eigencore may use a dense LAPACK SVD fallback rather than running
its iterative Golub-Kahan kernel. Either way the certificate covers both
`||A v - sigma u||` and `||A^T u - sigma v||`.

## RSpectra-compatible workflow

If your existing code uses `RSpectra::eigs_sym()`, you can call eigencore
in the same shape — the return list extends RSpectra's by adding
`certificate` and `diagnostics`:

```{r rspectra}
res <- eigs_sym(A, k = 5, which = "LA")
str(res, max.level = 1)
```

```{r rspectra-cert}
res$certificate
```

`eigs()`, `eigs_sym()`, and `svds()` accept the same `which` codes as
`RSpectra` — `"LM"`, `"SM"`, `"LA"`, `"SA"`, `"LR"`, `"SR"`, `"LI"`, `"SI"`,
and `"BE"`.

## Under the hood: operators, problems, and plans

`eig_partial()` and `svd_partial()` cover the common case, but every call
you've made in this vignette runs through the same four-stage architecture,
and you can drop down to it directly when you want to inspect or reuse a
piece of that pipeline.

**1. Operators.** Any matrix is wrapped in a *block-native operator* the
moment you hand it to a problem constructor. You can do this explicitly:

```{r as-operator}
Aop <- as_operator(A)
Aop
```

The operator carries dimensions, structure tags, and a flag indicating
whether the underlying storage has a native kernel (in this case dense
double — yes). `vignette("sparse-pca")` builds operators that center and
scale a sparse matrix without densifying it — the same object type, doing
more work per call.

**2. Problems.** `eigen_problem()` and `svd_problem()` describe *what* to
solve: the matrix, its structure (`hermitian()`, `general()`, ...), and the
spectral target (`largest()`, `smallest()`, `nearest()`, ...).

```{r problem}
P <- eigen_problem(A, structure = hermitian(), target = largest())
```

**3. Plans.** `plan_solver()` reports the primary method selected from the
problem's current structure, target, and policy, together with any allowed
fallback. It is an inspection object, not an executable or frozen plan:
certification can still trigger a recorded fallback while solving.

```{r plan}
plan <- plan_solver(P, k = 5)
plan
```

**4. Solve.** `solve()` on a problem is what `eig_partial()` calls
internally. Reuse the problem object when you want to solve it again. Inspect
the returned `method`, warnings, and restart diagnostics to see the route that
actually ran.

```{r solve}
same_fit <- solve(P, k = 5)
isTRUE(all.equal(same_fit$values, fit$values))
```

## Where to go next

- `vignette("sparse-pca")` — the flagship workflow: centering and scaling a
  sparse matrix without densifying it, and building matrix-free operators
  by hand.
- `vignette("certificates")` is the deep dive on reading the numerical
  evidence — what each field means and what to do when a check fails.
- `vignette("generalized-eigenproblems")` covers dense pencils, singular
  `B`, and the QZ decomposition.
- Run `help(package = "eigencore")` to browse the installed help index.
- `?certificate` documents the certificate fields in detail.
- `?plan_solver` explains how operator structure, target, and method
  combine to choose a kernel.
