function [stepsize, newx, newkey, ssstats] = ...
                    stepsize_sg(problem, x, d, iter, options, storedb, key) %#ok<INUSD>
% Standard step size selection algorithm for the stochastic gradient method
%
% Given a problem structure, a point x on the manifold problem.d and a
% tangent vector d at x, produces a stepsize (a positive real number) and a
% new point newx obtained by retraction -stepsize*d at x. Additional inputs
% include iter (the iteration number of x, where 0 marks the initial
% guess), an options structure, a storedb database and the key of point x
% in that database. Additional outputs include the key of newx in the
% database, newkey, as well as a structure ssstats collecting statistics
% about the work done during the call to this function.
%
% See in code for the role of available options:
%    options.stepsize_type
%    options.stepsize_init
%    options.stepsize_lambda
%    options.stepsize_decaysteps
%
% This function may create and maintain a structure called sssgmem inside
% storedb.internal. This gives the function the opportunity to remember
% what happened in previous calls.
%
% See also: stochasticgradient

% This file is part of Manopt: www.manopt.org.
% Original authors: Bamdev Mishra and Nicolas Boumal, March 30, 2017.
% Contributors: Hiroyuki Kasai and Hiroyuki Sato.
% Change log: 


    % Allow omission of the key, and even of storedb.
    if ~exist('key', 'var')
        if ~exist('storedb', 'var')
            storedb = StoreDB();
        end
        key = storedb.getNewKey(); %#ok<NASGU>
    end
    

    % Initial stepsize guess.
    default_options.stepsize_init = 0.1;
    % Stepsize evolution type. Options are 'decay', 'fix' and 'hybrid'.
    default_options.stepsize_type = 'decay';
    % If stepsize_type = 'decay' or 'hybrid', lambda is a weighting factor.
    default_options.stepsize_lambda = 0.1;
    % If stepsize_type = 'hybrid', decaysteps states for how many
    % iterations the step size decays before becoming constant.
    default_options.stepsize_decaysteps = 100;
    
    if ~exist('options', 'var') || isempty(options)
        options = struct();
    end
    options = mergeOptions(default_options, options);
    

    type = options.stepsize_type;
    init = options.stepsize_init;
    lambda = options.stepsize_lambda;
    decaysteps = options.stepsize_decaysteps;

    
    switch lower(type)
        
        % Step size decays as O(1/iter).
        case 'decay'
            stepsize = init / (1 + init*lambda*iter);

        % Step size is fixed.
        case {'fix', 'fixed'}
            stepsize = init;

        % Step size decays only for the few initial iterations.
        case 'hybrid'
            if iter < decaysteps
                stepsize = init / (1 + init*lambda*iter);
            else
                stepsize = init / (1 + init*lambda*decaysteps);
            end

        otherwise
            error(['Unknown options.stepsize_type. ' ...
                   'Should be ''fix'', ''decay'' or ''hybrid''.']);
               
    end

    % Store some information.
    ssstats = struct();
    ssstats.stepsize = stepsize;

    % Compute the new point and give it a key.
    newx = problem.M.retr(x, d, -stepsize);
    newkey = storedb.getNewKey();

end
