// MIT License
//
// Copyright (c) 2018 Lennart Braun
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#include <algorithm>
#include <array>
#include <cassert>
#include <cstdint>
#include <boost/asio.hpp>
#include <botan/blake2b.h>
#include <botan/hex.h>
#include "ot_hl17.hpp"
#include "util/threading.hpp"

OT_HL17::OT_HL17(Connection& connection) : connection_(connection)
{
}

// Notation
// * Group GG
// * of prime order p
// * with generator g
//
// * random oracle G: GG -> GG
// * random oracle H: GG^3 -> K

void OT_HL17::hash_point(curve25519::ge_p3& output, const curve25519::ge_p3& input)
{
    std::array<uint8_t, 32> hash_input;
    std::array<uint8_t, 64> hash_output{};
    auto hash(Botan::Blake2b(256));

    curve25519::ge_p3_tobytes(hash_input.data(), &input);
    hash.update(hash_input.data(), hash_input.size());
    hash.final(hash_output.data());
    curve25519::x25519_sc_reduce(hash_output.data());

    curve25519::x25519_ge_scalarmult_base(&output, hash_output.data());
}

void OT_HL17::send_0(Sender_State& state,
                     std::array<uint8_t, curve25519_ge_byte_size>& message_out)
{
    // sample y <- Zp
    curve25519::sc_random(state.y);

    // S = g^y
    curve25519::x25519_ge_scalarmult_base(&state.S, state.y);

    curve25519::ge_p3_tobytes(message_out.data(), &state.S);
}

void OT_HL17::send_1(Sender_State& state)
{
    // T = G(S)
    hash_point(state.T, state.S);
}

std::pair<bytes_t, bytes_t> OT_HL17::send_2(Sender_State& state,
                                            const std::array<uint8_t, curve25519_ge_byte_size>& message_in)
{
    // // assert R in GG
    if (!x25519_ge_frombytes_vartime(&state.R, message_in.data()))
        std::terminate();

    auto hash(Botan::Blake2b(128));

    auto output = std::make_pair<>(bytes_t(16), bytes_t(16));
    assert(output.first.size() == hash.output_length());
    assert(output.second.size() == hash.output_length());

    std::array<uint8_t, 3*curve25519_ge_byte_size> hash_input;
    curve25519::ge_p3_tobytes(hash_input.data(), &state.S);
    curve25519::ge_p3_tobytes(hash_input.data() + 32, &state.R);

    // j = 0:
    // y*R
    curve25519::ge_p2 y_times_R_p2;
    curve25519::x25519_ge_scalarmult(&y_times_R_p2, state.y, &state.R);
    curve25519::x25519_ge_tobytes(hash_input.data() + 64, &y_times_R_p2);

    // H(S, R, y*R)
    hash.update(hash_input.data(), hash_input.size());
    hash.final(reinterpret_cast<uint8_t*>(output.first.data()));

    // j = 1:
    // y*R + (-y)*T = y*(R - T)
    {
        curve25519::ge_cached T_cached;
        curve25519::x25519_ge_p3_to_cached(&T_cached, &state.T);

        curve25519::ge_p1p1 R_minus_T_p1p1;
        curve25519::x25519_ge_sub(&R_minus_T_p1p1, &state.R, &T_cached);

        curve25519::ge_p3 R_minus_T_p3;
        curve25519::x25519_ge_p1p1_to_p3(&R_minus_T_p3, &R_minus_T_p1p1);

        curve25519::ge_p2 y_times_R_minus_T_p2;
        curve25519::x25519_ge_scalarmult(&y_times_R_minus_T_p2, state.y, &R_minus_T_p3);
        curve25519::x25519_ge_tobytes(hash_input.data() + 64, &y_times_R_minus_T_p2);

    }

    // H(S, R, y*R - y*T)
    hash.update(hash_input.data(), hash_input.size());
    hash.final(reinterpret_cast<uint8_t*>(output.second.data()));

    return output;
}

void OT_HL17::recv_0(Receiver_State& state, bool choice)
{
    state.choice = choice;
    // sample x <- Zp
    curve25519::sc_random(state.x);
}

void OT_HL17::recv_1(Receiver_State& state,
                     std::array<uint8_t, curve25519_ge_byte_size>& message_out,
                     const std::array<uint8_t, curve25519_ge_byte_size>& message_in)
{
    // recv S
    auto res = curve25519::x25519_ge_frombytes_vartime(&state.S, message_in.data());
    // assert S in GG
    if (res == 0)
        std::terminate();

    // T = G(S)
    hash_point(state.T, state.S);

    // R = T^c * g^x

    // R = g^x
    curve25519::x25519_ge_scalarmult_base(&state.R, state.x);

    // FIXME: not constant time
    if (state.choice == 1)
    {
        curve25519::ge_p1p1 R_p1p1;
        curve25519::ge_cached T_cached;
        curve25519::x25519_ge_p3_to_cached(&T_cached, &state.T);
        curve25519::x25519_ge_add(&R_p1p1, &state.R, &T_cached);
        curve25519::x25519_ge_p1p1_to_p3(&state.R, &R_p1p1);
    }

    bytes_t R_bytes(32);
    curve25519::ge_p3_tobytes(message_out.data(), &state.R);
}

bytes_t OT_HL17::recv_2(Receiver_State& state)
{
    // k_R = H_(S,R)(S^x)
    //     = H_(S,R)(g^xy)

    bytes_t hash_output(16);

    std::array<uint8_t, 3*32> hash_input;
    curve25519::ge_p3_tobytes(hash_input.data(), &state.S);
    curve25519::ge_p3_tobytes(hash_input.data() + 32, &state.R);

    curve25519::ge_p2 S_to_the_x;
    curve25519::x25519_ge_scalarmult(&S_to_the_x, state.x, &state.S);
    curve25519::x25519_ge_tobytes(hash_input.data() + 64, &S_to_the_x);

    auto hash(Botan::Blake2b(128));
    assert(hash_output.size() == hash.output_length());
    hash.update(hash_input.data(), hash_input.size());
    hash.final(reinterpret_cast<uint8_t*>(hash_output.data()));

    return hash_output;
}

std::pair<bytes_t, bytes_t> OT_HL17::send()
{
    Sender_State state;
    std::array<uint8_t, curve25519_ge_byte_size> msg_s0;
    std::array<uint8_t, curve25519_ge_byte_size> msg_r1;

    send_0(state, msg_s0);
    connection_.send(msg_s0.data(), msg_s0.size());
    send_1(state);
    connection_.recv(msg_r1.data(), msg_r1.size());
    return send_2(state, msg_r1);
}

bytes_t OT_HL17::recv(bool choice)
{
    Receiver_State state;
    std::array<uint8_t, curve25519_ge_byte_size> msg_s0;
    std::array<uint8_t, curve25519_ge_byte_size> msg_r1;

    recv_0(state, choice);
    connection_.recv(msg_s0.data(), msg_s0.size());
    recv_1(state, msg_r1, msg_s0);
    connection_.send(msg_r1.data(), msg_r1.size());
    return recv_2(state);
}

std::vector<std::pair<bytes_t, bytes_t>> OT_HL17::send(size_t number_ots)
{
    std::vector<Sender_State> states(number_ots);
    std::vector<std::array<uint8_t, curve25519_ge_byte_size>> msgs_s0(number_ots);
    std::vector<std::array<uint8_t, curve25519_ge_byte_size>> msgs_r1(number_ots);
    std::vector<std::pair<bytes_t, bytes_t>> output(number_ots);

    for (size_t i = 0; i < number_ots; ++i)
    {
        send_0(states[i], msgs_s0[i]);
    }

    auto fut_send_msg_s0 = connection_.async_send(reinterpret_cast<uint8_t*>(msgs_s0.data()), msgs_s0.size() * curve25519_ge_byte_size);
    auto fut_recv_msg_r1 = connection_.async_recv(reinterpret_cast<uint8_t*>(msgs_r1.data()), msgs_r1.size() * curve25519_ge_byte_size);

    for (size_t i = 0; i < number_ots; ++i)
    {
        send_1(states[i]);
    }

    auto msg_r1_size = fut_recv_msg_r1.get();
    assert(msg_r1_size == msgs_r1.size() * curve25519_ge_byte_size);

    for (size_t i = 0; i < number_ots; ++i)
    {
        output[i] = send_2(states[i], msgs_r1[i]);
    }

    auto msg_s0_size = fut_send_msg_s0.get();
    assert(msg_s0_size == msgs_s0.size() * curve25519_ge_byte_size);

    return output;
}

std::vector<bytes_t> OT_HL17::recv(const std::vector<bool>& choices)
{
    auto number_ots = choices.size();
    std::vector<Receiver_State> states(number_ots);
    std::vector<std::array<uint8_t, curve25519_ge_byte_size>> msgs_s0(number_ots);
    std::vector<std::array<uint8_t, curve25519_ge_byte_size>> msgs_r1(number_ots);
    std::vector<bytes_t> output(number_ots);

    auto fut_recv_msg_s0 = connection_.async_recv(reinterpret_cast<uint8_t*>(msgs_s0.data()), msgs_s0.size() * curve25519_ge_byte_size);

    for (size_t i = 0; i < number_ots; ++i)
    {
        recv_0(states[i], choices[i]);
    }

    auto msg_s0_size = fut_recv_msg_s0.get();
    assert(msg_s0_size == msgs_s0.size() * curve25519_ge_byte_size);

    for (size_t i = 0; i < number_ots; ++i)
    {
        recv_1(states[i], msgs_r1[i], msgs_s0[i]);
    }

    auto fut_send_msg_r1 = connection_.async_send(reinterpret_cast<uint8_t*>(msgs_r1.data()), msgs_r1.size() * curve25519_ge_byte_size);

    for (size_t i = 0; i < number_ots; ++i)
    {
        output[i] = recv_2(states[i]);
    }

    auto msg_r1_size = fut_send_msg_r1.get();
    assert(msg_r1_size == msgs_s0.size() * curve25519_ge_byte_size);

    return output;
}

std::vector<std::pair<bytes_t, bytes_t>> OT_HL17::parallel_send(size_t number_ots, size_t number_threads, boost::asio::thread_pool& thread_pool)
{
    std::vector<Sender_State> states(number_ots);
    std::vector<std::array<uint8_t, curve25519_ge_byte_size>> msgs_s0(number_ots);
    std::vector<std::array<uint8_t, curve25519_ge_byte_size>> msgs_r1(number_ots);
    std::vector<std::pair<bytes_t, bytes_t>> output(number_ots);

    compute(thread_pool, number_ots, number_threads, [this, &states, &msgs_s0](size_t index){ send_0(states[index], msgs_s0[index]); });

    auto fut_send_msg_s0 = connection_.async_send(reinterpret_cast<uint8_t*>(msgs_s0.data()), msgs_s0.size() * curve25519_ge_byte_size);
    auto fut_recv_msg_r1 = connection_.async_recv(reinterpret_cast<uint8_t*>(msgs_r1.data()), msgs_r1.size() * curve25519_ge_byte_size);

    compute(thread_pool, number_ots, number_threads, [this, &states](size_t index){ send_1(states[index]); });

    auto msg_r1_size = fut_recv_msg_r1.get();
    assert(msg_r1_size == msgs_r1.size() * curve25519_ge_byte_size);

    compute(thread_pool, number_ots, number_threads, [this, &states, &msgs_r1, &output](size_t index){ output[index] = send_2(states[index], msgs_r1[index]); });

    auto msg_s0_size = fut_send_msg_s0.get();
    assert(msg_s0_size == msgs_s0.size() * curve25519_ge_byte_size);

    return output;
}

std::vector<bytes_t> OT_HL17::parallel_recv(const std::vector<bool>& choices, size_t number_threads, boost::asio::thread_pool& thread_pool)
{
    auto number_ots = choices.size();
    std::vector<Receiver_State> states(number_ots);
    std::vector<std::array<uint8_t, curve25519_ge_byte_size>> msgs_s0(number_ots);
    std::vector<std::array<uint8_t, curve25519_ge_byte_size>> msgs_r1(number_ots);
    std::vector<bytes_t> output(number_ots);

    auto fut_recv_msg_s0 = connection_.async_recv(reinterpret_cast<uint8_t*>(msgs_s0.data()), msgs_s0.size() * curve25519_ge_byte_size);

    compute(thread_pool, number_ots, number_threads, [this, &states, &choices](size_t index){ recv_0(states[index], choices[index]); });

    auto msg_s0_size = fut_recv_msg_s0.get();
    assert(msg_s0_size == msgs_s0.size() * curve25519_ge_byte_size);

    compute(thread_pool, number_ots, number_threads, [this, &states, &msgs_r1, &msgs_s0](size_t index){ recv_1(states[index], msgs_r1[index], msgs_s0[index]); });

    auto fut_send_msg_r1 = connection_.async_send(reinterpret_cast<uint8_t*>(msgs_r1.data()), msgs_r1.size() * curve25519_ge_byte_size);

    compute(thread_pool, number_ots, number_threads, [this, &states, &output](size_t index){ output[index] = recv_2(states[index]); });

    auto msg_r1_size = fut_send_msg_r1.get();
    assert(msg_r1_size == msgs_s0.size() * curve25519_ge_byte_size);

    return output;
}


// Copyright (c) 2017 Graphcore Ltd. All rights reserved.

#ifndef poplibs_test_Lstm_hpp
#define poplibs_test_Lstm_hpp

#include <boost/multi_array.hpp>
#include <boost/optional.hpp>
#include <popnn/LstmDef.hpp>
#include <popnn/NonLinearityDef.hpp>

namespace poplibs_test {
namespace lstm {

// Defines for state information in forward pass
#define LSTM_NUM_FWD_STATES 7
#define LSTM_FWD_STATE_ACTS_IDX 0
#define LSTM_FWD_STATE_CELL_STATE_IDX 1

// Defines for state information in backward pass
#define LSTM_NUM_BWD_STATES BASIC_LSTM_CELL_NUM_UNITS

/**
 * Compute the cell state and output of a basic non-fused LSTM cell (without
 * peephole connections). The cell state and outputs are concatented into a
 * single dimension.
 *
 * \param input               Input to the LSTM cell of dimension
 *                            [sequenceSize][batchSize][inputSize]
 * \param biases              Biases in the LSTM cell of dimension
 *                            [NUM_LSTM_UNITS][outputSize]
 * \param prevOutput          Previous output used in the first time step
 *                            [batchSize][outputSize]
 * \param weightsInput        Weights in the LSTM cell which weigh the input
 *                            sequence. It is of dimension
 *                            [NUM_LSTM_UNITS][inputSize][outputSize]
 * \param weightsOutput       Weights in the LSTM cell which weigh the output
 *                            sequence. It is of dimension
 *                            [NUM_LSTM_UNITS][outputSize][outputSize]
 * \param timeSteps           Optional time steps limit of shape [batchSize]
 * \param prevCellState       Initial cell state of shape
 *                            [batchSize][outputSize]
 * \param state               The forward state for all the sequence steps of
 *                            dimension
 *                            [LSTM_NUM_FWD_STATES][sequenceSize][batchSize]
 *                            [outputSize]
 * \param lastOutput          Output after the final time step, of shape
 *                            [batchSize][outputSize]
 * \param lastCellState       Final cell state, of shape
 *                            [batchSize][outputSize]
 * \param cellOrder           The order that the weights for each gate are
 *                            stored in the input.
 * \param activation          Activation function.
 * \param recurrentActivation Recurrent activation function.
 */
void basicLstmCellForwardPass(
    const boost::multi_array_ref<double, 3> input,
    const boost::multi_array_ref<double, 2> biases,
    const boost::multi_array_ref<double, 2> prevOutput,
    const boost::multi_array_ref<double, 3> weightsInput,
    const boost::multi_array_ref<double, 3> weightsOutput,
    const boost::optional<boost::multi_array_ref<unsigned, 1>> &timeSteps,
    boost::multi_array_ref<double, 2> prevCellState,
    boost::multi_array_ref<double, 4> state,
    boost::multi_array_ref<double, 2> lastOutput,
    boost::multi_array_ref<double, 2> lastCellState,
    const std::vector<BasicLstmCellUnit> &cellOrder,
    const popnn::NonLinearityType activation,
    const popnn::NonLinearityType recurrentActivation);

/** Run backward pass given forward sequence
 *
 * \param outputFullSequence  if true, the full sequence of outputs will be
 *                            returned, otherwise, only the output of the last
 *                            cell will be returned.
 * \param weightsInput        Input weights
 *                            shape: [NUM_LSTM_UNITS][input ch][output ch]
 * \param weightsOutput       Output weights
 *                            shape: [NUM_LSTM_UNITS][output ch][output ch]
 * \param gradsNextLayer      Gradients from next layer needed to compute
 *                            gradients for this layer.
 *                            shape: [sequence][batch][output ch]
 * \param prevCellState       Cell state of the initial step in the forward pass
 *                            shape: [batch][output ch]
 * \param fwdState            Forward state returned by \see
 *                            basicLstmCellForwardPass.
 *                            shape: [LSTM_NUM_FWD_STATES][sequence][batch]
 *                                   [output ch]
 * \param initOutputGrad      Gradient of output at the last step which is
 *                            used to initialise the time sequential output
 *                            gradient in the backward pass.
 *                            shape: [batch][output ch]
 * \param initCellStateGrad   Gradient of cell state at the last step which is
 *                            used to initialise the time sequential cell state
 *                            gradient in the backward pass.
 *                            shape: [batch][output ch]
 * \param timeSteps           Optional time steps limit of shape [batchSize]
 * \param bwdState            Backward state returned by this function
 *                            shape:[LSTM_NUM_BWD_STATES][sequence]
 *                                  [batch][output ch]
 * \param gradsPrevIn         Gradients for previous input computed by this
 *                            function shape: [sequence][batch][input ch]
 * \param gradsPrevOut        Gradients for previous output computed by this
 *                            function shape: [sequence][batch][output ch]
 * \param gradsPrevCellState  Gradients for previous cell state computed by this
 *                            function shape: [sequence][batch][output ch]
 * \param cellOrder           The order that the weights for each gate are
 *                            stored in the input.
 * \param activation          Activation function.
 * \param recurrentActivation Recurrent activation function.
 */
void basicLstmCellBackwardPass(
    bool outputFullSequence,
    const boost::multi_array_ref<double, 3> weightsInput,
    const boost::multi_array_ref<double, 3> weightsOutput,
    const boost::multi_array_ref<double, 3> gradsNextLayer,
    const boost::multi_array_ref<double, 2> prevCellState,
    const boost::multi_array_ref<double, 4> fwdState,
    const boost::optional<boost::multi_array_ref<double, 2>> initOutputGrad,
    const boost::optional<boost::multi_array_ref<double, 2>> initCellStateGrad,
    const boost::optional<boost::multi_array_ref<unsigned, 1>> &timeSteps,
    boost::multi_array_ref<double, 4> bwdState,
    boost::multi_array_ref<double, 3> gradsPrevIn,
    const boost::multi_array_ref<double, 2> gradPrevOut,
    const boost::multi_array_ref<double, 2> gradsPrevCellState,
    const std::vector<BasicLstmCellUnit> &cellOrder,
    const popnn::NonLinearityType activation,
    const popnn::NonLinearityType recurrentActivation);

/** Param update
 *
 * \param prevLayerActs   Activations from previous layer
 *                        shape: [sequence][batch][input ch]
 * \param fwdState        Forward state compute by \see basicLstmCellForwardPass
 *                        shape: [LSTM_NUM_FWD_STATES][sequence][batch][output]
 * \param outputActsInit  Initial activations for the forward pass
 *                        shape: [batch][output ch]
 * \param bwdState        Backward channel state generated by backward pass
 *                        shape" [LSTM_NUM_BWD_STATES][sequence][batch][output]
 * \param weightsInputDeltas  Weight deltas computed by this function
 *                            shape: [NUM_LSTM_UNITS][input ch][output ch]
 * \param weightsOutputDeltas Weight deltas computed by this function
 *                            shape: [NUM_LSTM_UNITS][output ch][output ch]
 *
 * \param biasDeltas      Bias deltas computed by this function
 *                        shape: [NUM_LSTM_UNITS][output ch]
 * \param cellOrder       The order that the weights for each gate are
 *                        stored in the input.
 */
void basicLstmCellParamUpdate(
    const boost::multi_array_ref<double, 3> prevLayerActs,
    const boost::multi_array_ref<double, 4> fwdState,
    const boost::multi_array_ref<double, 2> outputActsInit,
    const boost::multi_array_ref<double, 4> bwdState,
    boost::multi_array_ref<double, 3> weightsInputDeltas,
    boost::multi_array_ref<double, 3> weightsOutputDeltas,
    boost::multi_array_ref<double, 2> biasDeltas,
    const std::vector<BasicLstmCellUnit> &cellOrder);
} // namespace lstm
} // namespace poplibs_test

#endif // poplibs_test_Lstm_hpp


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_X86_AVX_SIMD_FUNCTION_LDEXP_HPP_INCLUDED
#define BOOST_SIMD_ARCH_X86_AVX_SIMD_FUNCTION_LDEXP_HPP_INCLUDED
#include <boost/simd/detail/overload.hpp>

#include <boost/simd/meta/hierarchy/simd.hpp>
#include <boost/config.hpp>
#include <boost/simd/function/bitwise_and.hpp>
#include <boost/simd/function/fma.hpp>
#include <boost/simd/function/is_flint.hpp>
#include <boost/simd/function/is_invalid.hpp>
#include <boost/simd/function/shift_left.hpp>
#include <boost/simd/detail/constant/maxexponent.hpp>
#include <boost/simd/constant/nbexponentbits.hpp>
#include <boost/simd/detail/constant/pow2mask.hpp>
#include <boost/simd/constant/two.hpp>
#include <boost/simd/detail/dispatch/meta/scalar_of.hpp>

namespace boost { namespace simd { namespace ext
{
   namespace bd = boost::dispatch;
   namespace bs = boost::simd;

  BOOST_DISPATCH_OVERLOAD ( ldexp_
                          , (typename A0)
                          , bs::avx_
                          , bs::pack_<bd::double_<A0>, avx_>
                          , bs::pack_<bd::double_<A0>, avx_>
                          )
  {
    BOOST_FORCEINLINE A0 operator() ( const A0& a0, const A0& a1) const BOOST_NOEXCEPT
    {
      using sA0 = bd::scalar_of_t<A0>;
      BOOST_ASSERT_MSG(assert_all(is_flint(a1)||is_invalid(a1)), "parameter is not a flint nor invalid");
      A0 oddv = fma(a1+Maxexponent<sA0>(), Two<sA0>(), (1 << (Nbexponentbits<sA0>()+1)) + 1);
      return a0*shift_left(bitwise_and(Pow2mask<A0>(), oddv), Nbexponentbits<sA0>());
    }
  };

} } }

#endif


/** \file ElInfo3d.h */

#pragma once

#include <boost/numeric/mtl/mtl.hpp>

#include "ElInfo.hpp"

namespace AMDiS
{

  /** \ingroup Traverse
   * \brief
   * ElInfo class for 3-dimensional elements (\ref Tetrahedron).
   */
  class ElInfo3d : public ElInfo
  {
  public:
    /// Constructor. Calls ElInfo's protected Constructor.
    ElInfo3d(Mesh* aMesh)
      : ElInfo(aMesh)
    {
      tmpWorldVecs.resize(3);
      for (int i = 0; i < 3; i++)
        tmpWorldVecs[i].resize(3);
    }

    /// Assignment operator
    ElInfo3d& operator=(const ElInfo3d& rhs)
    {
      ElInfo::operator=(rhs);
      elType = rhs.elType;
      orientation = rhs.orientation;
      return *this;
    }

    /// get ElInfo's \ref orientation
    signed char getOrientation() const
    {
      return orientation;
    }

    /// set ElInfo's \ref orientation to o
    void setOrientation(signed char o)
    {
      orientation = o;
    }

    /// 3-dimensional realisation of ElInfo's fillElInfo method.
    virtual void fillElInfo(int ichild, const ElInfo* elinfo_old) override;

    /// 3-dimensional realisation of ElInfo's fillMacroInfo method.
    virtual void fillMacroInfo(const MacroElement*) override;

    /// 3-dimensional realisation of ElInfo's worldToCoord method.
    virtual int worldToCoord(const WorldVector<double>& w, DimVec<double>& l) const override;

    /// 3-dimensional realisation of ElInfo's calcGrdLambda method.
    virtual double calcGrdLambda(DimVec<WorldVector<double>>& grd_lam) override;

    /// 3-dimensional realisation of ElInfo's getNormal method.
    virtual double getNormal(int side, WorldVector<double>& normal) const override;

    virtual double getElementNormal(WorldVector<double>& /*normal*/) const override
    {
      FUNCNAME("ElInfo3d::getElementNormal()");
      ERROR_EXIT("ElementNormal not available in 3d!");
      return 0.0;
    }

    /// update ElInfo after refinement (of some neighbours). Only in 3d!
    void update();

    /// implements \ref Elnfo::getSubElemCoordsMat
    virtual mtl::dense2D<double>& getSubElemCoordsMat(int degree) const override;

  protected:
    /** \brief
     * +/- 1: sign of the determinant of the transformation to the reference
     * element with vertices (0,0,0), (1,1,1), (1,1,0), (1,0,0).
     */
    signed char orientation;

    /// Tmp vectors used for calculations in calcGrdLambda and getNormal().
    std::vector<std::vector<double>> tmpWorldVecs;

    static double mat_d1_left_val[4][4];
    static mtl::dense2D<double> mat_d1_left;

    static double mat_d1_l0_right_val[4][4];
    static mtl::dense2D<double> mat_d1_l0_right;

    static double mat_d1_l12_right_val[4][4];
    static mtl::dense2D<double> mat_d1_l12_right;

    static double mat_d4_left_val[35][35];
    static mtl::dense2D<double> mat_d4_left;

    static double mat_d4_l0_right_val[35][35];
    static mtl::dense2D<double> mat_d4_l0_right;

    static double mat_d4_l12_right_val[35][35];
    static mtl::dense2D<double> mat_d4_l12_right;
  };

} // end namespace AMDiS


// -------------------------------------------------------------------------------------------------
//                              Copyright 2016 - NumScale SAS
//
//                   Distributed under the Boost Software License, Version 1.0.
//                        See accompanying file LICENSE.txt or copy at
//                            http://www.boost.org/LICENSE_1_0.txt
// -------------------------------------------------------------------------------------------------

#include <simd_bench.hpp>
#include <boost/simd/function/simd/tofloat.hpp>

namespace nsb = ns::bench;
namespace bs =  boost::simd;

DEFINE_SCALAR_BENCH(scalar_tofloat, bs::tofloat);

DEFINE_BENCH_MAIN()
{
  nsb::for_each<scalar_tofloat, int32_t, int64_t,uint32_t, uint64_t, float, double>(-10, 10);
}


#define BOOST_TEST_MODULE lue framework core hilbert_curve
#include <hpx/config.hpp>
#include <boost/test/unit_test.hpp>
#include "lue/framework/core/hilbert_curve.hpp"
#include "lue/framework/test/stream.hpp"
#include <tuple>
#include <vector>

namespace {

template<
    typename Shape,
    typename Coordinates>
void fill_and_test(
    Shape const& shape,
    Coordinates const& result_we_want)
{
    constexpr lue::Rank rank{2};

    Coordinates result_we_got(lue::nr_elements(shape));

    lue::hilbert_curve<rank>(shape,

            [&result_we_got](
                lue::Index const idx,
                lue::Index const row,
                lue::Index const col)
            {
                result_we_got[idx] = std::make_tuple(row, col);
            }

        );

    BOOST_CHECK_EQUAL_COLLECTIONS(
        result_we_got.begin(), result_we_got.end(),
        result_we_want.begin(), result_we_want.end());
}

}  // Anonymous namespace

BOOST_AUTO_TEST_CASE(d2)
{
    constexpr lue::Rank rank = 2;
    using Shape = lue::Shape<lue::Count, rank>;
    using Coordinate = std::tuple<lue::Index, lue::Index>;
    using Coordinates = std::vector<Coordinate>;

    {
        Shape const shape{4, 3};
        Coordinates const result_we_want{
                { 0, 0 },
                { 1, 0 },
                { 1, 1 },
                { 0, 1 },
                { 0, 2 },
                { 1, 2 },
                { 2, 2 },
                { 3, 2 },
                { 3, 1 },
                { 2, 1 },
                { 2, 0 },
                { 3, 0 },
            };

        fill_and_test(shape, result_we_want);
    }

    {
        Shape const shape{1, 1};
        Coordinates const result_we_want{
                { 0, 0 },
            };

        fill_and_test(shape, result_we_want);
    }

    {
        Shape const shape{0, 0};
        Coordinates const result_we_want{};
        fill_and_test(shape, result_we_want);
    }

    {
        Shape const shape{0, 1};
        Coordinates const result_we_want{};
        fill_and_test(shape, result_we_want);
    }

    {
        Shape const shape{1, 0};
        Coordinates const result_we_want{};
        fill_and_test(shape, result_we_want);
    }

}


// Copyright 2010 The Trustees of Indiana University.

// Distributed under 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)

//  Authors: Jeremiah Willcock
//           Andrew Lumsdaine

#include <boost/graph/random_spanning_tree.hpp>
#include <boost/graph/grid_graph.hpp>
#include <boost/array.hpp>
#include <boost/random.hpp>
#include <boost/property_map/shared_array_property_map.hpp>
#include <boost/property_map/dynamic_property_map.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/graph/property_maps/constant_property_map.hpp>
#include <string>
#include <iostream>
#include <fstream>
#include <boost/graph/iteration_macros.hpp>

using namespace boost;
using namespace std;

typedef grid_graph<2> graph_type;
typedef graph_traits<graph_type> gt;

template <typename Graph, typename PredMap, typename WeightMap>
void write_spanning_tree(const Graph& g, PredMap pred, WeightMap weight, string filename) {
  shared_array_property_map<string, typename property_map<Graph, edge_index_t>::const_type> edge_style(num_edges(g), get(edge_index, g));
  shared_array_property_map<string, typename property_map<Graph, vertex_index_t>::const_type> vertex_pos(num_vertices(g), get(vertex_index, g));
  BGL_FORALL_EDGES_T(e, g, Graph) {
    put(edge_style, e, (get(pred, target(e, g)) == source(e, g) || get(pred, source(e, g)) == target(e, g)) ? "bold" : "dotted");
  }
  BGL_FORALL_VERTICES_T(v, g, Graph) {
    put(vertex_pos, v, lexical_cast<string>(v[0]) + "," + lexical_cast<string>(v[1]));
  }
  dynamic_properties dp;
  dp.property("style", edge_style);
  dp.property("pos", vertex_pos);
  dp.property("len", weight);
  dp.property("node_id", get(vertex_index, g));
  ofstream out(filename.c_str());
  write_graphviz_dp(out, g, dp);
}

int main(int, char**) {

  boost::array<size_t, 2> sizes = {{ 5, 5 }};
  graph_type g(sizes);

  shared_array_property_map<gt::vertex_descriptor, property_map<graph_type, vertex_index_t>::const_type> pred(num_vertices(g), get(vertex_index, g));
  shared_array_property_map<double, property_map<graph_type, edge_index_t>::const_type> weight(num_edges(g), get(edge_index, g));

  BGL_FORALL_EDGES(e, g, graph_type) {put(weight, e, (1. + get(edge_index, g, e)) / num_edges(g));}

  boost::mt19937 gen;
  random_spanning_tree(g, gen, predecessor_map(pred));
  // write_spanning_tree(g, pred, constant_property_map<gt::edge_descriptor, double>(1.), "unweight_random_st.dot");
  random_spanning_tree(g, gen, predecessor_map(pred));
  // write_spanning_tree(g, pred, constant_property_map<gt::edge_descriptor, double>(1.), "unweight_random_st2.dot");

  random_spanning_tree(g, gen, predecessor_map(pred).weight_map(weight));
  // write_spanning_tree(g, pred, weight, "weight_random_st.dot");

  return 0;
}


//  (C) Copyright Gennadiy Rozental 2001-2005.
//  Distributed under 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)

//  See http://www.boost.org/libs/test for the library home page.
//
//  File        : $RCSfile: test_fp_comparisons.cpp,v $
//
//  Version     : $Revision: 1.22 $
//
//  Description : tests floating point comparison algorithms
// ***************************************************************************

// Boost.Test
#define BOOST_AUTO_TEST_MAIN
#include <boost/test/auto_unit_test.hpp>
#include <boost/test/test_case_template.hpp>
#include <boost/test/floating_point_comparison.hpp>

// Boost
#include <boost/mpl/list.hpp>
#include <boost/bind.hpp>

// STL
#include <functional>

using namespace boost;
using namespace boost::unit_test;
using namespace boost::test_tools;

bool not_func( bool arg ) { return !arg; }

//____________________________________________________________________________//

typedef boost::mpl::list<float,double,long double> test_types;

BOOST_AUTO_TEST_CASE_TEMPLATE( test_BOOST_CHECK_CLOSE, FPT, test_types )
{
#define CHECK_CLOSE( first, second, e )     \
    fp1     = static_cast<FPT>(first);      \
    fp2     = static_cast<FPT>(second);     \
    epsilon = static_cast<FPT>(e);          \
                                            \
    BOOST_CHECK_CLOSE( fp1, fp2, epsilon ); \
/**/

#define CHECK_NOT_CLOSE( first, second, e ) \
    fp1     = static_cast<FPT>(first);      \
    fp2     = static_cast<FPT>(second);     \
    epsilon = static_cast<FPT>(e);          \
                                            \
    BOOST_CHECK_PREDICATE( bind(not_func, bind(check_is_close, _1, _2, _3)), (fp1)(fp2)(epsilon) ); \
/**/

    FPT fp1, fp2, epsilon;

    CHECK_CLOSE( 1, 1, 0 );

    CHECK_NOT_CLOSE( 0, 1e-20, 1e-5 );
    CHECK_NOT_CLOSE( 0, 1e-30, 1e-5 );
    CHECK_NOT_CLOSE( 0, -1e-10, 0.1 );
    CHECK_NOT_CLOSE( 0.123456, 0.123457, 1e-4 );

    CHECK_CLOSE( 0.123456, 0.123457, 1e-3 );

    CHECK_NOT_CLOSE( 0.123456, -0.123457, 1e-3 );

    CHECK_CLOSE( 1.23456e28, 1.23457e28, 1e-3 );

    CHECK_CLOSE( 1.23456e-10, 1.23457e-10, 1e-3 );
    CHECK_NOT_CLOSE( 1.111e-10, 1.112e-10, 0.08999 );
    CHECK_CLOSE( 1.112e-10, 1.111e-10, 0.1 );

    CHECK_CLOSE( 1, 1.0001, 1.1e-2 );
    CHECK_CLOSE( 1.0002, 1.0001, 1.1e-2 );

    CHECK_NOT_CLOSE( 1, 1.0002, 1.1e-2 );
}

//____________________________________________________________________________//

BOOST_AUTO_TEST_CASE( test_CHECK_SMALL )
{
    BOOST_CHECK_SMALL( 1e-6, 1e-5 );
    BOOST_CHECK_SMALL( -1e-6, 1e-5 );

    BOOST_CHECK_PREDICATE( bind(not_func, bind(check_is_small, _1, _2 )), (1e-6)(1e-7) );
}

//____________________________________________________________________________//

BOOST_AUTO_TEST_CASE( test_close_at_tolerance )
{
    double fp1     = 1.00000001;
    double fp2     = 1.00000002;
    double epsilon = 1e-6;

    close_at_tolerance<double> pred( epsilon, FPC_WEAK );
    BOOST_CHECK_PREDICATE( pred, (fp1)(fp2) );

    BOOST_CHECK_PREDICATE( bind(not_func, bind(check_is_close, _1, _2, _3)), (fp1)(fp2)(epsilon) );

    fp1     = 1.23456e-10;
    fp2     = 1.23457e-10;
    epsilon = 8.1e-4;

    BOOST_CHECK_PREDICATE( close_at_tolerance<double>( epsilon, FPC_WEAK ), (fp1)(fp2) );
    BOOST_CHECK_PREDICATE( bind(not_func, bind(close_at_tolerance<double>( epsilon ), _1, _2)), (fp1)(fp2) );
}

//____________________________________________________________________________//

// ***************************************************************************
//  Revision History :
//
//  $Log: test_fp_comparisons.cpp,v $
//  Revision 1.22  2005/05/29 08:54:04  rogeeff
//  remove format testing
//
//  Revision 1.17  2005/02/20 08:28:34  rogeeff
//  This a major update for Boost.Test framework. See release docs for complete list of fixes/updates
//
// ***************************************************************************

// EOF


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

#include <index_of.hpp>

template<typename...> struct List;

BOOST_AUTO_TEST_CASE(index_of_test)
{
	using namespace meta;

	using map = List<int,double,float>;

	BOOST_CHECK( (index_of<map, int>::value    == 0) );
	BOOST_CHECK( (index_of<map, double>::value == 1) );
	BOOST_CHECK( (index_of<map, float>::value  == 2) );
}


#pragma once

#include <boost/container/flat_set.hpp>

#include <iron/core.h>

#include <iron/math/PrimeStash.hpp>

namespace iron {
namespace math {

struct PrimeCalculator {

    PrimeCalculator()
      : mStash(),
        mIOService(),
        mThreadPool(),
        mWork(new boost::asio::io_service::work(mIOService)) {

      unsigned numThreads = std::max((unsigned)4, (unsigned)2*boost::thread::hardware_concurrency());
      for (unsigned idx = 0; idx < numThreads; ++idx) {
          mThreadPool.create_thread(boost::bind(&boost::asio::io_service::run, &mIOService));
      }

      LOG_DEBUG("Created thread pool with (" << numThreads << ") threads");

      mPrimes.insert(2);
    }

    ~PrimeCalculator() {
      mWork.reset();
      mThreadPool.join_all();
    }

    // Stubbed capability, not yet multithreaded
    bool isPrime(uint64_t inVal) {
      LOG_TRACE(__func__ << "(" << inVal << ")");

      // Check previously computed
      if (mPrimes.count(inVal))      { return true;  }
      if (inVal < *mPrimes.rbegin()) { return false; }

      const uint64_t sqrtVal  = std::sqrt(inVal);

      while (*mPrimes.rbegin() < sqrtVal) {
        uint64_t trial = (*mPrimes.rbegin()) + 1;
        while (!isPrime(trial)) {
          ++trial;
        }
        mPrimes.insert(trial);
      }
      for (uint64_t p : mPrimes) {
        if (inVal % p == 0) return false;
      }

      return true;
    }

  protected:
    bool hasCached(uint64_t inVal) {
      uint64_t lastPrime    = mPrimes.empty() ? 2 : (*mPrimes.rbegin());
      uint64_t primeSquared = lastPrime * lastPrime;

      return (primeSquared >= inVal);
    }

  private:

    PrimeStash mStash;
    boost::asio::io_service mIOService;
    boost::thread_group     mThreadPool;
    std::unique_ptr<boost::asio::io_service::work> mWork;
    boost::container::flat_set<uint64_t> mPrimes;
};

} /* math */
} /* iron */


#include <gtest/gtest.h>

#include <Eigen/Dense>

#include "utils/compare_matrices_utils.h"

TEST(CompareMatricesUtilsTest, test_compare) {
	Eigen::Matrix2d matrix_x2_one_1;
	Eigen::Matrix2d matrix_x2_one_2;
	Eigen::Matrix2d matrix_x2_zero_1;

	matrix_x2_one_1 << 1.0, 1.0, 1.0, 1.0;
	matrix_x2_one_2 << 1.0, 1.0, 1.0, 1.0;
	matrix_x2_zero_1 << 0, 0, 0, 0;

	ASSERT_TRUE(compare_matrices(Eigen::Vector2d(1, 1), Eigen::Vector2d(1, 1)));
	ASSERT_TRUE(compare_matrices(matrix_x2_one_1, matrix_x2_one_2));

	ASSERT_FALSE(compare_matrices(Eigen::Vector2d(1, 2), Eigen::Vector2d(1, 1)));
	ASSERT_FALSE(compare_matrices(matrix_x2_one_1, matrix_x2_zero_1));

	ASSERT_FALSE(compare_matrices(Eigen::VectorXd(3), Eigen::VectorXd(2)));
	ASSERT_FALSE(compare_matrices(Eigen::MatrixXd(1, 2), Eigen::MatrixXd(1, 3)));
	ASSERT_FALSE(compare_matrices(Eigen::MatrixXd(2, 1), Eigen::MatrixXd(1, 1)));
}


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

#define BOOST_TEST_MODULE test_checks

//_DEBUG
#include <iostream>

//libraries
#include <boost/test/unit_test.hpp>
#include <boost/python.hpp>
#include <Eigen/Dense>
#include <unsupported/Eigen/CXX11/Tensor>

//test targets
#include "../src/math/almost_equal.hpp"
#include "../src/math/checks.hpp"
#include "../src/math/typedefs.hpp"

namespace eig = Eigen;

BOOST_AUTO_TEST_CASE(test_almost_equal_reals) {
	float a = 10.2093f;
	float b = 10.2394f;
	BOOST_REQUIRE(math::almost_equal(a, b, 0.04));
	BOOST_REQUIRE(!math::almost_equal(a, b, 0.03));
	double c = -291.299912;
	double d = -291.299936;
	BOOST_REQUIRE(math::almost_equal(c, d, 1e-4));
	BOOST_REQUIRE(!math::almost_equal(c, d, 1e-5));
	float e = 10.23935;
	BOOST_REQUIRE(math::almost_equal(b, e, 1e-4));
	BOOST_REQUIRE(!math::almost_equal(b, e, 1e-5));
}

BOOST_AUTO_TEST_CASE(test_almost_equal_real_matrices) {
	eig::MatrixXf a(2, 3);
	a << 1.0f, -2.0f, 3.0f,
			4.0f, 5.0f, 6.0f;
	eig::MatrixXf b(3, 2);
	b << 1.0f, -2.0f,
			3.0f, 4.0f,
			5.0f, 6.0f;
	BOOST_REQUIRE(!math::almost_equal(a, b, 1e-5));
	eig::MatrixXf c(2, 3);
	c << 1.0f, -2.0f, 3.0f,
			4.0f, 5.0f, 6.0000004f;
	BOOST_REQUIRE(math::almost_equal(a, c, 1e-5));
	BOOST_REQUIRE(!math::almost_equal(a, c, 1e-7));

	eig::MatrixXd e(2, 1);
	e << -10.003, 9.9997;
	eig::MatrixXd f(2, 1);
	f << -10.00, 10.00;
	BOOST_REQUIRE(math::almost_equal(e, f, 1e-2));
	BOOST_REQUIRE(!math::almost_equal(e, f, 1e-3));
}

BOOST_AUTO_TEST_CASE(test_almost_equal_nested_matrices) {
	math::MatrixXv2f a(1, 2);
	a << math::Vector2f(1.0f, 2.0f), math::Vector2f(-3.0f, 4.0f);
	math::MatrixXv2f b(1, 2);
	b << math::Vector2f(1.0f, 2.0f), math::Vector2f(-3.0f, 4.000002f);
	BOOST_REQUIRE(math::almost_equal(a, b, 1e-5));
	BOOST_REQUIRE(!math::almost_equal(a, b, 1e-6));

	math::MatrixXm2f c(2, 1);
	c << math::Matrix2f(1.0f, 2.0f, 3.0f, 4.0f), math::Matrix2f(4.0f, 0.0f, -12.0f, -230.129f);
	math::MatrixXm2f d(2, 1);
	d << math::Matrix2f(1.0f, 2.0f, 3.0f, 4.0f), math::Matrix2f(4.0f, 0.0f, -12.0f, -230.1291f);
	BOOST_REQUIRE(math::almost_equal(c, d, 1e-3));
	BOOST_REQUIRE(!math::almost_equal(c, d, 1e-4));

	math::MatrixXv2f e(2, 1);
	e << math::Vector2f(1.0f, 2.0f), math::Vector2f(-3.0f, 4.000002f);

	BOOST_REQUIRE(!math::almost_equal(b, e, 1e-4));
}

BOOST_AUTO_TEST_CASE(test_almost_equal_real_tensors) {
	math::Tensor3f a(1, 3, 2);
	a.setValues(
			{ { { 1.0f, 2.0f },
					{ 3.0f, -4.0f },
					{ 5.0f, 6.0f } } }
			);
	math::Tensor3f b(1, 3, 2);
	b.setValues(
			{ { { 1.0f, 2.0f },
					{ 3.0f, -3.9998f },
					{ 5.0f, 6.0f } } }
			);

	BOOST_REQUIRE(math::almost_equal(a, b, 3e-4));
	BOOST_REQUIRE(!math::almost_equal(a, b, 1e-4));

	math::Tensor3f c(1, 2, 3);
	c.setValues(
			{ { { 1.0f, 2.0f, 3.0f },
					{ -3.9998f, 5.0f, 6.0f } } }
			);
	BOOST_REQUIRE(!math::almost_equal(b, c, 1e-4));
}

typedef math::Vector3f t3;

BOOST_AUTO_TEST_CASE(test_almost_equal_nested_tensors) {
	math::Tensor3v3f a(1, 3, 2);

	a.setValues(
			{ { { t3(1.0f), t3(2.0f) },
					{ t3(3.0f), t3(-4.0f) },
					{ t3(5.0f), t3(6.0f) } } }
			);
	math::Tensor3v3f b(1, 3, 2);
	b.setValues(
			{ { { t3(1.0f), t3(2.0f) },
					{ t3(3.0f), t3(-4.0f, -3.9998f, -4.0f) },
					{ t3(5.0f), t3(6.0f) } } }
			);
	BOOST_REQUIRE(math::almost_equal(a, b, 3e-4));
	BOOST_REQUIRE(!math::almost_equal(a, b, 1e-4));

	math::Tensor3v3f c(1, 2, 3);
	c.setValues(
			{ { { t3(1.0f), t3(2.0f), t3(3.0f) },
					{ t3(-4.0f, -3.9998f, -4.0f), t3(5.0f), t3(6.0f) } } }
			);
	BOOST_REQUIRE(!math::almost_equal(b, c, 1e-4));

}

BOOST_AUTO_TEST_CASE(test_is_power_of_two) {
	BOOST_REQUIRE(math::is_power_of_two(128));
	BOOST_REQUIRE(math::is_power_of_two(2));
	BOOST_REQUIRE(math::is_power_of_two(16));
	BOOST_REQUIRE(!math::is_power_of_two(17));
	BOOST_REQUIRE(!math::is_power_of_two(38));
}

BOOST_AUTO_TEST_CASE(test_dimension_checks_matrix) {
	eig::MatrixXf a(2,3);
	eig::MatrixXf b(2,3);
	eig::MatrixXf c(2,3);
	eig::MatrixXf d(3,2);
	eig::MatrixXf e(2,4);
	BOOST_REQUIRE(math::are_dimensions_equal(a,b));
	BOOST_REQUIRE(!math::are_dimensions_equal(a,d));
	BOOST_REQUIRE(!math::are_dimensions_equal(a,e));
	BOOST_REQUIRE(math::are_dimensions_equal(a,b,c));
	BOOST_REQUIRE(!math::are_dimensions_equal(a,e,c));

	eig::MatrixXf f(2,3);
	math::MatrixXv2f g(2,3);
	math::MatrixXv2f h(2,4);
	eig::MatrixXd i(2,3);
	BOOST_REQUIRE(math::are_dimensions_equal(f,g));
	BOOST_REQUIRE(!math::are_dimensions_equal(f,h));
	BOOST_REQUIRE(!math::are_dimensions_equal(g,h));
	BOOST_REQUIRE(!math::are_dimensions_equal(g,h,i));
	BOOST_REQUIRE(math::are_dimensions_equal(f,g,i));
}

BOOST_AUTO_TEST_CASE(test_dimension_checks_tensor) {
	math::Tensor3f a(2,3,2);
	math::Tensor3f b(2,3,2);
	math::Tensor3f c(2,3,2);
	math::Tensor3f d(3,2,2);
	math::Tensor3f e(2,4,2);
	BOOST_REQUIRE(math::are_dimensions_equal(a,b));
	BOOST_REQUIRE(!math::are_dimensions_equal(a,d));
	BOOST_REQUIRE(!math::are_dimensions_equal(a,e));
	BOOST_REQUIRE(math::are_dimensions_equal(a,b,c));
	BOOST_REQUIRE(!math::are_dimensions_equal(a,e,c));

	math::Tensor3f f(1,2,3);
	math::Tensor3v3f g(1,2,3);
	math::Tensor3v3f h(1,2,4);
	eig::Tensor<double, 3, eig::ColMajor> i(1,2,3);
	BOOST_REQUIRE(math::are_dimensions_equal(f,g));
	BOOST_REQUIRE(!math::are_dimensions_equal(f,h));
	BOOST_REQUIRE(!math::are_dimensions_equal(g,h));
	BOOST_REQUIRE(!math::are_dimensions_equal(g,h,i));
	BOOST_REQUIRE(math::are_dimensions_equal(f,g,i));
}


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

#include <iostream>
#include <vector>
#include <madym/t1_methods/mdm_T1FitterVFA.h>
#include <madym/t1_methods/mdm_T1FitterIR.h>
#include <madym/tests/mdm_test_utils.h>

BOOST_AUTO_TEST_SUITE(test_mdm)

BOOST_AUTO_TEST_CASE(test_T1_VFA) {
  BOOST_TEST_MESSAGE("======= Testing T1 VFA mapping =======");

  //Read in T1 calibration file
  int nFAs;
  double T1;
  double M0;
  double TR;
  std::string T1FileName(mdm_test_utils::calibration_dir() + "T1.dat");
  std::ifstream T1FileStream(T1FileName, std::ios::in | std::ios::binary);
  T1FileStream.read(reinterpret_cast<char*>(&nFAs), sizeof(int));

  std::vector<double> FAs(nFAs);
  std::vector<double> signalsCalibration(nFAs);
  for (double &fa : FAs)
    T1FileStream.read(reinterpret_cast<char*>(&fa), sizeof(double));
  for (double &s : signalsCalibration)
    T1FileStream.read(reinterpret_cast<char*>(&s), sizeof(double));

  T1FileStream.read(reinterpret_cast<char*>(&T1), sizeof(double));
  T1FileStream.read(reinterpret_cast<char*>(&M0), sizeof(double));
  T1FileStream.read(reinterpret_cast<char*>(&TR), sizeof(double));

  T1FileStream.close();

  BOOST_TEST_MESSAGE(boost::format(
    "Read T1 calibration file, T1 = %1% , M0 =%2%, TR = %3%")
    % T1 % M0 % TR);

  //Now compute signals
  std::vector<double> signals(nFAs);
  BOOST_TEST_MESSAGE("Testing signals from VFA (calibration, computed): ");
  for (int i = 0; i < nFAs; i++)
  {
    signals[i] = mdm_T1FitterVFA::T1toSignal(T1, M0, FAs[i], TR);
    BOOST_TEST_MESSAGE(boost::format("(%1%, %2%)")
      % signalsCalibration[i] % signals[i]);
  }
  BOOST_CHECK_VECTORS(signals, signalsCalibration);

  //Next fit the signals to recover M0 and T1
  double T1fit, M0fit;
  mdm_T1FitterVFA T1CalculatorVFA(FAs, TR, false);
  T1CalculatorVFA.setInputs(signalsCalibration);
  auto errCode = T1CalculatorVFA.fitT1(T1fit, M0fit);
  BOOST_CHECK_MESSAGE(!errCode, "T1 fit returned error " << errCode);

  BOOST_TEST_MESSAGE("Testing fitted T1 match using VFA");
  BOOST_CHECK_CLOSE(T1fit, T1, 0.01);
  BOOST_TEST_MESSAGE("Testing fitted M0 match using VFA");
  BOOST_CHECK_CLOSE(M0fit, M0, 0.01);

  //Repeat the test using VFA B1 correction
  auto signalsCalibrationB1 = signalsCalibration;
  auto FAsB1 = FAs;
  double B1 = 0.9;
  for (auto &fa : FAsB1)
    fa /= B1;
  signalsCalibrationB1.push_back(B1);

  mdm_T1FitterVFA T1CalculatorB1(FAsB1, TR, true);
  T1CalculatorB1.setInputs(signalsCalibrationB1);
  errCode = T1CalculatorB1.fitT1(T1fit, M0fit);
  BOOST_CHECK_MESSAGE(!errCode, "T1 fit returned error " << errCode);

  BOOST_TEST_MESSAGE("Testing fitted T1 match using VFA B1 correction");
  BOOST_CHECK_CLOSE(T1fit, T1, 0.01);
  BOOST_TEST_MESSAGE("Testing fitted M0 match using VFA B1 correction");
  BOOST_CHECK_CLOSE(M0fit, M0, 0.01);
}

BOOST_AUTO_TEST_CASE(test_T1_IR) {
  BOOST_TEST_MESSAGE("======= Testing T1 inversion recovery mapping =======");
  //Test inversion recovery method

  //Read in T1 calibration file
  int nTIs;
  double T1;
  double M0;
  double TR;
  std::string T1FileName(mdm_test_utils::calibration_dir() + "T1_IR.dat");
  std::ifstream T1FileStream(T1FileName, std::ios::in | std::ios::binary);
  T1FileStream.read(reinterpret_cast<char*>(&nTIs), sizeof(int));

  std::vector<double> TIs(nTIs);
  std::vector<double> signalsCalibration(nTIs);
  for (double &ti : TIs)
    T1FileStream.read(reinterpret_cast<char*>(&ti), sizeof(double));
  for (double &s : signalsCalibration)
    T1FileStream.read(reinterpret_cast<char*>(&s), sizeof(double));

  T1FileStream.read(reinterpret_cast<char*>(&T1), sizeof(double));
  T1FileStream.read(reinterpret_cast<char*>(&M0), sizeof(double));
  T1FileStream.read(reinterpret_cast<char*>(&TR), sizeof(double));

  T1FileStream.close();

  BOOST_TEST_MESSAGE(boost::format(
    "Read T1 calibration file, T1 = %1% , M0 =%2%, TR = %3%")
    % T1 % M0 % TR);

  std::vector<double> signals(nTIs);
  BOOST_TEST_MESSAGE("Testing signals from IR (calibration, computed): ");
  for (int i = 0; i < nTIs; i++)
  {
    signals[i] = mdm_T1FitterIR::T1toSignal(T1, M0, TIs[i], TR);
    BOOST_TEST_MESSAGE(boost::format("(%1%, %2%)")
      % signalsCalibration[i] % signals[i]);
  }

  mdm_T1FitterIR T1CalculatorIR(TIs, TR);
  T1CalculatorIR.setInputs(signalsCalibration);

  double T1fit, M0fit;
  auto errCode = T1CalculatorIR.fitT1(T1fit, M0fit);
  BOOST_CHECK_MESSAGE(!errCode, "T1 fit returned error " << errCode);

  BOOST_TEST_MESSAGE("Testing fitted T1 match using inversion recovery");
  BOOST_CHECK_CLOSE(T1fit, T1, 0.01);
  BOOST_TEST_MESSAGE("Testing fitted M0 match using inversion recovery");
  BOOST_CHECK_CLOSE(M0fit, M0, 0.01);

}

BOOST_AUTO_TEST_SUITE_END() //