|
| 1 | +# aggregate_aux: single legacy vector, single named column out of a |
| 2 | +# data.frame, and multi-column data.frame - across get_distance_pair(), |
| 3 | +# get_distance_matrix(), normal/CH/CCH graphs and cpp_simplify(). |
| 4 | + |
| 5 | +# A strictly linear, tie-free graph: only one possible path between any two |
| 6 | +# nodes, so aggregate_aux values can be hand-verified exactly. |
| 7 | +line_edges <- function() { |
| 8 | + data.frame(from = c(1, 2, 3), to = c(2, 3, 4), dist = c(4, 6, 2)) |
| 9 | +} |
| 10 | + |
| 11 | +test_that("legacy single aux vector: aggregate_aux = TRUE returns a plain vector/matrix", { |
| 12 | + e <- line_edges() |
| 13 | + g <- makegraph(e, directed = TRUE, aux = e$dist * 2) # aux = 2x the main weight |
| 14 | + |
| 15 | + d <- get_distance_pair(g, 1, 4) |
| 16 | + a <- get_distance_pair(g, 1, 4, aggregate_aux = TRUE) |
| 17 | + expect_true(is.numeric(a) && !is.list(a)) |
| 18 | + expect_equal(a, 2 * d) |
| 19 | + |
| 20 | + m <- get_distance_matrix(g, c(1, 2), c(3, 4)) |
| 21 | + am <- get_distance_matrix(g, c(1, 2), c(3, 4), aggregate_aux = TRUE) |
| 22 | + expect_true(is.matrix(am)) |
| 23 | + expect_equal(unname(am), unname(2 * m)) |
| 24 | +}) |
| 25 | + |
| 26 | +test_that("data.frame aux with a single column resolved returns a length-1 list, not a bare vector/matrix", { |
| 27 | + e <- line_edges() |
| 28 | + g <- makegraph(e, directed = TRUE, aux = data.frame(dist2 = e$dist * 2)) |
| 29 | + |
| 30 | + a <- get_distance_pair(g, 1, 4, aggregate_aux = TRUE) |
| 31 | + expect_s3_class(a, "data.frame") |
| 32 | + expect_named(a, "dist2") |
| 33 | + expect_equal(a$dist2, 2 * get_distance_pair(g, 1, 4)) |
| 34 | + |
| 35 | + am <- get_distance_matrix(g, c(1, 2), c(3, 4), aggregate_aux = TRUE) |
| 36 | + expect_type(am, "list") |
| 37 | + expect_named(am, "dist2") |
| 38 | + expect_true(is.matrix(am$dist2)) |
| 39 | +}) |
| 40 | + |
| 41 | +test_that("multi-column data.frame aux: aggregate_aux = TRUE returns all columns, named", { |
| 42 | + e <- line_edges() |
| 43 | + g <- makegraph(e, directed = TRUE, aux = data.frame(hop = rep(1, nrow(e)), dist2 = e$dist * 2)) |
| 44 | + d <- get_distance_pair(g, 1, 4) |
| 45 | + |
| 46 | + a <- get_distance_pair(g, 1, 4, aggregate_aux = TRUE) |
| 47 | + expect_s3_class(a, "data.frame") |
| 48 | + expect_named(a, c("hop", "dist2")) |
| 49 | + expect_equal(a$hop, 3) # 3 edges on the only path 1->2->3->4 |
| 50 | + expect_equal(a$dist2, 2 * d) |
| 51 | + |
| 52 | + am <- get_distance_matrix(g, c(1, 2), c(3, 4), aggregate_aux = TRUE) |
| 53 | + expect_type(am, "list") |
| 54 | + expect_named(am, c("hop", "dist2")) |
| 55 | +}) |
| 56 | + |
| 57 | +test_that("character vector aggregate_aux selects a subset of columns", { |
| 58 | + e <- line_edges() |
| 59 | + g <- makegraph(e, directed = TRUE, aux = data.frame(hop = rep(1, nrow(e)), dist2 = e$dist * 2, dist3 = e$dist * 3)) |
| 60 | + |
| 61 | + a <- get_distance_pair(g, 1, 4, aggregate_aux = c("dist2", "dist3")) |
| 62 | + expect_named(a, c("dist2", "dist3")) |
| 63 | + expect_false("hop" %in% names(a)) |
| 64 | + |
| 65 | + a1 <- get_distance_pair(g, 1, 4, aggregate_aux = "hop") |
| 66 | + expect_s3_class(a1, "data.frame") |
| 67 | + expect_named(a1, "hop") |
| 68 | +}) |
| 69 | + |
| 70 | +test_that("aggregate_aux errors on unknown column names and on a graph without aux", { |
| 71 | + e <- line_edges() |
| 72 | + g_no_aux <- makegraph(e, directed = TRUE) |
| 73 | + expect_error(get_distance_pair(g_no_aux, 1, 4, aggregate_aux = TRUE)) |
| 74 | + |
| 75 | + g <- makegraph(e, directed = TRUE, aux = data.frame(hop = rep(1, nrow(e)))) |
| 76 | + expect_error(get_distance_pair(g, 1, 4, aggregate_aux = "not_a_column")) |
| 77 | +}) |
| 78 | + |
| 79 | +test_that("negative aux values trigger a warning at makegraph() time", { |
| 80 | + e <- line_edges() |
| 81 | + expect_warning(makegraph(e, directed = TRUE, aux = c(-1, 2, 3))) |
| 82 | + expect_warning(makegraph(e, directed = TRUE, aux = data.frame(a = c(-1, 2, 3)))) |
| 83 | +}) |
| 84 | + |
| 85 | +test_that("multi-column aggregate_aux agrees between normal graph and CH (no ties)", { |
| 86 | + g <- grid_graph(6, 6, directed = TRUE, with_aux = TRUE) |
| 87 | + ch <- cpp_contract(g, silent = TRUE) |
| 88 | + |
| 89 | + set.seed(11) |
| 90 | + nodes <- g$dict$ref |
| 91 | + from <- sample(nodes, 20, replace = TRUE) |
| 92 | + to <- sample(nodes, 20, replace = TRUE) |
| 93 | + |
| 94 | + a_normal <- get_distance_pair(g, from, to, aggregate_aux = TRUE) |
| 95 | + a_ch <- get_distance_pair(ch, from, to, aggregate_aux = TRUE) |
| 96 | + expect_equal(a_normal$hop, a_ch$hop, tolerance = 1e-8) |
| 97 | + expect_equal(a_normal$dist2, a_ch$dist2, tolerance = 1e-6) |
| 98 | + |
| 99 | + m_normal <- get_distance_matrix(g, from[1:6], to[1:6], aggregate_aux = TRUE) |
| 100 | + m_ch <- get_distance_matrix(ch, from[1:6], to[1:6], aggregate_aux = TRUE) |
| 101 | + expect_equal(unname(m_normal$hop), unname(m_ch$hop), tolerance = 1e-8) |
| 102 | + expect_equal(unname(m_normal$dist2), unname(m_ch$dist2), tolerance = 1e-6) |
| 103 | +}) |
| 104 | + |
| 105 | +test_that("aggregate_aux on a tied-cost graph: the aux column proportional to the main weight always matches exactly, even when paths differ", { |
| 106 | + # toy_graph has genuine cost ties (e.g. 1->4 via 1-2-4 or 1-3-4, both cost |
| 107 | + # 5) - documented, expected behavior (see get_distance_pair()'s @details). |
| 108 | + # dist2 = 2 * dist is linear in the optimized weight, so its sum along any |
| 109 | + # optimal path always equals 2 * distance, regardless of which tied path a |
| 110 | + # given algorithm/graph representation happens to pick. |
| 111 | + g <- toy_graph(aux = toy_aux()) |
| 112 | + ch <- cpp_contract(g, silent = TRUE) |
| 113 | + |
| 114 | + nodes <- g$dict$ref |
| 115 | + from <- rep(nodes, times = length(nodes)) |
| 116 | + to <- rep(nodes, each = length(nodes)) |
| 117 | + |
| 118 | + d_normal <- get_distance_pair(g, from, to) |
| 119 | + a_normal <- get_distance_pair(g, from, to, aggregate_aux = TRUE) |
| 120 | + a_ch <- get_distance_pair(ch, from, to, aggregate_aux = TRUE) |
| 121 | + |
| 122 | + ok <- !is.na(d_normal) |
| 123 | + expect_equal(a_normal$dist2[ok], 2 * d_normal[ok], tolerance = 1e-8) |
| 124 | + expect_equal(a_ch$dist2[ok], 2 * d_normal[ok], tolerance = 1e-8) |
| 125 | +}) |
| 126 | + |
| 127 | +test_that("cpp_simplify sums multi-column aux correctly across merged edge chains", { |
| 128 | + # a-b-c-d is a pure chain (b, c are non-intersection nodes and get |
| 129 | + # contracted away by cpp_simplify); the merged edge's aux must equal the |
| 130 | + # sum over the 3 original edges. |
| 131 | + e <- data.frame(from = c("a", "b", "c"), to = c("b", "c", "d"), dist = c(1, 2, 3)) |
| 132 | + aux <- data.frame(hop = c(1, 1, 1), dist2 = e$dist * 2) |
| 133 | + g <- makegraph(e, directed = TRUE, aux = aux) |
| 134 | + |
| 135 | + simplified <- cpp_simplify(g, iterate = TRUE) |
| 136 | + expect_equal(nrow(simplified$data), 1) # a-b-c-d collapsed to a single a->d edge |
| 137 | + |
| 138 | + a <- get_distance_pair(simplified, "a", "d", aggregate_aux = TRUE) |
| 139 | + expect_equal(a$hop, 3) |
| 140 | + expect_equal(a$dist2, sum(aux$dist2)) |
| 141 | +}) |
0 commit comments