text:
/*
 * @file
 * @author University of Warwick
 * @version 1.0
 *
 * @section LICENSE
 *
 * @section DESCRIPTION
 *
 */

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

#include "EuclideanVector.h"
#include "EuclideanVector3D.h"
#include "Error.h"
#include "Broadcast.h"
#include <iostream>

namespace utf = boost::unit_test;

using namespace cupcfd::geometry::euclidean;

// 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;

    MPI_Init(&argc, &argv);
}

// === Constructor1 ===
// Test 1: Check all values set to zero - 2D
BOOST_AUTO_TEST_CASE(constructor1_test1)
{
	EuclideanVector<double, 2> vec;

	BOOST_CHECK_EQUAL(vec.cmp[0], 0.0);
	BOOST_CHECK_EQUAL(vec.cmp[1], 0.0);
	BOOST_CHECK_EQUAL(vec.isRegistered(), false);
}

// Test 2: Check all values set to zero - 3D
BOOST_AUTO_TEST_CASE(constructor1_test2)
{
	EuclideanVector<double, 3> vec;

	BOOST_CHECK_EQUAL(vec.cmp[0], 0.0);
	BOOST_CHECK_EQUAL(vec.cmp[1], 0.0);
	BOOST_CHECK_EQUAL(vec.cmp[2], 0.0);
	BOOST_CHECK_EQUAL(vec.isRegistered(), false);
}

// === Constructor2 ===
// Test 1: Check values set to match parameters - 2D
BOOST_AUTO_TEST_CASE(constructor2_test1)
{
	EuclideanVector<double, 2> vec(2.0, 3.0);

	BOOST_CHECK_EQUAL(vec.cmp[0], 2.0);
	BOOST_CHECK_EQUAL(vec.cmp[1], 3.0);
	BOOST_CHECK_EQUAL(vec.isRegistered(), false);
}

// Test 2: Check values set to match parameters - 3D
BOOST_AUTO_TEST_CASE(constructor2_test2)
{
	EuclideanVector<double, 3> vec(2.0, 3.0, 4.0);

	BOOST_CHECK_EQUAL(vec.cmp[0], 2.0);
	BOOST_CHECK_EQUAL(vec.cmp[1], 3.0);
	BOOST_CHECK_EQUAL(vec.cmp[2], 4.0);
	BOOST_CHECK_EQUAL(vec.isRegistered(), false);
}

// === Operator= Vector ===
// Test 1: Check values are appropriately copied form src to dst - 2D
BOOST_AUTO_TEST_CASE(operator_assign_vector_test1)
{
	EuclideanVector<double, 2> srcVec(2.0, 3.0);
	EuclideanVector<double, 2> dstVec;

	dstVec = srcVec;

	srcVec.cmp[1] = -15.0;

	BOOST_CHECK_EQUAL(dstVec.cmp[0], 2.0);
	BOOST_CHECK_EQUAL(dstVec.cmp[1], 3.0);
}

// Test 2: Check values are appropriately copied form src to dst - 3D
BOOST_AUTO_TEST_CASE(operator_assign_vector_test2)
{
	EuclideanVector<double, 3> srcVec(2.0, 3.0, 4.0);
	EuclideanVector<double, 3> dstVec;

	dstVec = srcVec;

	srcVec.cmp[1] = -15.0;

	BOOST_CHECK_EQUAL(dstVec.cmp[0], 2.0);
	BOOST_CHECK_EQUAL(dstVec.cmp[1], 3.0);
	BOOST_CHECK_EQUAL(dstVec.cmp[2], 4.0);
}

// === Operator= Scalar ===
// Test 1: Check values are all set to same as scalar - 2D
BOOST_AUTO_TEST_CASE(operator_assign_scalar_test1)
{
	EuclideanVector<double, 2> vec;

	vec = 5.0;

	BOOST_CHECK_EQUAL(vec.cmp[0], 5.0);
	BOOST_CHECK_EQUAL(vec.cmp[1], 5.0);
}

// Test 2: Check values are all set to same as scalar - 3D
BOOST_AUTO_TEST_CASE(operator_assign_scalar_test2)
{
	EuclideanVector<double, 3> vec;

	vec = 5.0;

	BOOST_CHECK_EQUAL(vec.cmp[0], 5.0);
	BOOST_CHECK_EQUAL(vec.cmp[1], 5.0);
	BOOST_CHECK_EQUAL(vec.cmp[2], 5.0);
}

// === operator== ===
// Test 1: Test that vecs match
BOOST_AUTO_TEST_CASE(operator_equivalence_test1)
{
	EuclideanVector<double, 3> vec(1.0, 2.0, 3.0);
	EuclideanVector<double, 3> vec2(1.0, 2.0, 3.0);

	BOOST_CHECK_EQUAL(vec == vec2, true);
}

// Test 2: Test that vecs don't match
BOOST_AUTO_TEST_CASE(operator_equivalence_test2)
{
	EuclideanVector<double, 3> vec(1.0, 2.0, 3.0);
	EuclideanVector<double, 3> vec2(2.0, 2.0, 3.0);
	EuclideanVector<double, 3> vec3(1.0, 3.0, 3.0);
	EuclideanVector<double, 3> vec4(1.0, 2.0, 4.0);

	BOOST_CHECK_EQUAL(vec == vec2, false);
	BOOST_CHECK_EQUAL(vec == vec3, false);
	BOOST_CHECK_EQUAL(vec == vec4, false);
}

// === Operator+ Vector ===
// Test 1: Check values are appropriately summed - 2D
BOOST_AUTO_TEST_CASE(operator_add_vector_test1)
{
	EuclideanVector<double, 2> vec1(5.0, 8.0);
	EuclideanVector<double, 2> vec2(1.2, 4.5);

	EuclideanVector<double, 2> vec3 = vec1 + vec2;

	BOOST_CHECK_EQUAL(vec3.cmp[0], 6.2);
	BOOST_CHECK_EQUAL(vec3.cmp[1], 12.5);
}

// Test 2: Check values are appropriately summed - 3D
BOOST_AUTO_TEST_CASE(operator_add_vector_test2)
{
	EuclideanVector<double, 3> vec1(5.0, 8.0, 9.5);
	EuclideanVector<double, 3> vec2(1.2, 4.5, 19.3);

	EuclideanVector<double, 3> vec3 = vec1 + vec2;

	BOOST_CHECK_EQUAL(vec3.cmp[0], 6.2);
	BOOST_CHECK_EQUAL(vec3.cmp[1], 12.5);
	BOOST_CHECK_EQUAL(vec3.cmp[2], 28.8);
}

// === Operator+ Scalar ===
// Test 1: Check scalars are appropriately added when vector on left - 2D
BOOST_AUTO_TEST_CASE(operator_add_scalar_test1)
{
	EuclideanVector<double, 2> vec1(5.0, 8.0);
	double scalar = 5.0;

	EuclideanVector<double, 2> vec3 = vec1 + scalar;

	BOOST_CHECK_EQUAL(vec3.cmp[0], 10.0);
	BOOST_CHECK_EQUAL(vec3.cmp[1], 13.0);
}

// Test 2: Check scalars are appropriately added when vector on right - 2D
BOOST_AUTO_TEST_CASE(operator_add_scalar_test2)
{
	EuclideanVector<double, 2> vec1(5.0, 8.0);
	double scalar = 5.0;

	EuclideanVector<double, 2> vec3 = scalar + vec1;

	BOOST_CHECK_EQUAL(vec3.cmp[0], 10.0);
	BOOST_CHECK_EQUAL(vec3.cmp[1], 13.0);
}

// Test 3: Check scalars are appropriately added when vector on left - 3D
BOOST_AUTO_TEST_CASE(operator_add_scalar_test3)
{
	EuclideanVector<double, 3> vec1(5.0, 8.0, 9.5);
	double scalar = 5.0;

	EuclideanVector<double, 3> vec3 = vec1 + scalar;

	BOOST_CHECK_EQUAL(vec3.cmp[0], 10.0);
	BOOST_CHECK_EQUAL(vec3.cmp[1], 13.0);
	BOOST_CHECK_EQUAL(vec3.cmp[2], 14.5);
}

// Test 4: Check scalars are appropriately added when vector on right - 3D
BOOST_AUTO_TEST_CASE(operator_add_scalar_test4)
{
	EuclideanVector<double, 3> vec1(5.0, 8.0, 9.5);
	double scalar = 5.0;

	EuclideanVector<double, 3> vec3 = scalar + vec1;

	BOOST_CHECK_EQUAL(vec3.cmp[0], 10.0);
	BOOST_CHECK_EQUAL(vec3.cmp[1], 13.0);
	BOOST_CHECK_EQUAL(vec3.cmp[2], 14.5);
}

// === Operator- Vector ===
// Test 1: Check values are appropriately subtracted - 2D
BOOST_AUTO_TEST_CASE(operator_subtract_vector_test1)
{
	EuclideanVector<double,2> vec1(5.0, 8.0);
	EuclideanVector<double,2> vec2(1.2, 4.5);

	EuclideanVector<double,2> vec3 = vec1 - vec2;

	BOOST_CHECK_EQUAL(vec3.cmp[0], 3.8);
	BOOST_CHECK_EQUAL(vec3.cmp[1], 3.5);
}

// Test 2: Check values are appropriately subtracted - 3D
BOOST_AUTO_TEST_CASE(operator_subtract_vector_test2)
{
	EuclideanVector<double,3> vec1(5.0, 8.0, 9.5);
	EuclideanVector<double,3> vec2(1.2, 4.5, 19.3);

	EuclideanVector<double,3> vec3 = vec1 - vec2;

	BOOST_CHECK_EQUAL(vec3.cmp[0], 3.8);
	BOOST_CHECK_EQUAL(vec3.cmp[1], 3.5);
	BOOST_CHECK_EQUAL(vec3.cmp[2], -9.8);
}

// === Operator- Scalar ===
// Test 1: Check scalar values are subtracted when vector on left - 2D
BOOST_AUTO_TEST_CASE(operator_subtract_scalar_minus_test1,  * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,2> vec1(5.0, 8.0);
	double scalar = 5.0;

	EuclideanVector<double,2> vec3 = vec1 - scalar;

	BOOST_TEST(vec3.cmp[0] == 0.0);
	BOOST_TEST(vec3.cmp[1] == 3.0);
}

// Test 2: Check scalar values are subtracted when vector on right - 2D
BOOST_AUTO_TEST_CASE(operator_subtract_scalar_minus_test2,  * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,2> vec1(5.0, 8.0);
	double scalar = 5.0;

	EuclideanVector<double,2> vec3 = scalar - vec1;

	BOOST_TEST(vec3.cmp[0] == 0.0);
	BOOST_TEST(vec3.cmp[1] == -3.0);
}

// Test 3: Check scalar values are subtracted when vector on left - 3D
BOOST_AUTO_TEST_CASE(operator_subtract_scalar_minus_test3,  * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,3> vec1(5.0, 8.0, 9.5);
	double scalar = 5.0;

	EuclideanVector<double,3> vec3 = vec1 - scalar;

	BOOST_TEST(vec3.cmp[0] == 0.0);
	BOOST_TEST(vec3.cmp[1] == 3.0);
	BOOST_TEST(vec3.cmp[2] == 4.5);
}

// Test 4: Check scalar values are subtracted when vector on right - 3D
BOOST_AUTO_TEST_CASE(operator_subtract_scalar_minus_test4,  * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,3> vec1(5.0, 8.0, 9.5);
	double scalar = 5.0;

	EuclideanVector<double,3> vec3 = scalar - vec1;

	BOOST_TEST(vec3.cmp[0] == 0.0);
	BOOST_TEST(vec3.cmp[1] == -3.0);
	BOOST_TEST(vec3.cmp[2] == -4.5);
}

// === Operator* Scalar ===
// Test 1: Check scalar values are multiplied when vector on left - 2D
BOOST_AUTO_TEST_CASE(operator_subtract_scalar_multiply_test1,  * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,2> vec1(5.0, 8.0);
	double scalar = 5.0;

	EuclideanVector<double,2> vec3 = vec1 * scalar;

	BOOST_TEST(vec3.cmp[0] == 25.0);
	BOOST_TEST(vec3.cmp[1] == 40.0);
}

// Test 2: Check scalar values are multiplied when vector on right - 2D
BOOST_AUTO_TEST_CASE(operator_subtract_scalar_multiply_test2,  * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,2> vec1(5.0, 8.0);
	double scalar = 5.0;

	EuclideanVector<double,2> vec3 = scalar * vec1;

	BOOST_TEST(vec3.cmp[0] == 25.0);
	BOOST_TEST(vec3.cmp[1] == 40.0);
}

// Test 3: Check scalar values are multiplied when vector on left - 3D
BOOST_AUTO_TEST_CASE(operator_subtract_scalar_multiply_test3,  * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,3> vec1(5.0, 8.0, 9.5);
	double scalar = 5.0;

	EuclideanVector<double,3> vec3 = vec1 * scalar;

	BOOST_TEST(vec3.cmp[0] == 25.0);
	BOOST_TEST(vec3.cmp[1] == 40.0);
	BOOST_TEST(vec3.cmp[2] == 47.5);
}

// Test 4: Check scalar values are multiplied when vector on right - 3D
BOOST_AUTO_TEST_CASE(operator_subtract_scalar_multiply_test4,  * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,3> vec1(5.0, 8.0, 9.5);
	double scalar = 5.0;

	EuclideanVector<double,3> vec3 = scalar * vec1;

	BOOST_TEST(vec3.cmp[0] == 25.0);
	BOOST_TEST(vec3.cmp[1] == 40.0);
	BOOST_TEST(vec3.cmp[2] == 47.5);
}

// === Operator/ Scalar ===
// Test 1: Check scalar values are multiplied when vector on left - 2D
BOOST_AUTO_TEST_CASE(operator_subtract_scalar_divide_test1,  * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,2> vec1(5.0, 10.0);
	double scalar = 5.0;

	EuclideanVector<double,2> vec3 = vec1 / scalar;

	BOOST_TEST(vec3.cmp[0] == 1.0);
	BOOST_TEST(vec3.cmp[1] == 2.0);
}

// Test 2: Check scalar values are multiplied when vector on right - 2D
BOOST_AUTO_TEST_CASE(operator_subtract_scalar_divide_test2,  * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,2> vec1(5.0, 10.0);
	double scalar = 5.0;

	EuclideanVector<double,2> vec3 = scalar / vec1;

	BOOST_TEST(vec3.cmp[0] == 1.0);
	BOOST_TEST(vec3.cmp[1] == 0.5);
}

// Test 3: Check scalar values are multiplied when vector on left - 3D
BOOST_AUTO_TEST_CASE(operator_subtract_scalar_divide_test3,  * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,3> vec1(5.0, 10.0, 9.5);
	double scalar = 5.0;

	EuclideanVector<double,3> vec3 = vec1 / scalar;

	BOOST_TEST(vec3.cmp[0] == 1.0);
	BOOST_TEST(vec3.cmp[1] == 2.0);
	BOOST_TEST(vec3.cmp[2] == 1.9);
}

// Test 4: Check scalar values are multiplied when vector on left - 3D
BOOST_AUTO_TEST_CASE(operator_subtract_scalar_divide_test4,  * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,3> vec1(5.0, 10.0, 9.5);
	double scalar = 5.0;

	EuclideanVector<double,3> vec3 = scalar / vec1;

	BOOST_TEST(vec3.cmp[0] == 1.0);
	BOOST_TEST(vec3.cmp[1] == 0.5);
	BOOST_TEST(vec3.cmp[2] == 0.526315789473684);
}

// === CrossProduct ===

// Test 1: Check Cross-Product is computed correctly - 3D
BOOST_AUTO_TEST_CASE(operator_crossproduct_test1, * utf::tolerance(0.0000000001))
{
	EuclideanVector3D<double> vec1(2.0, 3.0, 4.0);
	EuclideanVector3D<double> vec2(3.5, 6.3, 19.7);
	EuclideanVector3D<double> result;

	result = vec1.crossProduct(vec2);

	BOOST_TEST(result.cmp[0] == 33.9);
	BOOST_TEST(result.cmp[1] == -25.4);
	BOOST_TEST(result.cmp[2] == 2.1);
}

// === rotateXAxisRadian ===
// Test 1: Rotate a vector 1 radian
BOOST_AUTO_TEST_CASE(rotateXAxisRadian_test1, * utf::tolerance(0.00001))
{
	EuclideanVector3D<double> vec(1.4, -3.2, 5.6);

	vec.rotateXAxisRadian(1.0);

	BOOST_TEST(vec.cmp[0] == 1.4);
	BOOST_TEST(vec.cmp[1] == -6.4412);
	BOOST_TEST(vec.cmp[2] == 0.332986);
}

// Test 2: Rotate a vector 90 degrees/1.5708 radians
BOOST_AUTO_TEST_CASE(rotateXAxisRadian_test2, * utf::tolerance(0.00001))
{
	EuclideanVector3D<double> vec(0.0,1.0,0.0);

	vec.rotateXAxisRadian(1.5708);

	BOOST_TEST(vec.cmp[0] == 0.0);
	BOOST_TEST(vec.cmp[1] == 0.0);
	BOOST_TEST(vec.cmp[2] == 1.0);
}

// Test 3: Rotate a vector -90 degrees/-1.5708 radians
BOOST_AUTO_TEST_CASE(rotateXAxisRadian_test3, * utf::tolerance(0.00001))
{
	EuclideanVector3D<double> vec(0.0,1.0,0.0);

	vec.rotateXAxisRadian(-1.5708);

	BOOST_TEST(vec.cmp[0] == 0.0);
	BOOST_TEST(vec.cmp[1] == 0.0);
	BOOST_TEST(vec.cmp[2] == -1.0);
}

// === rotateYAxisRadian ===
// Test 1:
BOOST_AUTO_TEST_CASE(rotateYAxisRadian_test1, * utf::tolerance(0.00001))
{
	EuclideanVector3D<double> vec(1.4, -3.2, 5.6);

	vec.rotateYAxisRadian(1.0);

	BOOST_TEST(vec.cmp[0] == 5.46866);
	BOOST_TEST(vec.cmp[1] == -3.2);
	BOOST_TEST(vec.cmp[2] == 1.84763);
}

// Test 2: Rotate a vector 90 degrees/1.5708 radians
BOOST_AUTO_TEST_CASE(rotateYAxisRadian_test2, * utf::tolerance(0.00001))
{
	EuclideanVector3D<double> vec(1.0,0.0,0.0);

	vec.rotateYAxisRadian(1.5708);

	BOOST_TEST(vec.cmp[0] == 0.0);
	BOOST_TEST(vec.cmp[1] == 0.0);
	BOOST_TEST(vec.cmp[2] == -1.0);
}

// Test 3: Rotate a vector -90 degrees/1.5708 radians
BOOST_AUTO_TEST_CASE(rotateYAxisRadian_test3, * utf::tolerance(0.00001))
{
	EuclideanVector3D<double> vec(1.0,0.0,0.0);

	vec.rotateYAxisRadian(-1.5708);

	BOOST_TEST(vec.cmp[0] == 0.0);
	BOOST_TEST(vec.cmp[1] == 0.0);
	BOOST_TEST(vec.cmp[2] == 1.0);
}

// === rotateZAxisRadian ===
// Test 1:
BOOST_AUTO_TEST_CASE(rotateZAxisRadian_test1, * utf::tolerance(0.00001))
{
	EuclideanVector3D<double> vec(1.4, 3.2, 5.6);

	vec.rotateZAxisRadian(1.0);

	BOOST_TEST(vec.cmp[0] == -1.93629);
	BOOST_TEST(vec.cmp[1] == 2.90703);
	BOOST_TEST(vec.cmp[2] == 5.6);
}

// Test 2: Rotate a unit vector
BOOST_AUTO_TEST_CASE(rotateZAxisRadian_test2, * utf::tolerance(0.00001))
{
	EuclideanVector3D<double> vec(1.0,0.0,0.0);

	vec.rotateZAxisRadian(1.5708);

	// Vector was pointing along X axis, should now be pointing up along Y-axis
	BOOST_TEST(vec.cmp[0] == 0.0);
	BOOST_TEST(vec.cmp[1] == 1.0);
	BOOST_TEST(vec.cmp[2] == 0.0);
}

// Test 3: Rotate a unit vector
BOOST_AUTO_TEST_CASE(rotateZAxisRadian_test3, * utf::tolerance(0.00001))
{
	EuclideanVector3D<double> vec(1.0,0.0,0.0);

	vec.rotateZAxisRadian(-1.5708);

	// Vector was pointing along X axis, should now be pointing up along Y-axis
	BOOST_TEST(vec.cmp[0] == 0.0);
	BOOST_TEST(vec.cmp[1] == -1.0);
	BOOST_TEST(vec.cmp[2] == 0.0);
}

// === isParallel ===
// Test 1: Test that parallel vectors detected - 2D
BOOST_AUTO_TEST_CASE(isParallel_test1, * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,2> vec1(2.0, 3.0);
	EuclideanVector<double,2> vec2(4.0, 6.0);

	bool isParallel = vec1.isParallel(vec2);
	BOOST_CHECK_EQUAL(isParallel, true);
}

// Test 2: Test that parallel vectors detected even with inverted sign - 2D
BOOST_AUTO_TEST_CASE(isParallel_test2, * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,2> vec1(2.0, 3.0);
	EuclideanVector<double,2> vec2(-4.0, -6.0);

	bool isParallel = vec1.isParallel(vec2);
	BOOST_CHECK_EQUAL(isParallel, true);
}

// Test 3: Test that parallel vectors detected - 3D
BOOST_AUTO_TEST_CASE(isParallel_test3, * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,3> vec1(2.0, 3.0, 4.0);
	EuclideanVector<double,3> vec2(4.0, 6.0, 8.0);

	bool isParallel = vec1.isParallel(vec2);
	BOOST_CHECK_EQUAL(isParallel, true);
}

// Test 4: Test that non-parallel vectors identified correctly - 2D
BOOST_AUTO_TEST_CASE(isParallel_test4, * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,2> vec1(2.0, 3.0);
	EuclideanVector<double,2> vec2(2.0, 3.5);

	bool isParallel = vec1.isParallel(vec2);
	BOOST_CHECK_EQUAL(isParallel, false);
}

// Test 5: Test that non-parallel vectors identified correctly - 3D
BOOST_AUTO_TEST_CASE(isParallel_test5, * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,3> vec1(2.0, 3.0, 7.6);
	EuclideanVector<double,3> vec2(4.0, 6.0, 8.9);

	bool isParallel = vec1.isParallel(vec2);
	BOOST_CHECK_EQUAL(isParallel, false);
}

// Test 6: Test that parallel vectors detected even if first component is zero - 3D
BOOST_AUTO_TEST_CASE(isParallel_test6, * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,3> vec1(0.0, 3.0, 4.0);
	EuclideanVector<double,3> vec2(0.0, 6.0, 8.0);

	bool isParallel = vec1.isParallel(vec2);
	BOOST_CHECK_EQUAL(isParallel, true);
}

// Test 7: Test that non-parallel vectors detected even if first component is zero - 3D
BOOST_AUTO_TEST_CASE(isParallel_test7, * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,3> vec1(0.0, 3.0, 7.6);
	EuclideanVector<double,3> vec2(0.0, 6.0, 8.9);

	bool isParallel = vec1.isParallel(vec2);
	BOOST_CHECK_EQUAL(isParallel, false);
}

// Test 8: Test that parallel vectors detected even if non-first component is zero - 3D
BOOST_AUTO_TEST_CASE(isParallel_test8, * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,3> vec1(2.0, 0.0, 4.0);
	EuclideanVector<double,3> vec2(4.0, 0.0, 8.0);

	bool isParallel = vec1.isParallel(vec2);
	BOOST_CHECK_EQUAL(isParallel, true);
}

// Test 9: Test that non-parallel vectors detected even if non-first component is zero - 3D
BOOST_AUTO_TEST_CASE(isParallel_test9, * utf::tolerance(0.0000000001))
{
	EuclideanVector<double,3> vec1(2.0, 0.0, 7.6);
	EuclideanVector<double,3> vec2(4.0, 0.0, 8.9);

	bool isParallel = vec1.isParallel(vec2);
	BOOST_CHECK_EQUAL(isParallel, false);
}

// === isPointOnLine ===
// Test 1: Test that point is detected on the line
BOOST_AUTO_TEST_CASE(isPointOnLine_test1, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(2.0, 3.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 6.0, 10.0);
	EuclideanPoint<double,3> point(3.0, 4.5, 7.5);

	bool isPointOnLine = cupcfd::geometry::euclidean::isPointOnLine(x1, x2, point);
	BOOST_CHECK_EQUAL(isPointOnLine, true);
}

// Test 2: Test that x1 is detected as on the line
BOOST_AUTO_TEST_CASE(isPointOnLine_test2, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(2.0, 3.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 6.0, 10.0);

	bool isPointOnLine = cupcfd::geometry::euclidean::isPointOnLine(x1, x2, x1);
	BOOST_CHECK_EQUAL(isPointOnLine, true);
}

// Test 3: Test that x2 is detected as on the line
BOOST_AUTO_TEST_CASE(isPointOnLine_test3, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(2.0, 3.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 6.0, 10.0);

	bool isPointOnLine = cupcfd::geometry::euclidean::isPointOnLine(x1, x2, x2);
	BOOST_CHECK_EQUAL(isPointOnLine, true);
}

// Test 4: Test that a point not on the line is detected as such
BOOST_AUTO_TEST_CASE(isPointOnLine_test4, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(2.0, 3.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 6.0, 10.0);
	EuclideanPoint<double,3> point(2.0, 3.0, 8.12);

	bool isPointOnLine = cupcfd::geometry::euclidean::isPointOnLine(x1, x2, point);
	BOOST_CHECK_EQUAL(isPointOnLine, false);
}

// Test 1: Test that point is detected on the line when there is a zero component to the vector (i.e. on a axis plane)
BOOST_AUTO_TEST_CASE(isPointOnLine_test5, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(2.0, 3.0, 5.0);
	EuclideanPoint<double,3> x2(2.0, 6.0, 10.0);
	EuclideanPoint<double,3> point(2.0, 4.5, 7.5);

	bool isPointOnLine = cupcfd::geometry::euclidean::isPointOnLine(x1, x2, point);
	BOOST_CHECK_EQUAL(isPointOnLine, true);
}

// === isVectorRangeIntersection (3D) ===
// Test 1: Test that two vectors are detected as intersecting - 3D
BOOST_AUTO_TEST_CASE(isVectorRangeIntersection_test1, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(7.0, 8.0, 6.0);
	EuclideanPoint<double,3> x3(7.0, 4.0, 5.0);
	EuclideanPoint<double,3> x4(3.0, 8.0, 6.0);

	// Should Intersect at 5.0, 6.0, 5.5
	bool intersect = cupcfd::geometry::euclidean::isVectorRangeIntersection(x1, x2, x3, x4);
	BOOST_CHECK_EQUAL(intersect, true);
}

// Test 2: Test two vectors that are not coplanar so don't intersect - 3D
BOOST_AUTO_TEST_CASE(isVectorRangeIntersection_test2, * utf::tolerance(0.0000000001))
{

	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(7.0, 8.0, 6.0);
	EuclideanPoint<double,3> x3(7.0, 4.0, 1.3);
	EuclideanPoint<double,3> x4(3.0, 8.0, 3.4);

	bool intersect = cupcfd::geometry::euclidean::isVectorRangeIntersection(x1, x2, x3, x4);

	BOOST_CHECK_EQUAL(intersect, false);
}

// Test 3: Test that two vectors would intersect, but don't because the range of vector 1 is too short - 3D
BOOST_AUTO_TEST_CASE(isVectorRangeIntersection_test3, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 5.0, 5.25);
	EuclideanPoint<double,3> x3(7.0, 4.0, 5.0);
	EuclideanPoint<double,3> x4(3.0, 8.0, 6.0);

	bool intersect = cupcfd::geometry::euclidean::isVectorRangeIntersection(x1, x2, x3, x4);
	BOOST_CHECK_EQUAL(intersect, false);
}

// Test 4: Test that two vectors would intersect, but don't because the range of vector 2 is too short - 3D
BOOST_AUTO_TEST_CASE(isVectorRangeIntersection_test4, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(7.0, 8.0, 6.0);
	EuclideanPoint<double,3> x3(4.0, 7.0, 5.75);
	EuclideanPoint<double,3> x4(3.0, 8.0, 6.0);

	bool intersect = cupcfd::geometry::euclidean::isVectorRangeIntersection(x1, x2, x3, x4);
	BOOST_CHECK_EQUAL(intersect, false);
}

// Test 5: Test two vectors that are coplanar but the intersect point is outside both ranges
BOOST_AUTO_TEST_CASE(isVectorRangeIntersection_test5, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 5.0, 5.25);
	EuclideanPoint<double,3> x3(4.0, 7.0, 5.75);
	EuclideanPoint<double,3> x4(3.0, 8.0, 6.0);

	bool intersect = cupcfd::geometry::euclidean::isVectorRangeIntersection(x1, x2, x3, x4);
	BOOST_CHECK_EQUAL(intersect, false);
}

// Test 6: Test that two lines that are colinear but don't overlap so don't intersect - 3D
BOOST_AUTO_TEST_CASE(isVectorRangeIntersection_test6, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 5.0, 6.0);

	EuclideanPoint<double,3> x3(9.0, 10.0, 11.0);
	EuclideanPoint<double,3> x4(15.0, 16.0, 17.0);

	bool intersect = cupcfd::geometry::euclidean::isVectorRangeIntersection(x1, x2, x3, x4);
	BOOST_CHECK_EQUAL(intersect, false);
}

// Test 7: Test that two lines that are colinear and do overlap are considered as intersecting - 3D
BOOST_AUTO_TEST_CASE(isVectorRangeIntersection_test7, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 5.0, 6.0);

	EuclideanPoint<double,3> x3(3.5, 4.5, 5.5);
	EuclideanPoint<double,3> x4(15.0, 16.0, 17.0);

	bool intersect = cupcfd::geometry::euclidean::isVectorRangeIntersection(x1, x2, x3, x4);
	BOOST_CHECK_EQUAL(intersect, true);
}

// Test 8: Test two vectors that are parallel but never overlap
BOOST_AUTO_TEST_CASE(isVectorRangeIntersection_test8, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 5.0, 6.0);

	EuclideanPoint<double,3> x3(6.0, 7.8, 2.3);
	EuclideanPoint<double,3> x4(7.0, 8.8, 3.3);

	bool intersect = cupcfd::geometry::euclidean::isVectorRangeIntersection(x1, x2, x3, x4);
	BOOST_CHECK_EQUAL(intersect, false);
}

// Test 9: Test that two lines that are colinear but don't overlap so don't intersect (one is in opposite direction) - 3D
// ToDo: This test case fails: Detects as colinear but thinks they overlap?
BOOST_AUTO_TEST_CASE(isVectorRangeIntersection_test9, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 5.0, 6.0);

	EuclideanPoint<double,3> x3(15.0, 16.0, 17.0);
	EuclideanPoint<double,3> x4(9.0, 10.0, 11.0);

	bool intersect = cupcfd::geometry::euclidean::isVectorRangeIntersection(x1, x2, x3, x4);
	BOOST_CHECK_EQUAL(intersect, false);
}

// Test 10: Test that two lines that are colinear and do overlap are considered as intersecting (one is in opposite direction) - 3D
BOOST_AUTO_TEST_CASE(isVectorRangeIntersection_test10, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 5.0, 6.0);

	EuclideanPoint<double,3> x3(15.0, 16.0, 17.0);
	EuclideanPoint<double,3> x4(3.5, 4.5, 5.5);

	bool intersect = cupcfd::geometry::euclidean::isVectorRangeIntersection(x1, x2, x3, x4);
	BOOST_CHECK_EQUAL(intersect, true);
}

// Test 11: Both ranges are just the same point
BOOST_AUTO_TEST_CASE(isVectorRangeIntersection_test11, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(3.0, 4.0, 5.0);

	EuclideanPoint<double,3> x3(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x4(3.0, 4.0, 5.0);

	bool intersect = cupcfd::geometry::euclidean::isVectorRangeIntersection(x1, x2, x3, x4);
	BOOST_CHECK_EQUAL(intersect, true);
}

// Test 12: Both ranges are just points, but not the same point
BOOST_AUTO_TEST_CASE(isVectorRangeIntersection_test12, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(3.0, 4.0, 5.0);

	EuclideanPoint<double,3> x3(4.0, 5.0, 7.0);
	EuclideanPoint<double,3> x4(4.0, 5.0, 7.0);

	bool intersect = cupcfd::geometry::euclidean::isVectorRangeIntersection(x1, x2, x3, x4);
	BOOST_CHECK_EQUAL(intersect, false);
}

// Test 13: Range 1 is a point, Range 2 is a line, the point does not lie on the line
BOOST_AUTO_TEST_CASE(isVectorRangeIntersection_test13, * utf::tolerance(0.0000000001))
{

}

// Test 14: Range 1 is a point, Range 2 is a line, the point does lie on the line
BOOST_AUTO_TEST_CASE(isVectorRangeIntersection_test14, * utf::tolerance(0.0000000001))
{

}

// Test 15: Range 2 is a point, Range 1 is a line, the point does not lie on the line
BOOST_AUTO_TEST_CASE(isVectorRangeIntersection_test15, * utf::tolerance(0.0000000001))
{

}

// Test 16: Range 2 is a point, Range 1 is a line, the point does lie on the line
BOOST_AUTO_TEST_CASE(isVectorRangeIntersection_test16, * utf::tolerance(0.0000000001))
{

}

// === computeVectorRangeIntersection ===
// Test 1: Test that two vectors are detected as intersecting - 3D
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test1, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(7.0, 8.0, 6.0);
	EuclideanPoint<double,3> x3(7.0, 4.0, 5.0);
	EuclideanPoint<double,3> x4(3.0, 8.0, 6.0);
	EuclideanPoint<double,3> intersectPoint;

	// Should Intersect at 5.0, 6.0, 5.5
	cupcfd::error::eCodes status = cupcfd::geometry::euclidean::computeVectorRangeIntersection(x1, x2, x3, x4, intersectPoint);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_TEST(intersectPoint.cmp[0] == 5.0);
	BOOST_TEST(intersectPoint.cmp[1] == 6.0);
	BOOST_TEST(intersectPoint.cmp[2] == 5.5);
}

// Test 2: Test two vectors that are not coplanar so don't intersect - 3D
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test2, * utf::tolerance(0.0000000001))
{

	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(7.0, 8.0, 6.0);
	EuclideanPoint<double,3> x3(7.0, 4.0, 1.3);
	EuclideanPoint<double,3> x4(3.0, 8.0, 3.4);
	EuclideanPoint<double,3> intersectPoint;

	cupcfd::error::eCodes status = cupcfd::geometry::euclidean::computeVectorRangeIntersection(x1, x2, x3, x4, intersectPoint);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_GEOMETRY_NO_INTERSECT);
}

// Test 3: Test that two vectors would intersect, but don't because the range of vector 1 is too short - 3D
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test3, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 5.0, 5.25);
	EuclideanPoint<double,3> x3(7.0, 4.0, 5.0);
	EuclideanPoint<double,3> x4(3.0, 8.0, 6.0);
	EuclideanPoint<double,3> intersectPoint;

	cupcfd::error::eCodes status = cupcfd::geometry::euclidean::computeVectorRangeIntersection(x1, x2, x3, x4, intersectPoint);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_GEOMETRY_NO_INTERSECT);
}

// Test 4: Test that two vectors would intersect, but don't because the range of vector 2 is too short - 3D
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test4, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(7.0, 8.0, 6.0);
	EuclideanPoint<double,3> x3(4.0, 7.0, 5.75);
	EuclideanPoint<double,3> x4(3.0, 8.0, 6.0);
	EuclideanPoint<double,3> intersectPoint;

	cupcfd::error::eCodes status = cupcfd::geometry::euclidean::computeVectorRangeIntersection(x1, x2, x3, x4, intersectPoint);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_GEOMETRY_NO_INTERSECT);
}

// Test 5: Test two vectors that are coplanar but the intersect point is outside both ranges
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test5, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 5.0, 5.25);
	EuclideanPoint<double,3> x3(4.0, 7.0, 5.75);
	EuclideanPoint<double,3> x4(3.0, 8.0, 6.0);
	EuclideanPoint<double,3> intersectPoint;

	cupcfd::error::eCodes status = cupcfd::geometry::euclidean::computeVectorRangeIntersection(x1, x2, x3, x4, intersectPoint);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_GEOMETRY_NO_INTERSECT);
}

// Test 6: Test that two lines that are colinear but don't overlap so don't intersect - 3D
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test6, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 5.0, 6.0);
	EuclideanPoint<double,3> x3(9.0, 10.0, 11.0);
	EuclideanPoint<double,3> x4(15.0, 16.0, 17.0);
	EuclideanPoint<double,3> intersectPoint;

	cupcfd::error::eCodes status = cupcfd::geometry::euclidean::computeVectorRangeIntersection(x1, x2, x3, x4, intersectPoint);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_GEOMETRY_NO_INTERSECT);
}

// Test 7: Test that two lines that are colinear and do overlap are considered as intersecting - 3D
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test7, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 5.0, 6.0);
	EuclideanPoint<double,3> x3(3.5, 4.5, 5.5);
	EuclideanPoint<double,3> x4(15.0, 16.0, 17.0);
	EuclideanPoint<double,3> intersectPoint;

	cupcfd::error::eCodes status = cupcfd::geometry::euclidean::computeVectorRangeIntersection(x1, x2, x3, x4, intersectPoint);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_TEST(intersectPoint.cmp[0] == 3.5);
	BOOST_TEST(intersectPoint.cmp[1] == 4.5);
	BOOST_TEST(intersectPoint.cmp[2] == 5.5);
}

// Test 8: Test two vectors that are parallel but never overlap
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test8, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 5.0, 6.0);
	EuclideanPoint<double,3> x3(6.0, 7.8, 2.3);
	EuclideanPoint<double,3> x4(7.0, 8.8, 3.3);
	EuclideanPoint<double,3> intersectPoint;

	cupcfd::error::eCodes status = cupcfd::geometry::euclidean::computeVectorRangeIntersection(x1, x2, x3, x4, intersectPoint);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_GEOMETRY_NO_INTERSECT);
}

// Test 9: Test that two lines that are colinear but don't overlap so don't intersect (one is in opposite direction) - 3D
// ToDo: This test case fails: Detects as colinear but thinks they overlap?
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test9, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 5.0, 6.0);
	EuclideanPoint<double,3> x3(15.0, 16.0, 17.0);
	EuclideanPoint<double,3> x4(9.0, 10.0, 11.0);
	EuclideanPoint<double,3> intersectPoint;

	cupcfd::error::eCodes status = cupcfd::geometry::euclidean::computeVectorRangeIntersection(x1, x2, x3, x4, intersectPoint);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_GEOMETRY_NO_INTERSECT);
}

// Test 10: Test that two lines that are colinear and do overlap are considered as intersecting (one is in opposite direction) - 3D
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test10, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(4.0, 5.0, 6.0);
	EuclideanPoint<double,3> x3(15.0, 16.0, 17.0);
	EuclideanPoint<double,3> x4(3.5, 4.5, 5.5);
	EuclideanPoint<double,3> intersectPoint;

	cupcfd::error::eCodes status = cupcfd::geometry::euclidean::computeVectorRangeIntersection(x1, x2, x3, x4, intersectPoint);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_TEST(intersectPoint.cmp[0] == 3.5);
	BOOST_TEST(intersectPoint.cmp[1] == 4.5);
	BOOST_TEST(intersectPoint.cmp[2] == 5.5);
}

// Test 11: Both ranges are just the same point
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test11, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x3(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x4(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> intersectPoint;

	cupcfd::error::eCodes status = cupcfd::geometry::euclidean::computeVectorRangeIntersection(x1, x2, x3, x4, intersectPoint);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_TEST(intersectPoint.cmp[0] == 3.0);
	BOOST_TEST(intersectPoint.cmp[1] == 4.0);
	BOOST_TEST(intersectPoint.cmp[2] == 5.0);
}

// Test 12: Both ranges are just points, but not the same point
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test12, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x2(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x3(4.0, 5.0, 7.0);
	EuclideanPoint<double,3> x4(4.0, 5.0, 7.0);
	EuclideanPoint<double,3> intersectPoint;

	cupcfd::error::eCodes status = cupcfd::geometry::euclidean::computeVectorRangeIntersection(x1, x2, x3, x4, intersectPoint);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_GEOMETRY_NO_INTERSECT);
}

// Test 13: Range 1 is a point, Range 2 is a line, the point does not lie on the line
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test13, * utf::tolerance(0.0000000001))
{

}

// Test 14: Range 1 is a point, Range 2 is a line, the point does lie on the line
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test14, * utf::tolerance(0.0000000001))
{

}

// Test 15: Range 2 is a point, Range 1 is a line, the point does not lie on the line
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test15, * utf::tolerance(0.0000000001))
{

}

// Test 16: Range 2 is a point, Range 1 is a line, the point does lie on the line
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test16, * utf::tolerance(0.0000000001))
{

}

// Test 17: Correct intersection when higher range is listed first in overlapping colinear setup of oposite directions
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test17, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(15.0, 16.0, 17.0);
	EuclideanPoint<double,3> x2(3.5, 4.5, 5.5);
	EuclideanPoint<double,3> x3(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x4(4.0, 5.0, 6.0);
	EuclideanPoint<double,3> intersectPoint;

	cupcfd::error::eCodes status = cupcfd::geometry::euclidean::computeVectorRangeIntersection(x1, x2, x3, x4, intersectPoint);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_TEST(intersectPoint.cmp[0] == 3.5);
	BOOST_TEST(intersectPoint.cmp[1] == 4.5);
	BOOST_TEST(intersectPoint.cmp[2] == 5.5);
}

// Test 18: Correct intersection when higher range is listed first in overlapping colinear setup
BOOST_AUTO_TEST_CASE(computeVectorRangeIntersection_test18, * utf::tolerance(0.0000000001))
{
	EuclideanPoint<double,3> x1(3.5, 4.5, 5.5);
	EuclideanPoint<double,3> x2(15.0, 16.0, 17.0);
	EuclideanPoint<double,3> x3(3.0, 4.0, 5.0);
	EuclideanPoint<double,3> x4(4.0, 5.0, 6.0);
	EuclideanPoint<double,3> intersectPoint;

	cupcfd::error::eCodes status = cupcfd::geometry::euclidean::computeVectorRangeIntersection(x1, x2, x3, x4, intersectPoint);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_TEST(intersectPoint.cmp[0] == 4.0);
	BOOST_TEST(intersectPoint.cmp[1] == 5.0);
	BOOST_TEST(intersectPoint.cmp[2] == 6.0);
}

// === DotProduct ===

// Test 1: Test correct dot product - 2D
BOOST_AUTO_TEST_CASE(dotproduct_test1, * utf::tolerance(0.01))
{
	EuclideanVector<double,2> vec1(2.0, 3.0);
	EuclideanVector<double,2> vec2(3.5, 6.3);
	double result = vec1.dotProduct(vec2);

	BOOST_TEST(result == 25.9);
}

// Test 2: Test correct dot product - 3D
BOOST_AUTO_TEST_CASE(dotproduct_test2, * utf::tolerance(0.01))
{
	EuclideanVector<double,3> vec1(2.0, 3.0, 4.0);
	EuclideanVector<double,3> vec2(3.5, 6.3, 19.7);
	double result = vec1.dotProduct(vec2);

	BOOST_TEST(result == 104.7);
}

// === length ===
// Test 1: Test correct length compute, double - 2D
BOOST_AUTO_TEST_CASE(length_test1, * utf::tolerance(0.00001))
{
	EuclideanVector<double,2> vec1(3.5, 6.3);
	double length = vec1.length();
	BOOST_TEST(length == 7.20694);
}

// Test 2: Test correct length compute, double - 3D
BOOST_AUTO_TEST_CASE(length_test2, * utf::tolerance(0.0001))
{
	EuclideanVector<double,3> vec1(3.5, 6.3, 19.7);
	double length = vec1.length();
	BOOST_TEST(length == 20.9769);
}

// === normalise ===
// Test 1: Test in-place normalisation - 2D
BOOST_AUTO_TEST_CASE(normalise_test1, * utf::tolerance(0.000001))
{
	// Setup
	EuclideanVector<double,2> vec(3.5, 6.3);
	vec.normalise();
	BOOST_TEST(vec.cmp[0] == 0.485643);
	BOOST_TEST(vec.cmp[1] == 0.874157);
}

// Test 2: Test in-place normalisation - 3D
BOOST_AUTO_TEST_CASE(normalise_test2, * utf::tolerance(0.00001))
{
	// Setup
	EuclideanVector<double,3> vec(3.5, 6.3, 19.7);
	vec.normalise();
	BOOST_TEST(vec.cmp[0] == 0.16685);
	BOOST_TEST(vec.cmp[1] == 0.30033);
	BOOST_TEST(vec.cmp[2] == 0.939129);
}

// === computeOrthagonalVector ===
// Test 1: Compute Orthagonal Vector
BOOST_AUTO_TEST_CASE(computeOrthagonalVector_test1, * utf::tolerance(0.00001))
{
	EuclideanVector<double,3> vec1(3.5, 6.3, 19.7);

	EuclideanVector<double,3> vec2 = vec1.computeOrthagonalVector();

	// Since the solution could technically be one of many things, test using dot product
	double dotProd = vec1.dotProduct(vec2);
	BOOST_TEST(dotProd == 0.0);
}

// Test 2: Compute Orthagonal Vector when first component of vector is Zero
// (I.e. should use another position as the unknown coefficient)
BOOST_AUTO_TEST_CASE(computeOrthagonalVector_test2, * utf::tolerance(0.00001))
{
	EuclideanVector<double,3> vec1(0.0, 6.3, 19.7);

	EuclideanVector<double,3> vec2 = vec1.computeOrthagonalVector();

	// Since the solution could technically be one of many things, test using dot product
	double dotProd = vec1.dotProduct(vec2);
	BOOST_TEST(dotProd == 0.0);
}

// === isRegistered ===
// Not sure quite where to/when to place this test....
// Should use a setter/getter to set/get registered?

// === register and deregisterMPIType ===
// Test 1: Test that Type is registered without error, and can be deregistered without error
BOOST_AUTO_TEST_CASE(registerMPIType_test1, * utf::tolerance(0.00001))
{
	cupcfd::error::eCodes status;
	EuclideanVector<double,3> vec;

	status = vec.registerMPIType();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	status = vec.deregisterMPIType();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
}

// Test 2: Test that if it is already registered, it does not register again
BOOST_AUTO_TEST_CASE(registerMPIType_test2, * utf::tolerance(0.00001))
{
	cupcfd::error::eCodes status;
	EuclideanVector<double,3> vec;

	status = vec.registerMPIType();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	status = vec.registerMPIType();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_MPI_DATATYPE_REGISTERED);

	// Cleanup for future tests since we use a static variable!
	status = vec.deregisterMPIType();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
}

// Test 3: Test that if unregistered, we cannot deregister again
BOOST_AUTO_TEST_CASE(registerMPIType_test3, * utf::tolerance(0.00001))
{
	cupcfd::error::eCodes status;
	EuclideanVector<double,3> vec;

	BOOST_CHECK_EQUAL(vec.isRegistered(), false);
	status = vec.deregisterMPIType();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_MPI_DATATYPE_UNREGISTERED);
}

// === getMPIType ===
// Test 1: Test that corrected ID is returned for the registered type with error code
BOOST_AUTO_TEST_CASE(getMPIType_test1, * utf::tolerance(0.00001))
{
	cupcfd::error::eCodes status;
	EuclideanVector<double,3> vec;
	MPI_Datatype dType;

	status = vec.registerMPIType();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	status = vec.getMPIType(&dType);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Cleanup for future tests since we use a static variable!
	status = vec.deregisterMPIType();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
}

// Test 2: Test error case detected when type unregistered
BOOST_AUTO_TEST_CASE(getMPIType_test2, * utf::tolerance(0.00001))
{
	cupcfd::error::eCodes status;
	EuclideanVector<double,3> vec;
	MPI_Datatype dType;

	BOOST_CHECK_EQUAL(vec.isRegistered(), false);
	status = vec.getMPIType(&dType);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_MPI_DATATYPE_UNREGISTERED);
}

// Test 3: Test corrected ID is returned for method without error code
BOOST_AUTO_TEST_CASE(getMPIType_test3, * utf::tolerance(0.00001))
{

}

// === MPI: Vector Broadcast ===
// Test 1: Testing the the MPI Type was setup correctly, and able to broadcast two vectors
// This should establish that the custom MPI type has the right sizes
BOOST_AUTO_TEST_CASE(MPIVectorBroadcast)
{
	cupcfd::error::eCodes status;
	cupcfd::comm::Communicator comm(MPI_COMM_WORLD);

	// Create Two Vectors on Rank 0

	// Register the type (only once per all tests in file since static?)
	EuclideanVector<double,3> vec;

	// Should not yet be registered
	BOOST_CHECK_EQUAL(vec.isRegistered(), false);

	// Register
	status = vec.registerMPIType();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

	// Should now be registered
	BOOST_CHECK_EQUAL(vec.isRegistered(), true);

	if(comm.rank == 0)
	{
		EuclideanVector<double,3> vecs[2] = {EuclideanVector<double,3>(3.5, 6.3, 19.7),
											 EuclideanVector<double,3>(3.0, 4.5, 6.7)};

		status = cupcfd::comm::Broadcast(vecs, 2, 0, comm);
		BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	}
	else
	{
		EuclideanVector<double,3> recvVecs[2];

		status = cupcfd::comm::Broadcast(recvVecs, 2, 0, comm);
		BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);

		BOOST_CHECK_EQUAL(recvVecs[0].cmp[0], 3.5);
		BOOST_CHECK_EQUAL(recvVecs[0].cmp[1], 6.3);
		BOOST_CHECK_EQUAL(recvVecs[0].cmp[2], 19.7);

		BOOST_CHECK_EQUAL(recvVecs[1].cmp[0], 3.0);
		BOOST_CHECK_EQUAL(recvVecs[1].cmp[1], 4.5);
		BOOST_CHECK_EQUAL(recvVecs[1].cmp[2], 6.7);
	}

	// Cleanup for future tests since we use a static variable!
	status = vec.deregisterMPIType();
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
}

BOOST_AUTO_TEST_CASE(cleanup)
{
    MPI_Finalize();
}

max_stars_repo_name: thorbenlouw/CUP-CFD

max_issues_repo_name: thorbenlouw/CUP-CFD

max_forks_repo_name: thorbenlouw/CUP-CFD


text:
// Copyright (c) 2021 Graphcore Ltd. All rights reserved.
#define BOOST_TEST_MODULE SubgraphPartitioningTest

#include <iomanip>
#include <iostream>
#include <sstream>
#include <utility>

#include <boost/test/unit_test.hpp>

#include <filereader.hpp>
#include <popart/builder.hpp>
#include <popart/graph.hpp>
#include <popart/ir.hpp>
#include <popart/liveness.hpp>
#include <popart/op/call.hpp>
#include <popart/sessionoptions.hpp>
#include <popart/subgraphcopyingstrategy.hpp>
#include <popart/subgraphpartitioner.hpp>

using namespace popart;
using LivenessNode = liveness::LivenessNode;
using OpStatus     = liveness::OpStatus;

// In lieu of writing unit tests for LivenessAnalyzer, SubgraphCopyingStrategy
// and SubgraphPartitioner separately, the tests in this file test their
// combined behaviour. The classes have dependencies as follows:
//
// +-------------------------+           +-------------------------+
// | LivenessAnalyzer        +---------->+ SubgraphCopyingStrategy |
// |                         +<----------+                         |
// +-------------------------+           +-------------------------+
//    ^
//    |
// +-------------------------+
// | SubgraphPartitioner     |
// +-------------------------+
//
// We test two things:
//
//   * LivenessAnalyzer uses SubgraphCopyingStrategy to determine where to put
//     copies between subgraphs in the schedule. We test that this
//     placement is as we expect.
//   * SubgraphPartitioner determines how subgraphs are partitioned based on
//     the LivenessAnalyzer's global schedule. We test that this partitioning
//     is as we expect.

namespace {

// Shorthands.
auto OnEnterAndExit = SubgraphCopyingStrategy::OnEnterAndExit;
auto JustInTime     = SubgraphCopyingStrategy::JustInTime;

// Layout params.
constexpr size_t CALLSTACK_PADDING = 15;
constexpr size_t STATUS_PADDING    = 25;
constexpr size_t DETAILS_PADDING   = 45;

/**
 * A struct that holds our test objects.
 */
struct TestObjects {
  std::unique_ptr<Ir> ir;
  std::unique_ptr<liveness::LivenessAnalyzer> livenessAnalyzer;
  std::unique_ptr<liveness::SubgraphCopyingStrategy> subgraphCopyingStrategy;
  std::unique_ptr<liveness::SubgraphPartitioner> subgraphPartitioner;
};

/**
 * Create a LivenessAnalyzer, SubgraphCopyingStrategy and SubgraphPartitioner,
 * link them up, and call apply.
 */
TestObjects setup(std::function<void(Builder *)> modelBuilder,
                  SubgraphCopyingStrategy copyStrat) {

  // Build model.
  auto builder = Builder::create();
  modelBuilder(builder.get());

  // Create session options.
  SessionOptions opts;
  opts.enableOutlining         = false;
  opts.aliasZeroCopy           = false;
  opts.subgraphCopyingStrategy = copyStrat;

  // Create IR from model.
  auto ir = std::make_unique<Ir>();
  ir->setUserOptions(opts);
  ir->setOnnxModel(io::getModelFromString(builder->getModelProto()));
  ir->registerInputTensors();
  ir->constructForwards();

  // Create the test objects.
  std::unique_ptr<liveness::SubgraphCopyingStrategy> copyingStrat;

  switch (opts.subgraphCopyingStrategy) {
  case SubgraphCopyingStrategy::OnEnterAndExit:
    copyingStrat =
        std::make_unique<liveness::OnEnterAndExitSubgraphCopyingStrategy>();
    break;
  case SubgraphCopyingStrategy::JustInTime:
    copyingStrat =
        std::make_unique<liveness::JustInTimeSubgraphCopyingStrategy>();
    copyingStrat->setIr(ir.get());
    break;
  default:
    assert(false);
  }

  auto livenessAnalyzer = std::make_unique<liveness::LivenessAnalyzer>(
      ir.get(), copyingStrat.get());
  auto subgraphPart = std::make_unique<liveness::SubgraphPartitioner>();

  // Meet dependencies.
  copyingStrat->setIr(ir.get());
  copyingStrat->setLivenessAnalyzer(livenessAnalyzer.get());
  subgraphPart->setIr(ir.get());
  subgraphPart->setLivenessAnalyzer(livenessAnalyzer.get());

  // Call their logic.
  copyingStrat->apply();
  livenessAnalyzer->apply();
  subgraphPart->apply();

  return {std::move(ir),
          std::move(livenessAnalyzer),
          std::move(copyingStrat),
          std::move(subgraphPart)};
}

/**
 * A way to represent an expected liveness node, using strings.
 */
struct ExpectedLivenessNode {
  // Represent callstack by graph names, e.g. "/sg0"
  const char *callstackStr;
  // Represent OpStatus, e.g., "Enter".
  const char *statusStr;
  // Represent node's details.
  const char *detailsStr;
};

using ExpectedLivenessSchedule = std::vector<ExpectedLivenessNode>;

/**
 * For a given liveness node, get a string representation of the call stack.
 */
std::string getCallstackStr(const LivenessNode &node) {
  std::string result = "";
  bool isFirst       = true;
  for (auto op : node.getCallStack()) {
    if (!isFirst)
      result += "|";
    result += op->getGraph().id.str();
    isFirst = false;
  }
  return result;
}

/**
 * For a given liveness node, get a string representation of the status.
 */
std::string getStatusStr(const LivenessNode &node) {
  std::stringstream ss;
  if (node.getStatus() != OpStatus::Normal) {
    ss << node.getStatus();
  } else {
    ss << node.getOp()->opid;
  }
  return ss.str();
}

/**
 * For a given liveness node, get a string representation of the call stack.
 */
std::string getDetailsStr(const LivenessNode &node) {
  std::stringstream ss;
  switch (node.getStatus()) {
  case OpStatus::Enter: {
    break;
  }
  case OpStatus::Exit: {
    break;
  }
  case OpStatus::CopyInput: {
    ss << "'" << node.getTensorIds().second << "'"
       << " := "
       << "'" << node.getTensorIds().first << "'";
    break;
  }
  case OpStatus::CopyOutput: {
    ss << "'" << node.getTensorIds().first << "'"
       << " := "
       << "'" << node.getTensorIds().second << "'";
    break;
  }
  case OpStatus::CopyModified: {
    ss << "'" << node.getTensorIds().first << "'"
       << " := "
       << "'" << node.getTensorIds().second << "'";
    break;
  }
  case OpStatus::CopyLoopCarried: {
    // We don't really care about loop carried, so don't output details.
    break;
  }
  case OpStatus::Normal: {
    bool firstOutput = true;
    ss << "{";
    for (auto output : node.getOp()->output->tensorIdMap()) {
      if (!firstOutput)
        ss << ",";
      ss << "'" << output.second << "'";
      firstOutput = false;
    }
    ss << "} <- {";
    bool firstInput = true;
    for (auto input : node.getOp()->input->tensorIdMap()) {
      if (!firstInput)
        ss << ",";
      ss << "'" << input.second << "'";
      firstInput = false;
    }
    ss << "}";

    break;
  }
  }
  return ss.str();
}

/**
 * Helper function that wraps a string in quotes, pads and aligns left, for
 * ease of reading. Also add a whitespace.
 */
void fieldOut(std::ostream &out, const std::string &str, size_t pad = 15) {
  std::string field = std::string("\"") + str + "\"";
  out << std::setw(pad) << field << " ";
}

/**
 * Stream operator to output global schedule for helpful error messages.
 */
std::ostream &operator<<(std::ostream &out,
                         const liveness::LivenessAnalyzer &liveness) {
  for (size_t l = 0; l < liveness.getOpScheduleSize(); ++l) {
    const auto &node = liveness.getOpScheduleAt(l);
    // Output index.
    out << "    #" << std::setw(4) << l << ": ";
    // Output callstack string.
    fieldOut(out, getCallstackStr(node), CALLSTACK_PADDING);
    // Output status string.
    fieldOut(out, getStatusStr(node), STATUS_PADDING);
    // Output details string.
    fieldOut(out, getDetailsStr(node), DETAILS_PADDING);

    out << std::endl;
  }
  return out;
}

/**
 * Stream operator to output expected schedule for helpful error messages.
 */
std::ostream &operator<<(std::ostream &out,
                         const ExpectedLivenessSchedule &expectedSchedule) {
  for (size_t l = 0; l < expectedSchedule.size(); ++l) {
    const auto &node = expectedSchedule[l];
    // Output index.
    out << "    #" << std::setw(4) << l << ": ";
    // Output callstack string.
    fieldOut(out, node.callstackStr, CALLSTACK_PADDING);
    // Output status string.
    fieldOut(out, node.statusStr, STATUS_PADDING);
    // Output details string.
    fieldOut(out, node.detailsStr, DETAILS_PADDING);
    out << std::endl;
  }
  return out;
}

/**
 * For a given model (defined by a function that takes a builder) and copying
 * strategy, test the produced global schedule against an expectation defined
 * via strings. Print a helpful message when it doesn't hold.
 */
void CHECK_LIVENESS_SCHEDULE(std::function<void(Builder *)> modelBuilder,
                             SubgraphCopyingStrategy copyStrat,
                             const ExpectedLivenessSchedule &expected) {

  // Call the test objects.
  auto testObjs = setup(modelBuilder, copyStrat);
  auto &actual  = *testObjs.livenessAnalyzer;

  std::stringstream ss;
  ss << std::left << std::endl;
  ss << "Expected liveness schedule:" << std::endl;
  ss << expected;
  ss << "Actual liveness schedule:" << std::endl;
  ss << actual;

  BOOST_REQUIRE_MESSAGE(actual.getOpScheduleSize() == expected.size(),
                        ss.str() << "Expected schedule size is "
                                 << expected.size() << ", actual size is "
                                 << actual.getOpScheduleSize());

  for (size_t l = 0; l < actual.getOpScheduleSize(); ++l) {
    auto &actNode = actual.getOpScheduleAt(l);
    auto &expNode = expected.at(l);

    // Check call stack string matches.
    BOOST_REQUIRE_MESSAGE(getCallstackStr(actNode) == expNode.callstackStr,
                          ss.str() << "Expected callstack at position #" << l
                                   << " is '" << expNode.callstackStr
                                   << "', actual is '"
                                   << getCallstackStr(actNode) << "'");

    // Check status string matches.
    BOOST_REQUIRE_MESSAGE(getStatusStr(actNode) == expNode.statusStr,
                          ss.str()
                              << "Expected status at position #" << l << " is '"
                              << expNode.statusStr << "', actual is '"
                              << getStatusStr(actNode) << "'");

    // Check details string matches.
    BOOST_REQUIRE_MESSAGE(getDetailsStr(actNode) == expNode.detailsStr,
                          ss.str() << "Expected details at position #" << l
                                   << " is '" << expNode.detailsStr
                                   << "', actual is '" << getDetailsStr(actNode)
                                   << "'");
  }
}

// Graph identifier.
using GraphId = std::string;
// Represent a subgraph partition as a list of tuples of subgraph part indices
// and strings describing what is lowered in that subgraph part.
using PartitionOpRep  = std::tuple<int, std::string>;
using PartitionOpReps = std::vector<PartitionOpRep>;

using PartitionOpRepsMap = std::map<GraphId, PartitionOpReps>;

/**
 * Get a simple object representation of a subgraph partition for a graph.
 */
PartitionOpReps getPartitionOpReps(const TestObjects &testObjs,
                                   const Graph &graph) {

  PartitionOpReps result;

  const auto &ir          = *testObjs.ir;
  const auto &liveness    = *testObjs.livenessAnalyzer;
  const auto &partitioner = *testObjs.subgraphPartitioner;

  const auto &sched = liveness.getGraphOpSchedule(graph.id.str());
  for (auto &op : sched) {
    std::stringstream ss;
    ss << op->opid;

    if (CallOp *callOp = dynamic_cast<CallOp *>(op)) {
      // For call-ops, get the callop schedule.
      const auto &callOpSchedule = partitioner.getCallOpSchedule(callOp);
      for (const auto &callOpScheduleEntry : callOpSchedule) {
        const auto &callOpPart   = std::get<0>(callOpScheduleEntry);
        const auto &subgraphPart = std::get<1>(callOpScheduleEntry);
        std::stringstream ss;
        ss << callOpPart;
        result.emplace_back(subgraphPart, ss.str());
      }

    } else {
      // For non-CallOp ops, check the op lowers in one part.
      BOOST_CHECK(partitioner.getOpSubgraphPartBegin(op) ==
                  partitioner.getOpSubgraphPartEnd(op) - 1);
      result.emplace_back(partitioner.getOpSubgraphPartBegin(op), ss.str());
    }
  }

  if (!sched.empty()) {
    // Check number of parts matches last ops end (to test getNumSubgraphParts).
    BOOST_CHECK(partitioner.getNumSubgraphParts(graph) ==
                partitioner.getOpSubgraphPartEnd(sched.back()));
  }

  return result;
}

/**
 * Get a simple object representation of subgraph partitions for all graphs.
 */
PartitionOpRepsMap getPartitionOpRepsMap(const TestObjects &testObjs) {
  PartitionOpRepsMap result;
  for (auto graph : testObjs.ir->getAllGraphs()) {
    result[graph->id.str()] = getPartitionOpReps(testObjs, *graph);
  }
  return result;
}

/**
 * Stream operator to output all subgraph schedules for helpful error messages.
 */
std::ostream &operator<<(std::ostream &out, const PartitionOpRepsMap &map) {

  for (const auto &entry : map) {
    out << "    graph '" << entry.first << "':" << std::endl;
    size_t l = 0;
    for (const auto &opRep : entry.second) {
      // Output index.
      out << "      #" << std::setw(4) << l++ << ": ";
      out << std::get<0>(opRep) << " -> " << std::get<1>(opRep) << std::endl;
    }
  }
  return out;
}

/**
 * For a given model (defined by a function that takes a builder) and copying
 * strategy, test the produced subgraph partition matches an expected
 * partitioning by means of a string-based represenation. Print helpful messages
 * when it doesn't hold.
 */
void CHECK_SUBGRAPH_PARTITION(std::function<void(Builder *)> modelBuilder,
                              SubgraphCopyingStrategy copyStrat,
                              const PartitionOpRepsMap &expected) {
  // Call the test objects.
  auto testObjs     = setup(modelBuilder, copyStrat);
  auto &ir          = *testObjs.ir;
  auto &partitioner = *testObjs.subgraphPartitioner;

  auto actual = getPartitionOpRepsMap(testObjs);

  std::stringstream ss;
  ss << std::left << std::endl;
  ss << "Expected subgraph partitions:" << std::endl;
  ss << expected;
  ss << "Actual subgraph partitions:" << std::endl;
  ss << actual;

  BOOST_REQUIRE_MESSAGE(expected.size() == actual.size(),
                        ss.str() << "found " << actual.size() << " graphs ("
                                 << "expected " << expected.size() << ")");

  for (const auto &entry : actual) {
    // Check we have an expectation.
    BOOST_REQUIRE_MESSAGE(expected.find(entry.first) != expected.end(),
                          ss.str() << "found graph '" << entry.first
                                   << "' without expectation");

    // Get the respective op representations.
    const auto &act = entry.second;
    const auto &exp = expected.at(entry.first);

    BOOST_REQUIRE_MESSAGE(act.size() == exp.size(),
                          ss.str() << "For subgraph '" << entry.first << "', "
                                   << "expected subgraph partition size is "
                                   << exp.size() << ", actual size is "
                                   << act.size());

    for (size_t i = 0; i < act.size(); ++i) {
      BOOST_REQUIRE_MESSAGE(std::get<0>(act.at(i)) == std::get<0>(exp.at(i)),
                            ss.str()
                                << "For subgraph '" << entry.first << "', "
                                << "position #" << i << ", expected "
                                << "subgraph part " << std::get<0>(exp.at(i))
                                << ", actual is " << std::get<0>(act.at(i)));

      BOOST_REQUIRE_MESSAGE(std::get<1>(act.at(i)) == std::get<1>(exp.at(i)),
                            ss.str()
                                << "For subgraph '" << entry.first << "', "
                                << "position #" << i << ", expected '"
                                << std::get<1>(exp.at(i)) << "', actual is '"
                                << std::get<1>(act.at(i)) << "'");
    }
  }
}

} // namespace

namespace {

auto model1 = [](Builder *builder) -> void {
  // Create a model that effectively does this:
  //
  // def sg0(a0, x0, y0):
  //   tmp0 = a0 + x0
  //   out0 = tmp0 + y0
  //   return out0
  //
  //  def main(a, x, y):
  //   out = sg0(a, x, y)
  //   return out

  // sg0.
  auto sg0Builder = &(builder->createSubgraphBuilder());
  sg0Builder->setGraphName("sg0");
  auto a0   = sg0Builder->addUntypedInputTensor("a0");
  auto x0   = sg0Builder->addUntypedInputTensor("x0");
  auto y0   = sg0Builder->addUntypedInputTensor("y0");
  auto tmp0 = sg0Builder->aiOnnxOpset9().add({a0, x0}, "tmp0");
  auto out0 = sg0Builder->aiOnnxOpset9().add({tmp0, y0}, "out0");
  sg0Builder->addOutputTensor(out0);

  // Main graph.
  Shape inputShape = {1};
  TensorInfo inputInfo{"INT32", inputShape};
  auto a = builder->addInputTensor(inputInfo, "a");
  auto x = builder->addInputTensor(inputInfo, "x");
  auto y = builder->addInputTensor(inputInfo, "y");
  auto out =
      builder->aiGraphcoreOpset1().call({a, x, y}, 1, *sg0Builder, "tmp")[0];
  builder->addOutputTensor(out);
};

} // namespace

BOOST_AUTO_TEST_CASE(DelayInput_Model1_OnEnterAndExit) {

  // Check that model1, when using the OnEnterAndExit strategy,
  // basically follows the copy inputs, do stuff, then copy outputs for each
  // call. This means subgraphs need not to be broken in multiple parts
  // because no parent copies happen in the middle of a subgraph.

  // clang-format off
  CHECK_LIVENESS_SCHEDULE(model1, OnEnterAndExit, {
    {"",     "Enter",         ""},
    {"",     "CopyInput",     "'sg0/a0' := 'a'"},
    {"",     "CopyInput",     "'sg0/x0' := 'x'"},
    {"",     "CopyInput",     "'sg0/y0' := 'y'"},
    {"|sg0", "ai.onnx.Add:7", "{'sg0/Add:0'} <- {'sg0/a0','sg0/x0'}"},
    {"|sg0", "ai.onnx.Add:7", "{'sg0/Add:0/1'} <- {'sg0/Add:0','sg0/y0'}"},
    {"",     "CopyOutput",    "'Call:0' := 'sg0/Add:0/1'"},
    {"",     "Exit",          ""}
  });
  // clang-format on

  // clang-format off
  CHECK_SUBGRAPH_PARTITION(model1, OnEnterAndExit, {
    {"",
      {
        {0, "CopyInput@0" },
        {0, "CopyInput@1" },
        {0, "CopyInput@2" },
        {0, "CallSubgraphPart(0)" },
        {0, "CopyOutput@0" }
      }
    },
    {"sg0",
      {
        {0, "ai.onnx.Add:7" },
        {0, "ai.onnx.Add:7" }
      }
    }
  });
  // clang-format on
}

BOOST_AUTO_TEST_CASE(DelayInput_Model1_JustInTime) {

  // Check that model1, when using the 'just-in-time' subgraph copying strategy,
  // delays an input copy for tensor 'sg0/y0'. To facilitate this, sg0 needs
  // to be partitioned into two parts, with adds being in one part each.

  // clang-format off
  CHECK_LIVENESS_SCHEDULE(model1, JustInTime, {
    {"",     "Enter",         ""},
    {"",     "CopyInput",     "'sg0/a0' := 'a'"},
    {"",     "CopyInput",     "'sg0/x0' := 'x'"},
    {"|sg0", "ai.onnx.Add:7", "{'sg0/Add:0'} <- {'sg0/a0','sg0/x0'}"},
    {"",     "CopyInput",     "'sg0/y0' := 'y'"}, // <- moved here
    {"|sg0", "ai.onnx.Add:7", "{'sg0/Add:0/1'} <- {'sg0/Add:0','sg0/y0'}"},
    {"",     "CopyOutput",    "'Call:0' := 'sg0/Add:0/1'"},
    {"",     "Exit",          ""}
  });
  // clang-format on

  // clang-format off
  CHECK_SUBGRAPH_PARTITION(model1, JustInTime, {
    {"",
      {
        {0, "CopyInput@0" },
        {0, "CopyInput@1" },
        {0, "CallSubgraphPart(0)" }, // call to part 0 of sg0.
        {0, "CopyInput@2" },
        {0, "CallSubgraphPart(1)" }, // call to part 1 of sg0.
        {0, "CopyOutput@0" }
      }
    },
    {"sg0",
      {
        {0, "ai.onnx.Add:7" },
        {1, "ai.onnx.Add:7" }  // <- sg0 has two parts.
      }
    }
  });
  // clang-format on
}

namespace {

auto model2 = [](Builder *builder) -> void {
  // Create a model that effectively does this:
  //
  // def sg0(a0, x0, y0):
  //   tmp0 = a0 + x0    <- tmp0 and a0 are the same variable, so this updates
  //                        a0 and hence a in the main graph.
  //   out0 = tmp0 + y0
  //   return out0
  //
  //  def main(a, x, y):
  //   out = sg0(a, x, y) <- a is 'passed by reference'
  //   return out

  // sg0.
  auto sg0Builder = &(builder->createSubgraphBuilder());
  sg0Builder->setGraphName("sg0");
  auto a0                       = sg0Builder->addUntypedInputTensor("a0");
  auto x0                       = sg0Builder->addUntypedInputTensor("x0");
  auto y0                       = sg0Builder->addUntypedInputTensor("y0");
  std::vector<TensorId> tensors = {a0, x0};
  // Inplace add.
  auto tmp0 = sg0Builder->customOp(
      Onnx::CustomOperators::AddLhsInplace, 1, {a0, x0}, 1, {}, "tmp0")[0];
  auto out0 = sg0Builder->aiOnnxOpset9().add({tmp0, y0}, "out0");
  sg0Builder->addOutputTensor(out0);

  // Main graph.
  Shape inputShape = {1};
  TensorInfo inputInfo{"INT32", inputShape};
  auto a = builder->addInputTensor(inputInfo, "a");
  auto x = builder->addInputTensor(inputInfo, "x");
  auto y = builder->addInputTensor(inputInfo, "y");

  // Instead of the call below, add a call which has the attributes necessary
  // to mimick inputIndex 0 being modified in the call using customOp.
  // auto tmp = builder->aiGraphcoreOpset1().call(
  //    {a, x, y}, 1, *sg0Builder, "tmp")[0];

  auto sg0ModelProto = io::getModelFromString(sg0Builder->getModelProto());
  auto calleeProto   = sg0ModelProto.graph();
  auto out           = builder->customOp(
      Onnx::AiGraphcore::OpSet1::Call,
      1,
      {a, x, y},
      1,
      {{"callee", calleeProto}, {"modifiedInputs", std::vector<int64_t>({0})}},
      "tmp")[0];

  builder->addOutputTensor(out);
};

} // namespace

BOOST_AUTO_TEST_CASE(CopyModified_Model2_OnEnterAndExit) {

  // Check that model2 (which is just like model1 except that the call
  // thinks that the first input is modified in the call, and hence introduces
  // a CopyModified) with the simple subgraph copying strategy inserts the
  // CopyModified at the end, not requiring any partitioning of subgraphs.

  // clang-format off
  CHECK_LIVENESS_SCHEDULE(model2, OnEnterAndExit, {
    {"",     "Enter",                        ""},
    {"",     "CopyInput",                    "'sg0/a0' := 'a'"},
    {"",     "CopyInput",                    "'sg0/x0' := 'x'"},
    {"",     "CopyInput",                    "'sg0/y0' := 'y'"},
    {"|sg0", "ai.graphcore.AddLhsInplace:1", "{'sg0/AddLhsInplace:0'} <- {'sg0/a0','sg0/x0'}"},
    {"|sg0", "ai.onnx.Add:7",                "{'sg0/Add:0'} <- {'sg0/AddLhsInplace:0','sg0/y0'}"},
    {"",     "CopyOutput",                   "'Call:0' := 'sg0/Add:0'"},
    {"",     "CopyModified",                 "'a' := 'sg0/a0'"},
    {"",     "Exit",                         ""}
  });
  // clang-format on

  // clang-format off
  CHECK_SUBGRAPH_PARTITION(model2, OnEnterAndExit, {
    {"",
      {
        {0, "CopyInput@0" },
        {0, "CopyInput@1" },
        {0, "CopyInput@2" },
        {0, "CallSubgraphPart(0)" },
        {0, "CopyOutput@0" },
        {0, "CopyModified@0" },
      }
    },
    {"sg0",
      {
        {0, "ai.graphcore.AddLhsInplace:1" },
        {0, "ai.onnx.Add:7" }
      }
    }
  });
  // clang-format on
}

BOOST_AUTO_TEST_CASE(CopyModified_Model2_JustInTime) {

  // Check that model2 (which is just like model1 except that the call
  // thinks that the first input is modified in the call, and hence introduces
  // a CopyModified) with the just-in-time subgraph copying strategy inserts the
  // CopyModified once the value of 'sg0/a0' is final.

  // clang-format off
  CHECK_LIVENESS_SCHEDULE(model2, JustInTime, {
    {"",     "Enter",                        ""},
    {"",     "CopyInput",                    "'sg0/a0' := 'a'"},
    {"",     "CopyInput",                    "'sg0/x0' := 'x'"},
    {"|sg0", "ai.graphcore.AddLhsInplace:1", "{'sg0/AddLhsInplace:0'} <- {'sg0/a0','sg0/x0'}"},
    {"",     "CopyModified",                 "'a' := 'sg0/a0'"}, // <- moved here
    {"",     "CopyInput",                    "'sg0/y0' := 'y'"}, // <- moved here
    {"|sg0", "ai.onnx.Add:7",                "{'sg0/Add:0'} <- {'sg0/AddLhsInplace:0','sg0/y0'}"},
    {"",     "CopyOutput",                   "'Call:0' := 'sg0/Add:0'"},
    {"",     "Exit",                         ""}
  });
  // clang-format on

  // clang-format off
  CHECK_SUBGRAPH_PARTITION(model2, JustInTime, {
    {"",
      {
        {0, "CopyInput@0" },
        {0, "CopyInput@1" },
        {0, "CallSubgraphPart(0)" }, // call to part 0 of sg0.
        {0, "CopyModified@0" },      // <- early modified return
        {0, "CopyInput@2" },         // <- late input copy
        {0, "CallSubgraphPart(1)" }, // call to part 1 of sg0.
        {0, "CopyOutput@0" }
      }
    },
    {"sg0",
      {
        {0, "ai.graphcore.AddLhsInplace:1" },
        // <-- main graph does copies here.
        {1, "ai.onnx.Add:7" }
      }
    }
  });
  // clang-format on
}

namespace {

auto model3 = [](Builder *builder) -> void {
  // Create a model that effectively does this:
  //
  // def sg0(a0):
  //   out0_0 = a0 + a0
  //   out0_1 = out0_0 + a0
  //   return out0_0, out0_1
  //
  //  def main(a):
  //   out_0, out_1 = sg0(a)
  //   return out_0, out_1

  // sg0.
  auto sg0Builder = &(builder->createSubgraphBuilder());
  sg0Builder->setGraphName("sg0");
  auto a0     = sg0Builder->addUntypedInputTensor("a0");
  auto out0_0 = sg0Builder->aiOnnxOpset9().add({a0, a0}, "out0_0");
  auto out0_1 = sg0Builder->aiOnnxOpset9().add({out0_0, a0}, "out0_1");
  sg0Builder->addOutputTensor(out0_0);
  sg0Builder->addOutputTensor(out0_1);

  // Main graph.
  Shape inputShape = {1};
  TensorInfo inputInfo{"INT32", inputShape};
  auto a     = builder->addInputTensor(inputInfo, "a");
  auto call  = builder->aiGraphcoreOpset1().call({a}, 2, *sg0Builder, "call");
  auto out_0 = call[0];
  auto out_1 = call[1];
  builder->addOutputTensor(out_0);
  builder->addOutputTensor(out_1);
};

} // namespace

BOOST_AUTO_TEST_CASE(EarlyOutput_Model3_OnEnterAndExit) {

  // Check that model3, when using the 'simple' subgraph copying strategy,
  // basically follows the copy inputs, do stuff, then copy outputs for each
  // call. This means subgraphs need not to be broken in multiple parts
  // because no parent copies happen in the middle of a subgraph.

  // clang-format off
  CHECK_LIVENESS_SCHEDULE(model3, OnEnterAndExit, {
    {"",     "Enter",         ""},
    {"",     "CopyInput",     "'sg0/a0' := 'a'"},
    {"|sg0", "ai.onnx.Add:7", "{'sg0/Add:0'} <- {'sg0/a0','sg0/a0'}"},
    {"|sg0", "ai.onnx.Add:7", "{'sg0/Add:0/1'} <- {'sg0/Add:0','sg0/a0'}"},
    {"",     "CopyOutput",    "'Call:0' := 'sg0/Add:0'"},
    {"",     "CopyOutput",    "'Call:1' := 'sg0/Add:0/1'"},
    {"",     "Exit",          ""}
  });
  // clang-format on

  // clang-format off
  CHECK_SUBGRAPH_PARTITION(model3, OnEnterAndExit, {
    {"",
      {
        {0, "CopyInput@0" },
        {0, "CallSubgraphPart(0)" },
        {0, "CopyOutput@0" },
        {0, "CopyOutput@1" }
      }
    },
    {"sg0",
      {
        {0, "ai.onnx.Add:7" },
        {0, "ai.onnx.Add:7" }
      }
    }
  });
  // clang-format on
}

BOOST_AUTO_TEST_CASE(EarlyOutput_Model3_JustInTime) {

  // Check that model3, when using the 'just-in-time' subgraph copying strategy,
  // one output is copied early. To facilitate this, sg0 needs is partitioned
  // into two parts, with adds being in one part each.

  // clang-format off
  CHECK_LIVENESS_SCHEDULE(model3, JustInTime, {
    {"",     "Enter",         ""},
    {"",     "CopyInput",     "'sg0/a0' := 'a'"},
    {"|sg0", "ai.onnx.Add:7", "{'sg0/Add:0'} <- {'sg0/a0','sg0/a0'}"},
    {"",     "CopyOutput",    "'Call:0' := 'sg0/Add:0'"},
    {"|sg0", "ai.onnx.Add:7", "{'sg0/Add:0/1'} <- {'sg0/Add:0','sg0/a0'}"},
    {"",     "CopyOutput",    "'Call:1' := 'sg0/Add:0/1'"},
    {"",     "Exit",          ""}
  });
  // clang-format on

  // clang-format off
  CHECK_SUBGRAPH_PARTITION(model3, JustInTime, {
    {"",
      {
        {0, "CopyInput@0" },
        {0, "CallSubgraphPart(0)" }, // <- Call to first add.
        {0, "CopyOutput@0" },        // <- Early output.
        {0, "CallSubgraphPart(1)" }, // <- Call to second add.
        {0, "CopyOutput@1" }
      }
    },
    {"sg0",
      {
        {0, "ai.onnx.Add:7" },
        // <-- main graph does copies here.
        {1, "ai.onnx.Add:7" }
      }
    }
  });
  // clang-format on
}

namespace {

auto model4 = [](Builder *builder) -> void {
  // Create a model with a nested call:
  //
  // def sg1(a1, b1, c1):
  //   out1_0 = a1 + b1
  //   out1_1 = out1_0 + c1
  //   return out1_0, out1_1
  //
  // def sg0(a0, b0, c0):
  //   tmp0_0 = a0 + a0
  //   out0_0, tmp0_1 = sg1(tmp0_0, b0, c0)
  //   tmp0_2, out0_1 = sg1(a0, tmp0_1, c0)
  //   return out0_0, out0_1
  //
  //  def main(a):
  //   out_0, out_1 = sg0(a, a, a)
  //   return out_0, out_1

  // sg1.
  auto sg1Builder = &(builder->createSubgraphBuilder());
  sg1Builder->setGraphName("sg1");
  auto a1     = sg1Builder->addUntypedInputTensor("a1");
  auto b1     = sg1Builder->addUntypedInputTensor("b1");
  auto c1     = sg1Builder->addUntypedInputTensor("c1");
  auto out1_0 = sg1Builder->aiOnnxOpset9().add({a1, b1}, "out1_0");
  auto out1_1 = sg1Builder->aiOnnxOpset9().add({out1_0, c1}, "c1");
  sg1Builder->addOutputTensor(out1_0);
  sg1Builder->addOutputTensor(out1_1);

  // sg0.
  auto sg0Builder = &(builder->createSubgraphBuilder());
  sg0Builder->setGraphName("sg0");
  auto a0      = sg0Builder->addUntypedInputTensor("a0");
  auto b0      = sg0Builder->addUntypedInputTensor("b0");
  auto c0      = sg0Builder->addUntypedInputTensor("c0");
  auto tmp0_0  = sg0Builder->aiOnnxOpset9().add({a0, a0}, "tmp0_0");
  auto call0_0 = sg0Builder->aiGraphcoreOpset1().call(
      {tmp0_0, b0, b0}, 2, *sg1Builder, "call0_0");
  auto out0_0  = call0_0[0];
  auto tmp0_1  = call0_0[1];
  auto call0_1 = sg0Builder->aiGraphcoreOpset1().call(
      {a0, tmp0_1, c0}, 2, *sg1Builder, "call0_1");
  // auto tmp0_2 = call0_1[0]; <- unused.
  auto out0_1 = call0_1[1];
  sg0Builder->addOutputTensor(out0_0);
  sg0Builder->addOutputTensor(out0_1);

  // Main graph.
  Shape inputShape = {1};
  TensorInfo inputInfo{"INT32", inputShape};
  auto a = builder->addInputTensor(inputInfo, "a");
  auto call =
      builder->aiGraphcoreOpset1().call({a, a, a}, 2, *sg0Builder, "call");
  auto out_0 = call[0];
  auto out_1 = call[1];
  builder->addOutputTensor(out_0);
  builder->addOutputTensor(out_1);
};

} // namespace

BOOST_AUTO_TEST_CASE(Nested_Model4_OnEnterAndExit) {

  // Check that model4, a model with nested CallOps, when using the 'on enter
  // and exit' subgraph copying strategy, no subgraphs are partitioned still.

  // clang-format off
  CHECK_LIVENESS_SCHEDULE(model4, OnEnterAndExit, {
    {"",         "Enter",                   ""},
    {"",         "CopyInput",               "'sg0/a0' := 'a'"},
    {"",         "CopyInput",               "'sg0/b0' := 'a'"},
    {"",         "CopyInput",               "'sg0/c0' := 'a'"},
    {"|sg0",     "ai.onnx.Add:7",           "{'sg0/Add:0'} <- {'sg0/a0','sg0/a0'}"},
    {"|sg0",     "Enter",                   ""},
    {"|sg0",     "CopyInput",               "'sg1/a1' := 'sg0/Add:0'"},
    {"|sg0",     "CopyInput",               "'sg1/b1' := 'sg0/b0'"},
    {"|sg0",     "CopyInput",               "'sg1/c1' := 'sg0/b0'"},
    {"|sg0|sg1", "ai.onnx.Add:7",           "{'sg1/Add:0'} <- {'sg1/a1','sg1/b1'}"},
    {"|sg0|sg1", "ai.onnx.Add:7",           "{'sg1/Add:0/1'} <- {'sg1/Add:0','sg1/c1'}"},
    {"|sg0",     "CopyOutput",              "'sg0/Call:0' := 'sg1/Add:0'"},
    {"|sg0",     "CopyOutput",              "'sg0/Call:1' := 'sg1/Add:0/1'"},
    {"|sg0",     "Exit",                    ""},
    {"|sg0",     "Enter",                   ""},
    {"|sg0",     "CopyInput",               "'sg1/a1' := 'sg0/a0'"},
    {"|sg0",     "CopyInput",               "'sg1/b1' := 'sg0/Call:1'"},
    {"|sg0",     "CopyInput",               "'sg1/c1' := 'sg0/c0'"},
    {"|sg0|sg1", "ai.onnx.Add:7",           "{'sg1/Add:0'} <- {'sg1/a1','sg1/b1'}"},
    {"|sg0|sg1", "ai.onnx.Add:7",           "{'sg1/Add:0/1'} <- {'sg1/Add:0','sg1/c1'}"},
    {"|sg0",     "CopyOutput",              "'sg0/Call:0/1' := 'sg1/Add:0'"},
    {"|sg0",     "CopyOutput",              "'sg0/Call:1/1' := 'sg1/Add:0/1'"},
    {"|sg0",     "Exit",                    ""},
    {"",         "CopyOutput",              "'Call:0/2' := 'sg0/Call:0'"},
    {"",         "CopyOutput",              "'Call:1/2' := 'sg0/Call:1/1'"},
    {"",         "Exit",                    ""}
  });
  // clang-format on

  // clang-format off
  CHECK_SUBGRAPH_PARTITION(model4, OnEnterAndExit, {
    {"",
      {
        {0, "CopyInput@0" },
        {0, "CopyInput@1" },
        {0, "CopyInput@2" },
        {0, "CallSubgraphPart(0)" },
        {0, "CopyOutput@0" },
        {0, "CopyOutput@1" }
      }
    },
    {"sg0",
      {
        {0, "ai.onnx.Add:7" },
        {0, "CopyInput@0" },
        {0, "CopyInput@1" },
        {0, "CopyInput@2" },
        {0, "CallSubgraphPart(0)" },
        {0, "CopyOutput@0" },
        {0, "CopyOutput@1" },
        {0, "CopyInput@0" },
        {0, "CopyInput@1" },
        {0, "CopyInput@2" },
        {0, "CallSubgraphPart(0)" },
        {0, "CopyOutput@0" },
        {0, "CopyOutput@1" }
      }
    },
    {"sg1",
      {
        {0, "ai.onnx.Add:7" },
        {0, "ai.onnx.Add:7" }
      }
    }
  });
  // clang-format on
}

BOOST_AUTO_TEST_CASE(Nested_Model4_JustInTime) {

  // Check that model4, when using the 'just-in-time' subgraph copying strategy,
  // one output is copied early. To facilitate this, sg0 as well as sg1 need
  // to be partitioned.

  // clang-format off
  CHECK_LIVENESS_SCHEDULE(model4, JustInTime, {
    {"",         "Enter",                   ""},
    {"",         "CopyInput",               "'sg0/a0' := 'a'"},
    {"|sg0",     "ai.onnx.Add:7",           "{'sg0/Add:0'} <- {'sg0/a0','sg0/a0'}"},
    {"|sg0",     "Enter",                   ""},
    {"",         "CopyInput",               "'sg0/b0' := 'a'"},
    {"|sg0",     "CopyInput",               "'sg1/a1' := 'sg0/Add:0'"},
    {"|sg0",     "CopyInput",               "'sg1/b1' := 'sg0/b0'"},
    {"|sg0|sg1", "ai.onnx.Add:7",           "{'sg1/Add:0'} <- {'sg1/a1','sg1/b1'}"},
    {"|sg0",     "CopyOutput",              "'sg0/Call:0' := 'sg1/Add:0'"},
    {"",         "CopyOutput",              "'Call:0/2' := 'sg0/Call:0'"},
    {"|sg0",     "CopyInput",               "'sg1/c1' := 'sg0/b0'"},
    {"|sg0|sg1", "ai.onnx.Add:7",           "{'sg1/Add:0/1'} <- {'sg1/Add:0','sg1/c1'}"},
    {"|sg0",     "CopyOutput",              "'sg0/Call:1' := 'sg1/Add:0/1'"},
    {"|sg0",     "Exit",                    ""},
    {"|sg0",     "Enter",                   ""},
    {"|sg0",     "CopyInput",               "'sg1/a1' := 'sg0/a0'"},
    {"|sg0",     "CopyInput",               "'sg1/b1' := 'sg0/Call:1'"},
    {"|sg0|sg1", "ai.onnx.Add:7",           "{'sg1/Add:0'} <- {'sg1/a1','sg1/b1'}"},
    {"|sg0",     "CopyOutput",              "'sg0/Call:0/1' := 'sg1/Add:0'"},
    {"",         "CopyInput",               "'sg0/c0' := 'a'"},
    {"|sg0",     "CopyInput",               "'sg1/c1' := 'sg0/c0'"},
    {"|sg0|sg1", "ai.onnx.Add:7",           "{'sg1/Add:0/1'} <- {'sg1/Add:0','sg1/c1'}"},
    {"|sg0",     "CopyOutput",              "'sg0/Call:1/1' := 'sg1/Add:0/1'"},
    {"",         "CopyOutput",              "'Call:1/2' := 'sg0/Call:1/1'"},
    {"|sg0",     "Exit",                    ""},
    {"",         "Exit",                    ""}
  });
  // clang-format on

  // clang-format off
  CHECK_SUBGRAPH_PARTITION(model4, JustInTime, {
    {"",
      {
        {0, "CopyInput@0" },
        {0, "CallSubgraphPart(0)" },
        {0, "CopyInput@1" },
        {0, "CallSubgraphPart(1)" },
        {0, "CopyOutput@0" },
        {0, "CallSubgraphPart(2)" },
        {0, "CopyInput@2" },
        {0, "CallSubgraphPart(3)" },
        {0, "CopyOutput@1" }
      }
    },
    {"sg0", // <-- Split over 4 parts (see calls to parts in main graph).
      {
        {0, "ai.onnx.Add:7" },
        // <-- main graph does copies here.
        {1, "CopyInput@0" },
        {1, "CopyInput@1" },
        {1, "CallSubgraphPart(0)" },
        {1, "CopyOutput@0" },
        // <-- main graph does copies here.
        {2, "CopyInput@2" },
        {2, "CallSubgraphPart(1)" },
        {2, "CopyOutput@1" },
        {2, "CopyInput@0" },
        {2, "CopyInput@1" },
        {2, "CallSubgraphPart(0)" },
        {2, "CopyOutput@0" },
        // <-- main graph does copies here.
        {3, "CopyInput@2" },
        {3, "CallSubgraphPart(1)" },
        {3, "CopyOutput@1" }
      }
    },
    {"sg1", // <-- Split over 2 parts (see calls to parts in "sg0").
      {
        {0, "ai.onnx.Add:7" },
        // <-- sg does copies here.
        {1, "ai.onnx.Add:7" }
      }
    }
  });
  // clang-format on
}

namespace {

auto model5 = [](Builder *builder) -> void {
  // Create a model with a pass-through subgraph:
  //
  // def sg0(a0):
  //   return a0
  //
  //  def main(a):
  //   out = sg0()
  //   return out

  // sg0.
  auto sg0Builder = &(builder->createSubgraphBuilder());
  sg0Builder->setGraphName("sg0");
  auto a0 = sg0Builder->addUntypedInputTensor("a0");
  sg0Builder->addOutputTensor(a0);

  // Main graph.
  Shape inputShape = {1};
  TensorInfo inputInfo{"INT32", inputShape};
  auto a   = builder->addInputTensor(inputInfo, "a");
  auto out = builder->aiGraphcoreOpset1().call({a}, 1, *sg0Builder, "call")[0];
  builder->addOutputTensor(out);
};

} // namespace

BOOST_AUTO_TEST_CASE(EdgeCase_Passthrough_Model5_JustInTime) {

  // Check that model5 which has an input that isn't consumed by an op and an
  // output that isn't produced by an op, we still see the inputs/outputs.

  // clang-format off
  CHECK_LIVENESS_SCHEDULE(model5, JustInTime, {
    {"",         "Enter",                   ""},
    {"",         "CopyInput",               "'sg0/a0' := 'a'"},
    {"",         "CopyOutput",              "'Call:0' := 'sg0/a0'"},
    {"",         "Exit",                    ""}
  });
  // clang-format on

  // clang-format off
  CHECK_SUBGRAPH_PARTITION(model5, JustInTime, {
    {"",
      {
        {0, "CopyInput@0" },
        {0, "CopyOutput@0" }
      }
    },
    {"sg0",
      {
      }
    }
  });
  // clang-format on
}

namespace {

auto model6 = [](Builder *builder) -> void {
  // Stuff needed for loops.
  int const_int_data[1]   = {10};
  bool const_bool_data[1] = {true};
  TensorInfo const_int32_shape{"INT32", std::vector<int64_t>{}};
  TensorInfo const_bool_shape{"BOOL", std::vector<int64_t>{}};
  TensorInfo const_int64_shape{"INT64", std::vector<int64_t>{}};
  popart::ConstVoidData const_int_cvdata{const_int_data, const_int32_shape};
  popart::ConstVoidData const_bool_cvdata{const_bool_data, const_bool_shape};
  auto M    = builder->aiOnnxOpset11().constant(const_int_cvdata);
  auto cond = builder->aiOnnxOpset11().constant(const_bool_cvdata);

  // def sg0(a0, b0):
  //   out0 = a0 + a0
  //   out1 = out0 + b0 <-- opportunity to copy b0 late
  //   return out0, out1
  auto sg0Builder = &(builder->createSubgraphBuilder());
  sg0Builder->setGraphName("sg1");
  auto iter      = sg0Builder->addInputTensor(const_int64_shape);
  auto keepgoing = sg0Builder->addInputTensor(const_bool_shape);
  auto a0        = sg0Builder->addUntypedInputTensor("a0");
  auto b0        = sg0Builder->addUntypedInputTensor("b0");
  auto out0      = sg0Builder->aiOnnxOpset9().add({a0, a0}, "out0");
  auto out1      = sg0Builder->aiOnnxOpset9().add({out0, a0}, "out1");
  sg0Builder->addOutputTensor(keepgoing);
  sg0Builder->addOutputTensor(out0);
  sg0Builder->addOutputTensor(out1);

  // Main graph.
  Shape inputShape = {1};
  TensorInfo inputInfo{"INT32", inputShape};
  auto a = builder->addInputTensor(inputInfo, "a");
  auto loop =
      builder->aiOnnxOpset11().loop({M, cond, a, a}, 2, *sg0Builder, "call");
  builder->addOutputTensor(loop[0]);
};

} // namespace

BOOST_AUTO_TEST_CASE(EdgeCase_Loop_Model6_JustInTime) {

  // Check that model6 which has loops, doesn't allow delaying inputs passed
  // a loop's subgraph or make outputs earlier before a loops exit. Input and
  // output copies should be just like with the 'on enter and exit' strategy
  // for subgraphs like this.

  // clang-format off
  CHECK_LIVENESS_SCHEDULE(model6, JustInTime, {
    {"",     "Enter",                   ""},
    {"",     "CopyInput",               "'sg1/input/1' := 'Constant:0/1'"},
    {"",     "CopyInput",               "'sg1/Add:0' := 'a'"},
    {"",     "CopyInput",               "'sg1/Add:0/1' := 'a'"},
    {"",     "CopyLoopCarried",         ""},
    {"",     "CopyLoopCarried",         ""},
    {"",     "CopyLoopCarried",         ""},
    {"|sg1", "ai.onnx.Add:7",           "{'sg1/Add:0'} <- {'sg1/a0','sg1/a0'}"},
    {"|sg1", "ai.onnx.Add:7",           "{'sg1/Add:0/1'} <- {'sg1/Add:0','sg1/a0'}"},
    {"",     "CopyLoopCarried",         ""},
    {"",     "CopyLoopCarried",         ""},
    {"",     "CopyLoopCarried",         ""},
    {"|sg1", "ai.onnx.Add:7",           "{'sg1/Add:0'} <- {'sg1/a0','sg1/a0'}"},
    {"|sg1", "ai.onnx.Add:7",           "{'sg1/Add:0/1'} <- {'sg1/Add:0','sg1/a0'}"},
    {"",     "CopyOutput",              "'Loop:0' := 'sg1/Add:0'"},
    {"",     "CopyOutput",              "'Loop:1' := 'sg1/Add:0/1'"},
    {"",     "Exit",                    ""}
  });
  // clang-format on

  // clang-format off
  CHECK_SUBGRAPH_PARTITION(model6, JustInTime, {
    {"",
      {
        {0, "ai.onnx.Loop:11" }, // <- No call schedule for loops.
      }
    },
    {"sg1",
      {
        {0, "ai.onnx.Add:7" },
        {0, "ai.onnx.Add:7" }
      }
    }
  });
  // clang-format on
}

ext: cpp


text:
/*********************************************************************
* Software License Agreement (BSD License)
*
* Copyright (c) 2019, WVU Interactive Robotics Laboratory
*                       https://web.statler.wvu.edu/~irl/
* All rights reserved.
*
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions
*  are met:
*
*   * Redistributions of source code must retain the above copyright
*     notice, this list of conditions and the following disclaimer.
*   * Redistributions in binary form must reproduce the above
*     copyright notice, this list of conditions and the following
*     disclaimer in the documentation and/or other materials provided
*     with the distribution.
*   * Neither the name of the WVU Interactive Robotics Laboratory nor
*     the names of its contributors may be used to endorse or promote products
*     derived from this software without specific prior written permission.
*
*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
*  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
*  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
*  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
*  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
*  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
*  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
*  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
*  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
*  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
*  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
*  POSSIBILITY OF SUCH DAMAGE.
*********************************************************************/

#ifndef BASE_SERIAL_INTERFACE_HPP__
#define BASE_SERIAL_INTERFACE_HPP__

#include <ros/ros.h>

#include <hw_interface/base_interface.hpp>

#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>

#include <boost/asio/basic_serial_port.hpp>
#include <boost/asio/serial_port.hpp>

#include <boost/asio/read_until.hpp>
#include <boost/regex.hpp>

#define MAX_SERIAL_READ 250

namespace base_classes
{
    class base_serial_interface : public base_interface
    {

    public:
        bool handleIORequest(const boost::system::error_code &ec, size_t bytesReceived);

        //handles ascii roboteq runtime queries
		    bool handleRegexRequest(const boost::system::error_code& e, std::size_t bytesTransferred);
        boost::regex regexExpr;
        std::string receivedRegexData;

        //this definition is used to check if a certain character sequence has been encountered
        //on the stream. The begin and end iterators represent positions in the stream that this
        //current functor is working. The return iterator represents where the next function call
        //(if needed) will begin in the buffer stream. The return bool indicates if the ASIO Service
        //is complete and should call the plugin IO Handler. (true = yes, call handler)

        //Plugins should overide this function if it intends on using this functionality

        virtual std::pair<matcherIterator, bool> matchFooter(matcherIterator begin, matcherIterator end, const char* sequence)
        {
            return std::make_pair(end, true);
        }

    private:

        bool interfaceReady();
        bool initPlugin(ros::NodeHandlePtr nhPtr,
                            const boost::shared_ptr<boost::asio::io_service> ioService);
        bool startWork(); //probably set some flag and start listening on port
        bool stopWork();  //probably unset some flag

        //will receive request, then run plugin readHandler(), check work flag,
            //if work flag restart async read

        //this ptr is used to pass the beginning of data to the plugin data handler.
        //  its void because we don't type discriminate around here.
        //But mainly so the plugin can interpret the data however it wants to.

    protected:
        boost::shared_ptr<boost::asio::serial_port> interfacePort;

        std::string deviceName;
        boost::asio::streambuf interfaceRegexBuffer;

        int readLength;
        std::string headerString, footerString;

        base_serial_interface();

        //needs better name
        //this function calls the plugin's function to read in ROS params,
        //subscribe to topics, publish topics. This function should fill
        //in the protected member's info
        virtual bool subPluginInit(ros::NodeHandlePtr nhPtr) = 0;
        virtual void setInterfaceOptions() = 0;

        virtual bool interfaceReadHandler(const size_t &bufferSize, int arrayStartPos, const boost::system::error_code &ec) = 0;

        void interfaceWriteHandler(const hw_interface_support_types::shared_const_buffer &buffer);
        void postInterfaceWriteRequest(const hw_interface_support_types::shared_const_buffer &buffer);

        virtual bool pluginStart()
        {
            return true;
        }

        virtual bool pluginStop()
        {
            return true;
        }

        template<typename Option>
        boost::system::error_code setOption(const Option * newOption)
        {
            boost::system::error_code ec;
            if(interfacePort.get()){
                return interfacePort->set_option<Option>(*newOption, ec);
            }
            ec.assign(static_cast<int>(boost::system::errc::no_such_device), boost::asio::error::get_system_category());
            return ec;
        }

        template<typename Option>
        Option getOption(){
            Option returnValue;
            if(interfacePort.get()){
                interfacePort->get_option<Option>(returnValue);
            }
            return returnValue;
        }
    };
};

#endif //BASE_SERIAL_INTERFACE_HPP__

ext: hpp

max_stars_repo_name: mfkiwl/corenav-GP

max_issues_repo_name: cagrikilic/corenav-GP

max_forks_repo_name: cagrikilic/corenav-GP


text:
#include <boost/dll/alias.hpp>

namespace plugin
{
	const char* plugin_name()
	{
		return "Vorbrodt's 1st Plugin";
	}

	const char* plugin_version()
	{
		return "1.0";
	}
}

BOOST_DLL_ALIAS(plugin::plugin_name, plugin_name);
BOOST_DLL_ALIAS(plugin::plugin_version, plugin_version);

max_stars_repo_name: salonmor/blog

max_issues_repo_name: salonmor/blog

max_forks_repo_name: salonmor/blog


text:
/*
 * Copyright (c) 2019, András Bodor
 * Licensed under BSD 3-Clause
 * For more information see the supplied
 * LICENSE file
 */

//
// Created by bodand on 2019. 05. 09..
//

#pragma once

#pragma clang diagnostic push
#pragma ide diagnostic ignored "MemberFunctionCanBeStaticInspection"
#pragma ide diagnostic ignored "cert-err58-cpp"

#include <boost/test/included/unit_test.hpp>

#include "../include/info/parse/OptionsParser.hpp"

BOOST_AUTO_TEST_SUITE(Test_OptionsParser)

  BOOST_AUTO_TEST_CASE(Test_OptionsParser_OptionParserCanBeInstantiated) {
      auto* parser = new OptionsParser;
      BOOST_CHECK_NE(parser, nullptr);
      delete parser;
  }

  BOOST_AUTO_TEST_CASE(Test_OptionsParser_MultipleOptionAdditionWorks) {
      auto* parser = new OptionsParser;
      bool a, b;
      parser->addOptions()
                    ("alpha|a", &a)
                    ("beta|b", &b);
      parser->parse("-a -b");
      BOOST_CHECK_MESSAGE(a && b, "Multiple addition of _options succeeded.");
      delete parser;
  }

  BOOST_AUTO_TEST_CASE(Test_OptionsParser_SequentialOptionAdditionWorks) {
      auto* parser = new OptionsParser;
      bool a, b;
      parser->addOption("abool|a", &a)
            .addOption("bbool|b", &b);
      parser->parse("-a -b");
      BOOST_CHECK_MESSAGE(a && b, "Sequential addition of _options succeeded.");
      delete parser;
  }

  BOOST_AUTO_TEST_CASE(Test_OptionsParser_OptionParserParsesLongFlags) {
      auto* parser = new OptionsParser;
      BOOST_REQUIRE_NE(parser, nullptr);
      bool a, b, c;
      parser->addOptions()
                    ("alpha|a", &a)
                    ("beta|b", &b)
                    ("gamma|g", &c);
      parser->parse("--alpha --gamma");
      BOOST_CHECK(a);
      BOOST_CHECK(!b);
      BOOST_CHECK(c);
      delete parser;
  }

  BOOST_AUTO_TEST_CASE(Test_OptionsParser_OptionParserParsesLongOptions) {
      auto* parser = new OptionsParser;
      BOOST_REQUIRE_NE(parser, nullptr);
      int a = 0, b = 0, c = 0;
      parser->addOptions()
                    ("alpha|a", &a)
                    ("beta|b", &b)
                    ("gamma|g", &c);
      parser->parse(" --alpha 42 --gamma 123 ");
      BOOST_CHECK_EQUAL(a, 42);
      BOOST_CHECK_EQUAL(b, 0);
      BOOST_CHECK_EQUAL(c, 123);
      delete parser;
  }

  BOOST_AUTO_TEST_CASE(Test_OptionsParser_OptionsParserParsesShortFlags) {
      auto* parser = new OptionsParser;
      BOOST_REQUIRE_NE(parser, nullptr);
      bool a, b, c;
      parser->addOptions()
                    ("alpha|a", &a)
                    ("beta|b", &b)
                    ("gamma|g", &c);
      parser->parse("-a -g");
      BOOST_CHECK(a);
      BOOST_CHECK(!b);
      BOOST_CHECK(c);
      delete parser;
  }

  BOOST_AUTO_TEST_CASE(Test_OptionsParser_OptionParserParsesShortOptions) {
      auto* parser = new OptionsParser;
      BOOST_REQUIRE_NE(parser, nullptr);
      int a = 0, b = 0, c = 0;
      parser->addOptions()
                    ("alpha|a", &a)
                    ("beta|b", &b)
                    ("gamma|g", &c);
      parser->parse(" -a 42 -g 123 ");
      BOOST_CHECK_EQUAL(a, 42);
      BOOST_CHECK_EQUAL(b, 0);
      BOOST_CHECK_EQUAL(c, 123);
      delete parser;
  }

  BOOST_AUTO_TEST_CASE(Test_OptionsParser_OptionParserParsesBundledArguments) {
      auto* parser = new OptionsParser;
      BOOST_REQUIRE_NE(parser, nullptr);
      int a = 0, b = 0, c = 0;
      bool aa, bb, cc = false;
      parser->addOptions()
                    ("alpha|a", &a)
                    ("beta|b", &b)
                    ("gamma|g", &c)
                    ("die|d", &aa)
                    ("sleep|s", &bb)
                    ("observe|o", &cc);
      parser->parse(" -dsa 42 -g 123 ");
      BOOST_CHECK_EQUAL(a, 42);
      BOOST_CHECK_EQUAL(b, 0);
      BOOST_CHECK_EQUAL(c, 123);
      BOOST_CHECK(aa);
      BOOST_CHECK(bb);
      BOOST_CHECK(!cc);
      delete parser;
  }

  void streamTieHack(const std::string& val) {
      *std::cout.tie() << val;
  }

  BOOST_AUTO_TEST_CASE(Test_OptionsParser_StreamTieHackForFunctionOutput) {
      std::stringstream ss;
      auto tieStream = std::cout.tie();
      std::cout.tie(&ss);
      OptionsParser parser;
      parser.addOption("value", streamTieHack);
      parser.parse(" --value=text ");
      BOOST_CHECK_EQUAL(ss.str(), "text");
      std::cout.tie(tieStream);
  }

  BOOST_AUTO_TEST_CASE(Test_OptionsParser_LambdaWorksAsCallback) {
      std::string s;
      OptionsParser parser;
      parser.addOption<void, const std::string&>("value", [&](const std::string& s_) {
        s = s_;
      });
      parser.parse(" --value=text ");
      BOOST_CHECK_EQUAL(s, "text");
  }

  BOOST_AUTO_TEST_CASE(Test_OptionsParser_StringViewCallback) {
      std::string s;
      OptionsParser parser;
      parser.addOption<void, const std::string&>("value", [&](std::string_view s_) {
        s = s_;
      });
      parser.parse(" --value=text ");
      BOOST_CHECK_EQUAL(s, "text");
  }

BOOST_AUTO_TEST_SUITE_END()

#pragma clang diagnostic pop

max_stars_repo_name: isbodand/infoparse

max_issues_repo_name: isbodand/infoparse

max_forks_repo_name: isbodand/infoparse


text:
// -*- mode: c++; -*-

#include <iostream>
using namespace std;

#include <lbp/frame_range.hpp>
#include <lbp/olbp.hpp>
#include <lbp/varlbp.hpp>
#include <lbp/utils.hpp>

#include <boost/hana/integral_constant.hpp>
#include <boost/hana/tuple.hpp>
#include <boost/hana/functional/compose.hpp>
namespace hana = boost::hana;
using namespace hana::literals;

#include <opencv2/highgui.hpp>
using namespace cv;

#include <options.hpp>
#include <run.hpp>

auto bgr2gray = [](const cv::Mat& src) {
    return lbp::bgr2gray (src);
};

auto gray2float = [](const cv::Mat& src) {
    return lbp::gray2float (src);
};

auto equalize = [](const cv::Mat& src) {
    return lbp::equalize (src);
};

static void
f (cv::VideoCapture& cap, const options_t& opts) {
    const bool display = opts.have ("display");

    auto olbp = lbp::olbp< unsigned char, 2, 10 >;
    auto varlbp = lbp::varlbp< float, 1, 8 >;

    auto op = hana::compose (
        equalize, varlbp, gray2float, equalize, olbp, bgr2gray);

    for (auto& frame : lbp::getframes_from (cap)) {
        lbp::frame_delay temp { 0 };

        const auto dst = op (frame);

        if (display) {
            imshow ("Extended LBP (E-LBP) operator", dst);
        }

        if (temp.wait_for_key (27))
            break;
    }
}

int main (int argc, char** argv) {
    program_options_from (argc, argv);
    return run_with (f, global_options ()), 0;
}

max_stars_repo_name: thinkoid/lbp

max_issues_repo_name: thinkoid/lbp

max_forks_repo_name: thinkoid/lbp