def test_gram(self): """test construction of gram matrix""" tdt_1 = tdt.basis_decomposition(self.data, self.phi_1).transpose(cores=[self.d]).matricize() tdt_2 = tdt.basis_decomposition(self.data_2, self.phi_1).transpose(cores=[self.d]).matricize() gram = tdt.gram(self.data, self.data_2, self.phi_1) self.assertLess(np.sum(np.abs(tdt_1.T.dot(tdt_2) - gram)), self.tol)
def construct_tdm(data, basis_list): """Construct transformed data matrices. Parameters ---------- data: ndarray snapshot matrix basis_list: list of lists of lambda functions list of basis functions in every mode Returns ------- psi_x: instance of TT class transformed data matrix corresponding to x psi_y: instance of TT class transformed data matrix corresponding to y """ # extract snapshots for x and y x = data[:, :-1] y = data[:, 1:] # construct psi_x and psi_y start_time = utl.progress('Construct transformed data matrices', 0) psi_x = tdt.basis_decomposition(x, basis_list).transpose(cores=2).matricize() utl.progress('Construct transformed data matrices', 50, cpu_time=_time.time() - start_time) psi_y = tdt.basis_decomposition(y, basis_list).transpose(cores=2).matricize() utl.progress('Construct transformed data matrices', 100, cpu_time=_time.time() - start_time) return psi_x, psi_y
def plot_eigenfunction(basis_list, eigenvector): """Plot eigenfunctions. Parameters ---------- basis_list: list of lists of lambda functions list of basis functions in every mode eigenvector: array TEDMD eigenvector """ # normalize eigenvector eigenvector = (1 / np.max(eigenvector)) * eigenvector plt.rcParams.update({'axes.grid': False}) plt.figure(dpi=300) # evaluate eigenfunction over domain grid_size = 100 dist = 12 / grid_size z = np.zeros([grid_size, grid_size]) for i in range(grid_size): for j in range(grid_size): x_tmp = np.array([-6 + i * dist, -6 + j * dist])[:, None] z[i, j] = tdt.basis_decomposition(x_tmp, basis_list).matricize().dot(eigenvector) plt.imshow(z, cmap='seismic', vmin=-1, vmax=1) plt.colorbar() plt.xticks([0, (grid_size - 1) / 2, grid_size - 1], [-6, 0, 6]) plt.yticks([0, (grid_size - 1) / 2, grid_size - 1], [-6, 0, 6]) plt.xlim([0, grid_size - 1]) plt.ylim([0, grid_size - 1]) plt.xlabel(r'$x_1$') plt.ylabel(r'$x_2$') plt.rcParams.update({'axes.grid': True})
def test_basis_decomposition(self): """test construction of transformed data tensors""" tdt_1 = tdt.basis_decomposition(self.data, self.phi_1).transpose(cores=[self.d]).matricize() tdt_2 = np.zeros([3 ** self.d, self.m]) for j in range(self.m): v = [1, self.data[0, j], self.data[0, j] ** 2] for i in range(1, self.d): v = np.kron(v, [1, self.data[i, j], self.data[i, j] ** 2]) tdt_2[:, j] = v self.assertEqual(np.sum(np.abs(tdt_1 - tdt_2)), 0) tdt_1 = tdt.basis_decomposition(self.data, self.phi_1) core_0 = tdt.basis_decomposition(self.data, self.phi_1, single_core=0) core_1 = tdt.basis_decomposition(self.data, self.phi_1, single_core=1) self.assertEqual(np.sum(np.abs(tdt_1.cores[0] - core_0)), 0) self.assertEqual(np.sum(np.abs(tdt_1.cores[1] - core_1)), 0)
def test_coordinate_major(self): """test coordinate-major decomposition""" tdt_1 = tdt.basis_decomposition(self.data, self.phi_1) tdt_2 = tdt.coordinate_major(self.data, self.psi_1) self.assertLess((tdt_1 - tdt_2).norm(), self.tol) core_0 = tdt.coordinate_major(self.data, self.psi_1, single_core=0) core_1 = tdt.coordinate_major(self.data, self.psi_1, single_core=1) self.assertEqual(np.sum(np.abs(tdt_1.cores[0] - core_0)), 0) self.assertEqual(np.sum(np.abs(tdt_1.cores[1] - core_1)), 0)
def test_function_major(self): """test function-major decomposition""" tdt_1 = tdt.basis_decomposition(self.data, self.phi_2) _ = tdt.function_major(self.data, self.psi_2, add_one=False) _ = tdt.function_major(self.data, self.psi_2, add_one=False, single_core=0) _ = tdt.function_major(self.data, self.psi_2, add_one=False, single_core=1) tdt_2 = tdt.function_major(self.data, self.psi_2) self.assertLess((tdt_1 - tdt_2).norm(), self.tol) core_0 = tdt.function_major(self.data, self.psi_2, single_core=0) core_1 = tdt.function_major(self.data, self.psi_2, single_core=1) self.assertEqual(np.sum(np.abs(tdt_1.cores[0] - core_0)), 0) self.assertEqual(np.sum(np.abs(tdt_1.cores[1] - core_1)), 0)
def test_hocur(self): """test higher-order CUR decomposition""" tdt_1 = tdt.basis_decomposition(self.data, self.phi_1).transpose(cores=[self.d]).matricize() tdt_2 = tdt.hocur(self.data, self.phi_1, 5, repeats=10, progress=False).transpose(cores=[self.d]).matricize() self.assertLess(np.sum(np.abs(tdt_1-tdt_2)), self.tol)
def test_mandy_kb(self): """test kernel-based approach""" # apply kernel-based MANDy z = reg.mandy_kb(self.kuramoto_x, self.kuramoto_y, self.kuramoto_basis) # construct coefficient tensor xi = tdt.basis_decomposition(self.kuramoto_x, self.kuramoto_basis) xi.cores[-1] = np.tensordot(xi.cores[-1], z.T, axes=(1, 0)).transpose([0, 3, 1, 2]) xi.row_dims[-1] = z.shape[0] # compute relative error rel_err = (xi - self.kuramoto_xi_exact ).norm() / self.kuramoto_xi_exact.norm() # check if relative error is smaller than tolerance self.assertLess(rel_err, self.tol)
def amuset_hosvd(data_matrix, x_indices, y_indices, basis_list, threshold=1e-2, progress=False): """ AMUSEt (AMUSE on tensors) using HOSVD. Apply tEDMD to a given data matrix by using AMUSEt with HOSVD. This procedure is a tensor-based version of AMUSE using the tensor-train format. For more details, see [1]_. Parameters ---------- data_matrix : np.ndarray snapshot matrix x_indices : np.ndarray or list[np.ndarray] index sets for snapshot matrix x y_indices : np.ndarray or list[np.ndarray] index sets for snapshot matrix y basis_list : list[list[function]] list of basis functions in every mode threshold : float, optional threshold for SVD/HOSVD, default is 1e-2 progress : boolean, optional whether to show progress bar, default is False Returns ------- eigenvalues : np.ndarray or list[np.ndarray] tEDMD eigenvalues eigentensors : TT or list[TT] tEDMD eigentensors in TT format References ---------- ..[1] F. Nüske, P. Gelß, S. Klus, C. Clementi. "Tensor-based EDMD for the Koopman analysis of high-dimensional systems", arXiv:1908.04741, 2019 """ # define quantities eigenvalues = [] eigentensors = [] # construct transformed data tensor in TT format using direct approach psi = tdt.basis_decomposition(data_matrix, basis_list) # left-orthonormalization psi = psi.ortho_left(threshold=threshold, progress=progress) # extract last core last_core = psi.cores[-1] # convert x_indices and y_indices to lists if not isinstance(x_indices, list): x_indices = [x_indices] y_indices = [y_indices] # loop over all index sets for i in range(len(x_indices)): # compute reduced matrix matrix, u, s, v = _reduced_matrix(last_core, x_indices[i], y_indices[i]) # solve reduced eigenvalue problem eigenvalues_reduced, eigenvectors_reduced = np.linalg.eig(matrix) idx = (np.abs(eigenvalues_reduced - 1)).argsort() eigenvalues_reduced = np.real(eigenvalues_reduced[idx]) eigenvectors_reduced = np.real(eigenvectors_reduced[:, idx]) # construct eigentensors eigentensors_tmp = psi eigentensors_tmp.cores[-1] = u.dot(np.diag(np.reciprocal(s))).dot(eigenvectors_reduced)[:, :, None, None] # append results eigenvalues.append(eigenvalues_reduced) eigentensors.append(eigentensors_tmp) # only return lists if more than one set of x-indices/y-indices was given if len(x_indices) == 1: eigenvalues = eigenvalues[0] eigentensors = eigentensors[0] return eigenvalues, eigentensors
def amuset_hosvd(data_matrix, basis_list, b, sigma, num_eigvals=np.infty, threshold=1e-2, max_rank=np.infty, return_option='eigenfunctionevals'): """ AMUSEt algorithm for the calculation of eigenvalues of the Koopman generator. The tensor-trains are created using the exact TT decomposition, whose ranks are reduced using SVDs. An efficient implementation of tensor contractions that exploits the special structure of the cores is used. Parameters ---------- data_matrix : np.ndarray snapshot matrix, shape (d, m) basis_list : list[list[Function]] list of basis functions in every mode b : np.ndarray drift, shape (d, m) sigma : np.ndarray diffusion, shape (d, d2, m) num_eigvals : int, optional number of eigenvalues and eigentensors that are returned default: return all calculated eigenvalues and eigentensors threshold : float, optional threshold for svd of psi max_rank : int, optional maximal rank of TT representations of psi after svd/ortho return_option : {'eigentensors', 'eigenfunctionevals', 'eigenvectors'} 'eigentensors': return a list of the eigentensors of the koopman generator 'eigenfunctionevals': return the evaluations of the eigenfunctions of the koopman generator at all snapshots 'eigenvectors': eigenvectors of M in AMUSEt Returns ------- eigvals : np.ndarray eigenvalues of Koopman generator eigtensors : list[TT] or np.ndarray eigentensors of Koopman generator or evaluations of eigenfunctions at snapshots (shape (*, m)) (cf. return_option) """ print('calculating Psi(X)...') psi = basis_decomposition(data_matrix, basis_list) p = psi.order - 1 # SVD of psi u, s, v = psi.svd(p, threshold=threshold, max_rank=max_rank, ortho_l=True, ortho_r=False) s_inv = np.diag(1.0 / s) psi = u.rank_tensordot(np.diag(s)) psi.concatenate(v, overwrite=True) # rank reduced version v = (v.cores[0][:, :, 0, 0]).T # translate v from TT to np.ndarray print('Psi(X): {}'.format(psi)) print('calculating M in AMUSEt') M = _amuset_efficient(u, s, v, data_matrix, basis_list, b, sigma) print('calculating eigenvalues and eigentensors...') # calculate eigenvalues of M eigvals, eigvecs = np.linalg.eig(M) sorted_indices = np.argsort(-eigvals) eigvals = eigvals[sorted_indices] eigvecs = eigvecs[:, sorted_indices] if not (eigvals < 0).all(): print('WARNING: there are eigenvalues >= 0') if len(eigvals) > num_eigvals: eigvals = eigvals[:num_eigvals] eigvecs = eigvecs[:, :num_eigvals] u.rank_tensordot(s_inv, mode='last', overwrite=True) # calculate eigentensors if return_option == 'eigentensors': eigvecs = eigvecs[:, :, np.newaxis] eigtensors = [] for i in range(eigvals.shape[0]): eigtensor = u.copy() eigtensor.rank_tensordot(eigvecs[:, i, :], overwrite=True) eigtensors.append(eigtensor) return eigvals, eigtensors elif return_option == 'eigenfunctionevals': u.rank_tensordot(eigvecs, overwrite=True) u.tensordot(psi, p, mode='first-first', overwrite=True) u = u.cores[0][0, :, 0, :].T return eigvals, u else: return eigvals, eigvecs