\documentclass{article} \usepackage[utf8]{inputenc} \usepackage[margin=2.5cm]{geometry} \title{bioIOT: inverse optimal transport for single-cell state transitions} \author{bioIOT package} \date{bioIOT version 0.2.2} %\VignetteIndexEntry{bioIOT quickstart} %\VignetteEngine{utils::Sweave} %\VignetteEncoding{UTF-8} \begin{document} \maketitle bioIOT solves the semi-relaxed inverse optimal transport (IOT) problem: it finds feature weights $\theta$ such that the soft-marginal OT plan induced by the linear cost $C = -\mathrm{einsum}(\phi, \theta)$ reproduces observed state transitions, then turns the plan into transition matrices, flow plots and pseudotime. \section{A reproducible demo in one line} \texttt{simulate\_iot\_states()} generates a synthetic single-cell-like dataset with a known ground truth (states, masses, true plan and transitions, cell-level metadata). The same dataset ships pre-computed as \texttt{demo\_iot\_states}. <>= library(bioIOT) set.seed(1) sim <- simulate_iot_states(K = 6, seed = 1) names(sim) @ \section{Fit feature weights} \texttt{fit\_iot()} runs two-stage (l1 selection then debias refit) multi-restart fitting with exact implicit gradients. <>= fit <- fit_iot(sim$phi, sim$a, sim$b, sim$T_true, n_restart = 2, epochs = 150, seed = 1) fit summary(fit) @ \section{Transition matrix and pseudotime} \texttt{transition\_matrix()} re-solves the plan at the fitted weights; \texttt{pseudotime\_from\_transition()} converts it into random-walk pseudotime (expected hitting times to the root state). <>= Q <- transition_matrix(fit) round(Q, 2) pseudotime_from_transition(Q, root = "S1") @ \section{Visualisation} <>= plot_transition_heatmap(Q) @ <>= plot_transition_flow(Q, sim$embedding, threshold = 0.04) @ <>= plot_theta(fit) @ \section{Single-cell interface} \texttt{runIOT()} works directly on cell-level inputs (base matrix, SingleCellExperiment or Seurat). Without observed transitions \texttt{T\_obs} it solves the plan with uniform feature weights; with \texttt{T\_obs} (e.g.\ from lineage/clone data) it fits the weights first. <>= res <- runIOT(sim$cell_embedding, sim$cell_state, from = sim$cell_time == "t0", to = sim$cell_time == "t1", root = "S1") round(res$Q[1:3, 1:3], 2) res$pseudotime @ \section{Bulk pathway utilities} The package also ships the paper's bulk-cohort pathway tools: 8-dimensional marker scoring (\texttt{score\_pathways}), probe-to-gene collapse (\texttt{collapse\_probes}) and trajectory plots. <>= expr <- matrix(rnorm(200 * 12), nrow = 200) rownames(expr) <- paste0("G", 1:200) colnames(expr) <- paste0("S", 1:12) rownames(expr)[1] <- "VIM" rownames(expr)[2] <- "CDH2" rownames(expr)[3] <- "MKI67" pw <- score_pathways(expr) print(plot_pathway_trend(pw, time = sort(runif(12)))) @ \end{document}