//==================================================================================================
/**
  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_COMMON_SIMD_FUNCTION_REC_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SIMD_FUNCTION_REC_HPP_INCLUDED

#include <boost/simd/detail/overload.hpp>
#include <boost/simd/detail/traits.hpp>
#include <boost/simd/function/if_else.hpp>
#include <boost/simd/function/if_else_allbits.hpp>
#include <boost/simd/function/if_else_zero.hpp>
#include <boost/simd/function/if_one_else_zero.hpp>
#include <boost/simd/function/abs.hpp>
#include <boost/simd/function/raw.hpp>
#include <boost/simd/constant/valmax.hpp>
#include <boost/simd/constant/one.hpp>

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

  ///////////////////////////////////////////////////////////////////////////////////////////////////
  /// rec for integral types
  ///////////////////////////////////////////////////////////////////////////////////////////////////
  BOOST_DISPATCH_OVERLOAD_IF( rec_
                            , (typename A0, typename X)
                            , (detail::is_native<X>)
                            , bd::cpu_
                            , bs::pack_<bd::uint_<A0>, X>
                            )
  {
    BOOST_FORCEINLINE A0 operator()( const A0& a0) const BOOST_NOEXCEPT
    {
      return if_else_allbits(a0, if_one_else_zero(a0 == One<A0>()));
    }
  };

  BOOST_DISPATCH_OVERLOAD_IF( rec_
                            , (typename A0, typename X)
                            , (detail::is_native<X>)
                            , bd::cpu_
                            , bs::pack_<bd::int_<A0>, X>
                            )
  {
    BOOST_FORCEINLINE A0 operator()( const A0& a0) const BOOST_NOEXCEPT
    {
      return if_else(a0, if_else_zero(bs::abs(a0) == One<A0>(), a0), Valmax<A0>());
    }
  };

  ///////////////////////////////////////////////////////////////////////////////////////////////////
  /// rec for floating types: take all version when no better exists reverting to division
  ///////////////////////////////////////////////////////////////////////////////////////////////////
  BOOST_DISPATCH_OVERLOAD( rec_
                         , (typename A0, typename X)
                         , bd::cpu_
                         , bs::pack_<bd::floating_<A0>, X>
                         )
  {
    BOOST_FORCEINLINE A0 operator()( const A0& a0) const BOOST_NOEXCEPT
    {
      return One<A0>()/a0;
    }
  };

  BOOST_DISPATCH_OVERLOAD_IF( rec_
                            , (typename T, typename X)
                            , (detail::is_native<X>)
                            , bd::cpu_
                            , bs::raw_tag
                            , bs::pack_<bd::unspecified_<T>, X>
                            )
  {
    BOOST_FORCEINLINE T operator()(const raw_tag &, T const& a) const BOOST_NOEXCEPT
    {
      return rec(a);
    }
  };
} } }

#endif


/* Copyright Institue of Sound and Vibration Research - All rights reserved. */

#include <librbbl/ipp_fft_wrapper.hpp>

#include <libefl/basic_vector.hpp>
#include <libefl/vector_functions.hpp>
#include <libvisr/constants.hpp>

#include <boost/test/unit_test.hpp>

#include <algorithm>
#include <complex>
#include <iostream>
#include <cstdio>
#include <memory>
#include <stdexcept>
#include <string>

namespace visr
{
namespace rbbl
{
namespace test
{

BOOST_AUTO_TEST_CASE( IppFftWrapperSingle )
{
  std::size_t dftSize = 8;
  std::size_t outputSize = dftSize / 2 + 1;

  IppFftWrapper<float> fft( dftSize, cVectorAlignmentSamples );

  efl::BasicVector<float> input( dftSize, cVectorAlignmentSamples );
  input[1] = 1.0f;

  efl::BasicVector<std::complex<float> > output( outputSize, cVectorAlignmentSamples );

  fft.forwardTransform( input.data(), output.data() );

  efl::BasicVector<float> resultAfterInverse( dftSize, cVectorAlignmentSamples );

  fft.inverseTransform( output.data(), resultAfterInverse.data() );
}

} // namespace test
} // namespace rbbl
} // namespace visr


#include "catch.hpp"

#include <boost/graph/graph_concepts.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/properties.hpp>

#include <arlib/details/arlib_utils.hpp>
#include <arlib/details/penalty_impl.hpp>
#include <arlib/graph_utils.hpp>
#include <arlib/penalty.hpp>
#include <arlib/routing_kernels/types.hpp>
#include <arlib/terminators.hpp>

#include "cittastudi_graph.hpp"
#include "test_types.hpp"
#include "utils.hpp"

#include <chrono>
#include <experimental/filesystem>
#include <memory>
#include <string>
#include <string_view>

using namespace arlib::test;

TEST_CASE("Penalty algorithm follows specifications", "[penalty]") {
  using namespace boost;

  auto G = arlib::read_graph_from_string<Graph>(std::string(graph_gr_esx));

  Vertex s = 0, t = 6;
  int k = 3;
  double theta = 0.5;
  auto p = 0.1;
  auto r = 0.1;
  auto bound_limit = 10;
  auto max_nb_steps = 100000;

  auto predecessors = arlib::multi_predecessor_map<Vertex>{};
  arlib::penalty(G, predecessors, s, t, k, theta, p, r, bound_limit,
                 max_nb_steps);
  auto res_paths = arlib::to_paths(G, predecessors, s, t);

  REQUIRE(
      alternative_paths_are_dissimilar(res_paths, get(edge_weight, G), theta));
  REQUIRE(res_paths.size() == 3);

  std::cout << "Penalty boost::Graph result:\n";
  for (auto &p : res_paths) {
    for (auto it = edges(p).first; it != edges(p).second; ++it) {
      std::cout << "(" << source(*it, p) << ", " << target(*it, p) << ") ";
    }
    std::cout << "\n";
  }
}

TEST_CASE("Graph penalization follows specifications", "[penalty]") {
  using namespace boost;
  using Edge = typename graph_traits<Graph>::edge_descriptor;

  auto G = arlib::read_graph_from_string<Graph>(std::string(graph_gr_esx));

  // Candidate solution
  auto candidate = std::vector<Edge>{edge(0, 3, G).first, edge(3, 5, G).first,
                                     edge(5, 6, G).first};

  // Distance maps
  auto distance_s = std::vector<Length>{0, 5, 4, 3, 7, 6, 8};
  auto distance_t = std::vector<Length>{8, 6, 7, 5, 2, 2, 0};

  // Weight map
  auto original_weight = get(edge_weight, G);
  auto penalty = arlib::details::penalty_functor{original_weight};

  // Penalty bounds
  auto penalty_bounds = std::unordered_map<Edge, int, boost::hash<Edge>>{};
  Vertex s = 0, t = 6;

  SECTION("Penalizing the first candidate updates correct edges weights",
          "[penalty]") {
    auto p = 0.1;
    auto r = 0.1;
    auto bound_limit = 1;

    auto old_penalty = penalty.clone();

    arlib::details::penalize_candidate_path(candidate, G, s, t, p, r, penalty,
                                            distance_s, distance_t,
                                            penalty_bounds, bound_limit);

    // Keep track of candidate edges to exclude them from incoming/outgoing
    // edges update
    auto candidate_edges = std::unordered_set<Edge, boost::hash<Edge>>{};
    auto candidate_vertices = std::unordered_set<Vertex, boost::hash<Vertex>>{};

    // Candidate path weights have been updated
    for (auto e : candidate) {
      candidate_edges.insert(e);

      auto u = source(e, G);
      auto v = target(e, G);
      candidate_vertices.insert(u);
      candidate_vertices.insert(v);

      // Weight penalization is correct
      REQUIRE(penalty[e] == old_penalty[e] + old_penalty[e] * p);

      // A nb of updates counter have been created
      auto search = penalty_bounds.find(e);
      REQUIRE(search != std::end(penalty_bounds));
      REQUIRE(search->second == 1);
    }

    // Incoming edges weights have been updated
    for (auto e : candidate_edges) {
      auto u = source(e, G);
      auto v = target(e, G);

      for (auto [it, end] = in_edges(u, G); it != end; ++it) {
        auto a = source(*it, G);

        if (candidate_vertices.find(a) == std::end(candidate_vertices)) {
          auto closeness = distance_t[u] / distance_t[s];
          REQUIRE(penalty[*it] ==
                  old_penalty[*it] + old_penalty[*it] * (0.1 + r * closeness));
        }
      }

      for (auto [it, end] = out_edges(v, G); it != end; ++it) {
        auto b = target(*it, G);

        if (candidate_vertices.find(b) == std::end(candidate_vertices)) {
          auto closeness = distance_s[v] / distance_s[t];
          REQUIRE(penalty[*it] ==
                  old_penalty[*it] + old_penalty[*it] * (0.1 + r * closeness));
        }
      }
    }
  }

  SECTION("Penalizing edges that reached the limit of number of updates leave "
          "their weights unchanged",
          "[penalty]") {
    auto p = 0.1;
    auto r = 0.1;
    auto bound_limit = 1;

    auto [e1, is_e1_valid] = edge(3, 5, G);
    auto [e2, is_e2_valid] = edge(1, 6, G);

    REQUIRE(is_e1_valid);
    REQUIRE(is_e2_valid);

    // Set e1 and e2 updates limit
    penalty_bounds.insert({e1, bound_limit});
    penalty_bounds.insert({e2, bound_limit});

    auto old_penalty = arlib::details::penalty_functor{penalty};

    arlib::details::penalize_candidate_path(candidate, G, s, t, p, r, penalty,
                                            distance_s, distance_t,
                                            penalty_bounds, bound_limit);

    // Weights must be left unchanged.
    REQUIRE(penalty[e1] == old_penalty[e1]);
    REQUIRE(penalty[e2] == old_penalty[e2]);
  }
}

TEST_CASE("Two-ways dijkstra computes right distance maps", "[penalty]") {
  using namespace boost;
  using Edge = typename graph_traits<Graph>::edge_descriptor;

  auto G = arlib::read_graph_from_string<Graph>(std::string(graph_gr_esx));
  Vertex s = 0, t = 6;

  // Candidate solution
  auto candidate = std::vector<Edge>{edge(0, 3, G).first, edge(3, 5, G).first,
                                     edge(5, 6, G).first};

  // Distance maps
  auto distance_s = std::vector<Length>{0, 5, 4, 3, 7, 6, 8};
  auto distance_t = std::vector<Length>{8, 6, 7, 5, 2, 2, 0};

  auto test_distance_s = std::vector<Length>(num_vertices(G));
  auto test_distance_t = std::vector<Length>(num_vertices(G));

  arlib::details::dijkstra_shortest_path_two_ways(G, s, t, test_distance_s,
                                                  test_distance_t);

  REQUIRE(distance_s == test_distance_s);
  REQUIRE(distance_t == test_distance_t);
}

TEST_CASE("Bidirectional dijkstra works with reverse penalty functor adaptor",
          "[penalty]") {
  using namespace boost;

  auto G = arlib::read_graph_from_string<Graph>(std::string(graph_gr_esx));
  Vertex s = 0, t = 6;

  // Make a local weight map to avoid modifying existing graph.
  auto original_weight = get(edge_weight, G);
  auto penalty = arlib::details::penalty_functor{original_weight};

  // Forward Dijkstra sp
  auto sp_forward = arlib::details::dijkstra_shortest_path(G, s, t, penalty);
  REQUIRE(sp_forward);

  // Bi-Dijkstra sp
  auto sp_bi =
      arlib::details::bidirectional_dijkstra_shortest_path(G, s, t, penalty);
  REQUIRE(sp_bi);

  REQUIRE(*sp_forward == *sp_bi);
}

TEST_CASE("Penalty running with bidirectional dijkstra returns same result as "
          "unidirectional dijkstra",
          "[penalty]") {
  using namespace boost;

  auto G = arlib::read_graph_from_string<Graph>(std::string(graph_gr_esx));

  Vertex s = 0, t = 6;
  int k = 3;
  double theta = 0.5;
  auto p = 0.1;
  auto r = 0.1;
  auto bound_limit = 10;
  auto max_nb_steps = 100000;

  auto predecessors_uni = arlib::multi_predecessor_map<Vertex>{};
  arlib::penalty(G, predecessors_uni, s, t, k, theta, p, r, bound_limit,
                 max_nb_steps);
  auto res_paths_uni = arlib::to_paths(G, predecessors_uni, s, t);

  auto predecessors_bi = arlib::multi_predecessor_map<Vertex>{};
  arlib::penalty(G, predecessors_bi, s, t, k, theta, p, r, bound_limit,
                 max_nb_steps, arlib::routing_kernels::bidirectional_dijkstra);
  auto res_paths_bi = arlib::to_paths(G, predecessors_bi, s, t);

  REQUIRE(res_paths_uni.size() == res_paths_bi.size());

  for (std::size_t i = 0; i < res_paths_uni.size(); ++i) {
    REQUIRE(res_paths_uni[i].length() == res_paths_bi[i].length());
  }
}

TEST_CASE("Penalty running with astar returns same result as "
          "unidirectional dijkstra",
          "[penalty]") {
  using namespace boost;

  auto G = arlib::read_graph_from_string<Graph>(std::string(graph_gr_esx));

  Vertex s = 0, t = 6;
  int k = 3;
  double theta = 0.5;
  auto p = 0.1;
  auto r = 0.1;
  auto bound_limit = 10;
  auto max_nb_steps = 100000;

  auto predecessors_uni = arlib::multi_predecessor_map<Vertex>{};
  arlib::penalty(G, predecessors_uni, s, t, k, theta, p, r, bound_limit,
                 max_nb_steps);
  auto res_paths_uni = arlib::to_paths(G, predecessors_uni, s, t);

  auto predecessors_bi = arlib::multi_predecessor_map<Vertex>{};
  arlib::penalty(G, predecessors_bi, s, t, k, theta, p, r, bound_limit,
                 max_nb_steps, arlib::routing_kernels::astar);
  auto res_paths_bi = arlib::to_paths(G, predecessors_bi, s, t);

  REQUIRE(res_paths_uni.size() == res_paths_bi.size());

  for (std::size_t i = 0; i < res_paths_uni.size(); ++i) {
    REQUIRE(res_paths_uni[i].length() == res_paths_bi[i].length());
  }
}

TEST_CASE("Penalty times-out on large graph", "[penalty]") {
  using namespace boost;
  using namespace std::chrono_literals;

  auto G = arlib::read_graph_from_string<Graph>(std::string(cittastudi_gr));

  Vertex s = 0, t = 20;
  auto k = 3;
  auto theta = 0.5;
  auto p = 0.1;
  auto r = 0.1;
  auto max_nb_updates = 10;
  auto max_nb_steps = 10000;
  auto predecessors = arlib::multi_predecessor_map<Vertex>{};

  REQUIRE_THROWS_AS(arlib::penalty(G, predecessors, s, t, k, theta, p, r,
                                   max_nb_updates, max_nb_steps,
                                   arlib::routing_kernels::astar,
                                   arlib::timer{1us}),
                    arlib::terminator_stop_error);
}


#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <map>
#include <boost/program_options.hpp>
#include "encoding.h"

using namespace std;
using namespace boost::program_options;
using namespace encoding;

struct SebException : exception {
  string msg;

  SebException(const string& message) {
    msg = message;
  }

  const char* what() const noexcept {
    return msg.c_str();
  }
};

string encrypt(const string& plain, const string& key, const Mapper& m) {
  string cipher;
  vector<uint32_t> ucipher;
  for (auto tchr = utf8_begin(plain), kchr=utf8_begin(key);
      tchr != utf8_end(plain); tchr++, kchr++) {
    if (kchr == utf8_end(key)) kchr = utf8_begin(key);
    uint32_t charId = m.charToInt(*tchr);
    uint32_t keyId = m.charToInt(*kchr);
    int cipherId = (charId + keyId) % m.size();
    ucipher.push_back(m.intToChar(cipherId));
  }
  utf8::utf32to8(ucipher.begin(),ucipher.end(),back_inserter(cipher));
  return cipher;
}

string decrypt(const string& cipher, const string& key, const Mapper& m) {
  string plain;
  vector<uint32_t> uplain;
  for (auto tchr = utf8_begin(cipher), kchr=utf8_begin(key);
      tchr != utf8_end(cipher); tchr++, kchr++) {
    if (kchr == utf8_end(key)) kchr = utf8_begin(key);
    uint32_t charId = m.charToInt(*tchr);
    uint32_t keyId = m.charToInt(*kchr);
    int cipherId = (charId - keyId + m.size()) % m.size();
    uplain.push_back(m.intToChar(cipherId));
  }
  utf8::utf32to8(uplain.begin(),uplain.end(),back_inserter(plain));
  return plain;
}

int main(int argc, char **argv) {
  try {
    string alphabet = "alfabeto.txt";
    string key, text_file;
    bool do_decrypt = false;
    bool ignore_unknown = false;
    options_description desc("Allowed options");
    desc.add_options()
      ("help,h", "print help message")
      ("alphabet,a", value(&alphabet), "text file containing the alphabet")
      ("key,k", value(&key), "key for vigenere")
      ("decrypt,d", bool_switch(&do_decrypt), "decrypt text instead of encrypting it")
      ("ignore,i", bool_switch(&ignore_unknown), "ignore unknown symbols of the alphabet");

    variables_map vm;
    store(parse_command_line(argc, argv,desc), vm);
    notify(vm);
    if (vm.count("help")) {
      cout << desc << endl;
      return 0;
    }
    if (vm.count("key") == 0) {
      cerr << "No key was given. Try --help to see options" << endl;
      return 1;
    }

    Mapper m(alphabet);
    string text, output;
    char ch;
    while ( (ch = cin.get()) != EOF ) {
      text.push_back(ch);
    }
    if (text.back() == '\n') text.pop_back();
    if (!ignore_unknown) {
      for (auto ch = utf8_begin(text); ch != utf8_end(text); ch++) {
        if (!m.containsChr(*ch)) {
          cerr << "The character Unicode=" << *ch << " of the input text is not in the alphabet" << endl;
          return 1;
        }
      }
    }

    if (!do_decrypt) {
      output = encrypt(text, key, m);
    } else {
      output = decrypt(text, key, m);
    }
    cout << output << endl;
  } catch (exception& e) {
    cerr << e.what() << endl;
  }
}


#ifndef CAFFE_LIBDNN_LIBDNN_BLAS_HPP_
#define CAFFE_LIBDNN_LIBDNN_BLAS_HPP_

#ifdef USE_LIBDNN

#include <boost/thread.hpp>
#include <boost/thread/locks.hpp>
#include <boost/thread/shared_mutex.hpp>
#include <unordered_map>

#include "caffe/common.hpp"
#include "caffe/definitions.hpp"
#include "caffe/quantizer.hpp"
#include "caffe/libdnn/libdnn.hpp"
#include "caffe/util/type_utils.hpp"
namespace caffe {

template<typename MItype, typename MOtype>
class LibDNNBlas : public LibDNN<MItype, MOtype> {
 public:
  explicit LibDNNBlas(Device* dev_ptr);

  // BLAS 1
  void axpby(const uint_tp n, const MItype alpha, vptr<const MItype> x,
             const MOtype beta, vptr<MOtype> y,
             const QuantizerValues* const alpha_quant = nullptr,
             const QuantizerValues* const x_quant = nullptr,
             const QuantizerValues* const beta_quant = nullptr,
             const QuantizerValues* const y_quant = nullptr);
  void scal(const uint_tp n, const MItype alpha, vptr<MItype> x,
            const QuantizerValues* const alpha_quant = nullptr,
            const QuantizerValues* const x_quant = nullptr);
  void dot(const uint_tp n, vptr<const MItype> x, vptr<const MItype> y,
           MOtype* out,
           const QuantizerValues* const x_quant = nullptr,
           const QuantizerValues* const y_quant = nullptr,
           const QuantizerValues* const out_quant = nullptr);
  void asum(const uint_tp n, vptr<const MItype> x, MOtype* y,
            const QuantizerValues* const x_quant = nullptr,
            const QuantizerValues* const y_quant = nullptr);
  void scale(const uint_tp n, const MItype alpha, vptr<const MItype> x,
             vptr<MOtype> y,
             const QuantizerValues* const alpha_quant = nullptr,
             const QuantizerValues* const x_quant = nullptr,
             const QuantizerValues* const y_quant = nullptr);

  // BLAS 2
  void gemv(const CBLAS_TRANSPOSE trans_A, const uint_tp M, const uint_tp N,
            const MOtype alpha, vptr<const MItype> A, vptr<const MItype> x,
            const MOtype beta, vptr<MOtype> y,
            const QuantizerValues* const alpha_quant = nullptr,
            const QuantizerValues* const a_quant = nullptr,
            const QuantizerValues* const x_quant = nullptr,
            const QuantizerValues* const beta_quant = nullptr,
            const QuantizerValues* const y_quant = nullptr);

  // BLAS 3
  void gemm(const CBLAS_TRANSPOSE trans_A, const CBLAS_TRANSPOSE trans_B,
            const uint_tp M, const uint_tp N, const uint_tp K,
            const MOtype alpha, vptr<const MItype> A, vptr<const MItype> B,
            const MOtype beta, vptr<MOtype> C,
            const QuantizerValues* const alpha_quant = nullptr,
            const QuantizerValues* const a_quant = nullptr,
            const QuantizerValues* const b_quant = nullptr,
            const QuantizerValues* const beta_quant = nullptr,
            const QuantizerValues* const c_quant = nullptr);

  void gemm_dropout(const CBLAS_TRANSPOSE trans_A, const CBLAS_TRANSPOSE trans_B,
            const uint_tp M, const uint_tp N, const uint_tp K,
            const MOtype alpha, vptr<const MItype> A, vptr<const MItype> B,
            const MOtype beta, vptr<MOtype> C,
            vptr<const uint8_t> dropout, float scale, DropoutType type,
            const QuantizerValues* const alpha_quant = nullptr,
            const QuantizerValues* const a_quant = nullptr,
            const QuantizerValues* const b_quant = nullptr,
            const QuantizerValues* const beta_quant = nullptr,
            const QuantizerValues* const c_quant = nullptr);

 private:
  /*The same string_identifier means the same DeviceProgram, and this map mechanism reduces the time cost to CreateProgram*/
  int_tp get_id(string identifier);
  int_tp get_id_or_new(string identifier);

  string generate_scale_source(shared_ptr<DeviceProgram> program,
                               shared_ptr<LibDNNTuner> tuner);
  string scale_string_identifier();

  string generate_axpby_source(shared_ptr<DeviceProgram> program,
                               shared_ptr<LibDNNTuner> tuner);
  string axpby_string_identifier();

  string generate_dot_source(shared_ptr<DeviceProgram> program,
                             shared_ptr<LibDNNTuner> tuner);
  string dot_string_identifier();

  string generate_asum_source(shared_ptr<DeviceProgram> program,
                              shared_ptr<LibDNNTuner> tuner);
  string asum_string_identifier();

  string generate_gemv_source(
         shared_ptr<DeviceProgram> program, shared_ptr<LibDNNTuner> tuner,
         bool trans_A, const uint_tp M, const uint_tp N,
         bool alpha_term, bool alpha_exactly_one,
         bool beta_term, bool beta_exactly_one);
  string gemv_string_identifier(const CBLAS_TRANSPOSE trans_A,
         const uint_tp M, const uint_tp N,
         bool alpha_term, bool alpha_exactly_one,
         bool beta_term, bool beta_exactly_one);
  void initialize_gemv_tuner(shared_ptr<DeviceProgram> program,
                             shared_ptr<LibDNNTuner> tuner);

  string generate_gemm_source(shared_ptr<DeviceProgram> program,
         shared_ptr<LibDNNTuner> tuner, bool trans_A, bool trans_B,
         const uint_tp M, const uint_tp N, const uint_tp K,
         bool alpha_term, bool alpha_exactly_one,
         bool beta_term, bool beta_exactly_one);
  string gemm_string_identifier(const CBLAS_TRANSPOSE trans_A,
         const CBLAS_TRANSPOSE trans_B, const uint_tp M, const uint_tp N,
         const uint_tp K, bool alpha_term, bool alpha_exactly_one,
         bool beta_term, bool beta_exactly_one);
  void initialize_gemm_tuner(shared_ptr<DeviceProgram> program,
         shared_ptr<LibDNNTuner> tuner);
  string generate_gemm_dropout_source(shared_ptr<DeviceProgram> program,
         shared_ptr<LibDNNTuner> tuner, bool trans_A, bool trans_B,
         const uint_tp M, const uint_tp N, const uint_tp K,
         bool alpha_term, bool alpha_exactly_one,
         bool beta_term, bool beta_exactly_one,
         float scale, DropoutType type);

  boost::shared_mutex program_mutex_;
  vector<bool> program_ready_;
  vector<shared_ptr<LibDNNTuner> > program_tuners_;
  vector<shared_ptr<DeviceProgram> > programs_;
  std::unordered_map<string, size_t> program_map_;
};

}  // namespace caffe

#endif  // USE_LIBDNN

#endif  // CAFFE_LIBDNN_LIBDNN_BLAS_HPP_


//---------------------------------------------------------------------------//
//!
//! \file   tstEvaporationDistribution.cpp
//! \author Aaron Tumulak
//! \brief  Evaporation distribution unit tests. Unit-aware unit tests
//!         added by Alex Robinson.
//!
//---------------------------------------------------------------------------//

// Std Lib Includes
#include <iostream>

// Boost Includes
#include <boost/units/systems/si.hpp>
#include <boost/units/systems/cgs.hpp>
#include <boost/units/io.hpp>

// FRENSIE Includes
#include "Utility_EvaporationDistribution.hpp"
#include "Utility_PhysicalConstants.hpp"
#include "Utility_RandomNumberGenerator.hpp"
#include "Utility_UnitTraits.hpp"
#include "Utility_QuantityTraits.hpp"
#include "Utility_ElectronVoltUnit.hpp"
#include "Utility_UnitTestHarnessWithMain.hpp"
#include "ArchiveTestHelpers.hpp"

//---------------------------------------------------------------------------//
// Testing Types
//---------------------------------------------------------------------------//

using boost::units::quantity;
using namespace Utility::Units;
namespace si = boost::units::si;
namespace cgs = boost::units::cgs;

typedef TestArchiveHelper::TestArchives TestArchives;

typedef std::tuple<
  std::tuple<si::energy,si::amount,cgs::energy,si::amount>,
  std::tuple<cgs::energy,si::amount,si::energy,si::amount>,
  std::tuple<si::energy,si::length,cgs::energy,cgs::length>,
  std::tuple<cgs::energy,cgs::length,si::energy,si::length>,
  std::tuple<si::energy,si::mass,cgs::energy,cgs::mass>,
  std::tuple<cgs::energy,cgs::mass,si::energy,si::mass>,
  std::tuple<si::energy,si::dimensionless,cgs::energy,cgs::dimensionless>,
  std::tuple<cgs::energy,cgs::dimensionless,si::energy,si::dimensionless>,
  std::tuple<si::energy,void*,cgs::energy,void*>,
  std::tuple<cgs::energy,void*,si::energy,void*>,
  std::tuple<ElectronVolt,si::amount,si::energy,si::amount>,
  std::tuple<ElectronVolt,si::amount,cgs::energy,si::amount>,
  std::tuple<ElectronVolt,si::amount,KiloElectronVolt,si::amount>,
  std::tuple<ElectronVolt,si::amount,MegaElectronVolt,si::amount>,
  std::tuple<KiloElectronVolt,si::amount,si::energy,si::amount>,
  std::tuple<KiloElectronVolt,si::amount,cgs::energy,si::amount>,
  std::tuple<KiloElectronVolt,si::amount,ElectronVolt,si::amount>,
  std::tuple<KiloElectronVolt,si::amount,MegaElectronVolt,si::amount>,
  std::tuple<MegaElectronVolt,si::amount,si::energy,si::amount>,
  std::tuple<MegaElectronVolt,si::amount,cgs::energy,si::amount>,
  std::tuple<MegaElectronVolt,si::amount,ElectronVolt,si::amount>,
  std::tuple<MegaElectronVolt,si::amount,KiloElectronVolt,si::amount>,
  std::tuple<void*,MegaElectronVolt,void*,KiloElectronVolt>
 > TestUnitTypeQuads;

//---------------------------------------------------------------------------//
// Testing Variables
//---------------------------------------------------------------------------//

std::shared_ptr<Utility::UnivariateDistribution> distribution(
                       new Utility::EvaporationDistribution( 1.0, 1.0, 0.1 ) );

std::shared_ptr<Utility::UnitAwareUnivariateDistribution<MegaElectronVolt,si::amount> >
  unit_aware_distribution( new Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount>( 1e3*keV, 1e3*keV, 100.0*keV ) );

//---------------------------------------------------------------------------//
// Tests.
//---------------------------------------------------------------------------//
// Check that the distribution can be evaluated
FRENSIE_UNIT_TEST( EvaporationDistribution, evaluate )
{
  double test_value_1 = 0.0 ;
  double test_value_2 = 0.9*exp( -0.9 );

  FRENSIE_CHECK_EQUAL( distribution->evaluate( 0.0 ), test_value_1, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 0.9 ), test_value_2, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 1.0 ), test_value_1, SHOW_LHS );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be evaluated
FRENSIE_UNIT_TEST( UnitAwareEvaporationDistribution, evaluate )
{
  double scale_factor = 0.9*exp(-0.9);

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.0*MeV ),
		       0.0*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.9*MeV ),
                       scale_factor*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 1.0*MeV ),
		       0.0*si::mole );
}

//---------------------------------------------------------------------------//
// Check that the PDF can be evaluated
FRENSIE_UNIT_TEST( EvaporationDistribution, evaluatePDF )
{
  double test_value_1 = 0.0 ;
  double test_value_2 = 0.9*exp(-0.9)/(1.0 - exp(-0.9)*(1.0+0.9));

  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 0.0 ), test_value_1 );
  FRENSIE_CHECK_FLOATING_EQUALITY( distribution->evaluatePDF( 0.9 ),
                          test_value_2,
                          1e-15 );
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 1.0 ), test_value_1 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware PDF can be evaluated
FRENSIE_UNIT_TEST( UnitAwareEvaporationDistribution, evaluatePDF )
{
  double scale_factor = 0.9*exp(-0.9)/(1.0 - exp(-0.9)*(1.0+0.9));

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( 0.0*MeV ),
		       0.0/MeV );
  FRENSIE_CHECK_FLOATING_EQUALITY( unit_aware_distribution->evaluatePDF( 0.9*MeV ),
                                  scale_factor/MeV,
                                  1e-15 );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( 1.0*MeV ),
                       0.0/MeV );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled using OpenMC method
FRENSIE_UNIT_TEST( EvaporationDistribution, sample )
{
  std::vector<double> fake_stream( 6 );
  fake_stream[0] = 0.8388165507412660;
  fake_stream[1] = 0.0267354287196134;
  fake_stream[2] = 0.9615425535640773;
  fake_stream[3] = 0.9996969006924931;
  fake_stream[4] = 0.1891183747157897;
  fake_stream[5] = 0.8650163759473460;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double sample = distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.70470821836401, 1e-14 );

  sample = distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.83920374256297, 1e-14 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that unit-aware distribution can be sampled using the OpenMC method
FRENSIE_UNIT_TEST( UnitAwareEvaporationDistribution, sample )
{
  std::vector<double> fake_stream( 6 );
  fake_stream[0] = 0.8388165507412660;
  fake_stream[1] = 0.0267354287196134;
  fake_stream[2] = 0.9615425535640773;
  fake_stream[3] = 0.9996969006924931;
  fake_stream[4] = 0.1891183747157897;
  fake_stream[5] = 0.8650163759473460;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<MegaElectronVolt> sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.70470821836401*MeV, 1e-14 );

  sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.83920374256297*MeV, 1e-14 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled using OpenMC method, and the
// trials can be recorded
FRENSIE_UNIT_TEST( EvaporationDistribution, sample_pass_parameters )
{
  std::vector<double> fake_stream( 6 );
  fake_stream[0] = 0.7109904185738904;
  fake_stream[1] = 0.1535897705184878;
  fake_stream[2] = 0.6915260327666102;
  fake_stream[3] = 0.6291257012282162;
  fake_stream[4] = 0.3183560295023423;
  fake_stream[5] = 0.7492388871308626;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double incident_energy, nuclear_temperature, restriction_energy, sample;

  incident_energy = 0.5;
  nuclear_temperature = 0.1;
  restriction_energy = 0.01;

  sample = Utility::EvaporationDistribution::sample( incident_energy, nuclear_temperature, restriction_energy );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.13885427138954, 1e-13 );

  incident_energy = 0.75;
  nuclear_temperature = 0.5;
  restriction_energy = 0.25;

  sample = Utility::EvaporationDistribution::sample( incident_energy, nuclear_temperature, restriction_energy );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.43320278283758, 1e-13 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled using OpenMC method,
// and the trials can be recorded
FRENSIE_UNIT_TEST( UnitAwareEvaporationDistribution, sample_pass_parameters )
{
  std::vector<double> fake_stream( 6 );
  fake_stream[0] = 0.7109904185738904;
  fake_stream[1] = 0.1535897705184878;
  fake_stream[2] = 0.6915260327666102;
  fake_stream[3] = 0.6291257012282162;
  fake_stream[4] = 0.3183560295023423;
  fake_stream[5] = 0.7492388871308626;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<MegaElectronVolt> incident_energy, nuclear_temperature,
    restriction_energy, sample;

  incident_energy = 0.5*MeV;
  nuclear_temperature = 0.1*MeV;
  restriction_energy = 0.01*MeV;

  sample = Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount>::sample( incident_energy, nuclear_temperature, restriction_energy );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.13885427138954*MeV, 1e-13 );

  incident_energy = 0.75*MeV;
  nuclear_temperature = 0.5*MeV;
  restriction_energy = 0.25*MeV;

  sample = Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount>::sample( incident_energy, nuclear_temperature, restriction_energy );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.43320278283758*MeV, 1e-13 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled using OpenMC method, and the
// trials can be recorded
FRENSIE_UNIT_TEST( EvaporationDistribution, sampleAndRecordTrials_pass_parameters )
{
  std::vector<double> fake_stream( 6 );
  fake_stream[0] = 0.7109904185738904;
  fake_stream[1] = 0.1535897705184878;
  fake_stream[2] = 0.6915260327666102;
  fake_stream[3] = 0.6291257012282162;
  fake_stream[4] = 0.3183560295023423;
  fake_stream[5] = 0.7492388871308626;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double incident_energy, nuclear_temperature, restriction_energy, sample;
  Utility::DistributionTraits::Counter trials = 0;

  incident_energy = 0.5;
  nuclear_temperature = 0.1;
  restriction_energy = 0.01;

  sample = Utility::EvaporationDistribution::sampleAndRecordTrials(incident_energy, nuclear_temperature, restriction_energy, trials);
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.13885427138954, 1e-13 );
  FRENSIE_CHECK_EQUAL( trials, 1.0 );

  incident_energy = 0.75;
  nuclear_temperature = 0.5;
  restriction_energy = 0.25;

  sample = Utility::EvaporationDistribution::sampleAndRecordTrials(incident_energy, nuclear_temperature, restriction_energy, trials);
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.43320278283758, 1e-13 );
  FRENSIE_CHECK_EQUAL( trials, 3.0 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled using OpenMC method,
// and the trials can be recorded
FRENSIE_UNIT_TEST( UnitAwareEvaporationDistribution,
		   sampleAndRecordTrials_pass_parameters )
{
  std::vector<double> fake_stream( 6 );
  fake_stream[0] = 0.7109904185738904;
  fake_stream[1] = 0.1535897705184878;
  fake_stream[2] = 0.6915260327666102;
  fake_stream[3] = 0.6291257012282162;
  fake_stream[4] = 0.3183560295023423;
  fake_stream[5] = 0.7492388871308626;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<MegaElectronVolt> incident_energy, nuclear_temperature,
    restriction_energy, sample;
  Utility::DistributionTraits::Counter trials = 0;

  incident_energy = 0.5*MeV;
  nuclear_temperature = 0.1*MeV;
  restriction_energy = 0.01*MeV;

  sample = Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount>::sampleAndRecordTrials(incident_energy, nuclear_temperature, restriction_energy, trials);
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.13885427138954*MeV, 1e-13 );
  FRENSIE_CHECK_EQUAL( trials, 1.0 );

  incident_energy = 0.75*MeV;
  nuclear_temperature = 0.5*MeV;
  restriction_energy = 0.25*MeV;

  sample = Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount>::sampleAndRecordTrials(incident_energy, nuclear_temperature, restriction_energy, trials);
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.43320278283758*MeV, 1e-13 );
  FRENSIE_CHECK_EQUAL( trials, 3.0 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the upper bound of the distribution independent variable can be
// returned
FRENSIE_UNIT_TEST( EvaporationDistribution, getUpperBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( distribution->getUpperBoundOfIndepVar(), 0.9 );
}

//---------------------------------------------------------------------------//
// Check that the upper bound of the unit-aware distribution independent
// variable can be returned
FRENSIE_UNIT_TEST( UnitAwareEvaporationDistribution, getUpperBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getUpperBoundOfIndepVar(),
		       0.9*MeV );
}

//---------------------------------------------------------------------------//
// Check that the lower bound of the distribution independent variable can be
// returned
FRENSIE_UNIT_TEST( EvaporationDistribution, getLowerBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( distribution->getLowerBoundOfIndepVar(), 0.0 );
}

//---------------------------------------------------------------------------//
// Check that the lower bound of the unit-aware distribution independent
// variable can be returned
FRENSIE_UNIT_TEST( UnitAwareEvaporationDistribution, getLowerBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getLowerBoundOfIndepVar(),
		       0.0*MeV );
}

//---------------------------------------------------------------------------//
// Check that the distribution type can be returned
FRENSIE_UNIT_TEST( EvaporationDistribution, getDistributionType )
{
  FRENSIE_CHECK_EQUAL( distribution->getDistributionType(),
		       Utility::EVAPORATION_DISTRIBUTION );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution type can be returned
FRENSIE_UNIT_TEST( UnitAwareEvaporationDistribution, getDistributionType )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getDistributionType(),
		       Utility::EVAPORATION_DISTRIBUTION );
}

//---------------------------------------------------------------------------//
// Check if the distribution is tabular
FRENSIE_UNIT_TEST( EvaporationDistribution, isTabular )
{
  FRENSIE_CHECK( !distribution->isTabular() );
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is tabular
FRENSIE_UNIT_TEST( UnitAwareEvaporationDistribution, isTabular )
{
  FRENSIE_CHECK( !unit_aware_distribution->isTabular() );
}

//---------------------------------------------------------------------------//
// Check if the distribution is continuous
FRENSIE_UNIT_TEST( EvaporationDistribution, isContinuous )
{
  FRENSIE_CHECK( distribution->isContinuous() );
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is continuous
FRENSIE_UNIT_TEST( UnitAwareEvaporationDistribution, isContinuous )
{
  FRENSIE_CHECK( unit_aware_distribution->isContinuous() );
}

//---------------------------------------------------------------------------//
// Check if the distribution is compatible with the interpolation type
FRENSIE_UNIT_TEST( EvaporationDistribution, isCompatibleWithInterpType )
{
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinLin>() );
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogLog>() );
}

//---------------------------------------------------------------------------//
// Check if the distribution is compatible with the interpolation type
FRENSIE_UNIT_TEST( UnitAwareEvaporationDistribution,
                   isCompatibleWithInterpType )
{
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLin>() );
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLog>() );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be placed in a stream
FRENSIE_UNIT_TEST( EvaporationDistribution, ostream_operator )
{
  std::ostringstream oss;

  oss << Utility::EvaporationDistribution();

  Utility::VariantMap dist_data =
    Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Evaporation Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toDouble(), 1.0 );
  FRENSIE_CHECK_EQUAL( dist_data["nuclear temp"].toDouble(), 1.0 );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toDouble(), 0.0 );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0 );

  oss.str( "" );
  oss.clear();

  oss << Utility::EvaporationDistribution( 2.0 );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Evaporation Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toDouble(), 2.0 );
  FRENSIE_CHECK_EQUAL( dist_data["nuclear temp"].toDouble(), 1.0 );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toDouble(), 0.0 );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0 );

  oss.str( "" );
  oss.clear();

  oss << Utility::EvaporationDistribution( 3.0, 2.0 );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Evaporation Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toDouble(), 3.0 );
  FRENSIE_CHECK_EQUAL( dist_data["nuclear temp"].toDouble(), 2.0 );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toDouble(), 0.0 );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0 );

  oss.str( "" );
  oss.clear();

  oss << Utility::EvaporationDistribution( 3.0, 2.0, 1.0 );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Evaporation Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toDouble(), 3.0 );
  FRENSIE_CHECK_EQUAL( dist_data["nuclear temp"].toDouble(), 2.0 );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toDouble(), 1.0 );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0 );

  oss.str( "" );
  oss.clear();

  oss << Utility::EvaporationDistribution( 3.0, 2.0, 1.0, 10.0 );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Evaporation Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toDouble(), 3.0 );
  FRENSIE_CHECK_EQUAL( dist_data["nuclear temp"].toDouble(), 2.0 );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toDouble(), 1.0 );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 10.0 );

  oss.str( "" );
  oss.clear();

  oss << *distribution;

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Evaporation Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toDouble(), 1.0 );
  FRENSIE_CHECK_EQUAL( dist_data["nuclear temp"].toDouble(), 1.0 );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toDouble(), 0.1 );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be placed in a stream
FRENSIE_UNIT_TEST( UnitAwareEvaporationDistribution, ostream_operator )
{
  std::ostringstream oss;

  oss << Utility::UnitAwareEvaporationDistribution<ElectronVolt,si::amount>();

  Utility::VariantMap dist_data =
    Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Evaporation Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<ElectronVolt>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toType<quantity<ElectronVolt> >(),
                       1.0*eV );
  FRENSIE_CHECK_EQUAL( dist_data["nuclear temp"].toType<quantity<ElectronVolt> >(),
                       1.0*eV );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toType<quantity<ElectronVolt> >(),
                       0.0*eV );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0 );

  oss.str( "" );
  oss.clear();

  oss << Utility::UnitAwareEvaporationDistribution<ElectronVolt,si::amount>( 2.0*eV );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Evaporation Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<ElectronVolt>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toType<quantity<ElectronVolt> >(),
                       2.0*eV );
  FRENSIE_CHECK_EQUAL( dist_data["nuclear temp"].toType<quantity<ElectronVolt> >(),
                       1.0*eV );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toType<quantity<ElectronVolt> >(),
                       0.0*eV );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0 );

  oss.str( "" );
  oss.clear();

  oss << Utility::UnitAwareEvaporationDistribution<ElectronVolt,si::amount>( 3.0*eV, 2.0*eV );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Evaporation Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<ElectronVolt>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toType<quantity<ElectronVolt> >(),
                       3.0*eV );
  FRENSIE_CHECK_EQUAL( dist_data["nuclear temp"].toType<quantity<ElectronVolt> >(),
                       2.0*eV );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toType<quantity<ElectronVolt> >(),
                       0.0*eV );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0 );

  oss.str( "" );
  oss.clear();

  oss << Utility::UnitAwareEvaporationDistribution<ElectronVolt,si::amount>( 3.0*eV, 2.0*eV, 1.0*eV );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Evaporation Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<ElectronVolt>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toType<quantity<ElectronVolt> >(),
                       3.0*eV );
  FRENSIE_CHECK_EQUAL( dist_data["nuclear temp"].toType<quantity<ElectronVolt> >(),
                       2.0*eV );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toType<quantity<ElectronVolt> >(),
                       1.0*eV );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0 );

  oss.str( "" );
  oss.clear();

  oss << Utility::UnitAwareEvaporationDistribution<ElectronVolt,si::amount>( 3.0*eV, 2.0*eV, 1.0*eV, 10.0 );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Evaporation Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<ElectronVolt>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toType<quantity<ElectronVolt> >(),
                       3.0*eV );
  FRENSIE_CHECK_EQUAL( dist_data["nuclear temp"].toType<quantity<ElectronVolt> >(),
                       2.0*eV );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toType<quantity<ElectronVolt> >(),
                       1.0*eV );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 10.0 );

  oss.str( "" );
  oss.clear();

  oss << *unit_aware_distribution;

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Evaporation Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<MegaElectronVolt>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toType<quantity<MegaElectronVolt> >(),
                       1.0*MeV );
  FRENSIE_CHECK_EQUAL( dist_data["nuclear temp"].toType<quantity<MegaElectronVolt> >(),
                       1.0*MeV );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toType<quantity<MegaElectronVolt> >(),
                       0.1*MeV );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0 );
}

//---------------------------------------------------------------------------//
// Check that a distribution can be archived
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( EvaporationDistribution,
                                   archive,
                                   TestArchives )
{
  FETCH_TEMPLATE_PARAM( 0, RawOArchive );
  FETCH_TEMPLATE_PARAM( 1, RawIArchive );

  typedef typename std::remove_pointer<RawOArchive>::type OArchive;
  typedef typename std::remove_pointer<RawIArchive>::type IArchive;

  std::string archive_base_name( "test_evaporation_dist" );
  std::ostringstream archive_ostream;

  // Create and archive some evaporation distributions
  {
    std::unique_ptr<OArchive> oarchive;

    createOArchive( archive_base_name, archive_ostream, oarchive );

    Utility::EvaporationDistribution dist_a;
    Utility::EvaporationDistribution dist_b( 3.0 );
    Utility::EvaporationDistribution dist_c( 3.0, 2.0 );
    Utility::EvaporationDistribution dist_d( 3.0, 2.0, 1.0 );
    Utility::EvaporationDistribution dist_e( 3.0, 2.0, 1.0, 10.0 );

    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_a )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_b )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_c )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_d )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_e )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                         (*oarchive) << BOOST_SERIALIZATION_NVP( distribution )
                             );
  }

  // Copy the archive ostream to an istream
  std::istringstream archive_istream( archive_ostream.str() );

  // Load the archived distributions
  std::unique_ptr<IArchive> iarchive;

  createIArchive( archive_istream, iarchive );

  Utility::EvaporationDistribution dist_a;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_a )
                           );
  FRENSIE_CHECK_EQUAL( dist_a, Utility::EvaporationDistribution() );

  Utility::EvaporationDistribution dist_b;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_b )
                           );
  FRENSIE_CHECK_EQUAL( dist_b, Utility::EvaporationDistribution( 3.0 ) );

  Utility::EvaporationDistribution dist_c;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_c )
                           );
  FRENSIE_CHECK_EQUAL( dist_c, Utility::EvaporationDistribution( 3.0, 2.0 ) );

  Utility::EvaporationDistribution dist_d;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_d )
                           );
  FRENSIE_CHECK_EQUAL( dist_d, Utility::EvaporationDistribution( 3.0, 2.0, 1.0 ) );

  Utility::EvaporationDistribution dist_e;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_e )
                           );
  FRENSIE_CHECK_EQUAL( dist_e, Utility::EvaporationDistribution( 3.0, 2.0, 1.0, 10.0 ) );

  std::shared_ptr<Utility::UnivariateDistribution> shared_dist;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> boost::serialization::make_nvp( "distribution", shared_dist ) );
  FRENSIE_CHECK_EQUAL( shared_dist->getLowerBoundOfIndepVar(), 0.0 );
  FRENSIE_CHECK_EQUAL( shared_dist->getUpperBoundOfIndepVar(), 0.9 );
  FRENSIE_CHECK_EQUAL( shared_dist->evaluate( 0.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( shared_dist->evaluate( 0.9 ), 0.9*exp( -0.9 ) );
  FRENSIE_CHECK_EQUAL( shared_dist->evaluate( 1.0 ), 0.0 );
}

//---------------------------------------------------------------------------//
// Check that a unit-aware distribution can be archived
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( UnitAwareEvaporationDistribution,
                                   archive,
                                   TestArchives )
{
  FETCH_TEMPLATE_PARAM( 0, RawOArchive );
  FETCH_TEMPLATE_PARAM( 1, RawIArchive );

  typedef typename std::remove_pointer<RawOArchive>::type OArchive;
  typedef typename std::remove_pointer<RawIArchive>::type IArchive;

  std::string archive_base_name( "test_unit_aware_evaporation_dist" );
  std::ostringstream archive_ostream;

  // Create and archive some evaporation distributions
  {
    std::unique_ptr<OArchive> oarchive;

    createOArchive( archive_base_name, archive_ostream, oarchive );

    Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount> dist_a;
    Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount> dist_b( 3.0*MeV );
    Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount> dist_c( 3.0*MeV, 2.0*MeV );
    Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount> dist_d( 3.0*MeV, 2.0*MeV, 1.0*MeV );
    Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount> dist_e( 3.0*MeV, 2.0*MeV, 1.0*MeV, 10.0 );

    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_a )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_b )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_c )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_d )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_e )
                             );
    FRENSIE_REQUIRE_NO_THROW(
              (*oarchive) << BOOST_SERIALIZATION_NVP( unit_aware_distribution )
                             );
  }

  // Copy the archive ostream to an istream
  std::istringstream archive_istream( archive_ostream.str() );

  // Load the archived distributions
  std::unique_ptr<IArchive> iarchive;

  createIArchive( archive_istream, iarchive );

  Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount> dist_a;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_a )
                           );
  FRENSIE_CHECK_EQUAL( dist_a, (Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount>()) );

  Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount> dist_b;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_b )
                           );
  FRENSIE_CHECK_EQUAL( dist_b, (Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount>( 3.0*MeV )) );

  Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount> dist_c;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_c )
                           );
  FRENSIE_CHECK_EQUAL( dist_c, (Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount>( 3.0*MeV, 2.0*MeV )) );

  Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount> dist_d;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_d )
                           );
  FRENSIE_CHECK_EQUAL( dist_d, (Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount>( 3.0*MeV, 2.0*MeV, 1.0*MeV )) );

  Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount> dist_e;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_e )
                           );
  FRENSIE_CHECK_EQUAL( dist_e, (Utility::UnitAwareEvaporationDistribution<MegaElectronVolt,si::amount>( 3.0*MeV, 2.0*MeV, 1.0*MeV, 10.0 )) );

  std::shared_ptr<Utility::UnitAwareUnivariateDistribution<MegaElectronVolt,si::amount> > shared_dist;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> boost::serialization::make_nvp( "distribution", shared_dist ) );
  FRENSIE_CHECK_EQUAL( shared_dist->getLowerBoundOfIndepVar(), 0.0*MeV );
  FRENSIE_CHECK_EQUAL( shared_dist->getUpperBoundOfIndepVar(), 0.9*MeV );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.0*MeV ),
		       0.0*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.9*MeV ),
                       0.9*exp(-0.9)*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 1.0*MeV ),
		       0.0*si::mole );
}

//---------------------------------------------------------------------------//
// Check that distributions can be scaled
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( UnitAwareEvaporationDistribution,
				   explicit_conversion,
				   TestUnitTypeQuads )
{
  FETCH_TEMPLATE_PARAM( 0, RawIndepUnitA );
  FETCH_TEMPLATE_PARAM( 1, RawDepUnitA );
  FETCH_TEMPLATE_PARAM( 2, RawIndepUnitB );
  FETCH_TEMPLATE_PARAM( 3, RawDepUnitB );

  typedef typename std::remove_pointer<RawIndepUnitA>::type IndepUnitA;
  typedef typename std::remove_pointer<RawDepUnitA>::type DepUnitA;
  typedef typename std::remove_pointer<RawIndepUnitB>::type IndepUnitB;
  typedef typename std::remove_pointer<RawDepUnitB>::type DepUnitB;

  typedef typename Utility::UnitTraits<IndepUnitA>::template GetQuantityType<double>::type IndepQuantityA;
  typedef typename Utility::UnitTraits<typename Utility::UnitTraits<IndepUnitA>::InverseUnit>::template GetQuantityType<double>::type InverseIndepQuantityA;

  typedef typename Utility::UnitTraits<IndepUnitB>::template GetQuantityType<double>::type IndepQuantityB;
  typedef typename Utility::UnitTraits<typename Utility::UnitTraits<IndepUnitB>::InverseUnit>::template GetQuantityType<double>::type InverseIndepQuantityB;

  typedef typename Utility::UnitTraits<DepUnitA>::template GetQuantityType<double>::type DepQuantityA;
  typedef typename Utility::UnitTraits<DepUnitB>::template GetQuantityType<double>::type DepQuantityB;

  // Copy from unitless distribution to distribution type A (static method)
  Utility::UnitAwareEvaporationDistribution<IndepUnitA,DepUnitA>
    unit_aware_dist_a_copy = Utility::UnitAwareEvaporationDistribution<IndepUnitA,DepUnitA>::fromUnitlessDistribution( *dynamic_cast<Utility::EvaporationDistribution*>( distribution.get() ) );

  // Copy from distribution type A to distribution type B (explicit cast)
  Utility::UnitAwareEvaporationDistribution<IndepUnitB,DepUnitB>
    unit_aware_dist_b_copy( unit_aware_dist_a_copy );

  IndepQuantityA indep_quantity_a =
    Utility::QuantityTraits<IndepQuantityA>::initializeQuantity( 0.0 );
  InverseIndepQuantityA inv_indep_quantity_a =
    Utility::QuantityTraits<InverseIndepQuantityA>::initializeQuantity( 0.0 );
  DepQuantityA dep_quantity_a =
    Utility::QuantityTraits<DepQuantityA>::initializeQuantity( 0.0 );

  IndepQuantityB indep_quantity_b( indep_quantity_a );
  InverseIndepQuantityB inv_indep_quantity_b( inv_indep_quantity_a );
  DepQuantityB dep_quantity_b( dep_quantity_a );

  FRENSIE_CHECK_FLOATING_EQUALITY(
			   unit_aware_dist_a_copy.evaluate( indep_quantity_a ),
			   dep_quantity_a,
			   1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			unit_aware_dist_a_copy.evaluatePDF( indep_quantity_a ),
			inv_indep_quantity_a,
			1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			   unit_aware_dist_b_copy.evaluate( indep_quantity_b ),
			   dep_quantity_b,
			   1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			unit_aware_dist_b_copy.evaluatePDF( indep_quantity_b ),
			inv_indep_quantity_b,
			1e-15 );

  Utility::setQuantity( indep_quantity_a, 0.89999999999999 );
  Utility::setQuantity( inv_indep_quantity_a,
			0.9*exp(-0.9)/(1.0 - exp(-0.9)*(1.0+0.9)) );
  Utility::setQuantity( dep_quantity_a, 0.9*exp(-0.9) );

  indep_quantity_b = IndepQuantityB( indep_quantity_a );
  inv_indep_quantity_b = InverseIndepQuantityB( inv_indep_quantity_a );
  dep_quantity_b = DepQuantityB( dep_quantity_a );

  FRENSIE_CHECK_FLOATING_EQUALITY(
			   unit_aware_dist_a_copy.evaluate( indep_quantity_a ),
			   dep_quantity_a,
			   1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			unit_aware_dist_a_copy.evaluatePDF( indep_quantity_a ),
			inv_indep_quantity_a,
			1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			   unit_aware_dist_b_copy.evaluate( indep_quantity_b ),
			   dep_quantity_b,
			   1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			unit_aware_dist_b_copy.evaluatePDF( indep_quantity_b ),
			inv_indep_quantity_b,
			1e-14 );
}

//---------------------------------------------------------------------------//
// Custom setup
//---------------------------------------------------------------------------//
FRENSIE_CUSTOM_UNIT_TEST_SETUP_BEGIN();

FRENSIE_CUSTOM_UNIT_TEST_INIT()
{
  // Initialize the random number generator
  Utility::RandomNumberGenerator::createStreams();
}

FRENSIE_CUSTOM_UNIT_TEST_SETUP_END();

//---------------------------------------------------------------------------//
// end tstEvaporationDistribution.cpp
//---------------------------------------------------------------------------//


///////////////////////////////////////////////////////////////////////////////
//  Copyright 2015 John Maddock. 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)

#include <boost/multiprecision/cpp_int.hpp>
#include <type_traits>

typedef boost::multiprecision::number<boost::multiprecision::rational_adaptor<boost::multiprecision::int128_t::backend_type> >  rat128_t;
typedef boost::multiprecision::number<boost::multiprecision::rational_adaptor<boost::multiprecision::uint128_t::backend_type> > urat128_t;
typedef boost::multiprecision::number<boost::multiprecision::rational_adaptor<boost::multiprecision::int512_t::backend_type> >  rat512_t;
typedef boost::multiprecision::number<boost::multiprecision::rational_adaptor<boost::multiprecision::uint512_t::backend_type> > urat512_t;

typedef boost::multiprecision::number<boost::multiprecision::rational_adaptor<boost::multiprecision::checked_int128_t::backend_type> >  checked_rat128_t;
typedef boost::multiprecision::number<boost::multiprecision::rational_adaptor<boost::multiprecision::checked_uint128_t::backend_type> > checked_urat128_t;
typedef boost::multiprecision::number<boost::multiprecision::rational_adaptor<boost::multiprecision::checked_int512_t::backend_type> >  checked_rat512_t;
typedef boost::multiprecision::number<boost::multiprecision::rational_adaptor<boost::multiprecision::checked_uint512_t::backend_type> > checked_urat512_t;

//
// Move construct:
//
static_assert(std::is_nothrow_move_constructible<boost::multiprecision::cpp_rational>::value, "is_nothrow_move_constructible test");
static_assert(std::is_nothrow_move_constructible<rat128_t>::value, "is_nothrow_move_constructible test");
static_assert(std::is_nothrow_move_constructible<checked_rat128_t>::value, "is_nothrow_move_constructible test");
static_assert(std::is_nothrow_move_constructible<urat128_t>::value, "is_nothrow_move_constructible test");
static_assert(std::is_nothrow_move_constructible<checked_urat128_t>::value, "is_nothrow_move_constructible test");
static_assert(std::is_nothrow_move_constructible<rat512_t>::value, "is_nothrow_move_constructible test");
static_assert(std::is_nothrow_move_constructible<checked_rat512_t>::value, "is_nothrow_move_constructible test");
static_assert(std::is_nothrow_move_constructible<urat512_t>::value, "is_nothrow_move_constructible test");
static_assert(std::is_nothrow_move_constructible<checked_urat512_t>::value, "is_nothrow_move_constructible test");
//
// Move assign:
//
static_assert(std::is_nothrow_move_assignable<boost::multiprecision::cpp_rational>::value, "is_nothrow_move_assignable test");
static_assert(std::is_nothrow_move_assignable<rat128_t>::value, "is_nothrow_move_assignable test");
static_assert(std::is_nothrow_move_assignable<checked_rat128_t>::value, "is_nothrow_move_assignable test");
static_assert(std::is_nothrow_move_assignable<urat128_t>::value, "is_nothrow_move_assignable test");
static_assert(std::is_nothrow_move_assignable<checked_urat128_t>::value, "is_nothrow_move_assignable test");
static_assert(std::is_nothrow_move_assignable<rat512_t>::value, "is_nothrow_move_assignable test");
static_assert(std::is_nothrow_move_assignable<checked_rat512_t>::value, "is_nothrow_move_assignable test");
static_assert(std::is_nothrow_move_assignable<urat512_t>::value, "is_nothrow_move_assignable test");
static_assert(std::is_nothrow_move_assignable<checked_urat512_t>::value, "is_nothrow_move_assignable test");

#if 0
//
// Everything below could/should be made to work, given modifications to Boost.Rational
//

//
// Construct:
//
#ifdef BOOST_HAS_NOTHROW_CONSTRUCTOR
static_assert(boost::has_nothrow_constructor<boost::multiprecision::cpp_rational>::value);
static_assert(boost::has_nothrow_constructor<rat128_t>::value);
static_assert(boost::has_nothrow_constructor<checked_rat128_t>::value);
static_assert(boost::has_nothrow_constructor<urat128_t>::value);
static_assert(boost::has_nothrow_constructor<checked_urat128_t>::value);
static_assert(boost::has_nothrow_constructor<rat512_t>::value);
static_assert(boost::has_nothrow_constructor<checked_rat512_t>::value);
static_assert(boost::has_nothrow_constructor<urat512_t>::value);
static_assert(boost::has_nothrow_constructor<checked_urat512_t>::value);
#endif
//
// Copy construct:
//
#ifdef BOOST_HAS_NOTHROW_COPY
static_assert(!boost::has_nothrow_copy<boost::multiprecision::cpp_rational>::value);
static_assert(boost::has_nothrow_copy<rat128_t>::value);
static_assert(boost::has_nothrow_copy<checked_rat128_t>::value);
static_assert(boost::has_nothrow_copy<urat128_t>::value);
static_assert(boost::has_nothrow_copy<checked_urat128_t>::value);
static_assert(boost::has_nothrow_copy<rat512_t>::value);
static_assert(boost::has_nothrow_copy<checked_rat512_t>::value);
static_assert(boost::has_nothrow_copy<urat512_t>::value);
static_assert(boost::has_nothrow_copy<checked_urat512_t>::value);
#endif
//
// Assign:
//
#ifdef BOOST_HAS_NOTHROW_ASSIGN
static_assert(!boost::has_nothrow_assign<boost::multiprecision::cpp_rational>::value);
static_assert(boost::has_nothrow_assign<rat128_t>::value);
static_assert(boost::has_nothrow_assign<checked_rat128_t>::value);
static_assert(boost::has_nothrow_assign<urat128_t>::value);
static_assert(boost::has_nothrow_assign<checked_urat128_t>::value);
static_assert(boost::has_nothrow_assign<rat512_t>::value);
static_assert(boost::has_nothrow_assign<checked_rat512_t>::value);
static_assert(boost::has_nothrow_assign<urat512_t>::value);
static_assert(boost::has_nothrow_assign<checked_urat512_t>::value);
#endif
//
// Construct from int:
//
static_assert(noexcept(boost::multiprecision::cpp_rational(std::declval<boost::multiprecision::signed_limb_type>())));
static_assert(noexcept(rat128_t(std::declval<boost::multiprecision::signed_limb_type>())));
static_assert(noexcept(checked_rat128_t(std::declval<boost::multiprecision::signed_limb_type>())));
static_assert(noexcept(urat128_t(std::declval<boost::multiprecision::signed_limb_type>())));
static_assert(!noexcept(checked_urat128_t(std::declval<boost::multiprecision::signed_limb_type>())));
static_assert(noexcept(rat512_t(std::declval<boost::multiprecision::signed_limb_type>())));
static_assert(noexcept(checked_rat512_t(std::declval<boost::multiprecision::signed_limb_type>())));
static_assert(noexcept(urat512_t(std::declval<boost::multiprecision::signed_limb_type>())));
static_assert(!noexcept(checked_urat512_t(std::declval<boost::multiprecision::signed_limb_type>())));
//
// Construct from unsigned int:
//
static_assert(noexcept(boost::multiprecision::cpp_rational(std::declval<boost::multiprecision::limb_type>())));
static_assert(noexcept(rat128_t(std::declval<boost::multiprecision::limb_type>())));
static_assert(noexcept(checked_rat128_t(std::declval<boost::multiprecision::limb_type>())));
static_assert(noexcept(urat128_t(std::declval<boost::multiprecision::limb_type>())));
static_assert(noexcept(checked_urat128_t(std::declval<boost::multiprecision::limb_type>())));
static_assert(noexcept(rat512_t(std::declval<boost::multiprecision::limb_type>())));
static_assert(noexcept(checked_rat512_t(std::declval<boost::multiprecision::limb_type>())));
static_assert(noexcept(urat512_t(std::declval<boost::multiprecision::limb_type>())));
static_assert(noexcept(checked_urat512_t(std::declval<boost::multiprecision::limb_type>())));
//
// Assign from int:
//
static_assert(noexcept(std::declval<boost::multiprecision::cpp_rational>() = std::declval<boost::multiprecision::signed_limb_type>()));
static_assert(noexcept(std::declval<rat128_t>() = std::declval<boost::multiprecision::signed_limb_type>()));
static_assert(noexcept(std::declval<checked_rat128_t>() = std::declval<boost::multiprecision::signed_limb_type>()));
static_assert(noexcept(std::declval<urat128_t>() = std::declval<boost::multiprecision::signed_limb_type>()));
static_assert(!noexcept(std::declval<checked_urat128_t>() = std::declval<boost::multiprecision::signed_limb_type>()));
static_assert(noexcept(std::declval<rat512_t>() = std::declval<boost::multiprecision::signed_limb_type>()));
static_assert(noexcept(std::declval<checked_rat512_t>() = std::declval<boost::multiprecision::signed_limb_type>()));
static_assert(noexcept(std::declval<urat512_t>() = std::declval<boost::multiprecision::signed_limb_type>()));
static_assert(!noexcept(std::declval<checked_urat512_t>() = std::declval<boost::multiprecision::signed_limb_type>()));
//
// Assign from unsigned int:
//
static_assert(noexcept(std::declval<boost::multiprecision::cpp_rational>() = std::declval<boost::multiprecision::limb_type>()));
static_assert(noexcept(std::declval<rat128_t>() = std::declval<boost::multiprecision::limb_type>()));
static_assert(noexcept(std::declval<checked_rat128_t>() = std::declval<boost::multiprecision::limb_type>()));
static_assert(noexcept(std::declval<urat128_t>() = std::declval<boost::multiprecision::limb_type>()));
static_assert(noexcept(std::declval<checked_urat128_t>() = std::declval<boost::multiprecision::limb_type>()));
static_assert(noexcept(std::declval<rat512_t>() = std::declval<boost::multiprecision::limb_type>()));
static_assert(noexcept(std::declval<checked_rat512_t>() = std::declval<boost::multiprecision::limb_type>()));
static_assert(noexcept(std::declval<urat512_t>() = std::declval<boost::multiprecision::limb_type>()));
static_assert(noexcept(std::declval<checked_urat512_t>() = std::declval<boost::multiprecision::limb_type>()));

#if defined(BOOST_LITTLE_ENDIAN)
//
// Construct from int:
//
static_assert(noexcept(boost::multiprecision::cpp_rational(std::declval<boost::multiprecision::signed_double_limb_type>())));
static_assert(noexcept(rat128_t(std::declval<boost::multiprecision::signed_double_limb_type>())));
static_assert(noexcept(checked_rat128_t(std::declval<boost::multiprecision::signed_double_limb_type>())));
static_assert(noexcept(urat128_t(std::declval<boost::multiprecision::signed_double_limb_type>())));
static_assert(!noexcept(checked_urat128_t(std::declval<boost::multiprecision::signed_double_limb_type>())));
static_assert(noexcept(rat512_t(std::declval<boost::multiprecision::signed_double_limb_type>())));
static_assert(noexcept(checked_rat512_t(std::declval<boost::multiprecision::signed_double_limb_type>())));
static_assert(noexcept(urat512_t(std::declval<boost::multiprecision::signed_double_limb_type>())));
static_assert(!noexcept(checked_urat512_t(std::declval<boost::multiprecision::signed_double_limb_type>())));
//
// Construct from unsigned int:
//
static_assert(noexcept(boost::multiprecision::cpp_rational(std::declval<boost::multiprecision::double_limb_type>())));
static_assert(noexcept(rat128_t(std::declval<boost::multiprecision::double_limb_type>())));
static_assert(noexcept(checked_rat128_t(std::declval<boost::multiprecision::double_limb_type>())));
static_assert(noexcept(urat128_t(std::declval<boost::multiprecision::double_limb_type>())));
static_assert(noexcept(checked_urat128_t(std::declval<boost::multiprecision::double_limb_type>())));
static_assert(noexcept(rat512_t(std::declval<boost::multiprecision::double_limb_type>())));
static_assert(noexcept(checked_rat512_t(std::declval<boost::multiprecision::double_limb_type>())));
static_assert(noexcept(urat512_t(std::declval<boost::multiprecision::double_limb_type>())));
static_assert(noexcept(checked_urat512_t(std::declval<boost::multiprecision::double_limb_type>())));
//
// Assign from int:
//
static_assert(noexcept(std::declval<boost::multiprecision::cpp_rational>() = std::declval<boost::multiprecision::signed_double_limb_type>()));
static_assert(noexcept(std::declval<rat128_t>() = std::declval<boost::multiprecision::signed_double_limb_type>()));
static_assert(noexcept(std::declval<checked_rat128_t>() = std::declval<boost::multiprecision::signed_double_limb_type>()));
static_assert(noexcept(std::declval<urat128_t>() = std::declval<boost::multiprecision::signed_double_limb_type>()));
static_assert(!noexcept(std::declval<checked_urat128_t>() = std::declval<boost::multiprecision::signed_double_limb_type>()));
static_assert(noexcept(std::declval<rat512_t>() = std::declval<boost::multiprecision::signed_double_limb_type>()));
static_assert(noexcept(std::declval<checked_rat512_t>() = std::declval<boost::multiprecision::signed_double_limb_type>()));
static_assert(noexcept(std::declval<urat512_t>() = std::declval<boost::multiprecision::signed_double_limb_type>()));
static_assert(!noexcept(std::declval<checked_urat512_t>() = std::declval<boost::multiprecision::signed_double_limb_type>()));
//
// Assign from unsigned int:
//
static_assert(noexcept(std::declval<boost::multiprecision::cpp_rational>() = std::declval<boost::multiprecision::double_limb_type>()));
static_assert(noexcept(std::declval<rat128_t>() = std::declval<boost::multiprecision::double_limb_type>()));
static_assert(noexcept(std::declval<checked_rat128_t>() = std::declval<boost::multiprecision::double_limb_type>()));
static_assert(noexcept(std::declval<urat128_t>() = std::declval<boost::multiprecision::double_limb_type>()));
static_assert(noexcept(std::declval<checked_urat128_t>() = std::declval<boost::multiprecision::double_limb_type>()));
static_assert(noexcept(std::declval<rat512_t>() = std::declval<boost::multiprecision::double_limb_type>()));
static_assert(noexcept(std::declval<checked_rat512_t>() = std::declval<boost::multiprecision::double_limb_type>()));
static_assert(noexcept(std::declval<urat512_t>() = std::declval<boost::multiprecision::double_limb_type>()));
static_assert(noexcept(std::declval<checked_urat512_t>() = std::declval<boost::multiprecision::double_limb_type>()));

#endif
#endif // little endian


#ifndef SODES_BOOSTODEINT_HPP
#define SODES_BOOSTODEINT_HPP

#include <Eigen/Core>
#include <boost/numeric/odeint.hpp>
#include <boost/numeric/odeint/algebra/vector_space_algebra.hpp>
#include <boost/numeric/odeint/external/eigen/eigen_resize.hpp>

#include <tuple>
#include <functional>
#include <vector>
#include <utility>
#include <stdexcept>

#include "math_types.hpp"

namespace sodes::detail::odeint {

using namespace boost::numeric::odeint;
using namespace Eigen;
using namespace std;

using namespace sodes::math_types;

typedef eigen_array_1d state_type;

struct Observer
{
    vector<state_type>& m_states;
    vector<double>& m_times;

    Observer(vector<state_type>& states, vector<double>& times)
        : m_states(states), m_times(times) {}

    void operator()(const state_type& x, const double& t)
    {
        m_states.push_back(x);
        m_times.push_back(t);
    }
};

template<typename Stepper>
pair<vector<double>, vector<state_type>> _integrate_const_stepper(
    const function<state_type(eigen_array_1d_constref, eigen_array_1d_ref, const double&)>& f,
    const tuple<double, double>& t_span,
    const double& dt,
    state_type& y0,
    Stepper stepper)
{
    // Containers to store solution
    vector<state_type> states;
    vector<double> times;

    // Integrate over time
    auto[t_init, t_final] = t_span;
    auto observer = Observer(states, times);
    integrate_const(
        stepper,
        f,
        y0,
        t_init,
        t_final,
        dt,
        observer);

    auto solution_pair = make_pair(times, states);

    return solution_pair;
}
}  // namespace sodes::detail::odeint

namespace sodes::odeint {

using namespace std;
using namespace Eigen;
using namespace sodes::math_types;

pair<vector<double>, vector<ArrayXd>> solve_ivp_const(
    const function<ArrayXd(eigen_array_1d_constref, eigen_array_1d_ref, const double&)>& f,
    const tuple<double, double>& t_span,
    const double& dt,
    ArrayXd& y0,
    const string& method = "runge_kutta4")
{
    using namespace sodes::detail::odeint;

    if (method == "runge_kutta4") {
        typedef runge_kutta4<state_type, double, state_type, double, vector_space_algebra> stepper;
        return _integrate_const_stepper<stepper>(f, t_span, dt, y0, stepper());
    }
//    else if (method == "runge_kutta_dopri5") {
//        typedef runge_kutta_dopri5<state_type, double, state_type, double, vector_space_algebra> stepper;
//        auto dense_output = make_dense_output<stepper>(1e-6, 1e-6);
//        return _integrate_const_stepper<dense_output>(f, t_span, dt, y0, dense_output);
//    }
    else if (method == "runge_kutta_cash_karp54") {
        typedef runge_kutta_cash_karp54<state_type, double, state_type, double, vector_space_algebra> stepper;
        return _integrate_const_stepper<stepper>(f, t_span, dt, y0, stepper());
    }
    else if (method == "runge_kutta_fehlberg78") {
        typedef runge_kutta_fehlberg78<state_type, double, state_type, double, vector_space_algebra> stepper;
        return _integrate_const_stepper<stepper>(f, t_span, dt, y0, stepper());
    }
    else if (method == "modified_midpoint") {
        typedef modified_midpoint<state_type, double, state_type, double, vector_space_algebra> stepper;
        return _integrate_const_stepper<stepper>(f, t_span, dt, y0, stepper());
    }
    else if (method == "euler") {
        typedef euler<state_type, double, state_type, double, vector_space_algebra> stepper;
        return _integrate_const_stepper<stepper>(f, t_span, dt, y0, stepper());
    }
    else {
        throw invalid_argument("Unavailable integration method from Boost::odeint");
    }
}
}  // namespace sodes::odeint

#endif //SODES_BOOSTODEINT_HPP


/*
 * ThreephaseEngine.cpp
 *
 * Copyright (c) 2014, Alessandro Pezzato
 */

#include "ThreephaseEngine.h"
#include "../../common/Logger.h"
#include "../../common/Config.h"
#include "../../projector/Projector.h"
#include "../../input/ImageInput.h"

#include <format.h>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <glm/gtx/transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <boost/foreach.hpp>
#include <boost/algorithm/string.hpp>

/*
 * http://public.vrac.iastate.edu/~song/publications/papers/2010-ole-review.pdf
 */

namespace threescanner {

namespace /* anonymous */{

static const int WRAP_V_SIZE = 511; /* i1 - i3, -255 to 255, |511| */
static const int WRAP_U_SIZE = 1021; /* 2 * i2 - i1 - i3, -510 to 510, |1021| */
static const int WRAP_V_OFFSET = 255; /* min value is -255 */
static const int WRAP_U_OFFSET = 510; /* min value is -510 */
static unsigned char PHASE_GAMMA_LUT[WRAP_V_SIZE][WRAP_U_SIZE][2];

/*
 * initPhaseGammaLut() is automatically called once at startup
 */
void initPhaseGammaLut() __attribute__ ((constructor)); /* FIXME: only gcc is compatible */
void initPhaseGammaLut() {
	float sqrt3 = sqrtf(3.0);
	float ipi = float(128. / CV_PI);
	for (int vo = 0; vo < WRAP_V_SIZE; vo++) {
		for (int uo = 0; uo < WRAP_U_SIZE; uo++) {
			float v = float(vo - WRAP_V_OFFSET);
			float u = float(uo - WRAP_U_OFFSET);
			float phase = atan2f(sqrt3 * v, u) * ipi;
			float modulation = sqrtf(3 * v * v + u * u);
			PHASE_GAMMA_LUT[vo][uo][0] = 128 + (unsigned char) phase;
			PHASE_GAMMA_LUT[vo][uo][1] = modulation > 255 ? 255 : (unsigned char) modulation;
		}
	}
}

bool isVertical(const std::string& orientation) {
	return orientation[0] == 'v' || orientation[0] == 'V';
}

bool isHorizontal(const std::string& orientation) {
	return orientation[0] == 'h' || orientation[0] == 'H';
}

void traceMatrix(const std::string& name, const glm::mat4& mat) {
	const float* a = (const float*) glm::value_ptr(mat);
	logDebug("%16s | %8.3f %8.3f %8.3f %8.3f", name.c_str(), a[0], a[4], a[8], a[12]);
	logDebug("                 | %8.3f %8.3f %8.3f %8.3f", a[1], a[5], a[9], a[13]);
	logDebug("                 | %8.3f %8.3f %8.3f %8.3f", a[2], a[6], a[10], a[14]);
	logDebug("                 | %8.3f %8.3f %8.3f %8.3f", a[3], a[7], a[11], a[15]);
}

} /* namespace anonymous */

ThreephaseEngine::ThreephaseEngine(const Config& cfg) :
				Engine(cfg),
				wrapMethod_(cfg.get<int>("wrapMethod")),
				options_(),
				toProcess_(),
				hImages_ { },
				vImages_ { },
				phases_(hImages_),
				process_(),
				unwrapped_(),
				mask_(),
				depth_() {
	/* tuning */
	Config tuning = cfg.getChild("tuning");

	options_["mscale"] = tuning.get<float>("mscale", 200.0);
	options_["zscale"] = tuning.get<float>("zscale", 151.0);
	options_["zskew"] = tuning.get<float>("zskew", 180.0);
	options_["znoise"] = tuning.get<float>("znoise", 0.720);
	options_["zblur"] = tuning.get<float>("zblur", 16);
	options_["cloudScale"] = tuning.get<float>("cloudScale", 1.0);

}

ThreephaseEngine::~ThreephaseEngine() {

}

void ThreephaseEngine::scanSync() {
	/* TODO:
	 * should get images from ImageInput (Camera)
	 */
	if (projector_.get() == nullptr) {
		throw std::runtime_error("Cannot scan without a valid projector");
	}
	if (input_.get() == nullptr) {
		throw std::runtime_error("Cannot scan without a valid image input");
	}
	logDebug("Wait until ready");
	projector_->waitUntilReady();
	logDebug("ready");
	std::string orientation = "h"; /* TODO: from Config */
	projector_->setParameter("orientation", orientation);
	for (int phase = 1; phase <= 3; ++phase) {
		logDebug("Scan phase %i", phase);
		this->projector_->setParameter("phase", fmt::sprintf("%i", phase));
		std::this_thread::sleep_for(std::chrono::milliseconds(250));
		ImagePtr image = input_->grabImage();
		this->setImage(fmt::sprintf("%s:%i", orientation, phase), *image);
	}
	this->process(orientation);
}

void ThreephaseEngine::setImage(const std::string& orientation, const size_t& phase, const cv::Mat& image) {
	if (phase > 3) {
		throw std::runtime_error(fmt::sprintf("Threephase invalid phase: %u", phase));
	}
	cv::Mat* phases = isVertical(orientation) ? vImages_ : hImages_;
	cv::Mat& dst = phases[phase - 1];
	cv::cvtColor(image, dst, CV_RGB2GRAY);
	logDebug("Threephase set image %u", phase);
}

void ThreephaseEngine::process(const std::string& orientation) {
	if (isVertical(orientation)) {
		phases_ = vImages_;
	} else if (isHorizontal(orientation)) {
		phases_ = hImages_;
	} else if (orientation == "last" && phases_ == nullptr) {
		throw std::runtime_error("Last orientation is undefined");
	} else {
		throw std::runtime_error("Invalid orientation");
	}
	if (!phases_[0].rows || !phases_[1].rows || !phases_[2].rows) {
		logWarning("Cannot process empty images");
		return;
	}
	const int znoise = options_["znoise"];
	const float zscale = options_["zscale"];
	const float mscale = options_["mscale"];
	const float zskew = options_["zskew"];
	this->setup();
	this->wrap(znoise);
	this->unwrap();
	this->blurMask();
	this->computeDepth(zscale - mscale, zskew - mscale);
	this->createCloud();
}

void ThreephaseEngine::blurMask() {
	const int zblur = options_["zblur"];
	if (zblur) {
		cv::blur(mask_, mask_, cv::Size(zblur, zblur));
	}
}

void ThreephaseEngine::unwrap() {
	int width = phases_[0].cols;
	int height = phases_[0].rows;
	int startX = width / 2;
	int startY = height / 2;

	toProcess_.clear();

	toProcess_.push_back(cv::Point(startX, startY));
	process_.at<uchar>(startY, startX) = false;

	while (!toProcess_.empty()) {
		cv::Point p = toProcess_.back();
		toProcess_.pop_back();

		int x = p.x;
		int y = p.y;
		float r = unwrapped_.at<float>(y, x);
		if (y > 0) {
			this->unwrap(r, x, y - 1);
		}
		if (y < height - 2) {
			this->unwrap(r, x, y + 1);
		}
		if (x > 0) {
			this->unwrap(r, x - 1, y);
		}
		if (x < width - 2) {
			this->unwrap(r, x + 1, y);
		}
	}
	toProcess_.clear();
}

void ThreephaseEngine::unwrap(float basePhase, int x, int y) {
	const int idx = x + y * process_.cols;
	float* unwrappedBuffer = reinterpret_cast<float*>(unwrapped_.data);
	const float bfquo = basePhase - round(basePhase);
	if (process_.data[idx]) {
		float& p0 = unwrappedBuffer[idx];
		float diff = p0 - bfquo;
		if (diff > 0.5f) {
			diff -= 1.0;
		} else if (diff < -0.5f) {
			diff += 1.0;
		}
		p0 = basePhase + diff;
		process_.data[idx] = 0;
		toProcess_.push_back(cv::Point(x, y));
	}
}

void ThreephaseEngine::wrap(float noiseThreshold) {
	int width = process_.cols;
	int height = process_.rows;

	for (int y = 0; y < height; y++) {
		for (int x = 0; x < width; x++) {
			const int i = x + y * width;
			const int lum0 = phases_[0].data[i];
			const int lum1 = phases_[1].data[i];
			const int lum2 = phases_[2].data[i];
			const int v = lum0 - lum2;
			const int u = 2 * lum1 - lum0 - lum2;
			const unsigned char* const cur = PHASE_GAMMA_LUT[v + WRAP_V_OFFSET][u + WRAP_U_OFFSET];
			mask_.data[i] = (cur[1] < noiseThreshold * 64);

			process_.data[i] = !mask_.data[i];
			static const float SQRT3 = sqrtf(3.0);
			static const float TWO_PI = 6.283185;

			float p2;
			switch (wrapMethod_) {
			case 0:
				p2 = atan2(SQRT3 * (lum0 - lum2), 2.0f * lum1 - lum0 - lum2) / TWO_PI;
				break;
			case 1:
				p2 = (cur[0] - 128.0f) / 255.0f;
				break;
			default:
				logWarning("calibration.json engine.wrapMethod can be 0 or 1");
				break;
			}
			float* unwrappedBuffer = reinterpret_cast<float*>(unwrapped_.data);
			unwrappedBuffer[i] = p2;
		}
	}
}

void ThreephaseEngine::computeDepth(float zscale, float zskew) {
	float* unwrappedBuffer = reinterpret_cast<float*>(unwrapped_.data);
	float* depthBuffer = reinterpret_cast<float*>(depth_.data);

	for (int i = 0; i < unwrapped_.rows; ++i) {
		float planephase = float(i - unwrapped_.rows / 2);
		if (zskew) {
			planephase /= zskew;
		}
		planephase += 0.5f;
		for (int j = 0; j < unwrapped_.cols; ++j) {
			int ii = i * unwrapped_.cols + j;
			depthBuffer[ii] = mask_.data[ii] ? 0.0f : ((unwrappedBuffer[ii] - planephase) * zscale);
		}
	}
}

void ThreephaseEngine::setup() {
	int w = phases_[0].cols;
	int h = phases_[0].rows;
	logDebug("Threephase setup %ux%u", w, h);
	unwrapped_ = cv::Mat::zeros(h, w, CV_32F);
	depth_ = cv::Mat::zeros(h, w, CV_32F);
	mask_ = cv::Mat::zeros(h, w, CV_8U);
	process_ = cv::Mat::zeros(h, w, CV_8U);
}

void ThreephaseEngine::createCloud() {
	logDebug("Save to cloud");
	/*
	 * TODO: controlla che il risultato sia corretto prima di salvare la point cloud.
	 * A volte (causa delay telecamera) le immagini catturate non sono valide.
	 * Un'opzione per controllare se e` valido e` vedere se il numero di punti
	 * rispetta una soglia minima aspettata.
	 */
	const float zscale = options_["zscale"];
	const float mscale = options_["mscale"];
	const float zskew = options_["zskew"];
	const float scale = zscale - mscale;
	float skew = zskew - mscale;
	if (skew == 0.0) {
		skew = 1.0;
	}
	int width = depth_.cols;
	int height = depth_.rows;

	struct RawPoint {
		float x, y, z;
	};

	PointCloud& cloud = *cloud_;
	/*
	 * TODO: need optimization. cloud_ memory can be reserved in
	 * initialization, then only size change. But how? std::vector
	 * do not permit to change size without deallocating.
	 */
	cloud.clear();

	for (int y = 0; y < height; ++y) {
		float planephase = 0.5 + float(y - (height / 2)) / skew;
		for (int x = 0; x < width; ++x) {
			if (!mask_.at<uchar>(y, x)) {
				float z = (unwrapped_.at<float>(y, x) - planephase) * scale;
				Point p;
				p.x = x;
				p.y = y;
				p.z = z;
				p.r = 0xff;
				p.g = 0xff;
				p.b = 0xff;
				cloud.push_back(p);
			}
		}
	}

	logDebug("Points: %u", cloud.size());
}

void ThreephaseEngine::setOption(const std::string& key, const float& value) {
	if (!options_.count(key)) {
		throw std::invalid_argument("Threephase option does not exist: " + key);
	}
	options_[key] = value;
}

void ThreephaseEngine::setParameter(const std::string& key, const std::string& value) {
	std::vector<std::string> k;
	boost::split(k, key, boost::is_any_of(":"));
	if (k[0] == "tuning" && k.size() == 2) {
		return this->setOption(k[1], boost::lexical_cast<float>(value));
	}
	throw std::invalid_argument("Threephase invalid parameter: " + key);
}

void ThreephaseEngine::setImage(const std::string& id, const cv::Mat& image) {
	std::vector<std::string> params;
	boost::split(params, id, boost::is_any_of(":"));
	auto orientation = params[0];
	auto phase = boost::lexical_cast<size_t>(params[1]);
	this->setImage(orientation, phase, image);
}

} /* namespace threescanner */


/* Copyright (c) 2021, the adamantine authors.
 *
 * This file is subject to the Modified BSD License and may not be distributed
 * without copyright and license information. Please refer to the file LICENSE
 * for the text and further information on this license.
 */

#ifndef EXPERIMENTAL_DATA_HH
#define EXPERIMENTAL_DATA_HH

#include <deal.II/base/point.h>
#include <deal.II/dofs/dof_handler.h>
#include <deal.II/lac/la_parallel_vector.h>

#include <boost/property_tree/ptree.hpp>

namespace adamantine
{
/**
 * Structure to encapsulate a point cloud and the values associated to each
 * point.
 */
template <int dim>
struct PointsValues
{
  std::vector<dealii::Point<dim>> points;
  std::vector<double> values;
};

/**
 * Read the experimental data (IR point cloud) and return a vector of
 * PointsValues. The size of the vector is equal to the number of frames.
 */
template <int dim>
std::vector<PointsValues<dim>> read_experimental_data_point_cloud(
    MPI_Comm const &communicator,
    boost::property_tree::ptree const &experiment_database);

/**
 * Fill the @p temperature Vector given @p points_values.
 */
template <int dim>
std::pair<std::vector<int>, std::vector<int>> set_with_experimental_data(
    PointsValues<dim> const &points_values,
    dealii::DoFHandler<dim> const &dof_handler,
    dealii::LinearAlgebra::distributed::Vector<double> &temperature);

/**
 * Parse the log file that maps experimental frames to their times.
 * The log file has a headerless CSV format with the frame index (int) as the
 * first entry per line followed by the timestamp for each camera. For now we
 * assume that the first frame for each camera is synced so that we can
 * re-reference the times to a fixed offset from the first frame time. The
 * function returns a vector containing the frame timings for each camera, i.e.
 * the first index is the camera index and the second is the frame index. The
 * frame indices in the output are such that the 'first frame' listed in the
 * input file is index 0.
 */
std::vector<std::vector<double>>
read_frame_timestamps(boost::property_tree::ptree const &experiment_database);

/**
 * Data structure representing a ray.
 */
template <int dim>
struct Ray
{
  /**
   * Origin of the ray.
   */
  dealii::Point<dim> origin;
  /**
   * Direction of propagation of the ray.
   */
  dealii::Tensor<1, dim> direction;
};

/**
 * This class performs ray tracing
 */
class RayTracing
{
public:
  /**
   * This class assumes the geometry is 3D.
   */
  static int constexpr dim = 3;

  /**
   * Constructor. Read all the rays and the values from all the frames.
   */
  RayTracing(boost::property_tree::ptree const &experiment_database);

  /**
   * Perform the ray tracing given a DoFHandler @p dof_handler. @p frame is the
   * relative frame the number, i.e., current frame id - first frame id
   */
  PointsValues<dim> get_intersection(dealii::DoFHandler<dim> const &dof_handler,
                                     unsigned int frame);

private:
  /**
   * Rays associated to each frame.
   */
  std::vector<std::vector<Ray<dim>>> _rays_all_frames;
  /**
   * Values associated to the rays of each frame.
   */
  std::vector<std::vector<double>> _values_all_frames;
};

} // namespace adamantine

#endif


/*
 * ublas_types.hpp
 *
 *  Created on: Jan 5, 2009
 *      Author: rubensmits
 */

#ifndef UBLAS_TYPES_HPP_
#define UBLAS_TYPES_HPP_

#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/matrix_proxy.hpp>
#include <boost/numeric/ublas/vector_proxy.hpp>
#include "kdl/frames.hpp"
#include "kdl/tree.hpp"
#include "kdl/chain.hpp"
#include "kdl/jacobian.hpp"
#include "kdl/jntarray.hpp"

namespace iTaSC{

namespace ublas = boost::numeric::ublas;
using KDL::Twist;
using KDL::Frame;
using KDL::Joint;
using KDL::Inertia;
using KDL::SegmentMap;
using KDL::Tree;
using KDL::JntArray;
using KDL::Jacobian;
using KDL::Segment;
using KDL::Rotation;
using KDL::Vector;
using KDL::Chain;

#define u_scalar double
#define u_vector ublas::vector<u_scalar>
#define u_zero_vector ublas::zero_vector<u_scalar>
#define u_matrix ublas::matrix<u_scalar>
#define u_matrix6 ublas::matrix<u_scalar,6,6>
#define u_identity_matrix ublas::identity_matrix<u_scalar>
#define u_scalar_vector ublas::scalar_vector<u_scalar>
#define u_zero_matrix ublas::zero_matrix<u_scalar>
#define u_vector6 ublas::bounded_vector<u_scalar,6>

inline static int changeBase(const u_matrix& J_in, const Frame& T, u_matrix& J_out) {

    if (J_out.size1() != 6 || J_in.size1() != 6 || J_in.size2() != J_out.size2())
        return -1;
    for (unsigned int j = 0; j < J_in.size2(); ++j) {
        ublas::matrix_column<const u_matrix > Jj_in = column(J_in,j);
        ublas::matrix_column<u_matrix > Jj_out = column(J_out,j);
		Twist arg;
        for(unsigned int i=0;i<6;++i)
            arg(i)=Jj_in(i);
		Twist tmp(T*arg);
        for(unsigned int i=0;i<6;++i)
                    Jj_out(i)=tmp(i);
    }
    return 0;
}
inline static int changeBase(const ublas::matrix_range<u_matrix >& J_in, const Frame& T, ublas::matrix_range<u_matrix >& J_out) {

    if (J_out.size1() != 6 || J_in.size1() != 6 || J_in.size2() != J_out.size2())
        return -1;
    for (unsigned int j = 0; j < J_in.size2(); ++j) {
        ublas::matrix_column<const ublas::matrix_range<u_matrix > > Jj_in = column(J_in,j);
        ublas::matrix_column<ublas::matrix_range<u_matrix > > Jj_out = column(J_out,j);
		Twist arg;
        for(unsigned int i=0;i<6;++i)
            arg(i)=Jj_in(i);
		Twist tmp(T*arg);
        for(unsigned int i=0;i<6;++i)
                    Jj_out(i)=tmp(i);
    }
    return 0;
}

}
#endif /* UBLAS_TYPES_HPP_ */