function grad = getPartialGradient(problem, x, I, storedb, key)
% Computes the gradient of a subset of terms in the cost function at x.
%
% function grad = getPartialGradient(problem, x, I)
% function grad = getPartialGradient(problem, x, I, storedb)
% function grad = getPartialGradient(problem, x, I, storedb, key)
%
% Assume the cost function described in the problem structure is a sum of
% many terms, as
%
%    f(x) = sum_i f_i(x) for i = 1:d,

% where d is specified as d = problem.ncostterms.
% 
% For a subset I of 1:d, getPartialGradient obtains the gradient of the
% partial cost function
% 
%    f_I(x) = sum_i f_i(x) for i = I.
%
% storedb is a StoreDB object, key is the StoreDB key to point x.
%
% See also: getGradient canGetPartialGradient getPartialEuclideanGradient

% This file is part of Manopt: www.manopt.org.
% Original author: Nicolas Boumal, June 28, 2016
% Contributors: 
% Change log: 
%
%   Feb. 10, 2020 (NB):
%       Allowing M.egrad2rgrad to take (storedb, key) as extra inputs.


    % Allow omission of the key, and even of storedb.
    if ~exist('key', 'var')
        if ~exist('storedb', 'var')
            storedb = StoreDB();
        end
        key = storedb.getNewKey();
    end
    
    
    % Make sure I is a row vector, so that it is natural to loop over it
    % with " for i = I ".
    I = (I(:)).';

    
    if isfield(problem, 'partialgrad')
    %% Compute the partial gradient using partialgrad.
    
        % Check whether this function wants to deal with storedb or not.
        switch nargin(problem.partialgrad)
            case 2
                grad = problem.partialgrad(x, I);
            case 3
                % Obtain, pass along, and save the store for x.
                store = storedb.getWithShared(key);
                [grad, store] = problem.partialgrad(x, I, store);
                storedb.setWithShared(store, key);
            case 4
                % Pass along the whole storedb (by reference), with key.
                grad = problem.partialgrad(x, I, storedb, key);
            otherwise
                up = MException('manopt:getPartialGradient:badpartialgrad', ...
                    'partialgrad should accept 2, 3 or 4 inputs.');
                throw(up);
        end
    
    elseif canGetPartialEuclideanGradient(problem)
    %% Compute the partial gradient using the Euclidean partial gradient.
        
        egrad = getPartialEuclideanGradient(problem, x, I, storedb, key);
        % Convert to the Riemannian gradient
        switch nargin(problem.M.egrad2rgrad)
            case 2
                grad = problem.M.egrad2rgrad(x, egrad);
            case 4
                grad = problem.M.egrad2rgrad(x, egrad, storedb, key);
            otherwise
                up = MException('manopt:getPartialGradient:egrad2rgrad', ...
                    'egrad2rgrad should accept 2 or 4 inputs.');
                throw(up);
        end

    else
    %% Abandon computing the partial gradient.
    
        up = MException('manopt:getPartialGradient:fail', ...
            ['The problem description is not explicit enough to ' ...
             'compute the partial gradient of the cost.']);
        throw(up);
        
    end
    
end
