#include "Day22-SlamShuffle.h"

#include <AdventOfCodeCommon/DisableLibraryWarningsMacros.h>

__BEGIN_LIBRARIES_DISABLE_WARNINGS
#include <boost/algorithm/string.hpp>
__END_LIBRARIES_DISABLE_WARNINGS

namespace AdventOfCode
{
namespace Year2019
{
namespace Day22
{

ShuffleInstruction createShuffleInstruction(const std::vector<std::string>& tokens)
{
    ShuffleType type;
    int arg = 0;

    if (tokens.back() == "stack")
    {
        type = ShuffleType::DEAL_INTO_NEW_STACK;
    }
    else if (tokens.front() == "cut")
    {
        type = ShuffleType::CUT;
        arg = std::stoi(tokens.back());
    }
    else
    {
        type = ShuffleType::DEAL_WITH_INCREMENT;
        arg = std::stoi(tokens.back());
    }

    return ShuffleInstruction{type, arg};
}

std::vector<ShuffleInstruction> createShuffleInstructions(const std::vector<std::string>& shuffleInstructionLines)
{
    std::vector<ShuffleInstruction> shuffleInstructions;

    for (const auto& line : shuffleInstructionLines)
    {
        std::vector<std::string> tokens;
        boost::split(tokens, line, boost::is_any_of(" "));

        ShuffleInstruction shuffleInstruction = createShuffleInstruction(tokens);

        shuffleInstructions.push_back(std::move(shuffleInstruction));
    }

    return shuffleInstructions;
}

std::vector<unsigned> cardsAfterShuffle(const std::vector<std::string>& shuffleInstructionLines, unsigned deckSize)
{
    std::vector<ShuffleInstruction> shuffleInstructions = createShuffleInstructions(shuffleInstructionLines);

    SlamShuffler shuffler{shuffleInstructions, deckSize, 1};

    return shuffler.getAllCardsAfterSingleIteration();
}

BigNumber cardAtPositionAfterMultipleShuffles(const std::vector<std::string>& shuffleInstructionLines, BigNumber deckSize, BigNumber numIterations, BigNumber position)
{
    std::vector<ShuffleInstruction> shuffleInstructions = createShuffleInstructions(shuffleInstructionLines);

    SlamShuffler shuffler{shuffleInstructions, deckSize, numIterations};

    return shuffler.getCardAtPosition(position);
}

}
}
}


// Copyright Louis Dionne 2013-2017
// 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_HANA_EXAMPLE_CPPCON_2014_MATRIX_COMPARABLE_HPP
#define BOOST_HANA_EXAMPLE_CPPCON_2014_MATRIX_COMPARABLE_HPP

#include "matrix.hpp"

#include <boost/hana/all.hpp>
#include <boost/hana/bool.hpp>
#include <boost/hana/equal.hpp>
#include <boost/hana/eval_if.hpp>
#include <boost/hana/zip_with.hpp>

namespace boost { namespace hana {
    template <unsigned R1, unsigned C1, unsigned R2, unsigned C2>
    struct equal_impl<cppcon::Matrix<R1, C1>, cppcon::Matrix<R2, C2>> {
        template <typename M1, typename M2>
        static constexpr auto apply(M1 const& m1, M2 const& m2) {
            return eval_if(bool_c<R1 == R2 && C1 == C2>,
                [&](auto _) {
                    return all(zip_with(_(equal), cppcon::rows(m1),
                                                  cppcon::rows(m2)));
                },
                [] { return false_c; }
            );
        }
    };
}}

#endif // !BOOST_HANA_EXAMPLE_CPPCON_2014_MATRIX_COMPARABLE_HPP


/*
 * reclayer.cpp
 *
 * Feed-forward:
 *  state:
 *    zz_h(t) = ww_hh * hh(t-1) + ww_xh*x(t) + b_h
 *    hh(t)   = state_activation_function(zz_h(t))
 *  output:
 *    zz_a(t) = ww_ha * hh(t) + b_a
 *    a(t)    = output_activation_function(zz_a(t))
 *
 * Back propagation:
 *   output:
 *    delta_a(t)  = elem_prod(gradient(C, a(t)), activation_derivative(zz_a(t)))
 *    dC/d b_a(t)   = delta_a(t)
 *    dC/d ww_ha(t) = hh(t) * delta_a(t)
 *    total_gradient(C, hh(t)) = gradient(C, hh(t)) + transp(ww_ha) * delta_a(t)
 *  state:
 *    delta_h(t) = elem_prod(total_gradient(C, hh(t)), activation_derivative(zz_h(t)))
 *    dC/d b_h(t) = delta_h(t)
 *    dC/d ww_xh(t) = x(t) * delta_h(t)
 *    dC/d ww_hh(t) = hh(t-1) * delta_h(t)
 *    gradient(C, hh(t-1)) = transp(ww_hh) * delta_h(t)
 *    gradient(C, x(t)) = transp(ww_xh) * delta_h(t)
 */
#include <boost/assert.hpp>

#include "core/utils.h"
#include "core/random.h"
#include "core/functions.h"
#include "reclayer.h"

using namespace std;
using namespace boost;
using namespace yann;

namespace yann {

////////////////////////////////////////////////////////////////////////////////////////////////
//
// RecurrentLayer_Context implementation
//
class RecurrentLayer_Context :
    public Layer::Context
{
  typedef Layer::Context Base;

  friend class RecurrentLayer;

public:
  RecurrentLayer_Context(
      const MatrixSize & state_size,
      const MatrixSize & output_size,
      const MatrixSize & max_batch_size) :
    Base(output_size, max_batch_size),
    _pos(0)
  {
    init(state_size);
  }

  RecurrentLayer_Context(
      const MatrixSize & state_size,
      const RefVectorBatch & output) :
    Base(output),
    _pos(0)
  {
    init(state_size);
  }

  // Layer::Context overwrites
  virtual void reset_state()
  {
    Base::reset_state();

    _pos = 0;
  }

private:
  void init(const MatrixSize & state_size)
  {
    YANN_CHECK_GT(state_size, 0);

    _hh.resize(get_batch_size(), state_size);
    _zz_h.resize(get_batch_size(), state_size);
    _zz_a.resize(get_batch_size(), get_output_size());
  }

protected:
  MatrixSize _pos;
  VectorBatch  _hh;       // state (save it for each step)
  VectorBatch  _zz_h;
  VectorBatch  _zz_a;
}; // class RecurrentLayer_Context

////////////////////////////////////////////////////////////////////////////////////////////////
//
// RecurrentLayer_TrainingContext implementation
//
class RecurrentLayer_TrainingContext :
    public RecurrentLayer_Context
{
  typedef RecurrentLayer_Context Base;
  friend class RecurrentLayer;

public:
  RecurrentLayer_TrainingContext(
      const MatrixSize & input_size,
      const MatrixSize & state_size,
      const MatrixSize & output_size,
      const MatrixSize & batch_size,
      const unique_ptr<Layer::Updater> & updater) :
    Base(state_size, output_size, batch_size),
    _ww_hh_updater(updater->copy()),
    _ww_xh_updater(updater->copy()),
    _bb_h_updater(updater->copy()),
    _ww_ha_updater(updater->copy()),
    _bb_a_updater(updater->copy())
  {
    init(input_size, state_size);
  }

  RecurrentLayer_TrainingContext(
      const MatrixSize & input_size,
      const MatrixSize & state_size,
      const RefVectorBatch & output,
      const unique_ptr<Layer::Updater> & updater) :
    Base(state_size, output),
    _ww_hh_updater(updater->copy()),
    _ww_xh_updater(updater->copy()),
    _bb_h_updater(updater->copy()),
    _ww_ha_updater(updater->copy()),
    _bb_a_updater(updater->copy())
  {
    init(input_size, state_size);
  }

  // Layer::Context overwrites
  virtual void start_epoch()
  {
    YANN_SLOW_CHECK(_ww_hh_updater);
    YANN_SLOW_CHECK(_ww_xh_updater);
    YANN_SLOW_CHECK(_bb_h_updater);
    YANN_SLOW_CHECK(_ww_ha_updater);
    YANN_SLOW_CHECK(_bb_a_updater);

    Base::start_epoch();

    _ww_hh_updater->start_epoch();
    _ww_xh_updater->start_epoch();
    _bb_h_updater->start_epoch();
    _ww_ha_updater->start_epoch();
    _bb_a_updater->start_epoch();
  }

  virtual void reset_state()
  {
    YANN_SLOW_CHECK(_ww_hh_updater);
    YANN_SLOW_CHECK(_ww_xh_updater);
    YANN_SLOW_CHECK(_bb_h_updater);
    YANN_SLOW_CHECK(_ww_ha_updater);
    YANN_SLOW_CHECK(_bb_a_updater);

    Base::reset_state();

    _gradient_h.setZero();

    _delta_ww_hh.setZero();
    _delta_ww_xh.setZero();
    _delta_bb_h.setZero();
    _delta_ww_ha.setZero();
    _delta_bb_a.setZero();

    _ww_hh_updater->reset();
    _ww_xh_updater->reset();
    _bb_h_updater->reset();
    _ww_ha_updater->reset();
    _bb_a_updater->reset();
  }

private:
  void init(const MatrixSize & input_size, const MatrixSize & state_size)
  {
    YANN_CHECK_GT(input_size, 0);
    YANN_CHECK_GT(state_size, 0);

    _delta_ww_hh.resize(state_size, state_size);
    _delta_ww_xh.resize(input_size, state_size);
    _delta_bb_h.resize(state_size);
    _delta_ww_ha.resize(state_size, get_output_size());
    _delta_bb_a.resize(get_output_size());

    _gradient_h.resize(state_size);
    _delta_h.resize(state_size);
    _delta_a.resize(get_output_size());

    _sigma_derivative_zz_h.resize(state_size);
    _sigma_derivative_zz_a.resize(get_output_size());

    _ww_hh_updater->init(_delta_ww_hh.rows(), _delta_ww_hh.cols());
    _ww_xh_updater->init(_delta_ww_xh.rows(), _delta_ww_xh.cols());
    _bb_h_updater->init(1, _delta_bb_h.size()); // RowMajor
    _ww_ha_updater->init(_delta_ww_ha.rows(), _delta_ww_ha.cols());
    _bb_a_updater->init(1, _delta_bb_a.size()); // RowMajor
  }

private:
  Matrix _delta_ww_hh;
  Matrix _delta_ww_xh;
  Vector _delta_bb_h;
  Matrix _delta_ww_ha;
  Vector _delta_bb_a;

  Vector _gradient_h;
  Vector _delta_h;
  Vector _delta_a;

  Vector _sigma_derivative_zz_h;
  Vector _sigma_derivative_zz_a;

  unique_ptr<Layer::Updater> _ww_hh_updater;
  unique_ptr<Layer::Updater> _ww_xh_updater;
  unique_ptr<Layer::Updater> _bb_h_updater;
  unique_ptr<Layer::Updater> _ww_ha_updater;
  unique_ptr<Layer::Updater> _bb_a_updater;
}; // class RecurrentLayer_TrainingContext

}; // namespace yann

////////////////////////////////////////////////////////////////////////////////////////////////
//
// yann::RecurrentLayer implementation
//
yann::RecurrentLayer::RecurrentLayer(
    const MatrixSize & input_size,
    const MatrixSize & state_size,
    const MatrixSize & output_size) :
    _state_activation_function(new SigmoidFunction()),
    _output_activation_function(new SigmoidFunction())
{
  YANN_CHECK_GT(input_size, 0);
  YANN_CHECK_GT(state_size, 0);
  YANN_CHECK_GT(output_size, 0);

  _ww_hh.resize(state_size, state_size);
  _ww_xh.resize(input_size, state_size);
  _bb_h.resize(state_size);

  _ww_ha.resize(state_size, output_size);
  _bb_a.resize(output_size);
}

yann::RecurrentLayer::~RecurrentLayer()
{
}

void yann::RecurrentLayer::set_values(
    const Matrix & ww_hh, const Matrix & ww_xh, const Vector & bb_h,
    const Matrix & ww_ha, const Vector & bb_a)
{
  YANN_CHECK(is_same_size(ww_hh, _ww_hh));
  YANN_CHECK(is_same_size(ww_xh, _ww_xh));
  YANN_CHECK(is_same_size(bb_h, _bb_h));
  YANN_CHECK(is_same_size(ww_ha, _ww_ha));
  YANN_CHECK(is_same_size(bb_a, _bb_a));
  _ww_hh = ww_hh;
  _ww_xh = ww_xh;
  _bb_h  = bb_h;
  _ww_ha = ww_ha;
  _bb_a  = bb_a;
}

void yann::RecurrentLayer::set_activation_functions(
    const std::unique_ptr<ActivationFunction> & state_activation_function,
    const std::unique_ptr<ActivationFunction> & output_activation_function)
{
  YANN_CHECK(state_activation_function);
  YANN_CHECK(output_activation_function);
  _state_activation_function = state_activation_function->copy();
  _output_activation_function = output_activation_function->copy();
}

// Layer overwrites
bool yann::RecurrentLayer::is_valid() const
{
  if(!Base::is_valid()) {
    return false;
  }
  if(!_state_activation_function || !_output_activation_function) {
    return false;
  }
  if(_ww_hh.rows() != _ww_hh.cols()) {
    return false;
  }
  if(_ww_xh.cols() != _ww_hh.cols()) {
    return false;
  }
  if(_ww_hh.cols() != _bb_h.size()) {
    return false;
  }
  if(_ww_hh.cols() != _ww_ha.rows()) {
    return false;
  }
  if(_ww_ha.cols() != _bb_a.size()) {
    return false;
  }

  return true;
}

std::string yann::RecurrentLayer::get_name() const
{
  return "RecurrentLayer";
}

string yann::RecurrentLayer::get_info() const
{
  YANN_CHECK(is_valid());

  ostringstream oss;
  oss << Base::get_info()
      << " state size: " << get_state_size()
      << ", state activation: " << _state_activation_function->get_info()
      << ", output activation: " << _output_activation_function->get_info()
      ;
  return oss.str();
}

bool yann::RecurrentLayer::is_equal(const Layer & other, double tolerance) const
{
  if(!Base::is_equal(other, tolerance)) {
    return false;
  }
  auto the_other = dynamic_cast<const RecurrentLayer*>(&other);
  if(the_other == nullptr) {
    return false;
  }
  // TOOD: add deep compare
  if(_state_activation_function->get_info() != the_other->_state_activation_function->get_info()) {
    return false;
  }
  if(_output_activation_function->get_info() != the_other->_output_activation_function->get_info()) {
    return false;
  }

  if(!_ww_hh.isApprox(the_other->_ww_hh, tolerance)) {
    return false;
  }
  if(!_ww_xh.isApprox(the_other->_ww_xh, tolerance)) {
    return false;
  }
  if(!_bb_h.isApprox(the_other->_bb_h, tolerance)) {
    return false;
  }
  if(!_ww_ha.isApprox(the_other->_ww_ha, tolerance)) {
    return false;
  }
  if(!_bb_a.isApprox(the_other->_bb_a, tolerance)) {
    return false;
  }
  return true;
}

MatrixSize yann::RecurrentLayer::get_input_size() const
{
  YANN_SLOW_CHECK(is_valid());
  return _ww_xh.rows();
}

MatrixSize yann::RecurrentLayer::get_state_size() const
{
  YANN_SLOW_CHECK(is_valid());
  return _ww_xh.cols();
}

MatrixSize yann::RecurrentLayer::get_output_size() const
{
  YANN_SLOW_CHECK(is_valid());
  return _ww_ha.cols();
}

unique_ptr<Layer::Context> yann::RecurrentLayer::create_context(const MatrixSize & batch_size) const
{
  YANN_CHECK(is_valid());
  return make_unique<RecurrentLayer_Context>(get_state_size(), get_output_size(), batch_size);
}
unique_ptr<Layer::Context> yann::RecurrentLayer::create_context(const RefVectorBatch & output) const
{
  YANN_CHECK(is_valid());
  return make_unique<RecurrentLayer_Context>(get_state_size(), output);
}
unique_ptr<Layer::Context> yann::RecurrentLayer::create_training_context(
    const MatrixSize & batch_size, const std::unique_ptr<Layer::Updater> & updater) const
{
  YANN_CHECK(is_valid());
  YANN_CHECK(updater);
  return make_unique<RecurrentLayer_TrainingContext>(
      get_input_size(), get_state_size(), get_output_size(), batch_size, updater);
}
unique_ptr<Layer::Context> yann::RecurrentLayer::create_training_context(
    const RefVectorBatch & output, const std::unique_ptr<Layer::Updater> & updater) const
{
  YANN_CHECK(is_valid());
  YANN_CHECK(updater);
  return make_unique<RecurrentLayer_TrainingContext>(
      get_input_size(), get_state_size(), output, updater);
}

template<typename InputType>
void yann::RecurrentLayer::feedforward_internal(
    const InputType & input,
    Context * context,
    enum OperationMode mode) const
{
  auto ctx = dynamic_cast<RecurrentLayer_Context *>(context);
  YANN_CHECK(ctx);
  YANN_CHECK(is_valid());
  YANN_CHECK_LE(get_batch_size(input) + ctx->_pos, ctx->get_batch_size());
  YANN_CHECK_LE(get_batch_size(input), get_batch_size(ctx->get_output()));
  YANN_CHECK_EQ(get_batch_item_size(input), get_input_size());
  YANN_SLOW_CHECK(is_same_size(ctx->_zz_h, ctx->_hh));
  YANN_SLOW_CHECK(is_same_size(ctx->_zz_a, ctx->get_output()));

  for(MatrixSize ii = 0; ii < get_batch_size(input); ++ii, ++ctx->_pos) {
    // hh(t) = state_activation_function(ww_hh * hh(t-1) + ww_xh*x(t) + b_h)  // state
    auto in = get_batch(input, ii);
    auto out = get_batch(ctx->get_output(), ctx->_pos);
    auto hh = get_batch(ctx->_hh, ctx->_pos);
    auto zz_h = get_batch(ctx->_zz_h, ctx->_pos);
    auto zz_a = get_batch(ctx->_zz_a, ctx->_pos);

    if(ctx->_pos > 0) {
      auto hh_prev = get_batch(ctx->_hh, ctx->_pos - 1);
      zz_h = MatrixFunctions<InputType>::product(hh_prev, _ww_hh) +
             MatrixFunctions<InputType>::product(in, _ww_xh) +
             _bb_h;
    } else {
      zz_h = MatrixFunctions<InputType>::product(in, _ww_xh) +
            _bb_h;
    }
    _state_activation_function->f(zz_h, hh, Operation_Assign);

    // a(t) = output_activation_function(ww_ha * hh(t) + b_a) // output
    zz_a = MatrixFunctions<InputType>::product(hh, _ww_ha) +
        _bb_a;
    _state_activation_function->f(zz_a, out, mode);
  }
}

void yann::RecurrentLayer::feedforward(
    const RefConstVectorBatch & input,
    Context * context,
    enum OperationMode mode) const
{
  feedforward_internal(input, context, mode);
}

void yann::RecurrentLayer::feedforward(
    const RefConstSparseVectorBatch & input,
    Context * context,
    enum OperationMode mode) const
{
  feedforward_internal(input, context, mode);
}

template<typename InputType>
void yann::RecurrentLayer::backprop_internal(
    const RefConstVectorBatch & gradient_output,
    const InputType & input,
    optional<RefVectorBatch> gradient_input,
    Context * context) const
{
  auto ctx = dynamic_cast<RecurrentLayer_TrainingContext *>(context);
  YANN_CHECK(ctx);
  YANN_CHECK(is_valid());
  YANN_CHECK_GT(get_batch_size(gradient_output), 0);
  YANN_CHECK_LE(get_batch_size(gradient_output), ctx->_pos);
  YANN_CHECK_EQ(get_batch_item_size(gradient_output), get_output_size());
  YANN_CHECK_EQ(get_batch_size(input), get_batch_size(gradient_output));
  YANN_CHECK_EQ(get_batch_item_size(input), get_input_size());
  YANN_CHECK_EQ(get_batch_item_size(input), get_input_size());
  YANN_CHECK(!gradient_input || is_same_size(input, *gradient_input));

  for(MatrixSize ii = get_batch_size(gradient_output) - 1; ii >= 0 && (--ctx->_pos) >= 0; --ii) {
    auto gradient_out = get_batch(gradient_output, ii);
    auto in = get_batch(input, ii);
    auto hh = get_batch(ctx->_hh, ctx->_pos);
    auto zz_h = get_batch(ctx->_zz_h, ctx->_pos);
    auto zz_a = get_batch(ctx->_zz_a, ctx->_pos);

    //
    // output:
    //
    auto & sigma_derivative_zz_a = ctx->_sigma_derivative_zz_a;
    auto & delta_a = ctx->_delta_a;
    auto & delta_ww_ha = ctx->_delta_ww_ha;
    auto & delta_bb_a = ctx->_delta_bb_a;
    auto & gradient_h = ctx->_gradient_h;

    // delta_a(t)    = elem_prod(gradient(C, a(t)), activation_derivative(zz_a(t)))
    YANN_SLOW_CHECK(is_same_size(zz_a, sigma_derivative_zz_a));
    YANN_SLOW_CHECK(is_same_size(zz_a, gradient_out));
    YANN_SLOW_CHECK(is_same_size(zz_a, delta_a));
    _output_activation_function->derivative(zz_a, sigma_derivative_zz_a);
    delta_a.array() = gradient_out.array() * sigma_derivative_zz_a.array();

    // dC/d b_a(t)   = delta_a(t)
    YANN_SLOW_CHECK(is_same_size(delta_bb_a, delta_a));
    delta_bb_a.noalias()  += delta_a;

    // dC/d ww_ha(t) = hh(t) * delta_a(t)
    YANN_SLOW_CHECK(is_same_size(delta_ww_ha, _ww_ha));
    delta_ww_ha.noalias() += MatrixFunctions<InputType>::product(hh.transpose(), delta_a);

    // total_gradient(C, hh(t)) = gradient(C, hh(t)) + transp(ww_ha) * delta_a(t)
    gradient_h.noalias()  += MatrixFunctions<InputType>::product(delta_a, _ww_ha.transpose());

    //
    // state:
    //
    auto & sigma_derivative_zz_h = ctx->_sigma_derivative_zz_h;
    auto & delta_h = ctx->_delta_h;
    auto & delta_ww_xh = ctx->_delta_ww_xh;
    auto & delta_ww_hh = ctx->_delta_ww_hh;
    auto & delta_bb_h = ctx->_delta_bb_h;

    // delta_h(t) = elem_prod(total_gradient(C, hh(t)), activation_derivative(zz_h(t)))
    YANN_CHECK(is_same_size(zz_h, sigma_derivative_zz_h));
    YANN_CHECK(is_same_size(zz_h, gradient_h));
    _state_activation_function->derivative(zz_h, sigma_derivative_zz_h);
    delta_h.array() = gradient_h.array() * sigma_derivative_zz_h.array();

    // dC/d b_h(t) = delta_h(t)
    YANN_CHECK(is_same_size(delta_bb_h, delta_h));
    delta_bb_h.noalias() += delta_h;

    // dC/d ww_xh(t) = x(t) * delta_h(t)
    YANN_SLOW_CHECK(is_same_size(delta_ww_xh, _ww_xh));
    delta_ww_xh += MatrixFunctions<InputType>::product(in.transpose(), delta_h);

    // dC/d ww_hh(t) = hh(t-1) * delta_h(t)
    if(ctx->_pos > 0) {
      auto prev_hh = get_batch(ctx->_hh, ctx->_pos - 1);
      YANN_SLOW_CHECK(is_same_size(delta_ww_hh, _ww_hh));
      delta_ww_hh.noalias() += MatrixFunctions<InputType>::product(prev_hh.transpose(), delta_h);
    }

    // gradient(C, hh(t-1)) = transp(ww_hh) * delta_h(t)
    if(ctx->_pos > 0) {
      gradient_h.noalias() = MatrixFunctions<InputType>::product(delta_h, _ww_hh.transpose());
    }

    // gradient(C, x(t)) = transp(ww_xh) * delta_h(t)
    if(gradient_input) {
      auto gradient_in = get_batch(*gradient_input, ii);
      gradient_in.noalias() = MatrixFunctions<InputType>::product(delta_h, _ww_xh.transpose());
    }
  }
}

void yann::RecurrentLayer::backprop(
    const RefConstVectorBatch & gradient_output,
    const RefConstVectorBatch & input,
    optional<RefVectorBatch> gradient_input,
    Context * context) const
{
  backprop_internal(gradient_output, input, gradient_input, context);
}

void yann::RecurrentLayer::backprop(
    const RefConstVectorBatch & gradient_output,
    const RefConstSparseVectorBatch & input,
    optional<RefVectorBatch> gradient_input,
    Context * context) const
{
  // <RefConstSparseMatrix> is required for MatrixFunctions<>::product
  backprop_internal<RefConstSparseMatrix>(gradient_output, input, gradient_input, context);
}

void yann::RecurrentLayer::init(enum InitMode mode, boost::optional<InitContext> init_context)
{
  switch (mode) {
  case InitMode_Zeros:
    _ww_hh.setZero();
    _ww_xh.setZero();
    _bb_h.setZero();
    _ww_ha.setZero();
    _bb_a.setZero();
    break;
  case InitMode_Random:
    {
      unique_ptr<RandomGenerator> gen01 = RandomGenerator::normal_distribution(0, 1,
          init_context ? optional<Value>(init_context->seed()) : boost::none);
      gen01->generate(_ww_hh);
      gen01->generate(_ww_xh);
      gen01->generate(_bb_h);
      gen01->generate(_ww_ha);
      gen01->generate(_bb_a);
    }
    break;
  }
}

void yann::RecurrentLayer::update(Context * context, const size_t & tests_num)
{
  auto ctx = dynamic_cast<RecurrentLayer_TrainingContext *>(context);
  YANN_CHECK(ctx);
  YANN_SLOW_CHECK(ctx->_ww_hh_updater);
  YANN_SLOW_CHECK(ctx->_ww_xh_updater);
  YANN_SLOW_CHECK(ctx->_bb_h_updater);
  YANN_SLOW_CHECK(ctx->_ww_ha_updater);
  YANN_SLOW_CHECK(ctx->_bb_a_updater);
  YANN_SLOW_CHECK(is_same_size(_ww_hh, ctx->_delta_ww_hh));
  YANN_SLOW_CHECK(is_same_size(_ww_xh, ctx->_delta_ww_xh));
  YANN_SLOW_CHECK(is_same_size(_bb_h, ctx->_delta_bb_h));
  YANN_SLOW_CHECK(is_same_size(_ww_ha, ctx->_delta_ww_ha));
  YANN_SLOW_CHECK(is_same_size(_bb_a, ctx->_delta_bb_a));

  ctx->_ww_hh_updater->update(ctx->_delta_ww_hh, tests_num, _ww_hh);
  ctx->_ww_xh_updater->update(ctx->_delta_ww_xh, tests_num, _ww_xh);
  ctx->_bb_h_updater->update(ctx->_delta_bb_h, tests_num, _bb_h);
  ctx->_ww_ha_updater->update(ctx->_delta_ww_ha, tests_num, _ww_ha);
  ctx->_bb_a_updater->update(ctx->_delta_bb_a, tests_num, _bb_a);
}

// the format is (wh:<_ww_hh>,wx:<_ww_xh>,bh:<_bb_h>,wa:<_ww_ha>,ba:<_bb_a>)
void yann::RecurrentLayer::read(std::istream & is)
{
  Base::read(is);

  read_char(is, '(');
  read_object(is, "wh", _ww_hh);
  read_char(is, ',');
  read_object(is, "wx", _ww_xh);
  read_char(is, ',');
  read_object(is, "bh", _bb_h);
  read_char(is, ',');
  read_object(is, "wa", _ww_ha);
  read_char(is, ',');
  read_object(is, "ba", _bb_a);
  read_char(is, ')');
}

// the format is (wh:<_ww_hh>,wx:<_ww_xh>,bh:<_bb_h>,wa:<_ww_ha>,ba:<_bb_a>)
void yann::RecurrentLayer::write(std::ostream & os) const
{
  Base::write(os);

  os << "(";
  write_object(os, "wh", _ww_hh);
  os << ",";
  write_object(os, "wx", _ww_xh);
  os << ",";
  write_object(os, "bh", _bb_h);
  os << ",";
  write_object(os, "wa", _ww_ha);
  os << ",";
  write_object(os, "ba", _bb_a);
  os << ")";
}


#include "NlMeans.hpp"

#include "thread/ThreadUtils.hpp"
#include "thread/ThreadPool.hpp"

#include "Logging.hpp"

#include <Eigen/Dense>
#include <iostream>
#include <vector>

namespace Tungsten {

Pixmap3f collaborativeRegression(const Pixmap3f &image, const Pixmap3f &guide,
        const std::vector<PixmapF> &features, const Pixmap3f &imageVariance,
        int F, int R, float k)
{
    int w = image.w();
    int h = image.h();
    int d = features.size() + 3;

    // We parallellize by dicing up the image into 32x32 tiles
    const int TileSize = 32;
    int padSize = TileSize + 2*F;

    struct Tile
    {
        Vec2i pos;
        Box2i dstRect;
        Pixmap3f result;
        PixmapF resultWeights;
        Tile(int x, int y) : pos(x, y) {}
    };

    std::vector<Tile> tiles;
    for (int tileY : range(0, h, TileSize))
        for (int tileX : range(0, w, TileSize))
            tiles.emplace_back(tileX, tileY);

    struct PerThreadData { Pixmap3f tmpBufA, tmpBufB; std::vector<PixmapF> weights; };
    std::vector<std::unique_ptr<PerThreadData>> threadData(ThreadUtils::idealThreadCount());

    ThreadUtils::pool->enqueue([&](uint32 i, uint32, uint32 threadId) {
        printProgressBar(i, tiles.size());

        if (!threadData[threadId]) {
            threadData[threadId].reset(new PerThreadData());
            threadData[threadId]->tmpBufA = Pixmap3f(padSize, padSize);
            threadData[threadId]->tmpBufB = Pixmap3f(padSize, padSize);
            for (int i = 0; i < (2*R + 1)*(2*R + 1); ++i)
                threadData[threadId]->weights.emplace_back(TileSize, TileSize);
        }
        auto &data = *threadData[threadId];
        Tile &tile = tiles[i];

        for (auto &w : data.weights)
            w.clear();

        Box2i srcRect(tile.pos, min(tile.pos + TileSize, Vec2i(w, h)));
        tile.dstRect = srcRect;
        tile.dstRect.grow(R);
        tile.dstRect.intersect(Box2i(Vec2i(0), Vec2i(w, h)));
        int dstW = tile.dstRect.diagonal().x(), dstH = tile.dstRect.diagonal().y();

        tile.result = Pixmap3f(dstW, dstH);
        tile.resultWeights = PixmapF(dstW, dstH);

        // Precompute weights for entire tile
        for (int dy = -R, idxW = 0; dy <= R; ++dy)
            for (int dx = -R; dx <= R; ++dx, ++idxW)
                nlMeansWeights(data.weights[idxW], data.tmpBufA, data.tmpBufB, guide, imageVariance,
                        srcRect, F, k, dx, dy, 2.0f);

        for (int y = srcRect.min().y(); y < srcRect.max().y(); ++y) {
            for (int x = srcRect.min().x(); x < srcRect.max().x(); ++x) {
                int x0 = max(x - R, 0), x1 = min(w, x + R + 1);
                int y0 = max(y - R, 0), y1 = min(h, y + R + 1);
                int n = (x1 - x0)*(y1 - y0);

                // Build weight matrix (W), feature matrix (X) and RHS (Y)
                Eigen::VectorXf W(n);
                Eigen::MatrixXf Y(n, 3);
                Eigen::MatrixXf X(n, d);

                for (int iy = y0; iy < y1; ++iy) {
                    for (int ix = x0; ix < x1; ++ix) {
                        int idxP = ix + iy*w;
                        int idx = (ix - x0) + (iy - y0)*(x1 - x0);

                        for (int i = 0; i < 3; ++i)
                            Y(idx, i) = image[idxP][i];

                        X(idx, 0) = 1.0f;
                        X(idx, 1) = ix - x;
                        X(idx, 2) = iy - y;
                        for (size_t i = 0; i < features.size(); ++i)
                            X(idx, i + 3) = features[i][idxP] - features[i][x + y*w];

                        int idxW = (ix - x + R) + (iy - y + R)*(2*R + 1);
                        W[idx] = data.weights[idxW][Vec2i(x, y) - tile.pos];
                    }
                }

                // Solve least squares system
                Eigen::VectorXf wSqrt = W.cwiseSqrt();
                Eigen::MatrixXf denoised = X*(wSqrt.asDiagonal()*X).colPivHouseholderQr().solve(wSqrt.asDiagonal()*Y);

                // Accumulate denoised patch into image
                for (int iy = y0; iy < y1; ++iy) {
                    for (int ix = x0; ix < x1; ++ix) {
                        Vec2i p = Vec2i(ix, iy) - tile.dstRect.min();
                        int idx = (ix - x0) + (iy - y0)*(x1 - x0);
                        tile.result       [p] += W[idx]*Vec3f(denoised(idx, 0), denoised(idx, 1), denoised(idx, 2));
                        tile.resultWeights[p] += W[idx];
                    }
                }
            }
        }

    }, tiles.size())->wait();

    // Gather results from all threads and divide by weights
    Pixmap3f result(w, h);
    PixmapF resultWeights(w, h);
    for (const auto &tile : tiles) {
        for (int y  : tile.dstRect.range(1)) {
            for (int x  : tile.dstRect.range(0)) {
                Vec2i p(x, y);
                result       [p] += tile.result       [p - tile.dstRect.min()];
                resultWeights[p] += tile.resultWeights[p - tile.dstRect.min()];
            }
        }
    }
    for (int j = 0; j < w*h; ++j)
        result[j] /= resultWeights[j];

    printProgressBar(tiles.size(), tiles.size());

    return std::move(result);
}

}


/*
@copyright Louis Dionne 2015
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

<% if input_size > 10 %>
    #define FUSION_MAX_VECTOR_SIZE <%= ((input_size + 9) / 10) * 10 %>
<% end %>

#include <boost/fusion/include/count_if.hpp>
#include <boost/fusion/include/make_vector.hpp>

#include <boost/hana/equal.hpp>
#include <boost/hana/integral_constant.hpp>
#include <boost/hana/mod.hpp>

struct is_even {
    template <typename N>
    constexpr auto operator()(N n) const {
        return n % boost::hana::int_c<2> == boost::hana::int_c<0>;
    }
};

int main() {
    auto vector = boost::fusion::make_vector(
        <%= (1..input_size).map { |n| "boost::hana::int_c<#{n}>" }.join(', ') %>
    );
    auto result = boost::fusion::count_if(vector, is_even{});
    (void)result;
}


/*
 * ElGammal.cpp
 *
 *  Created on: 03.10.2010
 *      Author: stephaniebayer
 */

#include "ElGammal.h"
#include "G_q.h"

#include <NTL/ZZ.h>
NTL_CLIENT

#include "Mod_p.h"
#include <stdio.h>
#include <time.h>
#include <vector>
#include <fstream>

ElGammal::ElGammal() {
	// TODO Auto-generated constructor stub

}

//Creates ElGammal with secret key s, public key p and group H
ElGammal::ElGammal(long s, Mod_p p, G_q H){
	G=H;
	sk = to_ZZ(s);
	pk = p;

}

//Creates ElGammal with secret key s, public key p and group H
ElGammal::ElGammal(ZZ s, Mod_p p, G_q H){
	G=H;
	sk = s;
	pk = p;

}

//Creates ElGammal with secret key s and group H, the public key is pk = gen^s , gen generator of H
ElGammal::ElGammal(long s, G_q H){
	Mod_p temp;
	G = H;
	sk = to_ZZ(s);
	temp = Mod_p(G.get_gen().get_val(), G.get_mod());
	pk = temp.expo(s);
}

//Creates ElGammal with secret key s and group H, the public key is pk = gen^s , gen generator of H
ElGammal::ElGammal(ZZ s, G_q H){
	Mod_p temp;
	G = H;
	sk = s;
	temp = Mod_p(G.get_gen().get_val(), G.get_mod());
	pk = temp.expo(s);
}

//Set the group to G_q with order o, G_q subset of G_mod_p and generator gen, secret key is s and pk = gen^s
ElGammal::ElGammal(Mod_p gen, long o, long  mod, long s){

	G = G_q(gen, o, mod);
	sk = to_ZZ(s);
	Mod_p temp;
	temp = Mod_p(gen.get_val(), mod);
	pk = temp.expo(s);
}

//Set the group to G_q with order o, G_q subset of G_mod_p and generator gen, secret key is s and pk = gen^s
ElGammal::ElGammal(Mod_p gen, long o, ZZ  mod, long s){

	G = G_q(gen, o, mod);
	sk = to_ZZ(s);
	Mod_p temp;
	temp = Mod_p(gen.get_val(), mod);
	pk = temp.expo(s);
}

//Set the group to G_q with order o, G_q subset of G_mod_p and generator gen, secret key is s and pk = gen^s
ElGammal::ElGammal(Mod_p gen, long o, ZZ  mod, ZZ s){

	G = G_q(gen, o, mod);
	sk = s;
	Mod_p temp;
	temp = Mod_p(gen.get_val(), mod);
	pk = temp.expo(s);
}

//Set the group to G_q with order o, G_q subset of G_mod_p and generator gen, secret key is s and pk = gen^s
ElGammal::ElGammal(Mod_p gen, ZZ o, ZZ  mod, long s){

	G = G_q(gen, o, mod);
	sk = to_ZZ(s);
	Mod_p temp;
	temp = Mod_p(gen.get_val(), mod);
	pk = temp.expo(s);
}

//Set the group to G_q with order o, G_q subset of G_mod_p and generator gen, secret key is s and pk = gen^s
ElGammal::ElGammal(Mod_p gen, ZZ o, ZZ  mod, ZZ s){

	G = G_q(gen, o, mod);
	sk = s;
	Mod_p temp;
	temp = Mod_p(gen.get_val(), mod);
	pk = temp.expo(s);
}

//Set the group to G_q with order o, G_q subset of G_mod_p and generator gen, secret key is s and public key p
ElGammal::ElGammal(Mod_p gen, long o, long  mod, long s, Mod_p p){

	G = G_q(gen, o, mod);
	sk = to_ZZ(s);
	pk = p;
}

//Set the group to G_q with order o, G_q subset of G_mod_p and generator gen, secret key is s and public key p
ElGammal::ElGammal(Mod_p gen, long o, ZZ  mod, long s, Mod_p p){

	G = G_q(gen, o, mod);
	sk = to_ZZ(s);
	pk = p;
}

//Set the group to G_q with order o, G_q subset of G_mod_p and generator gen, secret key is s and public key p
ElGammal::ElGammal(Mod_p gen, long o, ZZ  mod, ZZ s, Mod_p p){

	G = G_q(gen, o, mod);
	sk = s;
	pk = p;
}

//Set the group to G_q with order o, G_q subset of G_mod_p and generator gen, secret key is s and public key p
ElGammal::ElGammal(Mod_p gen, ZZ o, ZZ  mod, long s, Mod_p p){

	G = G_q(gen, o, mod);
	sk = to_ZZ(s);
	pk = p;
}

//Set the group to G_q with order o, G_q subset of G_mod_p and generator gen, secret key is s and public key p
ElGammal::ElGammal(Mod_p gen, ZZ o, ZZ  mod, ZZ s, Mod_p p){

	G = G_q(gen, o, mod);
	sk = s;
	pk = p;
}

//Set the group to G_q with order o, G_q subset of G_mod_p and generator gen, secret key is s and public key p
ElGammal::ElGammal( long o, long  mod, long s, Mod_p p){

	G = G_q( o, mod);
	sk = to_ZZ(s);
	pk = p;
}

//Set the group to G_q with order o and modular value mod, secret key is s and public key p
ElGammal::ElGammal( long o, ZZ  mod, long s, Mod_p p){

	G = G_q( o, mod);
	sk = to_ZZ(s);
	pk = p;
}

//Set the group to G_q with order o and modular value mod, secret key is s and public key p
ElGammal::ElGammal( long o, ZZ  mod, ZZ s, Mod_p p){

	G = G_q( o, mod);
	sk = s;
	pk = p;
}

//Set the group to G_q with order o and modular value mod, secret key is s and public key p
ElGammal::ElGammal( ZZ o, ZZ  mod, long s, Mod_p p){

	G = G_q( o, mod);
	sk = to_ZZ(s);
	pk = p;
}

//Set the group to G_q with order o and modular value mod, secret key is s and public key p
ElGammal::ElGammal( ZZ o, ZZ  mod, ZZ s, Mod_p p){

	G = G_q( o, mod);
	sk = to_ZZ(s);
	pk = p;
}

//Set the group to G_q with order o and modular value mod, secret key is s and public key pk = gen^s
ElGammal::ElGammal( long o, long  mod, long s){

	G = G_q( o, mod);
	sk = to_ZZ(s);
	Mod_p temp;
	temp = Mod_p(G.get_gen().get_val(), mod);
	pk = temp.expo(s);
}

//Set the group to G_q with order o and modular value mod, secret key is s and public key pk = gen^s
ElGammal::ElGammal( long o, ZZ  mod, long s){

	G = G_q( o, mod);
	sk = to_ZZ(s);
	Mod_p temp;
	temp = Mod_p(G.get_gen().get_val(), mod);
	pk = temp.expo(s);
}

//Set the group to G_q with order o and modular value mod, secret key is s and public key pk = gen^s
ElGammal::ElGammal( long o, ZZ  mod, ZZ s){

	G = G_q( o, mod);
	sk = s;
	Mod_p temp;
	temp = Mod_p(G.get_gen().get_val(), mod);
	pk = temp.expo(s);
}

//Set the group to G_q with order o and modular value mod, secret key is s and public key pk = gen^s
ElGammal::ElGammal( ZZ o, ZZ  mod, long s){

	G = G_q( o, mod);
	sk = to_ZZ(s);
	Mod_p temp;
	temp = Mod_p(G.get_gen().get_val(), mod);
	pk = temp.expo(s);
}

//Set the group to G_q with order o and modular value mod, secret key is s and public key pk = gen^s
ElGammal::ElGammal( ZZ o, ZZ  mod, ZZ s){

	G = G_q( o, mod);
	sk = s;
	Mod_p temp;
	temp = Mod_p(G.get_gen().get_val(), mod);
	pk = temp.expo(s);
}

ElGammal::~ElGammal() {
	// TODO Auto-generated destructor stub
}

//Access to the parameters
G_q ElGammal::get_group()const{
	return G;
}

Mod_p ElGammal::get_pk() const{

	return pk;
}

ZZ ElGammal::get_sk()const{

	return sk;
}

//functions to change parameters
void ElGammal::set_group(G_q H){

	G = H;
}

void ElGammal::set_sk(long s){

	sk = to_ZZ(s);
	pk = G.get_gen().expo( s);
}

void ElGammal::set_sk(ZZ s){

	sk = s;
	pk = G.get_gen().expo(s);
	string name = "example.txt";
	ofstream ost;
	ost.open(name.c_str(),ios::app);
	ost<<"private key and public key "<<sk<<" "<<pk<<endl;
}

//functions to encrypt value/element
Cipher_elg ElGammal::encrypt(Mod_p el){
	Cipher_elg c;
	Mod_p temp_1, temp_2;
	ZZ ran;
	SetSeed(to_ZZ(time(0)));
	ran = RandomBnd(G.get_ord());
	temp_1 = G.get_gen().expo(ran);
	temp_2 = pk.expo(ran)*el;
	c = Cipher_elg(temp_1,temp_2);
	return c;

}

Cipher_elg ElGammal::encrypt(ZZ m){
	Cipher_elg c;
	Mod_p temp_1, temp_2;
	ZZ ran;
	SetSeed(to_ZZ(time(0)));
	ran = RandomBnd(G.get_ord());
	cout<< ran << endl;
	temp_1 = G.get_gen().expo(ran);
	temp_2 = pk.expo(ran)*Mod_p(m,G.get_mod());
	c = Cipher_elg(temp_1,temp_2);
	return c;
}

Cipher_elg ElGammal::encrypt(long m){
	Cipher_elg c;
	Mod_p temp_1, temp_2;
	ZZ ran;
	SetSeed(to_ZZ(time(0)));
	ran = RandomBnd(G.get_ord());
	temp_1 = G.get_gen().expo(ran);
	temp_2 = pk.expo(ran)*Mod_p(m,G.get_mod());
	c = Cipher_elg(temp_1,temp_2);
	return c;
}

Cipher_elg ElGammal::encrypt(Mod_p el, long ran){
	Cipher_elg c;
	Mod_p temp_1, temp_2;
	temp_1 = G.get_gen().expo(ran);
	temp_2 = pk.expo(ran)*el;
	c = Cipher_elg(temp_1,temp_2);
	return c;

}

Cipher_elg ElGammal::encrypt(Mod_p el, ZZ ran){
	Cipher_elg c;
	Mod_p temp_1, temp_2;
	temp_1 = G.get_gen().expo(ran);
	temp_2 = pk.expo(ran)*el;
	c = Cipher_elg(temp_1,temp_2);
	return c;

}

Cipher_elg ElGammal::encrypt(ZZ m, long ran){
	Cipher_elg c;
	Mod_p temp_1, temp_2;
	temp_1 = G.get_gen().expo(ran);
	temp_2 = pk.expo(ran)*Mod_p(m,G.get_mod());
	c = Cipher_elg(temp_1,temp_2);
	return c;
}

Cipher_elg ElGammal::encrypt(ZZ m, ZZ ran){
	Cipher_elg c;
	Mod_p temp_1, temp_2;
	temp_1 = G.get_gen().expo(ran);
	temp_2 = pk.expo(ran)*Mod_p(m,G.get_mod());
	c = Cipher_elg(temp_1,temp_2);
	return c;
}

Cipher_elg ElGammal::encrypt(long m, long ran){
	Cipher_elg c;
	Mod_p temp_1, temp_2;
	temp_1 = G.get_gen().expo(ran);
	temp_2 = pk.expo(ran)*Mod_p(m,G.get_mod());
	c = Cipher_elg(temp_1,temp_2);
	return c;
}

Cipher_elg ElGammal::encrypt(long m, ZZ ran){
	Cipher_elg c;
	Mod_p temp_1, temp_2;
	temp_1 = G.get_gen().expo(ran);
	temp_2 = pk.expo(ran)*Mod_p(m,G.get_mod());
	c = Cipher_elg(temp_1,temp_2);
	return c;
}

//Decrypts the ciphertext c
Mod_p ElGammal::decrypt(Cipher_elg c){
	ZZ temp;
	ZZ mod = G.get_mod();
	temp = InvMod(c.get_u(),mod);
	temp = PowerMod(temp,sk, mod);
	temp = MulMod(temp,c.get_v(),mod);
	return temp;
}

//Assigment operator
void ElGammal::operator=(const ElGammal& el){

	G = el.get_group();
	sk = el.get_sk();
	pk = el.get_pk();
}


#ifndef TRIUMF_BNMR_SRF_NONLOCAL_HPP
#define TRIUMF_BNMR_SRF_NONLOCAL_HPP

// C++ standard library headers
#include <algorithm>
#include <cmath>
#include <limits>
#include <numeric>
#include <vector>

// Boost headers
#include <boost/math/interpolators/pchip.hpp>
#include <boost/math/quadrature/tanh_sinh.hpp>

// triumf++ headers
#include <triumf/bnmr/nuclei.hpp>
#include <triumf/nmr/dipole_dipole.hpp>
#include <triumf/nmr/nuclei.hpp>
#include <triumf/numpy.hpp>
#include <triumf/srim/pdf.hpp>
#include <triumf/superconductivity/bcs.hpp>
#include <triumf/superconductivity/pippard.hpp>

// ROOT headers
#include <ROOT/RCsvDS.hxx>
#include <ROOT/RDF/RInterface.hxx>
#include <ROOT/RDataFrame.hxx>

// TRIUMF: Canada's particle accelerator centre
namespace triumf {

// β-detected nuclear magnetic resonance (β-NMR)
namespace bnmr {

// superconducting radio-frequency (SRF) materials
namespace srf {

// nonlocal electrodynamics
namespace nonlocal {

/// model slr rate
template <typename T = double>
T slr_rate_z(T z, T temperature, T critical_temperature, T gap_meV, T xi_0,
             T mean_free_path, T lambda_0, T exponent, T applied_field,
             T dipole_field, T correlation_rate, T slr_constant, T slr_exponent,
             T surface_thickness, T surface_rate, T electron_phonon_coupling) {
  // correct depth for the surface layer
  T _z_ = z - surface_thickness;
  if (_z_ < 0.0) {
    return surface_rate;
  } else {
    // convert to values in the weak coupling limit
    // see e.g., Eqs. (1) & (2) in:
    // http://dx.doi.org/10.1103/PhysRevB.87.104508
    T lambda_0_wc = xi_0 * electron_phonon_coupling;
    T xi_0_wc = lambda_0 / std::sqrt(electron_phonon_coupling);
    // calculate the local field from the screening profile
    T screened_field =
        temperature > critical_temperature
            ? applied_field
            : triumf::superconductivity::pippard::field_penetration<T>(
                  _z_, temperature, critical_temperature, gap_meV, xi_0_wc,
                  mean_free_path, lambda_0_wc, exponent, applied_field);
    // calculate the dipole-dipole SLR rate in the superconducting state
    T dd_rate = triumf::nmr::dipole_dipole::slr_rate<T>(
        screened_field, dipole_field, correlation_rate,
        triumf::bnmr::nuclei::lithium_8<T>::gyromagnetic_ratio(),
        triumf::nmr::nuclei::niobium_93<T>::gyromagnetic_ratio());
    // calculate the SLR rate in the normal state
    T ns_rate = slr_constant * std::pow(temperature, slr_exponent);
    // return the "surface" contribution at shallow depths
    return dd_rate + ns_rate;
  }
}

/// depth-resolved analyzer
template <typename T = double> class DepthResolvedAnalyzer {
public:
  /// constructor.
  DepthResolvedAnalyzer(const std::string &csv_filename) {
    // read the data into a ROOT DataFrame...
    auto df = ROOT::RDF::MakeCsvDataFrame(csv_filename);
    // ...and extract the values
    _energy = df.Take<T>("Energy (keV)").GetValue();
    _alpha = df.Take<T>("Alpha").GetValue();
    _alpha_error = df.Take<T>("Alpha Error").GetValue();
    _beta = df.Take<T>("Beta").GetValue();
    _beta_error = df.Take<T>("Beta Error").GetValue();
    _z_max = df.Take<T>("Max (nm)").GetValue();
    _z_max_error = df.Take<T>("Max Error (nm)").GetValue();

    // default initialized values
    temperature = 2.5;
    critical_temperature = 9.25;
    gap_meV =
        triumf::superconductivity::bcs::gap_meV<double>(critical_temperature);
    xi_0 = 39.0;
    mean_free_path = 1e4;
    lambda_0 = 40.0;
    exponent = 4.0;
    applied_field = 0.02;
    dipole_field = 1e-5;
    correlation_rate = 1.0 / 23.8e-6;
    slr_constant = 0.75;
    slr_exponent = 1.0;
    surface_thickness = 5.0;
    surface_rate = 10.0;
    electron_phonon_coupling = 1.0;
    //
    n_bins = 101;
  };

  /// Return the the minium energy available for interpolation.
  T energy_min() {
    return *std::min_element(_energy.begin(), _energy.end()) +
           std::sqrt(std::numeric_limits<T>::epsilon());
  };

  /// Return the the maximum energy availalbe for interpolation.
  T energy_max() {
    return *std::max_element(_energy.begin(), _energy.end()) -
           std::sqrt(std::numeric_limits<T>::epsilon());
  };

  /// Return an interpolated alpha value.
  T alpha(T energy_keV) {
    static boost::math::interpolators::pchip<std::vector<T>> alpha_interpolator(
        std::move(std::vector<T>(_energy)), std::move(std::vector<T>(_alpha)));
    return alpha_interpolator(energy_keV);
  };

  /// Return an interpolated beta value.
  T beta(T energy_keV) {
    static boost::math::interpolators::pchip<std::vector<T>> beta_interpolator(
        std::move(std::vector<T>(_energy)), std::move(std::vector<T>(_beta)));
    return beta_interpolator(energy_keV);
  };

  /// Return an interpolated z_max value.
  T z_max(T energy_keV) {
    static boost::math::interpolators::pchip<std::vector<T>> z_max_interpolator(
        std::move(std::vector<T>(_energy)), std::move(std::vector<T>(_z_max)));
    return z_max_interpolator(energy_keV);
  };

  /// Return the average implantation depth.
  T z_average(T energy_keV) {
    T a = alpha(energy_keV);
    T b = beta(energy_keV);
    T zm = z_max(energy_keV);
    return zm * a / (a + b);
  };

  /*
  /// depth-averaging using numeric integration
  T operator()(T energy_keV) {
    // T a = alpha(energy_keV);
    // T b = beta(energy_keV);
    // T zm = z_max(energy_keV);
    static boost::math::quadrature::tanh_sinh<T> integrator;
    auto integrand = [&](T z) {
      return slr_fcn(z, GLOBAL_R_0, GLOBAL_D_0) *
             triumf::srim::pdf::modified_beta<T>(
                 z, alpha(energy_keV), beta(energy_keV), z_max(energy_keV));
    };
    T Q = integrator.integrate(integrand, 0.0, z_max(energy_keV));
    return Q;
  };
  */

  // depth-averaging using "histogram" summation
  T operator()(T energy_keV) {
    // bin edges
    std::vector<T> z_edge =
        triumf::numpy::linspace<T>(0.0, z_max(energy_keV), n_bins);
    // bin widths - adjust ranges by one for "correct" size
    std::vector<T> dz(n_bins - 1);
    std::adjacent_difference(std::begin(z_edge) + 1, std::end(z_edge),
                             std::begin(dz));
    // bin centres
    std::vector<T> z(n_bins - 1);
    // probabilities
    std::vector<T> p_z(n_bins - 1);
    std::vector<T> weights(n_bins - 1);
    std::vector<T> slr_rates(n_bins - 1);

    for (std::size_t i = 0; i < z.size(); ++i) {
      z.at(i) = z_edge.at(i) + 0.5 * dz.at(i);
      p_z.at(i) = triumf::srim::pdf::modified_beta<T>(
          z.at(i), alpha(energy_keV), beta(energy_keV), z_max(energy_keV));
      // std::cout << "p(" << z.at(i) << " nm ) = " << p_z.at(i) << " nm^-1\n";
      weights.at(i) = dz.at(i) * p_z.at(i);
      slr_rates.at(i) = slr_rate_z<T>(
          z.at(i), temperature, critical_temperature, gap_meV, xi_0,
          mean_free_path, lambda_0, exponent, applied_field, dipole_field,
          correlation_rate, slr_constant, slr_exponent, surface_thickness,
          surface_rate, electron_phonon_coupling);
    }

    T sum_weights = std::reduce(std::begin(weights), std::end(weights), 0.0);
    // std::cout << "sum_weights = " << sum_weights << "\n";
    T sum_weights_slr = std::transform_reduce(
        std::begin(weights), std::end(weights), std::begin(slr_rates), 0.0);
    // std::cout << "sum_weights_slr = " << sum_weights_slr << "\n";
    T weighted_average = sum_weights_slr / sum_weights;
    return weighted_average;
  };

  /// model parameters
  T temperature;
  T critical_temperature;
  T gap_meV;
  T xi_0;
  T mean_free_path;
  T lambda_0;
  T exponent;
  T applied_field;
  T dipole_field;
  T correlation_rate;
  T slr_constant;
  T slr_exponent;
  T surface_thickness;
  T surface_rate;
  T electron_phonon_coupling;

private:
  /// vectors of data from csv file
  std::vector<T> _energy;
  std::vector<T> _alpha;
  std::vector<T> _alpha_error;
  std::vector<T> _beta;
  std::vector<T> _beta_error;
  std::vector<T> _z_max;
  std::vector<T> _z_max_error;
  /// number of bins + 1 used in the "histogram" summation
  std::size_t n_bins;
};

} // namespace nonlocal

} // namespace srf

} // namespace bnmr

} // namespace triumf

#endif // TRIUMF_BNMR_SRF_NONLOCAL_HPP


//
// Copyright 2019 Olzhas Zhumabek <anonymous.from.applecity@gmail.com>
//
// Use, modification and distribution are subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/gil/image.hpp>
#include <boost/gil/image_processing/harris.hpp>
#include <boost/gil/image_processing/numeric.hpp>
#include <boost/gil/image_view.hpp>

#include <boost/core/lightweight_test.hpp>

namespace gil = boost::gil;

bool are_equal(gil::gray32f_view_t expected, gil::gray32f_view_t actual) {
  if (expected.dimensions() != actual.dimensions())
    return false;

  for (long int y = 0; y < expected.height(); ++y) {
    for (long int x = 0; x < expected.width(); ++x) {
      if (expected(x, y) != actual(x, y)) {
        return false;
      }
    }
  }

  return true;
}

void test_blank_image() {
  const gil::point_t dimensions(20, 20);
  gil::gray16s_image_t dx(dimensions, gil::gray16s_pixel_t(0), 0);
  gil::gray16s_image_t dy(dimensions, gil::gray16s_pixel_t(0), 0);

  gil::gray32f_image_t m11(dimensions);
  gil::gray32f_image_t m12_21(dimensions);
  gil::gray32f_image_t m22(dimensions);
  gil::gray32f_image_t expected(dimensions, gil::gray32f_pixel_t(0), 0);
  gil::compute_tensor_entries(gil::view(dx), gil::view(dy), gil::view(m11),
                              gil::view(m12_21), gil::view(m22));
  BOOST_TEST(are_equal(gil::view(expected), gil::view(m11)));
  BOOST_TEST(are_equal(gil::view(expected), gil::view(m12_21)));
  BOOST_TEST(are_equal(gil::view(expected), gil::view(m22)));

  gil::gray32f_image_t harris_response(dimensions, gil::gray32f_pixel_t(0), 0);
  auto unnormalized_mean = gil::generate_unnormalized_mean(5);
  gil::compute_harris_responses(gil::view(m11), gil::view(m12_21),
                                gil::view(m22), unnormalized_mean, 0.04f,
                                gil::view(harris_response));
  BOOST_TEST(are_equal(gil::view(expected), gil::view(harris_response)));
}

int main(int argc, char *argv[]) {
  test_blank_image();
  return boost::report_errors();
}


/**
 *  This test ensures that the sketch application (for mixed: internal ->
 *  Elemental matrices) is done correctly (on-the-fly matrix multiplication in
 *  the code is compared to true matrix multiplication).
 *  This test builds on the following assumptions:
 *
 *      - Elemental GEMM returns the correct result, and
 *      - the random numbers in row_idx and row_value (see
 *        hash_transform_data_t) are drawn from the promised distributions.
 */

#include <vector>

#include <boost/mpi.hpp>
#include <boost/test/minimal.hpp>

#include <skylark.hpp>

#define SKYLARK_NO_ANY

#include "test_utils.hpp"

typedef skylark::base::sparse_matrix_t<double> LocalMatrixType;

typedef El::DistMatrix<double, El::VC, El::STAR> dense_vc_star_matrix_t;
typedef skylark::base::sparse_vc_star_matrix_t<double> sparse_vc_star_matrix_t;

template<typename sketch_t, typename dense_matrix_t>
void compute_sketch_matrix(sketch_t sketch, dense_matrix_t &result) {

    std::vector<size_t> row_idx = sketch.getRowIdx();
    std::vector<double> row_val = sketch.getRowValues();

    // PI generated by random number gen
    for(size_t i = 0; i < row_val.size(); ++i) {
        if (result.IsLocal(row_idx[i], i))
            result.Update(row_idx[i], i, row_val[i]);
    }
}

int test_main(int argc, char *argv[]) {

    //////////////////////////////////////////////////////////////////////////
    //[> Parameters <]

    //FIXME: use random sizes?
    const size_t n   = 100;
    const size_t m   = 50;
    const size_t n_s = 60;
    const size_t m_s = 30;

    //////////////////////////////////////////////////////////////////////////
    //[> Setup test <]
    namespace mpi = boost::mpi;

    El::Initialize(argc, argv);

    mpi::environment env(argc, argv);
    mpi::communicator world;
    const size_t rank = world.rank();

    MPI_Comm mpi_world(world);
    El::Grid grid(mpi_world);

    skylark::base::context_t context(0);

    dense_vc_star_matrix_t A_vc(grid);
    El::Uniform(A_vc, n, m);
    El::Zero(A_vc);

    sparse_vc_star_matrix_t A_sparse_vc(n, m, grid);

    double count = 1.0;
    for(int col = 0; col < A_vc.Width(); col++) {
        for(int row = 0; row < A_vc.Height(); row++) {
            A_vc.Update(row, col, count);
            A_sparse_vc.queue_update(row, col, count);
            count++;
        }
    }

    A_sparse_vc.finalize();

    // rowwise application
    //dense_vc_star_matrix_t pi_sketch_r(grid);

    //////////////////////////////////////////////////////////////////////////
    //[> Column wise application DistSparseMatrix -> DistMatrix[VC/*] <]

    typedef El::DistMatrix<double, El::VC, El::STAR> vcs_target_t;

    //[> 1. Create the sketching matrix <]
    test::util::hash_transform_test_t<sparse_vc_star_matrix_t, vcs_target_t>
        SparseVC(n, n_s, context);

    //[> 2. Create space for the sketched matrix <]
    vcs_target_t sketch_A_vcs(n_s, m, grid);
    El::Zero(sketch_A_vcs);

    //[> 3. Apply the transform <]
    SparseVC.apply(A_sparse_vc, sketch_A_vcs,
            skylark::sketch::columnwise_tag());

    //[> 4. Compute expected value using a GEMM <]
    El::DistMatrix<double> Pi_mcmr(n_s, n, grid);
    El::Zero(Pi_mcmr);
    compute_sketch_matrix(SparseVC, Pi_mcmr);

    El::DistMatrix<double> A_mcmr = A_vc;
    El::DistMatrix<double> C(n_s, m, grid);
    El::Gemm(El::NORMAL, El::NORMAL, 1.0, Pi_mcmr, A_mcmr, 0.0, C);

    El::DistMatrix<double, El::STAR, El::STAR> expected_A(n_s, m, grid);
    expected_A = C;
    El::DistMatrix<double, El::STAR, El::STAR> sketched_A(n_s, m, grid);
    sketched_A = sketch_A_vcs;
    test::util::check_equal(expected_A, sketched_A);

#if 0
    //////////////////////////////////////////////////////////////////////////
    //[> Row wise application DistSparseMatrix -> DistMatrix[VC/*] <]

    //[> 1. Create the sketching matrix <]
    test::util::hash_transform_test_t<sparse_vc_star_matrix_t, vcs_target_t>
        Sparse_r_vcs(m, m_s, context);

    //[> 2. Create space for the sketched matrix <]
    vcs_target_t sketch_A_r_vcs(n, m_s, grid);
    El::Zero(sketch_A_r_vcs);

    //[> 3. Apply the transform <]
    Sparse_r_vcs.apply(A, sketch_A_r_vcs, skylark::sketch::rowwise_tag());

    //[> 4. Build structure to compare <]
    // easier to check if all processors own result
    result = sketch_A_r_vcs;

    pi_sketch_r.Transpose();
    compute_sketch_matrix(Sparse_r_vcs, A, pi_sketch_r);
    pi_sketch_r.Transpose();
    expected_AR = Mult_AnXBn_Synch<PTDD, double, col_t>(
            A, pi_sketch_r, false, false);
#endif

    return 0;
}


//  Copyright Matt Borland 2021.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Basic sanity check that header
// #includes all the files that it needs to.
//
#include <boost/math/cstdfloat/cstdfloat_limits.hpp>
//
// Note this header includes no other headers, this is
// important if this test is to be meaningful:
//
#include "test_compile_result.hpp"

void compile_and_link_test()
{
    #ifdef BOOST_FLOAT128_C
    check_result<bool>(std::numeric_limits<boost::float128_t>::is_specialized);
    check_result<boost::float128_t>((std::numeric_limits<boost::float128_t>::min)());
    check_result<boost::float128_t>((std::numeric_limits<boost::float128_t>::max)());
    check_result<boost::float128_t>(std::numeric_limits<boost::float128_t>::lowest());
    check_result<int>(std::numeric_limits<boost::float128_t>::digits);
    check_result<int>(std::numeric_limits<boost::float128_t>::digits10);
    check_result<int>(std::numeric_limits<boost::float128_t>::max_digits10);
    check_result<bool>(std::numeric_limits<boost::float128_t>::is_signed);
    check_result<bool>(std::numeric_limits<boost::float128_t>::is_integer);
    check_result<bool>(std::numeric_limits<boost::float128_t>::is_exact);
    check_result<int>(std::numeric_limits<boost::float128_t>::radix);
    check_result<boost::float128_t>(std::numeric_limits<boost::float128_t>::epsilon());
    check_result<int>(std::numeric_limits<boost::float128_t>::min_exponent);
    check_result<int>(std::numeric_limits<boost::float128_t>::min_exponent10);
    check_result<int>(std::numeric_limits<boost::float128_t>::max_exponent);
    check_result<int>(std::numeric_limits<boost::float128_t>::max_exponent10);
    check_result<bool>(std::numeric_limits<boost::float128_t>::has_infinity);
    check_result<bool>(std::numeric_limits<boost::float128_t>::has_quiet_NaN);
    check_result<bool>(std::numeric_limits<boost::float128_t>::has_signaling_NaN);
    check_result<std::float_denorm_style>(std::numeric_limits<boost::float128_t>::has_denorm);
    check_result<bool>(std::numeric_limits<boost::float128_t>::has_denorm_loss);
    check_result<boost::float128_t>(std::numeric_limits<boost::float128_t>::infinity());
    check_result<boost::float128_t>(std::numeric_limits<boost::float128_t>::quiet_NaN());
    check_result<boost::float128_t>(std::numeric_limits<boost::float128_t>::signaling_NaN());
    check_result<boost::float128_t>(std::numeric_limits<boost::float128_t>::denorm_min());
    check_result<bool>(std::numeric_limits<boost::float128_t>::is_iec559);
    check_result<bool>(std::numeric_limits<boost::float128_t>::is_bounded);
    check_result<bool>(std::numeric_limits<boost::float128_t>::is_modulo);
    check_result<bool>(std::numeric_limits<boost::float128_t>::traps);
    check_result<bool>(std::numeric_limits<boost::float128_t>::tinyness_before);
    check_result<std::float_round_style>(std::numeric_limits<boost::float128_t>::round_style);
    #endif
}


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

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_TWO_SPLIT_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_TWO_SPLIT_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

 /*!

    @ingroup group-arithmetic
    Function object implementing two_split capabilities

    For any real @c x, two_split computes two reals @c r0 and @c r1 (in an std::pair)
    such that:

    @code
    x == r0+r1
    @endcode

    and where @c r0 and @c r1 bit pattern don't overlap.

  **/
  std::pair<Value, Value> two_split(Value const& x);
} }
#endif

#include <boost/simd/function/scalar/two_split.hpp>
#include <boost/simd/function/simd/two_split.hpp>

#endif


#include "camera.hpp"

#include <Eigen/Core>
#include <Eigen/Geometry>
#include <igl/PI.h>
#include <igl/barycenter.h>

#include "eigen_json.hpp"

namespace swr {

Camera::Camera(const nlohmann::json& json)
    : zoom(1)
    , shift(0, 0, 0)
{
    is_perspective = json["is_perspective"];
    from_json(json["position"], position);
    from_json(json["gaze"], gaze);
    from_json(json["view_up"], view_up);
    field_of_view = json["field_of_view"];
    field_of_view =
        std::min(Float(172.847), std::max(Float(0.367), field_of_view));
    field_of_view *= igl::PI / 180;
    orthographic_scale = json["orthographic_scale"];
    near_clip = json["near_clip"];
    far_clip = json["far_clip"];
    from_json(json["resolution"], resolution);
}

Matrix4F Camera::shift_and_zoom() const
{
    return (Eigen::Scaling(zoom) * Eigen::Translation<Float, 3>(shift))
        .matrix();
}

Matrix4F Camera::view_transform() const
{
    Vector3F w = -gaze.normalized();
    Vector3F u = view_up.cross(w).normalized();
    Vector3F v = w.cross(u);

    Matrix4F view_inv;
    // clang-format off
    view_inv << u.x(), v.x(), w.x(), position.x(),
                u.y(), v.y(), w.y(), position.y(),
                u.z(), v.z(), w.z(), position.z(),
                0, 0, 0, 1;
    // clang-format on

    return view_inv.inverse();
}

Matrix4F Camera::projection_matrix() const
{
    Float t;
    if (is_perspective) {
        t = tan(field_of_view / 2) * near_clip;
    } else {
        t = orthographic_scale / 2;
    }
    Float b = -t;
    Float aspect_ratio = float(resolution.x()) / resolution.y();
    Float r = t * aspect_ratio;
    Float l = -r;
    Float n = -near_clip;
    Float f = -far_clip;

    Matrix4F M_orth;
    // clang-format off
    M_orth << 2 / (r - l), 0, 0, -(r + l) / (r - l),
              0, 2 / (t - b), 0, -(t + b) / (t - b),
              0, 0, 2 / (n - f), -(n + f) / (n - f),
              0, 0, 0, 1;
    // clang-format on

    Matrix4F P;
    if (is_perspective) {
        // clang-format off
        P << n, 0, 0, 0,
             0, n, 0, 0,
             0, 0, n + f, -f * n,
             0, 0, 1, 0;
        // clang-format on
    } else {
        P.setIdentity();
    }

    return M_orth * P;
}

void get_scale_and_shift_to_fit_mesh(
    const Eigen::MatrixXd& V,
    const Eigen::MatrixXi& F,
    Float& zoom,
    Vector3F& shift)
{
    if (V.rows() == 0) {
        return;
    }

    Eigen::MatrixXd BC;
    if (F.rows() <= 1) {
        BC = V;
    } else {
        igl::barycenter(V, F, BC);
    }

    auto min_point = V.colwise().minCoeff();
    auto max_point = V.colwise().maxCoeff();
    auto centroid = (0.5 * (min_point + max_point)).eval();
    shift.setConstant(0);
    shift.head(centroid.size()) = -centroid.cast<Float>();
    zoom = 2.0 / (max_point - min_point).array().abs().maxCoeff();
}

void Camera::align_camera_center(
    const Eigen::MatrixXd& V, const Eigen::MatrixXi& F)
{
    if (V.rows() == 0) {
        return;
    }
    get_scale_and_shift_to_fit_mesh(V, F, zoom, shift);
}

} // namespace swr


#pragma once

#include <vector>
#include <stdexcept>
#include <climits>
#include <array>
#include <limits>
#include <boost/dynamic_bitset.hpp>

namespace FACore {

    template<unsigned int AlphabetSize>
    class DAutomaton {
        public:
            constexpr static unsigned int ALPHABET_SIZE = AlphabetSize;
            constexpr static unsigned int INVALID_STATE = std::numeric_limits<unsigned int>::max();

            // A StateId is really just an index into a vector
            typedef unsigned int StateId;

            // Label values must be between 0 and ALPHABET_SIZE
            typedef unsigned int Label;

            // These arrays will be indexed by Label
            typedef std::array<StateId, ALPHABET_SIZE> TransitionArr;

            StateId AddState(bool isFinal) {
                if(mFinalStates.size() == INVALID_STATE) {
                    // WARNING this is outside of testing code coverage
                    throw std::out_of_range("Invalid label");
                }
                StateId result = mTransitions.size();
                mTransitions.emplace_back();
                mTransitions[result].fill((unsigned int)INVALID_STATE);
                mFinalStates.push_back(isFinal);
                return result;
            }

            void SetArc(StateId src, Label label, StateId dest) {
                if(ALPHABET_SIZE <= label) {
                    throw std::out_of_range("Invalid label");
                }
                if(!IsValidState(src)) {
                    throw std::out_of_range("Invalid source state.");
                }
                if(!IsValidState(dest)) {
                    throw std::out_of_range("Invalid dest state");
                }
                mTransitions[src][label] = dest;
            }

            bool IsValidState(StateId state) const {
                return state < mTransitions.size();
            }

            bool IsValidLabel(Label label) const {
                return label < ALPHABET_SIZE;
            }

            StateId GetNext(StateId src, Label label) const {
                if(IsValidState(src) && IsValidLabel(label) ) {
                    return mTransitions[src][label];
                } else {
                    return INVALID_STATE;
                }
            }

            bool IsFinal(StateId state) const {
                if(!IsValidState(state)) {
                    return false;
                }
                return mFinalStates[state];
            }

        private:
            std::vector<TransitionArr> mTransitions;
            boost::dynamic_bitset<> mFinalStates;
    };

    typedef DAutomaton<2> DFA;
}


/* boost random/poisson_distribution.hpp header file
 *
 * Copyright Jens Maurer 2002
 * Copyright Steven Watanabe 2010
 * Distributed under the Boost Software License, Version 1.0. (See
 * accompanying file LICENSE_1_0.txt or copy at
 * http://www.boost.org/LICENSE_1_0.txt)
 *
 * See http://www.boost.org for most recent version including documentation.
 *
 * $Id$
 *
 */

#ifndef BOOST_RANDOM_POISSON_DISTRIBUTION_HPP
#define BOOST_RANDOM_POISSON_DISTRIBUTION_HPP

#include <boost/config/no_tr1/cmath.hpp>
#include <cstdlib>
#include <iosfwd>
#include <boost/assert.hpp>
#include <boost/limits.hpp>
#include <boost/random/uniform_01.hpp>
#include <boost/random/detail/config.hpp>

#include <boost/random/detail/disable_warnings.hpp>

namespace lslboost {
namespace random {

namespace detail {

template<class RealType>
struct poisson_table {
    static RealType value[10];
};

template<class RealType>
RealType poisson_table<RealType>::value[10] = {
    0.0,
    0.0,
    0.69314718055994529,
    1.7917594692280550,
    3.1780538303479458,
    4.7874917427820458,
    6.5792512120101012,
    8.5251613610654147,
    10.604602902745251,
    12.801827480081469
};

}

/**
 * An instantiation of the class template @c poisson_distribution is a
 * model of \random_distribution.  The poisson distribution has
 * \f$p(i) = \frac{e^{-\lambda}\lambda^i}{i!}\f$
 *
 * This implementation is based on the PTRD algorithm described
 *
 *  @blockquote
 *  "The transformed rejection method for generating Poisson random variables",
 *  Wolfgang Hormann, Insurance: Mathematics and Economics
 *  Volume 12, Issue 1, February 1993, Pages 39-45
 *  @endblockquote
 */
template<class IntType = int, class RealType = double>
class poisson_distribution {
public:
    typedef IntType result_type;
    typedef RealType input_type;

    class param_type {
    public:
        typedef poisson_distribution distribution_type;
        /**
         * Construct a param_type object with the parameter "mean"
         *
         * Requires: mean > 0
         */
        explicit param_type(RealType mean_arg = RealType(1))
          : _mean(mean_arg)
        {
            BOOST_ASSERT(_mean > 0);
        }
        /* Returns the "mean" parameter of the distribution. */
        RealType mean() const { return _mean; }
#ifndef BOOST_RANDOM_NO_STREAM_OPERATORS
        /** Writes the parameters of the distribution to a @c std::ostream. */
        template<class CharT, class Traits>
        friend std::basic_ostream<CharT, Traits>&
        operator<<(std::basic_ostream<CharT, Traits>& os,
                   const param_type& parm)
        {
            os << parm._mean;
            return os;
        }

        /** Reads the parameters of the distribution from a @c std::istream. */
        template<class CharT, class Traits>
        friend std::basic_istream<CharT, Traits>&
        operator>>(std::basic_istream<CharT, Traits>& is, param_type& parm)
        {
            is >> parm._mean;
            return is;
        }
#endif
        /** Returns true if the parameters have the same values. */
        friend bool operator==(const param_type& lhs, const param_type& rhs)
        {
            return lhs._mean == rhs._mean;
        }
        /** Returns true if the parameters have different values. */
        friend bool operator!=(const param_type& lhs, const param_type& rhs)
        {
            return !(lhs == rhs);
        }
    private:
        RealType _mean;
    };

    /**
     * Constructs a @c poisson_distribution with the parameter @c mean.
     *
     * Requires: mean > 0
     */
    explicit poisson_distribution(RealType mean_arg = RealType(1))
      : _mean(mean_arg)
    {
        BOOST_ASSERT(_mean > 0);
        init();
    }

    /**
     * Construct an @c poisson_distribution object from the
     * parameters.
     */
    explicit poisson_distribution(const param_type& parm)
      : _mean(parm.mean())
    {
        init();
    }

    /**
     * Returns a random variate distributed according to the
     * poisson distribution.
     */
    template<class URNG>
    IntType operator()(URNG& urng) const
    {
        if(use_inversion()) {
            return invert(urng);
        } else {
            return generate(urng);
        }
    }

    /**
     * Returns a random variate distributed according to the
     * poisson distribution with parameters specified by param.
     */
    template<class URNG>
    IntType operator()(URNG& urng, const param_type& parm) const
    {
        return poisson_distribution(parm)(urng);
    }

    /** Returns the "mean" parameter of the distribution. */
    RealType mean() const { return _mean; }

    /** Returns the smallest value that the distribution can produce. */
    IntType min BOOST_PREVENT_MACRO_SUBSTITUTION() const { return 0; }
    /** Returns the largest value that the distribution can produce. */
    IntType max BOOST_PREVENT_MACRO_SUBSTITUTION() const
    { return (std::numeric_limits<IntType>::max)(); }

    /** Returns the parameters of the distribution. */
    param_type param() const { return param_type(_mean); }
    /** Sets parameters of the distribution. */
    void param(const param_type& parm)
    {
        _mean = parm.mean();
        init();
    }

    /**
     * Effects: Subsequent uses of the distribution do not depend
     * on values produced by any engine prior to invoking reset.
     */
    void reset() { }

#ifndef BOOST_RANDOM_NO_STREAM_OPERATORS
    /** Writes the parameters of the distribution to a @c std::ostream. */
    template<class CharT, class Traits>
    friend std::basic_ostream<CharT,Traits>&
    operator<<(std::basic_ostream<CharT,Traits>& os,
               const poisson_distribution& pd)
    {
        os << pd.param();
        return os;
    }

    /** Reads the parameters of the distribution from a @c std::istream. */
    template<class CharT, class Traits>
    friend std::basic_istream<CharT,Traits>&
    operator>>(std::basic_istream<CharT,Traits>& is, poisson_distribution& pd)
    {
        pd.read(is);
        return is;
    }
#endif

    /** Returns true if the two distributions will produce the same
        sequence of values, given equal generators. */
    friend bool operator==(const poisson_distribution& lhs,
                           const poisson_distribution& rhs)
    {
        return lhs._mean == rhs._mean;
    }
    /** Returns true if the two distributions could produce different
        sequences of values, given equal generators. */
    friend bool operator!=(const poisson_distribution& lhs,
                           const poisson_distribution& rhs)
    {
        return !(lhs == rhs);
    }

private:

    /// @cond show_private

    template<class CharT, class Traits>
    void read(std::basic_istream<CharT, Traits>& is) {
        param_type parm;
        if(is >> parm) {
            param(parm);
        }
    }

    bool use_inversion() const
    {
        return _mean < 10;
    }

    static RealType log_factorial(IntType k)
    {
        BOOST_ASSERT(k >= 0);
        BOOST_ASSERT(k < 10);
        return detail::poisson_table<RealType>::value[k];
    }

    void init()
    {
        using std::sqrt;
        using std::exp;

        if(use_inversion()) {
            _u._exp_mean = exp(-_mean);
        } else {
            _u._ptrd.smu = sqrt(_mean);
            _u._ptrd.b = 0.931 + 2.53 * _u._ptrd.smu;
            _u._ptrd.a = -0.059 + 0.02483 * _u._ptrd.b;
            _u._ptrd.inv_alpha = 1.1239 + 1.1328 / (_u._ptrd.b - 3.4);
            _u._ptrd.v_r = 0.9277 - 3.6224 / (_u._ptrd.b - 2);
        }
    }

    template<class URNG>
    IntType generate(URNG& urng) const
    {
        using std::floor;
        using std::abs;
        using std::log;

        while(true) {
            RealType u;
            RealType v = uniform_01<RealType>()(urng);
            if(v <= 0.86 * _u._ptrd.v_r) {
                u = v / _u._ptrd.v_r - 0.43;
                return static_cast<IntType>(floor(
                    (2*_u._ptrd.a/(0.5-abs(u)) + _u._ptrd.b)*u + _mean + 0.445));
            }

            if(v >= _u._ptrd.v_r) {
                u = uniform_01<RealType>()(urng) - 0.5;
            } else {
                u = v/_u._ptrd.v_r - 0.93;
                u = ((u < 0)? -0.5 : 0.5) - u;
                v = uniform_01<RealType>()(urng) * _u._ptrd.v_r;
            }

            RealType us = 0.5 - abs(u);
            if(us < 0.013 && v > us) {
                continue;
            }

            RealType k = floor((2*_u._ptrd.a/us + _u._ptrd.b)*u+_mean+0.445);
            v = v*_u._ptrd.inv_alpha/(_u._ptrd.a/(us*us) + _u._ptrd.b);

            RealType log_sqrt_2pi = 0.91893853320467267;

            if(k >= 10) {
                if(log(v*_u._ptrd.smu) <= (k + 0.5)*log(_mean/k)
                               - _mean
                               - log_sqrt_2pi
                               + k
                               - (1/12. - (1/360. - 1/(1260.*k*k))/(k*k))/k) {
                    return static_cast<IntType>(k);
                }
            } else if(k >= 0) {
                if(log(v) <= k*log(_mean)
                           - _mean
                           - log_factorial(static_cast<IntType>(k))) {
                    return static_cast<IntType>(k);
                }
            }
        }
    }

    template<class URNG>
    IntType invert(URNG& urng) const
    {
        RealType p = _u._exp_mean;
        IntType x = 0;
        RealType u = uniform_01<RealType>()(urng);
        while(u > p) {
            u = u - p;
            ++x;
            p = _mean * p / x;
        }
        return x;
    }

    RealType _mean;

    union {
        // for ptrd
        struct {
            RealType v_r;
            RealType a;
            RealType b;
            RealType smu;
            RealType inv_alpha;
        } _ptrd;
        // for inversion
        RealType _exp_mean;
    } _u;

    /// @endcond
};

} // namespace random

using random::poisson_distribution;

} // namespace lslboost

#include <boost/random/detail/enable_warnings.hpp>

#endif // BOOST_RANDOM_POISSON_DISTRIBUTION_HPP


/*=============================================================================
Copyright (c) 2016 Paul W. Bible

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)
==============================================================================*/
#ifndef GRASM_SHARED_INFORMATION
#define GRASM_SHARED_INFORMATION

#include <ggtk/SharedInformationInterface.hpp>
#include <ggtk/TermInformationContentMap.hpp>
#include <ggtk/GoGraph.hpp>
#include <ggtk/Accumulators.hpp>
#include <ggtk/SetUtilities.hpp>

#include <utility>
#include <algorithm>

#include <boost/unordered_map.hpp>
#include <boost/graph/breadth_first_search.hpp>

/*! \class CoutoGraSMSharedInformation
	\brief A class to calculate shared infromation accross disjoint common ancetors using the exact algorithm as written in the paper.

	This class calculates shared infromation accross disjoint common ancetors.

    F. M. Couto, M. J. Silva, and P. M. Coutinho, "Measuring semantic similarity
	between Gene Ontology terms," Data & Knowledge Engineering, vol. 61,
	pp. 137-152, Apr 2007.

	Couto proposing calculating this value a subsituite for the IC of the MICA in calculating
	 Resnik, Lin, and Jiang-Conrath

*/
class CoutoGraSMSharedInformation : public SharedInformationInterface{

public:

	//! A constructor
	/*!
		Creates the CoutoGraSMGreaterOrEqual class
	*/
	inline CoutoGraSMSharedInformation(GoGraph* goGraph, TermInformationContentMap &icMap){
		_goGraph = goGraph;
		_icMap = icMap;
		_pathMemory = boost::unordered_map<std::string, size_t>();
	}

	//! A method for determining the common disjunctive ancestors
	/*!
		This method returns the common disjunctive ancestors for two terms
	*/
	inline boost::unordered_set<std::string> getCommonDisjointAncestors(const std::string &termC1,const std::string &termC2){

		boost::unordered_set<std::string> ancestorsC1 = _goGraph->getAncestorTerms(termC1);
		ancestorsC1.insert(termC1);
		//std::cout << ancestorsC1.size() << std::endl;
		boost::unordered_set<std::string> ancestorsC2 = _goGraph->getAncestorTerms(termC2);
		ancestorsC2.insert(termC2);
		//std::cout << ancestorsC2.size() << std::endl;

		//Couto: CommonDisjAnc = {}
		boost::unordered_set<std::string> cda;

		if(termC1.compare(termC2) == 0){
			cda.insert(termC1);
			return cda;
		}

		//Couto: Anc = CommonAnc(c1,c2)
		boost::unordered_set<std::string> commonAncestors = SetUtilities::set_intersection(ancestorsC1,ancestorsC2);
		//std::cout << commonAncestors.size() << std::endl;

		std::vector<std::pair<double,std::string> > orderedCommonAncestors;

		//create a pair to associate a term with its information content
		boost::unordered_set<std::string>::iterator iter;
		for(iter = commonAncestors.begin(); iter != commonAncestors.end(); ++iter){
			std::string term = *iter;
			orderedCommonAncestors.push_back(std::pair<double,std::string>(_icMap[term],term));
		}

		//sort descending
		std::sort(orderedCommonAncestors.begin(),orderedCommonAncestors.end(),std::greater<std::pair<double,std::string> >());

		//start of main algorithm
		std::vector<std::pair<double,std::string> >::iterator pairIter;
		//Couto: for all a in sortDescByIC(Anc) do ...
		for(pairIter = orderedCommonAncestors.begin(); pairIter != orderedCommonAncestors.end(); ++pairIter){
			std::pair<double,std::string> myPair = *pairIter;
			//std::cout << myPair.first << " " << myPair.second << std::endl;

			std::string termA = myPair.second;

			//Couto: isDisj=true
			bool isDisj = true;

			//std::cout << "testing " << termA << std::endl;

			//Couto: for all cda in CommonDisjAnc do ...
			boost::unordered_set<std::string>::iterator cdaIter;
			for(cdaIter = cda.begin(); cdaIter != cda.end();++cdaIter){
				std::string termCda = *cdaIter;

				//std::cout << "VS " << termCda << std::endl;

				//continue if the terms are the same
				if(termCda.compare(termA) == 0){
					continue;
				}

				//Couto: isDisj = isDisj ^ ( DisjAnc(c1,(a,cda)) or DisjAnc(c2,(a,cda)) )
				isDisj = isDisj && (isDisjoint(termC1,termA,termCda) || isDisjoint(termC2,termA,termCda));

			}

			//Couto: if isDisj then...
			if(isDisj){
				//std::cout << myPair.second << " is cda " << std::endl;
				//Couto: addTo(CommonDisjAnc,a)
				cda.insert(myPair.second);
			}
		}
		return cda;
	}

	//! A method for determining if for a term c, a pair (a1,a2) is disjoint in c
	/*!
		This method returns
	*/
	inline bool isDisjoint(const std::string &termC, const std::string &termA1, const std::string &termA2){

		//std::cout << "isDisjoint " << termC << " ("  << termA1 << " , " << termA2 << ") "; //<< std::endl;
		//if not from same ontology, return 0;
		if(_goGraph->getTermOntology(termA1) != _goGraph->getTermOntology(termA2) ||
		   _goGraph->getTermOntology(termC) != _goGraph->getTermOntology(termA1) ||
		   _goGraph->getTermOntology(termC) != _goGraph->getTermOntology(termA2))
		{
			return false;
		}

		if(_icMap[termA1] <= _icMap[termA2]){
			//std::cout << "case 1" << std::endl;
			size_t nPaths = getNumPaths(termA1,termA2);
			//std::cout << "nPaths " << termA1 << " to "  << termA2 << " " << nPaths << std::endl << std::endl;
			size_t nPaths1 = getNumPaths(termA1,termC);
			//std::cout << "nPaths " << termA1 << " to "  << termC << " " << nPaths1 << std::endl << std::endl;
			size_t nPaths2 = getNumPaths(termA2,termC);
			//std::cout << "nPaths " << termA2 << " to "  << termC << " " << nPaths2 << std::endl << std::endl;
			if(nPaths1 >= nPaths*nPaths2){
				//std::cout << "true" << std::endl;
				return true;
			}else{
				//std::cout << "false" << std::endl;
				return false;
			}
			//return nPaths1 > nPaths*nPaths2;
		}else{
			return false;
		}
	}

	//! A method for calculating the number of paths for one term to another.
	/*!
		This method returns the number of paths between two terms
	*/
	inline std::size_t getNumPaths(const std::string &termA, const std::string &termB){
		if(_icMap[termA] > _icMap[termB]){
			return 0;
		}

		return pathCount(termA, termB);
	}

	//! An method for returning the shared information of two terms
	/*!
		This method returns the mean information content disjoint common ancestors
	*/
	inline double sharedInformation(const std::string &termA, const std::string &termB){
		// return 0 for any terms not in the datbase
		if (!_icMap.hasTerm(termA) || !_icMap.hasTerm(termB)){
			return 0.0;
		}
		// return 0 for terms in different ontologies
		if (_goGraph->getTermOntology(termA) != _goGraph->getTermOntology(termB)){
			return 0.0;
		}

		Accumulators::MeanAccumulator meanIC;
		boost::unordered_set<std::string> cda = getCommonDisjointAncestors(termA,termB);
		//std::cout << "size " << cda.size() << std::endl;

		boost::unordered_set<std::string>::iterator iter = cda.begin();
		for(;iter != cda.end(); ++iter){
			//std::cout << *iter << std::endl;
			//std::cout << _icMap[*iter] << std::endl;
			meanIC(_icMap[*iter]);
		}

		return Accumulators::extractMean(meanIC);
	}

	//! An interface method for returning the shared information of a single terms,or information content
	/*!
		This method privdes a mechanism for returing a term's infromation content.
	*/
	inline double sharedInformation(const std::string &term){
		// return 0 for any terms not in the datbase
		if (!_icMap.hasTerm(term)){
			return 0.0;
		}
		return _icMap[term];
	}

	//! An interface method for returning the maximum information content for a term
	/*!
		This method provides the absolute max information content within a corpus for normalization purposes.
	*/
	inline double maxInformationContent(const std::string &term){

		double maxIC;

		//select the correct ontology normalization factor
		GO::Onto ontoType = _goGraph->getTermOntology(term);
		if(ontoType == GO::BP){
			maxIC = -std::log(_icMap.getMinBP());
		}else if(ontoType == GO::MF){
			maxIC = -std::log(_icMap.getMinMF());
		}else{
			maxIC = -std::log(_icMap.getMinCC());
		}

		return maxIC;
	}

	//! An interface method for determining if a term can be found
	/*!
		Determines if the term can be found in the current map.
	*/
	inline bool hasTerm(const std::string &term){
		return _icMap.hasTerm(term);
	}

	//! An interface method for determining if the two terms are of like ontologies.
	/*!
		Determine if two terms are of the same ontology.
	*/
	bool isSameOntology(const std::string &termA, const std::string &termB){
		return _goGraph->getTermOntology(termA) == _goGraph->getTermOntology(termB);
	}

private:

	//! Count paths from B to A
	/*!
		Count paths between B and A
	*/
	std::size_t pathCount(const std::string &termA, const std::string &termB){
		if (_icMap[termA] > _icMap[termB]){
			return 0;
		}

		boost::unordered_set<std::string> ancestors = _goGraph->getAncestorTerms(termB);
		boost::unordered_set<std::string> finished;
		boost::unordered_map<std::string, size_t> pathMap;
		ancestors.insert(termB);
		GoGraph::Graph* g = _goGraph->getGraph();
		GoGraph::GoVertex v = _goGraph->getTermRootVertex(termB);
		visitHelper(v, g, ancestors, finished, pathMap);

		return pathMap[termA];
	}

	//! Recursive helper method that performs the DFS topological sort for path counting
	/*!
		A path counting topological sort recursive method.
	*/
	void visitHelper(const GoGraph::GoVertex &v, GoGraph::Graph* g,
		boost::unordered_set<std::string> &ancestors,
		boost::unordered_set<std::string> &finished,
		boost::unordered_map<std::string, size_t> &pathMap)
	{
		size_t childCount = 0;
		std::string vTerm = (*g)[v].termId;
		//std::cout << "discover vertex " << vTerm << std::endl;

		//examine children and recurse
		GoGraph::InEdgeIterator it, end;
		for (boost::tie(it, end) = boost::in_edges(v, *g); it != end; ++it){
			GoGraph::GoVertex child = boost::source(*it, *g);
			std::string childTerm = (*g)[child].termId;
			if (!SetUtilities::set_contains(ancestors, childTerm)){
				continue;
			}
			//recurse if child is not finished
			if (!SetUtilities::set_contains(finished, childTerm)){
				visitHelper(child, g, ancestors, finished, pathMap);
			}
			++childCount;
		}

		//finish vertex
		finished.insert(vTerm);
		//std::cout << "finish vertex " << vTerm << ", childred " << childCount << std::endl;
		if (childCount == 0){
			pathMap[vTerm] = 1;
		}
		else{
			pathMap[vTerm] = 0;
			for (boost::tie(it, end) = boost::in_edges(v, *g); it != end; ++it){
				GoGraph::GoVertex child = boost::source(*it, *g);
				std::string childTerm = (*g)[child].termId;
				if (!SetUtilities::set_contains(ancestors, childTerm)){
					continue;
				}
				pathMap[vTerm] += pathMap[childTerm];
			}
		}
	}

	//! A private function to create a string key from a pair of terms
	/*!
		Creates a string key our of a pair to use in memorizing path counts
	*/
	std::string keyPair(const std::string &termA, const std::string &termB){
		if (termA.compare(termB) > 0){
			return termB + "_" + termA;
		}
		else{
			return termB + "_" + termA;
		}
	}

	//! A private function to test if the key as been seen already
	/*!
		A private function to test if the key as been seen already.
	*/
	bool hasSeenKey(const std::string &key){
		if (_pathMemory.find(key) != _pathMemory.end()){
			return true;
		}
		else{
			return false;
		}
	}

	GoGraph* _goGraph;
	TermInformationContentMap _icMap;
	boost::unordered_map<std::string, size_t> _pathMemory;
};
#endif


/*
 * @file
 * @author University of Warwick
 * @version 1.0
 *
 * @section LICENSE
 *
 * @section DESCRIPTION
 *
 * Unit Tests for the methods of the Polygon3D class
 */

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

#include "Polygon3D.h"

using namespace cupcfd::geometry::shapes;
namespace euc = cupcfd::geometry::euclidean;
namespace utf = boost::unit_test;

// Note we only test static methods in this tester since many methods just passthrough
// to an inheriting class (CRTP)

// === isVertexOrderClockwise ===
// Test 1: AntiClockwise from Origin
BOOST_AUTO_TEST_CASE(isVertexOrderClockwise_test1, * utf::tolerance(0.00001))
{
	// Setup
	euc::EuclideanPoint<double,3> observation(0.0, 0.0, 0.0);

	euc::EuclideanPoint<double,3> points[3];
	points[0] = euc::EuclideanPoint<double,3>(3.0, 4.0, 8.0);
	points[1] = euc::EuclideanPoint<double,3>(3.15, 6.0, 8.0);
	points[2] = euc::EuclideanPoint<double,3>(3.3, 4.0, 8.0);

	// Test and Check
	bool dir = isVertexOrderClockwise(observation, points, 3);
	BOOST_CHECK_EQUAL(dir, false);
}

// Test 2: Clockwise from origin
BOOST_AUTO_TEST_CASE(isVertexOrderClockwise_test2, * utf::tolerance(0.00001))
{
	// Setup
	euc::EuclideanPoint<double,3> observation(0.0, 0.0, 0.0);

	euc::EuclideanPoint<double,3> points[3];
	points[0] = euc::EuclideanPoint<double,3>(3.0, 4.0, 8.0);
	points[1] = euc::EuclideanPoint<double,3>(3.3, 4.0, 8.0);
	points[2] = euc::EuclideanPoint<double,3>(3.15, 6.0, 8.0);

	// Test and Check
	bool dir = isVertexOrderClockwise(observation, points, 3);
	BOOST_CHECK_EQUAL(dir, true);
}


// Copyright (C) 2013 The Regents of the University of California (Regents).
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//
//     * Neither the name of The Regents or University of California nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Please contact the author of this library if you have any questions.
// Author: Chris Sweeney (cmsweeney@cs.ucsb.edu)

#include <Eigen/Core>
#include <Eigen/Geometry>
#include <glog/logging.h>
#include <algorithm>
#include <vector>
#include "gtest/gtest.h"

#include "theia/math/util.h"
#include "theia/solvers/sample_consensus_estimator.h"
#include "theia/test/test_utils.h"
#include "theia/sfm/create_and_initialize_ransac_variant.h"
#include "theia/sfm/estimators/estimate_fundamental_matrix.h"
#include "theia/matching/feature_correspondence.h"
#include "theia/sfm/pose/test_util.h"
#include "theia/sfm/pose/util.h"

namespace theia {

using Eigen::Matrix3d;
using Eigen::Vector2d;
using Eigen::Vector3d;

static const int kNumTrials = 100;

void ExecuteRandomTest(const RansacParameters& options,
                       const Matrix3d& rotation,
                       const Vector3d& position,
                       const double focal_length1,
                       const double focal_length2,
                       const double inlier_ratio,
                       const double noise) {
  static const int kNumCorrespondences = 600;
  InitRandomGenerator();

  // Create feature correspondences (inliers and outliers) and add noise if
  // appropriate.
  const Vector3d translation = (-rotation * position).normalized();
  std::vector<FeatureCorrespondence> correspondences;
  for (int i = 0; i < kNumCorrespondences; i++) {
    FeatureCorrespondence correspondence;
    // Add an inlier or outlier.
    if (i < inlier_ratio * kNumCorrespondences) {
      // Make sure the point is in front of the camera.
      const Vector3d point_3d = Vector3d::Random() + Vector3d(0, 0, 8);
      correspondence.feature1 = focal_length1 * point_3d.hnormalized();
      correspondence.feature2 =
          focal_length2 * (rotation * point_3d + translation).hnormalized();

      AddNoiseToProjection(noise, &correspondence.feature1);
      AddNoiseToProjection(noise, &correspondence.feature2);
    } else {
      correspondence.feature1 = focal_length1 * Vector2d::Random();
      correspondence.feature2 = focal_length2 * Vector2d::Random();
    }
    correspondences.emplace_back(correspondence);
  }

  // Estimate the fundamental matrix.
  Eigen::Matrix3d fundamental_matrix;
  RansacSummary ransac_summary;
  EXPECT_TRUE(EstimateFundamentalMatrix(options,
                                        RansacType::RANSAC,
                                        correspondences,
                                        &fundamental_matrix,
                                        &ransac_summary));

  // Expect that the inlier ratio is reasonable.
  const double observed_inlier_ratio =
      ransac_summary.inliers.size() /
      static_cast<double>(correspondences.size());
  EXPECT_GT(observed_inlier_ratio, 0.7 * inlier_ratio);
}

TEST(EstimateFundamentalMatrix, AllInliersNoNoise) {
  RansacParameters options;
  options.use_mle = true;
  options.error_thresh = 2;
  options.failure_probability = 0.001;
  const double kInlierRatio = 1.0;
  const double kNoise = 0.0;

  for (int k = 0; k < kNumTrials; k++) {
    const Matrix3d rotation = ProjectToRotationMatrix(Matrix3d::Identity() +
                                                      0.3 * Matrix3d::Random());
    const Vector3d position = Vector3d::Random();
    const double focal_length1 = RandDouble(800, 1600);
    const double focal_length2 = RandDouble(800, 1600);
    ExecuteRandomTest(options,
                      rotation,
                      position,
                      focal_length1,
                      focal_length2,
                      kInlierRatio,
                      kNoise);
  }
}

TEST(EstimateFundamentalMatrix, AllInliersWithNoise) {
  RansacParameters options;
  options.use_mle = true;
  options.error_thresh = 2;
  options.failure_probability = 0.001;
  const double kInlierRatio = 1.0;
  const double kNoise = 1.0;

  for (int k = 0; k < kNumTrials; k++) {
    const Matrix3d rotation = ProjectToRotationMatrix(Matrix3d::Identity() +
                                                      0.3 * Matrix3d::Random());
    const Vector3d position = Vector3d::Random();
    const double focal_length1 = RandDouble(800, 1600);
    const double focal_length2 = RandDouble(800, 1600);
    ExecuteRandomTest(options,
                      rotation,
                      position,
                      focal_length1,
                      focal_length2,
                      kInlierRatio,
                      kNoise);
  }
}

TEST(EstimateFundamentalMatrix, OutliersNoNoise) {
  RansacParameters options;
  options.use_mle = true;
  options.error_thresh = 2;
  options.failure_probability = 0.001;
  const double kInlierRatio = 0.7;
  const double kNoise = 0.0;

  for (int k = 0; k < kNumTrials; k++) {
    const Matrix3d rotation = ProjectToRotationMatrix(Matrix3d::Identity() +
                                                      0.3 * Matrix3d::Random());
    const Vector3d position = Vector3d::Random();
    const double focal_length1 = RandDouble(800, 1600);
    const double focal_length2 = RandDouble(800, 1600);
    ExecuteRandomTest(options,
                      rotation,
                      position,
                      focal_length1,
                      focal_length2,
                      kInlierRatio,
                      kNoise);
  }
}

TEST(EstimateFundamentalMatrix, OutliersWithNoise) {
  RansacParameters options;
  options.use_mle = true;
  options.error_thresh = 4.0 * 4.0;
  options.failure_probability = 0.001;
  const double kInlierRatio = 0.7;
  const double kNoise = 1.0;

  for (int k = 0; k < kNumTrials; k++) {
    const Matrix3d rotation = ProjectToRotationMatrix(Matrix3d::Identity() +
                                                      0.3 * Matrix3d::Random());
    const Vector3d position = Vector3d::Random();
    const double focal_length1 = RandDouble(800, 1600);
    const double focal_length2 = RandDouble(800, 1600);
    ExecuteRandomTest(options,
                      rotation,
                      position,
                      focal_length1,
                      focal_length2,
                      kInlierRatio,
                      kNoise);
  }
}

}  // namespace theia