    // Copyright (C) 2009  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_KCENTROId_OVERLOADS_
    #define DLIB_KCENTROId_OVERLOADS_

    #include "kcentroid_abstract.h"
    #include "sparse_kernel.h"
    #include "sparse_vector.h"
    #include <map>

    namespace dlib
    {
        /*
            This file contains optimized overloads of the kcentroid object for the following
            linear cases:
                kcentroid<linear_kernel<T>>
                kcentroid<sparse_linear_kernel<T>>
                kcentroid<offset_kernel<linear_kernel<T>>>
                kcentroid<offset_kernel<sparse_linear_kernel<T>>>
        */

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    //                     Overloads for when kernel_type == linear_kernel
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <typename T>
        class kcentroid<linear_kernel<T> >
        {

            typedef linear_kernel<T> kernel_type;
        public:
            typedef typename kernel_type::scalar_type scalar_type;
            typedef typename kernel_type::sample_type sample_type;
            typedef typename kernel_type::mem_manager_type mem_manager_type;

            explicit kcentroid (
                const kernel_type& kernel_,
                scalar_type tolerance_ = 0.001,
                unsigned long max_dictionary_size_ = 1000000,
                bool remove_oldest_first_ = false
            ) :
                my_remove_oldest_first(remove_oldest_first_),
                kernel(kernel_),
                my_tolerance(tolerance_),
                my_max_dictionary_size(max_dictionary_size_)
            {
                // make sure requires clause is not broken
                DLIB_ASSERT(tolerance_ >= 0 && max_dictionary_size_ > 0,
                    "\tkcentroid::kcentroid()"
                    << "\n\t You have to give a positive tolerance"
                    << "\n\t this:                 " << this
                    << "\n\t tolerance_:           " << tolerance_
                    << "\n\t max_dictionary_size_: " << max_dictionary_size_
                    );

                clear_dictionary();
            }

            scalar_type     tolerance() const               { return my_tolerance; }
            unsigned long   max_dictionary_size() const     { return my_max_dictionary_size; }
            bool            remove_oldest_first () const    { return my_remove_oldest_first; }
            const kernel_type& get_kernel () const          { return kernel; }
            scalar_type     samples_trained () const        { return samples_seen; }

            void clear_dictionary ()
            {
                samples_seen = 0;
                set_all_elements(w, 0);
                alpha = 0;
            }

            scalar_type operator() (
                const kcentroid& x
            ) const
            {
                // make sure requires clause is not broken
                DLIB_ASSERT(x.get_kernel() == get_kernel(),
                    "\tscalar_type kcentroid::operator()(const kcentroid& x)"
                    << "\n\tYou can only compare two kcentroid objects if they use the same kernel"
                    << "\n\tthis: " << this
                    );

                if (w.size() > 0)
                {
                    if (x.w.size() > 0)
                        return length(alpha*w - x.alpha*x.w);
                    else
                        return alpha*length(w);
                }
                else
                {
                    if (x.w.size() > 0)
                        return x.alpha*length(x.w);
                    else
                        return 0;
                }
            }

            scalar_type inner_product (
                const sample_type& x
            ) const
            {
                if (w.size() > 0)
                    return alpha*trans(w)*x;
                else
                    return 0;
            }

            scalar_type inner_product (
                const kcentroid& x
            ) const
            {
                // make sure requires clause is not broken
                DLIB_ASSERT(x.get_kernel() == get_kernel(),
                    "\tscalar_type kcentroid::inner_product(const kcentroid& x)"
                    << "\n\tYou can only compare two kcentroid objects if they use the same kernel"
                    << "\n\tthis: " << this
                    );

                if (w.size() > 0 && x.w.size() > 0)
                    return alpha*x.alpha*trans(w)*x.w;
                else
                    return 0;
            }

            scalar_type squared_norm (
            ) const
            {
                if (w.size() > 0)
                    return alpha*alpha*trans(w)*w;
                else
                    return 0;
            }

            scalar_type operator() (
                const sample_type& x
            ) const
            {
                if (w.size() > 0)
                    return length(x-alpha*w);
                else
                    return length(x);
            }

            scalar_type test_and_train (
                const sample_type& x
            )
            {
                ++samples_seen;
                const scalar_type xscale = 1/samples_seen;
                const scalar_type cscale = 1-xscale;

                do_train(x, cscale, xscale);

                return (*this)(x);
            }

            void train (
                const sample_type& x
            )
            {
                ++samples_seen;
                const scalar_type xscale = 1/samples_seen;
                const scalar_type cscale = 1-xscale;

                do_train(x, cscale, xscale);
            }

            scalar_type test_and_train (
                const sample_type& x,
                scalar_type cscale,
                scalar_type xscale
            )
            {
                ++samples_seen;

                do_train(x, cscale, xscale);

                return (*this)(x);
            }

            void scale_by (
                scalar_type cscale
            )
            {
                alpha *= cscale;
            }

            void train (
                const sample_type& x,
                scalar_type cscale,
                scalar_type xscale
            )
            {
                ++samples_seen;
                do_train(x, cscale, xscale);
            }

            void swap (
                kcentroid& item
            )
            {
                exchange(my_remove_oldest_first, item.my_remove_oldest_first);
                exchange(kernel, item.kernel);
                exchange(w, item.w);
                exchange(alpha, item.alpha);
                exchange(my_tolerance, item.my_tolerance);
                exchange(my_max_dictionary_size, item.my_max_dictionary_size);
                exchange(samples_seen, item.samples_seen);
            }

            unsigned long dictionary_size (
            ) const
            {
                if (samples_seen > 0)
                    return 1;
                else
                    return 0;
            }

            friend void serialize(const kcentroid& item, std::ostream& out)
            {
                serialize(item.my_remove_oldest_first, out);
                serialize(item.kernel, out);
                serialize(item.w, out);
                serialize(item.alpha, out);
                serialize(item.my_tolerance, out);
                serialize(item.my_max_dictionary_size, out);
                serialize(item.samples_seen, out);
            }

            friend void deserialize(kcentroid& item, std::istream& in)
            {
                deserialize(item.my_remove_oldest_first, in);
                deserialize(item.kernel, in);
                deserialize(item.w, in);
                deserialize(item.alpha, in);
                deserialize(item.my_tolerance, in);
                deserialize(item.my_max_dictionary_size, in);
                deserialize(item.samples_seen, in);
            }

            distance_function<kernel_type> get_distance_function (
            ) const
            {
                if (samples_seen > 0)
                {
                    typename distance_function<kernel_type>::sample_vector_type temp_basis_vectors;
                    typename distance_function<kernel_type>::scalar_vector_type temp_alpha;

                    temp_basis_vectors.set_size(1);
                    temp_basis_vectors(0) = w;
                    temp_alpha.set_size(1);
                    temp_alpha(0) = alpha;

                    return distance_function<kernel_type>(temp_alpha, squared_norm(), kernel, temp_basis_vectors);
                }
                else
                {
                    return distance_function<kernel_type>(kernel);
                }
            }

        private:

            void do_train (
                const sample_type& x,
                scalar_type cscale,
                scalar_type xscale
            )
            {
                set_size_of_w(x);

                const scalar_type temp = cscale*alpha;

                if (temp != 0)
                {
                    w = w + xscale*x/temp;
                    alpha = temp;
                }
                else
                {
                    w = cscale*alpha*w + xscale*x;
                    alpha = 1;
                }
            }

            void set_size_of_w (
                const sample_type& x
            )
            {
                if (x.size() != w.size())
                {
                    w.set_size(x.nr(), x.nc());
                    set_all_elements(w, 0);
                    alpha = 0;
                }
            }

            bool my_remove_oldest_first;

            kernel_type kernel;

            sample_type w;
            scalar_type alpha;

            scalar_type my_tolerance;
            unsigned long my_max_dictionary_size;
            scalar_type samples_seen;

        };

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    //               Overloads for when kernel_type == offset_kernel<linear_kernel>
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <typename T>
        class kcentroid<offset_kernel<linear_kernel<T> > >
        {

            /*!
                INITIAL VALUE
                    - x_extra == sqrt(kernel.offset)

                CONVENTION
                    - x_extra == sqrt(kernel.offset)
                    - w_extra == the value of the extra dimension tacked onto the
                      end of the w vector
            !*/

            typedef offset_kernel<linear_kernel<T> > kernel_type;
        public:
            typedef typename kernel_type::scalar_type scalar_type;
            typedef typename kernel_type::sample_type sample_type;
            typedef typename kernel_type::mem_manager_type mem_manager_type;

            explicit kcentroid (
                const kernel_type& kernel_,
                scalar_type tolerance_ = 0.001,
                unsigned long max_dictionary_size_ = 1000000,
                bool remove_oldest_first_ = false
            ) :
                my_remove_oldest_first(remove_oldest_first_),
                kernel(kernel_),
                my_tolerance(tolerance_),
                my_max_dictionary_size(max_dictionary_size_)
            {
                // make sure requires clause is not broken
                DLIB_ASSERT(tolerance_ >= 0 && max_dictionary_size_ > 0,
                    "\tkcentroid::kcentroid()"
                    << "\n\t You have to give a positive tolerance"
                    << "\n\t this:                 " << this
                    << "\n\t tolerance_:           " << tolerance_
                    << "\n\t max_dictionary_size_: " << max_dictionary_size_
                    );

                x_extra = std::sqrt(kernel.offset);

                clear_dictionary();
            }

            scalar_type     tolerance() const               { return my_tolerance; }
            unsigned long   max_dictionary_size() const     { return my_max_dictionary_size; }
            bool            remove_oldest_first () const    { return my_remove_oldest_first; }
            const kernel_type& get_kernel () const          { return kernel; }
            scalar_type     samples_trained () const        { return samples_seen; }

            void clear_dictionary ()
            {
                samples_seen = 0;
                set_all_elements(w, 0);
                alpha = 0;
                w_extra = x_extra;
            }

            scalar_type operator() (
                const kcentroid& x
            ) const
            {
                // make sure requires clause is not broken
                DLIB_ASSERT(x.get_kernel() == get_kernel(),
                    "\tscalar_type kcentroid::operator()(const kcentroid& x)"
                    << "\n\tYou can only compare two kcentroid objects if they use the same kernel"
                    << "\n\tthis: " << this
                    );

                if (w.size() > 0)
                {
                    if (x.w.size() > 0)
                    {
                        scalar_type temp1 = length_squared(alpha*w - x.alpha*x.w);
                        scalar_type temp2 = alpha*w_extra - x.alpha*x.w_extra;
                        return std::sqrt(temp1 + temp2*temp2);
                    }
                    else
                    {
                        return alpha*std::sqrt(length_squared(w) + w_extra*w_extra);
                    }
                }
                else
                {
                    if (x.w.size() > 0)
                        return x.alpha*std::sqrt(length_squared(x.w) + x.w_extra*x.w_extra);
                    else
                        return 0;
                }
            }

            scalar_type inner_product (
                const sample_type& x
            ) const
            {
                if (w.size() > 0)
                    return alpha*(trans(w)*x + w_extra*x_extra);
                else
                    return 0;
            }

            scalar_type inner_product (
                const kcentroid& x
            ) const
            {
                // make sure requires clause is not broken
                DLIB_ASSERT(x.get_kernel() == get_kernel(),
                    "\tscalar_type kcentroid::inner_product(const kcentroid& x)"
                    << "\n\tYou can only compare two kcentroid objects if they use the same kernel"
                    << "\n\tthis: " << this
                    );

                if (w.size() > 0 && x.w.size() > 0)
                    return alpha*x.alpha*(trans(w)*x.w + w_extra*x.w_extra);
                else
                    return 0;
            }

            scalar_type squared_norm (
            ) const
            {
                if (w.size() > 0)
                    return alpha*alpha*(trans(w)*w + w_extra*w_extra);
                else
                    return 0;
            }

            scalar_type operator() (
                const sample_type& x
            ) const
            {
                if (w.size() > 0)
                {
                    scalar_type temp1 = length_squared(x-alpha*w);
                    scalar_type temp2 = x_extra - alpha*w_extra;
                    return std::sqrt(temp1 + temp2*temp2);
                }
                else
                {
                    return std::sqrt(length_squared(x) + x_extra*x_extra);
                }
            }

            scalar_type test_and_train (
                const sample_type& x
            )
            {
                ++samples_seen;
                const scalar_type xscale = 1/samples_seen;
                const scalar_type cscale = 1-xscale;

                do_train(x, cscale, xscale);

                return (*this)(x);
            }

            void train (
                const sample_type& x
            )
            {
                ++samples_seen;
                const scalar_type xscale = 1/samples_seen;
                const scalar_type cscale = 1-xscale;

                do_train(x, cscale, xscale);
            }

            scalar_type test_and_train (
                const sample_type& x,
                scalar_type cscale,
                scalar_type xscale
            )
            {
                ++samples_seen;

                do_train(x, cscale, xscale);

                return (*this)(x);
            }

            void scale_by (
                scalar_type cscale
            )
            {
                alpha *= cscale;
                w_extra *= cscale;
            }

            void train (
                const sample_type& x,
                scalar_type cscale,
                scalar_type xscale
            )
            {
                ++samples_seen;
                do_train(x, cscale, xscale);
            }

            void swap (
                kcentroid& item
            )
            {
                exchange(my_remove_oldest_first, item.my_remove_oldest_first);
                exchange(kernel, item.kernel);
                exchange(w, item.w);
                exchange(alpha, item.alpha);
                exchange(w_extra, item.w_extra);
                exchange(x_extra, item.x_extra);
                exchange(my_tolerance, item.my_tolerance);
                exchange(my_max_dictionary_size, item.my_max_dictionary_size);
                exchange(samples_seen, item.samples_seen);
            }

            unsigned long dictionary_size (
            ) const
            {
                if (samples_seen > 0)
                {
                    if (std::abs(w_extra) > std::numeric_limits<scalar_type>::epsilon())
                        return 1;
                    else
                        return 2;
                }
                else
                    return 0;
            }

            friend void serialize(const kcentroid& item, std::ostream& out)
            {
                serialize(item.my_remove_oldest_first, out);
                serialize(item.kernel, out);
                serialize(item.w, out);
                serialize(item.alpha, out);
                serialize(item.w_extra, out);
                serialize(item.x_extra, out);
                serialize(item.my_tolerance, out);
                serialize(item.my_max_dictionary_size, out);
                serialize(item.samples_seen, out);
            }

            friend void deserialize(kcentroid& item, std::istream& in)
            {
                deserialize(item.my_remove_oldest_first, in);
                deserialize(item.kernel, in);
                deserialize(item.w, in);
                deserialize(item.alpha, in);
                deserialize(item.w_extra, in);
                deserialize(item.x_extra, in);
                deserialize(item.my_tolerance, in);
                deserialize(item.my_max_dictionary_size, in);
                deserialize(item.samples_seen, in);
            }

            distance_function<kernel_type> get_distance_function (
            ) const
            {

                if (samples_seen > 0)
                {
                    typename distance_function<kernel_type>::sample_vector_type temp_basis_vectors;
                    typename distance_function<kernel_type>::scalar_vector_type temp_alpha;

                    // What we are doing here needs a bit of explanation.  The w vector
                    // has an implicit extra dimension tacked on to it with the value of w_extra.
                    // The kernel we are using takes normal vectors and implicitly tacks the value
                    // x_extra onto their end.  So what we are doing here is scaling w so that
                    // the value it should have tacked onto it is x_scale.  Note that we also
                    // adjust alpha so that the combination of alpha*w stays the same.
                    scalar_type scale;

                    // if w_extra is basically greater than 0
                    if (std::abs(w_extra) > std::numeric_limits<scalar_type>::epsilon())
                    {
                        scale = (x_extra/w_extra);
                        temp_basis_vectors.set_size(1);
                        temp_alpha.set_size(1);
                        temp_basis_vectors(0) = w*scale;
                        temp_alpha(0) = alpha/scale;
                    }
                    else
                    {
                        // In this case w_extra is zero. So the only way we can get the same
                        // thing in the output basis vector set is by using two vectors
                        temp_basis_vectors.set_size(2);
                        temp_alpha.set_size(2);
                        temp_basis_vectors(0) = 2*w;
                        temp_alpha(0) = alpha;
                        temp_basis_vectors(1) = w;
                        temp_alpha(1) = -alpha;
                    }

                    return distance_function<kernel_type>(temp_alpha, squared_norm(), kernel, temp_basis_vectors);
                }
                else
                {
                    return distance_function<kernel_type>(kernel);
                }
            }

        private:

            void do_train (
                const sample_type& x,
                scalar_type cscale,
                scalar_type xscale
            )
            {
                set_size_of_w(x);

                const scalar_type temp = cscale*alpha;

                if (temp != 0)
                {
                    w = w + xscale*x/temp;
                    w_extra = w_extra + xscale*x_extra/temp;
                    alpha = temp;
                }
                else
                {
                    w = cscale*alpha*w + xscale*x;
                    w_extra = cscale*alpha*w_extra + xscale*x_extra;
                    alpha = 1;
                }
            }

            void set_size_of_w (
                const sample_type& x
            )
            {
                if (x.size() != w.size())
                {
                    w.set_size(x.nr(), x.nc());
                    set_all_elements(w, 0);
                    alpha = 0;
                    w_extra = x_extra;
                }
            }

            bool my_remove_oldest_first;

            kernel_type kernel;

            sample_type w;
            scalar_type alpha;

            scalar_type w_extra;
            scalar_type x_extra;

            scalar_type my_tolerance;
            unsigned long my_max_dictionary_size;
            scalar_type samples_seen;

        };

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    //                     Overloads for when kernel_type == sparse_linear_kernel
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <typename T>
        class kcentroid<sparse_linear_kernel<T> >
        {

            typedef sparse_linear_kernel<T> kernel_type;
        public:
            typedef typename kernel_type::scalar_type scalar_type;
            typedef typename kernel_type::sample_type sample_type;
            typedef typename kernel_type::mem_manager_type mem_manager_type;

            explicit kcentroid (
                const kernel_type& kernel_,
                scalar_type tolerance_ = 0.001,
                unsigned long max_dictionary_size_ = 1000000,
                bool remove_oldest_first_ = false
            ) :
                my_remove_oldest_first(remove_oldest_first_),
                kernel(kernel_),
                my_tolerance(tolerance_),
                my_max_dictionary_size(max_dictionary_size_)
            {
                // make sure requires clause is not broken
                DLIB_ASSERT(tolerance_ >= 0 && max_dictionary_size_ > 0,
                    "\tkcentroid::kcentroid()"
                    << "\n\t You have to give a positive tolerance"
                    << "\n\t this:                 " << this
                    << "\n\t tolerance_:           " << tolerance_
                    << "\n\t max_dictionary_size_: " << max_dictionary_size_
                    );

                clear_dictionary();
            }

            scalar_type     tolerance() const               { return my_tolerance; }
            unsigned long   max_dictionary_size() const     { return my_max_dictionary_size; }
            bool            remove_oldest_first () const    { return my_remove_oldest_first; }
            const kernel_type& get_kernel () const          { return kernel; }
            scalar_type     samples_trained () const        { return samples_seen; }

            void clear_dictionary ()
            {
                samples_seen = 0;
                w.clear();
                alpha = 0;
            }

            scalar_type operator() (
                const kcentroid& x
            ) const
            {
                // make sure requires clause is not broken
                DLIB_ASSERT(x.get_kernel() == get_kernel(),
                    "\tscalar_type kcentroid::operator()(const kcentroid& x)"
                    << "\n\tYou can only compare two kcentroid objects if they use the same kernel"
                    << "\n\tthis: " << this
                    );

                return distance(alpha,w , x.alpha,x.w);
            }

            scalar_type inner_product (
                const sample_type& x
            ) const
            {
                return alpha*dot(w,x);
            }

            scalar_type inner_product (
                const kcentroid& x
            ) const
            {
                // make sure requires clause is not broken
                DLIB_ASSERT(x.get_kernel() == get_kernel(),
                    "\tscalar_type kcentroid::inner_product(const kcentroid& x)"
                    << "\n\tYou can only compare two kcentroid objects if they use the same kernel"
                    << "\n\tthis: " << this
                    );

                return alpha*x.alpha*dot(w,x.w);
            }

            scalar_type squared_norm (
            ) const
            {
                return alpha*alpha*length_squared(w);
            }

            scalar_type operator() (
                const sample_type& x
            ) const
            {
                return distance(static_cast<scalar_type>(1), x, alpha, w);
            }

            scalar_type test_and_train (
                const sample_type& x
            )
            {
                ++samples_seen;
                const scalar_type xscale = 1/samples_seen;
                const scalar_type cscale = 1-xscale;

                do_train(x, cscale, xscale);

                return (*this)(x);
            }

            void train (
                const sample_type& x
            )
            {
                ++samples_seen;
                const scalar_type xscale = 1/samples_seen;
                const scalar_type cscale = 1-xscale;

                do_train(x, cscale, xscale);
            }

            scalar_type test_and_train (
                const sample_type& x,
                scalar_type cscale,
                scalar_type xscale
            )
            {
                ++samples_seen;

                do_train(x, cscale, xscale);

                return (*this)(x);
            }

            void scale_by (
                scalar_type cscale
            )
            {
                alpha *= cscale;
            }

            void train (
                const sample_type& x,
                scalar_type cscale,
                scalar_type xscale
            )
            {
                ++samples_seen;
                do_train(x, cscale, xscale);
            }

            void swap (
                kcentroid& item
            )
            {
                exchange(my_remove_oldest_first, item.my_remove_oldest_first);
                exchange(kernel, item.kernel);
                exchange(w, item.w);
                exchange(alpha, item.alpha);
                exchange(my_tolerance, item.my_tolerance);
                exchange(my_max_dictionary_size, item.my_max_dictionary_size);
                exchange(samples_seen, item.samples_seen);
            }

            unsigned long dictionary_size (
            ) const
            {
                if (samples_seen > 0)
                    return 1;
                else
                    return 0;
            }

            friend void serialize(const kcentroid& item, std::ostream& out)
            {
                serialize(item.my_remove_oldest_first, out);
                serialize(item.kernel, out);
                serialize(item.w, out);
                serialize(item.alpha, out);
                serialize(item.my_tolerance, out);
                serialize(item.my_max_dictionary_size, out);
                serialize(item.samples_seen, out);
            }

            friend void deserialize(kcentroid& item, std::istream& in)
            {
                deserialize(item.my_remove_oldest_first, in);
                deserialize(item.kernel, in);
                deserialize(item.w, in);
                deserialize(item.alpha, in);
                deserialize(item.my_tolerance, in);
                deserialize(item.my_max_dictionary_size, in);
                deserialize(item.samples_seen, in);
            }

            distance_function<kernel_type> get_distance_function (
            ) const
            {
                if (samples_seen > 0)
                {
                    typename distance_function<kernel_type>::sample_vector_type temp_basis_vectors;
                    typename distance_function<kernel_type>::scalar_vector_type temp_alpha;

                    temp_basis_vectors.set_size(1);
                    temp_basis_vectors(0) = sample_type(w.begin(), w.end());
                    temp_alpha.set_size(1);
                    temp_alpha(0) = alpha;

                    return distance_function<kernel_type>(temp_alpha, squared_norm(), kernel, temp_basis_vectors);
                }
                else
                {
                    return distance_function<kernel_type>(kernel);
                }
            }

        private:

            void do_train (
                const sample_type& x,
                scalar_type cscale,
                scalar_type xscale
            )
            {
                const scalar_type temp = cscale*alpha;

                if (temp != 0)
                {
                    // compute w += xscale*x/temp
                    typename sample_type::const_iterator i;
                    for (i = x.begin(); i != x.end(); ++i)
                    {
                        w[i->first] += xscale*(i->second)/temp;
                    }

                    alpha = temp;
                }
                else
                {
                    // first compute w = cscale*alpha*w
                    for (typename std::map<unsigned long,scalar_type>::iterator i = w.begin(); i != w.end(); ++i)
                    {
                        i->second *= cscale*alpha;
                    }

                    // now compute w += xscale*x
                    for (typename sample_type::const_iterator i = x.begin(); i != x.end(); ++i)
                    {
                        w[i->first] += xscale*(i->second);
                    }

                    alpha = 1;
                }
            }

            bool my_remove_oldest_first;

            kernel_type kernel;

            std::map<unsigned long,scalar_type> w;
            scalar_type alpha;

            scalar_type my_tolerance;
            unsigned long my_max_dictionary_size;
            scalar_type samples_seen;

        };

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    //               Overloads for when kernel_type == offset_kernel<sparse_linear_kernel>
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <typename T>
        class kcentroid<offset_kernel<sparse_linear_kernel<T> > >
        {

            /*!
                INITIAL VALUE
                    - x_extra == sqrt(kernel.offset)

                CONVENTION
                    - x_extra == sqrt(kernel.offset)
                    - w_extra == the value of the extra dimension tacked onto the
                      end of the w vector
            !*/

            typedef offset_kernel<sparse_linear_kernel<T> > kernel_type;
        public:
            typedef typename kernel_type::scalar_type scalar_type;
            typedef typename kernel_type::sample_type sample_type;
            typedef typename kernel_type::mem_manager_type mem_manager_type;

            explicit kcentroid (
                const kernel_type& kernel_,
                scalar_type tolerance_ = 0.001,
                unsigned long max_dictionary_size_ = 1000000,
                bool remove_oldest_first_ = false
            ) :
                my_remove_oldest_first(remove_oldest_first_),
                kernel(kernel_),
                my_tolerance(tolerance_),
                my_max_dictionary_size(max_dictionary_size_)
            {
                // make sure requires clause is not broken
                DLIB_ASSERT(tolerance_ >= 0 && max_dictionary_size_ > 0,
                    "\tkcentroid::kcentroid()"
                    << "\n\t You have to give a positive tolerance"
                    << "\n\t this:                 " << this
                    << "\n\t tolerance_:           " << tolerance_
                    << "\n\t max_dictionary_size_: " << max_dictionary_size_
                    );

                x_extra = std::sqrt(kernel.offset);

                clear_dictionary();
            }

            scalar_type     tolerance() const               { return my_tolerance; }
            unsigned long   max_dictionary_size() const     { return my_max_dictionary_size; }
            bool            remove_oldest_first () const    { return my_remove_oldest_first; }
            const kernel_type& get_kernel () const          { return kernel; }
            scalar_type     samples_trained () const        { return samples_seen; }

            void clear_dictionary ()
            {
                samples_seen = 0;
                w.clear();
                alpha = 0;
                w_extra = x_extra;
            }

            scalar_type operator() (
                const kcentroid& x
            ) const
            {
                // make sure requires clause is not broken
                DLIB_ASSERT(x.get_kernel() == get_kernel(),
                    "\tscalar_type kcentroid::operator()(const kcentroid& x)"
                    << "\n\tYou can only compare two kcentroid objects if they use the same kernel"
                    << "\n\tthis: " << this
                    );

                if (samples_seen > 0)
                {
                    scalar_type temp1 = distance_squared(alpha,w , x.alpha,x.w);
                    scalar_type temp2 = alpha*w_extra - x.alpha*x.w_extra;
                    return std::sqrt(temp1 + temp2*temp2);
                }
                else
                {
                    return 0;
                }
            }

            scalar_type inner_product (
                const sample_type& x
            ) const
            {
                if (samples_seen > 0)
                    return alpha*(dot(w,x) + w_extra*x_extra);
                else
                    return 0;
            }

            scalar_type inner_product (
                const kcentroid& x
            ) const
            {
                // make sure requires clause is not broken
                DLIB_ASSERT(x.get_kernel() == get_kernel(),
                    "\tscalar_type kcentroid::inner_product(const kcentroid& x)"
                    << "\n\tYou can only compare two kcentroid objects if they use the same kernel"
                    << "\n\tthis: " << this
                    );

                if (samples_seen > 0 && x.samples_seen > 0)
                    return alpha*x.alpha*(dot(w,x.w) + w_extra*x.w_extra);
                else
                    return 0;
            }

            scalar_type squared_norm (
            ) const
            {
                if (samples_seen > 0)
                    return alpha*alpha*(length_squared(w) + w_extra*w_extra);
                else
                    return 0;
            }

            scalar_type operator() (
                const sample_type& x
            ) const
            {
                if (samples_seen > 0)
                {
                    scalar_type temp1 = distance_squared(1,x,alpha,w);
                    scalar_type temp2 = x_extra - alpha*w_extra;
                    return std::sqrt(temp1 + temp2*temp2);
                }
                else
                {
                    return std::sqrt(length_squared(x) + x_extra*x_extra);
                }
            }

            scalar_type test_and_train (
                const sample_type& x
            )
            {
                ++samples_seen;
                const scalar_type xscale = 1/samples_seen;
                const scalar_type cscale = 1-xscale;

                do_train(x, cscale, xscale);

                return (*this)(x);
            }

            void train (
                const sample_type& x
            )
            {
                ++samples_seen;
                const scalar_type xscale = 1/samples_seen;
                const scalar_type cscale = 1-xscale;

                do_train(x, cscale, xscale);
            }

            scalar_type test_and_train (
                const sample_type& x,
                scalar_type cscale,
                scalar_type xscale
            )
            {
                ++samples_seen;

                do_train(x, cscale, xscale);

                return (*this)(x);
            }

            void scale_by (
                scalar_type cscale
            )
            {
                alpha *= cscale;
                w_extra *= cscale;
            }

            void train (
                const sample_type& x,
                scalar_type cscale,
                scalar_type xscale
            )
            {
                ++samples_seen;
                do_train(x, cscale, xscale);
            }

            void swap (
                kcentroid& item
            )
            {
                exchange(my_remove_oldest_first, item.my_remove_oldest_first);
                exchange(kernel, item.kernel);
                exchange(w, item.w);
                exchange(alpha, item.alpha);
                exchange(w_extra, item.w_extra);
                exchange(x_extra, item.x_extra);
                exchange(my_tolerance, item.my_tolerance);
                exchange(my_max_dictionary_size, item.my_max_dictionary_size);
                exchange(samples_seen, item.samples_seen);
            }

            unsigned long dictionary_size (
            ) const
            {
                if (samples_seen > 0)
                {
                    if (std::abs(w_extra) > std::numeric_limits<scalar_type>::epsilon())
                        return 1;
                    else
                        return 2;
                }
                else
                {
                    return 0;
                }
            }

            friend void serialize(const kcentroid& item, std::ostream& out)
            {
                serialize(item.my_remove_oldest_first, out);
                serialize(item.kernel, out);
                serialize(item.w, out);
                serialize(item.alpha, out);
                serialize(item.w_extra, out);
                serialize(item.x_extra, out);
                serialize(item.my_tolerance, out);
                serialize(item.my_max_dictionary_size, out);
                serialize(item.samples_seen, out);
            }

            friend void deserialize(kcentroid& item, std::istream& in)
            {
                deserialize(item.my_remove_oldest_first, in);
                deserialize(item.kernel, in);
                deserialize(item.w, in);
                deserialize(item.alpha, in);
                deserialize(item.w_extra, in);
                deserialize(item.x_extra, in);
                deserialize(item.my_tolerance, in);
                deserialize(item.my_max_dictionary_size, in);
                deserialize(item.samples_seen, in);
            }

            distance_function<kernel_type> get_distance_function (
            ) const
            {
                if (samples_seen > 0)
                {
                    typename distance_function<kernel_type>::sample_vector_type temp_basis_vectors;
                    typename distance_function<kernel_type>::scalar_vector_type temp_alpha;

                    // What we are doing here needs a bit of explanation.  The w vector
                    // has an implicit extra dimension tacked on to it with the value of w_extra.
                    // The kernel we are using takes normal vectors and implicitly tacks the value
                    // x_extra onto their end.  So what we are doing here is scaling w so that
                    // the value it should have tacked onto it is x_scale.  Note that we also
                    // adjust alpha so that the combination of alpha*w stays the same.
                    scalar_type scale;

                    // if w_extra is basically greater than 0
                    if (std::abs(w_extra) > std::numeric_limits<scalar_type>::epsilon())
                    {
                        scale = (x_extra/w_extra);
                        temp_basis_vectors.set_size(1);
                        temp_alpha.set_size(1);
                        temp_basis_vectors(0) = sample_type(w.begin(), w.end());
                        dlib::scale_by(temp_basis_vectors(0), scale);
                        temp_alpha(0) = alpha/scale;
                    }
                    else
                    {
                        // In this case w_extra is zero. So the only way we can get the same
                        // thing in the output basis vector set is by using two vectors
                        temp_basis_vectors.set_size(2);
                        temp_alpha.set_size(2);
                        temp_basis_vectors(0) = sample_type(w.begin(), w.end());
                        dlib::scale_by(temp_basis_vectors(0), 2);
                        temp_alpha(0) = alpha;
                        temp_basis_vectors(1) = sample_type(w.begin(), w.end());
                        temp_alpha(1) = -alpha;
                    }

                    return distance_function<kernel_type>(temp_alpha, squared_norm(), kernel, temp_basis_vectors);

                }
                else
                {
                    return distance_function<kernel_type>(kernel);
                }

            }

        private:

            void do_train (
                const sample_type& x,
                scalar_type cscale,
                scalar_type xscale
            )
            {

                const scalar_type temp = cscale*alpha;

                if (temp != 0)
                {
                    // compute w += xscale*x/temp
                    typename sample_type::const_iterator i;
                    for (i = x.begin(); i != x.end(); ++i)
                    {
                        w[i->first] += xscale*(i->second)/temp;
                    }

                    w_extra = w_extra + xscale*x_extra/temp;
                    alpha = temp;
                }
                else
                {
                    // first compute w = cscale*alpha*w
                    for (typename std::map<unsigned long,scalar_type>::iterator i = w.begin(); i != w.end(); ++i)
                    {
                        i->second *= cscale*alpha;
                    }

                    // now compute w += xscale*x
                    for (typename sample_type::const_iterator i = x.begin(); i != x.end(); ++i)
                    {
                        w[i->first] += xscale*(i->second);
                    }

                    w_extra = cscale*alpha*w_extra + xscale*x_extra;
                    alpha = 1;
                }
            }

            bool my_remove_oldest_first;

            kernel_type kernel;

            std::map<unsigned long,scalar_type> w;
            scalar_type alpha;

            scalar_type w_extra;
            scalar_type x_extra;

            scalar_type my_tolerance;
            unsigned long my_max_dictionary_size;
            scalar_type samples_seen;

        };

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

    }

    #endif // DLIB_KCENTROId_OVERLOADS_