#include "LogDblFact.h"
#include <boost/log/attributes/constant.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <cassert>
#include <cmath>

size_t LogDblFact::get_capacity() const {
	return num_reserved;
}
LogDblFact::Scalar LogDblFact::lg_fast(size_t x) const {
	if (countMaxUsage && x > max_called_lg) {
		max_called_lg = x;
	}
	assert(x < num_reserved);
	return cache_lg[x];
}
[[maybe_unused]] LogDblFact::Scalar LogDblFact::lg_dbl_fact_fast(long x) const {
	if (countMaxUsage && x > max_called_dbl_fac) {
		max_called_dbl_fac = x;
	}
	assert(static_cast<size_t>(std::abs(x)) < num_reserved);
	assert(x >= -1);
	return cache_fac[static_cast<size_t>(x + 1)];
}
LogDblFact::Scalar LogDblFact::lg_rooted_dbl_fact_fast(long x) const {
	if (countMaxUsage && x > max_called_dbl_fac) {
		max_called_dbl_fac = x;
	}
	assert(x >= 2);
	assert(static_cast<size_t>(x + x - 2) < num_reserved);
	return cache_fac[static_cast<size_t>(x + x - 2)];
}
LogDblFact::Scalar LogDblFact::lg_unrooted_dbl_fact_fast(long x) const {
	if (countMaxUsage && x > max_called_dbl_fac) {
		max_called_dbl_fac = x;
	}
	assert(x >= 1);
	assert(static_cast<size_t>(x + x - 4) < num_reserved);
	return cache_fac[static_cast<size_t>(x + x - 4)];
}
void LogDblFact::reserve(size_t num_els) {
	if (num_els <= num_reserved) {
		return;
	}
	cache_fac.reserve(num_els);
	cache_lg.reserve(num_els);
	calc_vals(num_els);
}
LogDblFact::Scalar LogDblFact::lg(size_t x) {
	if (countMaxUsage && x > max_called_lg) {
		max_called_lg = x;
	}
	if (num_reserved <= x) {
		reserve(static_cast<size_t>(x) * GROW_RATE);
	}
	return lg_fast(x);
}
LogDblFact::Scalar LogDblFact::lg_dbl_fact(long x) {
	if (countMaxUsage && x > max_called_dbl_fac) {
		max_called_dbl_fac = x;
	}
	assert(x > -2);
	if (num_reserved <= static_cast<size_t>(std::abs(x))) {
		reserve(static_cast<size_t>(x) * GROW_RATE);
	}
	return lg_dbl_fact_fast(x);
}
LogDblFact::Scalar LogDblFact::lg_rooted_dbl_fact(long x) {
	if (countMaxUsage && x > max_called_rooted) {
		max_called_rooted = x;
	}
	assert(x > -2);
	if (num_reserved <= static_cast<size_t>(std::abs(x + x - 2))) {
		reserve(static_cast<size_t>(x + x - 2) * GROW_RATE); // TODO: make (x - 1) << 1
	}
	return lg_rooted_dbl_fact_fast(x);
}
LogDblFact::Scalar LogDblFact::lg_unrooted_dbl_fact(long x) {
	assert(x > -2);
	if (countMaxUsage && x > max_called_unrooted) {
		max_called_unrooted = x;
	}
	if (num_reserved <= static_cast<size_t>(std::abs(x + x - 4))) {
		reserve(static_cast<size_t>(x + x - 4) * GROW_RATE); // TODO: make (x - 2) << 1
	}
	return lg_unrooted_dbl_fact_fast(x);
}

LogDblFact::LogDblFact() noexcept : cache_fac(PRECALC_NUM_ELEMS), cache_lg(PRECALC_NUM_ELEMS) {
	static_assert(PRECALC_NUM_ELEMS > 3);
	static_assert(GROW_RATE >= 1.4);
	cache_fac[0] = 0.; // lg2(-1!!)
	cache_fac[1] = 0.; // lg2(0!!)
	cache_fac[2] = 0.;
	cache_lg[0] = static_cast<double>(-INFINITY);
	cache_lg[1] = 0;
	cache_lg[2] = 1;
	// calc factorials and log from here
	calc_vals(PRECALC_NUM_ELEMS);
	logger.add_attribute("Tag",
	                     boost::log::attributes::constant<std::string>("DoubleFac_Lg_Cache"));
}
void LogDblFact::calc_vals(size_t new_num_els) {
	for (size_t x = 3; x < new_num_els; ++x) {
		cache_lg[x] = (std::log2(x));
		// calc lg((x-1)!!)
		cache_fac[x] = cache_fac[x - 2] + cache_lg[x - 1];
	}
	num_reserved = new_num_els;
}
void LogDblFact::printLog() {
	if (countMaxUsage) {
		BOOST_LOG_SEV(logger, lg::notification)
		    << "Reserved " << num_reserved << " elements. Maximum lg called was " << max_called_lg
		    << " Max unrooted: " << max_called_unrooted << " (=>"
		    << max_called_unrooted + max_called_unrooted - 4 << ")"
		    << " Max rooted: " << max_called_rooted << " ("
		    << max_called_rooted + max_called_rooted - 2 << ")"
		    << " Max dbl fac: " << max_called_dbl_fac << " (=>" << max_called_dbl_fac + 1 << ")";
	}
}


#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/random/mersenne_twister.hpp>

#include "asserts.hpp"
#include "unit_test.hpp"
#include "uuid.hpp"

namespace {

boost::mt19937* twister_rng() {
	static boost::mt19937 ran;
	ran.seed(boost::posix_time::microsec_clock::local_time().time_of_day().total_milliseconds());
	return &ran;
}

UNIT_TEST(serialize_uuid) {
	for(int i = 0; i != 8; ++i) {
		boost::uuids::uuid id = generate_uuid();
		const bool succeeded = id == read_uuid(write_uuid(id));
		CHECK_EQ(succeeded, true);
	}
}

}

boost::uuids::uuid generate_uuid() {
	static boost::uuids::basic_random_generator<boost::mt19937> gen(twister_rng());
	return gen();
}

std::string write_uuid(const boost::uuids::uuid& id) {
	char result[33];
	char* ptr = result;
	for(auto num : id) {
		sprintf(ptr, "%02x", static_cast<int>(num));
		ptr += 2;
	}
	return std::string(result, result+32);
}

boost::uuids::uuid read_uuid(const std::string& s) {
	boost::uuids::uuid result;

	const std::string& nums = s;
	const char* ptr = nums.c_str();
	ASSERT_LOG(nums.size() == 32, "Trying to deserialize bad UUID: " << nums);
	for(auto itor = result.begin(); itor != result.end(); ++itor) {
		char buf[3];
		buf[0] = *ptr++;
		buf[1] = *ptr++;
		buf[2] = 0;

		*itor = strtol(buf, NULL, 16);
	}

	return result;
}


//////////////////////////////////////////////////////////////////////////////
// data::unit::survival::records::record.hpp								//
//                                                                          //
//                                                                          //
//  (C) Copyright 2010 Erwann Rogard                                        //
//  Use, modification and distribution are subject to the                   //
//  Boost Software License, Version 1.0. (See accompanying file             //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)        //
//////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_DETAIL_DATA_UNIT_SURVIVAL_RECORDS_RECORD_HPP_ER_2010
#define BOOST_STATISTICS_DETAIL_DATA_UNIT_SURVIVAL_RECORDS_RECORD_HPP_ER_2010
#include <boost/statistics/detail/data/field/survival/fields/entry_time.hpp>
#include <boost/statistics/detail/data/field/survival/fields/failure_time.hpp>
#include <boost/statistics/detail/data/unit/framework/impl.hpp>

namespace boost{
namespace statistics{
namespace detail{
namespace data{
namespace unit{
namespace survival{

    // This is probably the most common form of record
    typedef data::unit::impl<
        field::float_valued::entry_time,
        field::float_valued::failure_time
    > record;

}// unit
}// data
}// detail
}// statistics
}// boost

#endif


// Copyright (c) 2017 Graphcore Ltd. All rights reserved.
#define BOOST_TEST_MODULE StdArithmeticTests

#include "poplibs_support/TileConstants.hpp"
#include "popops/ElementWise.hpp"
#include <boost/multi_array.hpp>
#include <boost/test/unit_test.hpp>
#include <cmath>
#include <cstring>
#include <iostream>
#include <poplar/CSRFunctions.hpp>
#include <poplar/Engine.hpp>
#include <poplar/IPUModel.hpp>
#include <poplibs_support/TestDevice.hpp>
#include <popops/Cast.hpp>
#include <popops/ScaledAdd.hpp>
#include <popops/codelets.hpp>
#include <poputil/TileMapping.hpp>

using namespace poplar;
using namespace poplar::program;
using namespace poputil;
using namespace popops;
using namespace poplibs_support;

namespace utf = boost::unit_test;
namespace fpc = boost::test_tools::fpc;

#define DIM_SIZE 10

__attribute((unused)) static std::tuple<Tensor, Tensor>
mapBinaryOpTensors(Graph &graph, const Type &type) {
  auto in1 = graph.addVariable(type, {DIM_SIZE, DIM_SIZE}, "in1");
  mapTensorLinearly(graph, in1);

  auto in2 = graph.addVariable(type, {DIM_SIZE, DIM_SIZE}, "in2");
  mapTensorLinearly(graph, in2);

  return std::make_pair(in1.dimShuffle({1, 0}), in2.dimShuffle({1, 0}));
}

__attribute((unused)) static void
setBinaryOpInputs(float hIn1[DIM_SIZE][DIM_SIZE],
                  float hIn2[DIM_SIZE][DIM_SIZE]) {
  float val1 = -100;
  float val2 = 50;
  for (auto r = 0U; r != DIM_SIZE; ++r) {
    for (auto c = 0U; c != DIM_SIZE; ++c) {
      float sign1 = (1.0 - 2.0 * ((c + 1) & 1));
      float sign2 = (1.0 - 2.0 * ((r + c) & 1));
      hIn1[r][c] = (val1 + (r * DIM_SIZE + c) * .1) * sign1;
      hIn2[r][c] = (val2 + (r * DIM_SIZE + c) * .1) * sign2;
    }
  }
}

__attribute((unused)) static void
setBinaryOpInputs(int hIn1[DIM_SIZE][DIM_SIZE], int hIn2[DIM_SIZE][DIM_SIZE]) {
  int val1 = -100;
  int val2 = 50;
  for (auto r = 0; r != DIM_SIZE; ++r) {
    for (auto c = 0; c != DIM_SIZE; ++c) {
      int sign1 = (1 - 2 * ((c + 1) & 1));
      int sign2 = (1 - 2 * ((r + c) & 1));
      hIn1[r][c] = (val1 + (r * DIM_SIZE + c) * 1) * sign1;
      hIn2[r][c] = (val2 + (r * DIM_SIZE + c) * 1) * sign2;
    }
  }
}
__attribute((unused)) static void
setBroadcastOpInputs(float hIn1[DIM_SIZE][DIM_SIZE]) {
  float val1 = -100;
  for (auto r = 0; r != DIM_SIZE; ++r) {
    for (auto c = 0; c != DIM_SIZE; ++c) {
      int sign1 = (1 - 2 * ((c + 1) & 1));
      hIn1[r][c] = (val1 + (r * DIM_SIZE + c) * 1) * sign1;
    }
  }
}

BOOST_AUTO_TEST_CASE(
    StdBroadcastAdd_float,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(0.01)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(0.01))) {
  auto device = createTestDevice(TEST_TARGET);
  Graph graph(device.getTarget());
  popops::addCodelets(graph);

  float hIn[DIM_SIZE][DIM_SIZE];
  setBroadcastOpInputs(hIn);

  float k = 2;
  auto B = graph.addVariable(FLOAT, {});
  graph.setInitialValue(B, k);
  auto in = graph.addVariable(FLOAT, {DIM_SIZE, DIM_SIZE}, "in1");
  mapTensorLinearly(graph, in);
  mapTensorLinearly(graph, B);

  graph.createHostWrite("in", in);
  graph.createHostRead("out", in);
  auto prog = Sequence();

  addInPlace(graph, in, B, prog);
  Engine eng(graph, prog);
  float hOut[DIM_SIZE][DIM_SIZE];

  device.bind([&](const Device &d) {
    eng.load(d);

    eng.writeTensor("in", hIn, &hIn[DIM_SIZE]);
    eng.run();
    eng.readTensor("out", hOut, &hOut[DIM_SIZE]);
  });

  /* Check result */
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    for (auto j = 0U; j < DIM_SIZE; ++j) {
      double res = hIn[i][j] + k;
      BOOST_TEST(hOut[i][j] == res);
    }
  }
}

BOOST_AUTO_TEST_CASE(
    StdBroadcastMultiply_float,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(0.01)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(0.01))) {
  auto device = createTestDevice(TEST_TARGET);
  Graph graph(device.getTarget());
  popops::addCodelets(graph);

  float hIn[DIM_SIZE][DIM_SIZE];
  setBroadcastOpInputs(hIn);

  float k = 2;
  auto B = graph.addVariable(FLOAT, {});
  graph.setInitialValue(B, k);
  auto in = graph.addVariable(FLOAT, {DIM_SIZE, DIM_SIZE}, "in1");
  mapTensorLinearly(graph, in);
  mapTensorLinearly(graph, B);

  graph.createHostWrite("in", in);
  graph.createHostRead("out", in);
  auto prog = Sequence();

  mulInPlace(graph, in, B, prog);
  Engine eng(graph, prog);
  float hOut[DIM_SIZE][DIM_SIZE];

  device.bind([&](const Device &d) {
    eng.load(d);

    eng.writeTensor("in", hIn, &hIn[DIM_SIZE]);
    eng.run();
    eng.readTensor("out", hOut, &hOut[DIM_SIZE]);
  });

  /* Check result */
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    for (auto j = 0U; j < DIM_SIZE; ++j) {
      double res = hIn[i][j] * k;
      BOOST_TEST(hOut[i][j] == res);
    }
  }
}

BOOST_AUTO_TEST_CASE(
    StdBroadcastSubtract_half,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(0.01)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(0.01))) {
  auto device = createTestDevice(TEST_TARGET);
  auto target = device.getTarget();
  Graph graph(target);
  popops::addCodelets(graph);

  float hIn[DIM_SIZE][DIM_SIZE];
  setBroadcastOpInputs(hIn);

  auto rawBufSize = target.getTypeSize(HALF) * DIM_SIZE * DIM_SIZE;
  std::vector<char> rawIn(rawBufSize);
  poplar::copyFloatToDeviceHalf(target, &hIn[0][0], rawIn.data(),
                                DIM_SIZE * DIM_SIZE);

  float k = 2;
  auto B = graph.addVariable(HALF, {});
  graph.setInitialValue(B, k);
  auto in = graph.addVariable(HALF, {DIM_SIZE, DIM_SIZE}, "in1");
  mapTensorLinearly(graph, in);
  mapTensorLinearly(graph, B);

  std::vector<char> rawOut(rawBufSize);
  graph.createHostWrite("in", in);
  graph.createHostRead("out", in);
  auto prog = Sequence();

  subInPlace(graph, in, B, prog);
  Engine eng(graph, prog);

  device.bind([&](const Device &d) {
    eng.load(d);

    eng.writeTensor("in", rawIn.data(), rawIn.data() + rawIn.size());
    eng.run();
    eng.readTensor("out", rawOut.data(), rawOut.data() + rawOut.size());
  });

  float hOut[DIM_SIZE][DIM_SIZE];
  poplar::copyDeviceHalfToFloat(target, rawOut.data(), &hOut[0][0],
                                DIM_SIZE * DIM_SIZE);

  /* Check result */
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    for (auto j = 0U; j < DIM_SIZE; ++j) {
      double res = hIn[i][j] - k;
      BOOST_TEST(hOut[i][j] == res);
    }
  }
}

BOOST_AUTO_TEST_CASE(
    StdAddTo_half_float_tensor,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(1.4)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(1.4))) {
  auto device = createTestDevice(TEST_TARGET);
  auto target = device.getTarget();
  Graph graph(target);
  popops::addCodelets(graph);

  float hIn1[DIM_SIZE][DIM_SIZE], hIn2[DIM_SIZE][DIM_SIZE];
  setBinaryOpInputs(hIn1, hIn2);

  float k = 2;
  auto factor = graph.addVariable(HALF, {});
  graph.setInitialValue(factor, k);

  auto in1 = graph.addVariable(HALF, {DIM_SIZE, DIM_SIZE}, "in1");
  auto in2 = graph.addVariable(FLOAT, {DIM_SIZE, DIM_SIZE}, "in2");
  mapTensorLinearly(graph, in1);
  mapTensorLinearly(graph, in2);
  mapTensorLinearly(graph, factor);

  auto rawBufSize = target.getTypeSize(HALF) * DIM_SIZE * DIM_SIZE;
  std::vector<char> rawIn1(rawBufSize);
  poplar::copyFloatToDeviceHalf(target, &hIn1[0][0], rawIn1.data(),
                                DIM_SIZE * DIM_SIZE);

  graph.createHostWrite("in1", in1);
  graph.createHostWrite("in2", in2);
  graph.createHostRead("out", in1);
  auto prog = Sequence();
  scaledAddTo(graph, in1, in2, factor, prog);
  Engine eng(graph, prog);

  std::vector<char> rawOut(rawBufSize);
  float hOut[DIM_SIZE][DIM_SIZE];

  device.bind([&](const Device &d) {
    eng.load(d);
    eng.writeTensor("in1", rawIn1.data(), rawIn1.data() + rawIn1.size());
    eng.writeTensor("in2", hIn2, &hIn2[DIM_SIZE]);
    eng.run();
    eng.readTensor("out", rawOut.data(), rawOut.data() + rawOut.size());
  });

  poplar::copyDeviceHalfToFloat(target, rawOut.data(), &hOut[0][0],
                                DIM_SIZE * DIM_SIZE);

  // Check result
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    for (auto j = 0U; j < DIM_SIZE; ++j) {
      double res = hIn1[i][j] + k * hIn2[i][j];
      BOOST_TEST(hOut[i][j] == res);
    }
  }
}

BOOST_AUTO_TEST_CASE(
    StdAddTo_float_half,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(1.4)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(1.4))) {
  auto device = createTestDevice(TEST_TARGET, 1, 2);
  auto target = device.getTarget();
  Graph graph(target);
  popops::addCodelets(graph);

  float hIn1[DIM_SIZE][DIM_SIZE], hIn2[DIM_SIZE][DIM_SIZE];
  setBinaryOpInputs(hIn1, hIn2);

  float k = 2.1;
  auto factorHalf = graph.addVariable(HALF, {});
  auto factorFloat = graph.addVariable(FLOAT, {});
  graph.setInitialValue(factorHalf, k);
  graph.setInitialValue(factorFloat, k);

  auto in1TensorFloat = graph.addVariable(FLOAT, {DIM_SIZE, DIM_SIZE}, "in1TF");
  auto in1TensorHalf = graph.addVariable(FLOAT, {DIM_SIZE, DIM_SIZE}, "in1TH");
  auto in1ConstFloat = graph.addVariable(FLOAT, {DIM_SIZE, DIM_SIZE}, "in1CF");
  auto in1ConstHalf = graph.addVariable(FLOAT, {DIM_SIZE, DIM_SIZE}, "in1CH");
  auto in2 = graph.addVariable(HALF, {DIM_SIZE, DIM_SIZE}, "in2");
  graph.setTileMapping(in1TensorFloat, 0);
  graph.setTileMapping(in1TensorHalf, 0);
  graph.setTileMapping(in1ConstFloat, 0);
  graph.setTileMapping(in1ConstHalf, 0);

  mapTensorLinearly(graph, in2);
  mapTensorLinearly(graph, factorHalf);
  mapTensorLinearly(graph, factorFloat);

  // Map differently, causing 2D decisions and vertex connection to happen
  graph.setTileMapping(in1TensorFloat.flatten().slice({4, 8}), 1);
  graph.setTileMapping(in1ConstFloat.flatten().slice({4, 8}), 1);
  graph.setTileMapping(in1TensorHalf.flatten().slice({4, 8}), 1);
  graph.setTileMapping(in1ConstHalf.flatten().slice({4, 8}), 1);

  auto rawBufSize = target.getTypeSize(HALF) * DIM_SIZE * DIM_SIZE;
  std::vector<char> rawIn2(rawBufSize);
  poplar::copyFloatToDeviceHalf(target, &hIn2[0][0], rawIn2.data(),
                                DIM_SIZE * DIM_SIZE);

  graph.createHostWrite("in1TF", in1TensorFloat);
  graph.createHostWrite("in1TH", in1TensorHalf);
  graph.createHostWrite("in1CF", in1ConstFloat);
  graph.createHostWrite("in1CH", in1ConstHalf);
  graph.createHostWrite("in2", in2);
  graph.createHostRead("outTF", in1TensorFloat);
  graph.createHostRead("outTH", in1TensorHalf);
  graph.createHostRead("outCF", in1ConstFloat);
  graph.createHostRead("outCH", in1ConstHalf);
  auto prog = Sequence();
  scaledAddTo(graph, in1TensorFloat, in2, factorFloat, prog);
  scaledAddTo(graph, in1TensorHalf, in2, factorHalf, prog);
  scaledAddTo(graph, in1ConstFloat, in2, k, prog, "ForcedFloatScale",
              {{"scaleFloatToHalfTolerance", "2.0"}});
  scaledAddTo(graph, in1ConstHalf, in2, k, prog);
  Engine eng(graph, prog);

  float hOutTensorFloat[DIM_SIZE][DIM_SIZE];
  float hOutTensorHalf[DIM_SIZE][DIM_SIZE];
  float hOutConstFloat[DIM_SIZE][DIM_SIZE];
  float hOutConstHalf[DIM_SIZE][DIM_SIZE];

  device.bind([&](const Device &d) {
    eng.load(d);
    eng.writeTensor("in1TF", hIn1, &hIn1[DIM_SIZE]);
    eng.writeTensor("in1TH", hIn1, &hIn1[DIM_SIZE]);
    eng.writeTensor("in1CF", hIn1, &hIn1[DIM_SIZE]);
    eng.writeTensor("in1CH", hIn1, &hIn1[DIM_SIZE]);
    eng.writeTensor("in2", rawIn2.data(), rawIn2.data() + rawIn2.size());
    eng.run();
    eng.readTensor("outTF", hOutTensorFloat, &hOutTensorFloat[DIM_SIZE]);
    eng.readTensor("outTH", hOutTensorHalf, &hOutTensorHalf[DIM_SIZE]);
    eng.readTensor("outCF", hOutConstFloat, &hOutConstFloat[DIM_SIZE]);
    eng.readTensor("outCH", hOutConstHalf, &hOutConstHalf[DIM_SIZE]);
  });

  // Check result
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    for (auto j = 0U; j < DIM_SIZE; ++j) {
      double res = hIn1[i][j] + k * hIn2[i][j];
      BOOST_TEST(hOutTensorFloat[i][j] == res);
      BOOST_TEST(hOutTensorHalf[i][j] == res);
      BOOST_TEST(hOutConstFloat[i][j] == res);
      BOOST_TEST(hOutConstHalf[i][j] == res);
    }
  }
}

BOOST_AUTO_TEST_CASE(
    StdAddTo_half_scale_float_tensor_const,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(0.1)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(0.1))) {
  auto device = createTestDevice(TEST_TARGET, 1, 2);
  auto target = device.getTarget();
  Graph graph(target);
  popops::addCodelets(graph);

  float hIn1[DIM_SIZE];
  float hIn2[DIM_SIZE];

  // Large values for the 2nd operand which, when multiplied by a very small
  // scale should have a sensible result
  for (unsigned i = 0; i < DIM_SIZE; i++) {
    hIn1[i] = (1.0f / 1024.0f);
    hIn2[i] = 10000 * (i % 4);
  }
  // Very small k to make the selection of codelets work to solve the issue
  // that it can't be represented as a half
  float k = (3.0e-9);
  auto factor = graph.addVariable(FLOAT, {});
  graph.setInitialValue(factor, k);

  auto in1 = graph.addVariable(HALF, {DIM_SIZE}, "in1");
  auto in1ConstTest = graph.addVariable(HALF, {DIM_SIZE}, "in1");
  auto in1Fails = graph.addVariable(HALF, {DIM_SIZE}, "in1");
  auto in1ConstTestFails = graph.addVariable(HALF, {DIM_SIZE}, "in1");
  auto in2 = graph.addVariable(HALF, {DIM_SIZE}, "in2");
  mapTensorLinearly(graph, in1);
  mapTensorLinearly(graph, in1ConstTest);
  mapTensorLinearly(graph, in1Fails);
  mapTensorLinearly(graph, in1ConstTestFails);
  mapTensorLinearly(graph, in2);
  mapTensorLinearly(graph, factor);
  // Map differently, causing 2D decisions and vertex connection to happen
  graph.setTileMapping(in1[4], 1);

  auto rawBufSize = target.getTypeSize(HALF) * DIM_SIZE;
  std::vector<char> rawIn1(rawBufSize), rawIn2(rawBufSize);
  poplar::copyFloatToDeviceHalf(target, &hIn1[0], rawIn1.data(), DIM_SIZE);
  poplar::copyFloatToDeviceHalf(target, &hIn2[0], rawIn2.data(), DIM_SIZE);

  graph.createHostWrite("in1", in1);
  graph.createHostWrite("in1ConstTest", in1ConstTest);
  graph.createHostWrite("in1Fails", in1Fails);
  graph.createHostWrite("in1ConstTestFails", in1ConstTestFails);
  graph.createHostWrite("in2", in2);
  graph.createHostRead("out", in1);
  graph.createHostRead("outConstTest", in1ConstTest);
  graph.createHostRead("outFails", in1Fails);
  graph.createHostRead("outConstTestFails", in1ConstTestFails);

  auto prog = Sequence();
  // These tests should produce a reasonable answer as the scale and multiply
  // is implemented in full precision
  scaledAddTo(graph, in1, in2, factor, prog, "Tensor test",
              {{"scaleFloatToHalfTolerance", "1e-6"}});
  scaledAddTo(graph, in1ConstTest, in2, k, prog, "Const test",
              {{"scaleFloatToHalfTolerance", "1e-6"}});

  // These tests should "fail", producing 0.0 exactly as the scale becomes
  // 0.0 in half precision.  We use the tolerance option to switch
  // automatic selection of full precision arithmetic off.
  scaledAddTo(graph, in1Fails, in2, factor, prog, "Tensor test fail",
              {{"scaleFloatToHalfTolerance", "2.0"}});
  scaledAddTo(graph, in1ConstTestFails, in2, k, prog, "Const test fail",
              {{"scaleFloatToHalfTolerance", "2.0"}});
  Engine eng(graph, prog);

  std::vector<char> rawOut(rawBufSize), rawOutConstTest(rawBufSize);
  std::vector<char> rawOutFails(rawBufSize), rawOutConstTestFails(rawBufSize);

  device.bind([&](const Device &d) {
    eng.load(d);

    eng.writeTensor("in1", rawIn1.data(), rawIn1.data() + rawIn1.size());
    eng.writeTensor("in1ConstTest", rawIn1.data(),
                    rawIn1.data() + rawIn1.size());
    eng.writeTensor("in1Fails", rawIn1.data(), rawIn1.data() + rawIn1.size());
    eng.writeTensor("in1ConstTestFails", rawIn1.data(),
                    rawIn1.data() + rawIn1.size());
    eng.writeTensor("in2", rawIn2.data(), rawIn2.data() + rawIn2.size());

    eng.run();

    eng.readTensor("out", rawOut.data(), rawOut.data() + rawOut.size());
    eng.readTensor("outConstTest", rawOutConstTest.data(),
                   rawOutConstTest.data() + rawOutConstTest.size());
    eng.readTensor("outFails", rawOutFails.data(),
                   rawOutFails.data() + rawOutFails.size());
    eng.readTensor("outConstTestFails", rawOutConstTestFails.data(),
                   rawOutConstTestFails.data() + rawOutConstTestFails.size());
  });

  float hOut[DIM_SIZE], hOutConstTest[DIM_SIZE];
  float hOutFails[DIM_SIZE], hOutConstTestFails[DIM_SIZE];
  poplar::copyDeviceHalfToFloat(target, rawOut.data(), &hOut[0], DIM_SIZE);
  poplar::copyDeviceHalfToFloat(target, rawOutConstTest.data(),
                                &hOutConstTest[0], DIM_SIZE);
  poplar::copyDeviceHalfToFloat(target, rawOutFails.data(), &hOutFails[0],
                                DIM_SIZE);
  poplar::copyDeviceHalfToFloat(target, rawOutConstTestFails.data(),
                                &hOutConstTestFails[0], DIM_SIZE);

  // Check result
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    double res = hIn1[i] + k * hIn2[i];
    BOOST_TEST(hOut[i] == res);
    BOOST_TEST(hOutConstTest[i] == res);
    // These tests should "fail", producing hIn1 exactly.
    std::cout << "hOut:" << hOut[i] << " hOutConstTest:" << hOutConstTest[i]
              << " hOutFails:" << hOutFails[i]
              << " hOutConstTestFails:" << hOutConstTestFails[i] << "\n";
    // Avoid testing this properly in IPUModel, as half isn't accurate
    if (!isIpuModel(TEST_TARGET)) {
      BOOST_TEST(static_cast<bool>(hOutFails[i] == hIn1[i]));
      BOOST_TEST(static_cast<bool>(hOutConstTestFails[i] == hIn1[i]));
    }
  }
}

BOOST_AUTO_TEST_CASE(
    StdAddTo_float_constant,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(0.01)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(0.01))) {
  auto device = createTestDevice(TEST_TARGET);
  Graph graph(device.getTarget());
  popops::addCodelets(graph);

  float hIn1[DIM_SIZE][DIM_SIZE], hIn2[DIM_SIZE][DIM_SIZE];
  setBinaryOpInputs(hIn1, hIn2);

  float k = 2;
  Tensor in1, in2;
  std::tie(in1, in2) = mapBinaryOpTensors(graph, FLOAT);

  graph.createHostWrite("in1", in1);
  graph.createHostWrite("in2", in2);
  graph.createHostRead("out", in1);
  auto prog = Sequence();
  scaledAddTo(graph, in1, in2, k, prog);
  Engine eng(graph, prog);
  float hOut[DIM_SIZE][DIM_SIZE];

  device.bind([&](const Device &d) {
    eng.load(d);
    eng.writeTensor("in1", hIn1, &hIn1[DIM_SIZE]);
    eng.writeTensor("in2", hIn2, &hIn2[DIM_SIZE]);
    eng.run();
    eng.readTensor("out", hOut, &hOut[DIM_SIZE]);
  });

  /* Check result */
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    for (auto j = 0U; j < DIM_SIZE; ++j) {
      double res = hIn1[i][j] + k * hIn2[i][j];
      BOOST_TEST(hOut[i][j] == res);
    }
  }
}

BOOST_AUTO_TEST_CASE(
    StdAddTo_float_runtime_fast_path,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(0.01)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(0.01))) {
  auto device = createTestDevice(TEST_TARGET);
  Graph graph(device.getTarget());
  popops::addCodelets(graph);

  float hIn1[DIM_SIZE][DIM_SIZE], hIn2[DIM_SIZE][DIM_SIZE];
  setBinaryOpInputs(hIn1, hIn2);

  float k = 2;
  // Creating a larger tensor to force a gap between allocations of the two
  // operands will result in the fast path being chosen at runtime.
  // Note TMEM_ELEMSIZE is in bytes
  const unsigned padSize = 16 + (TMEM_ELEMSIZE * 2) / 4;
  const unsigned regionSize = 2 * DIM_SIZE * DIM_SIZE + padSize;
  auto in = graph.addVariable(FLOAT, {regionSize}, "Whole input");
  graph.setTileMapping(in, 0);

  auto in1 = in.slice(0, DIM_SIZE * DIM_SIZE);
  auto in2 = in.slice(padSize, padSize + DIM_SIZE * DIM_SIZE);

  graph.createHostWrite("in1", in1);
  graph.createHostWrite("in2", in2);
  graph.createHostRead("out", in1);
  auto prog = Sequence();
  scaledAddTo(graph, in1, in2, k, prog);
  Engine eng(graph, prog);
  float hOut[DIM_SIZE][DIM_SIZE];

  device.bind([&](const Device &d) {
    eng.load(d);
    eng.writeTensor("in1", hIn1, &hIn1[DIM_SIZE]);
    eng.writeTensor("in2", hIn2, &hIn2[DIM_SIZE]);
    eng.run();
    eng.readTensor("out", hOut, &hOut[DIM_SIZE]);
  });

  /* Check result */
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    for (auto j = 0U; j < DIM_SIZE; ++j) {
      double res = hIn1[i][j] + k * hIn2[i][j];
      BOOST_TEST(hOut[i][j] == res);
    }
  }
}

BOOST_AUTO_TEST_CASE(
    StdAddTo_float_tensor,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(0.01)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(0.01))) {
  auto device = createTestDevice(TEST_TARGET);
  Graph graph(device.getTarget());
  popops::addCodelets(graph);

  float hIn1[DIM_SIZE][DIM_SIZE], hIn2[DIM_SIZE][DIM_SIZE];
  setBinaryOpInputs(hIn1, hIn2);

  float k = 2;
  auto factor = graph.addVariable(FLOAT, {});
  graph.setInitialValue(factor, k);
  Tensor in1, in2;
  std::tie(in1, in2) = mapBinaryOpTensors(graph, FLOAT);

  graph.createHostWrite("in1", in1);
  graph.createHostWrite("in2", in2);
  graph.createHostRead("out", in1);
  auto prog = Sequence();
  scaledAddTo(graph, in1, in2, factor, prog);
  Engine eng(graph, prog);
  float hOut[DIM_SIZE][DIM_SIZE];

  device.bind([&](const Device &d) {
    eng.load(d);
    eng.writeTensor("in1", hIn1, &hIn1[DIM_SIZE]);
    eng.writeTensor("in2", hIn2, &hIn2[DIM_SIZE]);
    eng.run();
    eng.readTensor("out", hOut, &hOut[DIM_SIZE]);
  });

  // Check result
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    for (auto j = 0U; j < DIM_SIZE; ++j) {
      double res = hIn1[i][j] + k * hIn2[i][j];
      BOOST_TEST(hOut[i][j] == res);
    }
  }
}

BOOST_AUTO_TEST_CASE(
    StdSubtractFrom_float_tensor,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(0.01)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(0.01))) {
  auto device = createTestDevice(TEST_TARGET);
  Graph graph(device.getTarget());
  popops::addCodelets(graph);

  float hIn1[DIM_SIZE][DIM_SIZE], hIn2[DIM_SIZE][DIM_SIZE];
  setBinaryOpInputs(hIn1, hIn2);

  float k = 2;
  auto factor = graph.addVariable(FLOAT, {});
  graph.setInitialValue(factor, k);
  Tensor in1, in2;
  std::tie(in1, in2) = mapBinaryOpTensors(graph, FLOAT);

  graph.createHostWrite("in1", in1);
  graph.createHostWrite("in2", in2);
  graph.createHostRead("out", in1);
  auto prog = Sequence();
  scaledSubtractFrom(graph, in1, in2, factor, prog);
  Engine eng(graph, prog);
  float hOut[DIM_SIZE][DIM_SIZE];

  device.bind([&](const Device &d) {
    eng.load(d);
    eng.writeTensor("in1", hIn1, &hIn1[DIM_SIZE]);
    eng.writeTensor("in2", hIn2, &hIn2[DIM_SIZE]);
    eng.run();
    eng.readTensor("out", hOut, &hOut[DIM_SIZE]);
  });

  // Check result
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    for (auto j = 0U; j < DIM_SIZE; ++j) {
      double res = hIn1[i][j] - k * hIn2[i][j];
      BOOST_TEST(hOut[i][j] == res);
    }
  }
}

BOOST_AUTO_TEST_CASE(
    StdSubFrom_int,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(0.01)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(0.01))) {
  auto device = createTestDevice(TEST_TARGET);
  Graph graph(device.getTarget());
  popops::addCodelets(graph);

  int hIn1[DIM_SIZE][DIM_SIZE], hIn2[DIM_SIZE][DIM_SIZE];
  setBinaryOpInputs(hIn1, hIn2);

  int k = 2;
  Tensor in1, in2;
  std::tie(in1, in2) = mapBinaryOpTensors(graph, INT);

  graph.createHostWrite("in1", in1);
  graph.createHostWrite("in2", in2);
  graph.createHostRead("out", in1);
  auto prog = Sequence();
  scaledSubtractFrom(graph, in1, in2, k, prog);
  Engine eng(graph, prog);
  int hOut[DIM_SIZE][DIM_SIZE];

  device.bind([&](const Device &d) {
    eng.load(d);
    eng.writeTensor("in1", hIn1, &hIn1[DIM_SIZE]);
    eng.writeTensor("in2", hIn2, &hIn2[DIM_SIZE]);
    eng.run();
    eng.readTensor("out", hOut, &hOut[DIM_SIZE]);
  });

  /* Check result */
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    for (auto j = 0U; j < DIM_SIZE; ++j) {
      double res = hIn1[i][j] - k * hIn2[i][j];
      BOOST_TEST(hOut[i][j] == res);
    }
  }
}

// Test for "aX + bY", via 'scaledAddTo()'. A few different sub-tests are run.
//    X   can be    HALF or FLOAT
//    Y   is always HALF
//   a,b  can be    HALF or FLOAT tensors, or constants
BOOST_AUTO_TEST_CASE(
    StdaXPlusbY_halfin_tensor_and_const,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(1)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(1))) {
  auto device = createTestDevice(TEST_TARGET);
  auto target = device.getTarget();
  Graph graph(target);
  popops::addCodelets(graph);

  // We do a bunch of sub test cases (test variants) that do mostly the same
  // stuff; we put in this class the common parts.
  struct Variant {
    std::string name; // name of the test
    Type dataXType;   // Device type of the X data
    float a, b;       // Scale values
    poplar::OptionFlags opts;

    Variant(std::string name, Type dataXType, float a, float b,
            OptionFlags opts)
        : name(name), dataXType(dataXType), a(a), b(b), opts(opts) {}
    virtual ~Variant() {}

    // data tensors and associated buffers
    Tensor X, Y;
    std::vector<char> rawX;
    boost::multi_array<float, 2> hX{boost::extents[DIM_SIZE][DIM_SIZE]};
    std::vector<char> rawY;
    boost::multi_array<float, 2> hY{boost::extents[DIM_SIZE][DIM_SIZE]};

    // Setup everything before bind/load/run
    void setup(Graph &graph, Target &target, Sequence &prog) {
      X = graph.addVariable(dataXType, {DIM_SIZE, DIM_SIZE}, name + "X");
      Y = graph.addVariable(HALF, {DIM_SIZE, DIM_SIZE}, name + "Y");
      mapTensorLinearly(graph, X);
      mapTensorLinearly(graph, Y);
      graph.createHostWrite(name + "X", X);
      graph.createHostRead(name + "Xout", X);
      graph.createHostWrite(name + "Y", Y);
      callScaledAdd(graph, prog);
    };

    // This will call the 'scaledAddTo()'. Different for Tensors and Const
    virtual void callScaledAdd(Graph &graph, Sequence &prog) = 0;

    // Setup the host buffers and write the data Tensors to the device
    void write(Target &target, Engine &eng, float Xvalues[DIM_SIZE][DIM_SIZE],
               float Yvalues[DIM_SIZE][DIM_SIZE]) {
      auto floatBufSize = sizeof(float) * DIM_SIZE * DIM_SIZE;
      auto rawBufSize = target.getTypeSize(HALF) * DIM_SIZE * DIM_SIZE;

      std::memcpy(&hX[0][0], Xvalues, floatBufSize);
      if (dataXType == HALF) {
        rawX.resize(rawBufSize);
        poplar::copyFloatToDeviceHalf(target, &hX[0][0], rawX.data(),
                                      DIM_SIZE * DIM_SIZE);
        eng.writeTensor(name + "X", rawX.data(), rawX.data() + rawX.size());
      } else {
        float *data = &hX[0][0];
        eng.writeTensor(name + "X", data, data + floatBufSize);
      }

      std::memcpy(&hY[0][0], Yvalues, floatBufSize);
      rawY.resize(rawBufSize);
      poplar::copyFloatToDeviceHalf(target, &hY[0][0], rawY.data(),
                                    DIM_SIZE * DIM_SIZE);
      eng.writeTensor(name + "Y", rawY.data(), rawY.data() + rawY.size());
    };

    // Read the result Tensor from the device and check results
    void readAndVerify(Target &target, Engine &eng) {
      boost::multi_array<float, 2> hXout{boost::extents[DIM_SIZE][DIM_SIZE]};
      if (dataXType == HALF) {
        eng.readTensor(name + "Xout", rawX.data(), rawX.data() + rawX.size());
        poplar::copyDeviceHalfToFloat(target, rawX.data(), &hXout[0][0],
                                      DIM_SIZE * DIM_SIZE);
      } else {
        float *data = &hXout[0][0];
        auto floatBufSize = sizeof(float) * DIM_SIZE * DIM_SIZE;
        eng.readTensor(name + "Xout", data, data + floatBufSize);
      }

      // Is the result from the device within expected accuracy?
      for (auto i = 0U; i < DIM_SIZE; ++i) {
        for (auto j = 0U; j < DIM_SIZE; ++j) {
          auto expected = a * hX[i][j] + b * hY[i][j];
          auto computed = hXout[i][j];
          BOOST_TEST(expected == computed, name << ": [" << i << "][" << j
                                                << "] - expected:" << expected
                                                << ", computed:" << computed);
        }
      }
    }
  };

  // Some test variants have the scale values as constants. We just call
  // scaledAddTo with the parameters.
  struct VariantConst : Variant {
    VariantConst(std::string name, Type dataXType, float a, float b,
                 OptionFlags opts)
        : Variant(name + "Const", dataXType, a, b, opts) {}
    void callScaledAdd(Graph &graph, Sequence &prog) {
      scaledAddTo(graph, X, a, Y, b, prog, name, opts);
    }
  };

  // Some test variants have the scale values 'a', 'b' as tensors, so we create
  // 'A' and 'B' tensors for them.
  struct VariantTens : Variant {
    Type scaleType;
    VariantTens(std::string name, Type dataXType, Type scaleType, float a,
                float b, OptionFlags opts)
        : Variant(name + "Tensor", dataXType, a, b, opts),
          scaleType(scaleType) {}
    void callScaledAdd(Graph &graph, Sequence &prog) {
      auto A = graph.addVariable(scaleType, {});
      mapTensorLinearly(graph, A);
      graph.setInitialValue(A, a);
      auto B = graph.addVariable(scaleType, {});
      mapTensorLinearly(graph, B);
      graph.setInitialValue(B, b);
      scaledAddTo(graph, X, A, Y, B, prog, name, opts);
    }
  };

  // Input data values (as single) is the same for all variants
  float Xdata[DIM_SIZE][DIM_SIZE]; // a.k.a "A" data tensor
  float Ydata[DIM_SIZE][DIM_SIZE]; // a.k.a "B" data tensor
  setBinaryOpInputs(Ydata, Xdata);

  // ============ All the variants ===============

  // Value for 'bSmall' is chosen so that it doesn't have enough accuracy to fit
  // in a HALF float (with default 'ScaledAddOptions::floatToHalfTolerance').
  // When the 'half, float, Tensor' vertex is created, this will make sure the
  // real 'mixed' path is chosen at runtime.
  float a = 2, b = 3, bSmall = 0.0007;

  std::vector<Variant *> variants = {
      // clang-format off
    new VariantTens{"half half", HALF, HALF, a, bSmall,
                    {{"optimizeForSpeed", "true"}}},
    new VariantConst{"half half", HALF, -a, -b,
                     {{"optimizeForSpeed", "true"}}},
    new VariantTens{"float half", FLOAT, HALF, a, b,
                    {{"optimizeForSpeed", "true"}}},
    new VariantConst{"float half", FLOAT, -a, -b,
                     {{"optimizeForSpeed", "true"}}},

    // Test the "mixed" tensor vertex (data = HALF, scales = FLOAT) with a
    // "normal" and a "small" 'b', so that we verify that both paths are taken
    // at runtime.
    new VariantTens{"half float", HALF, FLOAT, a, b,
                    {{"optimizeForSpeed", "true"}}},
    new VariantTens{"half float bSmall", HALF, FLOAT, a, bSmall,
                    {{"optimizeForSpeed", "true"}}},
    // With a "small" 'b', the "mixed" tensor vertex (data = HALF,
    // scales = FLOAT) will be chosen directly by 'scaledAddTo'
    new VariantConst{"half float", HALF, a, bSmall,
                    {{"optimizeForSpeed", "true"}}},
      // clang-format on
  };

  // ------ Run all the variants contained in Variants[] ------
  auto prog = Sequence();
  for (auto test : variants)
    test->setup(graph, target, prog);
  Engine eng(graph, prog);
  device.bind([&](const Device &d) {
    eng.load(d);
    for (auto test : variants)
      test->write(target, eng, Xdata, Ydata);
    eng.run();
    for (auto test : variants)
      test->readAndVerify(target, eng);
  });
}

BOOST_AUTO_TEST_CASE(
    StdXMinusaXPlusbY_halfin_tensor_and_const,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(1)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(1))) {
  auto device = createTestDevice(TEST_TARGET);
  auto target = device.getTarget();
  Graph graph(target);
  popops::addCodelets(graph);

  float hInOut[DIM_SIZE][DIM_SIZE];
  float hIn[DIM_SIZE][DIM_SIZE];
  setBinaryOpInputs(hIn, hInOut);

  auto rawBufSize = target.getTypeSize(HALF) * DIM_SIZE * DIM_SIZE;
  std::vector<char> rawIn(rawBufSize);
  poplar::copyFloatToDeviceHalf(target, &hIn[0][0], rawIn.data(),
                                DIM_SIZE * DIM_SIZE);
  std::vector<char> rawInOut(rawBufSize);
  poplar::copyFloatToDeviceHalf(target, &hInOut[0][0], rawInOut.data(),
                                DIM_SIZE * DIM_SIZE);

  float k = 2, k2 = 3;
  auto A = graph.addVariable(HALF, {});
  graph.setInitialValue(A, k);
  auto B = graph.addVariable(HALF, {});
  graph.setInitialValue(B, k2);
  auto inOut = graph.addVariable(HALF, {DIM_SIZE, DIM_SIZE}, "inOut");
  auto inOutConstTest =
      graph.addVariable(HALF, {DIM_SIZE, DIM_SIZE}, "inOutConstTest");
  auto in = graph.addVariable(HALF, {DIM_SIZE, DIM_SIZE}, "in");
  mapTensorLinearly(graph, A);
  mapTensorLinearly(graph, B);
  mapTensorLinearly(graph, inOut);
  mapTensorLinearly(graph, inOutConstTest);
  mapTensorLinearly(graph, in);

  std::vector<char> rawOut(rawBufSize);
  std::vector<char> rawOutConstTest(rawBufSize);
  graph.createHostWrite("in", in);
  graph.createHostWrite("inOut", inOut);
  graph.createHostRead("out", inOut);
  graph.createHostRead("outConstTest", inOutConstTest);

  auto prog = Sequence();

  prog.add(Copy(inOut, inOutConstTest));
  scaledAddTo(graph, inOut, A, in, B, prog,
              popops::ScaledAddSpecialisation::X_MINUS_AX_PLUS_BY,
              "Debug - optimized", {{"optimizeForSpeed", "true"}});
  scaledAddTo(graph, inOutConstTest, -k, in, -k2, prog,
              popops::ScaledAddSpecialisation::X_MINUS_AX_PLUS_BY,
              "Debug - optimized", {{"optimizeForSpeed", "true"}});
  Engine eng(graph, prog);
  device.bind([&](const Device &d) {
    eng.load(d);

    eng.writeTensor("in", rawIn.data(), rawIn.data() + rawIn.size());
    eng.writeTensor("inOut", rawInOut.data(),
                    rawInOut.data() + rawInOut.size());
    eng.run();
    eng.readTensor("out", rawOut.data(), rawOut.data() + rawOut.size());
    eng.readTensor("outConstTest", rawOutConstTest.data(),
                   rawOutConstTest.data() + rawOutConstTest.size());
  });

  float hOut[DIM_SIZE][DIM_SIZE];
  poplar::copyDeviceHalfToFloat(target, rawOut.data(), &hOut[0][0],
                                DIM_SIZE * DIM_SIZE);
  float hOutConstTest[DIM_SIZE][DIM_SIZE];
  poplar::copyDeviceHalfToFloat(target, rawOutConstTest.data(),
                                &hOutConstTest[0][0], DIM_SIZE * DIM_SIZE);

  /* Check result */
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    for (auto j = 0U; j < DIM_SIZE; ++j) {
      double res = (1 - k) * hInOut[i][j] + k2 * hIn[i][j];
      BOOST_TEST(hOut[i][j] == res, "Tensor scale test");
      double resConst = (1 + k) * hInOut[i][j] - k2 * hIn[i][j];
      BOOST_TEST(hOutConstTest[i][j] == resConst, "Constant scale test");
    }
  }
}

BOOST_AUTO_TEST_CASE(
    StdaXPlusbY_float_tensor_and_const,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(1)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(1))) {
  auto device = createTestDevice(TEST_TARGET);
  auto target = device.getTarget();
  Graph graph(target);
  popops::addCodelets(graph);

  float hInOut[DIM_SIZE][DIM_SIZE];
  float hIn[DIM_SIZE][DIM_SIZE];
  setBinaryOpInputs(hIn, hInOut);

  float k = 2, k2 = 3;
  auto A = graph.addVariable(FLOAT, {});
  graph.setInitialValue(A, k);
  auto B = graph.addVariable(FLOAT, {});
  graph.setInitialValue(B, k2);

  auto inOut = graph.addVariable(FLOAT, {DIM_SIZE, DIM_SIZE}, "inOut");
  auto inOutConstTest =
      graph.addVariable(FLOAT, {DIM_SIZE, DIM_SIZE}, "inOutConstTest");
  auto in = graph.addVariable(FLOAT, {DIM_SIZE, DIM_SIZE}, "in");

  mapTensorLinearly(graph, A);
  mapTensorLinearly(graph, B);
  mapTensorLinearly(graph, inOut);
  mapTensorLinearly(graph, inOutConstTest);
  mapTensorLinearly(graph, in);

  graph.createHostWrite("in", in);
  graph.createHostWrite("inOut", inOut);
  graph.createHostRead("out", inOut);
  graph.createHostRead("outConstTest", inOutConstTest);

  auto prog = Sequence();
  prog.add(Copy(inOut, inOutConstTest));

  scaledAddTo(graph, inOut, A, in, B, prog, "Debug - optimized",
              {{"optimizeForSpeed", "true"}});
  scaledAddTo(graph, inOutConstTest, -k, in, -k2, prog, "Debug - optimized",
              {{"optimizeForSpeed", "true"}});

  Engine eng(graph, prog);

  float hResult[DIM_SIZE][DIM_SIZE];
  float hResultConst[DIM_SIZE][DIM_SIZE];

  device.bind([&](const Device &d) {
    eng.load(d);

    eng.writeTensor("in", hIn, &hIn[DIM_SIZE]);
    eng.writeTensor("inOut", hInOut, &hInOut[DIM_SIZE]);
    eng.run();
    eng.readTensor("out", hResult, &hResult[DIM_SIZE]);
    eng.readTensor("outConstTest", hResultConst, &hResultConst[DIM_SIZE]);
  });

  /* Check result */
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    for (auto j = 0U; j < DIM_SIZE; ++j) {
      double res = k * hInOut[i][j] + k2 * hIn[i][j];
      BOOST_TEST(hResult[i][j] == res, "Tensor scale test");
      BOOST_TEST(hResultConst[i][j] == -res, "Constant scale test");
    }
  }
}

BOOST_AUTO_TEST_CASE(StdCast) {
  auto device = createTestDevice(TEST_TARGET);
  Graph graph(device.getTarget());
  popops::addCodelets(graph);

  float hIn[DIM_SIZE];
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    hIn[i] = (float)i;
  }

  auto in = graph.addVariable(FLOAT, {DIM_SIZE}, "in");
  mapTensorLinearly(graph, in);
  graph.createHostWrite("in", in);

  auto prog = Sequence();

  poplar::Tensor out = cast(graph, in, INT, prog, "cast");
  graph.createHostRead("out", out);

  int hOut[DIM_SIZE];

  Engine eng(graph, prog);
  device.bind([&](const Device &d) {
    eng.load(d);
    eng.writeTensor("in", hIn, &hIn[DIM_SIZE]);
    eng.run();
    eng.readTensor("out", hOut, &hOut[DIM_SIZE]);
  });

  /* Check result */
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    BOOST_TEST(hOut[i] == i);
  }
}

BOOST_AUTO_TEST_CASE(
    StdaXMinusbY_float_tensor_and_const,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(1)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(1))) {
  auto device = createTestDevice(TEST_TARGET);
  auto target = device.getTarget();
  Graph graph(target);
  popops::addCodelets(graph);

  float hInOut[DIM_SIZE][DIM_SIZE];
  float hIn[DIM_SIZE][DIM_SIZE];
  setBinaryOpInputs(hIn, hInOut);

  float k = 2, k2 = 3;
  auto A = graph.addVariable(FLOAT, {});
  graph.setInitialValue(A, k);
  auto B = graph.addVariable(FLOAT, {});
  graph.setInitialValue(B, k2);

  auto inOut = graph.addVariable(FLOAT, {DIM_SIZE, DIM_SIZE}, "inOut");
  auto inOutConstTest =
      graph.addVariable(FLOAT, {DIM_SIZE, DIM_SIZE}, "inOutConstTest");
  auto in = graph.addVariable(FLOAT, {DIM_SIZE, DIM_SIZE}, "in");

  mapTensorLinearly(graph, A);
  mapTensorLinearly(graph, B);
  mapTensorLinearly(graph, inOut);
  mapTensorLinearly(graph, inOutConstTest);
  mapTensorLinearly(graph, in);

  graph.createHostWrite("in", in);
  graph.createHostWrite("inOut", inOut);
  graph.createHostRead("out", inOut);
  graph.createHostRead("outConstTest", inOutConstTest);

  auto prog = Sequence();
  prog.add(Copy(inOut, inOutConstTest));

  scaledSubtractFrom(graph, inOut, A, in, B, prog, "Debug - optimized",
                     {{"optimizeForSpeed", "true"}});
  scaledSubtractFrom(graph, inOutConstTest, k, in, k2, prog,
                     "Debug - optimized", {{"optimizeForSpeed", "true"}});

  Engine eng(graph, prog);

  float hResult[DIM_SIZE][DIM_SIZE];
  float hResultConst[DIM_SIZE][DIM_SIZE];

  device.bind([&](const Device &d) {
    eng.load(d);

    eng.writeTensor("in", hIn, &hIn[DIM_SIZE]);
    eng.writeTensor("inOut", hInOut, &hInOut[DIM_SIZE]);
    eng.run();
    eng.readTensor("out", hResult, &hResult[DIM_SIZE]);
    eng.readTensor("outConstTest", hResultConst, &hResultConst[DIM_SIZE]);
  });

  /* Check result */
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    for (auto j = 0U; j < DIM_SIZE; ++j) {
      double res = k * hInOut[i][j] - k2 * hIn[i][j];
      BOOST_TEST(hResult[i][j] == res, "Tensor scale test");
      BOOST_TEST(hResultConst[i][j] == res, "Constant scale test");
    }
  }
}

BOOST_AUTO_TEST_CASE(
    checkAccuracyFloatHalf,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(0.1)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(0.1))) {
  auto device = createTestDevice(TEST_TARGET);
  auto target = device.getTarget();
  Graph graph(target);
  popops::addCodelets(graph);
  // Values chosen because:
  // 1.0 - simple, exact, expect 1
  // 65500 - in range, expect 1
  // 80000 - Not in range of half, expect 0
  // 3e-7 - Uses denorms so expect 0, but on IPUModel we get 1 as it is not
  //        doing half precision correctly
  // (1.0f/32768.0f) - Precise as, although denorm it is a power of 2
  float hIn[DIM_SIZE] = {1.0,  65500,  80000,  3e-7,  (1.0f / 32768.0f),
                         -1.0, -65500, -80000, -3e-7, (-1.0f / 32768.0f)};
  double tolerance[DIM_SIZE] = {1e-6, 1e-4, 1e-6, 1e-6, 1e-6,
                                1e-6, 1e-4, 1e-6, 1e-6, 1e-6};
  auto input = graph.addVariable(FLOAT, {DIM_SIZE}, "in");
  graph.setTileMapping(input, 0);

  auto prog = Sequence();
  // Some casts can cause exceptions, if the float is unrepresentable as a half.
  // The codelet should disable exceptions.  Setting them on here means that
  // we are checking that it does so.
  FloatingPointBehaviour behaviour;
  setFloatingPointBehaviour(graph, prog, behaviour, "Set Exceptions");

  auto castResult =
      checkAccuracyWhenCast(graph, input[0], HALF, tolerance[0], prog);
  auto isAccurate = castResult.reshape({1});
  for (unsigned i = 1; i < DIM_SIZE; i++) {
    auto castResult =
        checkAccuracyWhenCast(graph, input[i], HALF, tolerance[i], prog);
    isAccurate = concat(isAccurate, castResult.reshape({1}));
  }
  graph.createHostWrite("input", input);
  graph.createHostRead("isAccurate", isAccurate);

  bool hIsAccurate[DIM_SIZE];
  Engine eng(graph, prog);
  device.bind([&](const Device &d) {
    eng.load(d);
    eng.writeTensor("input", hIn, &hIn[DIM_SIZE]);
    eng.run();
    eng.readTensor("isAccurate", hIsAccurate, &hIsAccurate[DIM_SIZE]);
  });

  bool expected[DIM_SIZE] = {1, 1, 0, 0, 1, 1, 1, 0, 0, 1};
  bool expectedIpuModel[DIM_SIZE] = {1, 1, 0, 1, 1, 1, 1, 0, 1, 1};
  /* Check result */
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    BOOST_TEST(hIsAccurate[i] ==
               (isIpuModel(TEST_TARGET) ? expectedIpuModel[i] : expected[i]));
  }
}

void checkVarianceConvertImpl(float hInVariance[DIM_SIZE],
                              float hInInvStdDev[DIM_SIZE], bool doCast,
                              bool force2D, bool useConstEpsilon) {
  auto device = createTestDevice(TEST_TARGET, 1, 4);
  auto target = device.getTarget();
  Graph graph(target);
  popops::addCodelets(graph);

  const float epsilon = 0.001f;

  auto epsilonF = graph.addVariable(FLOAT, {});
  graph.setInitialValue(epsilonF, epsilon);

  auto epsilonH = graph.addVariable(HALF, {});
  graph.setInitialValue(epsilonH, epsilon);

  auto varianceIn = graph.addVariable(FLOAT, {DIM_SIZE}, "varIn");
  auto invStdDevIn = graph.addVariable(HALF, {DIM_SIZE}, "isdIn");

  mapTensorLinearly(graph, varianceIn);
  mapTensorLinearly(graph, invStdDevIn);
  if (force2D) {
    graph.setTileMapping(varianceIn.slice({1, 4}), 1);
    graph.setTileMapping(invStdDevIn.slice({7, 8}), 1);
  }
  mapTensorLinearly(graph, epsilonF);
  mapTensorLinearly(graph, epsilonH);

  graph.createHostWrite("varianceIn", varianceIn);
  graph.createHostWrite("invStdDevIn", invStdDevIn);

  auto prog = Sequence();
  auto invStdDevOut = useConstEpsilon
                          ? varianceToInvStdDev(graph, varianceIn, epsilon,
                                                prog, doCast ? HALF : FLOAT)
                          : varianceToInvStdDev(graph, varianceIn, epsilonF,
                                                prog, doCast ? HALF : FLOAT);
  auto varianceOut = useConstEpsilon
                         ? invStdDevToVariance(graph, invStdDevIn, epsilon,
                                               prog, doCast ? FLOAT : HALF)
                         : invStdDevToVariance(graph, invStdDevIn, epsilonH,
                                               prog, doCast ? FLOAT : HALF);

  graph.createHostRead("invStdDevOut", invStdDevOut);
  graph.createHostRead("varianceOut", varianceOut);

  auto rawBufSize = target.getTypeSize(HALF) * DIM_SIZE;
  std::vector<char> rawIn(rawBufSize);
  std::vector<char> rawOut(rawBufSize);
  poplar::copyFloatToDeviceHalf(target, &hInInvStdDev[0], rawIn.data(),
                                DIM_SIZE);

  float hInvStdDevOut[DIM_SIZE];
  float hVarianceOut[DIM_SIZE];

  Engine eng(graph, prog);
  device.bind([&](const Device &d) {
    eng.load(d);

    eng.writeTensor("varianceIn", hInVariance, &hInVariance[DIM_SIZE]);
    eng.writeTensor("invStdDevIn", rawIn.data(), rawIn.data() + rawIn.size());
    eng.run();
    if (doCast) {
      eng.readTensor("invStdDevOut", rawOut.data(),
                     rawOut.data() + rawOut.size());
      eng.readTensor("varianceOut", hVarianceOut, &hVarianceOut[DIM_SIZE]);
    } else {
      eng.readTensor("varianceOut", rawOut.data(),
                     rawOut.data() + rawOut.size());
      eng.readTensor("invStdDevOut", hInvStdDevOut, &hInvStdDevOut[DIM_SIZE]);
    }
  });
  if (doCast) {
    poplar::copyDeviceHalfToFloat(target, rawOut.data(), &hInvStdDevOut[0],
                                  DIM_SIZE);
  } else {
    poplar::copyDeviceHalfToFloat(target, rawOut.data(), &hVarianceOut[0],
                                  DIM_SIZE);
  }

  /* Check result */
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    double resInvStdDev = 1 / std::sqrt(hInVariance[i] + epsilon);
    double resVariance = (1 / (hInInvStdDev[i] * hInInvStdDev[i])) - epsilon;
    BOOST_TEST(hInvStdDevOut[i] == resInvStdDev, "varianceToInvStdDev test");
    BOOST_TEST(hVarianceOut[i] == resVariance, "invStdDevToVariance test");
  }
}

BOOST_AUTO_TEST_CASE(
    checkVarianceConversionWithCast,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(1)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(1))) {

  float hInVariance[DIM_SIZE], hInInvStdDev[DIM_SIZE];
  for (unsigned i = 0; i < DIM_SIZE; i++) {
    hInVariance[i] = 500 * i;
    hInInvStdDev[i] = 0.001 * (i + 1);
  }
  checkVarianceConvertImpl(hInVariance, hInInvStdDev, true, false, false);
}

BOOST_AUTO_TEST_CASE(
    checkVarianceConversionWithCast2D,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(1)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(1))) {

  float hInVariance[DIM_SIZE], hInInvStdDev[DIM_SIZE];
  for (unsigned i = 0; i < DIM_SIZE; i++) {
    hInVariance[i] = 500 * i;
    hInInvStdDev[i] = 0.001 * (i + 1);
  }
  checkVarianceConvertImpl(hInVariance, hInInvStdDev, true, true, true);
}

BOOST_AUTO_TEST_CASE(
    checkVarianceConversionWithoutCast,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(1)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(1))) {
  auto device = createTestDevice(TEST_TARGET);
  auto target = device.getTarget();
  Graph graph(target);
  popops::addCodelets(graph);

  float hInVariance[DIM_SIZE], hInInvStdDev[DIM_SIZE];
  for (unsigned i = 0; i < DIM_SIZE; i++) {
    hInVariance[i] = 10 * i;
    hInInvStdDev[i] = 10 * (i + 1);
  }
  checkVarianceConvertImpl(hInVariance, hInInvStdDev, false, false, true);
}

BOOST_AUTO_TEST_CASE(
    StdaXMinusbY_halfin_tensor_and_const,
    *utf::tolerance<float>(fpc::percent_tolerance<float>(1)) *
        utf::tolerance<double>(fpc::percent_tolerance<double>(1))) {
  auto device = createTestDevice(TEST_TARGET);
  auto target = device.getTarget();
  Graph graph(target);
  popops::addCodelets(graph);

  float hInOut[DIM_SIZE][DIM_SIZE];
  float hIn[DIM_SIZE][DIM_SIZE];
  float hInOutFloat[DIM_SIZE][DIM_SIZE];
  setBinaryOpInputs(hIn, hInOut);
  std::memcpy(hInOutFloat, hInOut, sizeof(hInOutFloat));

  auto rawBufSize = target.getTypeSize(HALF) * DIM_SIZE * DIM_SIZE;
  std::vector<char> rawIn(rawBufSize);
  poplar::copyFloatToDeviceHalf(target, &hIn[0][0], rawIn.data(),
                                DIM_SIZE * DIM_SIZE);
  std::vector<char> rawInOut(rawBufSize);
  poplar::copyFloatToDeviceHalf(target, &hInOut[0][0], rawInOut.data(),
                                DIM_SIZE * DIM_SIZE);

  float k = 2, k2 = 3;
  auto A = graph.addVariable(HALF, {});
  graph.setInitialValue(A, k);
  auto B = graph.addVariable(HALF, {});
  graph.setInitialValue(B, k2);
  auto inOut = graph.addVariable(HALF, {DIM_SIZE, DIM_SIZE}, "inOut");
  auto inOutFloat =
      graph.addVariable(FLOAT, {DIM_SIZE, DIM_SIZE}, "inOutFloat");
  auto inOutConstTest =
      graph.addVariable(HALF, {DIM_SIZE, DIM_SIZE}, "inOutConstTest");
  auto inOutFloatConstTest =
      graph.addVariable(FLOAT, {DIM_SIZE, DIM_SIZE}, "inOutFloatConstTest");
  auto in = graph.addVariable(HALF, {DIM_SIZE, DIM_SIZE}, "in");
  mapTensorLinearly(graph, A);
  mapTensorLinearly(graph, B);
  mapTensorLinearly(graph, inOut);
  mapTensorLinearly(graph, inOutFloat);
  mapTensorLinearly(graph, inOutConstTest);
  mapTensorLinearly(graph, inOutFloatConstTest);
  mapTensorLinearly(graph, in);

  std::vector<char> rawOut(rawBufSize);
  std::vector<char> rawOutConstTest(rawBufSize);
  graph.createHostWrite("in", in);
  graph.createHostWrite("inOut", inOut);
  graph.createHostWrite("inOutFloat", inOutFloat);
  graph.createHostRead("out", inOut);
  graph.createHostRead("outFloat", inOutFloat);
  graph.createHostRead("outConstTest", inOutConstTest);
  graph.createHostRead("outFloatConstTest", inOutFloatConstTest);

  auto prog = Sequence();

  prog.add(Copy(inOut, inOutConstTest));
  prog.add(Copy(inOutFloat, inOutFloatConstTest));
  scaledSubtractFrom(graph, inOut, A, in, B, prog, "Debug - optimized",
                     {{"optimizeForSpeed", "true"}});
  scaledSubtractFrom(graph, inOutConstTest, -k, in, -k2, prog,
                     "Debug - optimized", {{"optimizeForSpeed", "true"}});
  scaledSubtractFrom(graph, inOutFloat, A, in, B, prog,
                     "Float out Debug - optimized",
                     {{"optimizeForSpeed", "true"}});
  scaledSubtractFrom(graph, inOutFloatConstTest, -k, in, -k2, prog,
                     "Float out Debug - optimized",
                     {{"optimizeForSpeed", "true"}});

  Engine eng(graph, prog);

  float hOutFloat[DIM_SIZE][DIM_SIZE];
  float hOutFloatConst[DIM_SIZE][DIM_SIZE];

  device.bind([&](const Device &d) {
    eng.load(d);

    eng.writeTensor("in", rawIn.data(), rawIn.data() + rawIn.size());
    eng.writeTensor("inOut", rawInOut.data(),
                    rawInOut.data() + rawInOut.size());
    eng.writeTensor("inOutFloat", hInOutFloat, &hInOutFloat[DIM_SIZE]);
    eng.run();
    eng.readTensor("out", rawOut.data(), rawOut.data() + rawOut.size());
    eng.readTensor("outConstTest", rawOutConstTest.data(),
                   rawOutConstTest.data() + rawOutConstTest.size());
    eng.readTensor("outFloat", hOutFloat, &hOutFloat[DIM_SIZE]);
    eng.readTensor("outFloatConstTest", hOutFloatConst,
                   &hOutFloatConst[DIM_SIZE]);
  });

  float hOut[DIM_SIZE][DIM_SIZE];
  poplar::copyDeviceHalfToFloat(target, rawOut.data(), &hOut[0][0],
                                DIM_SIZE * DIM_SIZE);
  float hOutConstTest[DIM_SIZE][DIM_SIZE];
  poplar::copyDeviceHalfToFloat(target, rawOutConstTest.data(),
                                &hOutConstTest[0][0], DIM_SIZE * DIM_SIZE);

  /* Check result */
  for (auto i = 0U; i < DIM_SIZE; ++i) {
    for (auto j = 0U; j < DIM_SIZE; ++j) {
      double res = k * hInOut[i][j] - k2 * hIn[i][j];
      double resFloat = k * hInOutFloat[i][j] - k2 * hIn[i][j];
      BOOST_TEST(hOut[i][j] == res, "Tensor scale test");
      BOOST_TEST(hOutConstTest[i][j] == -res, "Constant scale test");
      BOOST_TEST(hOutFloat[i][j] == resFloat, "Tensor scale float out test");
      BOOST_TEST(hOutFloatConst[i][j] == -res, "Constant float out scale test");
    }
  }
}


/**
 * @file
 * This file is part of SeisSol.
 *
 * @author Sebastian Wolf (wolf.sebastian AT tum.de, https://www5.in.tum.de/wiki/index.php/Sebastian_Wolf,_M.Sc.)
 * @section LICENSE
 * Copyright (c) 2019 - 2020, SeisSol Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 3. Neither the name of the copyright holder 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 HOLDER 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.
 *
 * @section DESCRIPTION
 **/

#ifndef MODEL_ANISOTROPIC_DATASTRUCTURES_H_
#define MODEL_ANISOTROPIC_DATASTRUCTURES_H_

#include <Model/common_datastructures.hpp>
#include <Equations/elastic/Model/datastructures.hpp>
#include <Eigen/Eigen>
#include <Eigen/Eigenvalues>
#include <generated_code/init.h>
#include <generated_code/tensor.h>
#include <generated_code/kernel.h>

namespace seissol {
  namespace model {
    struct AnisotropicMaterial : Material {
      double c11;
      double c12;
      double c13;
      double c14;
      double c15;
      double c16;
      double c22;
      double c23;
      double c24;
      double c25;
      double c26;
      double c33;
      double c34;
      double c35;
      double c36;
      double c44;
      double c45;
      double c46;
      double c55;
      double c56;
      double c66;

      AnisotropicMaterial() {}

      explicit AnisotropicMaterial(ElasticMaterial m) {
        rho = m.rho;
        c11 = m.lambda + 2*m.mu;
        c12 = m.lambda;
        c13 = m.lambda;
        c14 = 0;
        c15 = 0;
        c16 = 0;
        c22 = m.lambda + 2*m.mu;
        c23 = m.lambda;
        c24 = 0;
        c25 = 0;
        c26 = 0;
        c33 = m.lambda + 2*m.mu;
        c34 = 0;
        c35 = 0;
        c36 = 0;
        c44 = m.mu;
        c45 = 0;
        c46 = 0;
        c55 = m.mu;
        c56 = 0;
        c66 = m.mu;
      }

      AnisotropicMaterial( double* materialValues, int numMaterialValues)
      {
        assert(numMaterialValues == 22);

        this->rho = materialValues[0];
        this->c11 = materialValues[1];
        this->c12 = materialValues[2];
        this->c13 = materialValues[3];
        this->c14 = materialValues[4];
        this->c15 = materialValues[5];
        this->c16 = materialValues[6];
        this->c22 = materialValues[7];
        this->c23 = materialValues[8];
        this->c24 = materialValues[9];
        this->c25 = materialValues[10];
        this->c26 = materialValues[11];
        this->c33 = materialValues[12];
        this->c34 = materialValues[13];
        this->c35 = materialValues[14];
        this->c36 = materialValues[15];
        this->c44 = materialValues[16];
        this->c45 = materialValues[17];
        this->c46 = materialValues[18];
        this->c55 = materialValues[19];
        this->c56 = materialValues[20];
        this->c66 = materialValues[21];
      }

      virtual ~AnisotropicMaterial() {};

      void getFullStiffnessTensor(std::array<real, 81>& fullTensor) const final {
        auto stiffnessTensorView = init::stiffnessTensor::view::create(fullTensor.data());
        stiffnessTensorView.setZero();
        stiffnessTensorView(0,0,0,0) = c11;
        stiffnessTensorView(0,0,0,1) = c16;
        stiffnessTensorView(0,0,0,2) = c15;
        stiffnessTensorView(0,0,1,0) = c16;
        stiffnessTensorView(0,0,1,1) = c12;
        stiffnessTensorView(0,0,1,2) = c14;
        stiffnessTensorView(0,0,2,0) = c15;
        stiffnessTensorView(0,0,2,1) = c14;
        stiffnessTensorView(0,0,2,2) = c13;
        stiffnessTensorView(0,1,0,0) = c16;
        stiffnessTensorView(0,1,0,1) = c66;
        stiffnessTensorView(0,1,0,2) = c56;
        stiffnessTensorView(0,1,1,0) = c66;
        stiffnessTensorView(0,1,1,1) = c26;
        stiffnessTensorView(0,1,1,2) = c46;
        stiffnessTensorView(0,1,2,0) = c56;
        stiffnessTensorView(0,1,2,1) = c46;
        stiffnessTensorView(0,1,2,2) = c36;
        stiffnessTensorView(0,2,0,0) = c15;
        stiffnessTensorView(0,2,0,1) = c56;
        stiffnessTensorView(0,2,0,2) = c55;
        stiffnessTensorView(0,2,1,0) = c56;
        stiffnessTensorView(0,2,1,1) = c25;
        stiffnessTensorView(0,2,1,2) = c45;
        stiffnessTensorView(0,2,2,0) = c55;
        stiffnessTensorView(0,2,2,1) = c45;
        stiffnessTensorView(0,2,2,2) = c35;
        stiffnessTensorView(1,0,0,0) = c16;
        stiffnessTensorView(1,0,0,1) = c66;
        stiffnessTensorView(1,0,0,2) = c56;
        stiffnessTensorView(1,0,1,0) = c66;
        stiffnessTensorView(1,0,1,1) = c26;
        stiffnessTensorView(1,0,1,2) = c46;
        stiffnessTensorView(1,0,2,0) = c56;
        stiffnessTensorView(1,0,2,1) = c46;
        stiffnessTensorView(1,0,2,2) = c36;
        stiffnessTensorView(1,1,0,0) = c12;
        stiffnessTensorView(1,1,0,1) = c26;
        stiffnessTensorView(1,1,0,2) = c25;
        stiffnessTensorView(1,1,1,0) = c26;
        stiffnessTensorView(1,1,1,1) = c22;
        stiffnessTensorView(1,1,1,2) = c24;
        stiffnessTensorView(1,1,2,0) = c25;
        stiffnessTensorView(1,1,2,1) = c24;
        stiffnessTensorView(1,1,2,2) = c23;
        stiffnessTensorView(1,2,0,0) = c14;
        stiffnessTensorView(1,2,0,1) = c46;
        stiffnessTensorView(1,2,0,2) = c45;
        stiffnessTensorView(1,2,1,0) = c46;
        stiffnessTensorView(1,2,1,1) = c24;
        stiffnessTensorView(1,2,1,2) = c44;
        stiffnessTensorView(1,2,2,0) = c45;
        stiffnessTensorView(1,2,2,1) = c44;
        stiffnessTensorView(1,2,2,2) = c34;
        stiffnessTensorView(2,0,0,0) = c15;
        stiffnessTensorView(2,0,0,1) = c56;
        stiffnessTensorView(2,0,0,2) = c55;
        stiffnessTensorView(2,0,1,0) = c56;
        stiffnessTensorView(2,0,1,1) = c25;
        stiffnessTensorView(2,0,1,2) = c45;
        stiffnessTensorView(2,0,2,0) = c55;
        stiffnessTensorView(2,0,2,1) = c45;
        stiffnessTensorView(2,0,2,2) = c35;
        stiffnessTensorView(2,1,0,0) = c14;
        stiffnessTensorView(2,1,0,1) = c46;
        stiffnessTensorView(2,1,0,2) = c45;
        stiffnessTensorView(2,1,1,0) = c46;
        stiffnessTensorView(2,1,1,1) = c24;
        stiffnessTensorView(2,1,1,2) = c44;
        stiffnessTensorView(2,1,2,0) = c45;
        stiffnessTensorView(2,1,2,1) = c44;
        stiffnessTensorView(2,1,2,2) = c34;
        stiffnessTensorView(2,2,0,0) = c13;
        stiffnessTensorView(2,2,0,1) = c36;
        stiffnessTensorView(2,2,0,2) = c35;
        stiffnessTensorView(2,2,1,0) = c36;
        stiffnessTensorView(2,2,1,1) = c23;
        stiffnessTensorView(2,2,1,2) = c34;
        stiffnessTensorView(2,2,2,0) = c35;
        stiffnessTensorView(2,2,2,1) = c34;
        stiffnessTensorView(2,2,2,2) = c33;
      }

      //calculate maximal wave speed
      //Wavespeeds for anisotropic materials depend on the direction of propagation.
      //An analytic solution for the maximal wave speed is hard to obtain.
      //Instead of solving an optimization problem we sample the velocitiy for
      //different directions and take the maximum.
      double getMaxWaveSpeed() const final{
        auto samplingDirections = init::samplingDirections::view::create(const_cast<real*>(init::samplingDirections::Values));

        Eigen::SelfAdjointEigenSolver<Eigen::Matrix<double, 3, 3>> saes;

        double maxEv = 0;

        std::array<real, 81> fullTensor;
        getFullStiffnessTensor(fullTensor);
        kernel::computeChristoffel computeChristoffel;
        computeChristoffel.stiffnessTensor = fullTensor.data();

        for(unsigned j = 0; j < 200; ++j)
        {
          real n[3] = { samplingDirections(j, 0),
                        samplingDirections(j, 1),
                        samplingDirections(j, 2)
          };
          real M[9];
          computeChristoffel.direction = n;
          computeChristoffel.christoffel = M;
          computeChristoffel.execute();

          saes.compute(Eigen::Matrix<real, 3, 3>(M).cast<double>());
          auto eigenvalues = saes.eigenvalues();
          for(unsigned i = 0; i < 3; ++i) {
            maxEv = std::max(eigenvalues(i), maxEv);
          }
        }
        return sqrt(maxEv / rho);
      }

      //calculate P-wave speed based on averaged material parameters
      double getPWaveSpeed() const final {
        double muBar = (c44 + c55 + c66) / 3.0;
        double lambdaBar = (c11 + c22 + c33) / 3.0 - 2.0*muBar;
        return std::sqrt((lambdaBar + 2*muBar) / rho);
      }

      //calculate S-wave speed based on averaged material parameters
      double getSWaveSpeed() const final {
        double muBar = (c44 + c55 + c66) / 3.0;
        return std::sqrt(muBar / rho);
      }

      MaterialType getMaterialType() const {
        return MaterialType::anisotropic;
      }
    };

  }
}

#endif


// Copyright 2015, Sensics, Inc.
// Copyright 2021, Collabora, Ltd.
// SPDX-License-Identifier: Apache-2.0
//
// Code for "motion log" test app.
// Original author of the motion_onset code this was based on: Russell Taylor working for Sensics.com through ReliaSolve.com
// Ported to PlatformIO and multi-board, multi-app framework by Ryan Pavlik for Collabora.
#ifdef APP_LOG
// Must come before Arduino because of abs
#include <Eigen/Core>
#include "apps.h"

using Eigen::Vector3f;

#include <Arduino.h>
#include "defines.h"
#include "gyroProc.h"

#include "motionShared.h"

GyroProc gyroProc{};

//*****************************************************
void logSetup()
//*****************************************************
{
    Serial.println("latency_hardware_firmware motion log test v01.00.00");
    Serial.println(" Mount the photosensor rigidly on the eyepiece or screen.");
    Serial.println(" Rotate the inertial sensor along with the tracking hardware.");
    Serial.println(" Make the app vary brightness darker in one direction");
    Serial.println(" and lighter in the other.");
    Serial.println(" Hold the device still for 2 seconds.");

    delay(100);
    Serial.println("us,drx,dry,drz,brightness");
}

//*****************************************************
void logLoop(Board &board)
//*****************************************************
{
    startupImu(board, gyroProc);

    // Read the values from the inertial sensors and photosensor.
    // Record the time we read these values.
    auto data = doRead(board, gyroProc);
    if (!data.dataGood)
    {
        return;
    }
    int brightness = readBrightness();
    unsigned long now = data.timestamp;
    Serial.print(now);
    Serial.print(",");
    Serial.print(data.gyro.x());
    Serial.print(",");
    Serial.print(data.gyro.y());
    Serial.print(",");
    Serial.print(data.gyro.z());
    Serial.print(",");
    Serial.println(brightness);
}

#endif


#include "../xoshiro.hpp"
#include "../dice_distribution.hpp"

#include <iostream>
#include <random>
#include <boost/random/uniform_smallint.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <benchmark/benchmark.h>

using namespace xoshiro;

template <class T>
static void BM_PRNG(benchmark::State& state) {
    T rng(42);
    for (auto _ : state) {
        benchmark::DoNotOptimize(rng());
    }
}

BENCHMARK_TEMPLATE(BM_PRNG, std::mt19937);
BENCHMARK_TEMPLATE(BM_PRNG, std::mt19937_64);

BENCHMARK_TEMPLATE(BM_PRNG, xoshiro256pp);
BENCHMARK_TEMPLATE(BM_PRNG, xoshiro256ss);
BENCHMARK_TEMPLATE(BM_PRNG, xoshiro256p);
BENCHMARK_TEMPLATE(BM_PRNG, xoroshiro128pp);
BENCHMARK_TEMPLATE(BM_PRNG, xoroshiro128ss);
BENCHMARK_TEMPLATE(BM_PRNG, xoroshiro128p);

BENCHMARK_TEMPLATE(BM_PRNG, xoshiro128pp);
BENCHMARK_TEMPLATE(BM_PRNG, xoshiro128ss);
BENCHMARK_TEMPLATE(BM_PRNG, xoshiro128p);
BENCHMARK_TEMPLATE(BM_PRNG, xoroshiro64ss);
BENCHMARK_TEMPLATE(BM_PRNG, xoroshiro64s);

template <class TRng, class TDist>
static void BM_Roll(benchmark::State& state) {
    TRng rng(42);
    TDist dist(3, 6, 1);
    TDist dist1("3d6 + 1");
    TDist dist2("1d100");
    TDist dist3("1d4-1");
    TDist dist4("100d2");
    for (auto _ : state) {
        benchmark::DoNotOptimize(dist(rng));
        benchmark::DoNotOptimize(dist1(rng));
        benchmark::DoNotOptimize(dist2(rng));
        benchmark::DoNotOptimize(dist3(rng));
        benchmark::DoNotOptimize(dist4(rng));
    }
}

BENCHMARK_TEMPLATE(BM_Roll, std::mt19937, dice_distribution<>);
BENCHMARK_TEMPLATE(BM_Roll, xoshiro256pp, dice_distribution<>);
BENCHMARK_TEMPLATE(BM_Roll, xoshiro128pp, dice_distribution<>);
BENCHMARK_TEMPLATE(BM_Roll, xoroshiro64ss, dice_distribution<>);
BENCHMARK_TEMPLATE(BM_Roll, xoshiro256pp, dice_distribution<int, boost::random::uniform_int_distribution<int>>);
BENCHMARK_TEMPLATE(BM_Roll, xoshiro256pp, dice_distribution<int, boost::random::uniform_smallint<int>>);
BENCHMARK_TEMPLATE(BM_Roll, xoshiro128pp, dice_distribution<int, boost::random::uniform_smallint<int>>);
BENCHMARK_TEMPLATE(BM_Roll, xoroshiro64ss, dice_distribution<int, boost::random::uniform_smallint<int>>);

BENCHMARK_MAIN();


/*!
 * @file
 * Contains unit tests for the @ref IntegralDomain typeclass.
 */

#include <boost/mpl11/integral_domain.hpp>

#include <boost/mpl11/core.hpp>

#include "test_method_dispatch.hpp"

using namespace boost::mpl11;

///////////////////////////
// Test method dispatching
///////////////////////////
namespace boost { namespace mpl11 {
    template <>
    struct IntegralDomain<Archetype<0>>
        : instantiate<IntegralDomain>::with<Archetype<0>>
    {
        template <typename, typename> using div_impl = method_tag<div>;
        template <typename, typename> using mod_impl = method_tag<mod>;
    };
}}

struct tests
    : method<div, archetype<0>, archetype<0>>
    // , method<div, archetype<0>, archetype<1>>
    // , method<div, archetype<1>, archetype<0>>

    , method<mod, archetype<0>, archetype<0>>
    // , method<mod, archetype<0>, archetype<1>>
    // , method<mod, archetype<1>, archetype<0>>
{ };

int main() { }


#include <stan/math/rev.hpp>
#include <boost/mp11.hpp>
#include <gtest/gtest.h>
#include <test/unit/math/rev/functor/test_fixture_ode.hpp>
#include <test/unit/math/rev/functor/test_fixture_ode_fho.hpp>
#include <test/unit/math/rev/functor/ode_test_functors.hpp>

/**
 *
 * Use same solver functor type for both w & w/o tolerance control
 */
template <typename solve_type, typename... Ts>
using ode_test_tuple = std::tuple<solve_type, solve_type, Ts...>;

/**
 * Outer product of test types
 */
using forced_harm_osc_ts_test_types = boost::mp11::mp_product<
    ode_test_tuple,
    ::testing::Types<ode_adams_functor, ode_bdf_functor, ode_ckrk_functor,
                     ode_rk45_functor, ode_adjoint_functor>,
    ::testing::Types<stan::math::var_value<double> >,          // time
    ::testing::Types<double, stan::math::var_value<double> >,  // y0
    ::testing::Types<double, stan::math::var_value<double> >   // theta
    >;

TYPED_TEST_SUITE_P(forced_harm_osc_ts_test);
TYPED_TEST_P(forced_harm_osc_ts_test, ts_ad) { this->test_ts_ad(); }
REGISTER_TYPED_TEST_SUITE_P(forced_harm_osc_ts_test, ts_ad);
INSTANTIATE_TYPED_TEST_SUITE_P(StanOde, forced_harm_osc_ts_test,
                               forced_harm_osc_ts_test_types);


//
// Copyright (c) 2015-2019 CNRS INRIA
//

#include <iostream>

#include "pinocchio/multibody/model.hpp"
#include "pinocchio/multibody/data.hpp"

#include "pinocchio/multibody/geometry.hpp"
#include "pinocchio/algorithm/kinematics.hpp"
#include "pinocchio/algorithm/geometry.hpp"
#include "pinocchio/parsers/urdf.hpp"
#include "pinocchio/parsers/srdf.hpp"

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

using namespace pinocchio;

typedef std::map <std::string, pinocchio::SE3> PositionsMap_t;
typedef std::map <std::string, pinocchio::SE3> JointPositionsMap_t;
typedef std::map <std::string, pinocchio::SE3> GeometryPositionsMap_t;
typedef std::map <std::pair < std::string , std::string >, fcl::DistanceResult > PairDistanceMap_t;
JointPositionsMap_t fillPinocchioJointPositions(const pinocchio::Model& model, const pinocchio::Data & data);
GeometryPositionsMap_t fillPinocchioGeometryPositions(const pinocchio::GeometryModel & geomModel,
                                                      const pinocchio::GeometryData & geomData);

std::vector<std::string> getBodiesList();

BOOST_AUTO_TEST_SUITE ( BOOST_TEST_MODULE )

BOOST_AUTO_TEST_CASE ( simple_boxes )
{
  using namespace pinocchio;
  Model model;
  GeometryModel geomModel;

  Model::JointIndex idx;
  idx = model.addJoint(model.getJointId("universe"),JointModelPlanar(),SE3::Identity(),"planar1_joint");
  model.addJointFrame(idx);
  model.appendBodyToJoint(idx,Inertia::Random(),SE3::Identity());
  model.addBodyFrame("planar1_body", idx, SE3::Identity());

  idx = model.addJoint(model.getJointId("universe"),JointModelPlanar(),SE3::Identity(),"planar2_joint");
  model.addJointFrame(idx);
  model.appendBodyToJoint(idx,Inertia::Random(),SE3::Identity());
  model.addBodyFrame("planar2_body", idx, SE3::Identity());

  boost::shared_ptr<fcl::Box> sample(new fcl::Box(1, 1, 1));
  Model::FrameIndex body_id_1 = model.getBodyId("planar1_body");
  Model::JointIndex joint_parent_1 = model.frames[body_id_1].parent;
  Model::JointIndex idx_geom1 = geomModel.addGeometryObject(GeometryObject("ff1_collision_object",
                                                                           model.getBodyId("planar1_body"),joint_parent_1,
                                                                           sample,SE3::Identity(), "", Eigen::Vector3d::Ones())
                                                            );
  geomModel.geometryObjects[idx_geom1].parentJoint = model.frames[body_id_1].parent;

  boost::shared_ptr<fcl::Box> sample2(new fcl::Box(1, 1, 1));
  Model::FrameIndex body_id_2 = model.getBodyId("planar2_body");
  Model::JointIndex joint_parent_2 = model.frames[body_id_2].parent;
  Model::JointIndex idx_geom2 = geomModel.addGeometryObject(GeometryObject("ff2_collision_object",
                                                                           model.getBodyId("planar2_body"),joint_parent_2,
                                                                           sample2,SE3::Identity(), "", Eigen::Vector3d::Ones()),
                                                            model);
  BOOST_CHECK(geomModel.geometryObjects[idx_geom2].parentJoint == model.frames[body_id_2].parent);

  boost::shared_ptr<fcl::Box> universe_body_geometry(new fcl::Box(1, 1, 1));
  model.addBodyFrame("universe_body", 0, SE3::Identity());
  Model::FrameIndex body_id_3 = model.getBodyId("universe_body");
  Model::JointIndex joint_parent_3 = model.frames[body_id_3].parent;
  SE3 universe_body_placement = SE3::Random();
  Model::JointIndex idx_geom3 = geomModel.addGeometryObject(GeometryObject("universe_collision_object",
                                                                           model.getBodyId("universe_body"),joint_parent_3,
                                                                           universe_body_geometry,universe_body_placement, "", Eigen::Vector3d::Ones()),
                                                            model);

  BOOST_CHECK(geomModel.geometryObjects[idx_geom3].parentJoint == model.frames[body_id_3].parent);

  geomModel.addAllCollisionPairs();
  pinocchio::Data data(model);
  pinocchio::GeometryData geomData(geomModel);

  BOOST_CHECK(CollisionPair(0,1) == geomModel.collisionPairs[0]);

  std::cout << "------ Model ------ " << std::endl;
  std::cout << model;
  std::cout << "------ Geom ------ " << std::endl;
  std::cout << geomModel;
  std::cout << "------ DataGeom ------ " << std::endl;
  std::cout << geomData;

  Eigen::VectorXd q(model.nq);
  q <<  0, 0, 1, 0,
        0, 0, 1, 0 ;

  pinocchio::updateGeometryPlacements(model, data, geomModel, geomData, q);
  BOOST_CHECK(geomData.oMg[idx_geom3].isApprox(universe_body_placement));
  BOOST_CHECK(computeCollision(geomModel,geomData,0) == true);

  q <<  2, 0, 1, 0,
        0, 0, 1, 0 ;

  pinocchio::updateGeometryPlacements(model, data, geomModel, geomData, q);
  BOOST_CHECK(computeCollision(geomModel,geomData,0) == false);

  q <<  0.99, 0, 1, 0,
        0, 0, 1, 0 ;

  pinocchio::updateGeometryPlacements(model, data, geomModel, geomData, q);
  BOOST_CHECK(computeCollision(geomModel,geomData,0) == true);

  q <<  1.01, 0, 1, 0,
        0, 0, 1, 0 ;

  pinocchio::updateGeometryPlacements(model, data, geomModel, geomData, q);
  BOOST_CHECK(computeCollision(geomModel,geomData,0) == false);
}

BOOST_AUTO_TEST_CASE ( loading_model )
{
  typedef pinocchio::Model Model;
  typedef pinocchio::GeometryModel GeometryModel;
  typedef pinocchio::Data Data;
  typedef pinocchio::GeometryData GeometryData;

  std::string filename = PINOCCHIO_SOURCE_DIR"/models/romeo/romeo_description/urdf/romeo_small.urdf";
  std::vector < std::string > packageDirs;
  std::string meshDir  = PINOCCHIO_SOURCE_DIR"/models/romeo/";
  packageDirs.push_back(meshDir);

  Model model;
  pinocchio::urdf::buildModel(filename, pinocchio::JointModelFreeFlyer(),model);
  GeometryModel geomModel;
  pinocchio::urdf::buildGeom(model, filename, pinocchio::COLLISION, geomModel, packageDirs );
  geomModel.addAllCollisionPairs();

  Data data(model);
  GeometryData geomData(geomModel);
  fcl::CollisionResult result;

  Eigen::VectorXd q(model.nq);
  q << 0, 0, 0.840252, 0, 0, 0, 1, 0, 0, -0.3490658, 0.6981317, -0.3490658, 0, 0, 0, -0.3490658,
       0.6981317, -0.3490658, 0, 0, 1.5, 0.6, -0.5, -1.05, -0.4, -0.3, -0.2, 0, 0, 0, 0,
       1.5, -0.6, 0.5, 1.05, -0.4, -0.3, -0.2 ;

  pinocchio::updateGeometryPlacements(model, data, geomModel, geomData, q);
  pinocchio::Index idx = geomModel.findCollisionPair(CollisionPair(1,10));
  BOOST_CHECK(computeCollision(geomModel,geomData,idx) == false);

  fcl::DistanceResult distance_res = computeDistance(geomModel,geomData,idx);
  BOOST_CHECK(distance_res.min_distance > 0.);
}

BOOST_AUTO_TEST_CASE ( test_collisions )
{
  typedef pinocchio::Model Model;
  typedef pinocchio::GeometryModel GeometryModel;
  typedef pinocchio::Data Data;
  typedef pinocchio::GeometryData GeometryData;

  const std::string filename = PINOCCHIO_SOURCE_DIR"/models/romeo/romeo_description/urdf/romeo_small.urdf";
  std::vector < std::string > packageDirs;
  const std::string meshDir  = PINOCCHIO_SOURCE_DIR"/models/romeo/";
  packageDirs.push_back(meshDir);
  const std::string srdf_filename = PINOCCHIO_SOURCE_DIR"/models/romeo/romeo_description/srdf/romeo.srdf";

  Model model;
  pinocchio::urdf::buildModel(filename, pinocchio::JointModelFreeFlyer(),model);
  GeometryModel geom_model;
  pinocchio::urdf::buildGeom(model, filename, pinocchio::COLLISION, geom_model, packageDirs);
  geom_model.addAllCollisionPairs();
  pinocchio::srdf::removeCollisionPairs(model,geom_model,srdf_filename,false);

  Data data(model);
  GeometryData geom_data(geom_model);
  fcl::CollisionResult result;

  pinocchio::srdf::loadReferenceConfigurations(model,srdf_filename,false);
  Eigen::VectorXd q = model.referenceConfigurations["half_sitting"];

  pinocchio::updateGeometryPlacements(model, data, geom_model, geom_data, q);

  BOOST_CHECK(computeCollisions(geom_model,geom_data) == false);

  {
    Data data(model);
    GeometryData geom_data(geom_model);
    BOOST_CHECK(computeCollisions(model,data,geom_model,geom_data,q) == false);
  }
}

BOOST_AUTO_TEST_CASE ( test_distances )
{
  typedef pinocchio::Model Model;
  typedef pinocchio::GeometryModel GeometryModel;
  typedef pinocchio::Data Data;
  typedef pinocchio::GeometryData GeometryData;

  const std::string filename = PINOCCHIO_SOURCE_DIR"/models/romeo/romeo_description/urdf/romeo_small.urdf";
  std::vector < std::string > packageDirs;
  const std::string meshDir  = PINOCCHIO_SOURCE_DIR"/models/romeo/";
  packageDirs.push_back(meshDir);
  const std::string srdf_filename = PINOCCHIO_SOURCE_DIR"/models/romeo/romeo_description/srdf/romeo.srdf";

  Model model;
  pinocchio::urdf::buildModel(filename, pinocchio::JointModelFreeFlyer(),model);
  GeometryModel geom_model;
  pinocchio::urdf::buildGeom(model, filename, pinocchio::COLLISION, geom_model, packageDirs);
  geom_model.addAllCollisionPairs();
  pinocchio::srdf::removeCollisionPairs(model,geom_model,srdf_filename,false);

  Data data(model);
  GeometryData geom_data(geom_model);
  fcl::CollisionResult result;

  pinocchio::srdf::loadReferenceConfigurations(model,srdf_filename,false);
  Eigen::VectorXd q = model.referenceConfigurations["half_sitting"];

  pinocchio::updateGeometryPlacements(model, data, geom_model, geom_data, q);

  BOOST_CHECK(computeDistances(geom_model,geom_data) < geom_model.collisionPairs.size());

  {
    Data data(model);
    GeometryData geom_data(geom_model);
    BOOST_CHECK(computeDistances(model,data,geom_model,geom_data,q) < geom_model.collisionPairs.size());
  }
}

BOOST_AUTO_TEST_CASE ( test_append_geom_models )
{
  typedef pinocchio::Model Model;
  typedef pinocchio::GeometryModel GeometryModel;

  const std::string filename = PINOCCHIO_SOURCE_DIR"/models/romeo/romeo_description/urdf/romeo_small.urdf";
  std::vector < std::string > packageDirs;
  const std::string meshDir  = PINOCCHIO_SOURCE_DIR"/models/romeo/";
  packageDirs.push_back(meshDir);

  Model model;
  pinocchio::urdf::buildModel(filename, pinocchio::JointModelFreeFlyer(),model);
  GeometryModel geom_model1;
  pinocchio::urdf::buildGeom(model, filename, pinocchio::COLLISION, geom_model1, packageDirs);

  GeometryModel geom_model2;
  pinocchio::urdf::buildGeom(model, filename, pinocchio::COLLISION, geom_model2, packageDirs);

  appendGeometryModel(geom_model2,geom_model1);
  BOOST_CHECK(geom_model2.ngeoms == 2*geom_model1.ngeoms);

  {
    GeometryModel geom_model_empty;
    GeometryModel geom_model;
    BOOST_CHECK(geom_model_empty.ngeoms == 0);
    appendGeometryModel(geom_model,geom_model_empty);
    BOOST_CHECK(geom_model.ngeoms== 0);
  }
}

#if defined(PINOCCHIO_WITH_URDFDOM) && defined(PINOCCHIO_WITH_HPP_FCL)
BOOST_AUTO_TEST_CASE (radius)
{
  std::vector < std::string > packageDirs;

  std::string filename = PINOCCHIO_SOURCE_DIR"/models/romeo/romeo_description/urdf/romeo_small.urdf";
  std::string meshDir  = PINOCCHIO_SOURCE_DIR"/models/romeo/";
  packageDirs.push_back(meshDir);

  pinocchio::Model model;
  pinocchio::urdf::buildModel(filename, pinocchio::JointModelFreeFlyer(),model);
  pinocchio::GeometryModel geom;
  pinocchio::urdf::buildGeom(model, filename, pinocchio::COLLISION, geom, packageDirs);
  Data data(model);
  GeometryData geomData(geom);

  // Test that the algorithm does not crash
  pinocchio::computeBodyRadius(model, geom, geomData);
  BOOST_FOREACH( double radius, geomData.radius) BOOST_CHECK(radius>=0.);
}
#endif // if defined(PINOCCHIO_WITH_URDFDOM) && defined(PINOCCHIO_WITH_HPP_FCL)

BOOST_AUTO_TEST_SUITE_END ()


#pragma once
#include "json_types.hpp"
#include "frea/src/angle.hpp"
#include <boost/variant.hpp>

namespace rev {
	class Camera3D;
	using HCam3 = std::shared_ptr<Camera3D>;
}
namespace rev::gltf {
	struct Camera {
		struct Perspective {
			float		aspectRatio;
			frea::RadF	yfov;
			float		zfar,
						znear;
			Perspective(const JValue& v);
		};
		struct Orthographic {
			float	xmag,
					ymag,
					zfar,
					znear;
			Orthographic(const JValue& v);
		};
		using Value = boost::variant<Perspective, Orthographic>;
		Value	value;

		Camera(const JValue& v);
		Value _init(const JValue& v);
		// デフォルト姿勢のカメラを作成
		// (Perspectiveのみの対応)
		HCam3 makeCamera() const;
	};
}