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

#include <dlib/sparse_vector.h>
#include "tester.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.sparse_vector");

    void test_sparse_matrix_vector_multiplies()
    {
        dlib::rand rnd;

        const long size = 30;

        for (int iter = 0; iter < 10; ++iter)
        {
            print_spinner();

            std::vector<sample_pair> edges;
            std::vector<ordered_sample_pair> oedges;
            matrix<double> M(size,size);
            M = 0;
            for (long i = 0; i < M.size()/3; ++i)
            {
                const long r = rnd.get_random_32bit_number()%M.nr();
                const long c = rnd.get_random_32bit_number()%M.nc();
                const double d = rnd.get_random_gaussian()*10;
                M(r,c) += d;
                oedges.push_back(ordered_sample_pair(r,c,d));
            }

            matrix<double> SM(size,size);
            SM = 0;
            for (long i = 0; i < SM.size()/3; ++i)
            {
                const long r = rnd.get_random_32bit_number()%SM.nr();
                const long c = rnd.get_random_32bit_number()%SM.nc();
                const double d = rnd.get_random_gaussian()*10;
                SM(r,c) += d;
                if (r != c)
                    SM(c,r) += d;
                edges.push_back(sample_pair(r,c,d));
            }

            const matrix<double> v = randm(size,1);

            matrix<double> result;

            sparse_matrix_vector_multiply(oedges, v, result);
            DLIB_TEST_MSG(length(M*v - result) < 1e-12, length(M*v - result));

            sparse_matrix_vector_multiply(edges, v, result);
            DLIB_TEST_MSG(length(SM*v - result) < 1e-12, length(SM*v - result));

        }
    }

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

    void test_sparse_matrix_vector_multiply1()
    {
        print_spinner();
        std::map<unsigned long,double> sv;
        sv[2] = 8;
        sv[6] = 2.3;

        matrix<double,10,1> v;
        v = 0;
        v(2) = 8;
        v(6) = 2.3;


        matrix<double,0,1> r1, r2;

        r1 = gaussian_randm(4,10)*v;
        r2 = sparse_matrix_vector_multiply(gaussian_randm(4,std::numeric_limits<long>::max()),sv);

        DLIB_TEST(max(abs(r1-r2)) < 1e-15);
    }

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

    void test_sparse_matrix_vector_multiply2()
    {
        std::vector<std::pair<unsigned long,double> > sv;
        sv.push_back(make_pair(6, 1.42));
        sv.push_back(make_pair(3, 5));

        matrix<double,9,1> v;
        v = 0;
        v(3) = 5;
        v(6) = 1.42;


        matrix<double,0,1> r1, r2;

        r1 = gaussian_randm(3,9)*v;
        r2 = sparse_matrix_vector_multiply(gaussian_randm(3,std::numeric_limits<long>::max()),sv);

        DLIB_TEST(max(abs(r1-r2)) < 1e-15);
    }

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

    void test_make_sparse_vector_inplace()
    {
        std::vector<std::pair<unsigned long,double> > vect;
        vect.push_back(make_pair(4,1));
        vect.push_back(make_pair(0,1));
        vect.push_back(make_pair(4,1));
        vect.push_back(make_pair(3,1));
        vect.push_back(make_pair(8,1));
        vect.push_back(make_pair(8,1));
        vect.push_back(make_pair(8,1));
        vect.push_back(make_pair(8,1));

        make_sparse_vector_inplace(vect);

        DLIB_TEST(vect.size() == 4);
        DLIB_TEST(vect[0].first == 0);
        DLIB_TEST(vect[1].first == 3);
        DLIB_TEST(vect[2].first == 4);
        DLIB_TEST(vect[3].first == 8);

        DLIB_TEST(vect[0].second == 1);
        DLIB_TEST(vect[1].second == 1);
        DLIB_TEST(vect[2].second == 2);
        DLIB_TEST(vect[3].second == 4);
    }

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

    class sparse_vector_tester : public tester
    {
    public:
        sparse_vector_tester (
        ) :
            tester (
                "test_sparse_vector",       // the command line argument name for this test
                "Run tests on the sparse_vector routines.", // the command line argument description
                0                     // the number of command line arguments for this test
            )
        {
        }


        void perform_test (
        )
        {
            test_make_sparse_vector_inplace();

            std::map<unsigned int, double> v;
            v[4] = 8;
            v[2] = -4;
            v[9] = 10;

            DLIB_TEST(max(v) == 10);
            DLIB_TEST(min(v) == -4);

            v.clear();
            v[4] = 8;
            v[9] = 10;
            DLIB_TEST(max(v) == 10);
            DLIB_TEST(min(v) == 0);


            v.clear();
            v[4] = -9;
            v[9] = -4;
            DLIB_TEST(max(v) == 0);
            DLIB_TEST(min(v) == -9);


            {
                matrix<double> a(2,2), b(2,2);
                a = randm(2,2);
                b = randm(2,2);

                DLIB_TEST(equal(a-b, subtract(a,b)));
                DLIB_TEST(equal(a+b, add(a,b)));
                DLIB_TEST(equal(a-(b+b), subtract(a,b+b)));
                DLIB_TEST(equal(a+b+b, add(a,b+b)));
            }

            {
                std::map<unsigned long,double> a, b, c;
                a[1] = 2;
                a[3] = 5;

                b[0] = 3;
                b[1] = 1;

                c = add(a,b);
                DLIB_TEST(c.size() == 3);
                DLIB_TEST(c[0] == 3);
                DLIB_TEST(c[1] == 3);
                DLIB_TEST(c[3] == 5);

                c = subtract(a,b);
                DLIB_TEST(c.size() == 3);
                DLIB_TEST(c[0] == -3);
                DLIB_TEST(c[1] == 1);
                DLIB_TEST(c[3] == 5);

                c = add(b,a);
                DLIB_TEST(c.size() == 3);
                DLIB_TEST(c[0] == 3);
                DLIB_TEST(c[1] == 3);
                DLIB_TEST(c[3] == 5);

                c = subtract(b,a);
                DLIB_TEST(c.size() == 3);
                DLIB_TEST(c[0] == 3);
                DLIB_TEST(c[1] == -1);
                DLIB_TEST(c[3] == -5);

                std::vector<std::pair<unsigned long,double> > aa, bb, cc;

                aa.assign(a.begin(), a.end());
                bb.assign(b.begin(), b.end());

                cc = add(aa,bb); 
                DLIB_TEST(cc.size() == 3);
                DLIB_TEST(cc[0].first == 0);
                DLIB_TEST(cc[1].first == 1);
                DLIB_TEST(cc[2].first == 3);
                DLIB_TEST(cc[0].second == 3);
                DLIB_TEST(cc[1].second == 3);
                DLIB_TEST(cc[2].second == 5);

                cc = subtract(aa,bb); 
                DLIB_TEST(cc.size() == 3);
                DLIB_TEST(cc[0].first == 0);
                DLIB_TEST(cc[1].first == 1);
                DLIB_TEST(cc[2].first == 3);
                DLIB_TEST(cc[0].second == -3);
                DLIB_TEST(cc[1].second == 1);
                DLIB_TEST(cc[2].second == 5);

                cc = add(bb,aa); 
                DLIB_TEST(cc.size() == 3);
                DLIB_TEST(cc[0].first == 0);
                DLIB_TEST(cc[1].first == 1);
                DLIB_TEST(cc[2].first == 3);
                DLIB_TEST(cc[0].second == 3);
                DLIB_TEST(cc[1].second == 3);
                DLIB_TEST(cc[2].second == 5);

                cc = subtract(bb,aa); 
                DLIB_TEST(cc.size() == 3);
                DLIB_TEST(cc[0].first == 0);
                DLIB_TEST(cc[1].first == 1);
                DLIB_TEST(cc[2].first == 3);
                DLIB_TEST(cc[0].second == 3);
                DLIB_TEST(cc[1].second == -1);
                DLIB_TEST(cc[2].second == -5);

            }

            test_sparse_matrix_vector_multiplies();
            test_sparse_matrix_vector_multiply1();
            test_sparse_matrix_vector_multiply2();

            {
                matrix<double,0,1> a, b;
                a = gaussian_randm(6,1, 0);
                b = gaussian_randm(6,1, 1);

                std::vector<std::pair<unsigned long,double> > aa, bb;

                assign(aa, a);
                assign(bb, b);

                // dot() does something special when the sparse vectors have entries for
                // each dimension, which is what happens when they are copied from dense
                // vectors.  So the point of the tests in this block is to make sure dot()
                // works right in this case.
                DLIB_TEST(std::abs(dot(a,b) - dot(aa,bb)) < 1e-14);
                a(3) = 0;
                assign(aa, a);
                DLIB_TEST(std::abs(dot(a,b) - dot(aa,bb)) < 1e-14);
            }
        }
    };

    sparse_vector_tester a;

}