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

    #include "tester.h"
    #include <dlib/svm.h>
    #include <dlib/rand.h>
    #include <dlib/string.h>
    #include <vector>
    #include <sstream>
    #include <ctime>

    namespace
    {
        using namespace test;
        using namespace dlib;
        using namespace std;
        dlib::logger dlog("test.ekm_and_lisf");

        class empirical_kernel_map_tester : public tester
        {
            /*!
                WHAT THIS OBJECT REPRESENTS
                    This object represents a unit test.  When it is constructed
                    it adds itself into the testing framework.
            !*/
        public:
            empirical_kernel_map_tester (
            ) :
                tester (
                    "test_ekm_and_lisf",       // the command line argument name for this test
                    "Run tests on the empirical_kernel_map and linearly_independent_subset_finder objects.", // the command line argument description
                    0                     // the number of command line arguments for this test
                )
            {
                thetime = time(0);
            }

            time_t thetime;
            dlib::rand rnd;

            template <typename T>
            void validate (
                const T& ekm_small,
                const T& ekm_big
            )
            {
                matrix<double> tmat;
                projection_function<typename T::kernel_type> proj;

                ekm_small.get_transformation_to(ekm_big, tmat, proj);
                DLIB_TEST(tmat.nr() == ekm_big.out_vector_size());
                DLIB_TEST(tmat.nc() == ekm_small.out_vector_size());
                DLIB_TEST((unsigned long)proj.basis_vectors.size() == ekm_big.basis_size() - ekm_small.basis_size());
                for (unsigned long i = 0; i < 6; ++i)
                {
                    const typename T::sample_type temp = randm(4,1,rnd);
                    DLIB_TEST(length(ekm_big.project(temp) - (tmat*ekm_small.project(temp) + proj(temp))) < 1e-10);
                }
            }

            void test_transformation_stuff()
            {
                typedef matrix<double,0,1> sample_type;
                typedef radial_basis_kernel<sample_type> kernel_type;
                const kernel_type kern(1);

                for (unsigned long n = 1; n < 6; ++n)
                {
                    print_spinner();
                    for (unsigned long extra = 1; extra < 10; ++extra)
                    {
                        std::vector<sample_type> samps_small, samps_big;
                        linearly_independent_subset_finder<kernel_type> lisf_small(kern, 1000);
                        linearly_independent_subset_finder<kernel_type> lisf_big(kern, 1000);
                        for (unsigned long i = 0; i < n; ++i)
                        {
                            samps_small.push_back(randm(4,1,rnd));
                            samps_big.push_back(samps_small.back());
                            lisf_big.add(samps_small.back());
                            lisf_small.add(samps_small.back());
                        }
                        for (unsigned long i = 0; i < extra; ++i)
                        {
                            samps_big.push_back(randm(4,1,rnd));
                            lisf_big.add(samps_big.back());
                        }

                        // test no lisf
                        {
                            empirical_kernel_map<kernel_type> ekm_small, ekm_big;
                            ekm_small.load(kern, samps_small);
                            ekm_big.load(kern, samps_big);

                            validate(ekm_small, ekm_big);
                        }

                        // test with lisf
                        {
                            empirical_kernel_map<kernel_type> ekm_small, ekm_big;
                            ekm_small.load(lisf_small);
                            ekm_big.load(lisf_big);

                            validate(ekm_small, ekm_big);
                        }

                        // test with partly lisf
                        {
                            empirical_kernel_map<kernel_type> ekm_small, ekm_big;
                            ekm_small.load(kern, samps_small);
                            ekm_big.load(lisf_big);

                            validate(ekm_small, ekm_big);
                        }

                        // test with partly lisf
                        {
                            empirical_kernel_map<kernel_type> ekm_small, ekm_big;
                            ekm_small.load(lisf_small);
                            ekm_big.load(kern, samps_big);

                            validate(ekm_small, ekm_big);
                        }

                    }
                }

                // test what happens if the bigger ekm only has repeated basis vectors
                {
                    empirical_kernel_map<kernel_type> ekm_big, ekm_small;
                    std::vector<sample_type> samps_big, samps_small;

                    sample_type temp = randm(4,1,rnd);

                    samps_small.push_back(temp);
                    samps_big.push_back(temp);
                    samps_big.push_back(temp);

                    ekm_big.load(kern, samps_big);
                    ekm_small.load(kern, samps_small);

                    validate(ekm_small, ekm_big);

                }
                {
                    empirical_kernel_map<kernel_type> ekm_big, ekm_small;
                    linearly_independent_subset_finder<kernel_type> lisf_small(kern, 1000);
                    std::vector<sample_type> samps_big;

                    sample_type temp = randm(4,1,rnd);

                    lisf_small.add(temp);
                    samps_big.push_back(temp);
                    samps_big.push_back(temp);

                    ekm_big.load(kern, samps_big);
                    ekm_small.load(lisf_small);

                    validate(ekm_small, ekm_big);

                }
                {
                    empirical_kernel_map<kernel_type> ekm_big, ekm_small;
                    std::vector<sample_type> samps_big, samps_small;

                    sample_type temp = randm(4,1,rnd);
                    sample_type temp2 = randm(4,1,rnd);

                    samps_small.push_back(temp);
                    samps_small.push_back(temp2);
                    samps_big.push_back(temp);
                    samps_big.push_back(temp2);
                    samps_big.push_back(randm(4,1,rnd));

                    ekm_big.load(kern, samps_big);
                    ekm_small.load(kern, samps_small);

                    validate(ekm_small, ekm_big);

                }
                {
                    empirical_kernel_map<kernel_type> ekm_big, ekm_small;
                    linearly_independent_subset_finder<kernel_type> lisf_small(kern, 1000);
                    std::vector<sample_type> samps_big;

                    sample_type temp = randm(4,1,rnd);
                    sample_type temp2 = randm(4,1,rnd);

                    lisf_small.add(temp);
                    lisf_small.add(temp2);
                    samps_big.push_back(temp);
                    samps_big.push_back(temp2);
                    samps_big.push_back(temp);

                    ekm_big.load(kern, samps_big);
                    ekm_small.load(lisf_small);

                    validate(ekm_small, ekm_big);

                }

            }

            void perform_test (
            )
            {
                ++thetime;
                typedef matrix<double,0,1> sample_type;
                //dlog << LINFO << "time seed: " << thetime;
                //rnd.set_seed(cast_to_string(thetime));

                typedef radial_basis_kernel<sample_type> kernel_type;

                for (int n = 1; n < 10; ++n)
                {
                    print_spinner();
                    dlog << LINFO << "matrix size " << n;

                    std::vector<sample_type> samples;
                    // make some samples
                    for (int i = 0; i < n; ++i)
                    {
                        samples.push_back(randm(4,1,rnd));
                        // double up the samples just to mess with the lisf
                        if (n > 5)
                            samples.push_back(samples.back());
                    }

                    dlog << LINFO << "samples.size(): "<< samples.size();

                    const kernel_type kern(1);

                    linearly_independent_subset_finder<kernel_type> lisf(kern, 100, 1e-4);
                    unsigned long count = 0;
                    for (unsigned long i = 0; i < samples.size(); ++i)
                    {
                        if (lisf.add(samples[i]))
                        {
                            DLIB_TEST(equal(lisf[lisf.size()-1], samples[i]));
                            ++count;
                        }
                    }
                    DLIB_TEST(count == lisf.size());

                    DLIB_TEST(lisf.size() == (unsigned int)n);

                    dlog << LINFO << "lisf.size(): "<< lisf.size();

                    // make sure the kernel matrices coming out of the lisf are correct
                    DLIB_TEST(dlib::equal(lisf.get_kernel_matrix(), kernel_matrix(kern, lisf), 1e-8));
                    DLIB_TEST(dlib::equal(lisf.get_inv_kernel_marix(), inv(kernel_matrix(kern, lisf.get_dictionary())), 1e-8));

                    empirical_kernel_map<kernel_type> ekm;
                    ekm.load(lisf);
                    DLIB_TEST(ekm.basis_size() == lisf.size());

                    std::vector<sample_type> proj_samples;
                    for (unsigned long i = 0; i < samples.size(); ++i)
                    {
                        double err;
                        proj_samples.push_back(ekm.project(samples[i], err));
                        DLIB_TEST(err <= 1e-4);
                        const double error_agreement = std::abs(err - lisf.projection_error(samples[i]));
                        dlog << LTRACE << "err: " << err << "    error_agreement: "<< error_agreement;
                        DLIB_TEST(error_agreement < 1e-11);
                    }

                    for (int i = 0; i < 5; ++i)
                    {
                        sample_type temp = randm(4,1,rnd);
                        double err;
                        ekm.project(temp, err);
                        const double error_agreement = std::abs(err - lisf.projection_error(temp));
                        dlog << LTRACE << "err: " << err << "    error_agreement: "<< error_agreement;
                        DLIB_TEST(error_agreement < 1e-11);
                    }

                    // make sure the EKM did the projection correctly
                    DLIB_TEST(dlib::equal(kernel_matrix(kern, samples), kernel_matrix(linear_kernel<sample_type>(), proj_samples), 1e-5));
                }

                test_transformation_stuff();

            }
        };

        // Create an instance of this object.  Doing this causes this test
        // to be automatically inserted into the testing framework whenever this cpp file
        // is linked into the project.  Note that since we are inside an unnamed-namespace
        // we won't get any linker errors about the symbol a being defined multiple times.
        empirical_kernel_map_tester a;

    }