2020# ' See \strong{Details} section for further explanations
2121# ' @param algo One of "dijkstra" or "diffusion". "diffusion" is suited for
2222# ' connectivity matrices only
23- # ' @param which.dijkstra one of "igraph", "fast" or "slow". "auto" (default)
24- # ' chooses for you. See \strong{Details} section
23+ # ' @param which.dijkstra one of "cpp", " igraph", "fast" or "slow". "auto"
24+ # ' (default) chooses for you. See \strong{Details} section
2525# ' @param dijkstra.ncores number of cores to use for Dijkstra's algorithm.
2626# ' Ignored when \code{which.dijkstra = "igraph"}
2727# ' @param dijkstra.tol number of sequential iterations with identical best
2828# ' neighbours found to consider that Dijkstra's algorithm should be stopped.
29- # ' Ignored when \code{which.dijkstra = "igraph"}
29+ # ' Ignored when \code{which.dijkstra = "igraph" | "cpp" }
3030# ' @param diffusion.iter maximum number of iterations to reach \code{k.target}
3131# ' @param verbose whether to print progress messages
3232# '
4343# '
4444# ' One can choose to keep the graph as it is and consider it as a directed graph
4545# ' (\code{do.symmetrize = FALSE}).
46+ # '
4647# ' The alternative solution is to use all computed distances to extend the knn
4748# ' graph by making the matrix symmetric. Note that connectivity graphs are
4849# ' already symmetric, so the argument value should have no effect on the result.
4950# '
51+ # ' The choice of Dijkstra's algorithm implementation (\code{which.dijkstra}) is
52+ # ' handled automatically by default. The igraph implementation is only preferred
53+ # ' when the number of cells is low (1,000 at most). However, for a large
54+ # ' \code{k.target} value (from approximately 1,000), igraph is faster on a single
55+ # ' thread and thus can be imposed by setting \code{which.dijkstra = 'igraph'}.
56+ # '
57+ # ' "fast" and "slow" are pure R implementations. "fast" is faster than "slow"
58+ # ' but is not accurate on symmetric graph because it is suited for knn graphs
59+ # ' with a constant k value. "slow" is slower but more accurate. No matter,
60+ # ' \strong{"slow" and "fast" are both much slower than "cpp" and are deprecated}.
61+ # ' They remain available in the even that some users have trouble running the
62+ # ' "cpp" implementation.
63+ # '
64+ # ' @note
65+ # ' igraph implementation of Dijkstra's algorithm is single threaded only.
66+ # '
67+ # ' "cpp" implementation is parallelized with OpenMP
68+ # '
5069# ' @importFrom SeuratObject DefaultAssay Cells as.Graph
5170# '
5271# ' @export
@@ -56,7 +75,7 @@ setGeneric("ExpandNeighbours",
5675 graph.type = c(" distances" , " connectivities" ),
5776 k.target = 90L , do.symmetrize = FALSE ,
5877 algo = c(" dijkstra" , " diffusion" ),
59- which.dijkstra = c(" auto" , " igraph" , " fast" , " slow" ),
78+ which.dijkstra = c(" auto" , " cpp " , " igraph" , " fast" , " slow" ),
6079 dijkstra.ncores = 1L , dijkstra.tol = 1L , diffusion.iter = 26L ,
6180 assay = NULL , verbose = TRUE )
6281 standardGeneric(" ExpandNeighbours" ))
@@ -68,7 +87,7 @@ setMethod("ExpandNeighbours", "Seurat",
6887 graph.type = c(" distances" , " connectivities" ),
6988 k.target = 90L , do.symmetrize = FALSE ,
7089 algo = c(" dijkstra" , " diffusion" ),
71- which.dijkstra = c(" auto" , " igraph" , " fast" , " slow" ),
90+ which.dijkstra = c(" auto" , " cpp " , " igraph" , " fast" , " slow" ),
7291 dijkstra.ncores = 1L , dijkstra.tol = 1L , diffusion.iter = 26L ,
7392 assay = NULL , verbose = TRUE ) {
7493 assay <- assay %|| % DefaultAssay(object )
@@ -118,18 +137,19 @@ setMethod("ExpandNeighbours", "Seurat",
118137setGeneric ("expand_neighbours_dijkstra ",
119138 function (object , graph.type = c(" distances" , " connectivities" ),
120139 k.target = 90L , do.symmetrize = FALSE ,
121- which.dijkstra = c(" auto" , " igraph" , " fast" , " slow" ),
140+ which.dijkstra = c(" auto" , " cpp " , " igraph" , " fast" , " slow" ),
122141 ncores = 1L , tol = 1L , verbose = TRUE )
123142 standardGeneric(" expand_neighbours_dijkstra" ))
124143
125144# ' @importFrom SeuratObject as.Neighbor
126- # ' @importFrom Matrix sparseMatrix drop0
145+ # ' @importFrom Matrix sparseMatrix t drop0
146+ # ' @importFrom RhpcBLASctl omp_get_max_threads omp_set_num_threads
127147# ' @keywords internal
128148# ' @noRd
129149setMethod ("expand_neighbours_dijkstra ", "Matrix",
130150 function (object , graph.type = c(" distances" , " connectivities" ),
131151 k.target = 90L , do.symmetrize = FALSE ,
132- which.dijkstra = c(" auto" , " igraph" , " fast" , " slow" ),
152+ which.dijkstra = c(" auto" , " cpp " , " igraph" , " fast" , " slow" ),
133153 ncores = 1L , tol = 1L , verbose = TRUE ) {
134154 n <- ncol(object )
135155 const.k <- is.kconstant(object )
@@ -142,14 +162,17 @@ setMethod("expand_neighbours_dijkstra", "Matrix",
142162 }
143163
144164 if (which.dijkstra == " auto" ) {
145- if (n < = 1e4 ) {
146- which.dijkstra <- " igraph"
147- } else if (const.k ) {
148- which.dijkstra <- " fast"
165+ which.dijkstra <- if (n < = 1e4 ) {
166+ " igraph"
149167 } else {
150- which.dijkstra <- " slow "
168+ " cpp "
151169 }
152170 }
171+ if (which.dijkstra == " cpp" ) {
172+ oomp <- omp_get_max_threads()
173+ omp_set_num_threads(ncores )
174+ on.exit(omp_set_num_threads(oomp ))
175+ }
153176 object.symmetry <- const.k.symmetry <- NULL
154177 igraph.mode <- " directed"
155178 if (do.symmetrize && ! isSymmetric(object )) {
@@ -189,7 +212,7 @@ setMethod("expand_neighbours_dijkstra", "Matrix",
189212 warning(" When all cells have the same number k of nearest " ,
190213 " neighbors, " , sQuote(" fast" ), " implementation of " ,
191214 " Dijkstra's algorithm is recommended" )
192- } else {
215+ } else if ( which.dijkstra == " fast " ) {
193216 object <- as.Neighbor(x = object )
194217 }
195218 } else if (which.dijkstra == " fast" ) {
@@ -204,6 +227,7 @@ setMethod("expand_neighbours_dijkstra", "Matrix",
204227 message(msg [verbose ], appendLF = F )
205228 beginning <- Sys.time()
206229 res <- switch (which.dijkstra ,
230+ cpp = dijkstra_cpp(m = t(drop0(object )), k = k.target ),
207231 igraph = dijkstra.igraph(knnmat = object , k.target = k.target ,
208232 mode = igraph.mode , weighted = T ,
209233 diag = F ),
@@ -225,17 +249,31 @@ setMethod("expand_neighbours_dijkstra", "Matrix",
225249 i <- rep(1 : nrow(res $ knn.idx ), k.target )
226250 j <- as.vector(res $ knn.idx )
227251 x <- as.vector(res $ knn.dist )
228- # correct igraph output when not enough neighbours
229- infs <- which(is.infinite(x ))
230- if (length(infs ) > 0 ) {
231- x [is.infinite(x )] <- 0
232- warning(' Dijkstra (igraph) : could not find enough neighbours' ,
233- ' for ' , length(infs ), ' cell(s) ' ,
234- paste0(infs , collapse = ' , ' ),
235- call. = F , immediate. = F )
252+ if (which.dijkstra == " igraph" ) {
253+ # correct igraph output when not enough neighbours
254+ infs <- which(is.infinite(x ))
255+ if (length(infs ) > 0 ) {
256+ less_neighbours <- sort(unique(i [infs ]))
257+ i <- i [- infs ]
258+ j <- j [- infs ]
259+ x <- x [- infs ]
260+ warning(' Dijkstra (igraph): could not find enough neighbours' ,
261+ ' for ' , length(less_neighbours ), ' cell(s) ' ,
262+ paste0(less_neighbours , collapse = ' , ' ),
263+ call. = F , immediate. = F )
264+ }
236265 }
266+
237267 expanded.mat <- sparseMatrix(i = i , j = j , x = x , dims = rep(n , 2 ))
238- expanded.mat <- drop0(expanded.mat )
268+ if (which.dijkstra == " cpp" ) {
269+ less_neighbours <- which(get.k(expanded.mat , ' all' ) < k.target )
270+ if (length(less_neighbours ) > 0 ) {
271+ warning(' Dijkstra (cpp): could not find enough neighbours' ,
272+ ' for ' , length(less_neighbours ), ' cell(s) ' ,
273+ paste0(less_neighbours , collapse = ' , ' ),
274+ call. = F , immediate. = F )
275+ }
276+ }
239277 if (do.symmetrize && which.dijkstra == " fast" ) {
240278 expanded.mat <- SymmetrizeKnn(expanded.mat , use.max = FALSE )
241279 }
@@ -249,7 +287,7 @@ setMethod("expand_neighbours_dijkstra", "Matrix",
249287setMethod ("expand_neighbours_dijkstra ", "Neighbor",
250288 function (object , graph.type = c(" distances" , " connectivities" ),
251289 k.target = 90L , do.symmetrize = FALSE ,
252- which.dijkstra = c(" auto" , " igraph" , " fast" , " slow" ),
290+ which.dijkstra = c(" auto" , " cpp " , " igraph" , " fast" , " slow" ),
253291 ncores = 1L , tol = 1L , verbose = TRUE ) {
254292
255293 return (expand_neighbours_dijkstra(as.Graph(object ),
0 commit comments