/*******************************************************************************
*   Copyright 2014 Analog Devices, Inc.
*
*   Licensed under the Apache License, Version 2.0 (the "License");
*   you may not use this file except in compliance with the License.
*   You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
*   Unless required by applicable law or agreed to in writing, software
*   distributed under the License is distributed on an "AS IS" BASIS,
*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*   See the License for the specific language governing permissions and
*   limitations under the License.
********************************************************************************/

package com.analog.lyric.dimple.test.FactorFunctions;

import java.util.Arrays;
import java.util.Random;

import org.junit.Test;

import com.analog.lyric.dimple.factorfunctions.MatrixVectorProduct;
import com.analog.lyric.dimple.model.domains.Domain;
import com.analog.lyric.dimple.model.domains.RealDomain;
import com.analog.lyric.dimple.model.domains.RealJointDomain;
import com.analog.lyric.dimple.model.values.Value;

public class TestMatrixVectorProduct extends FactorFunctionTester
{

	private final Random _rand = new Random(123123);
	
	@Test
	public void test()
	{
		for (int inLength = 2; inLength < 4; ++inLength)
		{
			for (int outLength = 2; outLength < 4; ++outLength)
			{
				testDeterministic(inLength, outLength);
			}
		}
	}
	
	private void testDeterministic(int inLength, int outLength)
	{
		final int nCases = 5;
		final MatrixVectorProduct function = new MatrixVectorProduct(inLength, outLength);
		
		final RealDomain rd = RealDomain.unbounded();
		final RealJointDomain rd2 = RealJointDomain.create(2);
		
		final int[] outputIndices = new int[outLength];
		for (int i = 0; i < outLength; ++i)
		{
			outputIndices[i] = i;
		}
		
		final double[][][] matrices = new double[nCases][][];
		final double[][] inVectors = new double[nCases][];
		final double[][] outVectors = new double[nCases][];
		
		for (int i = 0; i < nCases; ++i)
		{
			double[][] matrix = matrices[i] = new double[outLength][];
			for (int row = 0; row < outLength; ++row)
			{
				double[] rowValues = matrix[row] = new double[inLength];
				for (int col = 0; col < inLength; ++col)
				{
					rowValues[col] = 1 + (_rand.nextDouble() * 10);
				}
			}
	
			double[] inVector = inVectors[i] = new double[inLength];
			for (int col = 0; col < inLength; ++col)
			{
				inVector[col] = 1 + (_rand.nextDouble() * 10);
			}
		
			double[] outVector = outVectors[i] = new double[outLength];
			for (int row = 0; row < outLength; ++row)
			{
				for (int col = 0; col < inLength; ++col)
				{
					outVector[row] += matrix[row][col] * inVector[col];
				}
			}
		}
		
		//
		// Constant matrix and input vector
		//
		
		Domain[] domains = new Domain[2 + outLength];
		Arrays.fill(domains, rd);
		domains[outLength] = null; // matrix
		domains[outLength + 1] = rd2;
		
		Value[][] testCases = new Value[nCases][];
		for (int i = 0; i < nCases; ++i)
		{
			double[] inVector = inVectors[i];
			double[][] matrix = matrices[i];
			double[] outVector = outVectors[i];
			
			Value[] testCase = testCases[i] = new Value[2 + outLength];
			for (int j = 0; j < outLength; ++j)
			{
				testCase[j] = Value.create(outVector[j]);
			}
			testCase[outLength] = Value.create(matrix);
			testCase[outLength + 1] = Value.create(inVector.clone());
		}
		
		testEvalDeterministic(function, domains, outputIndices, testCases);
		
		//
		// Constant matrix, flattened variable vector
		//
		
		domains = new Domain[1 + outLength + inLength];
		Arrays.fill(domains, rd);
		domains[outLength] = null; // matrix
		
		for (int i = 0; i < nCases; ++i)
		{
			double[] inVector = inVectors[i];
			double[][] matrix = matrices[i];
			double[] outVector = outVectors[i];
			
			Value[] testCase = testCases[i] = new Value[domains.length];
			for (int j = 0; j < outLength; ++j)
			{
				testCase[j] = Value.create(outVector[j]);
			}
			testCase[outLength] = Value.create(matrix);
			for (int j = 0; j < inLength; ++j)
			{
				testCase[outLength + 1 + j] = Value.create(inVector[j]);
			}
		}
		
		testEvalDeterministic(function, domains, outputIndices, testCases);
		
		//
		// Constant input vector, flattened variable matrix
		//
		
		domains = new Domain[1 + outLength + inLength * outLength];
		Arrays.fill(domains, rd);
		domains[domains.length - 1] = rd2;

		for (int i = 0; i < nCases; ++i)
		{
			double[] inVector = inVectors[i];
			double[][] matrix = matrices[i];
			double[] outVector = outVectors[i];

			Value[] testCase = testCases[i] = new Value[domains.length];
			for (int j = 0; j < outLength; ++j)
			{
				testCase[j] = Value.create(outVector[j]);
			}
			int j = outLength;
			for (int col = 0; col < inLength; ++col)
			{
				for (int row = 0; row < outLength; ++row)
				{
					testCase[j++] = Value.create(matrix[row][col]);
				}
			}
			testCase[domains.length - 1] = Value.create(inVector);
		}

		testEvalDeterministic(function, domains, outputIndices, testCases);
		
		//
		// Flattened variable matrix and input vector
		//
		
		domains = new Domain[outLength + inLength + inLength * outLength];
		Arrays.fill(domains, rd);
		int inVectorOffset = domains.length - inLength;

		for (int i = 0; i < nCases; ++i)
		{
			double[] inVector = inVectors[i];
			double[][] matrix = matrices[i];
			double[] outVector = outVectors[i];

			Value[] testCase = testCases[i] = new Value[domains.length];
			for (int j = 0; j < outLength; ++j)
			{
				testCase[j] = Value.create(outVector[j]);
			}
			int j = outLength;
			for (int col = 0; col < inLength; ++col)
			{
				for (int row = 0; row < outLength; ++row)
				{
					testCase[j++] = Value.create(matrix[row][col]);
				}
			}
			for (j = 0; j < inLength; ++j)
			{
				testCase[inVectorOffset + j] = Value.create(inVector[j]);
			}
		}

		testEvalDeterministic(function, domains, outputIndices, testCases);
		
		// TODO: test evalEnergy for non-deterministic cases w/ smoothing
	}

}
