| Title: | Rust backend for smotif motif enumeration |
|---|---|
| Description: | Drop-in Rust acceleration for the smotif R package: parallel enumeration of size-2 / size-3 / size-4 multi-cellular motifs in spatial omics graphs, plus direct parquet I/O for million-cell datasets. Distributed as pre-compiled binaries via R-universe. |
| Authors: | Ruben Dries [aut, cre] |
| Maintainer: | Ruben Dries <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.2.0 |
| Built: | 2026-10-15 23:16:37 UTC |
| Source: | https://github.com/drieslab/smotifrs |
The order returned here is the order cell_type must be supplied in to
motif_enrichment_edge_store().
edge_store_nodes(nodes_path)edge_store_nodes(nodes_path)
nodes_path |
path to the store's |
a data.table with node_id and int_id.
Skips the R-side SpatialGraph round-trip: opens both parquet files
inside Rust via arrow-rs, builds a CSR adjacency, and returns the
same Motifs object as find_motifs_rs(). Intended for million-cell
datasets where holding the graph in R memory would be wasteful.
find_motifs_from_parquet( nodes_path, edges_path, size = 3L, colored = TRUE, anchored_on = NULL, max_instances = NULL )find_motifs_from_parquet( nodes_path, edges_path, size = 3L, colored = TRUE, anchored_on = NULL, max_instances = NULL )
nodes_path, edges_path
|
file paths to the parquet outputs of
|
size |
integer 2, 3, or 4. |
colored |
logical; if |
anchored_on |
optional character vector of cell ids to restrict enumeration to motifs touching at least one anchor. |
max_instances |
optional integer cap on enumerated instances. |
a smotif::Motifs S3 object.
Drop-in replacement for smotif::find_motifs() reached via
smotif::find_motifs(..., backend = "rust"). Uses a CSR-backed Rust
kernel with sorted-merge triangle enumeration and rayon-friendly
loops over vertices. Output is bit-for-bit compatible with the
igraph backend so downstream Motifs-consumers (null_models,
genes, compare) work unchanged.
find_motifs_rs( sg, size = 3L, colored = TRUE, anchored_on = NULL, max_instances = NULL )find_motifs_rs( sg, size = 3L, colored = TRUE, anchored_on = NULL, max_instances = NULL )
sg |
a |
size |
integer 2, 3, or 4. |
colored |
logical; if |
anchored_on |
optional character vector of cell ids to restrict enumeration to motifs touching at least one anchor. |
max_instances |
optional integer cap on enumerated instances. |
a smotif::Motifs S3 object.
Reads a parquetEdgeStore in Rust and runs the enrichment without
materializing the graph in R.
motif_enrichment_edge_store( nodes_path, edges_path, cell_type, size = 3L, n_perm = 1000L, seed = 1L, null = c("label", "conditional"), cond_temp = 1 )motif_enrichment_edge_store( nodes_path, edges_path, cell_type, size = 3L, n_perm = 1000L, seed = 1L, null = c("label", "conditional"), cond_temp = 1 )
nodes_path, edges_path
|
paths to the store's |
cell_type |
factor or character vector of labels, one per node, in
sidecar |
size |
motif size: 2, 3 or 4. |
n_perm, seed
|
permutation count and seed. |
null |
|
cond_temp |
Metropolis temperature for the conditional null. |
GiottoDisk interns node ids at write time, so the edge parquet already holds
integer endpoints. Reading those columns straight through skips the
string-to-index hashing that find_motifs_from_parquet() has to do for
smotif's own on-disk format – there is no hash map and no string per cell.
Cell type labels are not in the edge store by design (the node sidecar
carries ids only), so they are supplied here, aligned to the sidecar's
int_id order. edge_store_nodes() returns that order.
a data.table in the same shape as motif_enrichment_rs().
Enumerates every connected induced subgraph of the given size, then tests each colored motif class against a label-permutation null. Enumeration happens once and each permutation is a relabel-and-count over the fixed instance set, so per-instance data never reaches R and the cost of the null is integer arithmetic rather than string construction.
motif_enrichment_rs( from, to, cell_type, n_nodes = NULL, size = 3L, n_perm = 1000L, seed = 1L, null = c("label", "stratified", "conditional"), cond_temp = 1, strata = NULL, anchored_on = NULL )motif_enrichment_rs( from, to, cell_type, n_nodes = NULL, size = 3L, n_perm = 1000L, seed = 1L, null = c("label", "stratified", "conditional"), cond_temp = 1, strata = NULL, anchored_on = NULL )
from, to
|
integer vectors of 1-based node indices, one pair per edge. Direction is ignored and duplicate or reversed edges are collapsed. |
cell_type |
factor or character vector of cell type labels, one per node, in node-index order. |
n_nodes |
number of nodes. Defaults to |
size |
motif size: 2, 3 or 4. |
n_perm |
number of label permutations. |
seed |
integer seed. Results depend only on |
null |
which null to test against.
The conditional null conditions by tolerance, not exactly: a Metropolis
chain over label swaps holds the pairwise table close rather than pinning
it. Three attributes report what the chain actually did – Watch |
cond_temp |
Metropolis temperature for |
strata |
optional factor or integer vector, one per node. Required for
|
anchored_on |
optional integer vector of 1-based node indices. Enumeration is restricted to subgraphs containing at least one of them. |
The graph is supplied as plain vectors rather than as any package's graph class, so callers outside smotif can use it directly. Cell types are passed as a factor (or coerced to one), which fixes the level ordering on the R side – the backend never applies its own collation.
a data.table, one row per motif class, with columns motif_id,
topology, size, color_tuple (a list column of labels in canonical
orbit order), observed, expected, sd_null, z, fold, p_enrich,
p_deplete and p_adj. n_instances is attached as an attribute.
Positions within color_tuple are structural roles, not an arbitrary
sort, so A-B-B and B-A-A are different motifs. The first position is
the distinguished vertex wherever a topology has one: the centre of a
open wedge or a claw, and the degree-3 vertex of a paw. For path
the tuple runs end to end, for cycle it runs around the ring, and for
closed (triangle), diamond and K4 the remaining positions are
interchangeable within their degree class.
# a 6-cycle alternating between two cell types from <- c(1L, 2L, 3L, 4L, 5L, 6L) to <- c(2L, 3L, 4L, 5L, 6L, 1L) ct <- rep(c("A", "B"), 3) motif_enrichment_rs(from, to, ct, size = 3L, n_perm = 99L)# a 6-cycle alternating between two cell types from <- c(1L, 2L, 3L, 4L, 5L, 6L) to <- c(2L, 3L, 4L, 5L, 6L, 1L) ct <- rep(c("A", "B"), 3) motif_enrichment_rs(from, to, ct, size = 3L, n_perm = 99L)
Runs the enrichment against edges pulled from an Arrow stream rather than read from a file path, so the producer decides what the edge set is.
motif_enrichment_stream( edges, int_ids, cell_type, size = 3L, n_perm = 1000L, seed = 1L, null = c("label", "conditional"), cond_temp = 1 )motif_enrichment_stream( edges, int_ids, cell_type, size = 3L, n_perm = 1000L, seed = 1L, null = c("label", "conditional"), cond_temp = 1 )
edges |
anything |
int_ids |
integer ids keying the label lookup, in the same order as
|
cell_type |
factor or character vector of labels, one per entry of
|
size |
motif size: 2, 3 or 4. |
n_perm, seed
|
permutation count and seed. |
null |
|
cond_temp |
Metropolis temperature for the conditional null. |
motif_enrichment_edge_store() opens a store's parquet files directly,
which means it sees them as they sit on disk. That is wrong whenever the
caller's view of the store differs from its files: a parquetEdgeStore
carrying a pending subset, a dataset written across several files, or a
query over something that is not parquet at all. A stream moves that
decision to the producer –
GiottoDisk::storeRead(x, output = "arrowstream") applies a store's
pending ops before yielding any batch – and leaves this package knowing
only that batches arrive with from_id and to_id columns.
The network's nodes are the stream's endpoints. Labels travel beside the
stream as a lookup keyed by int_ids; mapping cell IDs to those integers
(a store's node sidecar, say) is the caller's business, and nothing here
reads it. The lookup may cover more ids than the stream uses – a node with
no edges is not in the network and does not enter the null – but every
endpoint needs a label.
Ownership of the stream moves to Rust, which releases it when the last batch has been pulled. Pass a fresh stream per call.
a data.table in the same shape as motif_enrichment_rs().
motif_enrichment_edge_store() for the path-based form.
Returns the cells making up each occurrence of the named motif classes.
motif_instances_rs( from, to, cell_type, motif_ids, n_nodes = NULL, size = 3L, max_per_class = 5000 )motif_instances_rs( from, to, cell_type, motif_ids, n_nodes = NULL, size = 3L, max_per_class = 5000 )
from, to
|
integer vectors of 1-based node indices, one pair per edge. Direction is ignored and duplicate or reversed edges are collapsed. |
cell_type |
factor or character vector of cell type labels, one per node, in node-index order. |
motif_ids |
character vector of |
n_nodes |
number of nodes. Defaults to |
size |
motif size: 2, 3 or 4. |
max_per_class |
cap on instances returned per class. |
Deliberately narrow. motif_enrichment_rs() never returns per-instance data
because at size 4 on a real dataset that is tens of millions of rows; this
returns instances for named classes only, so the output is bounded by those
classes' observed counts. Pick the motifs worth looking at first, then ask
for their instances.
a data.table with motif_id, instance, slot (the structural
position within the motif, 1-based) and node (the 1-based node index),
in long form – k rows per instance.
from <- c(1L, 2L, 3L, 4L, 5L, 6L) to <- c(2L, 3L, 4L, 5L, 6L, 1L) ct <- rep(c("A", "B"), 3) e <- motif_enrichment_rs(from, to, ct, size = 3L, n_perm = 19L) motif_instances_rs(from, to, ct, size = 3L, motif_ids = e$motif_id[1])from <- c(1L, 2L, 3L, 4L, 5L, 6L) to <- c(2L, 3L, 4L, 5L, 6L, 1L) ct <- rep(c("A", "B"), 3) e <- motif_enrichment_rs(from, to, ct, size = 3L, n_perm = 19L) motif_instances_rs(from, to, ct, size = 3L, motif_ids = e$motif_id[1])