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

#include <MaterialProperty.hh>

#include <deal.II/base/point.h>
#include <deal.II/base/quadrature_lib.h>
#include <deal.II/dofs/dof_handler.h>
#include <deal.II/fe/fe_values.h>
#include <deal.II/hp/fe_values.h>
#include <deal.II/hp/q_collection.h>

#include <boost/algorithm/string/split.hpp>
#include <boost/optional.hpp>

#include <algorithm>

namespace adamantine
{

template <int dim>
MaterialProperty<dim>::MaterialProperty(
    MPI_Comm const &communicator,
    dealii::parallel::distributed::Triangulation<dim> const &tria,
    boost::property_tree::ptree const &database)
    : _communicator(communicator), _fe(0), _mp_dof_handler(tria)
{
  // Because deal.II cannot easily attach data to a cell. We store the state
  // of the material in distributed::Vector. This allows to use deal.II to
  // compute the new state after refinement of the mesh. However, this
  // requires to use another DoFHandler.
  reinit_dofs();

  // Set the material state to the state defined in the geometry.
  set_initial_state();

  // Fill the _properties map
  fill_properties(database);
}

template <int dim>
double MaterialProperty<dim>::get_cell_value(
    typename dealii::Triangulation<dim>::active_cell_iterator const &cell,
    StateProperty prop) const
{
  unsigned int property = static_cast<unsigned int>(prop);
  auto const mp_dof_index = get_dof_index(cell);

  return _property_values[property][mp_dof_index];
}

template <int dim>
double MaterialProperty<dim>::get_cell_value(
    typename dealii::Triangulation<dim>::active_cell_iterator const &cell,
    Property prop) const
{
  dealii::types::material_id material_id = cell->material_id();
  unsigned int property = static_cast<unsigned int>(prop);

  return _properties(material_id, property);
}

template <int dim>
void MaterialProperty<dim>::reinit_dofs()
{
  _mp_dof_handler.distribute_dofs(_fe);
  // Initialize the state vectors
  for (auto &vec : _state)
    vec.reinit(_mp_dof_handler.locally_owned_dofs(), _communicator);
}

template <int dim>
void MaterialProperty<dim>::update(
    dealii::DoFHandler<dim> const &temperature_dof_handler,
    dealii::LA::distributed::Vector<double> const &temperature)
{
  auto temperature_average =
      compute_average_temperature(temperature_dof_handler, temperature);
  for (auto &val : _property_values)
    val.reinit(temperature_average.get_partitioner());

  std::vector<dealii::types::global_dof_index> mp_dof(1);
  for (auto cell :
       dealii::filter_iterators(_mp_dof_handler.active_cell_iterators(),
                                dealii::IteratorFilters::LocallyOwnedCell()))
  {
    dealii::types::material_id material_id = cell->material_id();

    unsigned int constexpr liquid =
        static_cast<unsigned int>(MaterialState::liquid);
    unsigned int constexpr powder =
        static_cast<unsigned int>(MaterialState::powder);
    unsigned int constexpr solid =
        static_cast<unsigned int>(MaterialState::solid);
    unsigned int constexpr prop_solidus =
        static_cast<unsigned int>(Property::solidus);
    unsigned int constexpr prop_liquidus =
        static_cast<unsigned int>(Property::liquidus);

    double const solidus = _properties(material_id, prop_solidus);
    double const liquidus = _properties(material_id, prop_liquidus);
    cell->get_dof_indices(mp_dof);
    unsigned int const dof = mp_dof[0];

    // First determine the ratio of liquid.
    double liquid_ratio = -1.;
    double powder_ratio = -1.;
    double solid_ratio = -1.;
    if (temperature_average[dof] < solidus)
      liquid_ratio = 0.;
    else if (temperature_average[dof] > liquidus)
      liquid_ratio = 1.;
    else
      liquid_ratio =
          (temperature_average[dof] - solidus) / (liquidus - solidus);
    // Because the powder can only become liquid, the solid can only become
    // liquid, and the liquid can only become solid, the ratio of powder can
    // only decrease.
    powder_ratio = std::min(1. - liquid_ratio, _state[powder][dof]);
    // Use max to make sure that we don't create matter because of round-off.
    solid_ratio = std::max(1 - liquid_ratio - powder_ratio, 0.);

    // Update the value
    _state[liquid][dof] = liquid_ratio;
    _state[powder][dof] = powder_ratio;
    _state[solid][dof] = solid_ratio;

    if (_use_table)
    {
      for (unsigned int property = 0; property < _n_state_properties;
           ++property)
      {
        for (unsigned int material_state = 0;
             material_state < _n_material_states; ++material_state)
        {
          _property_values[property][dof] +=
              _state[material_state][dof] *
              compute_property_from_table(material_id, material_state, property,
                                          temperature_average[dof]);
        }
      }
    }
    else
    {
      for (unsigned int property = 0; property < _n_state_properties;
           ++property)
      {
        for (unsigned int material_state = 0;
             material_state < _n_material_states; ++material_state)
        {
          for (unsigned int i = 0; i <= _polynomial_order; ++i)
          {
            _property_values[property][dof] +=
                _state[material_state][dof] *
                _state_property_polynomials(material_id, material_state,
                                            property, i) *
                std::pow(temperature_average[dof], i);
          }
        }
      }
    }

    // If we are in the mushy state, i.e., part liquid part solid, we need to
    // modify the rho C_p to take into account the latent heat.
    if ((liquid_ratio > 0.) && (liquid_ratio < 1.))
    {
      unsigned int const specific_heat_prop =
          static_cast<unsigned int>(StateProperty::specific_heat);
      unsigned int const latent_heat_prop =
          static_cast<unsigned int>(Property::latent_heat);
      for (unsigned int material_state = 0; material_state < _n_material_states;
           ++material_state)
      {
        _property_values[specific_heat_prop][dof] +=
            _state[material_state][dof] *
            _properties(material_id, latent_heat_prop) / (liquidus - solidus);
      }
    }

    // The radiation heat transfer coefficient is not a real material property
    // but it is derived from other material properties:
    // h_rad = emissitivity * stefan-boltzmann constant * (T + T_infty) (T^2 +
    // T^2_infty).
    unsigned int const emissivity_prop =
        static_cast<unsigned int>(StateProperty::emissivity);
    unsigned int const radiation_heat_transfer_coef_prop =
        static_cast<unsigned int>(StateProperty::radiation_heat_transfer_coef);
    unsigned int const radiation_temperature_infty_prop =
        static_cast<unsigned int>(Property::radiation_temperature_infty);
    double const T = temperature_average[dof];
    double const T_infty =
        _properties(material_id, radiation_temperature_infty_prop);
    double const emissivity = _property_values[emissivity_prop][dof];
    _property_values[radiation_heat_transfer_coef_prop][dof] =
        emissivity * Constant::stefan_boltzmann * (T + T_infty) *
        (T * T + T_infty * T_infty);
  }
}

template <int dim>
void MaterialProperty<dim>::update_boundary_material_properties(
    dealii::DoFHandler<dim> const &temperature_dof_handler,
    dealii::LA::distributed::Vector<double> const &temperature)
{
  auto temperature_average =
      compute_average_temperature(temperature_dof_handler, temperature);
  for (auto &val : _property_values)
    val.reinit(temperature_average.get_partitioner());

  std::vector<dealii::types::global_dof_index> mp_dof(1);
  // We don't need to loop over all the active cells. We only need to loop over
  // the cells at the boundary and at the interface with FE_Nothing. However, to
  // do this we need to use the temperature_dof_handler instead of the
  // _mp_dof_handler.
  for (auto cell :
       dealii::filter_iterators(_mp_dof_handler.active_cell_iterators(),
                                dealii::IteratorFilters::LocallyOwnedCell()))
  {
    dealii::types::material_id material_id = cell->material_id();

    cell->get_dof_indices(mp_dof);
    unsigned int const dof = mp_dof[0];
    if (_use_table)
    {
      // We only care about properties that are used to compute the boundary
      // condition. So we start at 3.
      for (unsigned int property = 3; property < _n_state_properties;
           ++property)
      {
        for (unsigned int material_state = 0;
             material_state < _n_material_states; ++material_state)
        {
          _property_values[property][dof] +=
              _state[material_state][dof] *
              compute_property_from_table(material_id, material_state, property,
                                          temperature_average[dof]);
        }
      }
    }
    else
    {
      // We only care about properties that are used to compute the boundary
      // condition. So we start at  3.
      for (unsigned int property = 3; property < _n_state_properties;
           ++property)
      {
        for (unsigned int material_state = 0;
             material_state < _n_material_states; ++material_state)
        {
          for (unsigned int i = 0; i <= _polynomial_order; ++i)
          {
            _property_values[property][dof] +=
                _state[material_state][dof] *
                _state_property_polynomials(material_id, material_state,
                                            property, i) *
                std::pow(temperature_average[dof], i);
          }
        }
      }
    }

    // The radiation heat transfer coefficient is not a real material property
    // but it is derived from other material properties:
    // h_rad = emissitivity * stefan-boltzmann constant * (T + T_infty) (T^2 +
    // T^2_infty).
    unsigned int const emissivity_prop =
        static_cast<unsigned int>(StateProperty::emissivity);
    unsigned int const radiation_heat_transfer_coef_prop =
        static_cast<unsigned int>(StateProperty::radiation_heat_transfer_coef);
    unsigned int const radiation_temperature_infty_prop =
        static_cast<unsigned int>(Property::radiation_temperature_infty);
    double const T = temperature_average[dof];
    double const T_infty =
        _properties(material_id, radiation_temperature_infty_prop);
    double const emissivity = _property_values[emissivity_prop][dof];
    _property_values[radiation_heat_transfer_coef_prop][dof] =
        emissivity * Constant::stefan_boltzmann * (T + T_infty) *
        (T * T + T_infty * T_infty);
  }
}

template <int dim>
ADAMANTINE_HOST_DEV dealii::VectorizedArray<double>
MaterialProperty<dim>::compute_material_property(
    StateProperty state_property, dealii::types::material_id const *material_id,
    dealii::VectorizedArray<double> const *state_ratios,
    dealii::VectorizedArray<double> temperature) const
{
  dealii::VectorizedArray<double> value = 0.0;
  unsigned int const property_index = static_cast<unsigned int>(state_property);

  if (_use_table)
  {
    for (unsigned int material_state = 0; material_state < _n_material_states;
         ++material_state)
    {
      for (unsigned int n = 0; n < dealii::VectorizedArray<double>::size(); ++n)
      {

        const dealii::types::material_id m_id = material_id[n];

        value[n] += state_ratios[material_state][n] *
                    compute_property_from_table(m_id, material_state,
                                                property_index, temperature[n]);
      }
    }
  }
  else
  {
    for (unsigned int material_state = 0; material_state < _n_material_states;
         ++material_state)
    {
      for (unsigned int n = 0; n < dealii::VectorizedArray<double>::size(); ++n)
      {

        dealii::types::material_id m_id = material_id[n];

        for (unsigned int i = 0; i <= _polynomial_order; ++i)
        {
          value[n] += state_ratios[material_state][n] *
                      _state_property_polynomials(m_id, material_state,
                                                  property_index, i) *
                      std::pow(temperature[n], i);
        }
      }
    }
  }

  return value;
}

template <int dim>
void MaterialProperty<dim>::set_state(
    dealii::Table<2, dealii::VectorizedArray<double>> const &liquid_ratio,
    dealii::Table<2, dealii::VectorizedArray<double>> const &powder_ratio,
    std::map<typename dealii::DoFHandler<dim>::cell_iterator,
             std::pair<unsigned int, unsigned int>> &cell_it_to_mf_cell_map,
    dealii::DoFHandler<dim> const &dof_handler)
{
  auto const powder_state = static_cast<unsigned int>(MaterialState::powder);
  auto const liquid_state = static_cast<unsigned int>(MaterialState::liquid);
  auto const solid_state = static_cast<unsigned int>(MaterialState::solid);
  std::vector<dealii::types::global_dof_index> mp_dof(1.);

  for (auto cell :
       dealii::filter_iterators(dof_handler.active_cell_iterators(),
                                dealii::IteratorFilters::LocallyOwnedCell()))
  {
    typename dealii::Triangulation<dim>::active_cell_iterator cell_tria(cell);
    auto mp_dof_index = get_dof_index(cell_tria);
    auto const &mf_cell_vector = cell_it_to_mf_cell_map[cell];
    unsigned int const n_q_points = dof_handler.get_fe().tensor_degree() + 1;
    double liquid_ratio_sum = 0.;
    double powder_ratio_sum = 0.;
    for (unsigned int q = 0; q < n_q_points; ++q)
    {
      liquid_ratio_sum +=
          liquid_ratio(mf_cell_vector.first, q)[mf_cell_vector.second];
      powder_ratio_sum +=
          powder_ratio(mf_cell_vector.first, q)[mf_cell_vector.second];
    }
    _state[liquid_state][mp_dof_index] = liquid_ratio_sum / n_q_points;
    _state[powder_state][mp_dof_index] = powder_ratio_sum / n_q_points;
    _state[solid_state][mp_dof_index] =
        std::max(1. - _state[liquid_state][mp_dof_index] -
                     _state[powder_state][mp_dof_index],
                 0.);
  }
}

template <int dim>
void MaterialProperty<dim>::set_initial_state()
{
  // Set the material state to the one defined by the user_index
  std::vector<dealii::types::global_dof_index> mp_dof(1);
  for (auto cell :
       dealii::filter_iterators(_mp_dof_handler.active_cell_iterators(),
                                dealii::IteratorFilters::LocallyOwnedCell()))
  {
    cell->get_dof_indices(mp_dof);
    _state[cell->user_index()][mp_dof[0]] = 1.;
  }
}

template <int dim>
void MaterialProperty<dim>::fill_properties(
    boost::property_tree::ptree const &database)
{
  std::array<std::string, _n_material_states> material_state = {
      {"powder", "solid", "liquid"}};
  std::array<std::string, _n_properties> properties = {
      {"liquidus", "solidus", "latent_heat", "radiation_temperature_infty",
       "convection_temperature_infty"}};
  std::array<std::string, _n_state_properties> state_properties = {
      {"density", "specific_heat", "thermal_conductivity", "emissivity",
       "radiation_heat_transfer_coef", "convection_heat_transfer_coef"}};

  // PropertyTreeInput materials.property_format
  std::string property_format = database.get<std::string>("property_format");
  ASSERT_THROW((property_format == "table") ||
                   (property_format == "polynomial"),
               "property_format should be table or polynomial.");
  _use_table = (property_format == "table");
  // PropertyTreeInput materials.n_materials
  unsigned int const n_materials = database.get<unsigned int>("n_materials");
  // Find all the material_ids being used.
  std::vector<dealii::types::material_id> material_ids;
  for (dealii::types::material_id id = 0;
       id < dealii::numbers::invalid_material_id; ++id)
  {
    if (database.count("material_" + std::to_string(id)) != 0)
      material_ids.push_back(id);
    if (material_ids.size() == n_materials)
      break;
  }
  ASSERT_THROW(material_ids.size() == n_materials,
               "Could not find all the material_ids.");

  // When using the polynomial format we allocate one contiguous block of
  // memory. Thus, the largest material_id should be as small as possible
  unsigned int const n_material_ids =
      *std::max_element(material_ids.begin(), material_ids.end()) + 1;
  _properties.reinit(n_material_ids);
  if (_use_table)
  {
    // TODO read table size from input file
    _table_size = 4;
    _state_property_tables.reinit(n_material_ids, _table_size);
    _state_property_tables.set_zero();
  }
  else
  {
    // TODO read polynomial_order from input file
    _polynomial_order = 4;
    _state_property_polynomials.reinit(n_material_ids + 1,
                                       _polynomial_order + 1);
    _state_property_polynomials.set_zero();
  }

  for (auto const material_id : material_ids)
  {
    // Get the material property tree.
    boost::property_tree::ptree const &material_database =
        database.get_child("material_" + std::to_string(material_id));
    // For each material, loop over the possible states.
    bool valid_state = false;
    for (unsigned int state = 0; state < _n_material_states; ++state)
    {
      // The state may or may not exist for the material.
      boost::optional<boost::property_tree::ptree const &> state_database =
          material_database.get_child_optional(material_state[state]);
      if (state_database)
      {
        valid_state = true;
        // For each state, loop over the possible properties.
        for (unsigned int p = 0; p < _n_state_properties; ++p)
        {
          // The property may or may not exist for that state
          boost::optional<std::string> const property =
              state_database.get().get_optional<std::string>(
                  state_properties[p]);
          // If the property exists, put it in the map. If the property does not
          // exist, we have a nullptr.
          if (property)
          {
            // Remove blank spaces
            std::string property_string = property.get();
            property_string.erase(
                std::remove_if(property_string.begin(), property_string.end(),
                               [](unsigned char x) { return std::isspace(x); }),
                property_string.end());
            if (_use_table)
            {
              std::vector<std::string> parsed_property;
              boost::split(parsed_property, property_string,
                           [](char c) { return c == ';'; });
              unsigned int const parsed_property_size = parsed_property.size();
              ASSERT_THROW(parsed_property_size <= _table_size,
                           "Too many coefficients, increase the table size");
              for (unsigned int i = 0; i < parsed_property_size; ++i)
              {
                std::vector<std::string> t_v;
                boost::split(t_v, parsed_property[i],
                             [](char c) { return c == ','; });
                ASSERT(t_v.size() == 2, "Error reading material property.");
                _state_property_tables(material_id, state, p, i, 0) =
                    std::stod(t_v[0]);
                _state_property_tables(material_id, state, p, i, 1) =
                    std::stod(t_v[1]);
              }
              // fill the rest  with the last value
              for (unsigned int i = parsed_property_size; i < _table_size; ++i)
              {
                _state_property_tables(material_id, state, p, i, 0) =
                    _state_property_tables(material_id, state, p, i - 1, 0);
                _state_property_tables(material_id, state, p, i, 1) =
                    _state_property_tables(material_id, state, p, i - 1, 1);
              }
            }
            else
            {
              std::vector<std::string> parsed_property;
              boost::split(parsed_property, property_string,
                           [](char c) { return c == ','; });
              unsigned int const parsed_property_size = parsed_property.size();
              ASSERT_THROW(
                  parsed_property_size <= _polynomial_order,
                  "Too many coefficients, increase the polynomial order");
              for (unsigned int i = 0; i < parsed_property_size; ++i)
              {
                _state_property_polynomials(material_id, state, p, i) =
                    std::stod(parsed_property[i]);
              }
            }
          }
        }
      }
    }
    // Check that there is at least one valid MaterialState
    ASSERT_THROW(
        valid_state == true,
        "Material without any valid state (solid, powder, or liquid).");

    // Check for the properties that are associated to a material but that
    // are independent of an individual state. These properties are duplicated
    // for every state.
    for (unsigned int p = 0; p < _n_properties; ++p)
    {
      // The property may or may not exist for that state
      boost::optional<double> const property =
          material_database.get_optional<double>(properties[p]);
      // If the property exists, put it in the map. If the property does not
      // exist, we use the largest possible value. This is useful if the
      // liquidus and the solidus are not set.
      _properties(material_id, p) =
          property ? property.get() : std::numeric_limits<double>::max();
    }
  }
}

// We need to compute the average temperature on the cell because we need the
// material properties to be uniform over the cell. If there aren't then we have
// problems with the weak form discretization.
template <int dim>
dealii::LA::distributed::Vector<double>
MaterialProperty<dim>::compute_average_temperature(
    dealii::DoFHandler<dim> const &temperature_dof_handler,
    dealii::LA::distributed::Vector<double> const &temperature) const
{
  // TODO: this should probably done in a matrix-free fashion.
  // The triangulation is the same for both DoFHandler
  dealii::LA::distributed::Vector<double> temperature_average(
      _mp_dof_handler.locally_owned_dofs(), temperature.get_mpi_communicator());
  temperature.update_ghost_values();
  temperature_average = 0.;
  auto mp_cell = _mp_dof_handler.begin_active();
  auto mp_end_cell = _mp_dof_handler.end();
  auto enth_cell = temperature_dof_handler.begin_active();
  dealii::hp::FECollection<dim> const &fe_collection =
      temperature_dof_handler.get_fe_collection();
  dealii::hp::QCollection<dim> q_collection;
  q_collection.push_back(dealii::QGauss<dim>(fe_collection.max_degree() + 1));
  q_collection.push_back(dealii::QGauss<dim>(1));
  dealii::hp::FEValues<dim> hp_fe_values(
      fe_collection, q_collection,
      dealii::UpdateFlags::update_values |
          dealii::UpdateFlags::update_quadrature_points |
          dealii::UpdateFlags::update_JxW_values);
  std::vector<dealii::types::global_dof_index> mp_dof_indices(1);
  unsigned int const n_q_points = q_collection.max_n_quadrature_points();
  unsigned int const dofs_per_cell = fe_collection.max_dofs_per_cell();
  std::vector<dealii::types::global_dof_index> enth_dof_indices(dofs_per_cell);
  for (; mp_cell != mp_end_cell; ++enth_cell, ++mp_cell)
  {
    ASSERT(mp_cell->is_locally_owned() == enth_cell->is_locally_owned(),
           "Internal Error");
    if ((mp_cell->is_locally_owned()) && (enth_cell->active_fe_index() == 0))
    {
      hp_fe_values.reinit(enth_cell);
      dealii::FEValues<dim> const &fe_values =
          hp_fe_values.get_present_fe_values();
      mp_cell->get_dof_indices(mp_dof_indices);
      dealii::types::global_dof_index const mp_dof_index = mp_dof_indices[0];
      enth_cell->get_dof_indices(enth_dof_indices);
      double volume = 0.;
      for (unsigned int q = 0; q < n_q_points; ++q)
        for (unsigned int i = 0; i < dofs_per_cell; ++i)
        {
          volume += fe_values.shape_value(i, q) * fe_values.JxW(q);
          temperature_average[mp_dof_index] +=
              fe_values.shape_value(i, q) * temperature[enth_dof_indices[i]] *
              fe_values.JxW(q);
        }
      temperature_average[mp_dof_index] /= volume;
    }
  }

  return temperature_average;
}

template <int dim>
double MaterialProperty<dim>::compute_property_from_table(
    unsigned int const material_id, unsigned int const material_state,
    unsigned int const property, double const temperature) const
{
  if (temperature <=
      _state_property_tables(material_id, material_state, property, 0, 0))
  {
    return _state_property_tables(material_id, material_state, property, 0, 1);
  }
  else
  {
    unsigned int i = 0;
    unsigned int const size = _state_property_tables.extent(3);
    for (; i < size; ++i)
    {
      if (temperature <
          _state_property_tables(material_id, material_state, property, i, 0))
      {
        break;
      }
    }

    if (i >= size - 1)
    {
      return _state_property_tables(material_id, material_state, property,
                                    size - 1, 1);
    }
    else
    {
      auto tempertature_i =
          _state_property_tables(material_id, material_state, property, i, 0);
      auto tempertature_im1 = _state_property_tables(
          material_id, material_state, property, i - 1, 0);
      auto property_i =
          _state_property_tables(material_id, material_state, property, i, 1);
      auto property_im1 = _state_property_tables(material_id, material_state,
                                                 property, i - 1, 1);
      return property_im1 + (temperature - tempertature_im1) *
                                (property_i - property_im1) /
                                (tempertature_i - tempertature_im1);
    }
  }
}

} // namespace adamantine


//
//  main.cpp
//  test
//
//  Created by Maksym Lunin on 3/11/16.
//  Copyright © 2016 Maksym Lunin. All rights reserved.
//

#include <iostream>

#define BOOST_TEST_MAIN 1
#define BOOST_TEST_DYN_LINK

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

#define TEST 1

#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit))

#include "../../../game/game.ino"

#include <cassert>

using namespace states;

states::lamp next(std::initializer_list<int> bin){

   states::card inputs(0);
   for (size_t i = 0; i < bin.size(); ++i) {
      inputs.set(i, *(bin.begin()+i));
   }
   auto outputs = machine.next_state(&inputs);
   std::cout << "in: " << inputs.to_string('A') << " out: " << outputs.to_string('1')<< " " << machine.state_name() << std::endl;
   return outputs;
}

BOOST_AUTO_TEST_CASE(ABC){
   setup_state_machine();
   machine.reset();

   BOOST_CHECK(next({0,0,0,0}) ==  ____ .value);
   BOOST_CHECK(next({1,0,0,0}) ==  A___ .value);
   BOOST_CHECK(next({1,1,0,0}) ==  AB__ .value);
   BOOST_CHECK(next({1,1,1,0}) ==  ABC_ .value);
   BOOST_CHECK(next({1,0,1,0}) ==  A_C_2.value);
   BOOST_CHECK(next({1,1,1,0}) ==  ABC_2.value);
   BOOST_CHECK(next({1,1,1,1}) ==  ABCD .value);
}

BOOST_AUTO_TEST_CASE(ACB){
   setup_state_machine();
   machine.reset();

   BOOST_CHECK(next({0,0,0,0}) ==  ____ .value);
   BOOST_CHECK(next({1,0,0,0}) ==  A___ .value);
   BOOST_CHECK(next({1,0,1,0}) ==  A_C_ .value);
   BOOST_CHECK(next({1,1,1,0}) ==  ABC_ .value);
   BOOST_CHECK(next({1,0,1,0}) ==  A_C_2.value);
   BOOST_CHECK(next({1,1,1,0}) ==  ABC_2.value);
   BOOST_CHECK(next({1,1,1,1}) ==  ABCD .value);
}

BOOST_AUTO_TEST_CASE (BAC) {
   setup_state_machine();
   machine.reset();

   BOOST_CHECK(next({0,0,0,0}) == ____  .value);
   BOOST_CHECK(next({0,1,0,0}) == _B__  .value);
   BOOST_CHECK(next({1,1,0,0}) == AB__  .value);
   BOOST_CHECK(next({1,1,1,0}) == ABC_  .value);
   BOOST_CHECK(next({1,0,1,0}) == A_C_2 .value);
   BOOST_CHECK(next({1,1,1,0}) == ABC_2 .value);
   BOOST_CHECK(next({1,1,1,1}) == ABCD  .value);
}

BOOST_AUTO_TEST_CASE (BCA) {
   setup_state_machine();
   machine.reset();

   BOOST_CHECK(next({0,0,0,0}) == ____  .value);
   BOOST_CHECK(next({0,1,0,0}) == _B__  .value);
   BOOST_CHECK(next({0,1,1,0}) == _BC_  .value);
   BOOST_CHECK(next({1,1,1,0}) == ABC_  .value);
   BOOST_CHECK(next({1,0,1,0}) == A_C_2 .value);
   BOOST_CHECK(next({1,1,1,0}) == ABC_2 .value);
   BOOST_CHECK(next({1,1,1,1}) == ABCD  .value);
}

BOOST_AUTO_TEST_CASE (CAB) {
   setup_state_machine();
   machine.reset();

   BOOST_CHECK(next({0,0,0,0}) == ____  .value);
   BOOST_CHECK(next({0,0,1,0}) == __C_  .value);
   BOOST_CHECK(next({1,0,1,0}) == A_C_  .value);
   BOOST_CHECK(next({1,1,1,0}) == ABC_  .value);
   BOOST_CHECK(next({1,0,1,0}) == A_C_2 .value);
   BOOST_CHECK(next({1,1,1,0}) == ABC_2 .value);
   BOOST_CHECK(next({1,1,1,1}) == ABCD  .value);
}

BOOST_AUTO_TEST_CASE (CBA) {
   setup_state_machine();
   machine.reset();

   BOOST_CHECK(next({0,0,0,0}) == ____  .value);
   BOOST_CHECK(next({0,0,1,0}) == __C_  .value);
   BOOST_CHECK(next({0,1,1,0}) == _BC_  .value);
   BOOST_CHECK(next({1,1,1,0}) == ABC_  .value);
   BOOST_CHECK(next({1,0,1,0}) == A_C_2 .value);
   BOOST_CHECK(next({1,1,1,0}) == ABC_2 .value);
   BOOST_CHECK(next({1,1,1,1}) == ABCD  .value);
}

BOOST_AUTO_TEST_CASE(wrong_ABC){
   setup_state_machine();
   machine.reset();

   BOOST_CHECK(next({0,0,0,0}) ==  ____ .value);
   BOOST_CHECK(next({1,0,0,0}) ==  A___ .value);
   BOOST_CHECK(next({0,0,0,0}) ==  A___ .value);
   BOOST_CHECK(next({1,1,0,0}) ==  AB__ .value);
   BOOST_CHECK(next({0,0,0,0}) ==  AB__ .value);
   BOOST_CHECK(next({1,1,1,0}) ==  ABC_ .value);
   BOOST_CHECK(next({0,0,0,0}) ==  ABC_ .value);
   BOOST_CHECK(next({1,0,1,0}) ==  A_C_2.value);
   BOOST_CHECK(next({0,0,0,0}) ==  A_C_2.value);
   BOOST_CHECK(next({1,1,1,0}) ==  ABC_2.value);
   BOOST_CHECK(next({0,0,0,0}) ==  ABC_2.value);
   BOOST_CHECK(next({1,1,1,1}) ==  ABCD .value);
}


#include "formulation/updater/drift_diffusion_updater.hpp"

#include <deal.II/lac/vector.h>

#include "quadrature/calculators/tests/angular_flux_integrator_mock.hpp"
#include "formulation/scalar/tests/diffusion_mock.hpp"
#include "formulation/scalar/tests/drift_diffusion_mock.hpp"
#include "formulation/tests/stamper_mock.hpp"
#include "formulation/updater/tests/updater_tests.h"
#include "test_helpers/gmock_wrapper.h"
#include "test_helpers/test_helper_functions.h"
#include "test_helpers/test_assertions.hpp"
#include "system/solution/solution_types.h"
#include "system/moments/tests/spherical_harmonic_mock.h"

namespace  {

using namespace bart;
template <int dim>
using UpdaterTest = bart::formulation::updater::test_helpers::UpdaterTests<dim>;

using ::testing::A;
using ::testing::ContainerEq, ::testing::DoDefault, ::testing::_, ::testing::Ref, ::testing::Return, ::testing::ReturnRef;

template <typename DimensionWrapper>
class FormulationUpdaterDriftDiffusionTest : public UpdaterTest<DimensionWrapper::value> {
 public:
  static constexpr int dim{ DimensionWrapper::value };
  using AngularFluxStorageMap = system::solution::EnergyGroupToAngularSolutionPtrMap;
  using DiffusionFormulation = formulation::scalar::DiffusionMock<dim>;
  using DriftDiffusionFormulation = formulation::scalar::DriftDiffusionMock<dim>;
  using IntegratedFluxCalculator = quadrature::calculators::AngularFluxIntegratorMock;
  using Stamper = formulation::StamperMock<dim>;
  using Boundary = problem::Boundary;
  using Vector = dealii::Vector<double>;
  using GroupAngularFluxMap = std::map<quadrature::QuadraturePointIndex, std::shared_ptr<Vector>>;
  using HighOrderMoments = system::moments::SphericalHarmonicMock;

  // Test object
  using TestUpdater = formulation::updater::DriftDiffusionUpdater<dim>;
  std::unique_ptr<TestUpdater> test_updater_ptr_{ nullptr };

  // Supporting objects
  AngularFluxStorageMap angular_flux_storage_map_{}; // All angular fluxes
  GroupAngularFluxMap group_angular_flux_{}; // Angular fluxes for a single group (the test group)
  std::vector<dealii::Vector<double>> current_at_dofs_{};
  std::array<dealii::Vector<double>, dim> current_components_at_dofs_;
  dealii::Vector<double> group_scalar_flux_;

  // Mock observation pointers
  DiffusionFormulation* diffusion_formulation_obs_ptr_{ nullptr };
  DriftDiffusionFormulation* drift_diffusion_formulation_obs_ptr_{ nullptr };
  IntegratedFluxCalculator* integrated_flux_calculator_obs_ptr_{ nullptr };
  Stamper* stamper_obs_ptr_{ nullptr };
  std::shared_ptr<HighOrderMoments> high_order_moments_ptr_{ nullptr };

  // Test parameters (each should be a different value to ensure the correct values are being passed
  const Vector::size_type angular_flux_size{ static_cast<unsigned int>(test_helpers::RandomInt(5, 10)) };

  std::unordered_set<Boundary> reflective_boundaries{Boundary::kXMin, Boundary::kYMax};

  void SetUp() override;
};

template<typename DimensionWrapper>
void FormulationUpdaterDriftDiffusionTest<DimensionWrapper>::SetUp() {
  UpdaterTest<dim>::SetUp();
  auto diffusion_formulation_ptr = std::make_unique<DiffusionFormulation>();
  diffusion_formulation_obs_ptr_ = diffusion_formulation_ptr.get();
  auto drift_diffusion_formulation_ptr = std::make_unique<DriftDiffusionFormulation>();
  drift_diffusion_formulation_obs_ptr_ = drift_diffusion_formulation_ptr.get();
  auto integrated_flux_calculator_ptr = std::make_shared<IntegratedFluxCalculator>();
  integrated_flux_calculator_obs_ptr_ = integrated_flux_calculator_ptr.get();
  auto stamper_ptr = std::shared_ptr<Stamper>(this->MakeStamper());
  stamper_obs_ptr_ = stamper_ptr.get();
  high_order_moments_ptr_ = std::make_shared<HighOrderMoments>();
  group_scalar_flux_ = dealii::Vector<double>(angular_flux_size);

  for (unsigned int i = 0; i < angular_flux_size; ++i) {
    // Create a dim-sized current vector for each degree of freedom.
    Vector current_vector(dim);
    for (int dir = 0; dir < dim; ++dir)
      current_vector[dir] = test_helpers::RandomDouble(-100, 100);
    current_at_dofs_.push_back(current_vector);
  }
  for (int dir = 0; dir < dim; ++dir) {
    Vector current_component_at_dofs(angular_flux_size);
    for (unsigned int i = 0; i < angular_flux_size; ++i) {
      current_component_at_dofs[i] = current_at_dofs_.at(i)[dir];
    }
    current_components_at_dofs_[dir] = current_component_at_dofs;
  }

  using Index = system::SolutionIndex;
  for (int group = 0; group < this->total_groups; ++group) {
    for (int angle = 0; angle < this->total_angles; ++angle) {
      auto angular_flux = std::make_shared<Vector>(angular_flux_size);
      Index solution_index{ system::EnergyGroup(group), system::AngleIdx(angle) };
      angular_flux_storage_map_.insert({solution_index, angular_flux});
      if (group == this->group_number)
        group_angular_flux_.insert({quadrature::QuadraturePointIndex(angle), angular_flux});
    }
  }
  test_updater_ptr_ = std::make_unique<TestUpdater>(std::move(diffusion_formulation_ptr),
                                                    std::move(drift_diffusion_formulation_ptr),
                                                    stamper_ptr,
                                                    std::move(integrated_flux_calculator_ptr),
                                                    high_order_moments_ptr_,
                                                    angular_flux_storage_map_,
                                                    reflective_boundaries);
}

TYPED_TEST_SUITE(FormulationUpdaterDriftDiffusionTest, bart::testing::AllDimensions);

// Constructor should not throw, dependencies should have been saved and getters return non-null ptrs
TYPED_TEST(FormulationUpdaterDriftDiffusionTest, ConstructorDependencyGetters) {
  constexpr int dim{ this->dim };
  using DiffusionFormulation = formulation::scalar::DiffusionMock<dim>;
  using DriftDiffusionFormulation = formulation::scalar::DriftDiffusionMock<dim>;
  using IntegratedFluxCalculator = quadrature::calculators::AngularFluxIntegratorMock;
  using Updater = formulation::updater::DriftDiffusionUpdater<dim>;
  using Stamper = formulation::StamperMock<dim>;

  std::unique_ptr<Updater> test_updater{ nullptr };
  EXPECT_NO_THROW({
    test_updater = std::make_unique<Updater>(std::make_unique<DiffusionFormulation>(),
                                             std::make_unique<DriftDiffusionFormulation>(),
                                             std::make_shared<Stamper>(),
                                             std::make_shared<IntegratedFluxCalculator>(),
                                             this->high_order_moments_ptr_,
                                             this->angular_flux_storage_map_);
  });
  ASSERT_NE(test_updater->formulation_ptr(), nullptr);
  ASSERT_NE(test_updater->drift_diffusion_formulation_ptr(), nullptr);
  ASSERT_NE(test_updater->stamper_ptr(), nullptr);
  ASSERT_NE(test_updater->integrated_flux_calculator_ptr(), nullptr);
  EXPECT_EQ(test_updater->high_order_moments(), this->high_order_moments_ptr_.get());
  ASSERT_THAT(test_updater->angular_flux_storage_map(), ContainerEq(this->angular_flux_storage_map_));
}

TYPED_TEST(FormulationUpdaterDriftDiffusionTest, ConstructorBadDependencies) {
  constexpr int dim{ this->dim };
  using DiffusionFormulation = formulation::scalar::DiffusionMock<dim>;
  using DriftDiffusionFormulation = formulation::scalar::DriftDiffusionMock<dim>;
  using IntegratedFluxCalculator = quadrature::calculators::AngularFluxIntegratorMock;
  using Updater = formulation::updater::DriftDiffusionUpdater<dim>;
  using Stamper = formulation::StamperMock<dim>;
  using HighOrderMoments = system::moments::SphericalHarmonicMock;

  std::unique_ptr<Updater> test_updater{ nullptr };
  const int n_dependencies_to_check{ 5 };
  for (int i = 0; i < n_dependencies_to_check; ++i) {
    EXPECT_ANY_THROW({
      test_updater = std::make_unique<Updater>(i == 0 ? nullptr : std::make_unique<DiffusionFormulation>(),
                                               i == 1 ? nullptr : std::make_unique<DriftDiffusionFormulation>(),
                                               i == 2 ? nullptr : std::make_unique<Stamper>(),
                                               i == 3 ? nullptr : std::make_shared<IntegratedFluxCalculator>(),
                                               i == 4 ? nullptr : std::make_shared<HighOrderMoments>(),
                                               this->angular_flux_storage_map_);
                     });
  }
}

TYPED_TEST(FormulationUpdaterDriftDiffusionTest, UpdateFixedTermTest) {
  system::EnergyGroup group_number(this->group_number);
  quadrature::QuadraturePointIndex angle_index(this->angle_index);
  bart::system::Index scalar_index{this->group_number, 0};

  EXPECT_CALL(*this->mock_lhs_obs_ptr_, GetFixedTermPtr(scalar_index)).WillOnce(DoDefault());
  EXPECT_CALL(*this->mock_rhs_obs_ptr_, GetFixedTermPtr(scalar_index)).WillOnce(DoDefault());

  std::array<int, 3> moment_index{this->group_number, 0, 0};
  EXPECT_CALL(*this->high_order_moments_ptr_, GetMoment(moment_index)).WillOnce(ReturnRef(this->group_scalar_flux_));
  EXPECT_CALL(*this->integrated_flux_calculator_obs_ptr_, NetCurrent(_))
      .WillOnce(Return(this->current_at_dofs_));

  for (auto& cell : this->cells_) {
    EXPECT_CALL(*this->diffusion_formulation_obs_ptr_, FillCellStreamingTerm(_, cell, this->group_number));
    EXPECT_CALL(*this->diffusion_formulation_obs_ptr_, FillCellCollisionTerm(_, cell, this->group_number));
    EXPECT_CALL(*this->diffusion_formulation_obs_ptr_, FillCellFixedSource(_, cell, this->group_number));
    EXPECT_CALL(*this->drift_diffusion_formulation_obs_ptr_,
        FillCellDriftDiffusionTerm(_,
                                   cell,
                                   system::EnergyGroup(this->group_number),
                                   this->group_scalar_flux_,
                                   ContainerEq(this->current_components_at_dofs_)));
    if (cell->at_boundary()) {
      int faces_per_cell = dealii::GeometryInfo<this->dim>::faces_per_cell;
      for (int face = 0; face < faces_per_cell; ++face) {
        if (cell->face(face)->at_boundary()) {
          problem::Boundary boundary_id = static_cast<problem::Boundary>(cell->face(face)->boundary_id());

          using BoundaryType = typename formulation::scalar::DiffusionI<this->dim>::BoundaryType;
          BoundaryType boundary_type = BoundaryType::kVacuum;
          formulation::BoundaryType formulation_boundary_type{ formulation::BoundaryType::kVacuum };
          if (this->reflective_boundaries.count(boundary_id) == 1) {
            boundary_type = BoundaryType::kReflective;
            formulation_boundary_type = formulation::BoundaryType::kReflective;
          }

          EXPECT_CALL(*this->diffusion_formulation_obs_ptr_, FillBoundaryTerm(_, cell, face, boundary_type));
          EXPECT_CALL(*this->drift_diffusion_formulation_obs_ptr_,
              FillCellBoundaryTerm(_, cell, domain::FaceIndex(face), formulation_boundary_type,
                                   this->group_angular_flux_));
        }
      }
    }
  }

  EXPECT_CALL(*this->stamper_obs_ptr_, StampMatrix(Ref(*this->matrix_to_stamp), _))
      .Times(3)
      .WillRepeatedly(DoDefault());
  EXPECT_CALL(*this->stamper_obs_ptr_, StampBoundaryMatrix(Ref(*this->matrix_to_stamp), _))
      .Times(2)
      .WillRepeatedly(DoDefault());
  EXPECT_CALL(*this->stamper_obs_ptr_, StampVector(Ref(*this->vector_to_stamp), _)).WillOnce(DoDefault());

  this->test_updater_ptr_->UpdateFixedTerms(this->test_system_, group_number, angle_index);
  EXPECT_TRUE(test_helpers::AreEqual(this->expected_result, *this->matrix_to_stamp));
  EXPECT_TRUE(test_helpers::AreEqual(this->expected_vector_result, *this->vector_to_stamp));
}

} // namespace


// Copyright (c) 2016-2021 Duality Blockchain Solutions Developers
// Copyright (c) 2009-2021 The Bitcoin Developers
// Copyright (c) 2009-2021 Satoshi Nakamoto
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "key.h"

#include "base58.h"
#include "script/script.h"
#include "uint256.h"
#include "util.h"
#include "utilstrencodings.h"
#include "test/test_dynamic.h"
#include "dht/ed25519.h"

#include <string>
#include <stdint.h>
#include <string.h>
#include <vector>

#include <boost/test/unit_test.hpp>

//dht_key_tests

BOOST_FIXTURE_TEST_SUITE(dht_key_tests, BasicTestingSetup)

BOOST_AUTO_TEST_CASE(dht_key_getlinksharedpubkey_test)
{
    CKeyEd25519 dhtKey1;
    CKeyEd25519 dhtKey2;
    std::vector<unsigned char> vchSharedPubKey1 = GetLinkSharedPubKey(dhtKey1, dhtKey2.GetPubKey());
    std::vector<unsigned char> vchSharedPubKey2 = GetLinkSharedPubKey(dhtKey2, dhtKey1.GetPubKey());
    BOOST_CHECK(vchSharedPubKey1 == vchSharedPubKey2);
    std::cout << "Exit: dht_key_getlinksharedpubkey_test\n";

}

BOOST_AUTO_TEST_SUITE_END()


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

#ifndef SOURCES_SAMPLER_METROPOLISHASTINGS_HPP
#define SOURCES_SAMPLER_METROPOLISHASTINGS_HPP

#include <Eigen/Core>
#include <functional>
#include <limits>
#include <vector>

#include "Sampler/abstract_sampler.hpp"
#include "Utils/exceptions.hpp"
#include "Utils/messages.hpp"
#include "Utils/random_utils.hpp"

namespace netket {

class MetropolisHastings : public AbstractSampler {
 public:
  using TransitionKernel = std::function<void(
      Eigen::Ref<const RowMatrix<double>>, Eigen::Ref<RowMatrix<double>>,
      Eigen::Ref<Eigen::ArrayXd>)>;

 private:
  RowMatrix<double> current_X_;
  RowMatrix<double> proposed_X_;

  Eigen::ArrayXcd current_Y_;
  Eigen::ArrayXcd proposed_Y_;

  Eigen::ArrayXcd quotient_Y_;
  Eigen::ArrayXd probability_;

  Eigen::ArrayXd log_acceptance_correction_;

  Eigen::Array<bool, Eigen::Dynamic, 1> accept_;

  TransitionKernel transition_kernel_;

  Index n_chains_;
  Index sweep_size_;
  Index batch_size_;

  Index accepted_samples_;
  Index total_samples_;

 public:
  MetropolisHastings(AbstractMachine& ma, TransitionKernel tk, Index n_chains,
                     Index sweep_size, Index batch_size);

  Index BatchSize() const noexcept override;

  Index SweepSize() const noexcept;
  void SweepSize(Index sweep_size);

  Index NChains() const noexcept override;

  /// Returns a batch of current visible states and corresponding log values.
  std::pair<Eigen::Ref<const RowMatrix<double>>,
            Eigen::Ref<const Eigen::VectorXcd>>
  CurrentState() const override;

  void SetVisible(Eigen::Ref<const RowMatrix<double>> x) override;

  void OneStep();

  void Sweep() override;

  /// Resets the sampler.
  void Reset(bool init_random) override;

  NETKET_SAMPLER_ACCEPTANCE_DEFAULT(accepted_samples_, total_samples_);

 private:
  void LogValBatched(Eigen::Ref<const RowMatrix<double>> v,
                     Eigen::Ref<AbstractMachine::VectorType> out,
                     const any& cache);
};

namespace detail {
inline Index CheckBatchSize(const char* func, const Index batch_size,
                            const Index n_chains) {
  if (batch_size <= 0) {
    std::ostringstream msg;
    msg << func << ": invalid batch size: " << batch_size
        << "; expected a positive number";
    throw InvalidInputError{msg.str()};
  }
  if (n_chains % batch_size != 0) {
    WarningMessage()
        << "The total number of chains is not an integer multiple of the "
           "batch_size, this can lead to suboptimal performance. \n";
  }
  if (n_chains < batch_size) {
    WarningMessage()
        << "The requested batch size is larger than the number of chains, the "
           "effective batch size used will be n_chains. \n";
  }
  return batch_size;
}
}  // namespace detail

}  // namespace netket

#endif


#ifndef KANOOTH_NUMBERS_BOOST_NATNUM_HPP
#define KANOOTH_NUMBERS_BOOST_NATNUM_HPP

#include <boost/multiprecision/number.hpp>
#include <boost/multiprecision/detail/integer_ops.hpp>
#include <boost/multiprecision/detail/digits.hpp>
#include <boost/mpl/contains.hpp>
#include <boost/mpl/or.hpp>
#include <boost/type_traits/make_unsigned.hpp>
#include <boost/cstdint.hpp>
#include <limits>

#include <kanooth/numbers/integer_base.hpp>
#include <kanooth/numbers/natural_number.hpp>

namespace boost {
namespace multiprecision {
namespace backends {

template <typename B>
struct kanooth_natnum;

} // namespace backends

template <typename B>
struct number_category<backends::kanooth_natnum<B> > : public mpl::int_<number_kind_integer> {};

namespace backends {

template <typename B>
class kanooth_natnum : public kanooth::numbers::natural_number_base<B>
{
public:
    typedef mpl::list<long>          signed_types;
    typedef mpl::list<unsigned long> unsigned_types;
    typedef mpl::list<>              float_types;

    template <typename T, typename R>
    struct if_supported_int
        : public enable_if<mpl::or_<mpl::contains<unsigned_types, T>,
                                    mpl::contains<signed_types, T> >, R> {};

    kanooth_natnum() : base_type() {}
    //kanooth_natnum(long v) : integer_base(v) {}
    kanooth_natnum(unsigned long v) : base_type(v) {}
    kanooth_natnum(const char* s) : base_type(s) {}

private:
    typedef kanooth::numbers::natural_number_base<B> base_type;
};

namespace {
    template <typename T>
    typename boost::make_unsigned<T>::type to_unsigned(T v)
    {
        return static_cast<typename boost::make_unsigned<T>::type>(v);
    }
}

template <typename B>
inline bool eval_is_zero(const kanooth_natnum<B>& val)
{
    return val.is_zero();
}

template <typename B>
inline void eval_add(kanooth_natnum<B>& r, const kanooth_natnum<B>& a)
{
    r.add(r, a);
}
template <typename B>
inline void eval_add(kanooth_natnum<B>& r, const kanooth_natnum<B>& a, const kanooth_natnum<B>& b)
{
    r.add(a, b);
}
template <typename B>
inline void eval_subtract(kanooth_natnum<B>& r, const kanooth_natnum<B>& a)
{
    r.subtract(r, a);
}
template <typename B>
inline void eval_subtract(kanooth_natnum<B>& r, const kanooth_natnum<B>& a, const kanooth_natnum<B>& b)
{
    r.subtract(a, b);
}
template <typename B>
inline void eval_multiply(kanooth_natnum<B>& r, const kanooth_natnum<B>& a)
{
    r.multiply(r, a);
}
template <typename B>
inline void eval_multiply(kanooth_natnum<B>& r, const kanooth_natnum<B>& a, const kanooth_natnum<B>& b)
{
    r.multiply(a, b);
}
template <typename B>
inline void eval_divide(kanooth_natnum<B>& r, const kanooth_natnum<B>& a)
{
    r.divide(r, a);
}
template <typename B>
inline void eval_divide(kanooth_natnum<B>& r, const kanooth_natnum<B>& a, const kanooth_natnum<B>& b)
{
    r.divide(a, b);
}
template <typename B>
inline void eval_modulus(kanooth_natnum<B>& r, const kanooth_natnum<B>& a)
{
    r.modulus(r, a);
}
template <typename B>
inline void eval_modulus(kanooth_natnum<B>& r, const kanooth_natnum<B>& a, const kanooth_natnum<B>& b)
{
    r.modulus(a, b);
}

template <typename B, typename T>
inline typename kanooth_natnum<B>::template if_supported_int<T, void>::type
eval_add(kanooth_natnum<B>& r, T a)
{
    r.add(r, to_unsigned(a));
}

template <typename B, typename T>
inline typename kanooth_natnum<B>::template if_supported_int<T, void>::type
eval_subtract(kanooth_natnum<B>& r, T a)
{
    r.subtract(r, to_unsigned(a));
}

template <typename B, typename T>
inline typename kanooth_natnum<B>::template if_supported_int<T, void>::type
eval_multiply(kanooth_natnum<B>& r, T a)
{
    r.multiply(r, to_unsigned(a));
}

template <typename B, typename T>
inline typename kanooth_natnum<B>::template if_supported_int<T, void>::type
eval_divide(kanooth_natnum<B>& r, T a)
{
    r.divide(r, to_unsigned(a));
}

template <typename B, typename T>
inline typename kanooth_natnum<B>::template if_supported_int<T, void>::type
eval_modulus(kanooth_natnum<B>& r, T a)
{
    r.modulus(r, to_unsigned(a));
}

template <typename B, typename T>
inline typename kanooth_natnum<B>::template if_supported_int<T, void>::type
eval_add(kanooth_natnum<B>& r, kanooth_natnum<B>& a, T b)
{
    r.add(a, to_unsigned(b));
}

template <typename B, typename T>
inline typename kanooth_natnum<B>::template if_supported_int<T, void>::type
eval_subtract(kanooth_natnum<B>& r, kanooth_natnum<B>& a, T b)
{
    r.subtract(a, to_unsigned(b));
}

template <typename B, typename T>
inline typename kanooth_natnum<B>::template if_supported_int<T, void>::type
eval_multiply(kanooth_natnum<B>& r, kanooth_natnum<B>& a, T b)
{
    r.multiply(a, to_unsigned(b));
}

template <typename B, typename T>
inline typename kanooth_natnum<B>::template if_supported_int<T, void>::type
eval_divide(kanooth_natnum<B>& r, kanooth_natnum<B>& a, T b)
{
    r.divide(a, to_unsigned(b));
}

template <typename B, typename T>
inline typename kanooth_natnum<B>::template if_supported_int<T, void>::type
eval_modulus(kanooth_natnum<B>& r, kanooth_natnum<B>& a, T b)
{
    r.modulus(a, to_unsigned(b));
}

template <typename B, typename T>
inline void eval_qr(const kanooth_natnum<B>& x, const kanooth_natnum<B>& y, kanooth_natnum<B>& q, kanooth_natnum<B>& r)
{
    kanooth_natnum<B>::quotrem(q, r, x, y);
}

template <typename B, class Integer>
inline typename enable_if<is_unsigned<Integer>, Integer>::type eval_integer_modulus(const kanooth_natnum<B>& x, Integer val)
{
    if ((sizeof(Integer) <= sizeof(long)) || (val <= (std::numeric_limits<unsigned long>::max)())) {
        return kanooth_natnum<B>::integer_modulus(x, static_cast<unsigned long>(val));
    } else {
        return default_ops::eval_integer_modulus(x, val);
    }
}

template <typename B, class Integer>
inline typename enable_if<is_signed<Integer>, Integer>::type eval_integer_modulus(const kanooth_natnum<B>& x, Integer val)
{
   typedef typename make_unsigned<Integer>::type unsigned_type;
   return eval_integer_modulus(x, static_cast<unsigned_type>(std::abs(val)));
}

template <typename B>
inline void eval_bitwise_and(kanooth_natnum<B>& result, const kanooth_natnum<B>& v)
{
   result.bitwise_and(result, v);
}

template <typename B>
inline void eval_bitwise_or(kanooth_natnum<B>& result, const kanooth_natnum<B>& v)
{
   result.bitwise_or(result, v);
}

template <typename B>
inline void eval_bitwise_xor(kanooth_natnum<B>& result, const kanooth_natnum<B>& v)
{
   result.bitwise_xor(result, v);
}

template <typename B>
inline void eval_bitwise_and(kanooth_natnum<B>& result, const kanooth_natnum<B>& u, const kanooth_natnum<B>& v)
{
   result.bitwise_and(u, v);
}

template <typename B>
inline void eval_bitwise_or(kanooth_natnum<B>& result, const kanooth_natnum<B>& u, const kanooth_natnum<B>& v)
{
   result.bitwise_or(u, v);
}

template <typename B>
inline void eval_bitwise_xor(kanooth_natnum<B>& result, const kanooth_natnum<B>& u, const kanooth_natnum<B>& v)
{
   result.bitwise_xor(u, v);
}

template <typename B>
inline void eval_left_shift(kanooth_natnum<B>& r, unsigned long v)
{
   r.left_shift(r, v);
}

template <typename B>
inline void eval_left_shift(kanooth_natnum<B>& r, const kanooth_natnum<B>& u, unsigned long v)
{
   r.left_shift(u, v);
}

template <typename B>
inline void eval_right_shift(kanooth_natnum<B>& r, unsigned long v)
{
   r.right_shift(r, v);
}

template <typename B>
inline void eval_right_shift(kanooth_natnum<B>& r, const kanooth_natnum<B>& u, unsigned long v)
{
   r.right_shift(u, v);
}

template <typename B>
inline bool eval_bit_test(const kanooth_natnum<B>& u, unsigned pos)
{
    return u.bit_test(pos);
}

template <typename B>
inline void eval_bit_set(kanooth_natnum<B>& u, unsigned pos)
{
    u.bit_set(pos);
}

template <typename B>
inline void eval_bit_unset(kanooth_natnum<B>& u, unsigned pos)
{
    u.bit_unset(pos);
}

template <typename B>
inline void eval_bit_flip(kanooth_natnum<B>& u, unsigned pos)
{
    u.bit_flip(pos);
}

template <typename B>
inline unsigned eval_lsb(const kanooth_natnum<B>& u)
{
    return u.lsb();
}

template <typename B>
inline void eval_gcd(kanooth_natnum<B>& result, const kanooth_natnum<B>& a, const kanooth_natnum<B>& b)
{
    result.gcd(a, b);
}

template <typename B>
void eval_lcm(kanooth_natnum<B>& result, const kanooth_natnum<B>& a, const kanooth_natnum<B>& b)
{
    kanooth_natnum<B> t;
    eval_gcd(t, a, b);

    if (eval_is_zero(t)) {
        result = 0ul;
    } else {
        eval_divide(result, a, t);
        eval_multiply(result, b);
    }
}

template <typename B>
inline bool eval_eq(const kanooth_natnum<B>& a, const kanooth_natnum<B>& b)
{
    return a.compare(b) == 0;
}

template <typename B, typename T>
inline typename kanooth_natnum<B>::template if_supported_int<T, bool>::type
eval_eq(const kanooth_natnum<B>& a, T b)
{
    return a.compare(to_unsigned(b)) == 0;
}

template <typename B>
inline bool eval_lt(const kanooth_natnum<B>& a, const kanooth_natnum<B>& b)
{
    return a.compare(b) < 0;
}

template <typename B, typename T>
inline typename kanooth_natnum<B>::template if_supported_int<T, bool>::type
eval_lt(const kanooth_natnum<B>& a, T b)
{
    return a.compare(b) < 0;
}

template <typename B>
inline bool eval_gt(const kanooth_natnum<B>& a, const kanooth_natnum<B>& b)
{
    return a.compare(b) > 0;
}

template <typename B, typename T>
inline typename kanooth_natnum<B>::template if_supported_int<T, bool>::type
eval_gt(const kanooth_natnum<B>& a, T b)
{
    return a.compare(b) > 0;
}

} // namespace backends
} // namespace multiprecision
} // namespace boost

namespace kanooth {
namespace numbers {

typedef boost::multiprecision::number<boost::multiprecision::backends::kanooth_natnum<best_low_level> > boost_natnum;

} // namespace numbers
} // namespace kanooth

namespace std {

template <typename B, boost::multiprecision::expression_template_option ExpressionTemplates>
class numeric_limits<boost::multiprecision::number<boost::multiprecision::backends::kanooth_natnum<B>, ExpressionTemplates> >
{
   typedef boost::multiprecision::number<boost::multiprecision::backends::kanooth_natnum<B>, ExpressionTemplates> number_type;
public:
   BOOST_STATIC_CONSTEXPR bool is_specialized = true;
   //
   // Largest and smallest numbers are bounded only by available memory, set
   // to zero:
   //
   static number_type (min)() BOOST_NOEXCEPT
   {
      return number_type();
   }
   static number_type (max)() BOOST_NOEXCEPT
   {
      return number_type();
   }
   static number_type lowest() BOOST_NOEXCEPT { return (min)(); }
   BOOST_STATIC_CONSTEXPR int digits = INT_MAX;
   BOOST_STATIC_CONSTEXPR int digits10 = (INT_MAX / 1000) * 301L;
   BOOST_STATIC_CONSTEXPR int max_digits10 = digits10 + 2;
   BOOST_STATIC_CONSTEXPR bool is_signed = false;
   BOOST_STATIC_CONSTEXPR bool is_integer = true;
   BOOST_STATIC_CONSTEXPR bool is_exact = true;
   BOOST_STATIC_CONSTEXPR int radix = 2;
   static number_type epsilon() BOOST_NOEXCEPT { return number_type(); }
   static number_type round_error() BOOST_NOEXCEPT { return number_type(); }
   BOOST_STATIC_CONSTEXPR int min_exponent = 0;
   BOOST_STATIC_CONSTEXPR int min_exponent10 = 0;
   BOOST_STATIC_CONSTEXPR int max_exponent = 0;
   BOOST_STATIC_CONSTEXPR int max_exponent10 = 0;
   BOOST_STATIC_CONSTEXPR bool has_infinity = false;
   BOOST_STATIC_CONSTEXPR bool has_quiet_NaN = false;
   BOOST_STATIC_CONSTEXPR bool has_signaling_NaN = false;
   BOOST_STATIC_CONSTEXPR float_denorm_style has_denorm = denorm_absent;
   BOOST_STATIC_CONSTEXPR bool has_denorm_loss = false;
   static number_type infinity() BOOST_NOEXCEPT { return number_type(); }
   static number_type quiet_NaN() BOOST_NOEXCEPT { return number_type(); }
   static number_type signaling_NaN() BOOST_NOEXCEPT { return number_type(); }
   static number_type denorm_min() BOOST_NOEXCEPT { return number_type(); }
   BOOST_STATIC_CONSTEXPR bool is_iec559 = false;
   BOOST_STATIC_CONSTEXPR bool is_bounded = false;
   BOOST_STATIC_CONSTEXPR bool is_modulo = false;
   BOOST_STATIC_CONSTEXPR bool traps = false;
   BOOST_STATIC_CONSTEXPR bool tinyness_before = false;
   BOOST_STATIC_CONSTEXPR float_round_style round_style = round_toward_zero;
};

} // namespace std

#endif // KANOOTH_NUMBERS_BOOST_NATNUM_HPP


#ifndef NEIGHBORHOODS_HPP
#define NEIGHBORHOODS_HPP

#include <stdio.h>
#include <vector>
#include <boost/unordered_map.hpp>

class Neighborhoods {
public:

	typedef std::vector<std::pair<float, float> > Geometry;
//  typedef boost::unordered_map<std::string, Geometry> GeometryMap;

	Neighborhoods() {
	}

	static void getBounds(const Geometry &geom, float bounds[4]) {
		if (geom.size() == 0) {
			bounds[0] = bounds[1] = -1e30;
			bounds[2] = bounds[3] = 1e30;
		} else {
			bounds[0] = bounds[1] = 1e30;
			bounds[2] = bounds[3] = -1e30;
			for (size_t i = 0; i < geom.size(); i++) {
				if (geom[i].first < bounds[0])
					bounds[0] = geom[i].first;
				if (geom[i].first > bounds[2])
					bounds[2] = geom[i].first;
				if (geom[i].second < bounds[1])
					bounds[1] = geom[i].second;
				if (geom[i].second > bounds[3])
					bounds[3] = geom[i].second;
			}
		}
	}

#ifdef __CUDACC__
	__host__ __device__
#endif
	static bool isInside(int nvert, float *vert, float testx, float testy) {
		if (nvert <= 0)
			return true;
		float firstX = vert[0];
		float firstY = vert[1];
		int i, j, c = 0;
		for (i = 1, j = 0; i < nvert; j = i++) {
			if (((vert[i * 2 + 1] > testy) != (vert[j * 2 + 1] > testy))
					&& (testx
							< (vert[j * 2] - vert[i * 2])
									* (testy - vert[i * 2 + 1])
									/ (vert[j * 2 + 1] - vert[i * 2 + 1])
									+ vert[i * 2]))
				c = !c;
			if (vert[i * 2] == firstX && vert[i * 2 + 1] == firstY) {
				if (++i < nvert) {
					firstX = vert[i * 2];
					firstY = vert[i * 2 + 1];
				}
			}
		}
		return c;
	}

//private:
//  GeometryMap geometries;
};

#endif