expect_distance_structure = function(D, tolerance = 1e-10) { D = as.matrix(D) testthat::expect_true(is.numeric(D)) testthat::expect_true(length(dim(D)) == 2L) testthat::expect_identical(nrow(D), ncol(D)) testthat::expect_true(all(is.finite(D))) testthat::expect_true(all(D >= -tolerance)) testthat::expect_true(max(abs(D - t(D))) <= tolerance) testthat::expect_true(all(abs(diag(D)) <= tolerance)) invisible(D) } expect_positive_off_diagonal = function(D, tolerance = 1e-10) { D = as.matrix(D) if (nrow(D) > 1L) { testthat::expect_true(all(D[upper.tri(D)] > tolerance)) } invisible(D) } triangle_excess = function(D) { D = as.matrix(D) n = nrow(D) maximum = -Inf for (j in seq_len(n)) { via_j = outer(D[, j], D[j, ], FUN = "+") maximum = max(maximum, D - via_j) } maximum } expect_triangle_inequality = function(D, tolerance = 1e-10) { testthat::expect_lte(triangle_excess(D), tolerance) invisible(D) } expect_triangle_violation = function(D, tolerance = 1e-10) { testthat::expect_gt(triangle_excess(D), tolerance) invisible(D) } .validate_property_value = function(value, tolerance) { if (!is.numeric(value) || length(value) != 1L || !is.finite(value) || value < -tolerance) { stop("distance_function must return one finite non-negative number") } max(as.numeric(value), 0) } pairwise_property_matrix = function(objects, distance_function, expect_symmetric = TRUE, tolerance = 1e-10) { n = length(objects) D = matrix(0, nrow = n, ncol = n) for (i in seq_len(n)) { self = .validate_property_value( distance_function(objects[[i]], objects[[i]]), tolerance ) testthat::expect_lte(self, tolerance) D[i, i] = self } if (n > 1L) { for (i in seq.int(2L, n)) { for (j in seq_len(i - 1L)) { forward = .validate_property_value( distance_function(objects[[i]], objects[[j]]), tolerance ) reverse = .validate_property_value( distance_function(objects[[j]], objects[[i]]), tolerance ) if (isTRUE(expect_symmetric)) { testthat::expect_equal(forward, reverse, tolerance = tolerance) } D[i, j] = forward D[j, i] = reverse } } } D }