/**
 * @author André Anjos <andre.anjos@idiap.ch>
 * @author Laurent El Shafey <Laurent.El-Shafey@idiap.ch>
 * @date Tue Jan 18 17:07:26 2011 +0100
 *
 * @brief Principal Component Analysis implemented with Singular Value
 * Decomposition or using the Covariance Method. Both are implemented using
 * LAPACK.
 *
 * Copyright (C) Idiap Research Institute, Martigny, Switzerland
 */

#ifndef BOB_LEARN_LINEAR_PCA_H
#define BOB_LEARN_LINEAR_PCA_H

#include <bob.learn.linear/machine.h>

namespace bob { namespace learn { namespace linear {

  /**
   * @brief Sets a linear machine to perform the Karhunen-Loève Transform
   * (KLT) on a given dataset using either Singular Value Decomposition (SVD),
   * the default, or the Covariance Method.
   *
   * References:
   * 1. Eigenfaces for Recognition, Turk & Pentland, Journal of Cognitive
   *    Neuroscience (1991) Volume: 3, Issue: 1, Publisher: MIT Press,
   *    Pages: 71-86
   * 2. http://en.wikipedia.org/wiki/Singular_value_decomposition
   * 3. http://en.wikipedia.org/wiki/Principal_component_analysis
   */
  class PCATrainer {

    public: //api

      /**
       * @brief Initializes a new PCA trainer using SVD or, as an option, the
       * Covariance Matrix method. The training stage will place the resulting
       * principal components in the linear machine and set it up to extract
       * the variable means automatically. As an option, you may preset the
       * trainer so that the normalization performed by the resulting linear
       * machine also divides the variables by the standard deviation of each
       * variable ensemble.
       */
      PCATrainer(bool use_svd=true);

      /**
       * @brief Copy constructor
       */
      PCATrainer(const PCATrainer& other);

      /**
       * @brief Destructor
       */
      virtual ~PCATrainer();

      /**
       * @brief Assignment operator
       */
      PCATrainer& operator=(const PCATrainer& other);

      /**
       * @brief Equal to
       */
      bool operator==(const PCATrainer& other) const;

      /**
       * @brief Not equal to
       */
      bool operator!=(const PCATrainer& other) const;

      /**
       * @brief Gets the SVD/CovMat flag. <code>true</code> means use SVD
       * method.
       */
      bool getUseSVD () const { return m_use_svd; }

      /**
       * @brief Sets the SVD/CovMat flag. <code>true</code> means use SVD
       * method.
       */
      void setUseSVD (bool value) { m_use_svd = value; }

      /**
       * @brief Gets the SVD LAPACK function used. <code>true</code> means
       * use the LAPACK DGESVD method and false the DGESDD method.
       * To be useful, the SVD method should be used by enabling the UseSVD
       * flag.
       */
      bool getSafeSVD () const { return m_safe_svd; }

      /**
       * @brief Sets the SVD LAPACK function used. <code>true</code> means
       * use the LAPACK DGESVD method and false the DGESDD method..
       * To be useful, the SVD method should be used by enabling the UseSVD
       * flag.
       */
      void setSafeSVD (bool value) { m_safe_svd = value; }

      /**
       * @brief Trains the LinearMachine to perform the KLT. The resulting
       * machine will have the eigen-vectors of the covariance matrix arranged
       * by decreasing energy automatically. You don't need to sort the results.
       */
      virtual void train(Machine& machine,
          const blitz::Array<double,2>& X) const;

      /**
       * @brief Trains the LinearMachine to perform the KLT. The resulting
       * machien will have the eigen-vectors of the covariance matrix arranged
       * by decreasing energy automatically. You don't need to sort the results.
       * Also returns the eigen values of the covariance matrix so you can use
       * that to choose which components to keep.
       */
      virtual void train(Machine& machine,
          blitz::Array<double,1>& eigen_values,
          const blitz::Array<double,2>& X) const;

      /**
       * @brief Calculates the maximum possible rank for the covariance matrix
       * of X, given X.
       *
       * This determines what is the maximum number of non-zero eigen values
       * that can be generated by this trainer. It should be used to setup
       * Machines and input vectors prior to feeding them into this trainer.
       */
      size_t output_size(const blitz::Array<double,2>& X) const;

    private: //representation

      bool m_use_svd; ///< if this trainer should be using SVD or Covariance
      bool m_safe_svd; ///< if svd is set, tells which LAPACK function to use
                       ///  among dgesdd (false) and dgesvd (true)

  };

}}}

#endif /* BOB_LEARN_LINEAR_PCA_H */
