1import numpy as np 2from numpy.testing import assert_allclose, assert_equal, assert_almost_equal 3from pytest import raises as assert_raises 4 5from scipy.spatial import procrustes 6 7 8class TestProcrustes: 9 def setup_method(self): 10 """creates inputs""" 11 # an L 12 self.data1 = np.array([[1, 3], [1, 2], [1, 1], [2, 1]], 'd') 13 14 # a larger, shifted, mirrored L 15 self.data2 = np.array([[4, -2], [4, -4], [4, -6], [2, -6]], 'd') 16 17 # an L shifted up 1, right 1, and with point 4 shifted an extra .5 18 # to the right 19 # pointwise distance disparity with data1: 3*(2) + (1 + 1.5^2) 20 self.data3 = np.array([[2, 4], [2, 3], [2, 2], [3, 2.5]], 'd') 21 22 # data4, data5 are standardized (trace(A*A') = 1). 23 # procrustes should return an identical copy if they are used 24 # as the first matrix argument. 25 shiftangle = np.pi / 8 26 self.data4 = np.array([[1, 0], [0, 1], [-1, 0], 27 [0, -1]], 'd') / np.sqrt(4) 28 self.data5 = np.array([[np.cos(shiftangle), np.sin(shiftangle)], 29 [np.cos(np.pi / 2 - shiftangle), 30 np.sin(np.pi / 2 - shiftangle)], 31 [-np.cos(shiftangle), 32 -np.sin(shiftangle)], 33 [-np.cos(np.pi / 2 - shiftangle), 34 -np.sin(np.pi / 2 - shiftangle)]], 35 'd') / np.sqrt(4) 36 37 def test_procrustes(self): 38 # tests procrustes' ability to match two matrices. 39 # 40 # the second matrix is a rotated, shifted, scaled, and mirrored version 41 # of the first, in two dimensions only 42 # 43 # can shift, mirror, and scale an 'L'? 44 a, b, disparity = procrustes(self.data1, self.data2) 45 assert_allclose(b, a) 46 assert_almost_equal(disparity, 0.) 47 48 # if first mtx is standardized, leaves first mtx unchanged? 49 m4, m5, disp45 = procrustes(self.data4, self.data5) 50 assert_equal(m4, self.data4) 51 52 # at worst, data3 is an 'L' with one point off by .5 53 m1, m3, disp13 = procrustes(self.data1, self.data3) 54 #assert_(disp13 < 0.5 ** 2) 55 56 def test_procrustes2(self): 57 # procrustes disparity should not depend on order of matrices 58 m1, m3, disp13 = procrustes(self.data1, self.data3) 59 m3_2, m1_2, disp31 = procrustes(self.data3, self.data1) 60 assert_almost_equal(disp13, disp31) 61 62 # try with 3d, 8 pts per 63 rand1 = np.array([[2.61955202, 0.30522265, 0.55515826], 64 [0.41124708, -0.03966978, -0.31854548], 65 [0.91910318, 1.39451809, -0.15295084], 66 [2.00452023, 0.50150048, 0.29485268], 67 [0.09453595, 0.67528885, 0.03283872], 68 [0.07015232, 2.18892599, -1.67266852], 69 [0.65029688, 1.60551637, 0.80013549], 70 [-0.6607528, 0.53644208, 0.17033891]]) 71 72 rand3 = np.array([[0.0809969, 0.09731461, -0.173442], 73 [-1.84888465, -0.92589646, -1.29335743], 74 [0.67031855, -1.35957463, 0.41938621], 75 [0.73967209, -0.20230757, 0.52418027], 76 [0.17752796, 0.09065607, 0.29827466], 77 [0.47999368, -0.88455717, -0.57547934], 78 [-0.11486344, -0.12608506, -0.3395779], 79 [-0.86106154, -0.28687488, 0.9644429]]) 80 res1, res3, disp13 = procrustes(rand1, rand3) 81 res3_2, res1_2, disp31 = procrustes(rand3, rand1) 82 assert_almost_equal(disp13, disp31) 83 84 def test_procrustes_shape_mismatch(self): 85 assert_raises(ValueError, procrustes, 86 np.array([[1, 2], [3, 4]]), 87 np.array([[5, 6, 7], [8, 9, 10]])) 88 89 def test_procrustes_empty_rows_or_cols(self): 90 empty = np.array([[]]) 91 assert_raises(ValueError, procrustes, empty, empty) 92 93 def test_procrustes_no_variation(self): 94 assert_raises(ValueError, procrustes, 95 np.array([[42, 42], [42, 42]]), 96 np.array([[45, 45], [45, 45]])) 97 98 def test_procrustes_bad_number_of_dimensions(self): 99 # fewer dimensions in one dataset 100 assert_raises(ValueError, procrustes, 101 np.array([1, 1, 2, 3, 5, 8]), 102 np.array([[1, 2], [3, 4]])) 103 104 # fewer dimensions in both datasets 105 assert_raises(ValueError, procrustes, 106 np.array([1, 1, 2, 3, 5, 8]), 107 np.array([1, 1, 2, 3, 5, 8])) 108 109 # zero dimensions 110 assert_raises(ValueError, procrustes, np.array(7), np.array(11)) 111 112 # extra dimensions 113 assert_raises(ValueError, procrustes, 114 np.array([[[11], [7]]]), 115 np.array([[[5, 13]]])) 116 117