// Distributed under the MIT License.
// See LICENSE.txt for details.

#pragma once

#include <algorithm>
#include <array>
#include <boost/functional/hash.hpp>
#include <cstddef>
#include <iterator>
#include <utility>

#include "DataStructures/DataVector.hpp"
#include "DataStructures/FixedHashMap.hpp"
#include "DataStructures/Index.hpp"
#include "DataStructures/Tensor/EagerMath/DotProduct.hpp"
#include "DataStructures/Tensor/Tensor.hpp"
#include "DataStructures/Variables.hpp"
#include "Domain/Structure/Direction.hpp"
#include "Domain/Structure/DirectionMap.hpp"
#include "Domain/Structure/Element.hpp"
#include "Domain/Structure/ElementId.hpp"
#include "Domain/Structure/MaxNumberOfNeighbors.hpp"
#include "Evolution/DgSubcell/NeighborData.hpp"
#include "Evolution/Systems/GrMhd/ValenciaDivClean/ConservativeFromPrimitive.hpp"
#include "Evolution/Systems/GrMhd/ValenciaDivClean/Tags.hpp"
#include "NumericalAlgorithms/Spectral/Mesh.hpp"
#include "PointwiseFunctions/Hydro/EquationsOfState/EquationOfState.hpp"
#include "PointwiseFunctions/Hydro/SpecificEnthalpy.hpp"
#include "PointwiseFunctions/Hydro/Tags.hpp"
#include "Utilities/ErrorHandling/Assert.hpp"
#include "Utilities/Gsl.hpp"

namespace grmhd::ValenciaDivClean::fd {
template <typename TagsList, size_t ThermodynamicDim>
void compute_conservatives_for_reconstruction(
    const gsl::not_null<Variables<TagsList>*> vars_on_face,
    const EquationsOfState::EquationOfState<true, ThermodynamicDim>&
        eos) {
  // Computes:
  // 1. W v^i
  // 2. Lorentz factor as sqrt(1 + Wv^i Wv^j\gamma_{ij})
  // 3. v^i = Wv^i / W
  // 4. specific internal energy
  // 5. specific enthalpy
  // 6. conserved variables
  // - note: spatial metric, inv spatial metric, lapse, and shift are
  //         all already in vars_on_face
  const auto& spatial_metric = get<gr::Tags::SpatialMetric<3>>(*vars_on_face);
  const auto& lorentz_factor_times_spatial_velocity =
      get<hydro::Tags::LorentzFactorTimesSpatialVelocity<DataVector, 3>>(
          *vars_on_face);
  auto& lorentz_factor =
      get<hydro::Tags::LorentzFactor<DataVector>>(*vars_on_face);
  get(lorentz_factor) = 0.0;
  for (size_t i = 0; i < 3; ++i) {
    get(lorentz_factor) += spatial_metric.get(i, i) *
                           square(lorentz_factor_times_spatial_velocity.get(i));
    for (size_t j = i + 1; j < 3; ++j) {
      get(lorentz_factor) += 2.0 * spatial_metric.get(i, j) *
                             lorentz_factor_times_spatial_velocity.get(i) *
                             lorentz_factor_times_spatial_velocity.get(j);
    }
  }
  get(lorentz_factor) = sqrt(1.0 + get(lorentz_factor));
  auto& spatial_velocity =
      get<hydro::Tags::SpatialVelocity<DataVector, 3>>(*vars_on_face) =
          lorentz_factor_times_spatial_velocity;
  for (size_t i = 0; i < 3; ++i) {
    spatial_velocity.get(i) /= get(lorentz_factor);
  }
  const auto& rest_mass_density =
      get<hydro::Tags::RestMassDensity<DataVector>>(*vars_on_face);
  const auto& pressure = get<hydro::Tags::Pressure<DataVector>>(*vars_on_face);
  auto& specific_internal_energy =
      get<hydro::Tags::SpecificInternalEnergy<DataVector>>(*vars_on_face);
  if constexpr (ThermodynamicDim == 2) {
    specific_internal_energy =
        eos.specific_internal_energy_from_density_and_pressure(
            rest_mass_density, pressure);
  } else {
    specific_internal_energy =
        eos.specific_internal_energy_from_density(rest_mass_density);
  }
  auto& specific_enthalpy =
      get<hydro::Tags::SpecificEnthalpy<DataVector>>(*vars_on_face);
  hydro::relativistic_specific_enthalpy(make_not_null(&specific_enthalpy),
                                        rest_mass_density,
                                        specific_internal_energy, pressure);
  ConservativeFromPrimitive::apply(
      make_not_null(&get<ValenciaDivClean::Tags::TildeD>(*vars_on_face)),
      make_not_null(&get<ValenciaDivClean::Tags::TildeTau>(*vars_on_face)),
      make_not_null(
          &get<ValenciaDivClean::Tags::TildeS<Frame::Inertial>>(*vars_on_face)),
      make_not_null(
          &get<ValenciaDivClean::Tags::TildeB<Frame::Inertial>>(*vars_on_face)),
      make_not_null(&get<ValenciaDivClean::Tags::TildePhi>(*vars_on_face)),
      rest_mass_density, specific_internal_energy, specific_enthalpy, pressure,
      spatial_velocity, lorentz_factor,
      get<hydro::Tags::MagneticField<DataVector, 3, Frame::Inertial>>(
          *vars_on_face),
      get<gr::Tags::SqrtDetSpatialMetric<DataVector>>(*vars_on_face),
      get<gr::Tags::SpatialMetric<3>>(*vars_on_face),
      get<hydro::Tags::DivergenceCleaningField<DataVector>>(*vars_on_face));
}

template <typename PrimsTags, typename TagsList, size_t ThermodynamicDim,
          typename F>
void reconstruct_prims_work(
    const gsl::not_null<std::array<Variables<TagsList>, 3>*> vars_on_lower_face,
    const gsl::not_null<std::array<Variables<TagsList>, 3>*> vars_on_upper_face,
    const F& reconstruct, const Variables<PrimsTags>& volume_prims,
    const EquationsOfState::EquationOfState<true, ThermodynamicDim>& eos,
    const Element<3>& element,
    const FixedHashMap<maximum_number_of_neighbors(3) + 1,
                       std::pair<Direction<3>, ElementId<3>>,
                       evolution::dg::subcell::NeighborData,
                       boost::hash<std::pair<Direction<3>, ElementId<3>>>>
        neighbor_data,
    const Mesh<3>& subcell_mesh, size_t ghost_zone_size) {
  using prim_tags_for_reconstruction =
      tmpl::list<hydro::Tags::RestMassDensity<DataVector>,
                 hydro::Tags::Pressure<DataVector>,
                 hydro::Tags::LorentzFactorTimesSpatialVelocity<DataVector, 3>,
                 hydro::Tags::MagneticField<DataVector, 3>,
                 hydro::Tags::DivergenceCleaningField<DataVector>>;

  ASSERT(Mesh<3>(subcell_mesh.extents(0), subcell_mesh.basis(0),
                 subcell_mesh.quadrature(0)) == subcell_mesh,
         "The subcell mesh should be isotropic but got " << subcell_mesh);
  const size_t volume_num_pts = subcell_mesh.number_of_grid_points();
  const size_t reconstructed_num_pts =
      (subcell_mesh.extents(0) + 1) *
      subcell_mesh.extents().slice_away(0).product();
  const size_t neighbor_num_pts =
      ghost_zone_size * subcell_mesh.extents().slice_away(0).product();
  size_t vars_in_neighbor_count = 0;
  tmpl::for_each<prim_tags_for_reconstruction>([&element, &neighbor_data,
                                                neighbor_num_pts, &reconstruct,
                                                reconstructed_num_pts,
                                                volume_num_pts, &volume_prims,
                                                &vars_in_neighbor_count,
                                                &vars_on_lower_face,
                                                &vars_on_upper_face,
                                                &subcell_mesh](
                                                   auto tag_v) {
    using tag = tmpl::type_from<decltype(tag_v)>;
    const typename tag::type* volume_tensor_ptr = nullptr;
    Variables<tmpl::list<
        hydro::Tags::LorentzFactorTimesSpatialVelocity<DataVector, 3>>>
        lorentz_factor_times_v_I{};
    if constexpr (std::is_same_v<tag,
                                 hydro::Tags::LorentzFactorTimesSpatialVelocity<
                                     DataVector, 3>>) {
      // we need to handle the Wv^i reconstruction separately since we need to
      // first compute Wv^i in the volume (it's not one of our primitives from
      // the recovery). The components need to be stored contiguously, which is
      // why we have the Variables `lorentz_factor_times_v_I`
      const auto& spatial_velocity =
          get<hydro::Tags::SpatialVelocity<DataVector, 3>>(volume_prims);
      const auto& lorentz_factor =
          get<hydro::Tags::LorentzFactor<DataVector>>(volume_prims);
      lorentz_factor_times_v_I.initialize(get(lorentz_factor).size());
      auto& volume_tensor =
          get<hydro::Tags::LorentzFactorTimesSpatialVelocity<DataVector, 3>>(
              lorentz_factor_times_v_I) = spatial_velocity;
      for (size_t i = 0; i < 3; ++i) {
        volume_tensor.get(i) *= get(lorentz_factor);
      }
      volume_tensor_ptr = &volume_tensor;
    } else {
      volume_tensor_ptr = &get<tag>(volume_prims);
    }

    const size_t number_of_variables = volume_tensor_ptr->size();
    const gsl::span<const double> volume_vars = gsl::make_span(
        (*volume_tensor_ptr)[0].data(), number_of_variables * volume_num_pts);
    std::array<gsl::span<double>, 3> upper_face_vars{};
    std::array<gsl::span<double>, 3> lower_face_vars{};
    for (size_t i = 0; i < 3; ++i) {
      gsl::at(upper_face_vars, i) =
          gsl::make_span(get<tag>(gsl::at(*vars_on_upper_face, i))[0].data(),
                         number_of_variables * reconstructed_num_pts);
      gsl::at(lower_face_vars, i) =
          gsl::make_span(get<tag>(gsl::at(*vars_on_lower_face, i))[0].data(),
                         number_of_variables * reconstructed_num_pts);
    }

    DirectionMap<3, gsl::span<const double>> ghost_cell_vars{};
    for (const auto& direction : Direction<3>::all_directions()) {
      const auto& neighbors_in_direction = element.neighbors().at(direction);
      ASSERT(neighbors_in_direction.size() == 1,
             "Currently only support one neighbor in each direction, but "
             "got "
                 << neighbors_in_direction.size() << " in direction "
                 << direction);
      ghost_cell_vars[direction] = gsl::make_span(
          &neighbor_data
               .at(std::pair{direction, *neighbors_in_direction.begin()})
               .data_for_reconstruction[vars_in_neighbor_count *
                                        neighbor_num_pts],
          number_of_variables * neighbor_num_pts);
    }

    reconstruct(make_not_null(&upper_face_vars),
                make_not_null(&lower_face_vars), volume_vars, ghost_cell_vars,
                subcell_mesh.extents(), number_of_variables);

    vars_in_neighbor_count += number_of_variables;
  });

  for (size_t i = 0; i < 3; ++i) {
    compute_conservatives_for_reconstruction(
        make_not_null(&gsl::at(*vars_on_lower_face, i)), eos);
    compute_conservatives_for_reconstruction(
        make_not_null(&gsl::at(*vars_on_upper_face, i)), eos);
  }
}

template <typename TagsList, typename PrimsTags, size_t ThermodynamicDim,
          typename F0, typename F1>
void reconstruct_fd_neighbor_work(
    const gsl::not_null<Variables<TagsList>*> vars_on_face,
    const F0& reconstruct_lower_neighbor, const F1& reconstruct_upper_neighbor,
    const Variables<PrimsTags>& subcell_volume_prims,
    const EquationsOfState::EquationOfState<true, ThermodynamicDim>& eos,
    const Element<3>& element,
    const FixedHashMap<maximum_number_of_neighbors(3) + 1,
                       std::pair<Direction<3>, ElementId<3>>,
                       evolution::dg::subcell::NeighborData,
                       boost::hash<std::pair<Direction<3>, ElementId<3>>>>
        neighbor_data,
    const Mesh<3>& subcell_mesh, const Direction<3>& direction_to_reconstruct,
    const size_t ghost_zone_size) {
  using prim_tags_for_reconstruction =
      tmpl::list<hydro::Tags::RestMassDensity<DataVector>,
                 hydro::Tags::Pressure<DataVector>,
                 hydro::Tags::LorentzFactorTimesSpatialVelocity<DataVector, 3>,
                 hydro::Tags::MagneticField<DataVector, 3>,
                 hydro::Tags::DivergenceCleaningField<DataVector>>;

  const std::pair mortar_id{
      direction_to_reconstruct,
      *element.neighbors().at(direction_to_reconstruct).begin()};
  Index<3> ghost_data_extents = subcell_mesh.extents();
  ghost_data_extents[direction_to_reconstruct.dimension()] = ghost_zone_size;
  Variables<prim_tags_for_reconstruction> neighbor_prims{
      ghost_data_extents.product()};
  {
    ASSERT(neighbor_data.contains(mortar_id),
           "The neighbor data does not contain the mortar: ("
               << mortar_id.first << ',' << mortar_id.second << ")");
    const auto& neighbor_data_on_mortar = neighbor_data.at(mortar_id);
    std::copy(neighbor_data_on_mortar.data_for_reconstruction.begin(),
              std::next(neighbor_data_on_mortar.data_for_reconstruction.begin(),
                        static_cast<std::ptrdiff_t>(
                            neighbor_prims.number_of_independent_components *
                            ghost_data_extents.product())),
              neighbor_prims.data());
  }

  tmpl::for_each<prim_tags_for_reconstruction>(
      [&direction_to_reconstruct, &ghost_data_extents, &neighbor_prims,
       &reconstruct_lower_neighbor, &reconstruct_upper_neighbor, &subcell_mesh,
       &subcell_volume_prims, &vars_on_face](auto tag_v) {
        using tag = tmpl::type_from<decltype(tag_v)>;
        const typename tag::type* volume_tensor_ptr = nullptr;
        typename tag::type volume_tensor{};
        if constexpr (std::is_same_v<
                          tag, hydro::Tags::LorentzFactorTimesSpatialVelocity<
                                   DataVector, 3>>) {
          // we need to handle the Wv^i reconstruction separately since we need
          // to first compute Wv^i in the volume (it's not one of our primitives
          // from the recovery). The components need to be stored contiguously,
          // which is why we have the Variables `lorentz_factor_times_v_I`
          const auto& spatial_velocity =
              get<hydro::Tags::SpatialVelocity<DataVector, 3>>(
                  subcell_volume_prims);
          const auto& lorentz_factor =
              get<hydro::Tags::LorentzFactor<DataVector>>(subcell_volume_prims);
          volume_tensor = spatial_velocity;
          for (size_t i = 0; i < 3; ++i) {
            volume_tensor.get(i) *= get(lorentz_factor);
          }
          volume_tensor_ptr = &volume_tensor;
        } else {
          volume_tensor_ptr = &get<tag>(subcell_volume_prims);
        }

        const auto& tensor_neighbor = get<tag>(neighbor_prims);
        auto& tensor_on_face = get<tag>(*vars_on_face);
        if (direction_to_reconstruct.side() == Side::Upper) {
          for (size_t tensor_index = 0; tensor_index < tensor_on_face.size();
               ++tensor_index) {
            reconstruct_upper_neighbor(
                make_not_null(&tensor_on_face[tensor_index]),
                (*volume_tensor_ptr)[tensor_index],
                tensor_neighbor[tensor_index], subcell_mesh.extents(),
                ghost_data_extents, direction_to_reconstruct);
          }
        } else {
          for (size_t tensor_index = 0; tensor_index < tensor_on_face.size();
               ++tensor_index) {
            reconstruct_lower_neighbor(
                make_not_null(&tensor_on_face[tensor_index]),
                (*volume_tensor_ptr)[tensor_index],
                tensor_neighbor[tensor_index], subcell_mesh.extents(),
                ghost_data_extents, direction_to_reconstruct);
          }
        }
      });

  compute_conservatives_for_reconstruction(vars_on_face, eos);
}
}  // namespace grmhd::ValenciaDivClean::fd


#include <scorum/chain/services/budgets.hpp>
#include <algorithm>

#include <scorum/chain/schema/budget_objects.hpp>

#include <boost/range/adaptor/filtered.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/multi_index/detail/unbounded.hpp>

#include <scorum/chain/services/dynamic_global_property.hpp>
#include <scorum/chain/services/account.hpp>

#include <scorum/utils/math.hpp>

namespace scorum {
namespace chain {
namespace detail {
asset fund_calculate_per_block(time_point_sec start_date, time_point_sec end_date, const asset& balance)
{
    FC_ASSERT(start_date < end_date, "Start time ${1} must be less end time ${2}", ("1", start_date)("2", end_date));

    auto per_block = balance;

    uint32_t delta_in_sec = end_date.sec_since_epoch() - start_date.sec_since_epoch();

    per_block *= SCORUM_BLOCK_INTERVAL;
    per_block /= delta_in_sec;

    // non zero budget must return at least one satoshi
    per_block.amount = std::max(per_block.amount, share_type(1));

    return per_block;
}

asset adv_calculate_per_block(time_point_sec start,
                              time_point_sec deadline,
                              time_point_sec last_block_time,
                              const asset& balance)
{
    FC_ASSERT(start <= deadline, "Start time ${1} must be less or equal end time ${2}", ("1", start)("2", deadline));

    auto aligned_start_sec
        = utils::ceil(start.sec_since_epoch(), last_block_time.sec_since_epoch(), SCORUM_BLOCK_INTERVAL);

    auto aligned_deadline_sec
        = utils::ceil(deadline.sec_since_epoch(), last_block_time.sec_since_epoch(), SCORUM_BLOCK_INTERVAL);

    auto per_blocks_count = 1 + ((aligned_deadline_sec - aligned_start_sec) / SCORUM_BLOCK_INTERVAL);
    auto per_block = balance / per_blocks_count;

    // non zero budget must return at least one satoshi
    per_block.amount = std::max(per_block.amount, share_type(1));

    return per_block;
}
}

dbs_fund_budget::dbs_fund_budget(database& db)
    : dbs_service_base<fund_budget_service_i>(db)
    , _dprops_svc(db.dynamic_global_property_service())
{
}

const fund_budget_object& dbs_fund_budget::create_budget(const asset& balance, time_point_sec start, time_point_sec end)
{
    try
    {
        FC_ASSERT(balance.symbol() == SP_SYMBOL, "Invalid asset type (symbol).");
        FC_ASSERT(start < end, "Invalid dates.");

        auto per_block = detail::fund_calculate_per_block(start, end, balance);

        return create([&](fund_budget_object& budget) {
            budget.created = _dprops_svc.head_block_time();
            budget.start = start;
            budget.deadline = end;
            budget.balance = balance;
            budget.per_block = per_block;
        });
    }
    FC_CAPTURE_AND_RETHROW((balance)(start)(end))
}

asset dbs_fund_budget::allocate_cash(const fund_budget_object& budget)
{
    auto allocated = std::min(budget.balance, budget.per_block);

    update(budget, [&](fund_budget_object& b) { b.balance -= allocated; });
    if (budget.balance.amount <= 0)
        remove(budget);

    return allocated;
}

template <budget_type budget_type_v>
dbs_advertising_budget<budget_type_v>::dbs_advertising_budget(database& db)
    : dbs_service_base<typename budget_service_traits<budget_type_v>::service_type>(db)
    , _dgp_svc(db.dynamic_global_property_service())
    , _account_svc(db.account_service())
{
}

template <budget_type budget_type_v>
const adv_budget_object<budget_type_v>&
dbs_advertising_budget<budget_type_v>::create_budget(const uuid_type& uuid,
                                                     const account_name_type& owner,
                                                     const asset& balance,
                                                     time_point_sec start,
                                                     time_point_sec end,
                                                     const std::string& json_metadata)
{
    try
    {
        FC_ASSERT(balance.symbol() == SCORUM_SYMBOL, "Invalid asset type (symbol).");
        FC_ASSERT(balance.amount > 0, "Invalid balance.");

        auto head_block_time = _dgp_svc.head_block_time();
        auto per_block = detail::adv_calculate_per_block(start, end, head_block_time, balance);

        auto aligned_start_sec
            = utils::ceil(start.sec_since_epoch(), head_block_time.sec_since_epoch(), SCORUM_BLOCK_INTERVAL);
        auto cashout_sec = aligned_start_sec + SCORUM_ADVERTISING_CASHOUT_PERIOD_SEC - SCORUM_BLOCK_INTERVAL;

        const auto& budget = this->create([&](adv_budget_object<budget_type_v>& budget) {
            budget.uuid = uuid;
            budget.owner = owner;
#ifndef IS_LOW_MEM
            fc::from_string(budget.json_metadata, json_metadata);
#endif
            budget.created = head_block_time;
            budget.cashout_time = fc::time_point_sec(cashout_sec);
            budget.start = start;
            budget.deadline = end;
            budget.balance = balance;
            budget.per_block = per_block;
        });

        _account_svc.decrease_balance(_account_svc.get_account(owner), balance);

        update_totals([&](adv_total_stats::budget_type_stat& statistic) { statistic.volume += balance; });

        return budget;
    }
    FC_CAPTURE_AND_RETHROW((owner)(balance)(start)(end)(json_metadata))
}

template <budget_type budget_type_v>
const adv_budget_object<budget_type_v>&
dbs_advertising_budget<budget_type_v>::get(const oid<adv_budget_object<budget_type_v>>& id) const
{
    try
    {
        return this->get_by(id);
    }
    FC_CAPTURE_AND_RETHROW((id))
}

template <budget_type budget_type_v>
const adv_budget_object<budget_type_v>& dbs_advertising_budget<budget_type_v>::get(const uuid_type& uuid) const
{
    try
    {
        return this->template get_by<by_uuid>(uuid);
    }
    FC_CAPTURE_AND_RETHROW((uuid))
}

template <budget_type budget_type_v> bool dbs_advertising_budget<budget_type_v>::is_exists(const uuid_type& id) const
{
    try
    {
        return nullptr != this->template find_by<by_uuid>(id);
    }
    FC_CAPTURE_AND_RETHROW((id))
}

template <budget_type budget_type_v>
const adv_budget_object<budget_type_v>*
dbs_advertising_budget<budget_type_v>::find(const oid<adv_budget_object<budget_type_v>>& id) const
{
    try
    {
        return this->find_by(id);
    }
    FC_CAPTURE_AND_RETHROW((id))
}

template <budget_type budget_type_v>
typename dbs_advertising_budget<budget_type_v>::budgets_type dbs_advertising_budget<budget_type_v>::get_budgets() const
{
    try
    {
        return this->template get_range_by<by_id>(::boost::multi_index::unbounded, ::boost::multi_index::unbounded);
    }
    FC_CAPTURE_AND_RETHROW(())
}

template <budget_type budget_type_v>
typename dbs_advertising_budget<budget_type_v>::budgets_type
dbs_advertising_budget<budget_type_v>::get_top_budgets(const fc::time_point_sec& until, uint16_t limit) const
{
    namespace ba = boost::adaptors;
    try
    {
        budgets_type result;
        result.reserve(limit);

        // TODO: will be refactored using db_accessors
        auto& idx = this->db_impl().template get_index<adv_budget_index<budget_type_v>, by_per_block>();
        auto from = idx.begin();
        auto to = idx.lower_bound(false); // including

        auto rng = boost::make_iterator_range(from, to)
            | ba::filtered([&](const adv_budget_object<budget_type_v>& obj) { return obj.start <= until; });

        // TODO: will be refactored using take_n range adaptor
        for (auto it = rng.begin(); limit && it != rng.end(); ++it, --limit)
        {
            result.push_back(std::cref(*it));
        }
        return result;
    }
    FC_CAPTURE_AND_RETHROW((until)(limit))
}

template <budget_type budget_type_v>
typename dbs_advertising_budget<budget_type_v>::budgets_type
dbs_advertising_budget<budget_type_v>::get_top_budgets(const fc::time_point_sec& until) const
{
    try
    {
        return this->get_top_budgets(until, -1);
    }
    FC_CAPTURE_AND_RETHROW((until))
}

template <budget_type budget_type_v>
std::set<std::string>
dbs_advertising_budget<budget_type_v>::lookup_budget_owners(const std::string& lower_bound_owner_name,
                                                            uint32_t limit) const
{
    try
    {
        std::set<std::string> result;

        const auto& budgets_by_owner_name
            = this->db_impl().template get_index<adv_budget_index<budget_type_v>, by_owner_name>();

        // prepare output if limit > 0
        for (auto itr = budgets_by_owner_name.lower_bound(lower_bound_owner_name);
             limit && itr != budgets_by_owner_name.end(); ++itr)
        {
            --limit;
            result.insert(itr->owner);
        }
        return result;
    }
    FC_CAPTURE_AND_RETHROW((lower_bound_owner_name)(limit))
}

template <budget_type budget_type_v>
typename dbs_advertising_budget<budget_type_v>::budgets_type
dbs_advertising_budget<budget_type_v>::get_budgets(const account_name_type& owner) const
{
    try
    {
        return this->template get_range_by<by_owner_name>(owner <= ::boost::lambda::_1, ::boost::lambda::_1 <= owner);
    }
    FC_CAPTURE_AND_RETHROW((owner))
}

template <budget_type budget_type_v>
typename dbs_advertising_budget<budget_type_v>::budgets_type
dbs_advertising_budget<budget_type_v>::get_pending_budgets() const
{
    try
    {
        auto head_time = _dgp_svc.head_block_time();
        return this->template get_range_by<by_cashout_time>(boost::multi_index::unbounded,
                                                            ::boost::lambda::_1 <= head_time);
    }
    FC_CAPTURE_AND_RETHROW()
}

template <budget_type budget_type_v>
asset dbs_advertising_budget<budget_type_v>::allocate_cash(const adv_budget_object<budget_type_v>& budget)
{
    this->update(budget, [&](adv_budget_object<budget_type_v>& b) { b.balance -= budget.per_block; });

    update_totals([&](adv_total_stats::budget_type_stat& statistic) { statistic.volume -= budget.per_block; });

    if (budget.deadline <= _dgp_svc.head_block_time() || budget.balance.amount == 0)
        finish_budget(budget.uuid);

    return budget.per_block;
}

template <budget_type budget_type_v>
void dbs_advertising_budget<budget_type_v>::update_pending_payouts(const adv_budget_object<budget_type_v>& budget,
                                                                   const asset& owner_incoming,
                                                                   const asset& budget_outgoing)
{
    this->update(budget, [&](adv_budget_object<budget_type_v>& b) {
        b.owner_pending_income += owner_incoming;
        b.budget_pending_outgo += budget_outgoing;
    });

    update_totals([&](adv_total_stats::budget_type_stat& statistic) {
        statistic.owner_pending_income += owner_incoming;
        statistic.budget_pending_outgo += budget_outgoing;
    });
}

template <budget_type budget_type_v>
asset dbs_advertising_budget<budget_type_v>::perform_pending_payouts(const budgets_type& budgets)
{
    auto budgets_outgo = asset(0, SCORUM_SYMBOL);
    for (const adv_budget_object<budget_type_v>& budget : budgets)
    {
        budgets_outgo += budget.budget_pending_outgo;
        _account_svc.increase_balance(_account_svc.get_account(budget.owner), budget.owner_pending_income);

        update_totals([&](adv_total_stats::budget_type_stat& statistic) {
            statistic.owner_pending_income -= budget.owner_pending_income;
            statistic.budget_pending_outgo -= budget.budget_pending_outgo;
        });

        this->update(budget, [&](adv_budget_object<budget_type_v>& b) {
            b.owner_pending_income.amount = 0;
            b.budget_pending_outgo.amount = 0;
            b.cashout_time = _dgp_svc.head_block_time() + SCORUM_ADVERTISING_CASHOUT_PERIOD_SEC;
        });
    }

    return budgets_outgo;
}

template <budget_type budget_type_v> void dbs_advertising_budget<budget_type_v>::finish_budget(const uuid_type& uuid)
{
    const auto& budget = get(uuid);

    update_totals([&](adv_total_stats::budget_type_stat& statistic) {
        statistic.owner_pending_income += budget.balance;
        statistic.volume -= budget.balance;
    });

    this->update(budget, [&](adv_budget_object<budget_type_v>& b) {
        b.cashout_time = _dgp_svc.head_block_time();
        b.owner_pending_income += b.balance;
        b.balance.amount = 0;
    });
}

template <budget_type budget_type_v>
typename dbs_advertising_budget<budget_type_v>::budgets_type
dbs_advertising_budget<budget_type_v>::get_empty_budgets() const
{
    auto zero = asset(0, SCORUM_SYMBOL);
    auto empty_budgets = this->template get_range_by<by_balances>(
        boost::multi_index::unbounded, ::boost::lambda::_1 <= std::make_tuple(zero, zero, zero));

    return empty_budgets;
}

template <budget_type budget_type_v>
void dbs_advertising_budget<budget_type_v>::update_totals(
    std::function<void(adv_total_stats::budget_type_stat&)> callback)
{
    if (budget_type_v == budget_type::banner)
    {
        _dgp_svc.update([&](dynamic_global_property_object& dgp) { callback(dgp.advertising.banner_budgets); });
    }
    else if (budget_type_v == budget_type::post)
    {
        _dgp_svc.update([&](dynamic_global_property_object& dgp) { callback(dgp.advertising.post_budgets); });
    }
    else
    {
        FC_THROW("unsuported budget type");
    }
}

template class dbs_advertising_budget<budget_type::post>;
template class dbs_advertising_budget<budget_type::banner>;
}
}


/* pcmsolver_copyright_start */
/*
 *     PCMSolver, an API for the Polarizable Continuum Model
 *     Copyright (C) 2013-2016 Roberto Di Remigio, Luca Frediani and contributors
 *
 *     This file is part of PCMSolver.
 *
 *     PCMSolver is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     PCMSolver is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with PCMSolver.  If not, see <http://www.gnu.org/licenses/>.
 *
 *     For information on the complete list of contributors to the
 *     PCMSolver API, see: <http://pcmsolver.readthedocs.io/>
 */
/* pcmsolver_copyright_end */

#ifndef IEFSOLVER_HPP
#define IEFSOLVER_HPP

#include <iosfwd>
#include <string>
#include <vector>

#include "Config.hpp"

#include <Eigen/Core>

class Cavity;
class IGreensFunction;

#include "PCMSolver.hpp"

/*! \file IEFSolver.hpp
 *  \class IEFSolver
 *  \brief IEFPCM, collocation-based solver
 *  \author Luca Frediani, Roberto Di Remigio
 *  \date 2011, 2015, 2016
 *
 *  \note We store the non-Hermitian, symmetry-blocked T(epsilon) and Rinfinity matrices.
 *  The ASC is obtained by multiplying the MEP by Rinfinity and then using a partially
 *  pivoted LU decomposition of T(epsilon) on the resulting vector.
 *  In case the polarization weights are requested, we use the approach suggested in
 *  \cite Barone2004-ae.
 *  First, the adjoint problem is solved:
 *  \f[
 *      \mathbf{T}_\varepsilon^\dagger \tilde{v} = v
 *  \f]
 *  Also in this case a partially pivoted LU decomposition is used.
 *  The "transposed" ASC is obtained by the matrix-vector multiplication:
 *  \f[
 *      q^* = \mathbf{R}_\infty^\dagger \tilde{v}
 *  \f]
 *  Eventually, the two sets of charges are summed and divided by 2
 *  This avoids computing and storing the inverse explicitly, at the expense of storing
 *  both T(epsilon) and Rinfinity.
 */

class IEFSolver : public PCMSolver
{
public:
    IEFSolver() {}
    /*! \brief Construct solver
     *  \param[in] symm whether the system matrix has to be symmetrized
     */
    IEFSolver(bool symm) : PCMSolver(), hermitivitize_(symm) {}
    virtual ~IEFSolver() {}
    /*! \brief Builds PCM matrix for an anisotropic environment
     *  \param[in] cavity the cavity to be used.
     *  \param[in] gf_i Green's function inside the cavity
     *  \param[in] gf_o Green's function outside the cavity
     */
    void buildAnisotropicMatrix(const Cavity & cavity, const IGreensFunction & gf_i, const IGreensFunction & gf_o);
    /*! \brief Builds PCM matrix for an isotropic environment
     *  \param[in] cavity the cavity to be used.
     *  \param[in] gf_i Green's function inside the cavity
     *  \param[in] gf_o Green's function outside the cavity
     */
    void buildIsotropicMatrix(const Cavity & cavity, const IGreensFunction & gf_i, const IGreensFunction & gf_o);
    friend std::ostream & operator<<(std::ostream & os, IEFSolver & solver) {
        return solver.printSolver(os);
    }
private:
    /*! Whether the system matrix has to be symmetrized */
    bool hermitivitize_;
    /*! T(epsilon) matrix, not symmetry blocked */
    Eigen::MatrixXd Tepsilon_;
    /*! T(epsilon) matrix, symmetry blocked form */
    std::vector<Eigen::MatrixXd> blockTepsilon_;
    /*! R_infinity matrix, not symmetry blocked */
    Eigen::MatrixXd Rinfinity_;
    /*! R_infinity matrix, symmetry blocked form */
    std::vector<Eigen::MatrixXd> blockRinfinity_;

    /*! \brief Calculation of the PCM matrix
     *  \param[in] cavity the cavity to be used
     *  \param[in] gf_i Green's function inside the cavity
     *  \param[in] gf_o Green's function outside the cavity
     */
    virtual void buildSystemMatrix_impl(const Cavity & cavity, const IGreensFunction & gf_i, const IGreensFunction & gf_o) __override;
    /*! \brief Returns the ASC given the MEP and the desired irreducible representation
     *  \param[in] potential the vector containing the MEP at cavity points
     *  \param[in] irrep the irreducible representation of the MEP and ASC
     */
    virtual Eigen::VectorXd computeCharge_impl(const Eigen::VectorXd & potential,
            int irrep = 0) const __override;
    virtual std::ostream & printSolver(std::ostream & os) __override;
};

#endif // IEFSOLVER_HPP


#include <iostream>
#include <chrono>
#include <random>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <boost/program_options.hpp>
#include "deconvolve.hpp"
#include "regularizer.hpp"
#include "convolve.hpp"
#include "psnr.hpp"

cv::Mat matFromArray(const deconvolution::Array<2>& a) {
    int width = a.shape()[0];
    int height = a.shape()[1];
    cv::Mat m{height, width, CV_8UC1};
    for (int i = 0; i < width; ++i) {
        for (int j = 0; j < height; ++j) {
            m.at<unsigned char>(j,i) = a[i][j];
        }
    }
    return m;
}

deconvolution::Array<2> arrayFromMat(const cv::Mat& m) {
    int width = m.cols;
    int height = m.rows;
    deconvolution::Array<2> a{boost::extents[width][height]};
    for (int i = 0; i < width; ++i) {
        for (int j = 0; j < height; ++j) {
            a[i][j] = m.at<unsigned char>(j,i);
        }
    }
    return a;
}

void showImage(const cv::Mat& m) {
    cv::namedWindow("Display Window", CV_WINDOW_AUTOSIZE);
    cv::imshow("Display Window", m);
    cv::waitKey(1);
}

void showImage(const deconvolution::Array<2>& a) {
    showImage(matFromArray(a));
}

int main(int argc, char **argv) {
    namespace po = boost::program_options;
    // Variables set by program options
    std::string basename;
    std::string extension;
    std::string infilename;
    std::string outfilename;
    std::string blurfilename;
    int kerSize = 31;
    double sigma = 0.0;
    double noiseSigma = 0.0;
    double regularizerWidth = 0.0;
    double regularizerWeight = 0.0;
    std::string method;

    po::options_description options_desc("Deconvolve arguments");
    options_desc.add_options()
        ("help", "Display this help message")
        ("image", po::value<std::string>(&basename)->required(), "Name of image (without extension)")
        ("extension,e", po::value<std::string>(&extension)->default_value("pgm"), "Extension of filename")
        ("rweight", po::value<double>(&regularizerWeight)->default_value(5.0), "Regularizer weight")
        ("rwidth", po::value<double>(&regularizerWidth)->default_value(9.0), "Regularizer width")
        ("ksigma,s", po::value<double>(&sigma)->default_value(5.0), "Kernel sigma")
        ("nsigma", po::value<double>(&noiseSigma)->default_value(5.0), "Noise sigma")
        ("method,m", po::value<std::string>(&method)->default_value("primal"), "Optimization method, one of [primal|dual]")
    ;

    po::positional_options_description popts_desc;
    popts_desc.add("image", 1);

    po::variables_map vm;
    po::store(po::command_line_parser(argc, argv).
            options(options_desc).positional(popts_desc).run(), vm);

    if (vm.count("help") != 0) {
        std::cout << "Usage: image-deconvolve [options] basename\n";
        std::cout << options_desc;
        exit(0);
    }
    try {
        po::notify(vm);
    } catch (std::exception& e) {
        std::cout << "Parsing error: " << e.what() << "\n";
        std::cout << "Usage: image-deconvolve [options] basename\n";
        std::cout << options_desc;
        exit(-1);
    }
    infilename = basename + "." + extension;
    outfilename = basename + "-out.pgm";
    blurfilename = basename + "-blur.pgm";

    cv::Mat image = cv::imread(infilename.c_str(), CV_LOAD_IMAGE_GRAYSCALE);
    if (!image.data) {
        std::cout << "Could not load image: " << infilename << "\n";
        exit(-1);
    }

    cv::Mat orig = image.clone();

    auto width = image.cols;
    auto height = image.rows;
    assert(image.type() == CV_8UC1);

    // Set up gaussian blur kernel
    deconvolution::Array<2> ker{boost::extents[kerSize][kerSize]};
    std::vector<int> kerBase{-10, -10};
    ker.reindex(kerBase);
    for (int i = ker.index_bases()[0];
            i != ker.index_bases()[0] + int(ker.shape()[0]);
            ++i) {
        for (int j = ker.index_bases()[1];
                j != ker.index_bases()[1] + int(ker.shape()[1]);
                ++j) {
            ker[i][j] = exp(-(i*i + j*j)/(2*sigma*sigma))/(2*3.14159*sigma*sigma);
        }
    }

    auto y = arrayFromMat(image);
    auto blur = convolveFFT(y, ker);

    // Adding iid noise at each pixel
    std::mt19937 randGen;
    std::normal_distribution<double> norm{0, noiseSigma};
    for (int i = 0; i < width; ++i)
        for (int j = 0; j < height; ++j)
            blur[i][j] += norm(randGen);

    auto blurImage = matFromArray(blur);
    showImage(blurImage);
    cv::imwrite(blurfilename.c_str(), blurImage);

    deconvolution::LinearSystem<2> H =
        [&](const deconvolution::Array<2>& x) -> deconvolution::Array<2> {
            return convolveFFT(x, ker);
        };

    constexpr int nLabels = 16;
    constexpr double labelScale = 255.0/(nLabels-1);
    auto ep = deconvolution::SmoothEdge{regularizerWeight, regularizerWidth};
    auto R = deconvolution::GridRangeRegularizer<2, deconvolution::SmoothEdge>{
        std::vector<int>{width, height},
        nLabels, labelScale, ep, 255.0
    };

    cv::namedWindow("Display Window", CV_WINDOW_AUTOSIZE);
    deconvolution::ProgressCallback<2> progressCallback =
        [&](const deconvolution::Array<2>& x, double dual, double primalData, double primalReg, double smoothing) {
            auto xMat = matFromArray(x);
            auto p = psnrMse(xMat, orig);
            std::cout << "\tPSNR: " << p.first << "\n";
            showImage(xMat);
        };

    deconvolution::DeconvolveParams params {};
    deconvolution::DeconvolveStats s{};

    auto startTime = std::chrono::system_clock::now();
    std::cout << "Deconvolving\n";
    deconvolution::Array<2> deblur{boost::extents[width][height]};
    if (method == std::string("primal")) {
        const int numAnnealIters = 10;
        deblur = y;
        for (int iter = 0; iter <= numAnnealIters; ++iter) {
            double alpha = static_cast<double>(iter)/static_cast<double>(numAnnealIters);
            typedef deconvolution::ConvexCombEdge<deconvolution::SmoothEdge, deconvolution::L2Edge> AnnealEdge;
            auto epSmooth = AnnealEdge{ep, deconvolution::L2Edge{1.0/regularizerWidth}, alpha};
            auto Rsmooth = deconvolution::GridRangeRegularizer<2, AnnealEdge>{
                std::vector<int>{width, height},
                    nLabels, labelScale, epSmooth, 255.0 };

            deblur = deconvolution::DeconvolvePrimal<2>(y, H, H, Rsmooth, progressCallback, params, s, deblur);
            showImage(deblur);
        }
    } else if (method == std::string("dual"))
        deblur = deconvolution::Deconvolve<2>(y, H, H, R, progressCallback, params, s);
    else {
        std::cout << "Unknown optimization method!\n";
        exit(-1);
    }
    std::cout << "Done\n";

    std::cout << "Total time:       " << std::chrono::duration<double>{std::chrono::system_clock::now() - startTime}.count() << "\n";
    std::cout << "Evaluation time:  " << s.iterTime << "\n";
    std::cout << "Regularizer time: " << s.regularizerTime << "\n";
    std::cout << "Data time:        " << s.dataTime << "\n";
    std::cout << "Unary time:       " << s.unaryTime << "\n";

    auto deblurImage = matFromArray(deblur);
    cv::imwrite(outfilename.c_str(), deblurImage);
    auto p = psnrMse(deblurImage, orig);
    std::cout << "PSNR: " << p.first << "\n";
    showImage(deblurImage);

    return 0;
}


//==================================================================================================
/**
  EVE - Expressive Vector Engine
  Copyright : EVE Contributors & Maintainers
  SPDX-License-Identifier: MIT
**/
//==================================================================================================
#include <boost/math/special_functions/legendre.hpp>
#include <eve/function/legendre.hpp>
#include <eve/constant/inf.hpp>
#include <eve/constant/minf.hpp>
#include <eve/constant/nan.hpp>
#include <eve/platform.hpp>
#include <cmath>

TTS_CASE_TPL("Check eve::legendre return type", EVE_TYPE)
{
  TTS_EXPR_IS(eve::legendre(0, T(0)), T);
}

TTS_CASE_TPL("Check eve::legendre behavior", EVE_TYPE)
{

  auto eve__legendre =  [](auto n, auto x) { return eve::legendre(n, x); };
  auto boost_legendre =  [](auto n, auto x) { return boost::math::legendre_p(n, x); };
  using i_t = eve::as_integer_t<T,unsigned>;
  for(unsigned int i=0; i < 5; ++i)
  {
    for(double v = -1.0; v <= 1.0f; v+= 0.1)
    {
      TTS_ULP_EQUAL(eve__legendre(i, T(v)), T(boost_legendre(i, v)), 15);
      TTS_ULP_EQUAL(eve__legendre(i_t(i), T(v)), T(boost_legendre(i, v)), 15);
    }
  }
}


//------------------------------------------------------------------------------
/// \ref https://leetcode.com/problems/spiral-matrix/
//------------------------------------------------------------------------------
#include "Algorithms/LeetCode/PrintMatrixSpiral.h"

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

using Algorithms::PrintMatrixSpiral;
using std::vector;

BOOST_AUTO_TEST_SUITE(Algorithms)
BOOST_AUTO_TEST_SUITE(PrintMatrixSpiral_tests)

const vector<vector<int>> single_row {{42, 43, 44}};
const vector<vector<int>> single_column {{42}, {43}, {44}};
const vector<vector<int>> smallest_square {{42, 43}, {45, 44}};
const vector<vector<int>> smallest_two_row_rectangle {
  {42, 43, 44},
  {47, 46, 45}};
const vector<vector<int>> smallest_two_column_rectangle {
  {42, 43},
  {47, 44},
  {46, 45}};

const vector<vector<int>> a {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

const vector<vector<int>> example_2 {
  {1, 2, 3, 4},
  {5, 6, 7, 8},
  {9, 10, 11, 12}};

// cf. https://leetcode.com/submissions/detail/507365756/
const vector<vector<int>> test_case_15 {
  {1, 2, 3, 4},
  {5, 6, 7, 8},
  {9, 10, 11, 12},
  {13, 14, 15, 16}};

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(CheckSingleRowOnGetLinearSpiralOrder)
{
  PrintMatrixSpiral<int> printer;

  const vector<int> result {printer.get_linear_spiral_order(single_row)};
  BOOST_TEST(result.size() == 3);

  for (int i {0}; i < result.size(); ++i)
  {
    BOOST_TEST(result[i] == i + 42);
  }
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(CheckSingleColumnOnGetLinearSpiralOrder)
{
  PrintMatrixSpiral<int> printer;

  const vector<int> result {printer.get_linear_spiral_order(single_column)};
  BOOST_TEST(result.size() == 3);

  for (int i {0}; i < result.size(); ++i)
  {
    BOOST_TEST(result[i] == i + 42);
  }
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(LinearSpiralOrderOn2x3)
{
  PrintMatrixSpiral<int> printer;

  const vector<int> result {printer.get_linear_spiral_order(
    smallest_two_row_rectangle)};
  BOOST_TEST(result.size() == 6);

  for (int i {0}; i < result.size(); ++i)
  {
    BOOST_TEST(result[i] == i + 42);
  }
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(LinearSpiralOrderOn3x2)
{
  PrintMatrixSpiral<int> printer;

  const vector<int> result {printer.get_linear_spiral_order(
    smallest_two_column_rectangle)};
  BOOST_TEST(result.size() == 6);

  for (int i {0}; i < result.size(); ++i)
  {
    BOOST_TEST(result[i] == i + 42);
  }
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(GetLinearSpiralOrder)
{
  PrintMatrixSpiral<int> printer;

  const vector<int> result {printer.get_linear_spiral_order(a)};

  BOOST_TEST(result.size() == 9);

  for (int i {0}; i < a.size(); ++i)
  {
    BOOST_TEST(result[i] == i + 1);
  }
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(GetLinearSpiralOrderOnExample2)
{
  PrintMatrixSpiral<int> printer;

  const vector<int> result {printer.get_linear_spiral_order(example_2)};

  BOOST_TEST(result.size() == 12);

  BOOST_TEST(result[0] == 1);
  BOOST_TEST(result[1] == 2);
  BOOST_TEST(result[2] == 3);
  BOOST_TEST(result[3] == 4);
  BOOST_TEST(result[4] == 8);
  BOOST_TEST(result[5] == 12);
  BOOST_TEST(result[6] == 11);
  BOOST_TEST(result[7] == 10);
  BOOST_TEST(result[8] == 9);
  BOOST_TEST(result[9] == 5); //
  BOOST_TEST(result[10] == 6);
  BOOST_TEST(result[11] == 7);
}

BOOST_AUTO_TEST_SUITE_END() // PrintMatrixSpiral_tests
BOOST_AUTO_TEST_SUITE_END() // Algorithms


#include <frovedis.hpp>
#include <frovedis/ml/clustering/spectral_clustering.hpp>
#include <boost/program_options.hpp>

using namespace boost;
using namespace frovedis;

int main(int argc, char** argv) {
  use_frovedis use(argc,argv);

  using namespace boost::program_options;

  options_description opt("option");
  opt.add_options()
      ("help,h", "produce help message")
      ("input,i" , value<std::string>(), "input rowmajor matrix data.")
      ("n-clusters" , value<int>(), "the dimension of the projection subspace.[default = 2]")
      ("n-components" , value<int>(), "number of eigenvectors to use for the spectral embedding.[default = 2]")
      ("n-iter,n" , value<int>(), "number of times the algorithm will be run.[default = 100]")
      ("n-init" , value<int>(), "[default = 1]")
      ("seed" , value<int>(), "[default = 0]")
      ("gamma,g", value<double>(), "kernel coefficient for rbf, poly, sigmoid, laplacian and chi2 kernels.[default = 1.0]")
      ("eps,e", value<double>(), "[default = 0.01]")
      ("norm-laplacian", value<bool>(), "[default: true]")
      ("n-neighbors", value<int>(), "[default: 10]")
      ("drop-first", value<bool>(), "[default: false]")
      ("mode", value<int>(), "[default: 1]")
      ("verbose", "set loglevel to DEBUG")
      ("verbose2", "set loglevel to TRACE");

  variables_map argmap;
  store(command_line_parser(argc,argv).options(opt).allow_unregistered().
        run(), argmap);
  notify(argmap);

  int k = 2;
  int n_comp = 2;
  int niter = 300;
  int n_init = 1;
  int seed = 0;
  double gamma = 1.0;
  double eps = 0.1;
  bool norm_laplacian = true;
  bool drop_first = false;
  int mode = 1;
  int n_neighbors = 10;
  std::string data_p;
  std::string affinity = "rbf";

  if(argmap.count("help")){
    std::cerr << opt << std::endl;
    exit(1);
  }
  if(argmap.count("n-clusters")){
    k = argmap["n-clusters"].as<int>();
  }
  if(argmap.count("n-components")){
    n_comp = argmap["n-components"].as<int>();
  }
  if(argmap.count("n-iter")){
    niter = argmap["n-iter"].as<int>();
  }
  if(argmap.count("n-init")){
    n_init = argmap["n-init"].as<int>();
  }
  if(argmap.count("eps")){
    eps = argmap["eps"].as<double>();
  }
  if(argmap.count("seed")){
    seed = argmap["seed"].as<int>();
  }
  if(argmap.count("gamma")){
    gamma = argmap["gamma"].as<double>();
  }
  if(argmap.count("affinity")){
    affinity = argmap["affinity"].as<std::string>();
  }
  if(argmap.count("n_neighbors")){
    n_neighbors = argmap["n_neighbors"].as<int>();
  }
  if(argmap.count("norm-laplacian")){
    norm_laplacian = argmap["norm-laplacian"].as<bool>();
  }
  if(argmap.count("drop-first")){
    drop_first = argmap["drop-first"].as<bool>();
  }
  if(argmap.count("mode")){
    mode = argmap["mode"].as<int>();
  }
  if(argmap.count("input")){
    data_p = argmap["input"].as<std::string>();
  }else {
    std::cerr << "input is not specified" << std::endl;
    std::cerr << opt << std::endl;
    exit(1);
  }

  auto mat = make_rowmajor_matrix_load<double>(data_p); //./test_data

  auto sc = spectral_clustering<double>(k, n_comp, gamma, affinity,
                                        n_neighbors, niter, n_init, seed,
                                        eps, norm_laplacian, drop_first, mode);
  time_spent train(INFO);
  train.lap_start();
  sc.fit(std::move(mat));
  train.lap_stop();
  train.show_lap("training time: ");

  auto label = sc.labels_();
  std::cout << "\ncluster output: \n";
  for(auto e: label) std::cout << e << " "; std::cout << std::endl;
  return 0;
}


#pragma once

#include <boost/python.hpp>

#include "corridor/unscented_transformation/polar_coordinate_transformation.h"
#include "corridor/unscented_transformation/sigma_points.h"
#include "corridor/unscented_transformation/state_transformation.h"
#include "corridor/unscented_transformation/unscented_transformation.h"

// Phython API
#include "corridor_wrapper.hpp"
#include "utility.hpp"

namespace py = boost::python;

// /////////////////////////////////////////////////////////////////////////////
// Polar Coordinate transformation
// /////////////////////////////////////////////////////////////////////////////

corridor::StateMeanAndCovarianceMatrix UnscentedTransformationPolarCoordinates(
    const corridor::StateMeanAndCovarianceMatrix& initial_state) {
  namespace ut = corridor::unscented_transformation;
  corridor::PolarVector2D polar_mean;
  corridor::PolarCovarianceMatrix2D polar_cov_mat;

  ut::ToPolarCoordinates2D(initial_state.mean, initial_state.covMat,
                           &polar_mean, &polar_cov_mat);

  return corridor::StateMeanAndCovarianceMatrix(polar_mean, polar_cov_mat);
};

py::list pyCartesianToPolarTransformation2D(const corridor::RealType x1,
                                            const corridor::RealType x2) {
  namespace ut = corridor::unscented_transformation;
  Eigen::Vector2d polar_vector = ut::CartesianToPolarTransformation2D({x1, x2});
  py::list polar_coordinates;
  polar_coordinates.append(polar_vector(0));
  polar_coordinates.append(polar_vector(1));
  return polar_coordinates;
}

py::list pyPolarToCartesianTransformation2D(const corridor::RealType radius,
                                            const corridor::RealType phi) {
  namespace ut = corridor::unscented_transformation;
  Eigen::Vector2d cartesian_vector =
      ut::PolarToCartesianTransformation2D({radius, phi});
  py::list cartesian_coordinates;
  cartesian_coordinates.append(cartesian_vector(0));
  cartesian_coordinates.append(cartesian_vector(1));
  return cartesian_coordinates;
}

FlatPolarPositionAndCovMat2D UnscentedTransformationPolarCoordinate2D(
    const FlatCartesianPositionAndCovMat2D& cartesian_state) {
  using namespace corridor;
  StateMeanAndCovarianceMatrix polar_state =
      UnscentedTransformationPolarCoordinates(Convert(cartesian_state));
  return Convert(polar_state);
}

// /////////////////////////////////////////////////////////////////////////////
// State transformation
// /////////////////////////////////////////////////////////////////////////////

FlatFrenetStateAndCovMat2D UnscentedStateTransformation(
    const CorridorWrapper& corridor_wrapper,
    const FlatCartesianStateAndCovMat2D flat_cartesian_state,
    const bool moving_frenet_frame) {
  using namespace corridor;
  namespace ut = unscented_transformation;
  FrenetState2D frenet_state =
      ut::ToFrenetState(corridor_wrapper.corridor_,
                        Convert(flat_cartesian_state), moving_frenet_frame);
  return Convert(frenet_state);
}


#include "refill/distributions/likelihood.h"

#include <gtest/gtest.h>

#include <Eigen/Dense>

namespace refill {

class LikelihoodClass : public Likelihood {
  double getLikelihood(const Eigen::VectorXd& state,
                       const Eigen::VectorXd& measurement) const override {
    return 0;
  }
};

TEST(LikelihoodTest, GetLikelihoodVectorizedTest) {
  LikelihoodClass likelihood;

  Eigen::Matrix2d sampled_state = Eigen::Matrix2d::Zero();
  Eigen::Vector2d measurement = Eigen::Vector2d::Zero();

  Eigen::VectorXd likelihoods =
      likelihood.getLikelihoodVectorized(sampled_state, measurement);

  EXPECT_EQ(Eigen::Vector2d::Zero(), likelihoods);
}

}  // namespace refill


// generate by pypp <https://github.com/mugwort-rc/pypp>
// original source code: SPK_Vector3D.h

#include <Core/SPK_Vector3D.h>
#include <boost/python.hpp>

BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(Vector3DsetOverloads0, SPK::Vector3D::set, 2, 3)

void init_SPK_Vector3D_h() {
    boost::python::class_<SPK::Vector3D, std::shared_ptr<SPK::Vector3D>>("Vector3D", boost::python::init<boost::python::optional<float, float, float>>(
			":brief: Constructor for the Vector3D\n"
			":param x: x coordinate\n"
			":param y: y coordinate\n"
			":param z: z coordinate\n"))
        .def("__iadd__", static_cast<SPK::Vector3D &(SPK::Vector3D::*)(const SPK::Vector3D &)>(&SPK::Vector3D::operator+=), boost::python::return_value_policy<boost::python::copy_non_const_reference>())
        .def("__iadd__", static_cast<SPK::Vector3D &(SPK::Vector3D::*)(float)>(&SPK::Vector3D::operator+=), boost::python::return_value_policy<boost::python::copy_non_const_reference>())
        .def("__isub__", static_cast<SPK::Vector3D &(SPK::Vector3D::*)(const SPK::Vector3D &)>(&SPK::Vector3D::operator-=), boost::python::return_value_policy<boost::python::copy_non_const_reference>())
        .def("__isub__", static_cast<SPK::Vector3D &(SPK::Vector3D::*)(float)>(&SPK::Vector3D::operator-=), boost::python::return_value_policy<boost::python::copy_non_const_reference>())
        .def("__imul__", &SPK::Vector3D::operator*=, boost::python::return_value_policy<boost::python::copy_non_const_reference>())
        .def("__ifloordiv__", &SPK::Vector3D::operator/=, boost::python::return_value_policy<boost::python::copy_non_const_reference>())
        .def("__neg__", &SPK::Vector3D::operator-)

		//Indexcing is not supported
        //.def("operator[]", static_cast<float &(SPK::Vector3D::*)(int)>(&SPK::Vector3D::operator[]), boost::python::return_value_policy<boost::python::copy_non_const_reference>())
        //.def("operator[]", static_cast<const float &(SPK::Vector3D::*)(int) const>(&SPK::Vector3D::operator[]), boost::python::return_value_policy<boost::python::copy_const_reference>())

        .def("set", &SPK::Vector3D::set, Vector3DsetOverloads0(
			":brief: Sets the values of the Vector3D\n"
			":param x: x coordinate\n"
			":param y: y coordinate\n"
			":param z: z coordinate\n"))
        .def("getSqrNorm", &SPK::Vector3D::getSqrNorm,
			":brief: Gets the square norm of the Vector3D\n"
			"\n"
			"the square norm is defined as x * x + y * y + z * z.\n"
			"This method is faster than getNorm() and should be used when possible.\n"
			"\n"
			":return: the square norm of the Vector3D\n")
        .def("getNorm", &SPK::Vector3D::getNorm,
			":brief: Gets the norm of the Vector3D\n"
			"\n"
			"the norm is defined as math.sqrt(x * x + y * y + z * z).\n"
			"\n"
			":return: the norm of the Vector3D\n")
        .def("normalize", &SPK::Vector3D::normalize,
			":brief: Normalizes the Vector3D\n"
			"\n"
			"This method performs these operations :\n"
			"x /= |v|\n"
			"y /= |v|\n"
			"z /= |v|\n"
			"Note that if the norm is equal to 0, nothing happens and False is returned.\n"
			"\n"
			":return: True if this Vector3D can be normalized, False otherwise\n")
        .def("revert", &SPK::Vector3D::revert,
			":brief: Reverts the Vector3D\n"
			"\n"
			"This method performs these operations :\n"
			"x = -x\n"
			"y = -y\n"
			"z = -z\n")
        .def("abs", &SPK::Vector3D::abs,
			":brief: Sets this Vector3D to its absolute values\n"
			"\n"
			"This method performs these operations :\n"
			"x = abs(x)\n"
			"y = abs(y)\n"
			"z = abs(z)\n"
			"\n"
			":since: 1.02.00\n")
        .def("crossProduct", &SPK::Vector3D::crossProduct,
			":brief: Computes the cross product between v and the vector3D and store the result in the vector3D\n"
			":param v: the vector3D used to compute the cross product (*this x v)\n")
        .def_readwrite("x", &SPK::Vector3D::x, "x coordinate of the vector")
        .def_readwrite("y", &SPK::Vector3D::y, "y coordinate of the vector")
        .def_readwrite("z", &SPK::Vector3D::z, "z coordinate of the vector")
		.def(boost::python::self_ns::repr(boost::python::self))

		//operator +
		.def(boost::python::self + float())
		.def(float() + boost::python::self)
		.def(boost::python::self + boost::python::self)
		//operator -
		.def(boost::python::self - float())
		.def(float() - boost::python::self)
		.def(boost::python::self - boost::python::self)
		//operator *
		.def(boost::python::self * float())
		.def(float() * boost::python::self)
		//operator /
		.def(boost::python::self / float())
		.def(float() / boost::python::self)
		//operator ==
		.def(boost::python::self == boost::python::self)
		//operator !=
		.def(boost::python::self != boost::python::self)
        ;

    boost::python::def("dotProduct", static_cast<float(*)(const SPK::Vector3D &, const SPK::Vector3D &)>(&SPK::dotProduct),
		":brief: Returns the dot product between two Vector3D\n"
		":param v0: the first Vector3D\n"
		":param v1: the second Vector3D\n"
		":return: the dot product (v0 . v1)\n"
	);

	boost::python::def("getSqrDist", &SPK::getSqrDist);
}


#pragma once

#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

#include <iostream>

namespace x3 = boost::spirit::x3;

namespace mathjit
{
    namespace ast {

        struct nil {};
        struct expr;
        struct un_op;
        struct operand;
        struct complex {
            double imag;
            boost::optional<char> i;
        };

        struct operand : x3::variant<nil,
                                    complex,
                                    double,
                                    char,
                                    x3::forward_ast<un_op>,
                                    x3::forward_ast<expr>
                                    >
        {
            using base_type::base_type;
            using base_type::operator=;
        };

        struct expr {
            operand first;
            std::vector<un_op> ops;
            };

        struct un_op {
            char op;
            operand _operand;
        };

        template<typename T>
        struct visitor {
            virtual T operator()(nil)        const = 0;
            virtual T operator()(double n)   const = 0;
            virtual T operator()(complex n)  const = 0;
            virtual T operator()(char var)   const = 0;
            virtual T operator()(un_op op)   const = 0;
            virtual T operator()(expr e)     const = 0;
        };
        struct printer : public visitor<void> {
            void operator()(nil)        const {}
            void operator()(double n)   const { std::cout << n;}
            void operator()(complex n)  const {
                double real = 0.0, imag = 0.0;
                if(n.i) {
                    std::cout << std::complex<double>(0, n.imag);
                } else {
                    std::cout << n.imag;
                }
            }
            void operator()(char var)   const { std::cout << var;}
            void operator()(un_op op)   const {
                boost::apply_visitor(*this, op._operand);
                std::cout << op.op;
            }
            void operator()(expr e)     const {
                boost::apply_visitor(*this, e.first);
                for(const un_op& op : e.ops) {
                    (*this)(op);
                }
            }
        };
    }
}

BOOST_FUSION_ADAPT_STRUCT(mathjit::ast::complex, imag, i);
BOOST_FUSION_ADAPT_STRUCT(mathjit::ast::expr, first, ops);
BOOST_FUSION_ADAPT_STRUCT(mathjit::ast::un_op, op, _operand);


#include <boost/timer/timer.hpp>
#include <iostream>
#include <cmath>

using namespace boost::timer;

int main()
{
  cpu_timer timer;

  for (int i = 0; i < 1000000; ++i)
    std::pow(1.234, i);
  std::cout << timer.format() << '\n';
}


/*-----------------------------------------------------------------------------+
Copyright (c) 2011-2011: Joachim Faulhaber
+------------------------------------------------------------------------------+
   Distributed under the Boost Software License, Version 1.0.
      (See accompanying file LICENCE.txt or copy at
           http://www.boost.org/LICENSE_1_0.txt)
+-----------------------------------------------------------------------------*/
#define BOOST_TEST_MODULE icl::fix_icl_after_thread unit test
#include <libs/icl/test/disable_test_warnings.hpp>
#include "../unit_test_unwarned.hpp"

//#define BOOST_ICL_USE_STATIC_BOUNDED_INTERVALS
#include <boost/icl/interval_map.hpp>
#include <boost/icl/split_interval_map.hpp>
#include <boost/icl/separate_interval_set.hpp>
#include <boost/icl/split_interval_set.hpp>

using namespace std;
using namespace boost;
using namespace unit_test;
using namespace boost::icl;

BOOST_AUTO_TEST_CASE(ticket_5482)
{
    typedef interval_map<int,int,partial_absorber,std::less>    m1_t;
    typedef interval_map<int,int,partial_absorber,std::greater> m2_t;
    m1_t m1;
    m2_t m2;
    m1.insert(make_pair(m1_t::interval_type(1), 20));
    m1.insert(make_pair(m1_t::interval_type(2), 20));
    m1.insert(make_pair(m1_t::interval_type(3), 20));

    m2.insert(make_pair(m2_t::interval_type(1), 20));
    m2.insert(make_pair(m2_t::interval_type(2), 20));
    m2.insert(make_pair(m2_t::interval_type(3), 20));

    BOOST_CHECK_EQUAL(m1.iterative_size(), m2.iterative_size());
    BOOST_CHECK_EQUAL(m1.iterative_size(), 1);
    BOOST_CHECK_EQUAL(m2.iterative_size(), 1);
}

#include <boost/cstdint.hpp>
BOOST_AUTO_TEST_CASE(ticket_5559_Denis)
{
    //Submitted by Denis
    typedef boost::icl::interval_set<boost::uint32_t, std::greater> Set;
    const uint32_t ui32_max = (std::numeric_limits<uint32_t>::max)();

    Set q1( Set::interval_type::closed(ui32_max, 0) );
    Set q5( Set::interval_type::closed(0, 0) );

    BOOST_CHECK_EQUAL(q1, q1+q5);
}

//------------------------------------------------------------------------------
// Ticket #6095 Marvin Sielenkemper
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(ticket_6095_Marvin_Sielenkemper_isEmptyTest)
{
    typedef int                         Value;
    typedef boost::icl::interval<Value> Interval;
    typedef std::numeric_limits<Value>  Limits;

    Value const max((Limits::max)());

    Interval::interval_type piff = Interval::open(max,     max);

    BOOST_CHECK(!icl::is_empty(Interval::open(max - 2, max)));
    BOOST_CHECK( icl::is_empty(Interval::open(max - 1, max)));
    BOOST_CHECK( icl::is_empty(Interval::open(max,     max)));
}

BOOST_AUTO_TEST_CASE(ticket_6095_Marvin_Sielenkemper_totalRangeTest)
{
    typedef int                                                              Value;
    typedef boost::icl::interval<Value>                                      Interval;
    typedef std::numeric_limits<Value>                                       Limits;
    typedef boost::icl::interval_map<Value, int, boost::icl::total_enricher> Container;

    Value const min((Limits::min)());
    Value const max((Limits::max)());

    boost::icl::interval_map<Value, int, boost::icl::total_enricher> intervals;

    intervals += std::make_pair(Interval::closed(min, max), 0);
    intervals += std::make_pair(Interval::right_open(0, 10),  3);

    BOOST_CHECK_EQUAL(intervals.iterative_size(), 3);
}


// Copyright (c) 2021 Graphcore Ltd. All rights reserved.
#define BOOST_TEST_MODULE Test_AAPI_loop_accumulate
#include <boost/test/unit_test.hpp>
#include <memory>
#include <onnx/onnx_pb.h>
#include <tuple>
#include <vector>
#include <popart/aliasesmap.hpp>
#include <popart/graph.hpp>
#include <popart/iarray.hpp>
#include <popart/ir.hpp>
#include <popart/names.hpp>
#include <popart/ndarraywrapper.hpp>
#include <popart/op/accumulate.hpp>
#include <popart/op/add.hpp>
#include <popart/op/call.hpp>
#include <popart/op/exchange/hostcopy.hpp>
#include <popart/op/identity.hpp>
#include <popart/op/init.hpp>
#include <popart/op/loop.hpp>
#include <popart/op/mul.hpp>
#include <popart/optimizervalue.hpp>
#include <popart/session.hpp>
#include <popart/stepio.hpp>
#include <popart/tensor.hpp>
#include <popart/tensorinfo.hpp>
#include <popart/tensors.hpp>
#include <popart/testdevice.hpp>
#include <popart/transforms/autodiff.hpp>
#include <popart/util.hpp>

float generateReferenceWeights(float, std::vector<float>, int, float);

namespace popart {
/**
 * Create a basic training loop directly in IR:
 *
 *     W <- [0]
 *
 *     Loss(W, D) = (W + D) * D
 *
 *     for D in [1, 2, 3, 4]:
 *       stream D to device
 *       compute W_grad = dLoss / dW
 *       W -= lr * W_grad
 *
 *     return W to device.
 *
 * This isn't using the "implied" training loop, but a constructed LoopOp.
 */
BOOST_AUTO_TEST_CASE(TestBasicTrainingLoop) {
  auto test = [](bool useAzc) {
    // Construct IR and main graph.
    auto ir      = std::make_unique<Ir>();
    Graph &graph = ir->getMainGraph();
    Op::Settings gSettings(graph, "op", {});

    // Parameters.
    float lr                     = 0.1; // Learning rate in W -= lr * dW.
    const int loopTripCount      = 10;  // Number of loop iterations.
    const float weightsInitValue = 1.0; // Initial value for weights.

    // Define type and shape of tensors.
    TensorInfo tInfo(DataType::FLOAT, Shape{1}); // Weights and data info.

    // Host buffers.
    std::vector<float> dataHost(loopTripCount);
    std::iota(
        std::begin(dataHost), std::end(dataHost), 1); // Fill with [1,2,...]
    std::vector<float> weightsHost(tInfo.nelms(), weightsInitValue);
    const std::vector<float> oneHost(tInfo.nelms(), 1);

    // Create data stream.
    TensorId dataStream = "D_stream";
    graph.getTensors().addStream(dataStream, tInfo);

    // Create and read weights.
    TensorId weights    = "W";
    TensorId weightsOut = "W_out";
    graph.getTensors().addVarInit(weights, tInfo, weightsHost.data());

    // Create the loss (forward) subgraph. The loss is Loss(W, D) = (W + D) * D.
    // Inputs:
    //   - data (D)
    //   - weights (W)
    // Outputs:
    //   - loss = (W + D) * D
    // -------------------------------------------------------------------------
    auto &fwd = ir->createGraph(GraphId("fwd-subgraph"));
    Op::Settings fwdSettings(fwd, "fwd", fwd.getScope());

    TensorId fwdData    = addScope(fwd, "data");
    TensorId fwdWeights = addScope(fwd, "weights");
    TensorId fwdTmp     = addScope(fwd, "tmp");
    TensorId fwdLoss    = addScope(fwd, "loss");
    fwd.addInput(fwdData, tInfo);
    fwd.addInput(fwdWeights, tInfo);

    fwd.createConnectedOp<AddOp>({{AddOp::getArg0InIndex(), fwdWeights},
                                  {AddOp::getArg1InIndex(), fwdData}},
                                 {{AddOp::getOutIndex(), fwdTmp}},
                                 Onnx::Operators::Add_7,
                                 fwdSettings.copy("Add"));

    fwd.createConnectedOp<MulOp>(
        {{MulOp::getArg0InIndex(), fwdTmp}, {MulOp::getArg1InIndex(), fwdData}},
        {{MulOp::getOutIndex(), fwdLoss}},
        Onnx::Operators::Mul_7,
        fwdSettings.copy("Mul"));

    fwd.markAsOutput(fwdLoss);

    // Create the backward graph of the loss subgraph.
    // Inputs:
    //   - Gradient___loss
    //   - data
    //   - loss
    //   - weights
    // Outputs:
    //   - Gradient___data = 2 * data + weights
    //   - Gradient___weights = data
    // -------------------------------------------------------------------------
    Autodiff autodiff;
    auto result = autodiff.apply(*ir,
                                 fwd.id,
                                 Autodiff::TensorIds({fwdLoss}),
                                 Autodiff::TensorIds({fwdWeights}),
                                 FwdGraphToBwdGraphInfo(),
                                 AutodiffStitchStrategy::RecomputeMinimal);

    auto &bwd = ir->getGraph(result.at(fwd.id).bwdGraphId);

    TensorId bwdData    = addScope(bwd, "data");
    TensorId bwdWeights = addScope(bwd, "weights");
    TensorId bwdLoss    = addScope(bwd, "loss");
    TensorId bwdLossGrad =
        addScope(bwd, reservedGradientPrefix() + std::string("loss"));
    TensorId bwdDataGrad =
        addScope(bwd, reservedGradientPrefix() + std::string("data"));
    TensorId bwdWeightsGrad =
        addScope(bwd, reservedGradientPrefix() + std::string("weights"));

    // Create training loop.
    // -------------------------------------------------------------------------
    Graph &loopSg = ir->createGraph(GraphId{"loop-subgraph"});
    Op::Settings loopSettings(graph, "loop");

    // Add mandatory loop iterator tensor to subgraph (is not an output)
    TensorId loopIter = addScope(loopSg, reservedLoopIteratorPrefix());
    loopSg.addInput(loopIter, TensorInfo{DataType::INT32, {}});

    // Add mandatory loop condition tensor to subgraph (is also an output)
    TensorId loopCond = addScope(loopSg, reservedLoopCondPrefix());
    loopSg.addInput(loopCond, TensorInfo{DataType::BOOL, {}});
    loopSg.markAsOutput(loopCond);

    // Create LoopOp in parent graph.
    auto loopOp =
        graph.createOp<LoopOp>(Onnx::Operators::Loop_11, loopSettings, loopSg);
    loopOp->setTripCountValue(loopTripCount);

    TensorId loopWeights = addScope(loopSg, weights);
    loopOp->addLoopInput(
        LoopOp::getFirstInputInIndex(), weights, loopWeights, false);

    // Init data tensor. This is op is required for HostLoadOp - see
    // hostcopy.hpp.
    TensorId dataPrehostload = addScope(loopSg, "D_prehostload");
    loopSg.createConnectedOp<InitOp>({},
                                     {{InitOp::getOutIndex(), dataPrehostload}},
                                     Onnx::CustomOperators::Init_1,
                                     tInfo,
                                     TensorType::ActGrad,
                                     InitType::Zero,
                                     loopSettings.copy("Init"));

    // Fill data tensor with data from host.
    TensorId loopData = addScope(loopSg, "D");
    loopSg.createConnectedOp<HostLoadOp>(
        {{HostLoadOp::getLocalTensorInIndex(), dataPrehostload}},
        {{HostLoadOp::getLocalTensorOutIndex(), loopData}},
        Onnx::CustomOperators::HostLoad,
        loopSettings.copy("HostLoad"),
        dataStream);

    // Call the loss (forward) subgraph.
    TensorId loopLoss = addScope(loopSg, "loss");
    loopSg.createConnectedOp<CallOp>(
        {
            {fwd.getInputIndex(fwdData), loopData},
            {fwd.getInputIndex(fwdWeights), loopWeights},
        },
        {
            {fwd.getOutputIndex(fwdLoss), loopLoss},
        },
        Onnx::AiGraphcore::OpSet1::Call,
        std::ref(fwd),
        loopSettings.copy("CallFwd"));

    // Call the backward loss subgraph.
    TensorId loopWeightsGrad = addScope(loopSg, "W_grad");
    TensorId loopOne         = addScope(loopSg, "one");
    loopSg.getTensors().addConstInit(loopOne, tInfo, oneHost.data());
    loopSg.createConnectedOp<CallOp>(
        {
            {bwd.getInputIndex(bwdLossGrad), loopOne},
            {bwd.getInputIndex(bwdLoss), loopLoss},
            {bwd.getInputIndex(bwdData), loopData},
            {bwd.getInputIndex(bwdWeights), loopWeights},
        },
        {
            {bwd.getOutputIndex(bwdDataGrad), addScope(loopSg, "data_grad")},
            {bwd.getOutputIndex(bwdWeightsGrad), loopWeightsGrad},
        },
        Onnx::AiGraphcore::OpSet1::Call,
        std::ref(bwd),
        loopSettings.copy("CallBwd"));

    // Weight update.
    TensorId loopWeightsOut = addScope(loopSg, weightsOut);
    loopSg.createConnectedOp<AccumulateOp>(
        {
            {AccumulateOp::getVarToUpdateInIndex(), loopWeights},
            {AccumulateOp::getUpdaterInIndex(), loopWeightsGrad},
        },
        {
            {AccumulateOp::getUpdatedVarOutIndex(), loopWeightsOut},
        },
        AccumulationType::DampenedAdd,
        OptimizerValue{-lr},
        loopSettings.copy("Accumulate"));

    loopOp->addLoopOutput(0, weightsOut, loopWeightsOut, false);
    loopOp->setup();

    // Transfer modified inputs. This allows changes to loopWeights in the
    // loop subgraph to be reflected to weights in the main graph.
    AliasesMap aliasesMap{graph};
    Aliases &aliases = aliasesMap.getAliases(graph.id);
    std::vector<popart::Op *> loopOps;
    for (auto const &opMap : loopSg.getOps())
      loopOps.push_back(opMap.second.get());
    auto modifiedRegions = loopSg.getTensors()
                               .get(loopWeights)
                               ->modifiedRegionsByOps(loopOps, aliases);
    loopOp->addModified(
        loopOp->input->indices(graph.getTensors().get(weights))[0],
        modifiedRegions);

    // This needs to be set to the number of times data is be read from
    // host.
    ir->setDataFlow(DataFlow{loopTripCount});

    // Set IR state required for lowering.
    auto &opts                   = ir->getSessionOptions();
    opts.enableExplicitMainLoops = true;
    opts.useHostCopyOps          = true;
    opts.aliasZeroCopy           = useAzc;
    opts.explicitRecomputation   = useAzc;
    ir->updateVertices();
    ir->setOnnxModel({});
    ir->setPatterns(Patterns(PatternsLevel::Minimal));
    for (auto &id_graph : ir->getGraphs()) {
      auto &_graph = ir->getGraph(id_graph.first);
      ir->applyPreAliasPatterns(_graph);
    }
    for (auto &id_graph : ir->getGraphs()) {
      auto &_graph = ir->getGraph(id_graph.first);
      ir->applyInplacePattern(_graph);
    }
    ir->setIsPrepared();

    // Lower IR.
    const auto session = TrainingSession::createFromIr(
        std::move(ir), createTestDevice(TEST_TARGET));
    session->prepareDevice();

    // An extra dimension of `loopTripCount` needs to be added to the input
    // data on the host.
    TensorInfo dInfo(DataType::FLOAT, Shape{loopTripCount, 1});
    NDArrayWrapper<float> dataWrapper(dataHost.data(), dInfo);
    std::map<TensorId, IArray &> inputs = {{dataStream, dataWrapper}};

    StepIO stepio(inputs, {});
    stepio.enableRuntimeAsserts(false);

    session->weightsFromHost();
    session->run(stepio);

    WeightsIO weightsRead;
    weightsRead.insert(weights, {weightsHost.data(), tInfo});
    session->weightsToHost();
    session->readWeights(weightsRead);

    // Numerical tests.
    float weightsReference =
        generateReferenceWeights(weightsInitValue, dataHost, loopTripCount, lr);
    using boost::test_tools::tolerance;
    BOOST_TEST(weightsHost[0] == weightsReference, tolerance(1e-8));
  };

  test(true);
  test(false);
}
} // namespace popart

/**
 * Generate reference weights for the popART basic training loop.
 *
 * If the loss is L(W, D) = (W + D) * D, then the derivative of the loss w.r.t.
 * the weights W is dL / dW = D. Thus, the basic training loop is equivalent to:
 *
 *     for D in [1, 2, 3, 4]:
 *       W -= lr * D.
 *
 * This function implements this equivalent training loop.
 *
 * \param weightsInitValue The initial value of the weights.
 * \param data A vector with the input data.
 * \param loopTripCount The number of iterations.
 * \param lr The learning rate for weight updates.
 */
float generateReferenceWeights(const float weightsInitValue,
                               const std::vector<float> data,
                               const int loopTripCount,
                               const float lr) {
  float weights = weightsInitValue;
  for (int i = 0; i < loopTripCount; i++) {
    weights -= lr * data[i];
  }
  return weights;
}


#include <cstdlib>
#include <iostream>
#include <numeric>
#include <vector>

//! [substract]
#include <boost/simd/meta/cardinal_of.hpp>
#include <boost/simd/pack.hpp>

//! [substract-include]
#include <boost/simd/function/load.hpp>
#include <boost/simd/function/store.hpp>
//! [substract-include]
#include <boost/simd/function/minus.hpp>

int main()
{
  //! [substract-simd-types]
  namespace bs = boost::simd;

  size_t size = 128;
  std::vector<int32_t> array(size);
  std::vector<int32_t> out(size);
  // Initialize input array
  std::iota(array.begin(), array.end(), 0);
  int32_t scalar = 42;
  //! [substract-simd-types]

  //! [substract-cardinal]
  using pack_t     = bs::pack<int32_t>;
  size_t pack_card = bs::cardinal_of<pack_t>();
  //! [substract-cardinal]

  //! [substract-scalar]
  // Scalar version
  for (size_t i = 0; i < size; ++i) {
    out[i] = array[i] - scalar;
  }
  //! [substract-scalar]

  {
    //! [substract-loop-con]
    for (size_t i = 0; i < size; i += pack_card) {
    }
    //! [substract-loop-con]

    //! [substract-fortytwo]
    pack_t p_out, fortytwo(scalar);
    //! [substract-fortytwo]

    //! [substract-pointer]
    for (size_t i = 0; i < size; i += pack_card) {
      pack_t p_arr(array.data() + i);
      p_out = p_arr - fortytwo;
      bs::store(p_out, out.data() + i);
    }
    //! [substract-pointer]
  }

  {
    //! [substract-load]
    // Using explicit load/store
    pack_t p_out, p_arr, fortytwo{scalar};
    for (size_t i = 0; i < size; i += pack_card) {
      p_arr = bs::load<pack_t>(array.data() + i);
      p_out = p_arr - fortytwo;
      bs::store(p_out, out.data() + i);
    }
    //! [substract-load]
  }

  {
    //! [substract-remainder]
    // Using explicit load/store
    // set size to an arbitrary value
    size = 133;
    pack_t p_out, p_arr, fortytwo{42};
    array.resize(size);
    out.resize(size);
    size_t i = 0;
    for (; i + pack_card <= size; i += pack_card) {
      p_arr = bs::load<pack_t>(array.data() + i);
      p_out = p_arr - fortytwo;
      bs::store(p_out, out.data() + i);
    }

    for (; i < size; ++i) {
      out[i] = array[i] - scalar;
    }
    //! [substract-remainder]
  }
  return 0;
}
//! [substract]


#define BOOST_TEST_MAIN
#include <boost/test/included/unit_test.hpp>
#include <srook/numeric/fixed_point.hpp>
#include <srook/limits/numeric_limits.hpp>

BOOST_AUTO_TEST_SUITE(numeric_fixed_point_test)

BOOST_AUTO_TEST_CASE(fixed_point_compare_test1)
{
    typedef srook::numeric::fixed_point<4, 4> fixed_point_type;
    const fixed_point_type fp1 = srook::uint8_t(0xf), fp2 = srook::uint8_t(0xe);
    BOOST_TEST((fp2 < fp1));
    BOOST_TEST((fp1 > fp2));
    BOOST_TEST((fp2 <= fp1));
    BOOST_TEST((fp1 >= fp2));
    BOOST_TEST(!(fp1 == fp2));
    BOOST_TEST((fp1 != fp2));
}

BOOST_AUTO_TEST_CASE(fixed_point_compare_test2)
{
    typedef srook::numeric::fixed_point<4, 4> fixed_point_type;
    const fixed_point_type fp = 2;
    const int i = 3;
    BOOST_TEST((fp < i));
    BOOST_TEST((i > fp));
    BOOST_TEST((fp <= i));
    BOOST_TEST((i >= fp));
    BOOST_TEST(!(fp == i));
    BOOST_TEST((fp != i));
}

BOOST_AUTO_TEST_CASE(fixed_point_compare_test3)
{
    typedef srook::numeric::fixed_point<4, 4> fixed_point_type;
    const float f = 2.5f;
    const fixed_point_type fp = f;
    BOOST_TEST(!(f < fp));
    BOOST_TEST(!(f > fp));
    BOOST_TEST((f <= fp));
    BOOST_TEST((f >= fp));
    BOOST_TEST((f == fp));
    BOOST_TEST(!(f != fp));
}

BOOST_AUTO_TEST_SUITE_END()


<% render(instance) %>
<%= includes((0..x).size) %>

#include <boost/hana/detail/constexpr.hpp>
#include <boost/hana/list/list.hpp>
#include <boost/hana/logical/logical.hpp>
#include <boost/hana/pair.hpp>

BOOST_HANA_CONSTEXPR_LAMBDA auto partition = [](auto xs, auto pred) {
    return boost::hana::pair(
        boost::hana::filter(xs, pred),
        boost::hana::filter(xs, [=](auto x) { return boost::hana::not_(pred(x)); })
    );
};

<%= render('list/partition/main.cpp') %>