/*
Unit tests for GLQ functions.
*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "minunit.h"
#include "../lib/glq.h"
#include "../lib/constants.h"

/* Test data taken from:
    http://mathworld.wolfram.com/Legendre-GaussQuadrature.html */
double o2roots[2] = {-0.577350269, 0.577350269},
       o3roots[3] = {-0.774596669, 0., 0.774596669},
       o4roots[4] = {-0.861136312, -0.339981044, 0.339981044, 0.861136312},
       o5roots[5] = {-0.906179846, -0.53846931, 0., 0.53846931, 0.906179846},
       o19roots[19] = {-0.992406843843584350,
                       -0.960208152134830020,
                       -0.903155903614817900,
                       -0.822714656537142820,
                       -0.720966177335229390,
                       -0.600545304661680990,
                       -0.464570741375960940,
                       -0.316564099963629830,
                       -0.160358645640225370,
                        0.000000000000000000,
                        0.160358645640225370,
                        0.316564099963629830,
                        0.464570741375960940,
                        0.600545304661680990,
                        0.720966177335229390,
                        0.822714656537142820,
                        0.903155903614817900,
                        0.960208152134830020,
                        0.992406843843584350};

double o2weights[2] = {1., 1.},
       o3weights[3] = {0.555555556, 0.888888889, 0.555555556},
       o4weights[4] = {0.347854845, 0.652145155, 0.652145155, 0.347854845},
       o5weights[5] = {0.236926885, 0.47862867, 0.568888889, 0.47862867,
            0.236926885};

/* To store fail messages */
char msg[1000];

/* UNIT TESTS */

static char * test_glq_next_root_fail()
{
    double roots[10];
    int i, order, rc;

    /* Test order fail */
    i = 1;
    order = -1;
    rc = glq_next_root(0.5, i, order, roots);
    sprintf(msg, "(order %d) return code %d, expected 1", order, rc);
    mu_assert(rc == 1, msg);

    order = 0;
    rc = glq_next_root(-0.1, i, order, roots);
    sprintf(msg, "(order %d) return code %d, expected 1", order, rc);
    mu_assert(rc == 1, msg);

    order = 1;
    rc = glq_next_root(1.1, i, order, roots);
    sprintf(msg, "(order %d) return code %d, expected 1", order, rc);
    mu_assert(rc == 1, msg);

    /* Test index fail */
    order = 5;
    i = -1;
    rc = glq_next_root(0.5, i, order, roots);
    sprintf(msg, "(index %d, order %d) return code %d, expected 2", order, i,
            rc);
    mu_assert(rc == 2, msg);

    i = 5;
    rc = glq_next_root(0.5, i, order, roots);
    sprintf(msg, "(index %d, order %d) return code %d, expected 2", order, i,
            rc);
    mu_assert(rc == 2, msg);

    i = 10;
    rc = glq_next_root(0.5, i, order, roots);
    sprintf(msg, "(index %d, order %d) return code %d, expected 2", order, i,
            rc);
    mu_assert(rc == 2, msg);

    return 0;
}


static char * test_glq_next_root()
{
    double prec = pow(10, -9), root[19], initial;
    int rc, i, order;

    /* Test order 2 */
    order = 2;
    for(i = 0; i < order; i++)
    {
        initial = cos(PI*((order - i) - 0.25)/(order + 0.5));

        rc = glq_next_root(initial, i, order, root);

        sprintf(msg, "(order %d, root %d) return code %d, expected 0", order, i,
                rc);
        mu_assert(rc == 0, msg);

        sprintf(msg, "(order %d, root %d) expected %.15f got %.15f", order, i,
                o2roots[i], root[i]);
        mu_assert_almost_equals(root[i], o2roots[i], prec, msg);
    }

    /* Test order 3 */
    order = 3;
    for(i = 0; i < order; i++)
    {
        initial = cos(PI*((order - i) - 0.25)/(order + 0.5));

        rc = glq_next_root(initial, i, order, root);

        sprintf(msg, "(order %d, root %d) return code %d, expected 0", order, i,
                rc);
        mu_assert(rc == 0, msg);

        sprintf(msg, "(order %d, root %d) expected %.15f got %.15f", order, i,
                o3roots[i], root[i]);
        mu_assert_almost_equals(root[i], o3roots[i], prec, msg);
    }

    /* Test order 4 */
    order = 4;
    for(i = 0; i < order; i++)
    {
        initial = cos(PI*((order - i) - 0.25)/(order + 0.5));

        rc = glq_next_root(initial, i, order, root);

        sprintf(msg, "(order %d, root %d) return code %d, expected 0", order, i,
                rc);
        mu_assert(rc == 0, msg);

        sprintf(msg, "(order %d, root %d) expected %.15f got %.15f", order, i,
                o4roots[i], root[i]);
        mu_assert_almost_equals(root[i], o4roots[i], prec, msg);
    }

    /* Test order 5 */
    order = 5;
    for(i = 0; i < order; i++)
    {
        initial = cos(PI*((order - i) - 0.25)/(order + 0.5));

        rc = glq_next_root(initial, i, order, root);

        sprintf(msg, "(order %d, root %d) return code %d, expected 0", order, i,
                rc);
        mu_assert(rc == 0, msg);

        sprintf(msg, "(order %d, root %d) expected %.15f got %.15f", order, i,
                o5roots[i], root[i]);
        mu_assert_almost_equals(root[i], o5roots[i], prec, msg);
    }

    /* Test order 19 */
    order = 19;
    for(i = 0; i < order; i++)
    {
        initial = cos(PI*((order - i) - 0.25)/(order + 0.5));

        rc = glq_next_root(initial, i, order, root);

        sprintf(msg, "(order %d, root %d) return code %d, expected 0", order, i,
                rc);
        mu_assert(rc == 0, msg);

        sprintf(msg, "(order %d, root %d) expected %.15f got %.15f", order, i,
                o19roots[i], root[i]);
        mu_assert_almost_equals(root[i], o19roots[i], prec, msg);
    }

    return 0;
}


static char * test_glq_weights()
{
    double prec = pow(10, -9), weights[5];
    int rc, i, order;

    /* Test order 2 */
    order = 2;

    rc = glq_weights(order, o2roots, weights);

    sprintf(msg, "(order %d) return code %d, expected 0", order, rc);
    mu_assert(rc == 0, msg);

    for(i = 0; i < order; i++)
    {
        sprintf(msg, "(order %d, weight %d) expected %.15f got %.15f", order,
                i, o2weights[i], weights[i]);
        mu_assert_almost_equals(weights[i], o2weights[i], prec, msg);
    }

    /* Test order 3 */
    order = 3;

    rc = glq_weights(order, o3roots, weights);

    sprintf(msg, "(order %d) return code %d, expected 0", order, rc);
    mu_assert(rc == 0, msg);

    for(i = 0; i < order; i++)
    {
        sprintf(msg, "(order %d, weight %d) expected %.15f got %.15f", order,
                i, o3weights[i], weights[i]);
        mu_assert_almost_equals(weights[i], o3weights[i], prec, msg);
    }

    /* Test order 4 */
    order = 4;

    rc = glq_weights(order, o4roots, weights);

    sprintf(msg, "(order %d) return code %d, expected 0", order, rc);
    mu_assert(rc == 0, msg);

    for(i = 0; i < order; i++)
    {
        sprintf(msg, "(order %d, weight %d) expected %.15f got %.15f", order,
                i, o4weights[i], weights[i]);
        mu_assert_almost_equals(weights[i], o4weights[i], prec, msg);
    }

    /* Test order 5 */
    order = 5;

    rc = glq_weights(order, o5roots, weights);

    sprintf(msg, "(order %d) return code %d, expected 0", order, rc);
    mu_assert(rc == 0, msg);

    for(i = 0; i < order; i++)
    {
        sprintf(msg, "(order %d, weight %d) expected %.15f got %.15f", order,
                i, o5weights[i], weights[i]);
        mu_assert_almost_equals(weights[i], o5weights[i], prec, msg);
    }

    return 0;
}


static char * test_glq_nodes()
{
    double prec = pow(10, -9), nodes[19];
    int rc, i, order;

    /* Test order 2 */
    order = 2;

    rc = glq_nodes(order, nodes);

    sprintf(msg, "(order %d) return code %d, expected 0", order, rc);
    mu_assert(rc == 0, msg);

    for(i = 0; i < order; i++)
    {
        sprintf(msg, "(order %d, node %d) expected %.15f got %.15f", order,
                i, o2roots[i], nodes[i]);
        mu_assert_almost_equals(nodes[i], o2roots[i], prec, msg);
    }

    /* Test order 3 */
    order = 3;

    rc = glq_nodes(order, nodes);

    sprintf(msg, "(order %d) return code %d, expected 0", order, rc);
    mu_assert(rc == 0, msg);

    for(i = 0; i < order; i++)
    {
        sprintf(msg, "(order %d, node %d) expected %.15f got %.15f", order,
                i, o3roots[i], nodes[i]);
        mu_assert_almost_equals(nodes[i], o3roots[i], prec, msg);
    }

    /* Test order 4 */
    order = 4;

    rc = glq_nodes(order, nodes);

    sprintf(msg, "(order %d) return code %d, expected 0", order, rc);
    mu_assert(rc == 0, msg);

    for(i = 0; i < order; i++)
    {
        sprintf(msg, "(order %d, node %d) expected %.15f got %.15f", order,
                i, o4roots[i], nodes[i]);
        mu_assert_almost_equals(nodes[i], o4roots[i], prec, msg);
    }

    /* Test order 5 */
    order = 5;

    rc = glq_nodes(order, nodes);

    sprintf(msg, "(order %d) return code %d, expected 0", order, rc);
    mu_assert(rc == 0, msg);

    for(i = 0; i < order; i++)
    {
        sprintf(msg, "(order %d, node %d) expected %.15f got %.15f", order,
                i, o5roots[i], nodes[i]);
        mu_assert_almost_equals(nodes[i], o5roots[i], prec, msg);
    }

    /* Test order 19 */
    order = 19;

    rc = glq_nodes(order, nodes);

    sprintf(msg, "(order %d) return code %d, expected 0", order, rc);
    mu_assert(rc == 0, msg);

    for(i = 0; i < order; i++)
    {
        sprintf(msg, "(order %d, node %d) expected %.15f got %.15f", order,
                i, o19roots[i], nodes[i]);
        mu_assert_almost_equals(nodes[i], o19roots[i], prec, msg);
    }

    return 0;
}


static char * test_glq_set_limits()
{
    double prec = pow(10, -9), unscaled[5], scaled[5], a, b, correct;
    int rc, i;
    GLQ glq;

    glq.nodes_unscaled = unscaled;
    glq.nodes = scaled;

    glq.order = 2;
    a = -2.54;
    b = 14.9;
    mu_arraycp(o2roots, glq.nodes_unscaled, glq.order);

    rc = glq_set_limits(a, b, &glq);
    sprintf(msg, "(order %d, a %g, b %g) return code %d, expected 0", glq.order,
            a, b, rc);
    mu_assert(rc == 0, msg);

    for(i = 0; i < glq.order; i++)
    {
        correct = 8.72*o2roots[i] + 6.18;
        sprintf(msg,
                "(order %d, index %d, a %g, b %g) expected %.15f, got %.15f",
                glq.order, i, a, b, correct, glq.nodes[i]);
        mu_assert_almost_equals(glq.nodes[i], correct, prec, msg);
    }

    glq.order = 3;
    a = 125.6;
    b = 234.84;
    mu_arraycp(o3roots, glq.nodes_unscaled, glq.order);

    rc = glq_set_limits(a, b, &glq);
    sprintf(msg, "(order %d, a %g, b %g) return code %d, expected 0", glq.order,
            a, b, rc);
    mu_assert(rc == 0, msg);

    for(i = 0; i < glq.order; i++)
    {
        correct = 54.62*o3roots[i] + 180.22;
        sprintf(msg,
                "(order %d, index %d, a %g, b %g) expected %.15f, got %.15f",
                glq.order, i, a, b, correct, glq.nodes[i]);
        mu_assert_almost_equals(glq.nodes[i], correct, prec, msg);
    }

    glq.order = 4;
    a = 3.5;
    b = -12.4;
    mu_arraycp(o4roots, glq.nodes_unscaled, glq.order);

    rc = glq_set_limits(a, b, &glq);
    sprintf(msg, "(order %d, a %g, b %g) return code %d, expected 0", glq.order,
            a, b, rc);
    mu_assert(rc == 0, msg);

    for(i = 0; i < glq.order; i++)
    {
        correct = -7.95*o4roots[i] - 4.45;
        sprintf(msg,
                "(order %d, index %d, a %g, b %g) expected %.15f, got %.15f",
                glq.order, i, a, b, correct, glq.nodes[i]);
        mu_assert_almost_equals(glq.nodes[i], correct, prec, msg);
    }

    glq.order = 5;
    a = 0.0;
    b = 0.0;
    mu_arraycp(o5roots, glq.nodes_unscaled, glq.order);

    rc = glq_set_limits(a, b, &glq);
    sprintf(msg, "(order %d, a %g, b %g) return code %d, expected 0", glq.order,
            a, b, rc);
    mu_assert(rc == 0, msg);

    for(i = 0; i < glq.order; i++)
    {
        correct = 0.0;
        sprintf(msg,
                "(order %d, index %d, a %g, b %g) expected %.15f, got %.15f",
                glq.order, i, a, b, correct, glq.nodes[i]);
        mu_assert_almost_equals(glq.nodes[i], correct, prec, msg);
    }

    return 0;
}


static char * test_glq_intcos()
{
    double result, expected;
    double angles[6];
    int i, t, orders[6] = {2, 3, 5, 8, 15, 25};
    GLQ *glq;

    angles[0] = PI*0.1;
    angles[1] = PI;
    angles[2] = PI*1.2;
    angles[3] = PI*1.9;
    angles[4] = PI*4.3;
    angles[5] = PI*6.9;

    for(t = 0; t < 6; t++)
    {
        glq = glq_new(orders[t], 0., angles[t]);

        if(glq == NULL)
        {
            sprintf(msg,
                "(order %d, angle %g) failed to create new GLQ struct",
                orders[t], angles[t]);
            mu_assert(0, msg);
        }

        for(i = 0, result = 0; i < orders[t]; i++)
        {
            result += glq->weights[i]*cos(glq->nodes[i]);
        }
        result *= 0.5*angles[t];

        expected = sin(angles[t]);

        glq_free(glq);

        sprintf(msg, "(order %d, angle %g) expected %f, got %f", orders[t],
                angles[t], expected, result);
        mu_assert_almost_equals(result, expected, pow(10, -5), msg);
    }

    return 0;
}

static char * test_glq_sincos()
{
    GLQ *glq;
    int i;
    double result, d2r = PI/180.;

    glq = glq_new(10, 0, 90);
    glq_precompute_sincos(glq);

    for(i = 0; i < glq->order; i++)
    {
        result = sin(d2r*glq->nodes[i]);
        sprintf(msg, "expected sin(%g)=%g, got %g", glq->nodes[i], result,
               glq->nodes_sin[i]);
        mu_assert_almost_equals(result, glq->nodes_sin[i], pow(10, -15), msg);
        result = cos(d2r*glq->nodes[i]);
        sprintf(msg, "expected cos(%g)=%g, got %g", glq->nodes[i], result,
               glq->nodes_cos[i]);
        mu_assert_almost_equals(result, glq->nodes_cos[i], pow(10, -15), msg);
    }
    return 0;
}


int glq_run_all()
{
    int failed = 0;
    failed += mu_run_test(test_glq_next_root_fail,
                "glq_next_root returns correct fail code");
    failed += mu_run_test(test_glq_next_root, "glq_next_root produces correct results");
    failed += mu_run_test(test_glq_nodes, "glq_nodes produces correct results");
    failed += mu_run_test(test_glq_set_limits,
                "glq_set_limits produces correct results");
    failed += mu_run_test(test_glq_weights, "glq_weights produces correct results");
    failed += mu_run_test(test_glq_intcos,
                "glq cossine integration produces correct results");
    failed += mu_run_test(test_glq_sincos,
                "glq precomputes sin and cos correctly");
    return failed;
}
