manual_endres_schindelin = function(p, q) { p = p / sum(p) q = q / sum(q) m = (p + q) / 2 term = function(a, midpoint) { positive = a > 0 sum(a[positive] * log2(a[positive] / midpoint[positive])) } sqrt(term(p, m) + term(q, m)) } test_that("the discrete Endres-Schindelin implementation matches its definition", { p = c(0.2, 0.5, 0.3) q = c(0.1, 0.7, 0.2) expect_equal( endresSchindelin_Distribution(p, q), manual_endres_schindelin(p, q), tolerance = 1e-14 ) }) test_that("zero-probability components use the 0 log 0 convention", { p = c(1, 0, 0) q = c(0, 1, 0) expect_equal(endresSchindelin_Distribution(p, p), 0) expect_equal( endresSchindelin_Distribution(p, q), sqrt(2), tolerance = 1e-14 ) }) test_that("non-negative counts are normalized before comparison", { expect_equal( endresSchindelin_Distribution(c(2, 3, 5), c(4, 1, 5)), endresSchindelin_Distribution(c(0.2, 0.3, 0.5), c(0.4, 0.1, 0.5)) ) }) test_that("the discrete distance is symmetric and satisfies a triangle case", { p = c(0.7, 0.2, 0.1) q = c(0.1, 0.6, 0.3) r = c(0.2, 0.2, 0.6) dpq = endresSchindelin_Distribution(p, q) dqp = endresSchindelin_Distribution(q, p) dpr = endresSchindelin_Distribution(p, r) drq = endresSchindelin_Distribution(r, q) expect_equal(dpq, dqp, tolerance = 1e-14) expect_lte(dpq, dpr + drq + 1e-14) }) test_that("invalid probability vectors are rejected", { expect_error( endresSchindelin_Distribution(c(1, -0.1), c(1, 0)), "negative" ) expect_error( endresSchindelin_Distribution(c(0, 0), c(1, 0)), "positive finite sum" ) expect_error( endresSchindelin_Distribution(c(1, 0), c(1, 0, 0)), "same length" ) }) test_that("sample-based Endres-Schindelin distances use a common support", { skip_if_not_installed("ScatterDensity") set.seed(1403) x = rnorm(64) y = x + 3 expect_identical( endresSchindelin_TwoSamplePDF(x, x, grid.length = 32), 0 ) dxy = endresSchindelin_TwoSamplePDF(x, y, grid.length = 32) dyx = endresSchindelin_TwoSamplePDF(y, x, grid.length = 32) expect_true(is.finite(dxy)) expect_gt(dxy, 0) expect_equal(dxy, dyx, tolerance = 1e-6) })