GSP_TREE_MULTIRESOLUTION - Compute a multiresolution of trees

Program code:

function [Gs,subsampled_vertex_indices]=gsp_tree_multiresolution(G,Nlevel,param)
%GSP_TREE_MULTIRESOLUTION  Compute a multiresolution of trees
%   Usage:  [Gs,subsampled_vertex_indices]=gsp_tree_multiresolution(G,num_levels);
%           [Gs,subsampled_vertex_indices]=gsp_tree_multiresolution(G,num_levels,param);
%
%   Input parameters:
%         G                          : Graph structure of a tree.
%         Nlevel                     : Number of times to downsample and coarsen the tree.
%   Output parameters:
%         Gs                         : Cell array, with each element containing a graph structure represent a reduced tree.
%         subsampled_vertex_indices  : Indices of the vertices of the previous tree that are kept for the subsequent tree.
%   Additional parameters:
%         param.root                 : The index of the root of the tree (default=1)
%         param.reduction_method     : The graph reduction method (default='resistance_distance')
%         param.compute_full_eigen   : To also compute the graph Laplacian eigenvalues for every tree in the sequence
%         
%   'gsp_tree_multiresolution(G,num_levels)' computes a multiresolution of 
%   trees by repeatedly downsampling and performing a graph reduction. The
%   downsampling is performed by keeping all vertices at even depths of the
%   tree from the root vertex. Options for the graph reduction method
%   include: 'unweighted', 'sum' (add the weight connecting a child
%   node to its parent and the weight connecting the parent to the
%   grandparent and use that weight for the edge connecting the child to
%   the grandparent in the new graph), or 'resistance_distance', which
%   preserves the resistance distances by setting the new weights according
%   to:
% 
%      W_ik =              1                      
%                     -----------
%                      1       1
%                     ---  +  ---  
%                    W_ij     W_jk
%
%   where W_{i,j} is the weight connecting a child to its parent in the
%   original tree, and W_{j,k} is the weight connecting the parent to the
%   grandparent in the original tree.
%
%   See also: gsp_kron_pyramid  
%
%
%   Url: http://gspbox.sourceforge.net/doc/operators/gsp_tree_multiresolution.php

% Copyright (C) 2013-2014 Nathanael Perraudin, David I Shuman.
% This file is part of GSPbox version 0.2.0
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program.  If not, see <http://www.gnu.org/licenses/>.

%   AUTHOR : David I Shuman, Nathanael Perraudin
%   TESTING: 
%   REFERENCE:

% TODO: transform this in tree pyramid

if nargin<3
    param = struct;
end


if ~isfield(param,'root'), 
    if isfield(G,'root')
        param.root = G.root;
    end
    param.root=1; end
if ~isfield(param,'reduction_method'), param.reduction_method='resistance_distance'; end
if ~isfield(param,'compute_full_eigen'), param.compute_full_eigen=0; end


root=param.root;
G.root = root;


Gs = cell(Nlevel+1,1);
Gs{1} = G;
if param.compute_full_eigen
    Gs{1} = gsp_compute_fourier_basis(Gs{1});
end
subsampled_vertex_indices = cell(Nlevel,1);

[depths,parents] = gsp_tree_depths(G.A,root);
old_W = G.W;


for lev=1:Nlevel
    
    % Identify the vertices in the even depths of the current tree
    down_odd = mod(round(depths),2);
    down_even = ones(Gs{lev}.N,1) - down_odd;
    keep_inds = find(down_even==1);
    subsampled_vertex_indices{lev} = keep_inds;

    % There will be one undirected edge in the new graph connecting each 
    % non-root subsampled vertex to its new parent. Here, we find the new
    % indices of the new parents
    [non_root_keep_inds,new_non_root_inds]=setdiff(keep_inds,root); 
    old_parents_of_non_root_keep_inds=parents(non_root_keep_inds);
    old_grandparents_of_non_root_keep_inds=parents(old_parents_of_non_root_keep_inds);
    new_non_root_parents=dsearchn(keep_inds,old_grandparents_of_non_root_keep_inds);
    
    % Create new weighted adjacency matrix via graph reduction
    [old_W_i_inds,old_W_j_inds,old_W_weights]=find(old_W);
    i_inds=[new_non_root_inds;new_non_root_parents];
    j_inds=[new_non_root_parents;new_non_root_inds];
    new_N=sum(down_even);
    switch param.reduction_method
        case 'unweighted'
            new_weights=ones(length(i_inds),1);
        case 'sum'
            old_weights_to_parents_inds=dsearchn([old_W_i_inds,old_W_j_inds],[non_root_keep_inds,old_parents_of_non_root_keep_inds]);
            old_weights_to_parents=old_W_weights(old_weights_to_parents_inds); %old_W(non_root_keep_inds,old_parents_of_non_root_keep_inds);
            old_weights_parents_to_grandparents_inds=dsearchn([old_W_i_inds,old_W_j_inds],[old_parents_of_non_root_keep_inds,old_grandparents_of_non_root_keep_inds]);
            old_weights_parents_to_grandparents=old_W_weights(old_weights_parents_to_grandparents_inds); %old_W(old_parents_of_non_root_keep_inds,old_grandparents_of_non_root_keep_inds);
            new_weights=old_weights_to_parents + old_weights_parents_to_grandparents;
            new_weights=[new_weights;new_weights];
        case 'resistance_distance'
            old_weights_to_parents_inds=dsearchn([old_W_i_inds,old_W_j_inds],[non_root_keep_inds,old_parents_of_non_root_keep_inds]);
            old_weights_to_parents=old_W_weights(old_weights_to_parents_inds); %old_W(non_root_keep_inds,old_parents_of_non_root_keep_inds);
            old_weights_parents_to_grandparents_inds=dsearchn([old_W_i_inds,old_W_j_inds],[old_parents_of_non_root_keep_inds,old_grandparents_of_non_root_keep_inds]);
            old_weights_parents_to_grandparents=old_W_weights(old_weights_parents_to_grandparents_inds); %old_W(old_parents_of_non_root_keep_inds,old_grandparents_of_non_root_keep_inds);
            new_weights=1./(1./old_weights_to_parents+1./old_weights_parents_to_grandparents);
            new_weights=[new_weights;new_weights];
        otherwise
            error('Unknown graph reduction method');
    end
    new_W=sparse(i_inds,j_inds,new_weights,new_N,new_N);
    
    % Update parents
    new_root=find(keep_inds==root);
    parents=zeros(size(keep_inds));
    parents([1:new_root-1,new_root+1:end])=new_non_root_parents;
    
    % Update depths
    depths=depths(keep_inds);
    depths=depths/2;
    
    % Store new tree
    Gs{lev+1}=gsp_graph(new_W,Gs{lev}.coords(keep_inds,:),G.limits);
    Gs{lev+1} = gsp_create_laplacian( Gs{lev+1}, G.lap_type);
    Gs{lev+1}.type='tree';
    Gs{lev+1}.root=new_root;
    Gs{lev+1} = gsp_copy_graph_attributes(Gs{lev},0,Gs{lev+1});
    
    if param.compute_full_eigen
        Gs{lev+1}=gsp_compute_fourier_basis(Gs{lev+1});
    end
    
    % Replace current adjacency matrix and root
    old_W=new_W;
    root=new_root;
end


end