/**
 * @file sgd_impl.hpp
 * @author Ryan Curtin
 *
 * Implementation of stochastic gradient descent.
 *
 * This file is part of MLPACK 1.0.10.
 *
 * MLPACK is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * MLPACK 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 Lesser General Public License for more
 * details (LICENSE.txt).
 *
 * You should have received a copy of the GNU General Public License along with
 * MLPACK.  If not, see <http://www.gnu.org/licenses/>.
 */
#ifndef __MLPACK_CORE_OPTIMIZERS_SGD_SGD_IMPL_HPP
#define __MLPACK_CORE_OPTIMIZERS_SGD_SGD_IMPL_HPP

#include <mlpack/methods/regularized_svd/regularized_svd_function.hpp>
// In case it hasn't been included yet.
#include "sgd.hpp"

namespace mlpack {
namespace optimization {

template<typename DecomposableFunctionType>
SGD<DecomposableFunctionType>::SGD(DecomposableFunctionType& function,
                                   const double stepSize,
                                   const size_t maxIterations,
                                   const double tolerance,
                                   const bool shuffle) :
    function(function),
    stepSize(stepSize),
    maxIterations(maxIterations),
    tolerance(tolerance),
    shuffle(shuffle)
{ /* Nothing to do. */ }

//! Optimize the function (minimize).
template<typename DecomposableFunctionType>
double SGD<DecomposableFunctionType>::Optimize(arma::mat& iterate)
{
  // Find the number of functions to use.
  const size_t numFunctions = function.NumFunctions();

  // This is used only if shuffle is true.
  arma::vec visitationOrder;
  if (shuffle)
    visitationOrder = arma::shuffle(arma::linspace(0, (numFunctions - 1),
        numFunctions));

  // To keep track of where we are and how things are going.
  size_t currentFunction = 0;
  double overallObjective = 0;
  double lastObjective = DBL_MAX;

  // Calculate the first objective function.
  for (size_t i = 0; i < numFunctions; ++i)
    overallObjective += function.Evaluate(iterate, i);

  // Now iterate!
  arma::mat gradient(iterate.n_rows, iterate.n_cols);
  for (size_t i = 1; i != maxIterations; ++i, ++currentFunction)
  {
    // Is this iteration the start of a sequence?
    if ((currentFunction % numFunctions) == 0)
    {
      // Output current objective function.
      Rcpp::Rcout << "SGD: iteration " << i << ", objective " << overallObjective
          << "." << std::endl;

      if (overallObjective != overallObjective)
      {
        Rcpp::Rcout << "SGD: converged to " << overallObjective << "; terminating"
            << " with failure.  Try a smaller step size?" << std::endl;
        return overallObjective;
      }

      if (std::abs(lastObjective - overallObjective) < tolerance)
      {
        Rcpp::Rcout << "SGD: minimized within tolerance " << tolerance << "; "
            << "terminating optimization." << std::endl;
        return overallObjective;
      }

      // Reset the counter variables.
      lastObjective = overallObjective;
      overallObjective = 0;
      currentFunction = 0;

      if (shuffle) // Determine order of visitation.
        visitationOrder = arma::shuffle(visitationOrder);
    }

    // Evaluate the gradient for this iteration.
    function.Gradient(iterate, currentFunction, gradient);

    // And update the iterate.
    iterate -= stepSize * gradient;

    // Now add that to the overall objective function.
    overallObjective += function.Evaluate(iterate, currentFunction);
  }

  Rcpp::Rcout << "SGD: maximum iterations (" << maxIterations << ") reached; "
      << "terminating optimization." << std::endl;
  return overallObjective;
}

// Convert the object to a string.
template<typename DecomposableFunctionType>
std::string SGD<DecomposableFunctionType>::ToString() const
{
  std::ostringstream convert;
  convert << "SGD [" << this << "]" << std::endl;
  convert << "  Function:" << std::endl;
  convert << util::Indent(function.ToString(), 2);
  convert << "  Step size: " << stepSize << std::endl;
  convert << "  Maximum iterations: " << maxIterations << std::endl;
  convert << "  Tolerance: " << tolerance << std::endl;
  convert << "  Shuffle points: " << (shuffle ? "true" : "false") << std::endl;
  return convert.str();
}

}; // namespace optimization
}; // namespace mlpack

#endif
