def test_orthogonal_procrustes(): np.random.seed(1234) for m, n in ((6, 4), (4, 4), (4, 6)): # Sample a random target matrix. B = np.random.randn(m, n) # Sample a random orthogonal matrix # by computing eigh of a sampled symmetric matrix. X = np.random.randn(n, n) w, V = eigh(X.T + X) assert_allclose(inv(V), V.T) # Compute a matrix with a known orthogonal transformation that gives B. A = np.dot(B, V.T) # Check that an orthogonal transformation from A to B can be recovered. R, s = orthogonal_procrustes(A, B) assert_allclose(inv(R), R.T) assert_allclose(A.dot(R), B) # Create a perturbed input matrix. A_perturbed = A + 1e-2 * np.random.randn(m, n) # Check that the orthogonal procrustes function can find an orthogonal # transformation that is better than the orthogonal transformation # computed from the original input matrix. R_prime, s = orthogonal_procrustes(A_perturbed, B) assert_allclose(inv(R_prime), R_prime.T) # Compute the naive and optimal transformations of the perturbed input. naive_approx = A_perturbed.dot(R) optim_approx = A_perturbed.dot(R_prime) # Compute the Frobenius norm errors of the matrix approximations. naive_approx_error = norm(naive_approx - B, ord='fro') optim_approx_error = norm(optim_approx - B, ord='fro') # Check that the orthogonal Procrustes approximation is better. assert_array_less(optim_approx_error, naive_approx_error)
def EM_aux(X, Y, alpha, Q, sigma, muy, sigmay, is_soft): """ EM noise aware :param X: matrix 1 :param Y: matrix 2 :param alpha: percentage of clean pairs :param Q: transform matrix :param sigma: clean pairs variance :param muy: noisy pairs mean :param sigmay: noisy pair variance :param is_soft: true - soft EM, false - hard EM :return: transform matrix, alpha, clean indices, noisy indices """ n, dim = X.shape threshold = 0.00001 prev_alpha = -1 j = -1 while abs(alpha - prev_alpha) > threshold: j = j + 1 prev_alpha = alpha # E-step ws = [0] * n nom = [0] * n sup = [0] * n nom[:] = np.log(alpha) + P(Y, dim, np.dot(X, Q), sigma) sup[:] = np.log((1 - alpha)) + P(Y, dim, muy, sigmay) m = max(nom) ws[:] = np.exp(nom[:] - m) / (np.exp(nom[:] - m) + np.exp(sup[:] - m)) ws = np.where(np.isnan(ws), 0, ws) # M-step if is_soft: sum_ws = float(sum(ws)) alpha = sum_ws / float(n) Q, _ = orthogonal_procrustes(np.multiply(np.array(ws).reshape((n,1)),X), np.multiply(np.array(ws).reshape((n,1)),Y)) sigma = sum(np.linalg.norm(np.dot(X[i, :], Q) - Y[i, :]) ** 2 * ws[i] for i in range(0,n)) / (sum_ws * dim) muy = sum(Y[i, :] * (1 - ws[i]) for i in range(0,n)) / (n-sum_ws) sigmay = sum(np.linalg.norm(muy - Y[i, :]) ** 2 * (1 - ws[i]) for i in range(0,n)) / ((n-sum_ws) * dim) else: #hard EM t_indices = np.where(np.asarray(ws) >= 0.5)[0] f_indices = np.where(np.asarray(ws) < 0.5)[0] assert (len(t_indices) > 0) assert (len(f_indices) > 0) X_clean = np.squeeze(X[[t_indices], :]) Y_clean = np.squeeze(Y[[t_indices], :]) alpha = float(len(t_indices)) / float(n) Q, _ = orthogonal_procrustes(X_clean, Y_clean) sigma = sum(np.linalg.norm(np.dot(X[i, :], Q) - Y[i, :]) ** 2 for i in t_indices) / (len(t_indices) * dim) muy = sum(Y[i, :] for i in f_indices) / len(f_indices) sigmay = sum(np.linalg.norm(muy - Y[i, :]) ** 2 for i in f_indices) / (len(f_indices) * dim) # print('iter:', j, 'alpha:', round(alpha,3), 'sigma:', round(sigma,3), 'sigmay', round(sigmay,3)) t_indices = np.where(np.asarray(ws) >= 0.5)[0] f_indices = np.where(np.asarray(ws) < 0.5)[0] return np.asarray(Q), alpha, t_indices, f_indices
def test_orthogonal_procrustes_scale_invariance(): np.random.seed(1234) m, n = 4, 3 for i in range(3): A_orig = np.random.randn(m, n) B_orig = np.random.randn(m, n) R_orig, s = orthogonal_procrustes(A_orig, B_orig) for A_scale in np.square(np.random.randn(3)): for B_scale in np.square(np.random.randn(3)): R, s = orthogonal_procrustes(A_orig * A_scale, B_orig * B_scale) assert_allclose(R, R_orig)
def test_orthogonal_procrustes_array_conversion(): np.random.seed(1234) for m, n in ((6, 4), (4, 4), (4, 6)): A_arr = np.random.randn(m, n) B_arr = np.random.randn(m, n) As = (A_arr, A_arr.tolist(), np.matrix(A_arr)) Bs = (B_arr, B_arr.tolist(), np.matrix(B_arr)) R_arr, s = orthogonal_procrustes(A_arr, B_arr) AR_arr = A_arr.dot(R_arr) for A, B in product(As, Bs): R, s = orthogonal_procrustes(A, B) AR = A_arr.dot(R) assert_allclose(AR, AR_arr)
def PointCrustes(mtx1,mtx2): # translate all the data to the origin mu1=np.mean(mtx1, 0) mu2=np.mean(mtx2, 0) mtx1t =mtx1 - mu1 mtx2t =mtx2 - mu2 norm1 = np.linalg.norm(mtx1t) norm2 = np.linalg.norm(mtx2t) if norm1 == 0 or norm2 == 0: raise ValueError("Input matrices must contain >1 unique points") # change scaling of data (in rows) such that trace(mtx*mtx') = 1 mtx1t /= norm1 mtx2t /= norm2 # transform mtx2 to minimize disparity R, s = orthogonal_procrustes(mtx1t, mtx2t) print("Procs Point") print(mu2) print(s) print(mu1) print(R) t = mu2-s*np.dot(R.T,mu1) #or mu2-np.dot(mu1, R) return R,t
def sem_disp(self, other, average=True, align=True, n=-1): """ Computes the semantic displacement measure between the current instance and another Embedding. :param other: other Embedding object to compare :param average: (optional) average over the cosine distance between 'n' word vectors from two embeddings. :param align: (optional) bool value, use orthogonal Procrustes to align the current instance to the other Embedding prior to computing the measure. :param n: (optional) int value representing the number of words to use to compute the distance, where the words are assumed to be sorted by frequency in the current instance. :return: float distance """ if not isinstance(other, Embedding): raise ValueError("Only Embedding objects supported.") emb1, emb2 = self.get_subembeds_same_vocab(other, n=n) if align: R, _ = orthogonal_procrustes(emb1, emb2) emb1 = np.dot(emb1, R) rcos_dist = np.array([ spatial.distance.cosine(emb1[k], emb2[k]) for k in range(emb2.shape[0]) ]) if average: rcos_dist = np.nanmean(rcos_dist, axis=0) return rcos_dist
def get_rotations_realistic(era_axis, JD_INIT, array_location): p1 = np.array([1.0, 0.0, 0.0]) p2 = np.array([0.0, 1.0, 0.0]) p3 = np.array([0.0, 0.0, 1.0]) jd_axis = map(lambda era: era2JD(era, JD_INIT), era_axis) JDs = Time(jd_axis, format="jd", scale="ut1") rotations_axis = np.zeros((JDs.size, 3, 3), dtype=np.float) for i in range(JDs.size): gcrs_axes = coord.SkyCoord( x=p1 * units.one, y=p2 * units.one, z=p3 * units.one, location=array_location, obstime=JDs[i], frame="gcrs", representation="cartesian", ) transf_gcrs_axes = gcrs_axes.transform_to("altaz") M = np.zeros((3, 3)) M[:, 0] = transf_gcrs_axes.cartesian.x.value M[:, 1] = transf_gcrs_axes.cartesian.y.value M[:, 2] = transf_gcrs_axes.cartesian.z.value # the matrix M is generally not an orthogonal matrix # to working precision, but it is very close. This procedure # finds the orthogonal matrix that is nearest to M, # as measured by the Frobenius norm. Rt, _ = linalg.orthogonal_procrustes(M, np.eye(3)) rotations_axis[i] = np.transpose(Rt) return rotations_axis, JDs.jd
def createTransformationMatrix(modelA, modelB): # initialize the matrices labels = [] A = [] B = [] # keep the common words and add them to the matrices nb_words_A = len(modelA.index2word) nb_words_B = len(modelA.index2word) for i in range(0, nb_words_A): word = modelA.index2word[i] if word in modelB.index2word: # add the word to the matrices (and the labels) labels.append(word) A.append(modelA[word]) B.append(modelB[word]) # create the transformation matrix TransM, _ = orthogonal_procrustes(np.asarray(A), np.asarray(B), check_finite=False) # apply the transofrmation matrix to the first model matrix Z = np.matmul(A, TransM) # create the 2 models manually (by first creating a text file and reading it). # it would be most efficient not to have to store the results on files like this. constructModel(np.asarray(Z), labels, "tmpZ.model.txt") constructModel(np.asarray(B), labels, "tmpB.model.txt") modelZ_ = Word2Vec.load_word2vec_format('tmpZ.model.txt', binary=False) modelB_ = Word2Vec.load_word2vec_format('tmpB.model.txt', binary=False) return modelZ_, modelB_
def test_orthogonal_procrustes_skbio_example(): # This transformation is also exact. # It uses translation, scaling, and reflection. # # | # | a # | b # | c d # --+--------- # | # | w # | # | x # | # | z y # | # A_orig = np.array([[4, -2], [4, -4], [4, -6], [2, -6]], dtype=float) B_orig = np.array([[1, 3], [1, 2], [1, 1], [2, 1]], dtype=float) B_standardized = np.array([ [-0.13363062, 0.6681531], [-0.13363062, 0.13363062], [-0.13363062, -0.40089186], [0.40089186, -0.40089186]]) A, A_mu = _centered(A_orig) B, B_mu = _centered(B_orig) R, s = orthogonal_procrustes(A, B) scale = s / np.square(norm(A)) B_approx = scale * np.dot(A, R) + B_mu assert_allclose(B_approx, B_orig) assert_allclose(B / norm(B), B_standardized)
def unscaled_procrustes(reference, data): """Fit `data` to `reference` using procrustes analysis without scaling. Uses translation (mean-centering), reflection, and orthogonal rotation. Parameters: reference (array-like of shape (n_points, n_dim)): reference shape to fit `data` to data (array-like of shape (n_points, n_dim)): shape to align to `reference` Returns: reference_centered (np.ndarray of shape (n_points, n_dim)): 0-centered `reference` shape data_aligned (np.ndarray of shape (n_points, n_dim)): `data` aligned to the reference shape """ # Convert inputs to np.ndarray types reference = np.array(reference, dtype=np.double) data = np.array(data, dtype=np.double) # Translate data to the origin reference_centered = reference - reference.mean(axis=0) data_centered = data - data.mean(axis=0) # Rotate / reflect data to match reference # transform mtx2 to minimize disparity R, _ = orthogonal_procrustes(data_centered, reference_centered) data_aligned = data_centered @ R return reference_centered, data_aligned
def get_galactic_to_gcrs_rotation_matrix(): x_c = np.array([1.0, 0, 0]) # unit vectors to be transformed by astropy y_c = np.array([0, 1.0, 0]) z_c = np.array([0, 0, 1.0]) axes_gcrs = coord.SkyCoord(x=x_c, y=y_c, z=z_c, frame="gcrs", representation="cartesian") axes_gal = axes_gcrs.transform_to("galactic") axes_gal.representation = "cartesian" M = np.zeros((3, 3)) M[:, 0] = axes_gal.cartesian.x.value M[:, 1] = axes_gal.cartesian.y.value M[:, 2] = axes_gal.cartesian.z.value # the matrix M is generally not an orthogonal matrix # to working precision, but it is very close. This procedure # finds the orthogonal matrix that is nearest to M, # as measured by the Frobenius norm. Rt, _ = linalg.orthogonal_procrustes(M, np.eye(3)) # The 3D rotation matrix that defines the coordinate transformation. R_g2gcrs = np.transpose(Rt) return R_g2gcrs
def align(wv1, wv2, anchor_indices=None, anchor_words=None, anchor_top=None, anchor_bot=None, anchor_random=None, exclude={}, method="procrustes"): """ Implement OP alignment for a given set of landmarks. If no landmark is given, performs global alignment. Arguments: wv1 - WordVectors object to align to wv2 wv2 - Target WordVectors. Will align wv1 to it. anchor_indices - (optional) uses word indices as landmarks anchor_words - (optional) uses words as landmarks exclude - set of words to exclude from alignment method - Alignment objective. Currently only supports orthogonal procrustes. """ if anchor_top is not None: v1 = [ wv1.vectors[i] for i in range(anchor_top) if wv1.words[i] not in exclude ] v2 = [ wv2.vectors[i] for i in range(anchor_top) if wv2.words[i] not in exclude ] elif anchor_bot is not None: v1 = [ wv1.vectors[-i] for i in range(anchor_bot) if wv1.words[i] not in exclude ] v2 = [ wv2.vectors[-i] for i in range(anchor_bot) if wv2.words[i] not in exclude ] elif anchor_random is not None: anchors = np.random.choice(range(len(wv1.vectors)), anchor_random) v1 = [wv1.vectors[i] for i in anchors if wv1.words[i] not in exclude] v2 = [wv2.vectors[i] for i in anchors if wv2.words[i] not in exclude] elif anchor_indices is not None: v1 = [wv1.vectors[i] for i in indices if wv1.words[i] not in exclude] v2 = [wv2.vectors[i] for i in indices if wv2.words[i] not in exclude] elif anchor_words is not None: v1 = [wv1[w] for w in anchor_words if w not in exclude] v2 = [wv2[w] for w in anchor_words if w not in exclude] else: # just use all words v1 = [wv1[w] for w in wv1.words if w not in exclude] v2 = [wv2[w] for w in wv2.words if w not in exclude] v1 = np.array(v1) v2 = np.array(v2) if method == "procrustes": # align with OP Q, _ = orthogonal_procrustes(v1, v2) wv1_ = WordVectors(words=wv1.words, vectors=np.dot(wv1.vectors, Q)) return wv1_, wv2, Q
def update_rotation(self): """ Update the rotation matrix (self.R) based on the inlying refls """ from scipy.linalg import orthogonal_procrustes misset, _ = orthogonal_procrustes( self.qobs, self.qpred * self.wav[:, None], ) self.R = misset @ self.R
def compute_rotated_similarity(early, later): R,_ = orthogonal_procrustes(early, later, check_finite=False) rotated = early @ R sims = cosine_similarity(rotated.to_numpy(), later.to_numpy()) ret = list() for i in range(0, len(sims)): ret.append(sims[i][i]) return ret
def _check_fit_on_synthetic_example(self, latent_dim: int, n_samples: int, n_variables: int, lr: float, n_batch_size: int, patience: int, n_epochs: int) -> None: # create a random ground-truth for W and b W = np.random.randn(latent_dim, n_variables) b = np.random.randn(n_variables) # generate data from the ground-truth model # step 1: sample the latent variables zs = np.random.randn(n_samples, latent_dim) # step 2: use the latent variables to generate the observations data = ( # evaluate the probability vectors 1. / (1. + np.exp(-(np.matmul(zs, W) + b))) # compare the probabilities to uniform random noise to # create the observations > np.random.rand(n_samples, n_variables)).astype(int) # create and fit the model model = traits.LatentTraitModel(latent_dim=latent_dim) model.fit(data, lr=lr, n_batch_size=n_batch_size, patience=patience, n_epochs=n_epochs, device=th.device('cuda') if th.cuda.is_available() else th.device('cpu')) # evaluate the parameter estimates W_hat = model.W_.numpy() # Since the parameters in a latent trait / factor analysis model # are only determined up to a rotation, we must align the # learned parameters with the ground-truth with a rotation. R, _ = orthogonal_procrustes(W_hat, W) W_hat = np.matmul(W_hat, R) b_hat = model.b_.numpy() # check that self.n_samples_ and self.n_variables_ were set # correctly self.assertEqual(model.n_samples_, n_samples) self.assertEqual(model.n_variables_, n_variables) # sanity-check that the learned weights give a better estimate # than the origin if latent_dim > 0: self.assertLess(np.mean((W - W_hat)**2), np.mean(W**2)) self.assertLess(np.mean((b - b_hat)**2), np.mean(b**2)) # check that the learned weights give good estimates for this # easy problem if latent_dim > 0: self.assertLess(np.mean((W - W_hat)**2), 7.5e-2) self.assertLess(np.mean((b - b_hat)**2), 7.5e-2)
def test_scaled_procrustes_algorithmic(): '''Test Scaled procrustes''' X = np.random.randn(10, 20) Y = np.zeros_like(X) R = np.eye(X.shape[1]) R_test, _ = scaled_procrustes(X, Y) assert_array_almost_equal(R, R_test.toarray()) '''Test if scaled_procrustes basis is orthogonal''' X = np.random.rand(3, 4) X = X - X.mean(axis=1, keepdims=True) Y = np.random.rand(3, 4) Y = Y - Y.mean(axis=1, keepdims=True) R, _ = scaled_procrustes(X.T, Y.T) assert_array_almost_equal(R.dot(R.T), np.eye(R.shape[0])) assert_array_almost_equal(R.T.dot(R), np.eye(R.shape[0])) ''' Test if it sticks to scipy scaled procrustes in a simple case''' X = np.random.rand(4, 4) Y = np.random.rand(4, 4) R, _ = scaled_procrustes(X, Y) R_s, _ = orthogonal_procrustes(Y, X) assert_array_almost_equal(R.T, R_s) '''Test that primal and dual give same results''' # number of samples n , number of voxels p n, p = 100, 20 X = np.random.randn(n, p) Y = np.random.randn(n, p) R1, s1 = scaled_procrustes(X, Y, scaling=True, primal=True) R_s, _ = orthogonal_procrustes(Y, X) R2, s2 = scaled_procrustes(X, Y, scaling=True, primal=False) assert_array_almost_equal(R1, R2) assert_array_almost_equal(R2, R_s.T) n, p = 20, 100 X = np.random.randn(n, p) Y = np.random.randn(n, p) R1, s1 = scaled_procrustes(X, Y, scaling=True, primal=True) R_s, _ = orthogonal_procrustes(Y, X) R2, s2 = scaled_procrustes(X, Y, scaling=True, primal=False) assert_array_almost_equal(s1 * X.dot(R1), s2 * X.dot(R2))
def test_orthogonal_procrustes_stretched_example(): # Try again with a target with a stretched y axis. A_orig = np.array([[-3, 3], [-2, 3], [-2, 2], [-3, 2]], dtype=float) B_orig = np.array([[3, 40], [1, 0], [3, -40], [5, 0]], dtype=float) A, A_mu = _centered(A_orig) B, B_mu = _centered(B_orig) R, s = orthogonal_procrustes(A, B) scale = s / np.square(norm(A)) B_approx = scale * np.dot(A, R) + B_mu expected = np.array([[3, 21], [-18, 0], [3, -21], [24, 0]], dtype=float) assert_allclose(B_approx, expected, atol=1e-8) # Check disparity symmetry. expected_disparity = 0.4501246882793018 AB_disparity = np.square(norm(B_approx - B_orig) / norm(B)) assert_allclose(AB_disparity, expected_disparity) R, s = orthogonal_procrustes(B, A) scale = s / np.square(norm(B)) A_approx = scale * np.dot(B, R) + A_mu BA_disparity = np.square(norm(A_approx - A_orig) / norm(A)) assert_allclose(BA_disparity, expected_disparity)
def _optimize_rigid_pos_3d(self, recon_verts, tar_verts): tar_center = np.mean(tar_verts, axis=0) recon_center = np.mean(recon_verts, axis=0) tar_verts_centered = tar_verts - tar_center recon_verts_centered = recon_verts - recon_center scale_recon = np.linalg.norm(recon_verts_centered) / np.linalg.norm( tar_verts_centered) recon_verts_centered = recon_verts / scale_recon translate = tar_center rotation, _ = orthogonal_procrustes(tar_verts_centered, recon_verts_centered) return 1 / scale_recon, translate, rotation
def test_orthogonal_procrustes_exact_example(): # Check a small application. # It uses translation, scaling, reflection, and rotation. # # | # a b | # | # d c | w # | # --------+--- x ----- z --- # | # | y # | # A_orig = np.array([[-3, 3], [-2, 3], [-2, 2], [-3, 2]], dtype=float) B_orig = np.array([[3, 2], [1, 0], [3, -2], [5, 0]], dtype=float) A, A_mu = _centered(A_orig) B, B_mu = _centered(B_orig) R, s = orthogonal_procrustes(A, B) scale = s / np.square(norm(A)) B_approx = scale * np.dot(A, R) + B_mu assert_allclose(B_approx, B_orig, atol=1e-8)
def compute_optimal_rotation(L, L_true, scale=True): """ Find a rotation matrix R such that F_inf.dot(R) ~= F_true :return: """ from scipy.linalg import orthogonal_procrustes R = orthogonal_procrustes(L, L_true)[0] # Given R, find a scale such that Lp' = Lp*s is such that, # s = argmin_s y = (Lp * s - L)^T (Lp*s - L) # d/ds y = 2 * (Lp * s - L)^T Lp = 0 # => Lp^T Lp *s - L^T Lp = 0 # => s = (L^T Lp) / (Lp^T Lp) # # Then roll s into R such that Lp' = Lp*s = L.dot(R)*s = L.dot(R') # for R' = R*s if scale: Lp = L.dot(R) s = (L_true*Lp).sum() / (Lp*Lp).sum() return R*s else: return R
def compute_optimal_rotation(self, F_true, lmbda_true): """ Find a rotation matrix R such that F_inf * R ~= F_true :return: """ # from sklearn.linear_model import LinearRegression # assert F_true.shape == (self.N, self.D), "F_true must be NxD" # F_inf = self.F # # # TODO: Scale by lambda in order to get the scaled rotation # # R = np.zeros((self.D, self.D)) # for d in xrange(self.D): # lr = LinearRegression(fit_intercept=False) # R[:,d] = lr.fit(F_inf, F_true[:,d]).coef_ # # # TODO: Normalize this column of the rotation matrix # R[:,d] /= np.linalg.norm(R[:,d]) # from scipy.linalg import orthogonal_procrustes R = orthogonal_procrustes(self.F / np.sqrt(abs(self.lmbda[None, :])), F_true / np.sqrt(abs(lmbda_true[None,:])))[0] return R
def procrustes(data1, data2): r"""Procrustes analysis, a similarity test for two data sets. Each input matrix is a set of points or vectors (the rows of the matrix). The dimension of the space is the number of columns of each matrix. Given two identically sized matrices, procrustes standardizes both such that: - :math:`tr(AA^{T}) = 1`. - Both sets of points are centered around the origin. Procrustes ([1]_, [2]_) then applies the optimal transform to the second matrix (including scaling/dilation, rotations, and reflections) to minimize :math:`M^{2}=\sum(data1-data2)^{2}`, or the sum of the squares of the pointwise differences between the two input datasets. This function was not designed to handle datasets with different numbers of datapoints (rows). If two data sets have different dimensionality (different number of columns), simply add columns of zeros to the smaller of the two. Parameters ---------- data1 : array_like Matrix, n rows represent points in k (columns) space `data1` is the reference data, after it is standardised, the data from `data2` will be transformed to fit the pattern in `data1` (must have >1 unique points). data2 : array_like n rows of data in k space to be fit to `data1`. Must be the same shape ``(numrows, numcols)`` as data1 (must have >1 unique points). Returns ------- mtx1 : array_like A standardized version of `data1`. mtx2 : array_like The orientation of `data2` that best fits `data1`. Centered, but not necessarily :math:`tr(AA^{T}) = 1`. disparity : float :math:`M^{2}` as defined above. Raises ------ ValueError If the input arrays are not two-dimensional. If the shape of the input arrays is different. If the input arrays have zero columns or zero rows. See Also -------- scipy.linalg.orthogonal_procrustes scipy.spatial.distance.directed_hausdorff : Another similarity test for two data sets Notes ----- - The disparity should not depend on the order of the input matrices, but the output matrices will, as only the first output matrix is guaranteed to be scaled such that :math:`tr(AA^{T}) = 1`. - Duplicate data points are generally ok, duplicating a data point will increase its effect on the procrustes fit. - The disparity scales as the number of points per input matrix. References ---------- .. [1] Krzanowski, W. J. (2000). "Principles of Multivariate analysis". .. [2] Gower, J. C. (1975). "Generalized procrustes analysis". Examples -------- >>> from scipy.spatial import procrustes The matrix ``b`` is a rotated, shifted, scaled and mirrored version of ``a`` here: >>> a = np.array([[1, 3], [1, 2], [1, 1], [2, 1]], 'd') >>> b = np.array([[4, -2], [4, -4], [4, -6], [2, -6]], 'd') >>> mtx1, mtx2, disparity = procrustes(a, b) >>> round(disparity) 0.0 """ mtx1 = np.array(data1, dtype=np.double, copy=True) mtx2 = np.array(data2, dtype=np.double, copy=True) if mtx1.ndim != 2 or mtx2.ndim != 2: raise ValueError("Input matrices must be two-dimensional") if mtx1.shape != mtx2.shape: raise ValueError("Input matrices must be of same shape") if mtx1.size == 0: raise ValueError("Input matrices must be >0 rows and >0 cols") # translate all the data to the origin mtx1 -= np.mean(mtx1, 0) mtx2 -= np.mean(mtx2, 0) norm1 = np.linalg.norm(mtx1) norm2 = np.linalg.norm(mtx2) if norm1 == 0 or norm2 == 0: raise ValueError("Input matrices must contain >1 unique points") # change scaling of data (in rows) such that trace(mtx*mtx') = 1 mtx1 /= norm1 mtx2 /= norm2 # transform mtx2 to minimize disparity R, s = orthogonal_procrustes(mtx1, mtx2) mtx2 = np.dot(mtx2, R.T) * s # measure the dissimilarity between the two datasets disparity = np.sum(np.square(mtx1 - mtx2)) return mtx1, mtx2, disparity