// Copyright (C) 2013  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#undef DLIB_VECTOR_NORMALIZER_FRoBMETRIC_ABSTRACT_Hh_
#ifdef DLIB_VECTOR_NORMALIZER_FRoBMETRIC_ABSTRACT_Hh_

#include "../matrix.h"

namespace dlib
{

// ----------------------------------------------------------------------------------------

    template <
        typename matrix_type
        >
    struct frobmetric_training_sample 
    {
        /*!
            WHAT THIS OBJECT REPRESENTS
                This object represents a training data sample for the
                vector_normalizer_frobmetric object.  It defines a set of training triplets
                relative to a single anchor_vect vector.  That is, it specifies that the
                learned distance metric should satisfy num_triples() constraints which are,
                for all valid i and j:
                    length(T*anchor_vect-T*near_vects[i]) + 1 < length(T*anchor_vect - T*far_vects[j])
                for some appropriate linear transformation T which will be learned by
                vector_normalizer_frobmetric.
        !*/

        matrix_type anchor_vect;
        std::vector<matrix_type> near_vects;
        std::vector<matrix_type> far_vects;

        unsigned long num_triples (
        ) const { return near_vects.size() * far_vects.size(); }
        /*!
            ensures
                - returns the number of training triplets defined by this object.
        !*/

        void clear()
        /*!
            ensures
                - #near_vects.size() == 0
                - #far_vects.size() == 0
        !*/
    };

    template < typename matrix_type >
    void serialize(const frobmetric_training_sample<matrix_type>& item, std::ostream& out)
    template < typename matrix_type >
    void deserialize(frobmetric_training_sample<matrix_type>& item, std::istream& in)
    /*!
        provides serialisation support.
    !*/

// ----------------------------------------------------------------------------------------

    template <
        typename matrix_type
        >
    class vector_normalizer_frobmetric
    {
        /*!
            REQUIREMENTS ON matrix_type
                - must be a dlib::matrix object capable of representing column 
                  vectors

            INITIAL VALUE
                - in_vector_size() == 0
                - out_vector_size() == 0
                - get_epsilon() == 0.1
                - get_c() == 1
                - get_max_iterations() == 5000
                - This object is not verbose

            WHAT THIS OBJECT REPRESENTS
                This object is a tool for performing the FrobMetric distance metric
                learning algorithm described in the following paper:
                    A Scalable Dual Approach to Semidefinite Metric Learning
                    By Chunhua Shen, Junae Kim, Lei Wang, in CVPR 2011

                Therefore, this object is a tool that takes as input training triplets
                (anchor_vect, near, far) of vectors and attempts to learn a linear
                transformation T such that:
                    length(T*anchor_vect-T*near) + 1 < length(T*anchor_vect - T*far)
                That is, you give a bunch of anchor_vect vectors and for each anchor_vect
                you specify some vectors which should be near to it and some that should be
                far form it.  This object then tries to find a transformation matrix that
                makes the "near" vectors close to their anchors while the "far" vectors are
                farther away.

            THREAD SAFETY
                Note that this object contains a cached matrix object it uses 
                to store intermediate results for normalization.  This avoids
                needing to reallocate it every time this object performs normalization
                but also makes it non-thread safe.  So make sure you don't share
                instances of this object between threads. 
        !*/

    public:
        typedef typename matrix_type::mem_manager_type mem_manager_type;
        typedef typename matrix_type::type scalar_type;
        typedef matrix_type result_type;

        vector_normalizer_frobmetric (
        );
        /*!
            ensures
                - this object is properly initialized
        !*/

        void be_verbose(
        );
        /*!
            ensures
                - This object will print status messages to standard out so the user can
                  observe the progress of the train() routine.
        !*/

        void be_quiet (
        );
        /*!
            ensures
                - this object will not print anything to standard out.
        !*/

        void set_epsilon (
            double eps
        );
        /*!
            requires
                - eps > 0
            ensures
                - #get_epsilon() == eps 
        !*/

        double get_epsilon (
        ) const;
        /*!
            ensures
                - returns the error epsilon that determines when training should stop.
                  Smaller values may result in a more accurate solution but take longer to
                  execute.
        !*/

        void set_c (
            double C 
        );
        /*!
            requires
                - C > 0
            ensures
                - #set_c() == C
        !*/

        double get_c (
        ) const;
        /*!
            ensures
                - returns the regularization parameter.  It is the parameter that
                  determines the trade-off between trying to fit the training data exactly
                  or allowing more errors but hopefully improving the generalization of the
                  resulting distance metric.  Larger values encourage exact fitting while
                  smaller values of C may encourage better generalization. 
        !*/
       
        void set_max_iterations (
            unsigned long max_iterations
        );
        /*!
            ensures
                - #get_max_iterations() == max_iterations
        !*/

        unsigned long get_max_iterations (
        ) const;
        /*!
            ensures
                - The train() routine uses an iterative numerical solver to find the best
                  distance metric.  This function returns the maximum allowable number of
                  iterations it will use before terminating.  Note that typically the
                  solver terminates prior to the max iteration count limit due to the error
                  dropping below get_epsilon().
        !*/

        void train (
            const std::vector<frobmetric_training_sample<matrix_type> >& samples
        );
        /*!
            requires
                - samples.size() != 0
                - All matrices inside samples (i.e. anchors and elements of near_vects and far_vects)
                  are column vectors with the same non-zero dimension.
                - All the vectors in samples contain finite values.
                - All elements of samples contain data, specifically, for all valid i:
                    - samples[i].num_triples() != 0
            ensures
                - learns a distance metric from the given training samples.  After train
                  finishes you can use this object's operator() to transform vectors
                  according to the learned distance metric.  In particular, we will have:
                    - #transform() == The linear transformation learned by the FrobMetric
                      learning procedure.
                    - #in_vector_size() == samples[0].anchor_vect.size()
                    - You can call (*this)(x) to transform a vector according to the learned 
                      distance metric.  That is, it should generally be the case that:
                        - length((*this)(anchor_vect) - (*this)(near)) + 1 < length((*this)(anchor_vect) - (*this)(far))
                      for the anchor_vect, near, and far vectors in the training data.
                    - #transformed_means() == the mean of the input anchor_vect vectors
                      after being transformed by #transform()
        !*/

        long in_vector_size (
        ) const;
        /*!
            ensures
                - returns the number of rows that input vectors are required to contain if
                  they are to be normalized by this object.
        !*/

        long out_vector_size (
        ) const;
        /*!
            ensures
                - returns the number of rows in the normalized vectors that come out of
                  this object.
                - The value returned is always in_vector_size().  So out_vector_size() is
                  just provided to maintain interface consistency with other vector
                  normalizer objects.  That is, the transformations applied by this object
                  do not change the dimensionality of the vectors.
        !*/

        const matrix<scalar_type,0,1,mem_manager_type>& transformed_means (
        ) const;
        /*!
            ensures
                - returns a column vector V such that:
                    - V.size() == in_vector_size()
                    - V is a vector such that subtracting it from transformed vectors
                      results in them having an expected value of 0.  Therefore, it is
                      equal to transform() times the mean of the input anchor_vect vectors
                      given to train().
        !*/

        const matrix<scalar_type,0,0,mem_manager_type>& transform (
        ) const;
        /*!
            ensures
                - returns a copy of the transformation matrix we learned during the last 
                  call to train().
                - The returned matrix is square and has in_vector_size() by in_vector_size()
                  dimensions.
        !*/

        const result_type& operator() (
            const matrix_type& x
        ) const;
        /*!
            requires
                - in_vector_size() != 0
                - in_vector_size() == x.size()
                - is_col_vector(x) == true
            ensures
                - returns a normalized version of x, call it Z, that has the following
                  properties: 
                    - Z == The result of applying the linear transform we learned during
                      train() to the input vector x.
                    - Z == transform()*x-transformed_means()
                    - is_col_vector(Z) == true
                    - Z.size() == x.size()
                    - The expected value of each element of Z is 0.
        !*/
    };

// ----------------------------------------------------------------------------------------

    template <
        typename matrix_type
        >
    void serialize (
        const vector_normalizer_frobmetric<matrix_type>& item, 
        std::ostream& out 
    );
    /*!
        provides serialization support 
    !*/

// ----------------------------------------------------------------------------------------

    template <
        typename matrix_type
        >
    void deserialize (
        vector_normalizer_frobmetric<matrix_type>& item, 
        std::istream& in
    );
    /*!
        provides deserialization support 
    !*/

// ----------------------------------------------------------------------------------------

}

#endif // DLIB_VECTOR_NORMALIZER_FRoBMETRIC_ABSTRACT_Hh_

