def map(self, X):
        """ Maps X (configuration or trajectory) to reference structure by permuting identical particles """
        X = ensure_traj(X)
        Y = X.copy()
        C = distance_matrix_squared(np.tile(self.xref[:, self.ip_indices], (X.shape[0], 1)), X[:, self.ip_indices])

        for i in range(C.shape[0]):  # for each configuration
            _, col_assignment = linear_sum_assignment(C[i])
            assignment_components = [self.dim*col_assignment+i for i in range(self.dim)]
            col_assignment = np.vstack(assignment_components).T.flatten()
            Y[i, self.ip_indices] = X[i, self.ip_indices[col_assignment]]
        return Y
    def is_permuted(self, X):
        """ Returns True for permuted configurations """
        X = ensure_traj(X)
        C = distance_matrix_squared(np.tile(self.xref[:, self.ip_indices], (X.shape[0], 1)), X[:, self.ip_indices])
        isP = np.zeros(X.shape[0], dtype=bool)

        for i in range(C.shape[0]):  # for each configuration
            _, col_assignment = linear_sum_assignment(C[i])
            assignment_components = [self.dim*col_assignment+i for i in range(self.dim)]
            col_assignment = np.vstack(assignment_components).T.flatten()
            if not np.all(col_assignment == np.arange(col_assignment.size)):
                isP[i] = True
        return isP
 def _distance_squared_matrix(self, crd1, crd2):
     return distance_matrix_squared(crd1, crd2, dim=2)