.manual_weighted_minkowski = function(Data, p, Weights) { Data = as.matrix(Data) n = nrow(Data) D = matrix(0, nrow = n, ncol = n) if (n > 1L) { for (i in seq.int(2L, n)) { for (j in seq_len(i - 1L)) { difference = abs(Data[i, ] - Data[j, ]) value = if (is.infinite(p)) { max(difference) } else { components = Weights^(1 / p) * difference maximum = max(components) if (maximum == 0) { 0 } else { maximum * sum((components / maximum)^p)^(1 / p) } } D[i, j] = value D[j, i] = value } } } if (!is.null(rownames(Data))) { dimnames(D) = list(rownames(Data), rownames(Data)) } D } .opencl_test_context = function() { skip_on_cran() skip_if_not_installed("OpenCL") platforms = tryCatch( suppressWarnings(OpenCL::oclPlatforms()), error = identity ) skip_if(inherits(platforms, "error") || !length(platforms), "No OpenCL platform was detected") ctx = tryCatch(OpenCL::oclContext(precision = "best"), error = identity) skip_if(inherits(ctx, "error"), "No usable OpenCL context was created") ctx } test_that("parallelDist and internal multicore Minkowski backends agree", { Data = rbind( Case1 = c(0, 1, 2, 4), Case2 = c(1, 2, 4, 3), Case3 = c(2, 1, 3, 8), Case4 = c(4, 3, 1, 2), Case5 = c(-1, 0, 5, 6) ) Weights = c(1, 2, 0.5, 3) for (p in c(1, 1.5, 2, 4, 10)) { reference = .manual_weighted_minkowski(Data, p, Weights) parallel_result = Minkowski_Distance( Data, p = p, Weights = Weights, backend = "parallelDist", threads = 2 ) multicore_result = Minkowski_Distance( Data, p = p, Weights = Weights, backend = "multicore", threads = 2 ) expect_equal(parallel_result, reference, tolerance = 1e-10, info = paste("p =", p)) expect_equal(multicore_result, reference, tolerance = 1e-10, info = paste("p =", p)) } infinity_reference = .manual_weighted_minkowski( Data, Inf, rep.int(1, ncol(Data)) ) expect_equal( Minkowski_Distance( Data, p = Inf, backend = "parallelDist", threads = 2 ), infinity_reference, tolerance = 1e-12 ) expect_equal( Minkowski_Distance( Data, p = Inf, backend = "multicore", threads = 2 ), infinity_reference, tolerance = 1e-12 ) }) test_that("p = 2 agrees with the established weighted Euclidean CPU route", { Data = matrix(seq_len(30), nrow = 6, ncol = 5) Weights = c(1, 2, 0, 0.5, 3) expected = EuclideanGPU_Distance( Data, Weights = Weights, backend = "cpu", threads = 2 ) observed = Minkowski_Distance( Data, p = 2, Weights = Weights, backend = "multicore", threads = 2 ) expect_equal(observed, expected, tolerance = 1e-12) }) test_that("the internal generic calculation remains finite for large p", { Data = rbind( Case1 = c(0, 0, 0), Case2 = c(1e100, 2e100, 3e100), Case3 = c(2e100, 1e100, 4e100) ) p = 100 Weights = c(1, 2, 0.5) reference = .manual_weighted_minkowski(Data, p, Weights) observed = Minkowski_Distance( Data, p = p, Weights = Weights, backend = "multicore", threads = 2 ) expect_true(all(is.finite(observed))) expect_equal(observed, reference, tolerance = 1e-12) }) test_that("Minkowski output forms and row labels are preserved", { Data = rbind( A = c(0, 1), B = c(2, 3), C = c(4, 0) ) matrix_result = Minkowski_Distance( Data, p = 3, backend = "multicore", OutputType = "mat" ) dist_result = Minkowski_Distance( Data, p = 3, backend = "multicore", OutputType = "dist" ) vector_result = Minkowski_Distance( Data, p = 3, backend = "multicore", OutputType = "vec" ) expect_identical(rownames(matrix_result), rownames(Data)) expect_identical(colnames(matrix_result), rownames(Data)) expect_s3_class(dist_result, "dist") expect_equal(as.matrix(dist_result), matrix_result) expect_equal(vector_result, as.vector(matrix_result)) single = matrix(3, nrow = 1L, ncol = 1L, dimnames = list("OnlyCase", "OnlyVariable")) expect_identical( Minkowski_Distance(single, backend = "parallelDist"), matrix(0, 1L, 1L, dimnames = list("OnlyCase", "OnlyCase")) ) }) test_that("weighted Minkowski metric and pseudometric properties are explicit", { Data = rbind( c(0, 0, 0), c(1, 0, 2), c(0, 2, 1), c(3, 1, 4), c(-1, 2, 5) ) for (p in c(1, 1.5, 2, 4, 10, Inf)) { D = Minkowski_Distance( Data, p = p, Weights = rep.int(1, ncol(Data)), backend = "multicore", threads = 2 ) expect_distance_structure(D) expect_positive_off_diagonal(D) expect_triangle_inequality(D, tolerance = 1e-9) } collapsed = rbind(c(0, 1), c(0, 5), c(2, 5)) Dcollapsed = Minkowski_Distance( collapsed, p = 3, Weights = c(1, 0), backend = "multicore" ) expect_distance_structure(Dcollapsed) expect_triangle_inequality(Dcollapsed) expect_equal(Dcollapsed[1, 2], 0) expect_false(identical(collapsed[1, ], collapsed[2, ])) }) test_that("invalid Minkowski inputs are rejected clearly", { Data = matrix(seq_len(12), nrow = 4, ncol = 3) expect_error(Minkowski_Distance(Data, p = 0.5), "greater than or equal to 1") expect_error(Minkowski_Distance(Data, p = NA_real_), "greater than or equal") expect_error(Minkowski_Distance(Data, Weights = c(1, 2)), "one value per") expect_error(Minkowski_Distance(Data, Weights = c(1, -1, 1)), "non-negative") expect_error( Minkowski_Distance(Data, p = Inf, Weights = c(1, 2, 1)), "must contain only ones" ) expect_error(Minkowski_Distance(Data, backend = "unknown"), "must be one of") expect_error(Minkowski_Distance(Data, threads = 0), "positive integer") expect_error( Minkowski_Distance(Data, backend = "opencl", Mem = 0), "finite positive" ) expect_error( Minkowski_Distance(matrix(c(1, Inf), nrow = 2), backend = "multicore"), "finite values" ) }) test_that("auto falls back to the internal multicore backend", { Data = rbind(c(0, 1), c(1, 3), c(4, 2)) expected = Minkowski_Distance( Data, p = 3, backend = "multicore", threads = 2 ) expect_warning( observed <- Minkowski_Distance( Data, p = 3, backend = "auto", ctx = structure(list(), class = "invalid_opencl_context"), threads = 2 ), "using the internal multicore backend" ) expect_equal(observed, expected) }) test_that("DistanceMatrix dispatches the canonical Minkowski route", { Data = rbind( A = c(0, 1, 2), B = c(1, 2, 4), C = c(2, 1, 3), D = c(4, 3, 1) ) Weights = c(1, 2, 0.5) direct = Minkowski_Distance( Data, p = 3, Weights = Weights, backend = "multicore", threads = 2 ) dispatched = DistanceMatrix( Data, method = "Minkowski_Distance", p = 3, Weights = Weights, backend = "multicore", threads = 2 ) expect_equal(dispatched, direct) by_dim = DistanceMatrix( Data, method = "Minkowski_Distance", dim = 3, Weights = Weights, backend = "parallelDist", threads = 2 ) expect_equal( by_dim, Minkowski_Distance( Data, p = 3, Weights = Weights, backend = "parallelDist", threads = 2 ) ) default_cpu = DistanceMatrix( Data, method = "Minkowski_Distance", p = 3, Weights = Weights, threads = 2 ) expect_equal(default_cpu, by_dim) }) test_that("the separate OpenCL Minkowski kernels agree across memory regimes", { ctx = .opencl_test_context() Data = rbind( c(0, 1, 2), c(1, 2, 4), c(2, 1, 3), c(4, 3, 1), c(-1, 0, 5) ) full = getFromNamespace(".weighted_minkowski_opencl_full", "BIDistances") batches = getFromNamespace( ".weighted_minkowski_opencl_batches", "BIDistances" ) blocks = getFromNamespace( ".weighted_minkowski_opencl_blocks", "BIDistances" ) for (p in c(1, 1.5, 4, Inf)) { Weights = if (is.infinite(p)) rep.int(1, ncol(Data)) else c(1, 2, 0.5) CoordinateScales = if (is.infinite(p)) { rep.int(1, ncol(Data)) } else { Weights^(1 / p) } reference = .manual_weighted_minkowski(Data, p, Weights) dimnames(reference) = NULL observed_full = full(Data, CoordinateScales, p, ctx) observed_batches = batches( Data, CoordinateScales, p, ctx, c(2L, 2L, 1L) ) observed_blocks = blocks( Data, CoordinateScales, p, ctx, c(2L, 2L, 1L) ) expect_equal(observed_full, reference, tolerance = 1e-5, info = paste("p =", p)) expect_equal(observed_batches, reference, tolerance = 1e-5, info = paste("p =", p)) expect_equal(observed_blocks, reference, tolerance = 1e-5, info = paste("p =", p)) } }) test_that("OpenCL p = 2 delegates to the established Euclidean implementation", { ctx = .opencl_test_context() Data = matrix(seq_len(20), nrow = 5, ncol = 4) Weights = c(1, 2, 0.5, 3) expected = EuclideanGPU_Distance( Data, Weights = Weights, backend = "opencl", ctx = ctx ) observed = Minkowski_Distance( Data, p = 2, Weights = Weights, backend = "opencl", ctx = ctx ) expect_equal(observed, expected, tolerance = 1e-7) })