Nothing
context ("dodgr_paths")
test_all <- (identical (Sys.getenv ("MPADGE_LOCAL"), "true") ||
identical (Sys.getenv ("GITHUB_JOB"), "test-coverage"))
test_that ("paths", {
graph <- weight_streetnet (hampi)
from <- graph$from_id [1:100]
to <- graph$to_id [100:150]
to <- to [!to %in% from]
expect_message (
dp <- dodgr_paths (graph,
from = from,
to = to,
quiet = FALSE
),
"Calculating shortest paths ..."
)
expect_is (dp, "list")
expect_length (dp, 100)
expect_identical (unique (vapply (dp, length, integer (1L))), length (to))
expect_is (dp [[1]] [[1]], "character")
expect_true (attr (dp, "vertices"))
lens <- unlist (lapply (dp, lengths))
dp <- dodgr_paths (graph, from = from, to = to, vertices = FALSE)
expect_is (dp, "list")
expect_length (dp, 100)
expect_identical (unique (vapply (dp, length, integer (1L))), length (to))
expect_false (attr (dp, "vertices"))
lens2 <- unlist (lapply (dp, lengths))
# edge lists should all have one less item than vertex lists
lens2 <- lens2 [which (lens > 0)]
lens <- lens [which (lens > 0)]
expect_true (all (abs (lens - lens2) == 1))
})
test_that ("pairwise paths", {
graph <- weight_streetnet (hampi)
from <- graph$from_id [1:10]
to <- graph$to_id [100:105]
indx <- which (!to %in% from)
to <- to [indx]
from <- from [indx]
n <- length (indx)
dp <- dodgr_paths (graph, from = from, to = to, pairwise = TRUE)
expect_is (dp, "list")
expect_length (dp, n)
expect_true (all (lapply (dp, length) == 1))
expect_true (attr (dp, "vertices"))
expect_error (
dp <- dodgr_paths (graph,
from = from, to = to [-1],
pairwise = TRUE
),
"pairwise paths require from and to to have same length"
)
})
test_that ("sort_transitions", {
graph <- data.frame (
edge_id = 1:4,
from = c ("a", "c", "b", "d"),
to = c ("b", "d", "c", "e"),
d = 1:4,
d_weighted = 1:4
)
perm <- sort_transitions (graph)
expect_identical (perm, c (1L, 3L, 2L, 4L))
expect_identical (graph$from [perm], c ("a", "b", "c", "d"))
expect_identical (graph$to [perm], c ("b", "c", "d", "e"))
})
test_that ("dodgr_one_path_expand on synthetic graph", {
# A simple A-to-G chain of 6 original edges, contracted down to 2
# compound edges ('a101' = A-B-C-D, 'a103' = E-F-G) plus one
# pass-through edge ('4' = D-E) which was not aggregated at all, and so
# retains its original edge_id. `edge_map` intentionally lists the
# sub-edges of 'a101' out of order, to confirm that
# `dodgr_one_path_expand` (via `sort_transitions`) correctly re-orders
# them.
graph <- data.frame (
edge_id = 1:6,
from = c ("A", "B", "C", "D", "E", "F"),
to = c ("B", "C", "D", "E", "F", "G"),
d = 1:6,
d_weighted = 1:6
)
graph_c <- data.frame (
edge_id = c ("a101", "4", "a103"),
from = c ("A", "D", "E"),
to = c ("D", "E", "G"),
d = c (6, 4, 11),
d_weighted = c (6, 4, 11)
)
edge_map <- data.frame (
edge_new = c ("a101", "a101", "a101", "a103", "a103"),
edge_old = c (2, 3, 1, 6, 5)
)
path_c_chr <- c ("A", "D", "E", "G")
path <- dodgr_one_path_expand (
path_c_chr,
graph,
graph_c,
edge_map = edge_map
)
# character input returns the full, structurally-identical character
# vector of expanded vertex IDs
expect_type (path, "character")
expect_identical (path, c ("A", "B", "C", "D", "E", "F", "G"))
# equivalent integer-index path over 'graph_c' returns the full,
# structurally-identical integer vector of row indices into 'graph',
# tracing the same underlying sequence of edges as the character version
path_c_int <- match (
paste (path_c_chr [-length (path_c_chr)], path_c_chr [-1]),
paste (graph_c$from, graph_c$to)
)
path_int <- dodgr_one_path_expand (
path_c_int,
graph,
graph_c,
edge_map = edge_map
)
expect_type (path_int, "integer")
expect_identical (path_int, 1:6)
expect_identical (
c (graph$from [path_int], graph$to [path_int] [length (path_int)]),
path
)
# a single (contracted) edge is a valid path requiring no transitions,
# and expands to the pass-through edge's own (single) original edge
path_single <- dodgr_one_path_expand (
2L, # row index of pass-through edge '4', from D to E
graph,
graph_c,
edge_map = edge_map
)
expect_type (path_single, "integer")
expect_identical (path_single, 4L)
expect_identical (graph$from [path_single], "D")
expect_identical (graph$to [path_single], "E")
expect_error (
dodgr_one_path_expand (
path_c_chr [1],
graph,
graph_c,
edge_map = edge_map
),
"There are no transitions."
)
expect_error (
dodgr_one_path_expand (
c ("not_a_vertex", "another_one"),
graph,
graph_c,
edge_map = edge_map
),
"Not all transitions were matched to the contracted graph."
)
expect_error (
dodgr_one_path_expand (
c (1.5, 2.5),
graph,
graph_c,
edge_map = edge_map
),
"Path must be provided as integer or character vector."
)
})
test_that ("dodgr_paths_expand on synthetic graph", {
# Same fixture as the 'dodgr_one_path_expand' test above.
graph <- data.frame (
edge_id = 1:6,
from = c ("A", "B", "C", "D", "E", "F"),
to = c ("B", "C", "D", "E", "F", "G"),
d = 1:6,
d_weighted = 1:6
)
graph_c <- data.frame (
edge_id = c ("a101", "4", "a103"),
from = c ("A", "D", "E"),
to = c ("D", "E", "G"),
d = c (6, 4, 11),
d_weighted = c (6, 4, 11)
)
edge_map <- data.frame (
edge_new = c ("a101", "a101", "a101", "a103", "a103"),
edge_old = c (2, 3, 1, 6, 5)
)
path_c_chr <- c ("A", "D", "E", "G")
chr_expected <- dodgr_one_path_expand (
path_c_chr,
graph,
graph_c,
edge_map = edge_map
)
# A nested list mimicking the (`vertices = TRUE`) structure returned by
# `dodgr_paths`, with two origins and two destinations: a valid
# multi-transition path, a `NULL` (unreachable) entry, a trivial
# single-vertex (no transitions) entry, and a second valid path
# identical to the first.
paths_chr <- list (
list (path_c_chr, NULL),
list (path_c_chr [1], path_c_chr)
)
attr (paths_chr, "vertices") <- TRUE
result_chr <- dodgr_paths_expand (
paths_chr,
graph,
graph_c,
edge_map = edge_map
)
expect_type (result_chr, "list")
expect_identical (attr (result_chr, "vertices"), TRUE)
expect_length (result_chr, length (paths_chr))
expect_identical (
vapply (result_chr, length, integer (1L)),
vapply (paths_chr, length, integer (1L))
)
expect_type (result_chr [[1]] [[1]], "character")
expect_identical (result_chr [[1]] [[1]], chr_expected)
expect_null (result_chr [[1]] [[2]])
expect_null (result_chr [[2]] [[1]])
expect_identical (result_chr [[2]] [[2]], chr_expected)
# The same fixture, but as a (`vertices = FALSE`) nested list of integer
# row indices into `graph_c`, mixed with a `NULL` (unreachable) entry.
path_c_int <- match (
paste (path_c_chr [-length (path_c_chr)], path_c_chr [-1]),
paste (graph_c$from, graph_c$to)
)
int_expected <- dodgr_one_path_expand (
path_c_int,
graph,
graph_c,
edge_map = edge_map
)
paths_int <- list (list (path_c_int, NULL))
attr (paths_int, "vertices") <- FALSE
result_int <- dodgr_paths_expand (
paths_int,
graph,
graph_c,
edge_map = edge_map
)
expect_identical (attr (result_int, "vertices"), FALSE)
expect_type (result_int [[1]] [[1]], "integer")
expect_identical (result_int [[1]] [[1]], int_expected)
expect_null (result_int [[1]] [[2]])
})
# The remaining tests sometimes fail on GitHub runners, due to some kind of
# inconsistency in the underlying C++ graph contraction. This can't be
# reproduced locally. This will also skip these tests on CRAN.
testthat::skip_if (!test_all)
test_that ("dodgr_one_path_expand on real network", {
graph <- weight_streetnet (hampi)
graph_c <- dodgr_contract_graph (graph)
verts <- dodgr_vertices (graph_c)
# Restrict candidates to the same (largest) connected component, because
# `hampi` is not a single connected component, so an arbitrary pair may
# fall in different components with no path between them. (Selecting
# vertices by raw position in `verts$id` is not reliable here, because
# the character sort order of `id` values is locale-dependent.)
comp_sizes <- table (verts$component)
largest_comp <- names (comp_sizes) [which.max (comp_sizes)]
verts <- verts [verts$component == as.integer (largest_comp), ]
verts <- verts [order (verts$x), ]
# Try a handful of geographically-distant candidate pairs, from the two
# ends of the longitude range inwards. This guards against the very rare
# case (never seen locally, but observed on some CI runners) in which the
# underlying C++ graph contraction produces a self-inconsistent compound
# edge for a particular pair of vertices, presumably as a result of
# platform-dependent hash-container iteration order. A single such
# unlucky pair should not fail the whole test suite, so any pair that
# trips `sort_transitions`'s contiguous-chain check is simply skipped in
# favor of the next candidate.
n <- nrow (verts)
n_candidates <- min (5L, n)
candidates <- Map (
c,
seq_len (n_candidates),
rev (seq_len (n)) [seq_len (n_candidates)]
)
path_c_chr <- path <- NULL
for (candidate in candidates) {
from <- verts$id [candidate [1]]
to <- verts$id [candidate [2]]
if (from == to) next
path_c_chr_i <- dodgr_paths (graph_c, from = from, to = to) [[1]] [[1]]
if (length (path_c_chr_i) < 3) next
path_i <- tryCatch (
dodgr_one_path_expand (path_c_chr_i, graph, graph_c),
error = function (e) NULL
)
if (!is.null (path_i)) {
path_c_chr <- path_c_chr_i
path <- path_i
break
}
}
expect_true (length (path_c_chr) > 2)
# character input returns a structurally-identical character vector of
# expanded vertex IDs, tracing the same start and end points as the
# contracted path from which it was expanded
expect_type (path, "character")
expect_identical (path [1], path_c_chr [1])
expect_identical (path [length (path)], path_c_chr [length (path_c_chr)])
# every consecutive pair of vertices in the expanded path must
# correspond to an actual edge of the full, uncontracted graph
cols <- dodgr_graph_cols (graph)
graph_ft <- paste (graph [[cols$from]], graph [[cols$to]])
path_ft <- paste (path [-length (path)], path [-1])
expect_true (all (path_ft %in% graph_ft))
# explicitly-supplied edge map gives identical result to internal lookup
edge_map <- get_edge_map (graph)
path_em <- dodgr_one_path_expand (
path_c_chr,
graph,
graph_c,
edge_map = edge_map
)
expect_identical (path, path_em)
# equivalent integer (`vertices = FALSE`-style) path over `graph_c`
# returns a structurally-identical integer vector of row indices into
# `graph`, tracing the same underlying sequence of edges
path_c_int <- match (
paste (path_c_chr [-length (path_c_chr)], path_c_chr [-1]),
paste (graph_c [[cols$from]], graph_c [[cols$to]])
)
path_int <- dodgr_one_path_expand (path_c_int, graph, graph_c)
expect_type (path_int, "integer")
expect_identical (
c (
graph [[cols$from]] [path_int],
graph [[cols$to]] [path_int] [length (path_int)]
),
path
)
})
test_that ("dodgr_paths_expand on real network", {
graph <- weight_streetnet (hampi)
graph_c <- dodgr_contract_graph (graph)
verts <- dodgr_vertices (graph_c)
# Same robust candidate search as the 'dodgr_one_path_expand' test above,
# used here to find (origin, destination) pairs known to expand cleanly,
# so the wrapper can be exercised via `dodgr_paths`' own nested list
# output, for both `vertices = TRUE` and `vertices = FALSE`, and for
# both the default (cross-product) and `pairwise = TRUE` forms.
comp_sizes <- table (verts$component)
largest_comp <- names (comp_sizes) [which.max (comp_sizes)]
verts <- verts [verts$component == as.integer (largest_comp), ]
verts <- verts [order (verts$x), ]
n <- nrow (verts)
n_candidates <- min (10L, n)
candidates <- Map (
c,
seq_len (n_candidates),
rev (seq_len (n)) [seq_len (n_candidates)]
)
good <- list ()
for (candidate in candidates) {
from_i <- verts$id [candidate [1]]
to_i <- verts$id [candidate [2]]
if (from_i == to_i) next
path_c_chr_i <-
dodgr_paths (graph_c, from = from_i, to = to_i) [[1]] [[1]]
if (length (path_c_chr_i) < 3) next
ok <- !inherits (
tryCatch (
dodgr_one_path_expand (path_c_chr_i, graph, graph_c),
error = function (e) e
),
"error"
)
if (ok) {
good [[length (good) + 1L]] <- c (from_i, to_i)
}
if (length (good) >= 2L) {
break
}
}
expect_true (length (good) >= 1L)
from <- vapply (good, `[`, character (1L), 1L)
to <- vapply (good, `[`, character (1L), 2L)
cols <- dodgr_graph_cols (graph)
# -- vertices = TRUE: expanded paths are structurally-identical
# character vectors of vertex IDs --
paths_c <- dodgr_paths (graph_c, from = from, to = to)
paths <- dodgr_paths_expand (paths_c, graph, graph_c)
expect_type (paths, "list")
expect_identical (attr (paths, "vertices"), attr (paths_c, "vertices"))
expect_true (attr (paths, "vertices"))
expect_length (paths, length (from))
expect_length (paths [[1]], length (to))
path <- paths [[1]] [[1]]
path_c_chr <- paths_c [[1]] [[1]]
expect_type (path, "character")
expect_identical (
path,
dodgr_one_path_expand (path_c_chr, graph, graph_c)
)
expect_identical (path [1], path_c_chr [1])
expect_identical (path [length (path)], path_c_chr [length (path_c_chr)])
# -- vertices = FALSE: expanded paths are structurally-identical integer
# vectors of row indices into `graph`, tracing the same vertex sequence
# as the `vertices = TRUE` version above --
paths_c_e <- dodgr_paths (graph_c, from = from, to = to, vertices = FALSE)
paths_e <- dodgr_paths_expand (paths_c_e, graph, graph_c)
expect_identical (attr (paths_e, "vertices"), attr (paths_c_e, "vertices"))
expect_false (attr (paths_e, "vertices"))
path_e <- paths_e [[1]] [[1]]
expect_type (path_e, "integer")
path_e_verts <- c (
graph [[cols$from]] [path_e],
graph [[cols$to]] [path_e] [length (path_e)]
)
expect_identical (path_e_verts, path)
# -- pairwise = TRUE: wrapper correctly handles the pairwise nested
# structure (a list of single-element lists), giving identical results
# to the corresponding cross-product entries above --
paths_c_pw <- dodgr_paths (
graph_c,
from = from,
to = to,
pairwise = TRUE
)
paths_pw <- dodgr_paths_expand (paths_c_pw, graph, graph_c)
expect_type (paths_pw, "list")
expect_true (attr (paths_pw, "vertices"))
expect_length (paths_pw, length (from))
expect_true (all (vapply (paths_pw, length, integer (1L)) == 1L))
expect_identical (paths_pw [[1]] [[1]], path)
if (length (from) > 1L) {
expect_identical (paths_pw [[2]] [[1]], paths [[2]] [[2]])
}
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.