// Copyright (C) 2010, 2011, 2012, 2013, 2014 Steffen Rendle
// Contact:   srendle@libfm.org, http://www.libfm.org/
//
// This file is part of libFM.
//
// libFM 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.
//
// libFM 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 libFM.  If not, see <http://www.gnu.org/licenses/>.
//
//
// fm_learn_sgd.h: Stochastic Gradient Descent based learning for
// classification and regression
//
// Based on the publication(s):
// - Steffen Rendle (2010): Factorization Machines, in Proceedings of the 10th
//   IEEE International Conference on Data Mining (ICDM 2010), Sydney,
//   Australia.

#ifndef FM_LEARN_SGD_ELEMENT_H_
#define FM_LEARN_SGD_ELEMENT_H_

#include "fm_learn_sgd.h"

class fm_learn_sgd_element: public fm_learn_sgd {
 public:
  virtual ~fm_learn_sgd_element() = default;
  virtual void init();
  virtual void learn(Data& train, Data& test);
};

// Implementation
void fm_learn_sgd_element::init() {
  fm_learn_sgd::init();

  if (log != NULL) {
    log->addField("rmse_train", std::numeric_limits<double>::quiet_NaN());
  }
}

void fm_learn_sgd_element::learn(Data& train, Data& test) {
  fm_learn_sgd::learn(train, test);

  std::cout << "SGD: DON'T FORGET TO SHUFFLE THE ROWS IN TRAINING DATA TO GET THE BEST RESULTS." << std::endl;
  // SGD
  for (int i = 0; i < num_iter; i++) {

    double iteration_time = getusertime();
    for (train.data->begin(); !train.data->end(); train.data->next()) {
      double p = fm->predict(train.data->getRow(), sum, sum_sqr);
      double mult = 0;
      if (task == 0) {
        p = std::min(max_target, p);
        p = std::max(min_target, p);
        mult = -(train.target(train.data->getRowIndex())-p);
      } else if (task == 1) {
        mult = -train.target(train.data->getRowIndex())*(1.0-1.0/(1.0+exp(-train.target(train.data->getRowIndex())*p)));
      }
      SGD(train.data->getRow(), mult, sum);
    }
    iteration_time = (getusertime() - iteration_time);
    double rmse_train = evaluate(train);
    double rmse_test = evaluate(test);
    std::cout << "#Iter=" << std::setw(3) << i << "\tTrain=" << rmse_train << "\tTest=" << rmse_test << std::endl;
    if (log != NULL) {
      log->log("rmse_train", rmse_train);
      log->log("time_learn", iteration_time);
      log->newLine();
    }
  }
}

#endif /*FM_LEARN_SGD_ELEMENT_H_*/
