def tree_based_tensor(self): ''' Convert a FullTensor into a TreeBasedTensor. Returns ------- TreeBasedTensor The FullTensor in tree-based tensor format. ''' tree = tensap.DimensionTree.trivial(self.order) tensors = [FullTensor(self)] for dim in range(self.order): tensors.append(FullTensor(np.eye(self.shape[dim]))) return tensap.TreeBasedTensor(tensors, tree)
def hosvd(self, tensor): ''' Compute the truncated hosvd of tensor. Parameters ---------- tensor : np.ndarray or tensap.FullTensor or tensap.TreeBasedTensor The tensor to truncate. Raises ------ ValueError If the input tensor is of the wrong type. Returns ------- out : tensap.CanonicalTensor or tensap.TreeBasedTensor The truncated tensor. ''' if isinstance(tensor, np.ndarray): tensor = tensap.FullTensor(tensor) order = tensor.order if order == 2: out = self.svd(tensor) else: max_rank = np.atleast_1d(self.max_rank) if max_rank.size == 1: max_rank = np.repeat(max_rank, order) local_tol = self.tolerance / np.sqrt(order) if isinstance(tensor, tensap.FullTensor): vec = np.empty(order, dtype=object) for dim in range(order): self.max_rank = max_rank[dim] vec[dim] = self.trunc_svd(tensor.matricize(dim).numpy(), local_tol) vec[dim] = vec[dim].space[0] core = tensor.tensor_matrix_product(np.transpose(vec[0]), 0) for dim in np.arange(1, order): core = core.tensor_matrix_product( np.transpose(vec[dim]), dim) tensors = [core] + [tensap.FullTensor(x) for x in vec] tree = tensap.DimensionTree.trivial(order) out = tensap.TreeBasedTensor(tensors, tree) else: raise ValueError('Wrong type.') return out
def tree_based_tensor(self, tree=None, is_active_node=None): ''' Convert the tensap.DiagonalTensor into a tensap.TreeBasedTensor. Parameters ---------- tree : tensap.DimensionTree, optional The tree associated with the tree-based tensor representation. The default is a linear tree. is_active_node : list or numpy.ndarray, optional List or array of booleans indicating if each node of the tree is active. The default is True for all nodes except the leaves. Raises ------ ValueError If the internal nodes are not all active. Returns ------- tensap.TreeBasedTensor A tree-based tensor representation of the diagonal tensor. ''' if tree is None: tree = tensap.DimensionTree.linear(self.order) if is_active_node is None: is_active_node = np.full(tree.nb_nodes, True) is_active_node[tree.is_leaf] = False tensors = np.empty(tree.nb_nodes, dtype=object) tensors[np.logical_not(is_active_node)] = tensap.FullTensor([]) r = self.shape[0] for nod in np.arange(1, tree.nb_nodes + 1): ch = tree.children(nod) if tree.parent(nod) == 0: tensors[nod - 1] = tensap.FullTensor.diag(self.data, ch.size) elif tree.is_leaf[nod - 1] and is_active_node[nod - 1]: tensors[nod - 1] = tensap.FullTensor(np.eye(r), 2, [r, r]) elif is_active_node[nod - 1]: tensors[nod - 1] = tensap.FullTensor.diag( np.ones(r), ch.size + 1) elif not tree.is_leaf[nod - 1] and not is_active_node[nod - 1]: raise ValueError('The internal nodes should be active.') return tensap.TreeBasedTensor(tensors, tree)
def tree_based_tensor(self, tree, is_active_node=None): ''' Convert a CanonicalTensor to a tensap.TreeBasedTensor with given dimension tree and active nodes. Parameters ---------- tree : tensap.DimensionTree The dimension tree. is_active_node : list or numpy.ndarray, optional Booleans indicating if the nodes are active. The default is None, settings all the nodes active. Returns ------- out : tensap.TreeBasedTensor The canonical tensor converted into a tree-based tensor. ''' rank = self.core.shape[0] tensors = np.empty(tree.nb_nodes, dtype=object) for nod in np.arange(1, tree.nb_nodes + 1): ch = tree.children(nod) if tree.parent(nod) == 0: order = ch.size tensors[nod - 1] = tensap.FullTensor.diag( self.core.data, order) else: order = ch.size + 1 tensors[nod - 1] = tensap.FullTensor.diag(np.ones(rank), order) tensors[tree.dim2ind - 1] = self.space out = tensap.TreeBasedTensor(tensors, tree) if is_active_node is not None: out = out.inactivate_nodes( np.nonzero(np.logical_not(is_active_node))[0] + 1) return out
def tree_based_approximation(self, fun, shape, tree, is_active_node=None): ''' Approximation of a tensor of order d in tree based tensor format based on a Principal Component Analysis. For a prescribed precision, set TPCA.max_rank = np.inf and TPCA.tol to the desired precision (possibly an array of length d-1). For a prescribed rank, set TPCA.tol = np.inf and TPCA.max_rank to the desired rank (possibly an array of length d-1). See also the documentation of the class TensorPrincipalComponentAnalysis. Parameters ---------- fun : fun or tensap.Function Function of d variables i_1, ..., i_d which returns the entries of the tensor. shape : list or numpy.ndarray The shape of the tensor. tree : tensap.DimensionTree The required dimension tree. is_active_node : list or numpy.ndarray, optional An array of booleans indicating which nodes of the tree are active. The default is None, settings all the nodes active. Raises ------ ValueError If the provided tolerance and max ranks are not correct. Returns ------- tensap.TreeBasedTensor A tensor in tree based format. dict Dictionnary containing the outputs of the method. ''' solver = deepcopy(self) d = len(shape) if is_active_node is None: is_active_node = np.full(tree.nb_nodes, True) if (np.ndim(self.tol) == 0 or len(self.tol) == 1) and self.tol < 1: solver.tol /= np.sqrt(np.count_nonzero(is_active_node) - 1) if np.ndim(self.tol) == 0 or len(self.tol) == 1: solver.tol = np.full(tree.nb_nodes, solver.tol) elif len(self.tol) != tree.nb_nodes: raise ValueError('tol should be a scalar or an array of length ' + 'tree.nb_nodes.') if np.ndim(self.max_rank) == 0 or len(self.max_rank) == 1: solver.max_rank = np.full(tree.nb_nodes, self.max_rank) elif len(self.max_rank) != tree.nb_nodes: raise ValueError('max_rank should be a scalar or an array of ' + 'length tree.nb_nodes.') grids = [np.reshape(np.arange(x), (-1, 1)) for x in shape] alpha_basis = np.empty(tree.nb_nodes, dtype=object) alpha_grids = np.empty(tree.nb_nodes, dtype=object) outputs = np.empty(tree.nb_nodes, dtype=object) samples = np.empty(tree.nb_nodes, dtype=object) tensors = [[]] * tree.nb_nodes number_of_evaluations = 0 for nu in range(d): alpha = tree.dim2ind[nu] B_alpha = np.eye(shape[nu]) if is_active_node[alpha - 1]: tol_alpha = np.min( (solver.tol[alpha - 1], solver.max_rank[alpha - 1])) pc_alpha, outputs[alpha-1] = \ solver.alpha_principal_components(fun, shape, nu, tol_alpha, B_alpha, grids[nu]) samples[alpha - 1] = outputs[alpha - 1]['samples'] shape_alpha = [shape[nu], pc_alpha.shape[1]] tensors[alpha - 1] = tensap.FullTensor(pc_alpha, 2, shape_alpha) B_alpha = np.matmul(B_alpha, pc_alpha) I_alpha = tensap.magic_indices(B_alpha)[0] alpha_grids[alpha - 1] = grids[nu][I_alpha, :] alpha_basis[alpha - 1] = B_alpha[I_alpha, :] number_of_evaluations += outputs[alpha - 1]['number_of_evaluations'] if solver.display: print('alpha = %i : rank = %i, nb_eval = %i' % (alpha, shape_alpha[-1], outputs[alpha - 1]['number_of_evaluations'])) else: alpha_grids[alpha - 1] = grids[nu] alpha_basis[alpha - 1] = B_alpha for level in np.arange(np.max(tree.level), 0, -1): for alpha in np.intersect1d(tree.nodes_with_level(level), tree.internal_nodes): S_alpha = tree.children(alpha) B_alpha = TensorPrincipalComponentAnalysis.\ _tensor_product_b_alpha(alpha_basis[S_alpha-1]) alpha_grids[alpha-1] = \ tensap.FullTensorGrid(alpha_grids[S_alpha-1]).array() tol_alpha = np.min( (solver.tol[alpha - 1], solver.max_rank[alpha - 1])) pc_alpha, outputs[alpha-1] = \ solver.alpha_principal_components(fun, shape, tree.dims[alpha-1], tol_alpha, B_alpha, alpha_grids[alpha-1]) samples[alpha - 1] = outputs[alpha - 1]['samples'] shape_alpha = np.concatenate( ([x.shape[1] for x in alpha_basis[S_alpha - 1]], [pc_alpha.shape[1]])) tensors[alpha - 1] = tensap.FullTensor(pc_alpha, len(S_alpha) + 1, shape_alpha) B_alpha = np.matmul(B_alpha, pc_alpha) I_alpha = tensap.magic_indices(B_alpha)[0] alpha_grids[alpha - 1] = alpha_grids[alpha - 1][I_alpha, :] alpha_basis[alpha - 1] = B_alpha[I_alpha, :] number_of_evaluations += outputs[alpha - 1]['number_of_evaluations'] if solver.display: print('alpha = %i : rank = %i, nb_eval = %i' % (alpha, shape_alpha[-1], outputs[alpha - 1]['number_of_evaluations'])) alpha = tree.root S_alpha = tree.children(alpha) B_alpha = TensorPrincipalComponentAnalysis.\ _tensor_product_b_alpha(alpha_basis[S_alpha-1]) I_alpha = tensap.FullTensorGrid(alpha_grids[S_alpha - 1]).array() shape_alpha = [x.shape[1] for x in alpha_basis[S_alpha - 1]] ind = [np.nonzero(tree.dims[alpha - 1] == x)[0][0] for x in range(d)] tensors[alpha - 1] = tensap.FullTensor( np.linalg.solve(B_alpha, fun(I_alpha[:, ind])), len(S_alpha), shape_alpha) alpha_grids[alpha - 1] = I_alpha number_of_evaluations += I_alpha.shape[0] samples[alpha - 1] = I_alpha if solver.display: print('Interpolation - nb_eval = %i' % I_alpha.shape[0]) f = tensap.TreeBasedTensor(tensors, tree) output = { 'number_of_evaluations': number_of_evaluations, 'samples': samples, 'alpha_basis': alpha_basis, 'alpha_grids': alpha_grids, 'outputs': outputs } return f, output
def hsvd(self, tensor, tree=None, is_active_node=None): ''' Compute the truncated svd in tree-based tensor format of tensor. Parameters ---------- tensor : tensap.FullTensor or tensap.TreeBasedTensor The tensor to truncate. tree : tensap.DimensionTree, optional The tree of the output tree-based tensor. The default is None, indicating if tensor is a tensap.TreeBasedTensor to take tensor.tree. is_active_node : numpy.ndarray, optional Logical array indicating if the nodes are active.. The default is None, indicating if tensor is a tensap.TreeBasedTensor to take tensor.is_active_node. Raises ------ ValueError If the wrong value of the atttribude _hsvd_type is provided. NotImplementedError If the method is not implemented for the format. Returns ------- out : tensap.TreeBasedTensor The truncated tensor in tree-based tensor format. ''' if isinstance(tensor, tensap.TreeBasedTensor): if tree is not None or is_active_node is not None: warnings.warn('The provided tree and/or is_active_node ' 'are not taken into account when x is a ' 'tensap.TreeBasedTensor.') is_active_node = tensor.is_active_node tree = tensor.tree elif is_active_node is None: is_active_node = np.full(tree.nb_nodes, True) max_rank = np.atleast_1d(self.max_rank) if max_rank.size == 1: max_rank = np.repeat(max_rank, tree.nb_nodes) max_rank[tree.root-1] = 1 local_tol = self.tolerance / np.sqrt( np.count_nonzero(is_active_node)-1) if isinstance(tensor, tensap.FullTensor): root_rank_greater_than_one = tensor.order == len(tree.dim2ind)+1 tensors = np.empty(tree.nb_nodes, dtype=object) shape = np.array(tensor.shape) nodes_x = tree.dim2ind ranks = np.ones(tree.nb_nodes, dtype=int) for level in np.arange(np.max(tree.level), 0, -1): for nod in tree.nodes_with_level(level): if is_active_node[nod-1]: if tree.is_leaf[nod-1]: rep = np.nonzero(nod == nodes_x)[0][0] else: children = tree.children(nod) rep = [np.nonzero(np.isin(nodes_x, x))[0][0] for x in children] rep_c = tensap.fast_setdiff(np.arange(nodes_x.size), rep) if root_rank_greater_than_one: rep_c = np.concatenate((rep_c, [tensor.order-1])) self.max_rank = max_rank[nod-1] tmp = self.trunc_svd(tensor.matricize(rep).numpy(), local_tol) tensors[nod-1] = tmp.space[0] ranks[nod-1] = tensors[nod-1].shape[1] shape_loc = np.hstack((shape[rep], ranks[nod-1])) tensors[nod-1] = tensap.FullTensor(tensors[nod-1], shape=shape_loc) tmp = np.matmul(tmp.space[1], np.diag(tmp.core.data)) shape = np.hstack((shape[rep_c], ranks[nod-1])) tensor = tensap.FullTensor(tmp, shape=shape) if root_rank_greater_than_one: perm = np.concatenate((np.arange(tensor.order-2), [tensor.order-1], [tensor.order-2])) tensor = tensor.transpose(perm) shape = shape[perm] rep_c = rep_c[:-1] nodes_x = np.hstack((nodes_x[rep_c], nod)) else: tensors[nod-1] = [] root_ch = tree.children(tree.root) rep = [np.nonzero(np.isin(nodes_x, x))[0][0] for x in root_ch] if root_rank_greater_than_one: rep = np.concatenate((rep, [tensor.order-1])) tensors[tree.root-1] = tensor.transpose(rep) out = tensap.TreeBasedTensor(tensors, tree) elif isinstance(tensor, tensap.TreeBasedTensor): if self._hsvd_type == 1: out = tensor.orth() gram = out.gramians()[0] mat = np.empty(gram.shape, dtype=object) shape = np.zeros(gram.shape) for nod in range(gram.size): # Truncation of the Gramian in trace norm for a control # of Frobenius norm of the tensor if gram[nod] is not None: self.max_rank = max_rank[nod] tmp = self.trunc_svd(gram[nod], local_tol ** 2) shape[nod] = tmp.core.shape[0] mat[nod] = np.transpose(tmp.space[0]) # Interior nodes without the root for level in np.arange(1, np.max(tree.level)): nod_level = tensap.fast_setdiff( tree.nodes_with_level(level), np.nonzero(tree.is_leaf)[0]+1) for nod in tree.nodes_indices[nod_level-1]: order = out.tensors[nod-1].order out.tensors[nod-1] = \ out.tensors[nod-1].tensor_matrix_product( mat[nod-1], order-1) parent = tree.parent(nod) ch_nb = tree.child_number(nod) out.tensors[parent-1] = \ out.tensors[parent-1].tensor_matrix_product( mat[nod-1], ch_nb-1) # Leaves for nod in tree.dim2ind: if out.is_active_node[nod-1]: order = out.tensors[nod-1].order out.tensors[nod-1] = \ out.tensors[nod-1].tensor_matrix_product( mat[nod-1], order-1) parent = tree.parent(nod) ch_nb = tree.child_number(nod) out.tensors[parent-1] = \ out.tensors[parent-1].tensor_matrix_product( mat[nod-1], ch_nb-1) # Update the shape out = out.update_attributes() out.is_orth = False elif self._hsvd_type == 2: out = tensor.orth() gram = out.gramians()[0] for level in np.arange(np.max(tree.level), 0, -1): for nod in tensap.fast_intersect( tree.nodes_with_level(level), out.active_nodes): # Truncation of the Gramian in trace norm for a control # of Frobenius norm of the tensor self.max_rank = max_rank[nod-1] tmp = self.trunc_svd(gram[nod-1], local_tol ** 2) tmp = np.transpose(tmp.space[0]) order = out.tensors[nod-1].order out.tensors[nod-1] = \ out.tensors[nod-1].tensor_matrix_product(tmp, order-1) parent = tree.parent(nod) ch_nb = tree.child_number(nod) out.tensors[parent-1] = out.tensors[parent-1].\ tensor_matrix_product(tmp, ch_nb-1) out = out.update_attributes() out.is_orth = True out.orth_node = tree.root else: raise ValueError('Wrong value of _hsvd_type.') else: raise NotImplementedError('Method not implemented.') return out
def parameter_gradient_eval(self, alpha, x=None, *args): ''' Compute the gradient of the function with respect to its alpha-th parameter, evaluated at some points. Parameters ---------- alpha : int The number of the parameter with respect to which compute the gradient of self. x : list or numpy.ndarray, optional The points at which the gradient is to be evaluated. The default is None, indicating to use self.bases if self.evaluated_bases is True. Raises ------ ValueError If no input points are provided. Returns ------- grad : Tensor The gradient of the function with respect to its alpha-th parameter, evaluated at some points. ''' if self.evaluated_bases: bases_eval = self.bases elif x is not None: bases_eval = self.bases.eval(x) else: raise ValueError('Must provide the evaluation points or the ' + 'bases evaluations.') dims = np.arange(self.tensor.order) if isinstance(self.tensor, tensap.TreeBasedTensor): # Compute fH, the TimesMatrixEvalDiag of f with bases_eval in all # the dimensions except the ones associated with alpha (if alpha # is a leaf node) or with the inactive children of alpha (if # alpha is an internal node). The tensor fH is used to compute # the gradient of f with respect to f.tensor.tensors[alpha-1]. tree = self.tensor.tree if tree.is_leaf[alpha - 1]: dims = dims[tree.dim2ind != alpha] else: children = tree.children(alpha) ind = tensap.fast_intersect( tree.dim2ind, children[np.logical_not( self.tensor.is_active_node[children - 1])]) dims = dims[np.logical_not(np.isin(tree.dim2ind, ind))] if np.all(self.tensor.is_active_node): fH = self.tensor.tensor_matrix_product( [bases_eval[x] for x in dims], dims) else: remaining_dims = np.arange(self.tensor.order) tensors = np.array(self.tensor.tensors) dim2ind = np.array(tree.dim2ind) for leaf in tensap.fast_intersect(tree.dim2ind[dims], self.tensor.active_nodes): dims = tensap.fast_setdiff( dims, np.nonzero(tree.dim2ind == leaf)[0][0]) tensors[leaf-1] = self.tensor.tensors[leaf-1].\ tensor_matrix_product(bases_eval[ np.nonzero(tree.dim2ind == leaf)[0][0]], 0) for pa in np.unique( tree.parent( tensap.fast_setdiff(tree.dim2ind[dims], self.tensor.active_nodes))): ind = tensap.fast_intersect(tree.dim2ind[dims], tree.children(pa)) ind = tensap.fast_setdiff(ind, self.tensor.active_nodes) dims_loc = np.array( [np.nonzero(x == tree.dim2ind)[0][0] for x in ind]) if len(ind) > 1: tensors[pa-1] = self.tensor.tensors[pa-1].\ tensor_matrix_product_eval_diag([bases_eval[x] for x in dims_loc], tree.child_number( ind)-1) remaining_dims = tensap.fast_setdiff( remaining_dims, dims_loc[1:]) if np.all( np.logical_not(self.tensor.is_active_node[ tree.children(pa) - 1])): dim2ind[dims_loc[0]] = tree.parent( tree.dim2ind[dims_loc[0]]) else: dims = tensap.fast_setdiff(dims, dims_loc[0]) dim2ind[dims_loc[1:]] = 0 perm = np.concatenate( ([tree.child_number(ind[0]) - 1], tensap.fast_setdiff( np.arange(tensors[pa - 1].order), tree.child_number(ind[0]) - 1))) tensors[pa - 1] = tensors[pa - 1].itranspose(perm) elif len(ind) == 1: dims = dims[dims != dims_loc] tensors[pa-1] = self.tensor.tensors[pa-1].\ tensor_matrix_product([bases_eval[x] for x in dims_loc], tree.child_number(ind)-1) dim2ind[dims_loc] = tree.dim2ind[dims_loc] keep_ind = tensap.fast_setdiff(np.arange(tree.nb_nodes), tree.dim2ind[dims] - 1) adj_mat = tree.adjacency_matrix[np.ix_(keep_ind, keep_ind)] dim2ind = dim2ind[dim2ind != 0] ind = np.zeros(tree.nb_nodes) ind[tensap.fast_setdiff(np.arange(tree.nb_nodes), keep_ind)] = 1 ind = np.cumsum(ind).astype(int) dim2ind -= ind[dim2ind - 1] alpha = alpha - ind[alpha - 1] tree = tensap.DimensionTree(dim2ind, adj_mat) fH = tensap.TreeBasedTensor(tensors[keep_ind], tree) fH = fH.remove_unique_children() bases_eval = [bases_eval[x] for x in remaining_dims] else: if alpha <= self.tensor.order: dims = np.delete(dims, alpha - 1) fH = self.tensor.tensor_matrix_product( [bases_eval[x] for x in dims], dims) grad = fH.parameter_gradient_eval_diag(alpha, bases_eval) if isinstance(self.tensor, tensap.TreeBasedTensor) and \ not tree.is_leaf[alpha-1]: # If the order of the children has been modified in grad, compute # the inverse permutation. ch = tree.children(alpha) perm_1 = np.argsort( np.concatenate((np.atleast_1d(ch[fH.is_active_node[ch - 1]]), np.atleast_1d(ch[np.logical_not( fH.is_active_node[ch - 1])])))) if alpha == tree.root: perm_2 = [] else: perm_2 = [fH.tensors[alpha - 1].order] if alpha != tree.root and self.tensor.ranks[tree.root - 1] > 1: perm_3 = [grad.order - 1] else: perm_3 = [] grad = grad.transpose( np.concatenate(([0], perm_1 + 1, perm_2, perm_3)).astype(int)) return grad