blob: d1be33fd4feff9ed70fc90dc6b7eb9cc25da1eea [file] [log] [blame]
// Copyright ©2018 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mds
import (
"math"
"testing"
"gonum.org/v1/gonum/floats"
"gonum.org/v1/gonum/mat"
)
var torgersonScalingTests = []struct {
dis mat.Symmetric
wantK int
want *mat.Dense
wantVals []float64
tol float64
}{
// All expected values obtained from running R cmdscale with the input here.
{
// Data from http://rosetta.reltech.org/TC/v15/Mapping/data/dist-Aus.csv
dis: mat.NewSymDense(8, []float64{
0, 1328, 1600, 2616, 1161, 653, 2130, 1161,
1328, 0, 1962, 1289, 2463, 1889, 1991, 2026,
1600, 1962, 0, 2846, 1788, 1374, 3604, 732,
2616, 1289, 2846, 0, 3734, 3146, 2652, 3146,
1161, 2463, 1788, 3734, 0, 598, 3008, 1057,
653, 1889, 1374, 3146, 598, 0, 2720, 713,
2130, 1991, 3604, 2652, 3008, 2720, 0, 3288,
1161, 2026, 732, 3146, 1057, 713, 3288, 0,
}),
wantK: 4,
want: mat.NewDense(8, 4, []float64{
-208.3266, 369.5373, 80.544010, 7.078974,
904.8449, -356.0745, 92.309541, -19.077511,
-925.9941, -1067.8589, -38.585847, -6.696700,
1933.8035, -1129.8610, -50.099688, 7.344750,
-1318.6333, 704.3759, -56.673300, -17.419249,
-858.3951, 319.5948, 24.186572, 18.138426,
1591.6571, 1511.3688, -43.308441, 1.043611,
-1118.9564, -351.0825, -8.372847, 9.587699,
}),
wantVals: []float64{
1.172027697614e+07, 5.686036184502e+06, 2.474981412369e+04, 1.238300279584e+03,
-5.444455825182e-10, -2.471028925712e+02, -2.412961483429e+03, -8.452858567111e+04,
},
tol: 1e-9,
},
{
// R eurodist dataset.
dis: mat.NewSymDense(21, []float64{
0, 3313, 2963, 3175, 3339, 2762, 3276, 2610, 4485, 2977, 3030, 4532, 2753, 3949, 2865, 2282, 2179, 3000, 817, 3927, 1991,
3313, 0, 1318, 1326, 1294, 1498, 2218, 803, 1172, 2018, 1490, 1305, 645, 636, 521, 1014, 1365, 1033, 1460, 2868, 1802,
2963, 1318, 0, 204, 583, 206, 966, 677, 2256, 597, 172, 2084, 690, 1558, 1011, 925, 747, 285, 1511, 1616, 1175,
3175, 1326, 204, 0, 460, 409, 1136, 747, 2224, 714, 330, 2052, 739, 1550, 1059, 1077, 977, 280, 1662, 1786, 1381,
3339, 1294, 583, 460, 0, 785, 1545, 853, 2047, 1115, 731, 1827, 789, 1347, 1101, 1209, 1160, 340, 1794, 2196, 1588,
2762, 1498, 206, 409, 785, 0, 760, 1662, 2436, 460, 269, 2290, 714, 1764, 1035, 911, 583, 465, 1497, 1403, 937,
3276, 2218, 966, 1136, 1545, 760, 0, 1418, 3196, 460, 269, 2971, 1458, 2498, 1778, 1537, 1104, 1176, 2050, 650, 1455,
2610, 803, 677, 747, 853, 1662, 1418, 0, 1975, 1118, 895, 1936, 158, 1439, 425, 328, 591, 513, 995, 2068, 1019,
4485, 1172, 2256, 2224, 2047, 2436, 3196, 1975, 0, 2897, 2428, 676, 1817, 698, 1693, 2185, 2565, 1971, 2631, 3886, 2974,
2977, 2018, 597, 714, 1115, 460, 460, 1118, 2897, 0, 550, 2671, 1159, 2198, 1479, 1238, 805, 877, 1751, 949, 1155,
3030, 1490, 172, 330, 731, 269, 269, 895, 2428, 550, 0, 2280, 863, 1730, 1183, 1098, 851, 457, 1683, 1500, 1205,
4532, 1305, 2084, 2052, 1827, 2290, 2971, 1936, 676, 2671, 2280, 0, 1178, 668, 1762, 2250, 2507, 1799, 2700, 3231, 2937,
2753, 645, 690, 739, 789, 714, 1458, 158, 1817, 1159, 863, 1178, 0, 1281, 320, 328, 724, 471, 1048, 2108, 1157,
3949, 636, 1558, 1550, 1347, 1764, 2498, 1439, 698, 2198, 1730, 668, 1281, 0, 1157, 1724, 2010, 1273, 2097, 3188, 2409,
2865, 521, 1011, 1059, 1101, 1035, 1778, 425, 1693, 1479, 1183, 1762, 320, 1157, 0, 618, 1109, 792, 1011, 2428, 1363,
2282, 1014, 925, 1077, 1209, 911, 1537, 328, 2185, 1238, 1098, 2250, 328, 1724, 618, 0, 331, 856, 586, 2187, 898,
2179, 1365, 747, 977, 1160, 583, 1104, 591, 2565, 805, 851, 2507, 724, 2010, 1109, 331, 0, 821, 946, 1754, 428,
3000, 1033, 285, 280, 340, 465, 1176, 513, 1971, 877, 457, 1799, 471, 1273, 792, 856, 821, 0, 1476, 1827, 1249,
817, 1460, 1511, 1662, 1794, 1497, 2050, 995, 2631, 1751, 1683, 2700, 1048, 2097, 1011, 586, 946, 1476, 0, 2707, 1209,
3927, 2868, 1616, 1786, 2196, 1403, 650, 2068, 3886, 949, 1500, 3231, 2108, 3188, 2428, 2187, 1754, 1827, 2707, 0, 2105,
1991, 1802, 1175, 1381, 1588, 937, 1455, 1019, 2974, 1155, 1205, 2937, 1157, 2409, 1363, 898, 428, 1249, 1209, 2105, 0,
}),
// Note that k here is 12 despite the result from R's cmdscale.
// This is due to disparity between the ED performed by R and Gonum.
// See https://github.com/gonum/gonum/issues/768
wantK: 12,
want: mat.NewDense(21, 12, []float64{
2290.274680, 1798.80293, 53.79314, -103.826958, -156.955115, 54.755434, -47.6768205, 1.241284, -14.893196, -6.366664, 4.818373, 0,
-825.382790, 546.81148, -113.85842, 84.585831, 291.440759, -33.046236, -74.5267190, 3.766233, 225.620420, -21.270973, 22.050484, 0,
59.183341, -367.08135, 177.55291, 38.797514, -95.620447, 40.058268, 2.3212184, 34.351715, -2.262151, -129.298820, 75.568686, 0,
-82.845973, -429.91466, 300.19274, 106.353695, -180.446140, 31.336985, 88.6540176, 5.102633, 87.657362, 86.867202, 156.097411, 0,
-352.499435, -290.90843, 457.35294, 111.449150, -417.496682, -138.972847, 46.7759317, 23.806769, 108.307203, 99.578699, -103.623270, 0,
293.689633, -405.31194, 360.09323, -636.202379, 159.392662, -9.560652, 33.1089500, 9.193680, 19.417287, 7.925230, -1.272483, 0,
681.931545, -1108.64478, 26.09257, 151.693056, 254.114892, 197.254208, -176.0359152, -6.508596, -59.829991, 64.904841, -26.466935, 0,
-9.423364, 240.40600, -344.20659, 656.121110, -138.082259, -19.891841, -0.3128244, -20.167733, -18.250209, -11.175662, 2.160076, 0,
-2048.449113, 642.45854, 167.86631, 78.621423, 239.881353, -51.009028, 60.7527717, 125.492010, -144.940590, 35.754867, 13.633563, 0,
561.108970, -773.36929, 80.91722, 48.548472, -129.512374, 20.426493, 207.7920058, 23.646986, -166.184382, -64.027105, -5.187863, 0,
164.921799, -549.36704, 270.82327, 116.886334, 62.711250, 117.489888, -252.4906676, -47.193881, -62.053055, 75.466644, 4.907670, 0,
-1935.040811, 49.12514, -483.02056, -315.241752, -277.696589, -92.444183, -76.2549266, -28.529861, -26.279840, -2.503630, 7.050184, 0,
-226.423236, 187.08779, -358.43234, -257.737009, -190.609088, -110.666015, -119.7638108, -43.118326, -76.066370, 89.289877, 18.820576, 0,
-1423.353697, 305.87513, 253.26763, 2.478812, 5.366334, 205.060613, -45.8020725, -75.244576, -4.303057, -150.778068, -23.118399, 0,
-299.498710, 388.80726, -109.17417, 12.651217, 231.405239, 154.749482, 237.4507512, -227.372584, 25.449825, 71.404170, -35.167507, 0,
260.878046, 416.67381, -171.52428, 20.926369, 194.345825, -19.740272, 121.3353507, 184.415363, -5.563374, 90.507909, -22.562758, 0,
587.675679, 81.18224, -75.88485, 13.080496, 114.688714, -185.462926, -47.4707495, 209.170525, 39.452744, -65.352092, -23.127035, 0,
-156.836257, -211.13911, 131.30852, 27.089432, -100.800743, 6.794791, -25.6913210, -7.904762, 39.731234, -79.091817, -75.116800, 0,
709.413282, 1109.36665, -179.83052, -109.895049, -90.243055, 316.665218, 9.9070412, 9.976867, 6.316941, -13.285987, 1.496742, 0,
839.445911, -1836.79055, -541.35188, -108.755016, 25.155430, 41.399927, 71.8793196, 9.438407, 73.554358, -35.415426, -6.021569, 0,
911.230500, 205.93020, 98.02313, 62.375253, 198.960032, -525.197308, -13.9515306, -183.562155, -44.881159, -43.133195, 15.060856, 0,
}),
wantVals: []float64{
1.953837708954e+07, 1.185655533400e+07, 1.528844467987e+06, 1.118741950509e+06,
7.893472026801e+05, 5.816552067198e+05, 2.623192077011e+05, 1.925975616762e+05,
1.450845349644e+05, 1.079673069262e+05, 5.139484110774e+04, 7.598368987955e-11,
-9.496124219168e+03, -5.305819566947e+04, -1.322165749977e+05, -2.573360255637e+05,
-3.326719007160e+05, -5.162522542344e+05, -9.191490984121e+05, -1.006503960172e+06,
-2.251844331736e+06,
},
tol: 1e-9,
},
}
func TestTorgersonScaling(t *testing.T) {
for i, test := range torgersonScalingTests {
r, c := test.dis.Dims()
var got mat.Dense
gotK, gotVals := TorgersonScaling(&got, make([]float64, c), test.dis)
if gotK == 0 {
t.Error("unexpected scaling failure")
continue
}
if gotK != test.wantK {
var derivedK int
for derivedK = range gotVals {
if math.Abs(gotVals[derivedK]) < test.tol {
got = *got.Slice(0, r, 0, derivedK).(*mat.Dense)
break
}
}
if derivedK != test.wantK {
t.Errorf("unexpected k for test %d: got:%d want:%d", i, gotK, test.wantK)
} else {
t.Logf("low precision (tol=%.0e) k for test %d: gotK:%d derived=want:%d", test.tol, i, gotK, test.wantK)
}
}
if !mat.EqualApprox(colAbs{&got}, colAbs{test.want}, math.Sqrt(test.tol)) {
t.Errorf("unexpected result for test %d:\ngot:\n%.5f\nwant:\n%.5f",
i, mat.Formatted(&got), mat.Formatted(test.want))
}
if !floats.EqualApprox(gotVals, test.wantVals, test.tol) {
t.Errorf("unexpected Eigenvalues for test %d:\ngot: %.12e\nwant:%.12e", i, gotVals, test.wantVals)
}
}
}
// colAbs returns the value of columns reflected
// such that the first row is positive.
type colAbs struct {
mat.Matrix
}
func (m colAbs) At(i, j int) float64 {
if m.Matrix.At(0, j) < 0 {
return -m.Matrix.At(i, j)
}
return m.Matrix.At(i, j)
}