Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
    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
Ejemplo n.º 6
0
    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
Ejemplo n.º 7
0
    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