Bayesian ancestral reconstruction of protein language-model embeddings.
ancemb treats the embedding produced by a protein language model (pLM) as a set of continuous traits evolving along a phylogeny. It reconstructs ancestral embeddings at internal nodes under Brownian Motion (BM) and Ornstein–Uhlenbeck (OU) processes, fitted with pre-compiled Stan models through the instantiate package.
What we are doing
Protein language models compress each sequence into a high-dimensional vector that encodes structure and function. A natural question follows: do those coordinates behave like phylogenetic traits — and if so, where does ancestral information stop being recoverable from embedding geometry?
This package is the analysis interface of a research program that decomposes that question into three testable hypotheses:
- T1 — trait evolution. Embedding dimensions carry components that evolve like continuous traits on the tree (measurable as Blomberg’s K, Pagel’s λ, predictive density on held-out clades).
- T2 — trait reconstruction. Phylogenetic models (BM/OU) reconstruct ancestral embeddings better than phylogeny-free baselines.
- T3 — invertibility. Closeness in embedding space implies recovery of the ancestral sequence.
Keeping these separate is deliberate: a failure of T3 says nothing about T2 unless the same nodes, pools, and metrics are held fixed — which is what this package enforces.
What we intend
ancemb aims to be the single, tested entry point for ancestral embedding inference: data preparation, Stan posterior fits, pure-R reference implementations, an optional Python (reticulate) backend for embedding arrays, and manifest-driven metrics that make results comparable across families, representations, and models. The long-term goal is a calibrated predictive account of embedding evolution — including an honest map of where it fails.
What has been achieved so far
-
Validated estimator. On Gaussian-trait simulations where the truth is known by construction, the BM/OU fits recover the simulated ancestors. On real protein families, Bayesian posterior means agree with
phytools::fastAncML reconstructions to cosine ≈ 0.99. - Partial phylogenetic signal. Across a curated multi-family database (trees + alignments + deposited and experimentally resurrected ancestors), ~10.7% of embedding dimensions show significant Blomberg’s K after multiple-testing correction; ancestral reconstruction beats phylogeny-free baselines in shallow families and degrades with depth.
- An honest limit. Sequence-level recovery by ranking candidates in embedding space does not beat classical MAP reconstruction — in a 340-node benchmark, MAP wins as soon as real headroom exists (P(d > 0) ≈ 0.19). A linear probe recovers ancestral sequence information above all phylogeny-free baselines, so the information is present in the embeddings; the failing component is the cosine-ranking/decoder step. Measuring that boundary — not claiming superiority — is the contribution.
A manuscript describing the full benchmark is in preparation.
Installation
ancemb requires a working CmdStan installation. Install CmdStan from R with:
install.packages("cmdstanr", repos = c("https://mc-stan.org/r-packages/", getOption("repos")))
cmdstanr::install_cmdstan()Then install ancemb from GitHub:
# install.packages("remotes")
remotes::install_github("alrobles/ancemb")Quick start
Prepare a small tree and some observed tip embeddings:
tree <- ape::rtree(8)
tree$edge.length <- tree$edge.length / max(ape::node.depth.edgelength(tree))
z_obs <- matrix(stats::rnorm(8 * 4, sd = 0.5), nrow = 8)
rownames(z_obs) <- tree$tip.labelBuild the Stan data list and fit the Brownian Motion model:
stan_data <- prepare_embedding_data(tree, z_obs, obs_sigma = 0.1)
fit <- fit_embedding_bm(stan_data, chains = 2, iter_warmup = 300,
iter_sampling = 300, refresh = 0)
#> Running MCMC with 2 sequential chains...
#>
#> Chain 1 finished in 0.2 seconds.
#> Chain 2 finished in 0.2 seconds.
#>
#> Both chains finished successfully.
#> Mean chain execution time: 0.2 seconds.
#> Total execution time: 0.6 seconds.Extract ancestral states and compare the inferred root with phytools::fastAnc:
anc <- extract_ancestral(fit, data = stan_data, tree = tree)
root_node <- which(anc$node == stan_data$S + 1)
ml_root <- phytools::fastAnc(tree, z_obs[, 1])[1]
cat("ML root (dim 1):", ml_root, "\n")
#> ML root (dim 1): 0.5041557
cat("Bayes root mean (dim 1):", anc$mean[root_node, "dim1"], "\n")
#> Bayes root mean (dim 1): 0.4241764Reference implementations
Every exported Stan wrapper is paired with a pure-R reference function (*_r) that reproduces the same mathematics line by line: simulate_bm_embedding_r(), simulate_ou_embedding_r(), reconstruct_ancestral_bm_r(), reconstruct_ancestral_ou_r() and reconstruct_ancestral_ml_r(). The test suite cross-checks Stan posterior means against both these references and phytools::fastAnc().
Python backend
An optional reticulate adapter (configure_python_backend(), python_backend_status(), load_embedding_array()) reads NumPy NPZ embedding archives directly, so the R pipeline can consume pLM outputs without intermediate conversions.
Roadmap
- Representation geometry: whitening, PCA, probabilistic PCA and invertible autoencoders as pre-defined analysis arms.
- Held-out-clade (LOCO) predictive benchmark against sequence-only controls, with pre-registered admission gates.
- Decoders beyond cosine ranking over fixed candidate pools.
Citation
ancemb is research software under active development. If you use it, please cite the GitHub repository; a manuscript citation will be added when available.
Code of Conduct
Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.