Force-Directed Euclidean Embedding of Dissimilarity Data
Overview
A robust implementation of Topolow algorithm. It embeds objects into a low-dimensional Euclidean space from a matrix of pairwise dissimilarities, even when the data do not satisfy metric or Euclidean axioms. The package is particularly well-suited for sparse, incomplete, and censored (thresholded) datasets such as antigenic relationships. The core is a physics-inspired, gradient-free optimization framework that models objects as particles in a physical system, where observed dissimilarities define spring rest lengths and unobserved pairs exert repulsive forces. The package also provides functions specific to antigenic mapping to transform cross-reactivity and binding affinity measurements into accurate spatial representations in a phenotype space. Key features include: * Robust Embedding from Sparse Data: Effectively creates complete and consistent maps (in optimal dimensions) even with high proportions of missing data (e.g., >95%). * Physics-Inspired Optimization: Models objects (e.g., antigens, landmarks) as particles connected by springs (for measured dissimilarities) and subject to repulsive forces (for missing dissimilarities), and simulates the physical system using laws of mechanics, reducing the need for complex gradient computations. * Automatic Dimensionality Detection: Employs a likelihood-based approach to determine the optimal number of dimensions for the embedding/map, avoiding distortions common in methods with fixed low dimensions. * Noise and Bias Reduction: Naturally mitigates experimental noise and bias through its network-based, error-dampening mechanism. * Antigenic Velocity Calculation (for antigenic data): Introduces and quantifies "antigenic velocity," a vector that describes the rate and direction of antigenic drift for each pathogen isolate. This can help identify cluster transitions and potential lineage replacements. * Broad Applicability: Analyzes data from various objects that their dissimilarity may be of interest, ranging from complex biological measurements such as continuous and relational phenotypes, antibody-antigen interactions, and protein folding to abstract concepts, such as customer perception of different brands. Methods are described in the context of bioinformatics applications in Arhami and Rohani (2025a) doi:10.1093/bioinformatics/btaf372, and mathematical proofs and Euclidean embedding details are in Arhami and Rohani (2025b) doi:10.48550/arXiv.2508.01733.
Deprecations
create_diagnostic_plots()is deprecated in favor ofplot_mcmc_diagnostics(), so that every plotting function shares theplot_*prefix. The old function still works and warns; it will be removed in version 3.0.0.- **Migration**:
create_diagnostic_plots(...)-->plot_mcmc_diagnostics(...) - **Migration**: argument
res-->dpi
New Features
Subsampling for Computational Efficiency
- **Major Feature**: Added the optional
opt_subsampleparameter to key optimization functions, enabling efficient parameter optimization on large datasets while maintaining final embedding quality. Parameter optimization still works reliably with subsampling, because likelihoods of samples of the same size are comparable, allowing us to choose the optimal parameter values. - **New Functions**:
check_matrix_connectivity(): Validates that a dissimilarity matrix forms a connected graphsubsample_dissimilarity_matrix(): Creates random subsamples with automatic connectivity validation and adaptive size adjustment
Show all changes in 2.1.0Hide the rest of the changes in 2.1.0
sanity_check_subsample(): Validates subsample suitability for cross-validationprune_sparse_matrix(): Prunes sparse dissimilarity matrices to a well-connected subsetplot_mcmc_diagnostics(): Replacement forcreate_diagnostic_plots()(see Deprecations)plot_ll_improvement(),plot_performance_trace(): Diagnostics for the parameter searchplot_euclidify_diagnostics(),create_diagnostic_report(): Diagnostics forEuclidify()output- **Enhanced Functions**:
initial_parameter_optimization(): Now acceptsopt_subsampleparameterrun_adaptive_sampling(): Now acceptsopt_subsampleparameteradaptive_MC_sampling(): Now acceptsopt_subsampleparameter (internal)Euclidify(): Now acceptsopt_subsampleparameter
How Subsampling Works
When opt_subsample is specified:
1. Each parameter evaluation uses a random subsample of the specified size
2. Connectivity is automatically validated; disconnected subsamples are rejected
3. If connectivity fails, sample size needs to be increased
4. Different parameter evaluations use different subsamples for robustness
5. **Final embedding always uses the full dataset**
The opt_subsample parameter is optional (default: NULL = use full data).
Performance Benefits
- Substantially reduces the cost of parameter optimization on large datasets (>500 points), since each evaluation embeds a subsample rather than the full matrix
- Reduces memory usage proportional to subsample size
- Parameters found on subsamples generalize well to full data
Recommendations
- Datasets < 500 points: Use full data (
opt_subsample = NULL) - Datasets > 500 points: Recommended
opt_subsample = 200-500 - Always ensure
opt_subsample >= foldsfor reliable cross-validation
Epoch-Based Parameter Search
initial_parameter_optimization()gained anepochsargument (default: 1). With more than one epoch the search runs as a simple evolutionary strategy: 1. **Initialization**: start from the user-provided parameter ranges. 2. **Epoch loop**, for each epoch: a. Generatenum_samplespoints by Latin hypercube sampling within the current ranges. b. Ifopt_subsampleis set, each evaluation uses its own random subsample. c. Evaluate the parameter sets by cross-validation, in parallel batches. d. **Range update** (after every epoch but the last): sort by NLL, keep the top 50%, and set the next epoch's range to0.75 * min(survivors)through1.25 * max(survivors). This lets the search drift toward and zoom in on promising regions. 3. **Finalization**: log-transform the results of the final epoch for direct use with adaptive sampling.
Other changes
- Package
gridExtrais a required import now.
Bug Fixes
initial_parameter_optimization()(and thereforeEuclidify()) could fail on Windows withinvalid connectionwhenever the parameter search ran in more than one batch. A cluster shutdown handler was registered per batch, and all of them referred to the last cluster, so it was stopped repeatedly while the earlier clusters leaked. Each batch's cluster is now stopped as soon as that batch finishes. The failure was reliably triggered bymax_cores = 2, which is the configuration used on CRAN check machines.- Conversion of matrices to numeric in
R/adaptive_sampling.Ris now handled by the package'sextract_numeric_values()function. run_adaptive_sampling()no longer sleeps for a fixed 1.5 seconds on every call. TwoSys.sleep()calls guarded file visibility on networked filesystems (NFS, Lustre); they now poll for the expected state instead, so the wait is effectively free on a local filesystem while networked ones still get up to 5 seconds. This removed about a second from everyEuclidify()run and has no effect on results.
Documentation
- Clarified that the
*_rangearguments ofEuclidify()are a **starting range for the search, not a hard bound**. Initial sampling draws inside the range, but adaptive refinement samples from a kernel density estimate of the best parameter sets found so far, which can propose values outside it. Sondim_range = c(3, 6)does not guarantee a 3- to 6-dimensional result; only the sanity limits (for example1 <= ndim <= 50) are enforced. This behaviour is unchanged from earlier versions and is now documented, in a new section of?Euclidify. Useeuclidean_embedding()with an explicitndimwhen a hard cap is required.
Improvements
- Enhanced connectivity checking using igraph
- Better error messages for disconnected data
- Adaptive strategies for handling sparse data
- Comprehensive logging of subsampling operations
- New diagnostic plots including MCMC exploration and parameter fit traces
C++ Backend for Core Optimization
- **This is the first release of topolow containing compiled code.** The core optimization loop of
euclidean_embedding()has been rewritten in C++ using Rcpp, which speeds up the embedding substantially, most visibly on larger matrices. The remaining R-level loops in that function were replaced with vectorized operations. - **New parameter for
euclidean_embedding()**: convergence_check_freq: How often, in iterations, to test for convergence (default: 3). Larger values reduce the bookkeeping overhead; smaller values stop more promptly.- **Implementation details**:
- **COO format**: edge data is held as a coordinate list, which avoids the zero-dropping behaviour of sparse matrix classes
- **Shuffled sweep order**: the sweep order is permuted each iteration with a C++ Mersenne Twister (
std::mt19937), which is what lets the configuration escape local optima - **Immediate updates**: Gauss-Seidel style in-place position updates, as in the original R implementation
- **Single-pass error calculation**: the MAE over active constraints is accumulated in one pass over the edge list at each convergence check
- **Cache-friendly layout**: edge data is stored in contiguous arrays
- **Pre-computed factors**: degree-based normalization factors are computed once, before the iteration begins
- **Direct memory access**: the inner loop writes to the raw column-major position buffer
- **Return value enhancement**: the
convergencefield of the returnedtopolowobject now includes: achieved: whether convergence was reachederror: final MAE on active constraintsfinal_k: final spring constant after cooling- **Dependencies**: added
RcpptoLinkingTo(compile-time only; no new runtime dependency). The backend is Rcpp-only and links no BLAS or LAPACK routines, so nosrc/Makevarsis required on any platform.
Reproducibility and numerical equivalence
- The algorithm, its force model, cooling schedule and convergence criterion are unchanged from 2.0.1. Results from this version are **statistically equivalent** to 2.0.1 but **not bit-identical**, for two reasons:
- The sweep order is randomized by a C++ generator seeded from
std::random_device, independent of R's RNG.set.seed()fixes the starting configuration (drawn withstats::runif()) but not the sweep order, so repeated runs differ slightly. The R implementation in 2.0.1 shuffled the sweep order in the same way. - The convergence MAE (
result$convergence$error) is now accumulated in package code rather than by a BLAS routine, so it can differ in the last representable digit from earlier development builds. This is far below the run-to-run variation produced by the shuffle, and it makes the value consistent across platforms, which it previously was not. Embedded positions are unaffected. - See the new
Reproducibilitysection of?euclidean_embeddingfor what is and is not seeded, and for how to obtain a single canonical map.
Backward Compatibility
- All existing code using
euclidean_embedding()will work without modification - Default parameter values preserve the original algorithm's behavior
- Output structure remains compatible with downstream functions (
Euclidify(), parameter optimization, etc.)
From NEWS.md in the release
Install
Health
- OK13 OK2026-08-04
- NOTE12 OK, 1 NOTE2026-08-01
- OK13 OK2026-06-08
- ERROR12 OK, 1 ERROR2026-06-07
- OK14 OK2026-03-10
Documentation
- Help pages
- 79 (19 internal)includes a package overview page
- Examples that run
- 82%31 run in a plain check (1 of them under a condition) · 7 only when donttest examples are enabled (R CMD check --as-cran)
- Exported functions with examples
- 71%
- Documented parameters
- 95%
- Return-value docs
- 98%
- Help pages citing references
- 0%
Downloads
Repository
- PRs merged (all time)
- not measured
- Median days to close, 50 most recently updated closed issues
- 15
- Median days to close, 50 most recently updated closed PRs
- not measured
Stars over time
Forks over time
Releases over time
Issues over time
Commits over time
8 development-tooling and community-health practices detected across 6 families in the upstream repository
Checks run against github.com/omid-arhami/topolow on 2026-10-18.
The R CMD check workflow runs on Linux, macOS and Windows, and on R-devel
Show all practicesHide 2 practices
This repository shows signs of AI coding tools.
- Claude Codefirst seen 2026-09-02Seen in the repository: CLAUDE.mdNamed in .Rbuildignore: CLAUDE.md
Dependencies
Installing topolow adds 41 packages (23 with compiled code, 42 MB of source tarballs). Heaviest: stringi 11 MB, ggplot2 6.0 MB, data.table 5.7 MB.
Code & Tests
Datasets
People & History
Pick two releases to compare their code metrics.
- 2.1.0Latest2026-09-14 · current release · diff ↗ ·
added 9 exports
check_matrix_connectivity, create_diagnostic_report, plot_euclidify_diagnostics, plot_ll_improvement, plot_mcmc_diagnostics, plot_performance_trace, prune_sparse_matrix, sanity_check_subsample, subsample_dissimilarity_matrix - RR 4.6.0 released · 2026-04-24
- 2.0.12025-08-31 · diff ↗
- 2.0.02025-08-19 · diff ↗ ·
removed 21 exports
add_noise_bias, c25_claud, c25_old, c25_older, calculate_annual_distances, calculate_cumulative_distances, calculate_procrustes_difference, calculate_procrustes_significance, create_and_optimize_RACMACS_map, dist_to_titer_table, find_mode, generate_complex_data, generate_synthetic_datasets, generate_unique_string, increase_na_percentage, long_to_matrix, only_virus_vs_as, plot_distance_heatmap, prepare_heatmap_data, prune_distance_network, unweighted_kdeadded 6 exports
Euclidify, euclidean_embedding, extract_numeric_values, list_to_matrix, table_to_matrix, titers_list_to_matrix - 1.0.02025-07-11
- RR 4.5.0 released · 2025-04-11
Package metadata
- First published
- 2025-07-11
- Total releases
- 4 / 1 yrs
- License
- BSD_3_clause + file LICENSE OSI
- Minimum R
- ≥ 4.1.0
- Bundled data
- 333 KB / 5 files
- Download size
- 1.3 MB
Cite
Cite this package
Run in R for the authors' entry:
citation("topolow")The package provides a citation file, so citation() returns the authors' own entry.
It points to 10.1093/bioinformatics/btaf372, 10.48550/arxiv.2508.01733.
Cite the R Observatory
For a number measured here: a download total, a coverage figure, an archival date.
From data release v2026-10-22, which the citation names so these numbers can be found later. More on citing and the projects behind them.