test_that("Euclidean implementations satisfy the metric properties", { X = rbind(c(0, 0), c(1, 0), c(1, 2), c(-1, 1)) Dfast = EuclideanMulticore_Distance(X) expect_distance_structure(Dfast) expect_positive_off_diagonal(Dfast) expect_triangle_inequality(Dfast) Dweighted = EuclideanGPU_Distance( X, Weights = c(1, 3), backend = "cpu", OutputType = "mat" ) expect_distance_structure(Dweighted) expect_positive_off_diagonal(Dweighted) expect_triangle_inequality(Dweighted) # A zero weight removes a coordinate and can collapse different vectors. collapsed = EuclideanGPU_Distance( rbind(c(0, 0), c(0, 1), c(2, 1)), Weights = c(1, 0), backend = "cpu", OutputType = "mat" ) expect_distance_structure(collapsed) expect_triangle_inequality(collapsed) expect_equal(collapsed[1, 2], 0) }) test_that("fractional Minkowski behavior changes at p = 1", { X = rbind(c(0, 0), c(1, 0), c(1, 1)) D1 = Fractional_Distance(X, p = 1) D2 = Fractional_Distance(X, p = 2) expect_distance_structure(D1) expect_distance_structure(D2) expect_positive_off_diagonal(D1) expect_positive_off_diagonal(D2) expect_triangle_inequality(D1) expect_triangle_inequality(D2) Dhalf = Fractional_Distance(X, p = 1 / 2) expect_distance_structure(Dhalf) expect_equal(Dhalf[1, 3], 4) expect_triangle_violation(Dhalf) }) test_that("squared Euclidean and correlation dispatcher routes are classified correctly", { line = matrix(c(0, 1, 2), ncol = 1L) squared = DistanceMatrix(line, method = "sqeuclidean") expect_distance_structure(squared) expect_positive_off_diagonal(squared) expect_equal(squared[1, 3], 4) expect_triangle_violation(squared) expect_positive_off_diagonal(sqrt(squared)) expect_triangle_inequality(sqrt(squared)) # These three mean-zero unit vectors have pairwise Pearson correlations # cos(60 degrees), cos(60 degrees), and cos(120 degrees). u = c(1, -1, 0) / sqrt(2) v = c(1, 1, -2) / sqrt(6) angles = c(0, pi / 3, 2 * pi / 3) X = outer(cos(angles), u) + outer(sin(angles), v) correlation_dissimilarity = DistanceMatrix(X, method = "pearsond") expect_distance_structure(correlation_dissimilarity) expect_equal(correlation_dissimilarity[1, 2], 0.5, tolerance = 1e-12) expect_equal(correlation_dissimilarity[2, 3], 0.5, tolerance = 1e-12) expect_equal(correlation_dissimilarity[1, 3], 1.5, tolerance = 1e-12) expect_triangle_violation(correlation_dissimilarity) correlation_metric = DistanceMatrix(X, method = "pearsonm") expect_distance_structure(correlation_metric) expect_positive_off_diagonal(correlation_metric) expect_triangle_inequality(correlation_metric) encoded_examples = rbind( c(1, 2, 3, 4, 5), c(1, 3, 2, 5, 4), c(5, 4, 3, 2, 1), c(2, 1, 4, 3, 5) ) for (method in c("pearsonm", "spearmanm", "kendallm")) { D = DistanceMatrix(encoded_examples, method = method) expect_distance_structure(D) expect_positive_off_diagonal(D) expect_triangle_inequality(D) } original = c(1, 2, 4, 8) affine = rbind(original, 2 * original + 5, c(8, 4, 2, 1)) for (method in c( "pearsond", "spearmand", "kendalld", "pearsonm", "spearmanm", "kendallm" )) { expect_equal( DistanceMatrix(affine, method = method)[1, 2], 0, tolerance = 1e-12, info = method ) } }) test_that("cosine output is a dissimilarity rather than a metric", { angles = c(0, pi / 3, 2 * pi / 3) X = cbind(cos(angles), sin(angles)) D = Cosine_Distance(X) expect_distance_structure(D) expect_equal(D[1, 2], 0.5, tolerance = 1e-12) expect_equal(D[2, 3], 0.5, tolerance = 1e-12) expect_equal(D[1, 3], 1.5, tolerance = 1e-12) expect_triangle_violation(D) scale_collapse = Cosine_Distance(rbind(c(1, 0), c(2, 0))) expect_equal(scale_collapse[1, 2], 0, tolerance = 1e-12) zero_convention = Cosine_Distance(rbind(c(0, 0), c(0, 0), c(1, 0))) expect_equal(zero_convention[1, 2], 0) expect_equal(zero_convention[1, 3], 1) }) test_that("squared Mahalanobis output is non-metric but its square root is metric", { X = matrix(c(0, 1, 2), ncol = 1L) squared = Mahalanobis_Distance(X, cov = matrix(1, 1L, 1L)) expect_distance_structure(squared) expect_equal(squared[1, 3], 4) expect_triangle_violation(squared) expect_positive_off_diagonal(sqrt(squared)) expect_triangle_inequality(sqrt(squared)) expect_error( Mahalanobis_Distance(diag(2), cov = diag(c(1, -1)), inverted = TRUE), "positive definite" ) }) test_that("the similarity transformation enforces its Euclidean condition", { theta = c(0, pi / 6, pi / 3) unit_vectors = cbind(cos(theta), sin(theta)) S = tcrossprod(unit_vectors) D = TransformSimilarity2MetricDistance(S) expect_distance_structure(D) expect_positive_off_diagonal(D) expect_triangle_inequality(D) Sduplicate = matrix( c(1, 1, 0.5, 1, 1, 0.5, 0.5, 0.5, 1), nrow = 3L, byrow = TRUE ) Dduplicate = TransformSimilarity2MetricDistance(Sduplicate) expect_equal(Dduplicate[1, 2], 0) expect_triangle_inequality(Dduplicate) Sbad = matrix( c(1, 0.99, 0.19, 0.99, 1, 0.99, 0.19, 0.99, 1), nrow = 3L, byrow = TRUE ) expect_error( TransformSimilarity2MetricDistance(Sbad), "positive semidefinite" ) }) test_that("Endres-Schindelin and Hellinger satisfy distribution-metric cases", { distributions = list( c(1, 0, 0), c(0.5, 0.5, 0), c(0, 1, 0), c(0.2, 0.3, 0.5) ) Des = pairwise_property_matrix( distributions, function(a, b) endresSchindelin_Distribution(a, b) ) expect_distance_structure(Des) expect_positive_off_diagonal(Des, tolerance = 1e-12) expect_triangle_inequality(Des, tolerance = 1e-12) expect_equal( endresSchindelin_Distribution(c(1, 2, 3), c(2, 4, 6)), 0, tolerance = 1e-14 ) hellinger = getFromNamespace(".hellinger_distance", "BIDistances") Dh = pairwise_property_matrix(distributions, hellinger) expect_distance_structure(Dh) expect_positive_off_diagonal(Dh, tolerance = 1e-12) expect_triangle_inequality(Dh, tolerance = 1e-12) }) test_that("generalized Jaccard is a metric on non-negative vectors", { X = rbind( c(0, 0, 0), c(1, 0, 0), c(0, 1, 0), c(1, 1, 1), c(2, 1, 0) ) D = Jaccard_Distance(X) expect_distance_structure(D) expect_positive_off_diagonal(D, tolerance = 1e-12) expect_triangle_inequality(D, tolerance = 1e-12) expect_equal(D[1, 2], 1) expect_equal(D[2, 3], 1) expect_equal(D[2, 5], 2 / 3, tolerance = 1e-12) two_zero_rows = Jaccard_Distance(rbind(c(0, 0), c(0, 0))) expect_equal(two_zero_rows[1, 2], 0) }) test_that("tf-idf distance is a pseudometric on the original rows", { annotations = rbind( gene_a = c(1, 1, 0), gene_b = c(1, 0, 1), gene_c = c(1, 1, 1) ) tfidf = Tfidf_dist(annotations) expect_distance_structure(tfidf$Distance) expect_triangle_inequality(tfidf$Distance) expect_equal(tfidf$Distance["gene_a", "gene_b"], 0) expect_false(identical(annotations[1, ], annotations[2, ])) expect_named(tfidf$TfidfWeights, rownames(annotations)) expect_error( Tfidf_dist(rbind(c(0, 0), c(1, 0))), "at least one positive annotation" ) expect_error(Tfidf_dist(matrix(c(1, -1), nrow = 1L)), "non-negative") }) test_that("Gini distance is a pseudometric on the original rows", { skip_if_not_installed("ineq") gini_data = rbind( c(0, 1, 2), c(2, 1, 0), c(0, 0, 3), c(1, 1, 1) ) Dgini = Gini_Distance(gini_data) expect_distance_structure(Dgini) expect_triangle_inequality(Dgini) expect_equal(Dgini[1, 2], 0, tolerance = 1e-12) }) test_that("shared-neighbor distance is a metric on neighbor sets", { X = matrix(c(0, 1, 2, 3), ncol = 1L) D = SharedNeighbor_Distance( X, k = 1L, NThreads = 1L, ComputationInR = TRUE ) expect_distance_structure(D) expect_triangle_inequality(D) # Rows one and three have the same one-element nearest-neighbor set {2}. expect_equal(D[1, 3], 0) }) test_that("toroidal distance is metric after periodic identification", { points = rbind(c(0, 0), c(9, 0), c(5, 5), c(3, 8)) D = ToroidalEuclidean_Distance(Lines = 10, Columns = 10, Points = points) expect_distance_structure(D) expect_positive_off_diagonal(D) expect_triangle_inequality(D) expect_equal(D[1, 2], 1) periodic_collapse = ToroidalEuclidean_Distance( Lines = 10, Columns = 10, Points = rbind(c(0, 0), c(10, 0)) ) expect_equal(periodic_collapse[1, 2], 0) }) test_that("MSM and TWED satisfy deterministic metric cases", { msm_series = list(c(0, 0), c(0, 1), c(1, 1), c(2, -1, 0)) Dmsm = pairwise_property_matrix( msm_series, function(a, b) MSMD_Distance(a, b, ParameterC = 1) ) expect_distance_structure(Dmsm) expect_positive_off_diagonal(Dmsm) expect_triangle_inequality(Dmsm) twed_series = list( list(values = c(0, 0), time = c(1, 2)), list(values = c(0, 1), time = c(1, 2)), list(values = c(1, 1), time = c(1, 2)), list(values = c(0, 1, 0), time = c(1, 2, 3)) ) Dtwed = pairwise_property_matrix( twed_series, function(a, b) { TWED_Distance( a$values, b$values, a$time, b$time, Nu = 1, Lambda = 1, Degree = 2 )$TWED } ) expect_distance_structure(Dtwed) expect_positive_off_diagonal(Dtwed) expect_triangle_inequality(Dtwed) expect_error(TWED_Distance(1:2, 1:2, c(1, 1), c(1, 2)), "strictly increasing") expect_error(TWED_Distance(1:2, 1:2, c(0, 1), c(1, 2)), "positive timestamps") expect_error(TWED_Distance(1:2, 1:2, 1:2, 1:2, Nu = 0), "greater than 0") expect_error(TWED_Distance(1:2, 1:2, 1:2, 1:2, Degree = 0.5), "greater than or equal to 1") }) test_that("Kullback-Leibler outputs are divergences rather than metrics", { p = c(0.8, 0.2) q = c(0.5, 0.5) dpq = Kullback_Leibler_div( p, q, sym = FALSE, PDF = TRUE, Eps = 1e-12 )$KLD dqp = Kullback_Leibler_div( q, p, sym = FALSE, PDF = TRUE, Eps = 1e-12 )$KLD expect_false(isTRUE(all.equal(dpq, dqp, tolerance = 1e-12))) distributions = list( rep(1 / 3, 3), c(0.2, 0.4, 0.4), c(1 / 7, 3 / 7, 3 / 7) ) symmetric_kld = pairwise_property_matrix( distributions, function(a, b) { Kullback_Leibler_div( a, b, sym = TRUE, PDF = TRUE, Eps = 1e-12 )$KLD } ) expect_distance_structure(symmetric_kld) expect_triangle_violation(symmetric_kld, tolerance = 1e-12) }) test_that("Wasserstein is metric on empirical distributions but not ordered rows", { skip_if_not_installed("transport") X = rbind( c(0, 1, 2), c(2, 1, 0), c(1, 2, 3), c(0, 0, 4) ) D = Wasserstein_Distance(X, p = 1) expect_distance_structure(D) expect_triangle_inequality(D) expect_equal(D[1, 2], 0, tolerance = 1e-12) })