/*
 * File:            MinkowskiDistanceMetricTest.java
 * Authors:         Justin Basilico
 * Project:         Cognitive Foundry
 * 
 * Copyright 2011 Cognitive Foundry. All rights reserved.
 */

package gov.sandia.cognition.learning.function.distance;

import gov.sandia.cognition.math.matrix.Vector;
import gov.sandia.cognition.math.matrix.VectorFactory;
import gov.sandia.cognition.math.matrix.Vectorizable;
import gov.sandia.cognition.math.matrix.mtj.Vector2;

/**
 * Unit tests for class MinkowskiDistanceMetric.
 *
 * @author  Justin Basilico
 * @since   3.3.3
 */
public class MinkowskiDistanceMetricTest
    extends MetricTestHarness<Vectorizable>
{

    /**
     * Creates a new test.
     *
     * @param   testName
     *      The test name.
     */
    public MinkowskiDistanceMetricTest(
        final String testName)
    {
        super(testName);
    }

    /**
     * Test of constructors method, of class MinkowskiDistanceMetric.
     */
    public void testConstructors()
    {

        double power = MinkowskiDistanceMetric.DEFAULT_POWER;
        MinkowskiDistanceMetric instance = new MinkowskiDistanceMetric();
        assertEquals(power, instance.getPower(), 0.0);

        power *= RANDOM.nextDouble();
        instance = new MinkowskiDistanceMetric(power);
        assertEquals(power, instance.getPower(), 0.0);
    }

    /**
     * Test of evaluate method, of class MinkowskiDistanceMetric.
     */
    public void testEvaluate()
    {
        double epsilon = 1e-10;
        int d = 10;
        Vector x = VectorFactory.getDefault().createUniformRandom(d, -100, +100, RANDOM);
        Vector y = VectorFactory.getDefault().createUniformRandom(d, -100, +100, RANDOM);

        MinkowskiDistanceMetric instance = new MinkowskiDistanceMetric();
        instance.setPower(1.0);
        assertEquals(x.minus(y).norm1(), instance.evaluate(x, y), epsilon);
        assertEquals(x.minus(y).norm1(), instance.evaluate(y, x), epsilon);
        assertEquals(0.0, instance.evaluate(x, x), epsilon);
        assertEquals(0.0, instance.evaluate(y, y), epsilon);
        
        instance.setPower(2.0);
        assertEquals(x.minus(y).norm2(), instance.evaluate(x, y), epsilon);
        assertEquals(x.minus(y).norm2(), instance.evaluate(y, x), epsilon);
        assertEquals(0.0, instance.evaluate(x, x), epsilon);
        assertEquals(0.0, instance.evaluate(y, y), epsilon);
    }

    /**
     * Test of getPower method, of class MinkowskiDistanceMetric.
     */
    public void testGetPower()
    {
        this.testSetPower();
    }

    /**
     * Test of setPower method, of class MinkowskiDistanceMetric.
     */
    public void testSetPower()
    {
        double power = MinkowskiDistanceMetric.DEFAULT_POWER;
        MinkowskiDistanceMetric instance = new MinkowskiDistanceMetric();
        assertEquals(power, instance.getPower(), 0.0);

        double[] goodValues = {0.001, 0.2, 1.0, 10.0, 123.0, RANDOM.nextDouble()};
        for (double goodValue : goodValues)
        {
            power = goodValue;
            instance.setPower(power);
            assertEquals(power, instance.getPower(), 0.0);
        }

        double[] badValues = {0.0, -0.2, -1.0, -RANDOM.nextDouble()};
        for (double badValue : badValues)
        {
            boolean exceptionThrown = false;
            try
            {
                instance.setPower(badValue);
            }
            catch (IllegalArgumentException e)
            {
                exceptionThrown = true;
            }
            finally
            {
                assertTrue(exceptionThrown);
            }
            assertEquals(power, instance.getPower(), 0.0);
        }
    }

    @Override
    public MinkowskiDistanceMetric createInstance()
    {
        return new MinkowskiDistanceMetric(RANDOM.nextDouble() * 10 + 1.0);
    }

    @Override
    public Vector generateRandomFirstType()
    {
        return VectorFactory.getDefault().createUniformRandom(5,-10.0,5.0, RANDOM);
    }

    @Override
    public void testKnownValues()
    {
        System.out.println( "Known Values" );
        MinkowskiDistanceMetric instance = new MinkowskiDistanceMetric(2.0);

        // Create four points to compute the distances between.
        Vector2 v00 = new Vector2(0.0, 0.0);
        Vector2 v01 = new Vector2(0.0, 1.0);
        Vector2 v10 = new Vector2(1.0, 0.0);
        Vector2 v11 = new Vector2(1.0, 1.0);

        // Make sure the distance to self is zero.
        assertEquals(0.0, instance.evaluate(v00, v00));
        assertEquals(0.0, instance.evaluate(v01, v01));
        assertEquals(0.0, instance.evaluate(v10, v10));
        assertEquals(0.0, instance.evaluate(v11, v11));

        // Make sure the distances between points are correct.
        assertEquals(1.0, instance.evaluate(v00, v01));
        assertEquals(1.0, instance.evaluate(v00, v10));
        assertEquals(1.0, instance.evaluate(v01, v00));
        assertEquals(1.0, instance.evaluate(v01, v11));
        assertEquals(Math.sqrt(2), instance.evaluate(v00, v11));

        // Make sure that it works with negative points.
        assertEquals(1.0, instance.evaluate(v00, v01.scale(-1.0)));
        assertEquals(1.0, instance.evaluate(v00, v10.scale(-1.0)));
        assertEquals(Math.sqrt(2), instance.evaluate(v00, v11.scale(-1.0)));

        // Test examples of manhattan distance by setting power to 2.0.
        instance.setPower(1.0);


        assertEquals(0.0, instance.evaluate(v00, v00));
        assertEquals(0.0, instance.evaluate(v01, v01));
        assertEquals(0.0, instance.evaluate(v10, v10));
        assertEquals(0.0, instance.evaluate(v11, v11));

        // Make sure the distances between points are correct.
        assertEquals(1.0, instance.evaluate(v00, v01));
        assertEquals(1.0, instance.evaluate(v00, v10));
        assertEquals(1.0, instance.evaluate(v01, v00));
        assertEquals(1.0, instance.evaluate(v01, v11));
        assertEquals(2.0, instance.evaluate(v00, v11));

        // Make sure that it works with negative points.
        assertEquals(1.0, instance.evaluate(v00, v01.scale(-1.0)));
        assertEquals(1.0, instance.evaluate(v00, v10.scale(-1.0)));
        assertEquals(2.0, instance.evaluate(v00, v11.scale(-1.0)));

        for (int i = 0; i < NUM_SAMPLES; i++)
        {
            Vector x = this.generateRandomFirstType();
            Vector y = this.generateRandomFirstType();

            double expected = x.minus(y).norm1();
            double result = instance.evaluate(x, y);
            assertEquals(expected, result);
            assertTrue(result >= 0.0);
            assertEquals(0.0, instance.evaluate(x, x), TOLERANCE);
            assertEquals(0.0, instance.evaluate(y, y), TOLERANCE);
        }
    }

}