/*
 * @file
 * @author University of Warwick
 * @version 1.0
 *
 * @section LICENSE
 *
 * @section DESCRIPTION
 *
 * Serial Unit Tests for the LinearSolverPETSc Class
 */

#define BOOST_TEST_MODULE LinearSolverPETScSerial
#include <boost/test/unit_test.hpp>
#include <boost/test/output_test_stream.hpp>
#include <stdexcept>

#include "LinearSolverPETScSerial.h"
#include "Error.h"
#include "SparseMatrixCOO.h"
#include <iostream>

using namespace cupcfd::linearsolvers;

// Setup
BOOST_AUTO_TEST_CASE(setup)
{
    int argc = boost::unit_test::framework::master_test_suite().argc;
    char ** argv = boost::unit_test::framework::master_test_suite().argv;
	PetscInitialize(&argc, &argv, NULL, NULL);
}

// ============== Constructors ===================
// Test 1: Check values are probably defaulted to their null states
BOOST_AUTO_TEST_CASE(constructor_test1)
{
	LinearSolverPETScSerial<double> solver;

	// Workaround - seems OK to compare these in C++,
	// but BOOST doesn't like making the comparison
	if(solver.a != PETSC_NULL)
	{
		BOOST_CHECK_EQUAL(true, false);
	}

	// Workaround - seems OK to compare these in C++,
	// but BOOST doesn't like making the comparison
	if(solver.x != PETSC_NULL)
	{
		BOOST_CHECK_EQUAL(true, false);
	}

	// Workaround - seems OK to compare these in C++,
	// but BOOST doesn't like making the comparison
	if(solver.b != PETSC_NULL)
	{
		BOOST_CHECK_EQUAL(true, false);
	}

	BOOST_CHECK_EQUAL(solver.mLocal, 0);
	BOOST_CHECK_EQUAL(solver.nLocal, 0);
	BOOST_CHECK_EQUAL(solver.mGlobal, 0);
	BOOST_CHECK_EQUAL(solver.nGlobal, 0);

	BOOST_CHECK_EQUAL(solver.xRanges, static_cast<decltype(solver.xRanges)>(nullptr));
	BOOST_CHECK_EQUAL(solver.bRanges, static_cast<decltype(solver.bRanges)>(nullptr));
	BOOST_CHECK_EQUAL(solver.aRanges, static_cast<decltype(solver.aRanges)>(nullptr));
}

// ============== setupVectorX ===================
// Test 1: Check Vector is created if a suitable row size is set
BOOST_AUTO_TEST_CASE(setupVectorX_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// Manually set row size so it is valid
	solver.mLocal = 20;
	solver.mGlobal = 20;

	// Test and Check
	status = solver.setupVectorX();

	// Workaround - seems OK to compare these in C++,
	// but BOOST doesn't like making the comparison
	if(solver.x == PETSC_NULL)
	{
		BOOST_CHECK_EQUAL(true, false);
	}

	PetscInt cmp[2] = {0, 20};
	BOOST_CHECK_EQUAL_COLLECTIONS(cmp, cmp + 2, solver.xRanges, solver.xRanges + 2);

	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
}

// Test 2: Check error if a suitable row size is not set - e.g. 0
BOOST_AUTO_TEST_CASE(setupVectorX_test2)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// Manually set row size so it is valid
	solver.mLocal = 0;
	solver.mGlobal = 0;

	// Test and Check
	status = solver.setupVectorX();

	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_ROW_SIZE_UNSET);
}

// Test 3: Check error if the communicator is not set to PETSC_COMM_SELF
// ToDo
BOOST_AUTO_TEST_CASE(setupVectorX_test3)
{

}

// ============== resetVectorX ===================
// Test 1: Test the successful reset of a existing vector
BOOST_AUTO_TEST_CASE(resetVectorX_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	//Setup

	// Manually set row size so it is valid
	solver.mLocal = 20;
	solver.mGlobal = 20;

	// Create the vector
	status = solver.setupVectorX();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Test and Check
	status = solver.resetVectorX();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	if(solver.x != PETSC_NULL)
	{
		BOOST_CHECK_EQUAL(true, false);
	}

	BOOST_CHECK_EQUAL(solver.xRanges, static_cast<decltype(solver.xRanges)>(nullptr));
}

// Test 2: Test the successful reset of a non-existing vector (i.e. no
// change, but no error either).
BOOST_AUTO_TEST_CASE(resetVectorX_test2)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Check it is unset for the test
	if(solver.x != PETSC_NULL)
	{
		BOOST_CHECK_EQUAL(true, false);
	}

	// === Test and Check ===
	status = solver.resetVectorX();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	if(solver.x != PETSC_NULL)
	{
		BOOST_CHECK_EQUAL(true, false);
	}
}

// ============== setupVectorB ===================
// Test 1: Check Vector is created if a suitable row size is set
BOOST_AUTO_TEST_CASE(setupVectorB_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// Manually set row size so it is valid
	solver.mLocal = 20;
	solver.mGlobal = 20;

	// Test and Check
	status = solver.setupVectorB();

	// Workaround - seems OK to compare these in C++,
	// but BOOST doesn't like making the comparison
	if(solver.b == PETSC_NULL)
	{
		BOOST_CHECK_EQUAL(true, false);
	}

	BOOST_CHECK_EQUAL(solver.bRanges, static_cast<decltype(solver.bRanges)>(nullptr));

	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
}

// Test 2: Check error if a suitable row size is not set - e.g. 0
BOOST_AUTO_TEST_CASE(setupVectorB_test2)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// Manually set row size so it is valid
	solver.mLocal = 0;
	solver.mGlobal = 0;

	// Test and Check
	status = solver.setupVectorB();

	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_ROW_SIZE_UNSET);
}

// Test 3: Check error if the correct communicator is not set to PETSC_COMM_SELF
// ToDo
BOOST_AUTO_TEST_CASE(setupVectorB_test3)
{

}

// ============== resetVectorB ===================
// Test 1: Test the successful reset of a existing vector
BOOST_AUTO_TEST_CASE(resetVectorB_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	//Setup

	// Manually set row size so it is valid
	solver.mLocal = 20;
	solver.mGlobal = 20;

	// Create the vector
	status = solver.setupVectorB();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Test and Check
	status = solver.resetVectorB();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	if(solver.b != PETSC_NULL)
	{
		BOOST_CHECK_EQUAL(true, false);
	}

	BOOST_CHECK_EQUAL(solver.bRanges, static_cast<decltype(solver.bRanges)>(nullptr));
}

// Test 2: Test the successful reset of a non-existing vector (i.e. no
// change, but no error either).
BOOST_AUTO_TEST_CASE(resetVectorB_test2)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Check it is unset for the test
	if(solver.b != PETSC_NULL)
	{
		BOOST_CHECK_EQUAL(true, false);
	}

	// === Test and Check ===
	status = solver.resetVectorB();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	if(solver.b != PETSC_NULL)
	{
		BOOST_CHECK_EQUAL(true, false);
	}
}

// ============== setupMatrixA ===================
// Test 1: Test setup of PETSc matrix from a SparseCOO matrix
BOOST_AUTO_TEST_CASE(setupMatrixA_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===
	// Create a simple SparseMatrix
	cupcfd::data_structures::SparseMatrixCOO<int, double> matrix(8, 8, 0);

	int rows[13] = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7};
	int cols[13] = {0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7};
	double vals[13] = {0.1, 0.2, 0.1, 0.05, 0.07, 0.09, 0.06, 0.1, 0.15, 0.23, 0.11, 0.13, 0.09};

	for(int i = 0; i < 13; i++)
	{
		matrix.setElement(rows[i], cols[i], vals[i]);
	}

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Test and Check
	status = solver.setupMatrixA(matrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	PetscInt cmp[2] = {0, 8};
	BOOST_CHECK_EQUAL_COLLECTIONS(cmp, cmp + 2, solver.aRanges, solver.aRanges + 2);

	// ToDo: Ideally would like to check PETSc internal nnz structure of matrix
}

// ============== resetMatrixA ===================
//Test 1: Check the matrix is correctly reset after being setup
BOOST_AUTO_TEST_CASE(resetMatrixA_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===
	// Create a simple SparseMatrix
	cupcfd::data_structures::matrices::SparseMatrixCOO<int, double> matrix(8, 8, 0);

	int rows[13] {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7};
	int cols[13] = {0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7};
	double vals[13] = {0.1, 0.2, 0.1, 0.05, 0.07, 0.09, 0.06, 0.1, 0.15, 0.23, 0.11, 0.13, 0.09};

	for(int i = 0; i < 13; i++)
	{
		matrix.setElement(rows[i], cols[i], vals[i]);
	}

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Setup Matrix
	status = solver.setupMatrixA(matrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Test and Check Reset
	status = solver.resetMatrixA();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Check range array has been reset
	BOOST_CHECK_EQUAL(solver.aRanges, static_cast<decltype(solver.aRanges)>(nullptr));
}

// Test 2: Check the matrix reset function does not error when
// no matrix is yet setup
BOOST_AUTO_TEST_CASE(resetMatrixA_test2)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// Test and Check Reset
	status = solver.resetMatrixA();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
}

// ToDo
// Test 3: There should be an error code for PETSc errors
BOOST_AUTO_TEST_CASE(resetMatrixA_test3)
{

}

// ============== setup ===========
// Test 1: Test the successful setup of the object from a matrix
BOOST_AUTO_TEST_CASE(setup_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===
	// Create a simple SparseMatrix
	cupcfd::data_structures::matrices::SparseMatrixCOO<int, double> matrix(8, 8, 0);

	int rows[13] = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7};
	int cols[13] = {0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7};
	double vals[13] = {0.1, 0.2, 0.1, 0.05, 0.07, 0.09, 0.06, 0.1, 0.15, 0.23, 0.11, 0.13, 0.09};

	for(int i = 0; i < 13; i++)
	{
		matrix.setElement(rows[i], cols[i], vals[i]);
	}

	status = solver.setup(matrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
}

// ============== reset ===========
// Test 1: Test a successful reset after the object has been setup
BOOST_AUTO_TEST_CASE(reset_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===
	// Create a simple SparseMatrix
	cupcfd::data_structures::matrices::SparseMatrixCOO<int, double> matrix(8, 8, 0);

	int rows[13] = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7};
	int cols[13] = {0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7};
	double vals[13] = {0.1, 0.2, 0.1, 0.05, 0.07, 0.09, 0.06, 0.1, 0.15, 0.23, 0.11, 0.13, 0.09};

	for(int i = 0; i < 13; i++)
	{
		matrix.setElement(rows[i], cols[i], vals[i]);
	}

	status = solver.setup(matrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	status = solver.reset();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
}

// Test 2: Test a successful reset if the object is still in an unset state
BOOST_AUTO_TEST_CASE(reset_test2)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	status = solver.reset();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
}

// ============== setValuesVectorX(T scalar) ===========
// Test 1: Test setting the entire vector to a specific scalar
// Ideally want to check value using getter, but will
// use PETSc function directly to keep function tests separate
BOOST_AUTO_TEST_CASE(setValuesVectorX_scalar_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorX();

	// Test and Check
	status = solver.setValuesVectorX(2.4);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Any position should hold the value 2.4
	PetscInt pos = 5;
	PetscScalar result;
	VecGetValues(solver.x, 1, &pos, &result);
	BOOST_CHECK_EQUAL(result, 2.4);

	// Second check in different position to ensure more than one was updated
	pos = 7;
	result = 0.0;
	VecGetValues(solver.x, 1, &pos, &result);
	BOOST_CHECK_EQUAL(result, 2.4);
}

// Test 2: Check for error when vector does not exist
BOOST_AUTO_TEST_CASE(setValuesVectorX_scalar_test2)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// Test and Check
	status = solver.setValuesVectorX(2.4);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_INVALID_VECTOR);
}

// ============== setValuesVectorX(T * scalars, int nScalars) ===========
// Test 1: Test setting values at specific locations
BOOST_AUTO_TEST_CASE(setValuesVectorX_indexedscalars_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorX();

	// Test and Check
	int indices[3] = {0, 3, 7};
	double values[3] = {0.5, 0.7, 0.3};
	status = solver.setValuesVectorX(values, 3, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Any position should hold the value 2.4
	PetscInt pos[8] = {0, 1, 2, 3, 4, 5, 6, 7};
	PetscScalar result[8];
	VecGetValues(solver.x, 8, pos, result);

	PetscScalar cmp[8] = {0.5, 0.0, 0.0, 0.7, 0.0, 0.0, 0.0, 0.3};
	BOOST_CHECK_EQUAL_COLLECTIONS(cmp, cmp + 8, result, result + 8);
}

// Test 2: Check Error for arrays of mismatched sizes
BOOST_AUTO_TEST_CASE(setValuesVectorX_indexedscalars_test2)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorX();

	// Test and Check
	int indices[3] = {0, 3, 7};
	double values[2] = {0.5, 0.7};
	status = solver.setValuesVectorX(values, 2, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_ARRAY_MISMATCH_SIZE);
}

// Test 3: Check error for vector X not being setup
BOOST_AUTO_TEST_CASE(setValuesVectorX_indexedscalars_test3)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Test and Check
	int indices[3] = {0, 3, 7};
	double values[3] = {0.5, 0.7, 0.3};
	status = solver.setValuesVectorX(values, 3, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_INVALID_VECTOR);
}

// Test 4: Check error for unset row size
BOOST_AUTO_TEST_CASE(setValuesVectorX_indexedscalars_test4)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	int indices[3] = {0, 3, 7};
	double values[3] = {0.5, 0.7, 0.3};
	status = solver.setValuesVectorX(values, 3, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_ROW_SIZE_UNSET);
}

// Test 5: Check error for an index lower than minimum of global range
BOOST_AUTO_TEST_CASE(setValuesVectorX_indexedscalars_test5)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorX();

	// Test and Check
	int indices[3] = {-1, 3, 7};
	double values[3] = {0.5, 0.7, 0.3};
	status = solver.setValuesVectorX(values, 3, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_INVALID_INDEX);
}

// Test 6: Check error for an index higher than maximum of global range
BOOST_AUTO_TEST_CASE(setValuesVectorX_indexedscalars_test6)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorX();

	// Test and Check
	int indices[3] = {0, 3, 8};
	double values[3] = {0.5, 0.7, 0.3};
	status = solver.setValuesVectorX(values, 3, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_INVALID_INDEX);
}

// ============== setValuesVectorB(T scalar) ===========
// Test 1: Test setting the entire vector to a specific scalar
// Ideally want to check value using getter, but will
// use PETSc function directly to keep function tests separate
BOOST_AUTO_TEST_CASE(setValuesVectorB_scalar_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorB();

	// Test and Check
	status = solver.setValuesVectorB(2.4);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Any position should hold the value 2.4
	PetscInt pos = 5;
	PetscScalar result;
	VecGetValues(solver.b, 1, &pos, &result);
	BOOST_CHECK_EQUAL(result, 2.4);

	// Second check in different position to ensure more than one was updated
	pos = 7;
	result = 0.0;
	VecGetValues(solver.b, 1, &pos, &result);
	BOOST_CHECK_EQUAL(result, 2.4);
}

// Test 2: Check for error when vector does not exist
BOOST_AUTO_TEST_CASE(setValuesVectorB_scalar_test2)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// Test and Check
	status = solver.setValuesVectorB(2.4);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_INVALID_VECTOR);
}

// ============== setValuesVectorB(T * scalars, int nScalars) ===========
// Test 1: Test setting values at specific locations
BOOST_AUTO_TEST_CASE(setValuesVectorB_indexedscalars_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorB();

	// Test and Check
	int indices[3] = {0, 3, 7};
	double values[3] = {0.5, 0.7, 0.3};
	status = solver.setValuesVectorB(values, 3, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Any position should hold the value 2.4
	PetscInt pos[8] = {0, 1, 2, 3, 4, 5, 6, 7};
	PetscScalar result[8];
	VecGetValues(solver.b, 8, pos, result);

	PetscScalar cmp[8] = {0.5, 0.0, 0.0, 0.7, 0.0, 0.0, 0.0, 0.3};
	BOOST_CHECK_EQUAL_COLLECTIONS(cmp, cmp + 8, result, result + 8);
}

// Test 2: Check Error for arrays of mismatched sizes
BOOST_AUTO_TEST_CASE(setValuesVectorB_indexedscalars_test2)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorB();

	// Test and Check
	int indices[3] = {0, 3, 7};
	double values[2] = {0.5, 0.7};
	status = solver.setValuesVectorB(values, 2, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_ARRAY_MISMATCH_SIZE);
}

// Test 3: Check error for vector B not being setup
BOOST_AUTO_TEST_CASE(setValuesVectorB_indexedscalars_test3)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Test and Check
	int indices[3] = {0, 3, 7};
	double values[3] = {0.5, 0.7, 0.3};
	status = solver.setValuesVectorB(values, 3, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_INVALID_VECTOR);
}

// Test 4: Check error for unset row size
BOOST_AUTO_TEST_CASE(setValuesVectorB_indexedscalars_test4)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	int indices[3] = {0, 3, 7};
	double values[3] = {0.5, 0.7, 0.3};
	status = solver.setValuesVectorB(values, 3, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_ROW_SIZE_UNSET);
}

// Test 5: Check error for an index lower than minimum of global range
BOOST_AUTO_TEST_CASE(setValuesVectorB_indexedscalars_test5)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorB();

	// Test and Check
	int indices[3] = {-1, 3, 7};
	double values[3] = {0.5, 0.7, 0.3};
	status = solver.setValuesVectorB(values, 3, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_INVALID_INDEX);
}

// Test 6: Check error for an index higher than maximum of global range
BOOST_AUTO_TEST_CASE(setValuesVectorB_indexedscalars_test6)
{
	cupcfd::error::eCodes status;
	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorB();

	// Test and Check
	int indices[3] = {0, 3, 8};
	double values[3] = {0.5, 0.7, 0.3};
	status = solver.setValuesVectorB(values, 3, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_INVALID_INDEX);
}

// ============== setValuesMatrixA ===========
// Test 1: Successfully set the matrix values from a Sparse Matrix
BOOST_AUTO_TEST_CASE(setValuesMatrixA_test1)
{
	cupcfd::error::eCodes status;
	LinearSolverPETScSerial<double> solver;

	// === Setup ===
	// Create a simple SparseMatrix
	cupcfd::data_structures::matrices::SparseMatrixCOO<int, double> matrix(8, 8, 0);

	int rows[13] = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7};
	int cols[13] = {0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7};
	double vals[13] = {0.1, 0.2, 0.1, 0.05, 0.07, 0.09, 0.06, 0.1, 0.15, 0.23, 0.11, 0.13, 0.09};

	for(int i = 0; i < 13; i++)
	{
		matrix.setElement(rows[i], cols[i], vals[i]);
	}

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Setup the Matrix
	status = solver.setupMatrixA(matrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Test and Check
	status = solver.setValuesMatrixA(matrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Check the values are correct - use PETSc function directly since we haven't tested
	// the get function yet
	PetscInt rowResult = 3;
	PetscInt colResult[2] = {3, 4};
	PetscScalar valResult[2];

	MatGetValues(solver.a, 1, &rowResult, 2, colResult, valResult);
	BOOST_CHECK_EQUAL(valResult[0], 0.06);
	BOOST_CHECK_EQUAL(valResult[1], 0.1);
}

// Test 2: Error: Check the row sizes are set
BOOST_AUTO_TEST_CASE(setValuesMatrixA_test2)
{
	cupcfd::error::eCodes status;
	LinearSolverPETScSerial<double> solver;

	// === Setup ===
	// Create a simple SparseMatrix
	cupcfd::data_structures::matrices::SparseMatrixCOO<int, double> matrix(8, 8, 0);

	int rows[13] = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7};
	int cols[13] = {0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7};
	double vals[13] = {0.1, 0.2, 0.1, 0.05, 0.07, 0.09, 0.06, 0.1, 0.15, 0.23, 0.11, 0.13, 0.09};

	for(int i = 0; i < 13; i++)
	{
		matrix.setElement(rows[i], cols[i], vals[i]);
	}

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.mGlobal = 8;
	solver.nLocal = 8;
	solver.nGlobal = 8;

	// Setup the Matrix
	status = solver.setupMatrixA(matrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Test and Check: Don't use solver setup/set the solver matrix row sizes
	solver.mLocal = 0;
	solver.mGlobal = 0;
	status = solver.setValuesMatrixA(matrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_ROW_SIZE_UNSET);
}

// Test 3: Error: Check the column sizes are set
BOOST_AUTO_TEST_CASE(setValuesMatrixA_test3)
{
	cupcfd::error::eCodes status;
	LinearSolverPETScSerial<double> solver;

	// === Setup ===
	// Create a simple SparseMatrix
	cupcfd::data_structures::matrices::SparseMatrixCOO<int, double> matrix(8, 8, 0);

	int rows[13] = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7};
	int cols[13] = {0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7};
	double vals[13] = {0.1, 0.2, 0.1, 0.05, 0.07, 0.09, 0.06, 0.1, 0.15, 0.23, 0.11, 0.13, 0.09};

	for(int i = 0; i < 13; i++)
	{
		matrix.setElement(rows[i], cols[i], vals[i]);
	}

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.mGlobal = 8;
	solver.nLocal = 8;
	solver.nGlobal = 8;

	// Setup the Matrix
	status = solver.setupMatrixA(matrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Test and Check: Don't use solver setup/set the solver matrix col sizes
	solver.nLocal = 0;
	solver.nGlobal = 0;
	status = solver.setValuesMatrixA(matrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_COL_SIZE_UNSET);
}

// Test 4: Error: Check the Matrix has been setup
BOOST_AUTO_TEST_CASE(setValuesMatrixA_test4)
{
	cupcfd::error::eCodes status;
	LinearSolverPETScSerial<double> solver;

	// === Setup ===
	// Create a simple SparseMatrix
	cupcfd::data_structures::matrices::SparseMatrixCOO<int, double> matrix(8, 8, 0);

	int rows[13] = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7};
	int cols[13] = {0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7};
	double vals[13] = {0.1, 0.2, 0.1, 0.05, 0.07, 0.09, 0.06, 0.1, 0.15, 0.23, 0.11, 0.13, 0.09};

	for(int i = 0; i < 13; i++)
	{
		matrix.setElement(rows[i], cols[i], vals[i]);
	}

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Test and Check: Matrix has not been setup
	status = solver.setValuesMatrixA(matrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_INVALID_MATRIX);
}

// ============== getValuesVectorX ===========
// Test 1: Check all values are the same when
// set to all same value.
BOOST_AUTO_TEST_CASE(getValuesVectorX_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes, else we will get error
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorX();

	// Set the values
	status = solver.setValuesVectorX(2.4);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Test and Check
	double * result;
	int nResult;
	status = solver.getValuesVectorX(&result, &nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	double cmp[8] = {2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4};
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 8, cmp, cmp + 8);

	free(result);
}

// Test 2: Check all values are different when
// set to different values
BOOST_AUTO_TEST_CASE(getValuesVectorX_test2)
{

}

// Test 3: Check error when row size unset
BOOST_AUTO_TEST_CASE(getValuesVectorX_test3)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	double * result;
	int nResult;
	status = solver.getValuesVectorX(&result, &nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_ROW_SIZE_UNSET);

	// Since error, nothing to free
}

// Test 4: Check error when vector not set
BOOST_AUTO_TEST_CASE(getValuesVectorX_test4)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	double * result;
	int nResult;
	status = solver.getValuesVectorX(&result, &nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_INVALID_VECTOR);

	// Since error, nothing to free
}

// ============== getValuesVectorXIndexes ===========
// Test 1: Retrieve specific indexes
BOOST_AUTO_TEST_CASE(getValuesVectorX_indexes_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorX();

	// Setup
	int indices[3] = {0, 3, 7};
	double values[3] = {0.5, 0.7, 0.3};
	status = solver.setValuesVectorX(values, 3, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Test and Check
	int getIndices[4] = {7, 4, 0, 6};
	double * result;
	int nResult;
	status = solver.getValuesVectorX(&result, &nResult, getIndices, 4, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	PetscScalar cmp[4] = {0.3, 0.0, 0.5, 0.0};
	BOOST_CHECK_EQUAL_COLLECTIONS(cmp, cmp + 4, result, result + 4);

	free(result);
}

// Test 2: Error Check for unset global row size
BOOST_AUTO_TEST_CASE(getValuesVectorX_indexes_test2)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Create Vector
	solver.setupVectorX();

	// Test and Check
	int getIndices[4] = {7, 4, 0, 6};
	double * result;
	int nResult;
	status = solver.getValuesVectorX(&result, &nResult, getIndices, 4, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_ROW_SIZE_UNSET);
}

// Test 3: Error Check for unset vector
BOOST_AUTO_TEST_CASE(getValuesVectorX_indexes_test3)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Test and Check
	int getIndices[4] = {7, 4, 0, 6};
	double * result;
	int nResult;
	status = solver.getValuesVectorX(&result, &nResult, getIndices, 4, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_INVALID_VECTOR);
}

// Test 4: Error Check for invalid lower bound index
BOOST_AUTO_TEST_CASE(getValuesVectorX_indexes_test4)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorX();

	// Setup
	int indices[3] = {0, 3, 7};
	double values[3] = {0.5, 0.7, 0.3};
	status = solver.setValuesVectorX(values, 3, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Test and Check
	int getIndices[4] = {-1, 4, 0, 6};
	double * result;
	int nResult;
	status = solver.getValuesVectorX(&result, &nResult, getIndices, 4, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_INVALID_INDEX);
}

// Test 5: Error Check for invalid upper bound index
BOOST_AUTO_TEST_CASE(getValuesVectorX_indexes_test5)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorX();

	// Setup
	int indices[3] = {0, 3, 7};
	double values[3] = {0.5, 0.7, 0.3};
	status = solver.setValuesVectorX(values, 3, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Test and Check
	int getIndices[4] = {0, 4, 0, 8};
	double * result;
	int nResult;
	status = solver.getValuesVectorX(&result, &nResult, getIndices, 4, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_INVALID_INDEX);
}

// ============== getValuesVectorB ===========
// Test 1: Check all values are the same when
// set to all same value.
BOOST_AUTO_TEST_CASE(getValuesVectorB_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes, else we will get error
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorB();

	// Set the values
	status = solver.setValuesVectorB(2.4);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Test and Check
	double * result;
	int nResult;
	status = solver.getValuesVectorB(&result, &nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	double cmp[8] = {2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4};
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 8, cmp, cmp + 8);

	free(result);
}

// Test 2: Check all values are different when
// set to all same value.
BOOST_AUTO_TEST_CASE(getValuesVectorB_test2)
{

}

// Test 3: Check error when row size unset
BOOST_AUTO_TEST_CASE(getValuesVectorB_test3)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	double * result;
	int nResult;
	status = solver.getValuesVectorB(&result, &nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_ROW_SIZE_UNSET);

	// Since error, nothing to free
}

// Test 4: Check error when vector not set
BOOST_AUTO_TEST_CASE(getValuesVectorB_test4)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	double * result;
	int nResult;
	status = solver.getValuesVectorB(&result, &nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_INVALID_VECTOR);

	// Since error, nothing to free
}

// ============== getValuesVectorBIndexes ===========
// Test 1: Retrieve specific indexes
BOOST_AUTO_TEST_CASE(getValuesVectorB_indexes_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorB();

	// Setup
	int indices[3] = {0, 3, 7};
	double values[3] = {0.5, 0.7, 0.3};
	status = solver.setValuesVectorB(values, 3, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Test and Check
	int getIndices[4] = {7, 4, 0, 6};
	double * result;
	int nResult;
	status = solver.getValuesVectorB(&result, &nResult, getIndices, 4, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	PetscScalar cmp[4] = {0.3, 0.0, 0.5, 0.0};
	BOOST_CHECK_EQUAL_COLLECTIONS(cmp, cmp + 4, result, result + 4);

	free(result);
}

// Test 2: Error Check for unset global row size
BOOST_AUTO_TEST_CASE(getValuesVectorB_indexes_test2)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Create Vector
	solver.setupVectorB();

	// Test and Check
	int getIndices[4] = {7, 4, 0, 6};
	double * result;
	int nResult;
	status = solver.getValuesVectorB(&result, &nResult, getIndices, 4, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_ROW_SIZE_UNSET);
}

// Test 3: Error Check for unset vector
BOOST_AUTO_TEST_CASE(getValuesVectorB_indexes_test3)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Test and Check
	int getIndices[4] = {7, 4, 0, 6};
	double * result;
	int nResult;
	status = solver.getValuesVectorB(&result, &nResult, getIndices, 4, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_INVALID_VECTOR);
}

// Test 4: Error Check for invalid lower bound index
BOOST_AUTO_TEST_CASE(getValuesVectorB_indexes_test4)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorB();

	// Setup
	int indices[3] = {0, 3, 7};
	double values[3] = {0.5, 0.7, 0.3};
	status = solver.setValuesVectorB(values, 3, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Test and Check
	int getIndices[4] = {-1, 4, 0, 6};
	double * result;
	int nResult;
	status = solver.getValuesVectorB(&result, &nResult, getIndices, 4, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_INVALID_INDEX);
}

// Test 5: Error Check for invalid upper bound index
BOOST_AUTO_TEST_CASE(getValuesVectorB_indexes_test5)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorB();

	// Setup
	int indices[3] = {0, 3, 7};
	double values[3] = {0.5, 0.7, 0.3};
	status = solver.setValuesVectorB(values, 3, indices, 3, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Test and Check
	int getIndices[4] = {0, 4, 0, 8};
	double * result;
	int nResult;
	status = solver.getValuesVectorB(&result, &nResult, getIndices, 4, 0);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_INVALID_INDEX);
}

// ============== getValuesMatrixA ===========
// Test 1: Check we can retrieve values correctly from the matrix
BOOST_AUTO_TEST_CASE(getValuesMatrixA_test1)
{
	cupcfd::error::eCodes status;
	LinearSolverPETScSerial<double> solver;

	// === Setup ===
	// Create a simple SparseMatrix
	cupcfd::data_structures::matrices::SparseMatrixCOO<int, double> matrix(8, 8, 0);

	int rows[13] =    {0,   0,   1,   1,    2,    2,    3,    3,   4,    4,    5,    6,    7};
	int cols[13] =    {0,   1,   1,   2,    2,    3,    3,    4,   4,    5,    5,    6,    7};
	double vals[13] = {0.1, 0.2, 0.1, 0.05, 0.07, 0.09, 0.06, 0.1, 0.15, 0.23, 0.11, 0.13, 0.09};

	for(int i = 0; i < 13; i++)
	{
		matrix.setElement(rows[i], cols[i], vals[i]);
	}

	cupcfd::data_structures::matrices::SparseMatrixCOO<int, double> resultMatrix(8, 8, 0);

	int resultRows[9] = {0, 1, 2, 2, 4, 4, 5, 6, 6};
	int resultCols[9] = {1, 1, 2, 3, 4, 5, 5, 6, 7};
	double resultVals[9] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};

	for(int i = 0; i < 9; i++)
	{
		resultMatrix.setElement(resultRows[i], resultCols[i], resultVals[i]);
	}

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Setup the Matrix
	status = solver.setupMatrixA(matrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Set the values inside the matrix
	status = solver.setValuesMatrixA(matrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Test and Check
	// Retrieve values via the function and check they are what we expect
	status = solver.getValuesMatrixA(resultMatrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Check contents of SparseMatrixCOO nnz to verify it has been updated correctly
	double cmp[9] = {0.2, 0.1, 0.07, 0.09, 0.15, 0.23, 0.11, 0.13, 0.0};

	BOOST_CHECK_EQUAL_COLLECTIONS(cmp, cmp + 9, &resultMatrix.val[0], &resultMatrix.val[0] + 9);
}

// Not Done
// Test 2: Error Check: Check PETSc matrix is setup
BOOST_AUTO_TEST_CASE(getValuesMatrixA_test2)
{
	cupcfd::error::eCodes status;
	LinearSolverPETScSerial<double> solver;

	// === Setup ===
	// Create a simple SparseMatrix
	cupcfd::data_structures::matrices::SparseMatrixCOO<int, double> resultMatrix(8, 8, 0);

	int resultRows[9] = {0, 1, 2, 2, 4, 4, 5, 6, 6};
	int resultCols[9] = {1, 1, 2, 3, 4, 5, 5, 6, 7};
	double resultVals[9] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};

	for(int i = 0; i < 9; i++)
	{
		resultMatrix.setElement(resultRows[i], resultCols[i], resultVals[i]);
	}

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Skip setting up the matrix

	// Test and Check
	// Should be an error since the matrix is not setup
	status = solver.getValuesMatrixA(resultMatrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_INVALID_MATRIX);
}

// Test 3: Error Check: Check that the maximum indices of the matrix object are within the bounds
// of the PETSc matrix object
BOOST_AUTO_TEST_CASE(getValuesMatrixA_test3)
{

}

// ============== clearVectorX ===========
// Test 1: Check value is set to 0
BOOST_AUTO_TEST_CASE(clearVectorX_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes, else we will get error
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorX();

	// Set the values
	// Will need to use setValueVector, but this should have been
	// tested by this point
	status = solver.setValuesVectorX(2.4);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Test and Check
	status = solver.clearVectorX();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Get value to check
	// Will need to use getValuesVectorX, but should be tested by this point
	double * result;
	int nResult;
	status = solver.getValuesVectorX(&result, &nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	double cmp[8] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 8, cmp, cmp + 8);

	free(result);
}

// Test 2: Check for error when vector X is not setup
BOOST_AUTO_TEST_CASE(clearVectorX_test2)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes, else we will get error
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// === Test and Check ===
	status = solver.clearVectorX();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_INVALID_VECTOR);
}

// ============== clearVectorB ===========
BOOST_AUTO_TEST_CASE(clearVectorB_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes, else we will get error
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Create Vector
	solver.setupVectorB();

	// Set the values
	// Will need to use setValueVector, but this should have been
	// tested by this point
	status = solver.setValuesVectorB(2.4);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Test and Check
	status = solver.clearVectorB();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Get value to check
	// Will need to use getValuesVectorB, but should be tested by this point
	double * result;
	int nResult;
	status = solver.getValuesVectorB(&result, &nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	double cmp[8] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 8, cmp, cmp + 8);

	free(result);
}

BOOST_AUTO_TEST_CASE(clearVectorB_test2)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===

	// Set the row and col sizes, else we will get error
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// === Test and Check ===
	status = solver.clearVectorB();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_LINEARSOLVER_INVALID_VECTOR);
}

// ============== clearMatrixA ===========
BOOST_AUTO_TEST_CASE(clearMatrixA_test1)
{

}

// ============== resetSolverSelection ===========
BOOST_AUTO_TEST_CASE(resetSolverSelection_test1)
{

}

// ============== setSolverSelection ===========
BOOST_AUTO_TEST_CASE(setSolverSelection_test1)
{

}

// ============== resetTolerances ===========
BOOST_AUTO_TEST_CASE(resetTolerances_test1)
{

}

// ============== resetPETScSolver ===========
// Test 1: Test that we can reset when the KSP solver is not setup
BOOST_AUTO_TEST_CASE(resetPETScSolver_test1)
{

}

// Test 2: Test that we can reset when the KSP Solver is setup
BOOST_AUTO_TEST_CASE(resetPETScSolver_test2)
{

}

// ============== setupPETScSolver ===========
// Test 1: Test that we can setup the CG/AMG solver
BOOST_AUTO_TEST_CASE(setupPETScSolver_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===
	// Create a simple SparseMatrix
	cupcfd::data_structures::matrices::SparseMatrixCOO<int, double> matrix(8, 8, 0);

	int rows[13] = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7};
	int cols[13] = {0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7};
	double vals[13] = {0.1, 0.2, 0.1, 0.05, 0.07, 0.09, 0.06, 0.1, 0.15, 0.23, 0.11, 0.13, 0.09};

	for(int i = 0; i < 13; i++)
	{
		matrix.setElement(rows[i], cols[i], vals[i]);
	}

	// Set the row and col sizes
	solver.mLocal = 8;
	solver.nLocal = 8;
	solver.mGlobal = 8;
	solver.nGlobal = 8;

	// Test and Check
	status = solver.setupMatrixA(matrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	solver.petscSolverSelection = PETSC_SOLVER_CGAMG;

	// Test and Check
	status = solver.setupPETScSolver();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
}

// ============== solveMatrixA ===========
BOOST_AUTO_TEST_CASE(solveMatrixA_test1)
{
	cupcfd::error::eCodes status;

	LinearSolverPETScSerial<double> solver;

	// === Setup ===
	// Create a simple SparseMatrix
	cupcfd::data_structures::matrices::SparseMatrixCOO<int, double> matrix(8, 8, 0);

	//int rows[21] = {0, 1, 2, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7};
	//int cols[21] = {1, 2, 3, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 7};
	//double vals[21] = {0.1, 0.2, 0.1, 0.05, 0.07, 0.09, 0.06, 0.1, 0.15, 0.23, 0.11, 0.13, 0.09, 0.4, 0.2, 0.6, 0.8, 0.7, 0.4, 0.3, 0.2};
	int rows[8] = {0, 1, 2, 3, 4, 5, 6, 7};
	int cols[8] = {0, 1, 2, 3, 4, 5, 6, 7};
	double vals[8] = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8};

	for(int i = 0; i < 8; i++)
	{
		matrix.setElement(rows[i], cols[i], vals[i]);
	}

	// Setup the data structures of the solver
	status = solver.setup(matrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Set some suitable values for a very small test solve
	status = solver.setValuesMatrixA(matrix);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Set a suitable value for the B Vector
	//double tmp[8] = {0.23, 0.598, 0.46, 0.345, 0.644, 0.414, 0.506, 0.207};
	//int tmp2[8] = {0, 1, 2, 3, 4, 5, 6, 7};

	//status = solver.setValuesVectorB(tmp, 8, tmp2, 8, 0);
	status = solver.setValuesVectorB(0.1);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Clear the X Vector
	status = solver.clearVectorX();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Set the solver type
	status = solver.setSolverSelection(PETSC_SOLVER_CGAMG);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	solver.setTolerances(0.0, 0.0);

	// Test and Check
	// Run the solve
	status = solver.solve();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Get the contents of Vector X
	double * vecX;
	int nVecX;

	status = solver.getValuesVectorX(&vecX, &nVecX);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	double cmp[8] = {1, 0.5, 0.33333333333333337, 0.25, 0.2, 0.16666666666666669, 0.14285714285714288, 0.125};
	BOOST_CHECK_EQUAL_COLLECTIONS(cmp, cmp + 8, vecX, vecX + 8);
}

BOOST_AUTO_TEST_CASE(cleanup)
{
	PetscFinalize();
}


/**
 * @file numpdesetup.cc
 * @brief NPDE homework NumPDESetup code
 * @author Oliver Rietmann, Erick Schulz
 * @date 01.01.2020
 * @copyright Developed at ETH Zurich
 */

#include "numpdesetup.h"

#include <Eigen/Core>
#include <iostream>

namespace NumPDESetup {

/* SAM_LISTING_BEGIN_1 */
Eigen::VectorXd dummyFunction(double x, int n) {
  // Appears only in mastersolution
  std::cout << "NumPDESetup: master solution code" << std::endl;
  return Eigen::VectorXd::Constant(n, x);
}
/* SAM_LISTING_END_1 */

}  // namespace NumPDESetup


// Copyright András Vukics 2006–2020. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE.txt)
#include "BlitzArray.h"
#include "SliceIterator.tcc"

// #include "Random.h"

#define BOOST_TEST_MODULE BlitzArraySliceIterator test
#include <boost/test/unit_test.hpp>

#include <boost/fusion/sequence/io.hpp>
#include <boost/fusion/sequence/comparison.hpp>
#include <boost/fusion/functional/invocation/invoke.hpp>
#include <boost/fusion/sequence/intrinsic/at_c.hpp>
namespace mpl=boost::mpl;

#include <sstream>

using namespace cppqedutils::sliceiterator;
using namespace std;

using V=tmptools::Vector<3,6,1,9,7>;

const int RANK=11;

using TM=details::TransposerMeta_t<RANK,V>;

using FullIdx=details::IndexerBase<RANK,V>::Idx; // This contains Range types at the appropriate locations

const IdxTiny<RANK> idx(21,2,4,10,11,3,5,9,23,22,7);

const VecIdxTiny<RANK,V> filteredIdx(filterOut<RANK,V>(idx));

using DArray11=DArray<11>;

namespace {

using mpl::at_c, mpl::equal;

static_assert(at_c<TM,0>::type::value==0);
static_assert(at_c<TM,1>::type::value==3);
static_assert(at_c<TM,2>::type::value==2);
static_assert(at_c<TM,3>::type::value==6);
static_assert(at_c<TM,4>::type::value==4);
static_assert(at_c<TM,5>::type::value==5);
static_assert(at_c<TM,6>::type::value==1);
static_assert(at_c<TM,7>::type::value==9);
static_assert(at_c<TM,8>::type::value==8);
static_assert(at_c<TM,9>::type::value==7);
static_assert(at_c<TM,10>::type::value==10);

static_assert(equal<TM,     mpl::vector_c<int,0,3,2,6,4,5,1,9,8,7,10> >::value);
static_assert(equal<TM,tmptools::Vector  <    0,3,2,6,4,5,1,9,8,7,10> >::value);

using blitz::Range;

static_assert(equal<FullIdx,mpl::vector<int,Range,int,Range,int,int,Range,Range,int,Range,int> >::value);

}

BOOST_AUTO_TEST_CASE( FilterOutTest )
{
  BOOST_CHECK(all(filteredIdx==VecIdxTiny<RANK,V>(21,4,11,3,23,7)));
}

BOOST_AUTO_TEST_CASE( IdxValueTest )
{
  using namespace std;

  const blitz::Range a(blitz::Range::all());

  const FullIdx v(21,a,4,a,11,3,a,a,23,a,7);

  // The following strange solution is needed because there is no comparison operation for Ranges
  stringstream s1(stringstream::out), s2(stringstream::out);

  s1<<v; s2<<details::IndexerBase<RANK,V>::fillIdxValues(filteredIdx);

  BOOST_CHECK(s1.str()==s2.str());

}

BOOST_AUTO_TEST_CASE( ArraySlicingTest )
{
  const auto a{blitz::Range::all()};

  DArray11 array11{22,2,5,1,12,4,4,3,24,2,8};

  FullIdx v{21,a,4,a,11,3,a,a,23,a,7};

  using boost::fusion::at_c;

  BOOST_CHECK(
    all(DArray<5>{array11(at_c<0>(v),at_c<1>(v),at_c<2>(v),at_c<3>(v),at_c<4>(v),at_c<5>(v),at_c<6>(v),at_c<7>(v),at_c<8>(v),at_c<9>(v),at_c<10>(v))}.extent()
        ==
        ExtTiny<5>(2,1,4,3,2)));

}

BOOST_AUTO_TEST_CASE( ExampleFromManual )
{
  void actWithA(CArray<5>&);

  CArray<RANK> psi;

  for (auto p : fullRange<V>(psi) ) actWithA(p);
}

void actWithA(CArray<5>&) {}

// The following test comes from the old version of this file, and is intended to demonstrate the performance as a function of the arity of slices
/*
namespace basi_performance {

const int nRepetition=1;

CArray<11> array1(5,4,5,4,5,4,5,4,5,4,5), array2(array1.shape()), arrayRes(array1.shape()), arrayOrig(array1.shape());

struct Helper
{
  template<typename V> void operator()(V)
  {
    PROGRESS_TIMER_IN_POINT(cout);
    for (int i=nRepetition; i; --i) cppqedutils::for_each(fullRange<V>(array1),basi::begin<V>(array2),bll::_1*=bll::_2);
    cout<<"Arity "<<11-mpl::size<V>::value<<": ";
    PROGRESS_TIMER_OUT_POINT("");

    BOOST_CHECK(all(array1==arrayRes)); array1=arrayOrig;

    PROGRESS_TIMER_IN_POINT(cout);
    SlicesData<11,V> slicesData(array1);
    for (int i=nRepetition; i; --i) cppqedutils::for_each(basi_fast::fullRange(array1,slicesData),basi_fast::begin(array2,slicesData),bll::_1*=bll::_2);
    cout<<"Fast. Arity "<<11-mpl::size<V>::value;
    PROGRESS_TIMER_OUT_POINT("");

    BOOST_CHECK(all(array1==arrayRes)); array1=arrayOrig;
  }

};

} // basi_performance

BOOST_AUTO_TEST_CASE( BASI_Performance )
{

  using namespace randomized; using namespace basi_performance;

  fillWithRandom(array2,fillWithRandom(array1));

  arrayRes=arrayOrig=array1;

  PROGRESS_TIMER_IN_POINT(cout);
  for (int i=nRepetition; i; --i) arrayRes*=array2;
  PROGRESS_TIMER_OUT_POINT("\nBlitz internal multiplication");

  mpl::for_each<mpl::vector<
    tmptools::Vector<9,3,6,0,10,4,7,8,1,5,2>,
    tmptools::Vector<9,3,6,0,10,4,7,8,1,5>,
    tmptools::Vector<9,3,6,0,10,4,7,8,1>,
    tmptools::Vector<9,3,6,0,10,4,7,8>,
    tmptools::Vector<9,3,6,0,10,4,7>,
    tmptools::Vector<9,3,6,0,10,4>,
    tmptools::Vector<9,3,6,0,10>,
    tmptools::Vector<9,3,6,0>,
    tmptools::Vector<9,3,6>,
    tmptools::Vector<9,3>,
    tmptools::Vector<9>
      > >(Helper());

}

namespace basi_monitor {

CArray<6> array1(3,2,3,2,3,2), array2(array1.shape());

template<int RANK>
void helper(const CArray<RANK>& a1, const CArray<RANK>& a2, const dcomp* dc1, const dcomp* dc2)
{
  cout<<a1.zeroOffset()<<' '<<a1.shape()<<' '<<a1.stride()<<' '<<a1.ordering()<<' '<<a1.data()-dc1<<endl<<a2.zeroOffset()<<' '<<a2.shape()<<' '<<a2.stride()<<' '<<a2.ordering()<<' '<<a2.data()-dc2<<endl;
  BOOST_CHECK(all(a1==a2));
}

struct Helper
{

  template<typename V> void operator()(V)
  {
    SlicesData<6,V> slicesData(array1);

    cout<<endl<<
      "****************\n"<<
      "*** Slice Arity: "<<mpl::size<V>::value<<endl<<
      "****************\n";
    cppqedutils::for_each(fullRange<V>(array1),basi_fast::begin(array2,slicesData),boost::bind(helper<mpl::size<V>::value>,_1,_2,array1.data(),array2.data()));
  }

};

} // basi_monitor

BOOST_AUTO_TEST_CASE( BASI_Monitor )
{

  using namespace basi_monitor;

  randomized::fillWithRandom(array1);

  array2=array1;

  mpl::for_each<mpl::vector<
  tmptools::Vector<3,0,4,1,5>,
  tmptools::Vector<3,0,4,1>,
  tmptools::Vector<3,0,4>,
  tmptools::Vector<3,0>,
  tmptools::Vector<3>
    > > (Helper());

  // SlicesData<6,tmptools::Vector<3,0,4,1,5> > data(array1);
  // basi_fast::Iterator<6,tmptools::Vector<3,0,4,1,5>,true> iter(data,array2,mpl::false_());

}
*/

// BlitzArraySliceIteratorFast assumes: all storage ascending, all bases zero


#include <boost/test/unit_test.hpp>

#include <stdexcept>

#include "xmlparser.h"
#include "logs.h"
#include "route.h"

using namespace GPS;

/*////////////////////////////////////////Design//////////////////////////////////////////////////////////////

It's important to know that this test suit assumes correct implimentation of the route class.
As such it dose not attempt to check for null vectors and invalid arguments.

1: Testing one value

2: Test suit for data patterns
    1:Ascending
    2:Descending
    3:Staggered

3: Test suit for different values
    1:Positive
    2:Negative
    3:Positive and Negative

4: Large data set

5: Test suit for Ivalid input
    1: >=90
    2: <=-90

Test 1 is needed as the start as a failure would produce a failure for tests 2,3 and 4.

Test 2 comes before 3 but only uses positive values. I origionally had Test 3 before 2,
however they would need a pattern or to be staggered which gives the data patterns test no purpose.
As a result test 2 now only uses positive values - positive values test in different values is
still needed as tests can be run in any order.

Test 3 now uses only staggered values.

Test 5 is placed at the end to encourage correct code before handling exceptions.
*/

/*
    using tolerance as 0.1 as test data is widely spead +- ~10 however if there is manipulation of data
    maybe in a for loop that uses ++ then using 0.1 will distinguish between the values.
*/
BOOST_AUTO_TEST_SUITE(n0682255)

/*
 The check single value test case holds the smallest route possible of one position.
 If this test is failed it means the implimentation of the function is severly wrong.
 This is to catch functions that either manipulate the data, or return the wrong value.
 For the test data: the longditude is 1 and elevation 2 to distinguish between them if they are returned.
*/
BOOST_AUTO_TEST_CASE(checkSingleValue)
{
    const bool isFileName = true;
    const std::string filePath = LogFiles::GPXRoutesDir + "singleValueN0682255.gpx";

    Route route = Route(filePath, isFileName);

    BOOST_CHECK_CLOSE(route.minLatitude() , 0 , 1);

}

/*
    This test suit checks for hard coded return values e.g returning Position[0],
    these would pass tests where the smallest latitudes were at the begining.
    By testing descending, ascending and staggered values we can eliminate hard coded return values.
 */
BOOST_AUTO_TEST_SUITE(DataSetPatterns)

BOOST_AUTO_TEST_CASE(checkAscending)
{
    const bool isFileName = true;
    const std::string filePath = LogFiles::GPXRoutesDir + "posAscendN0682255.gpx";

    Route route = Route(filePath, isFileName);

    BOOST_CHECK_CLOSE(route.minLatitude() , 1 , 1);

}

BOOST_AUTO_TEST_CASE(checkDescending)
{
    const bool isFileName = true;
    const std::string filePath = LogFiles::GPXRoutesDir + "posDescendN0682255.gpx";

    Route route = Route(filePath, isFileName);

    BOOST_CHECK_CLOSE(route.minLatitude() , 60 , 1);

}

BOOST_AUTO_TEST_CASE(checkPosativeStaggered)
{
    const bool isFileName = true;
    const std::string filePath = LogFiles::GPXRoutesDir + "posStaggeredN0682255.gpx";

    Route route = Route(filePath, isFileName);

    BOOST_CHECK_CLOSE(route.minLatitude() , 20 , 1);

}
BOOST_AUTO_TEST_SUITE_END()

/*
    This test suit tests if the function can handle positive , negative and a mixture of the both.
 */
BOOST_AUTO_TEST_SUITE(ValueHandling)
BOOST_AUTO_TEST_CASE(checkPositive)
{
    const bool isFileName = true;
    const std::string filePath = LogFiles::GPXRoutesDir + "posStaggeredN0682255.gpx";

    Route route = Route(filePath, isFileName);

    BOOST_CHECK_CLOSE(route.minLatitude() , 20 , 1);

}

BOOST_AUTO_TEST_CASE(checkNegative)
{
    const bool isFileName = true;
    const std::string filePath = LogFiles::GPXRoutesDir + "negStaggeredN0682255.gpx";

    Route route = Route(filePath, isFileName);

    BOOST_CHECK_CLOSE(route.minLatitude() , -40 , 1);

}

BOOST_AUTO_TEST_CASE(checkPosativeandNegative)
{
    const bool isFileName = true;
    const std::string filePath = LogFiles::GPXRoutesDir + "posNegStaggeredN0682255.gpx";

    Route route = Route(filePath, isFileName);

    BOOST_CHECK_CLOSE(route.minLatitude() , -19.9869 , 1);

}
BOOST_AUTO_TEST_SUITE_END()

/*
    This test case uses large data sets. It also uses values with very similar numbers
so the tolerance has to be of greater precision. Otherwise the test may be passed.
 */
BOOST_AUTO_TEST_CASE(checkLargeData)
{
    const bool isFileName = true;
    const std::string filePath = LogFiles::GPXRoutesDir + "NorthYorkMoors.gpx";

    Route route = Route(filePath, isFileName);

    BOOST_CHECK_CLOSE(route.minLatitude() , 54.40526783466339  , 0.001);

}
BOOST_AUTO_TEST_SUITE(InvalidData)

/*
    These test cases check if there are any invalid values as latitude cannot be greater than 90 deg or less than -90 deg
*/
BOOST_AUTO_TEST_CASE(checkGreaterThan90)
{
    const bool isFileName = true;
    const std::string filePath = LogFiles::GPXRoutesDir + "posOutOfBoundsN0682255.gpx";

    Route route = Route(filePath, isFileName);

    BOOST_CHECK_THROW(route.minLatitude() , std::invalid_argument);

}

BOOST_AUTO_TEST_CASE(checkLessThanMinus90)
{
    const bool isFileName = true;
    const std::string filePath = LogFiles::GPXRoutesDir + "negOutOfBoundsN0682255.gpx";

    Route route = Route(filePath, isFileName);

    BOOST_CHECK_THROW(route.minLatitude() , std::invalid_argument);

}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE_END()


//  Copyright John Maddock 2006.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

// distributions.hpp provides definitions of the concept of a distribution
// and non-member accessor functions that must be implemented by all distributions.
// This is used to verify that
// all the features of a distributions have been fully implemented.

#ifndef BOOST_MATH_DISTRIBUTION_CONCEPT_HPP
#define BOOST_MATH_DISTRIBUTION_CONCEPT_HPP

#include <boost/math/distributions/complement.hpp>
#ifdef BOOST_MSVC
#pragma warning(push)
#pragma warning(disable: 4100)
#pragma warning(disable: 4510)
#pragma warning(disable: 4610)
#endif
#include <boost/concept_check.hpp>
#ifdef BOOST_MSVC
#pragma warning(pop)
#endif
#include <utility>

namespace boost{
namespace math{

namespace concepts
{
// Begin by defining a concept archetype
// for a distribution class:
//
template <class RealType>
class distribution_archetype
{
public:
   typedef RealType value_type;

   distribution_archetype(const distribution_archetype&); // Copy constructible.
   distribution_archetype& operator=(const distribution_archetype&); // Assignable.

   // There is no default constructor,
   // but we need a way to instantiate the archetype:
   static distribution_archetype& get_object()
   {
      // will never get caled:
      return *reinterpret_cast<distribution_archetype*>(0);
   }
}; // template <class RealType>class distribution_archetype

// Non-member accessor functions:
// (This list defines the functions that must be implemented by all distributions).

template <class RealType>
RealType pdf(const distribution_archetype<RealType>& dist, const RealType& x);

template <class RealType>
RealType cdf(const distribution_archetype<RealType>& dist, const RealType& x);

template <class RealType>
RealType quantile(const distribution_archetype<RealType>& dist, const RealType& p);

template <class RealType>
RealType cdf(const complemented2_type<distribution_archetype<RealType>, RealType>& c);

template <class RealType>
RealType quantile(const complemented2_type<distribution_archetype<RealType>, RealType>& c);

template <class RealType>
RealType mean(const distribution_archetype<RealType>& dist);

template <class RealType>
RealType standard_deviation(const distribution_archetype<RealType>& dist);

template <class RealType>
RealType variance(const distribution_archetype<RealType>& dist);

template <class RealType>
RealType hazard(const distribution_archetype<RealType>& dist);

template <class RealType>
RealType chf(const distribution_archetype<RealType>& dist);
// http://en.wikipedia.org/wiki/Characteristic_function_%28probability_theory%29

template <class RealType>
RealType coefficient_of_variation(const distribution_archetype<RealType>& dist);

template <class RealType>
RealType mode(const distribution_archetype<RealType>& dist);

template <class RealType>
RealType skewness(const distribution_archetype<RealType>& dist);

template <class RealType>
RealType kurtosis_excess(const distribution_archetype<RealType>& dist);

template <class RealType>
RealType kurtosis(const distribution_archetype<RealType>& dist);

template <class RealType>
RealType median(const distribution_archetype<RealType>& dist);

template <class RealType>
std::pair<RealType, RealType> range(const distribution_archetype<RealType>& dist);

template <class RealType>
std::pair<RealType, RealType> support(const distribution_archetype<RealType>& dist);

//
// Next comes the concept checks for verifying that a class
// fullfils the requirements of a Distribution:
//
template <class Distribution>
struct DistributionConcept
{
   void constraints()
   {
      function_requires<CopyConstructibleConcept<Distribution> >();
      function_requires<AssignableConcept<Distribution> >();

      typedef typename Distribution::value_type value_type;

      const Distribution& dist = DistributionConcept<Distribution>::get_object();

      value_type x = 0;
       // The result values are ignored in all these checks.
       value_type v = cdf(dist, x);
      v = cdf(complement(dist, x));
      v = pdf(dist, x);
      v = quantile(dist, x);
      v = quantile(complement(dist, x));
      v = mean(dist);
      v = mode(dist);
      v = standard_deviation(dist);
      v = variance(dist);
      v = hazard(dist, x);
      v = chf(dist, x);
      v = coefficient_of_variation(dist);
      v = skewness(dist);
      v = kurtosis(dist);
      v = kurtosis_excess(dist);
      v = median(dist);
      std::pair<value_type, value_type> pv;
      pv = range(dist);
      pv = support(dist);

      float f = 1;
      v = cdf(dist, f);
      v = cdf(complement(dist, f));
      v = pdf(dist, f);
      v = quantile(dist, f);
      v = quantile(complement(dist, f));
      v = hazard(dist, f);
      v = chf(dist, f);
      double d = 1;
      v = cdf(dist, d);
      v = cdf(complement(dist, d));
      v = pdf(dist, d);
      v = quantile(dist, d);
      v = quantile(complement(dist, d));
      v = hazard(dist, d);
      v = chf(dist, d);
#ifndef TEST_MPFR
      long double ld = 1;
      v = cdf(dist, ld);
      v = cdf(complement(dist, ld));
      v = pdf(dist, ld);
      v = quantile(dist, ld);
      v = quantile(complement(dist, ld));
      v = hazard(dist, ld);
      v = chf(dist, ld);
#endif
      int i = 1;
      v = cdf(dist, i);
      v = cdf(complement(dist, i));
      v = pdf(dist, i);
      v = quantile(dist, i);
      v = quantile(complement(dist, i));
      v = hazard(dist, i);
      v = chf(dist, i);
      unsigned long li = 1;
      v = cdf(dist, li);
      v = cdf(complement(dist, li));
      v = pdf(dist, li);
      v = quantile(dist, li);
      v = quantile(complement(dist, li));
      v = hazard(dist, li);
      v = chf(dist, li);
   }
private:
   static Distribution& get_object()
   {
      // will never get called:
      static char buf[sizeof(Distribution)];
      return * reinterpret_cast<Distribution*>(buf);
   }
}; // struct DistributionConcept

} // namespace concepts
} // namespace math
} // namespace boost

#endif // BOOST_MATH_DISTRIBUTION_CONCEPT_HPP


//------------------------------------------------------------------------------
/// \file Max_tests.cpp
/// \ref Vandevoorde, Josuttis, Gregor. C++ Templates: The Complete Guide. 2nd
/// Ed. Addison-Wesley Professional. 2017.
//------------------------------------------------------------------------------
#include "Cpp/Templates/FunctionT/MoreMax.h"

#include <boost/test/unit_test.hpp>
#include <complex>
#include <string>
#include <type_traits> // std::decay

using namespace Cpp::Templates::FunctionTemplates;

BOOST_AUTO_TEST_SUITE(Cpp)
BOOST_AUTO_TEST_SUITE(Templates)
BOOST_AUTO_TEST_SUITE(FunctionTemplates)
BOOST_AUTO_TEST_SUITE(MoreMax_tests)

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(ShowHowToUseFunctionTemplateDefinition)
{
  // Templates aren't compiled into single entities that can handle any type.
  // Instead, different entities are generated from the template for every type
  // for which template is used.
  // Process of replacing template parameters by concrete types is called
  // instantiations. Results in an instance of a template.
  // Note mere use of a function template can trigger such an instantiation
  // process.
  constexpr int i {42};
  BOOST_TEST(max1::max(7, i) == i);

  constexpr double f1 {3.4};
  constexpr double f2 {-6.7};
  BOOST_TEST(max1::max(f1, f2) == f1);

  const std::string s1 {"mathematics"};
  const std::string s2 {"math"};
  BOOST_TEST(max1::max(s1, s2) == s1);
}

// cf. Ch. 3.1 Function Templates, Gottschling. Discovering Modern C++.
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(FunctionTemplateDoesFunctionOverloading)
{
  BOOST_TEST(max1::max(3, 5) == 5);
  BOOST_TEST(max1::max(3l, 5l) == 5l);
  BOOST_TEST(max1::max(3.0, 5.0) == 5.0);

  unsigned u1 {2}, u2 {8};
  BOOST_TEST(max1::max(u1, u2) == u2);
  BOOST_TEST(max1::max(u1 * u2, u1 + u2) == u1 * u2);
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(TemplateArgumentDeducedAsPartOfConstReference)
{
  {
    int c {42};
    int i {7};

    BOOST_TEST(max_with_const_refs::max(i, c) == c);
  }

  {
    BOOST_TEST(max_with_const_refs::max(2, 3) == 3);
  }
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(CompilerDeducesReturnTypeWithAuto)
{
  {
    const int c {42};
    const double i {7.0};

    BOOST_TEST(max_auto::max(i, c) == c);
  }

  {
    BOOST_TEST(max_auto::max(2.0, 3) == 3);
  }
}

// Trailing return type is ->
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(Cpp11UsesTrailingReturnType)
{
  {
    const int c {42};
    const double i {7.0};

    BOOST_TEST(max_decltype::max(i, c) == c);
  }

  {
    BOOST_TEST(max_decltype::max(2.0, 3) == 3);
  }
}

// https://en.cppreference.com/w/cpp/types/decay
// Applies lvalue-to-rvalue, array-to-pointer, function-to-pointer implicit
// conversions to type T, removes cv-qualifiers, defines resulting type as
// member typedef type.
template <typename T, typename U>
struct DecayEquivalent :
  std::is_same<typename std::decay_t<T>, U>::type
{};

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(StdDecay)
{
  BOOST_TEST((DecayEquivalent<int, int>::value));
  BOOST_TEST((DecayEquivalent<int&, int>::value));
  BOOST_TEST((DecayEquivalent<int&&, int>::value));
  BOOST_TEST((DecayEquivalent<const int&, int>::value));
  BOOST_TEST((DecayEquivalent<int[2], int*>::value));
  BOOST_TEST((DecayEquivalent<int(int), int(*)(int)>::value));
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(MaxDecltypeDecayAppliesLvalueToRvalueDecay)
{
  {
    int a {42};
    int b {7};

    int& a_ref {a};
    int&& b_r_value {std::move(b)};

    auto result = max_decltype_decay::max(a_ref, b_r_value);
    BOOST_TEST(result == 42);
  }

}

/*

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(TypeDeductionForDefaultTemplateArgument)
{
  f1(); // OK
  BOOST_TEST(true);
}

// pp. 15, Sec. 1.5 Overloaindg Function Templates
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(OverloadingFunctionTemplates)
{
  BOOST_TEST(max(7, 42) == 42); // calls nontemplate for 2 ints.
  BOOST_TEST(max(7.0, 42.0) == 42.0); // call max <double> (by argument
    //deduction)
  BOOST_TEST(max('a', 'b') == 'b'); // calls max<char> (by argument deduction)
  BOOST_TEST(max<>(7, 42) == 42); // call max<int> (by argument deduction)
  BOOST_TEST(max<double>(7, 42) == 42.0); // calls max<double> (no argument
  // deduction)
  BOOST_TEST(max('a', 42.7) == 97); // call the nontemplate for two ints
}
*/

BOOST_AUTO_TEST_SUITE_END() // MoreMax_tests

BOOST_AUTO_TEST_SUITE_END() // FunctionTemplates
BOOST_AUTO_TEST_SUITE_END() // Templates
BOOST_AUTO_TEST_SUITE_END() // Cpp


<<<<<<< HEAD
/*    Copyright (c) 2010-2018, Delft University of Technology
=======
/*    Copyright (c) 2010-2019, Delft University of Technology
>>>>>>> origin/master
 *    All rigths reserved
 *
 *    This file is part of the Tudat. Redistribution and use in source and
 *    binary forms, with or without modification, are permitted exclusively
 *    under the terms of the Modified BSD license. You should have received
 *    a copy of the license with this file. If not, please or visit:
 *    http://tudat.tudelft.nl/LICENSE.
 *
 *    References
 *      HORIZONS Web-Interface, http://ssd.jpl.nasa.gov/horizons.cgi, last accessed: 5 April, 2011.
 *
 *    Notes
 *      It is noted during the 120513 check that this is not a very extensive and/or precise unit
 *      test. Given that the ephemeris class will soon be updated, this is not deemed a big issue.
 *      However the unit test will have to be improved a lot in the next update. Some code was
 *      outcommented when boostifying the unit test. It is attached in commented version at the
 *      bottom of this file.
 *
 *      Also the test of the approximate planet positions (3D) was changed. It used to check the
 *      only the spherical position coordinates. It was changed to check the cartesian elements in
 *      total. The accuracy with which this is possible is very low though.
 *
 */

#define BOOST_TEST_MAIN

#include <boost/test/unit_test.hpp>

#include "Tudat/Astrodynamics/BasicAstrodynamics/orbitalElementConversions.h"
#include "Tudat/Basics/testMacros.h"
#include "Tudat/Astrodynamics/BasicAstrodynamics/unitConversions.h"

#include "Tudat/Astrodynamics/Ephemerides/approximatePlanetPositions.h"
#include "Tudat/Astrodynamics/Ephemerides/approximatePlanetPositionsCircularCoplanar.h"

namespace tudat
{
namespace unit_tests
{

//! Test the functionality of the approximate planet position functions
BOOST_AUTO_TEST_SUITE( test_approximate_planet_positions )

//! Test the orbital elements function against the orbital elements of Mars at JD 2455626.5.
BOOST_AUTO_TEST_CASE( testOrbitalElements )
{
    using unit_conversions::convertDegreesToRadians;
    using namespace ephemerides;

    // Set tolerance.
    const double tolerance = 2.0e-2;

    // Expected result.
    Eigen::Matrix< double, 6, 1 > expectedKeplerianElements;
    expectedKeplerianElements[ 0 ] = 2.279361944126564e11;
    expectedKeplerianElements[ 1 ] = 9.338126166083623e-2;
    expectedKeplerianElements[ 2 ] = convertDegreesToRadians( 1.848907897011101 );
    expectedKeplerianElements[ 3 ] = convertDegreesToRadians( 2.866464026954701e2 );
    expectedKeplerianElements[ 4 ] = convertDegreesToRadians( 4.952419052428279e1 );
    expectedKeplerianElements[ 5 ] = convertDegreesToRadians( 3.577219707986779e2 );

    // Create Mars ephemeris.
    ApproximatePlanetPositions marsEphemeris( ApproximatePlanetPositions::mars );

    // Convert the expected Keplerian elements to Cartesian elements.
    Eigen::Vector6d expectedEphemeris;
    expectedEphemeris = orbital_element_conversions::
            convertKeplerianToCartesianElements(
            expectedKeplerianElements,
            marsEphemeris.getSunGravitationalParameter( ) );

    // Retrieve state of Mars in Cartesian elements at Julian date 2455626.5.
    Eigen::Vector6d marsState = marsEphemeris.getCartesianState(
                ( 2455626.5 - basic_astrodynamics::JULIAN_DAY_ON_J2000 ) * physical_constants::JULIAN_DAY );

    // Test if the computed ephemeris matches the expected ephemeris within the tolerance set.
    TUDAT_CHECK_MATRIX_CLOSE_FRACTION( expectedEphemeris, marsState, tolerance );

    // Check that the reference frame properties are as expected.
    BOOST_CHECK_EQUAL( marsEphemeris.getReferenceFrameOrientation( ), "ECLIPJ2000" );
    BOOST_CHECK_EQUAL( marsEphemeris.getReferenceFrameOrigin( ), "Sun" );
}

//! Test the cicular coplanar function against orbital elements of Mars at JD 2455626.5.
BOOST_AUTO_TEST_CASE( testCircularCoplannar )
{
    using namespace ephemerides;

    ApproximatePlanetPositionsCircularCoplanar marsEphemeris(
                ApproximatePlanetPositionsBase::mars );

    Eigen::Vector6d marsStateCircularCoplanar
            = marsEphemeris.getCartesianState(
                ( 2455626.5 - basic_astrodynamics::JULIAN_DAY_ON_J2000 ) * physical_constants::JULIAN_DAY );

    // Compute the Keplerian elements from this ephemeris.
    Eigen::Vector6d keplerianElementsCircularCoplanar;
    keplerianElementsCircularCoplanar = orbital_element_conversions::
            convertCartesianToKeplerianElements( marsStateCircularCoplanar,
                    marsEphemeris.getSunGravitationalParameter( ) + marsEphemeris.getPlanetGravitationalParameter( ) );

    // Check the eccentricity, inclination and z-component of velocity and position are 0.
    BOOST_CHECK_SMALL( keplerianElementsCircularCoplanar( 1 ), 1e-15 );
    BOOST_CHECK_SMALL( keplerianElementsCircularCoplanar( 2 ),
                       std::numeric_limits< double >::min( ) );
    BOOST_CHECK_SMALL( marsStateCircularCoplanar( 2 ), 2.0e-5 );
    BOOST_CHECK_SMALL( marsStateCircularCoplanar( 5 ),
                       std::numeric_limits< double >::min( ) );

    // Check that the reference frame properties are as expected.
    BOOST_CHECK_EQUAL( marsEphemeris.getReferenceFrameOrientation( ), "ECLIPJ2000" );
    BOOST_CHECK_EQUAL( marsEphemeris.getReferenceFrameOrigin( ), "Sun" );
}

BOOST_AUTO_TEST_SUITE_END( )

} // namespace unit_tests
} // namespace tudat

//    // Compute the difference in semi-major axis between Test 2 and
//    // the external EphemerisData "p_elem_t2.txt".
//    double errorSemiMajorAxis = fabs( positionMars.norm( )
//                                      - convertAstronomicalUnitsToMeters( 1.52371243 ) )
//            / convertAstronomicalUnitsToMeters( 1.52371243 );

//    if ( errorSemiMajorAxis > errorTolerance_ )
//    {
//        isApproximatePlanetPositionsErroneous = true;

//        // Generate error statements.
//        cerr << "The computed relative error in position of the  " << endl;
//        cerr << "coplanar circular position of Mars ( " << errorSemiMajorAxis << " )" << endl;
//        cerr << "using the ApproximatePlanetPositionsCircularCoplanar class, exceeds "
//             << "the maximum expected error " << endl;
//        cerr << "( " << errorTolerance_ << " )." << endl;
//    }

//    // Check orientation of position vector by comparison of separate components.
//    // Error in position should be smaller than maximum expected offset with respect to
//    // elliptical and inclined orbits.
//    double maximumErrorPosition =   keplerianElementsTest3D.getSemiMajorAxis( ) * (
//                keplerianElementsTest3D.getEccentricity( ) + 1.0
//                - cos( keplerianElementsTest3D.getInclination( ) ) );
//    Vector3d errorPositionVector = positionMars - marsEphemeris.getPosition( );

//    if ( fabs( errorPositionVector( 0 ) ) > maximumErrorPosition
//         || fabs( errorPositionVector( 1 ) ) > maximumErrorPosition )
//    {
//        isApproximatePlanetPositionsErroneous = true;

//        // Generate error statements.
//        cerr << "The computed error in position vector of the  " << endl;
//        cerr << "coplanar circular position of Mars ( "
//             << errorPositionVector << " meters )" << endl;
//        cerr << "using the ApproximatePlanetPositionsCircularCoplanar class, exceeds "
//             << "the expected error (" << endl;
//        cerr << "( " << maximumErrorPosition << " meters )." << endl;
//    }

    /* FIX THIS TEST!!!
    // Check size of velocity.
    Eigen::Vector3d errorVelocity = velocityMars - marsEphemeris.segment( 3, 3 );

    // Error in scalar velocity should be smaller than maximum expected offset with respect to
    // ellipitical and inclined orbits.
    double expectedErrorVelocity = fabs(
                sqrt( predefinedSun.getGravitationalParameter( )
                      / marsEphemeris.segment( 0, 3 ).norm( ) ) *
                ( ( 1.0 - cos( keplerianElementsTest3D.getInclination( ) )
                    + sqrt( ( 1.0 - keplerianElementsTest3D.getEccentricity( ) ) /
                            ( 1.0 + keplerianElementsTest3D.getEccentricity( ) ) ) - 1.0 ) ) );

    if ( errorVelocity.norm( ) > expectedErrorVelocity )
    {
        isApproximatePlanetPositionsErroneous = true;

        // Generate error statements.
        cerr << "The computed error in velocity of the " << endl;
        cerr << "coplanar circular position of Mars "
             << "( " << errorVelocity.norm( ) << " meters per second )" << endl;
        cerr << "using the ApproximatePlanetPositionsCircularCoplanar class, exceeds "
             << "the expected error " << endl;
        cerr << "( " << expectedErrorVelocity << " meters per second )." << endl;
        cerr << "The computed error exceeds the expected error by: "
             << fabs( errorVelocity.norm( ) - expectedErrorVelocity )
             << " meters per second." << endl;
    }
    */


#pragma once

/* =========================================================================
      Copyright (c) 2015-2017, COE of Peking University, Shaoqiang Tang.

                         -----------------
            cuarma - COE of Peking University, Shaoqiang Tang.
                         -----------------

                  Author Email    yangxianpku@pku.edu.cn

         Code Repo   https://github.com/yangxianpku/cuarma

                      License:    MIT (X11) License
============================================================================= */

/** @file tag_of.hpp
    @brief Dispatch facility for distinguishing between ublas, STL and cuarma types
*/

#include <vector>
#include <map>

#include "cuarma/forwards.h"

#ifdef CUARMA_WITH_UBLAS
#include <boost/numeric/ublas/matrix_sparse.hpp>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/vector.hpp>
#endif

namespace cuarma
{

// ----------------------------------------------------
// TAGS
//
/** @brief A tag class for identifying 'unknown' types. */
struct tag_none     {};

/** @brief A tag class for identifying types from uBLAS. */
struct tag_ublas    {};

/** @brief A tag class for identifying types from the C++ STL. */
struct tag_stl      {};

/** @brief A tag class for identifying types from cuarma. */
struct tag_cuarma {};

namespace traits
{
  // ----------------------------------------------------
  // GENERIC BASE
  //
  /** @brief Generic base for wrapping other linear algebra packages
  *
  *  Maps types to tags, e.g. cuarma::vector to tag_cuarma, ublas::vector to tag_ublas
  *  if the matrix type is unknown, tag_none is returned
  *
  *  This is an internal function only, there is no need for a library user of cuarma to care about it any further
  *
  * @tparam T   The type to be inspected
  */
  template< typename T, typename Active = void >
  struct tag_of;

  /** \cond */
  template< typename Sequence, typename Active >
  struct tag_of
  {
    typedef cuarma::tag_none  type;
  };

#ifdef CUARMA_WITH_UBLAS
  // ----------------------------------------------------
  // UBLAS
  //
  template< typename T >
  struct tag_of< boost::numeric::ublas::vector<T> >
  {
    typedef cuarma::tag_ublas  type;
  };

  template< typename T >
  struct tag_of< boost::numeric::ublas::matrix<T> >
  {
    typedef cuarma::tag_ublas  type;
  };

  template< typename T1, typename T2 >
  struct tag_of< boost::numeric::ublas::matrix_unary2<T1,T2> >
  {
    typedef cuarma::tag_ublas  type;
  };

  template< typename T1, typename T2 >
  struct tag_of< boost::numeric::ublas::compressed_matrix<T1,T2> >
  {
    typedef cuarma::tag_ublas  type;
  };

#endif

  // ----------------------------------------------------
  // STL types
  //

  //vector
  template< typename T, typename A >
  struct tag_of< std::vector<T, A> >
  {
    typedef cuarma::tag_stl  type;
  };

  //dense matrix
  template< typename T, typename A >
  struct tag_of< std::vector<std::vector<T, A>, A> >
  {
    typedef cuarma::tag_stl  type;
  };

  //sparse matrix (vector of maps)
  template< typename KEY, typename DATA, typename COMPARE, typename AMAP, typename AVEC>
  struct tag_of< std::vector<std::map<KEY, DATA, COMPARE, AMAP>, AVEC> >
  {
    typedef cuarma::tag_stl  type;
  };

  // ----------------------------------------------------
  // CUARMA
  //
  template< typename T, unsigned int alignment >
  struct tag_of< cuarma::vector<T, alignment> >
  {
    typedef cuarma::tag_cuarma  type;
  };

  template< typename T, typename F, unsigned int alignment >
  struct tag_of< cuarma::matrix<T, F, alignment> >
  {
    typedef cuarma::tag_cuarma  type;
  };

  template< typename T1, typename T2, typename OP >
  struct tag_of< cuarma::matrix_expression<T1,T2,OP> >
  {
    typedef cuarma::tag_cuarma  type;
  };

  template< typename T >
  struct tag_of< cuarma::matrix_range<T> >
  {
    typedef cuarma::tag_cuarma  type;

  };

  template< typename T, unsigned int I>
  struct tag_of< cuarma::compressed_matrix<T,I> >
  {
    typedef cuarma::tag_cuarma  type;
  };

  template< typename T, unsigned int I>
  struct tag_of< cuarma::coordinate_matrix<T,I> >
  {
    typedef cuarma::tag_cuarma  type;
  };

  template< typename T, unsigned int I>
  struct tag_of< cuarma::ell_matrix<T,I> >
  {
    typedef cuarma::tag_cuarma  type;
  };

  template< typename T, typename I>
  struct tag_of< cuarma::sliced_ell_matrix<T,I> >
  {
    typedef cuarma::tag_cuarma  type;
  };

  template< typename T, unsigned int I>
  struct tag_of< cuarma::hyb_matrix<T,I> >
  {
    typedef cuarma::tag_cuarma  type;
  };

  // ----------------------------------------------------
} // end namespace traits

/** @brief Meta function which checks whether a tag is tag_ublas
*
*  This is an internal function only, there is no need for a library user of cuarma to care about it any further
*/
template<typename Tag>
struct is_ublas
{
  enum { value = false };
};

/** \cond */
template<>
struct is_ublas< cuarma::tag_ublas >
{
  enum { value = true };
};
/** \endcond */

/** @brief Meta function which checks whether a tag is tag_ublas
*
*  This is an internal function only, there is no need for a library user of cuarma to care about it any further
*/
template<typename Tag>
struct is_stl
{
  enum { value = false };
};

/** \cond */
template<>
struct is_stl< cuarma::tag_stl >
{
  enum { value = true };
};
/** \endcond */

/** @brief Meta function which checks whether a tag is tag_cuarma
*
*  This is an internal function only, there is no need for a library user of cuarma to care about it any further
*/
template<typename Tag>
struct is_cuarma
{
  enum { value = false };
};

/** \cond */
template<>
struct is_cuarma< cuarma::tag_cuarma >
{
  enum { value = true };
};
/** \endcond */

} // end namespace cuarma