//
//  KDTree.cpp
//
//
//  Created by Sivaram Ambikasaran on 12/3/13.
//
//

#include "KDTree.hpp"
#include <Eigen/Dense>

KDTree::KDTree(const unsigned n_Locations, const unsigned n_Dimension, double* locations, const unsigned n_Properties, double* properties, const unsigned MinParticlesInLeaf, const unsigned nLevels) {
        this->n_Locations       =       n_Locations;
        this->n_Dimension       =       n_Dimension;
        this->n_Properties      =       n_Properties;
        this->MinParticlesInLeaf=       MinParticlesInLeaf;
        this->nLevels           =       nLevels;

        sorted_Contents         =       Eigen::MatrixXd(n_Locations, n_Dimension+n_Properties);

        unsigned count_Location =       0;
        unsigned count_Property =       0;

        for (unsigned j=0; j<n_Locations; ++j) {
                for (unsigned k=0; k<n_Dimension; ++k) {
                        sorted_Contents(j,k)    =       locations[count_Location];
                        ++count_Location;
                }
                for (unsigned k=n_Dimension; k<n_Dimension+n_Properties; ++k) {
                        sorted_Contents(j,k)    =       properties[count_Property];
                        ++count_Property;
                }
        }
}

void KDTree::merge_Sorted_Lists(unsigned n_Left_Start, unsigned n_Left_Size, unsigned n_Right_Start, unsigned n_Right_Size, unsigned n_Index) {

        Eigen::MatrixXd temp_List(n_Left_Size+n_Right_Size, n_Dimension+n_Properties);

        unsigned j_Left =       n_Left_Start;
        unsigned j_Right=       n_Right_Start;

        unsigned j      =       0;

        while (j_Left < n_Left_Start + n_Left_Size && j_Right < n_Right_Start + n_Right_Size) {
                if (sorted_Contents(j_Left, n_Index) < sorted_Contents(j_Right, n_Index)) {
                        temp_List.row(j)=       sorted_Contents.row(j_Left);
                        ++j_Left;
                }
                else {
                        temp_List.row(j)=       sorted_Contents.row(j_Right);
                        ++j_Right;
                }
                ++j;
        }

        while (j_Left < n_Left_Start + n_Left_Size) {
                temp_List.row(j)        =       sorted_Contents.row(j_Left);
                ++j_Left;
                ++j;
        }

        while (j_Right < n_Right_Start + n_Right_Size) {
                temp_List.row(j)        =       sorted_Contents.row(j_Right);
                ++j_Right;
                ++j;
        }

        sorted_Contents.block(n_Left_Start, 0, n_Left_Size + n_Right_Size, n_Dimension+n_Properties)        =       temp_List;
}

void KDTree::merge_Sort(unsigned n_Start, unsigned n_Size, unsigned n_Index) {
        if (n_Size<=1) {
                //      Do nothing
                return;
        }
        else {
                unsigned n_Left_Start   =       n_Start;
                unsigned n_Left_Size    =       n_Size/2;
                unsigned n_Right_Start  =       n_Start+n_Left_Size;
                unsigned n_Right_Size   =       n_Size-n_Left_Size;

                merge_Sort(n_Left_Start, n_Left_Size, n_Index);
                merge_Sort(n_Right_Start, n_Right_Size, n_Index);

                merge_Sorted_Lists(n_Left_Start, n_Left_Size, n_Right_Start, n_Right_Size, n_Index);
        }
}

int KDTree::sort_KDTree(unsigned n_Start, unsigned n_Size, unsigned n_Index, unsigned local_level, std::vector<int>& NumberOfParticlesInLeaves) {
    // static int nLevels = 0;
    n_Index                 =       n_Index%n_Dimension;
    merge_Sort(n_Start, n_Size, n_Index);

    unsigned n_Left_Start   =       n_Start;
    unsigned n_Left_Size    =       n_Size/2;
    unsigned n_Right_Start  =       n_Start+n_Left_Size;
    unsigned n_Right_Size   =       n_Size-n_Left_Size;

    ++n_Index;
    n_Index                 =       n_Index%n_Dimension;

    merge_Sort(n_Left_Start, n_Left_Size, n_Index);
    unsigned n_Left_Bottom_Start   =       n_Left_Start;
    unsigned n_Left_Bottom_Size    =       n_Left_Size/2;
    unsigned n_Left_Top_Start  =       n_Left_Start+n_Left_Bottom_Size;
    unsigned n_Left_Top_Size   =       n_Left_Size-n_Left_Bottom_Size;

    merge_Sort(n_Right_Start, n_Right_Size, n_Index);
    unsigned n_Right_Bottom_Start   =       n_Right_Start;
    unsigned n_Right_Bottom_Size    =       n_Right_Size/2;
    unsigned n_Right_Top_Start  =       n_Right_Start+n_Right_Bottom_Size;
    unsigned n_Right_Top_Size   =       n_Right_Size-n_Right_Bottom_Size;

    ++n_Index;
    n_Index                 =       n_Index%n_Dimension;

    if (local_level < nLevels) {
    // if (n_Left_Bottom_Size >= MinParticlesInLeaf &&  n_Left_Top_Size >= MinParticlesInLeaf && n_Right_Bottom_Size >= MinParticlesInLeaf && n_Right_Top_Size >= MinParticlesInLeaf && local_level < nLevels) {
      local_level++;
      if(local_level==nLevels) {
        // std::cout << n_Left_Bottom_Size << std::endl;
        // std::cout << n_Left_Top_Size << std::endl;
        // std::cout << n_Right_Bottom_Size << std::endl;
        // std::cout << n_Right_Top_Size << std::endl;
        // std::cout << "--------------------------------" << std::endl;
        NumberOfParticlesInLeaves.push_back(n_Left_Bottom_Size);
        NumberOfParticlesInLeaves.push_back(n_Left_Top_Size);
        NumberOfParticlesInLeaves.push_back(n_Right_Bottom_Size);
        NumberOfParticlesInLeaves.push_back(n_Right_Top_Size);
      }
      // std::cout << "local_level: " << local_level << std::endl;
      sort_KDTree(n_Left_Bottom_Start, n_Left_Bottom_Size, n_Index, local_level, NumberOfParticlesInLeaves);
      sort_KDTree(n_Left_Top_Start, n_Left_Top_Size, n_Index, local_level, NumberOfParticlesInLeaves);
      sort_KDTree(n_Right_Bottom_Start, n_Right_Bottom_Size, n_Index, local_level, NumberOfParticlesInLeaves);
      sort_KDTree(n_Right_Top_Start, n_Right_Top_Size, n_Index, local_level, NumberOfParticlesInLeaves);
      // nLevels += 1;
    }
    return 0;
    // return nLevels;
}

int KDTree::sort_KDTree(std::vector<int>& NumberOfParticlesInLeaves) {
        if (n_Locations<=1) {
                //      Do nothing
                return 0;
        }
        else {
                //      Number of point on the left cluster
                return sort_KDTree(0, n_Locations, 0, 0, NumberOfParticlesInLeaves);
        }
}

void KDTree::get_Location_Properties(double* locations, double* properties) {
        unsigned count_Location         =       0;
        unsigned count_Property         =       0;
        for (unsigned j=0; j<n_Locations; ++j) {
                for (unsigned k=0; k<n_Dimension; ++k) {
                        locations[count_Location]       =       sorted_Contents(j,k);
                        ++count_Location;
                }
                for (unsigned k=n_Dimension; k<n_Dimension+n_Properties; ++k) {
                        properties[count_Property]      =       sorted_Contents(j,k);
                        ++count_Property;
                }
        }
}

void KDTree::get_Location_Properties(const unsigned n_Index, double* location, double* properties) {
        for (unsigned k=0; k<n_Dimension; ++k) {
                location[k]     =       sorted_Contents(n_Index,k);
        }
        for (unsigned k=0; k<n_Properties; ++k) {
                properties[k]   =       sorted_Contents(n_Index,k+n_Dimension);
        }
}

/////////////////GLOBAL FUNCTIONS TO BE CALLED FROM MAIN ///////////////////////////
void display(std::string display_String, unsigned n_Locations, unsigned n_Dimension, double* locations, unsigned n_Properties, double* properties) {
        std::cout << display_String << std::endl;

        unsigned count_Location  =       0;
        unsigned count_Property  =       0;

        for (unsigned j=0; j<n_Locations; ++j) {
                for (unsigned k=0; k<n_Dimension; ++k) {
                        std::cout << locations[count_Location] << "\t";
                        ++count_Location;
                }
                for (unsigned k=0; k<n_Properties; ++k) {
                        std::cout << properties[count_Property] << "\t";
                        ++count_Property;
                }
                std::cout << std::endl;
        }
        std::cout << std::endl;
}

void sort_KDTree(unsigned N, unsigned n_Dimension, double* locations, unsigned n_Properties, double* properties, unsigned MinParticlesInLeaf, unsigned nLevels, double* sorted_Locations, double* sorted_Properties, std::vector<std::vector<int> >& boxNumbers, std::vector<int>& NumberOfParticlesInLeaves) {
    KDTree* B                       =       new KDTree(N, n_Dimension, locations, n_Properties, properties, MinParticlesInLeaf, nLevels);
    delete locations;
    delete properties;
    NumberOfParticlesInLeaves.reserve(pow(4,nLevels));
    //      Sorts the locations based on KDTree.
    B->sort_KDTree(NumberOfParticlesInLeaves);
    // converting N ordering to the mirrored C ordering.
    // The sorted locations that the KD Tree outputs, correspond to boxes in N ordering.
    // So here we are generating N ordering sequence of box numbers in terms of the mirrored C ordering.
    // only leaf level ordering is what we want: boxNumbers[nLevels]
    std::vector<int> boxNumbersInLevel0;
    boxNumbersInLevel0.push_back(0);//level 0
    boxNumbers.push_back(boxNumbersInLevel0);
    for (size_t j = 1; j <= nLevels; j++) {
      std::vector<int> boxNumbersInALevel;
      for (size_t k = 0; k < boxNumbers[j-1].size(); k++) {
        boxNumbersInALevel.push_back(boxNumbers[j-1][k]*4 + 0);
        boxNumbersInALevel.push_back(boxNumbers[j-1][k]*4 + 3);
        boxNumbersInALevel.push_back(boxNumbers[j-1][k]*4 + 1);
        boxNumbersInALevel.push_back(boxNumbers[j-1][k]*4 + 2);
      }
      boxNumbers.push_back(boxNumbersInALevel);
    }
    // Obtains the sorted location.
    // sorted_Locations contains the locations sorted as per the KD Tree.(In N ordering of boxes)
    B->get_Location_Properties(sorted_Locations, sorted_Properties);

    // Display the sorted contents.
   // display("Sorted contents: ", N, n_Dimension, sorted_Locations, n_Properties, sorted_Properties);
   // exit(0);
   // Take away from kD tree class: sorted_Locations, sorted_Properties, boxNumbers[nLevels], NumberOfParticlesInLeaves. Give these as inputs to FMM2DTree constructor.
    /////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////

    /* example:
    boxNumbers NumberOfParticlesInLeaves
    0           4
    3           4
    1           4
    2           4
    */
    delete B;
}


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

#ifndef DYNO_DETAIL_HAS_DUPLICATES_HPP
#define DYNO_DETAIL_HAS_DUPLICATES_HPP

#include <boost/hana/core/to.hpp>
#include <boost/hana/length.hpp>
#include <boost/hana/not_equal.hpp>
#include <boost/hana/set.hpp>

namespace dyno { namespace detail {

// Returns whether a Foldable contains duplicate elements, i.e. elements
// that compare equal to each other.
//
// TODO: Lift this into the public interface of Hana.
template <typename Foldable>
constexpr auto has_duplicates(Foldable const& foldable) {
  return boost::hana::not_equal(
    boost::hana::length(boost::hana::to_set(foldable)),
    boost::hana::length(foldable)
  );
}

}} // end namespace dyno::detail

#endif // DYNO_DETAIL_HAS_DUPLICATES_HPP


//  (C) Copyright Matt Borland 2021.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_MATH_CCMATH_TRUNC_HPP
#define BOOST_MATH_CCMATH_TRUNC_HPP

#include <cmath>
#include <type_traits>
#include <boost/math/tools/is_constant_evaluated.hpp>
#include <boost/math/ccmath/abs.hpp>
#include <boost/math/ccmath/isinf.hpp>
#include <boost/math/ccmath/isnan.hpp>
#include <boost/math/ccmath/floor.hpp>
#include <boost/math/ccmath/ceil.hpp>

namespace boost::math::ccmath {

namespace detail {

template <typename T>
inline constexpr T trunc_impl(T arg) noexcept
{
    return (arg > 0) ? boost::math::ccmath::floor(arg) : boost::math::ccmath::ceil(arg);
}

} // Namespace detail

template <typename Real, std::enable_if_t<!std::is_integral_v<Real>, bool> = true>
inline constexpr Real trunc(Real arg) noexcept
{
    if(BOOST_MATH_IS_CONSTANT_EVALUATED(arg))
    {
        return boost::math::ccmath::abs(arg) == Real(0) ? arg :
               boost::math::ccmath::isinf(arg) ? arg :
               boost::math::ccmath::isnan(arg) ? arg :
               boost::math::ccmath::detail::trunc_impl(arg);
    }
    else
    {
        using std::trunc;
        return trunc(arg);
    }
}

template <typename Z, std::enable_if_t<std::is_integral_v<Z>, bool> = true>
inline constexpr double trunc(Z arg) noexcept
{
    return boost::math::ccmath::trunc(static_cast<double>(arg));
}

inline constexpr float truncf(float arg) noexcept
{
    return boost::math::ccmath::trunc(arg);
}

#ifndef BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS
inline constexpr long double truncl(long double arg) noexcept
{
    return boost::math::ccmath::trunc(arg);
}
#endif

} // Namespaces

#endif // BOOST_MATH_CCMATH_TRUNC_HPP


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

    @copyright 2016 NumScale SAS

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

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{
  /*!
    @ingroup group-arithmetic

    This function object computes the smallest of its parameter if they share the same sign,
    zero instead.

    @par Header <boost/simd/function/minmod.hpp>

    @par Notes

    Using `minmod(x, y)` is similar to `x*y > 0 ? min(x, y) : 0`

    @see min, minnum, minnummag, minmag

    @par Example:

      @snippet minmod.cpp minmod

    @par Possible output:

      @snippet minmod.txt minmod

  **/
  Value minmod(Value const& x, Value const& y);
} }
#endif

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

#endif


/* Boost check_gmp.cpp test file

 Copyright 2010-2011 Karsten Ahnert
 Copyright 2011 Mario Mulansky

 This file tests the odeint library with the gmp arbitrary precision types

 Distributed under the Boost Software License, Version 1.0.
 (See accompanying file LICENSE_1_0.txt or
 copy at http://www.boost.org/LICENSE_1_0.txt)
*/

#define BOOST_TEST_MODULE odeint_gsl

#include <gsl/gsl_vector.h>
#include <boost/numeric/odeint/stepper/euler.hpp>
#include <boost/numeric/odeint/external/gsl/gsl_wrapper.hpp>

#include <boost/test/unit_test.hpp>

using namespace boost::unit_test;
using namespace boost::numeric::odeint;

typedef gsl_vector *state_type;

const double sigma = 10.0;
const double R = 28.0;
const double b = 8.0 / 3.0;

void lorenz( const state_type x , state_type dxdt , double t )
{
    gsl_vector_set( dxdt , 0 , sigma * ( gsl_vector_get(x , 1 ) - gsl_vector_get( x , 0 ) ) );
    gsl_vector_set( dxdt , 1 , R * gsl_vector_get( x , 0 ) - gsl_vector_get( x , 1 ) - gsl_vector_get( x , 0 ) * gsl_vector_get( x , 2) );
    gsl_vector_set( dxdt , 2 , gsl_vector_get( x , 0 ) * gsl_vector_get( x , 1 ) - b * gsl_vector_get( x , 2) );
}

BOOST_AUTO_TEST_CASE( gsl )
{
    euler< state_type > euler;

    state_type x = gsl_vector_alloc( 3 );

    // check resizing
    state_type y = 0;
    boost::numeric::odeint::resize( y , x );
    BOOST_CHECK( 0 != y );

    gsl_vector_set( x , 0 , 1.0);
    gsl_vector_set( x , 1 , 1.0);
    gsl_vector_set( x , 2 , 2.0);

    euler.do_step( lorenz , x , 0.0 , 0.1 );

    //cout << gsl_vector_get( x , 0 ) << "  " << gsl_vector_get( x , 1 ) << "  " << gsl_vector_get( x , 2 ) << endl;

    gsl_vector_free( x );

}


#include "fbstab/components/full_residual.h"

#include <Eigen/Dense>
#include <algorithm>
#include <cmath>
#include <stdexcept>

#include "fbstab/components/full_variable.h"

namespace fbstab {

FullResidual::FullResidual(int nz, int nl, int nv) {
  if (nz <= 0 || nl < 0 || nv <= 0) {
    throw std::runtime_error(
        "All inputs to FullResidual::FullResidual must be >= 1.");
  }
  nz_ = nz;
  nl_ = nl;
  nv_ = nv;

  z_.resize(nz_);
  l_.resize(nl_);
  v_.resize(nv_);

  Fill(0.0);
}

void FullResidual::Fill(double a) {
  z_.setConstant(a);
  l_.setConstant(a);
  v_.setConstant(a);
}

void FullResidual::Negate() {
  z_ *= -1;
  l_ *= -1;
  v_ *= -1;
}

double FullResidual::Norm() const {
  return sqrt(znorm_ * znorm_ + lnorm_ * lnorm_ + vnorm_ * vnorm_);
}

double FullResidual::Merit() const {
  const double temp = this->Norm();
  return 0.5 * temp * temp;
}

void FullResidual::InnerResidual(const FullVariable& x,
                                 const FullVariable& xbar, double sigma) {
  NullDataCheck();
  // r.z = H*z + f + G'*l + A'*v + sigma*(z-zbar)
  z_.setConstant(0.0);
  data_->axpyf(1.0, &z_);
  data_->gemvH(x.z(), 1.0, 1.0, &z_);
  data_->gemvGT(x.l(), 1.0, 1.0, &z_);
  data_->gemvAT(x.v(), 1.0, 1.0, &z_);
  z_.noalias() += sigma * (x.z() - xbar.z());

  // r.l = h - G*z + sigma(l - lbar)
  l_.setConstant(0.0);
  data_->axpyh(1.0, &l_);
  data_->gemvG(x.z(), -1.0, 1.0, &l_);
  l_.noalias() += sigma * (x.l() - xbar.l());

  // rv = phi(y + sigma*(v-vbar),v)
  for (int i = 0; i < nv_; i++) {
    const double ys = x.y()(i) + sigma * (x.v()(i) - xbar.v()(i));
    v_(i) = pfb(ys, x.v()(i), alpha_);
  }
  znorm_ = z_.norm();
  lnorm_ = l_.norm();
  vnorm_ = v_.norm();
}

void FullResidual::NaturalResidual(const FullVariable& x) {
  NullDataCheck();
  // r.z = H*z + f + G'*l + A'*v
  z_.setConstant(0.0);
  data_->axpyf(1.0, &z_);
  data_->gemvH(x.z(), 1.0, 1.0, &z_);
  data_->gemvGT(x.l(), 1.0, 1.0, &z_);
  data_->gemvAT(x.v(), 1.0, 1.0, &z_);

  // r.l = h - G*z + sigma(l - lbar)
  l_.setConstant(0.0);
  data_->axpyh(1.0, &l_);
  data_->gemvG(x.z(), -1.0, 1.0, &l_);

  // rv = min(y,v)
  for (int i = 0; i < nv_; i++) {
    v_(i) = std::min(x.y()(i), x.v()(i));
  }
  znorm_ = z_.norm();
  lnorm_ = l_.norm();
  vnorm_ = v_.norm();
}

void FullResidual::PenalizedNaturalResidual(const FullVariable& x) {
  NaturalResidual(x);

  for (int i = 0; i < nv_; i++) {
    v_(i) = alpha_ * v_(i) +
            (1 - alpha_) * std::max(0.0, x.y()(i)) * std::max(0.0, x.v()(i));
  }
  znorm_ = z_.norm();
  lnorm_ = l_.norm();
  vnorm_ = v_.norm();
}

bool FullResidual::SameSize(const FullVariable& x) const {
  return (x.nz_ == nz_ && x.nl_ == nl_ && x.nv_ == nv_);
}

double FullResidual::pfb(double a, double b, double alpha) {
  double fb = a + b - sqrt(a * a + b * b);
  return alpha * fb + (1.0 - alpha) * std::max(0.0, a) * std::max(0.0, b);
}

void FullResidual::NullDataCheck() const {
  if (data_ == nullptr) {
    throw std::runtime_error(
        "FullResidual tried to access problem data before it's linked.");
  }
}

}  // namespace fbstab


///////////////////////////////////////////////////////////////////////////////
// statistics::survival::data::random::failure_time.hpp                      //
//                                                                           //
//  Copyright 2009 Erwann Rogard. Distributed under the Boost                //
//  Software License, Version 1.0. (See accompanying file                    //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)         //
///////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_SURVIVAL_DATA_RANDOM_FAILURE_TIME_HPP_ER_2009
#define BOOST_STATISTICS_SURVIVAL_DATA_RANDOM_FAILURE_TIME_HPP_ER_2009
#include <boost/dist_random/include.hpp>
#include <boost/statistics/model/wrap/aggregate/model_covariate_parameter.hpp>
#include <boost/statistics/survival/data/meta/failure_distribution.hpp>
#include <boost/statistics/survival/model/concept/random_failure.hpp>

namespace boost{
namespace statistics{
namespace survival{
namespace data{
namespace random{

    // Model + Covariate + Parameter + URNG ---> failure time
    //
    // This is a default implementaion that relies on meta::failure_random. It
    // can be overloaded by specializing on M.
    template<typename T,typename M,typename X,typename P,typename U>
    T failure_time(
        boost::statistics::model::model_covariate_parameter_<M,X,P> mcp,
        U& urng
    );

    // Implementation //

    template<typename T,typename M,typename X,typename P,typename U>
    T failure_time(
        boost::statistics::model::model_covariate_parameter_<M,X,P> mcp,
        U& urng
    ){
        BOOST_CONCEPT_ASSERT((
            survival::model::RandomFailure<T,M,X,P,U>
        ));

        return make_failure_random(mcp)(urng);
    }

}// random
}// data
}// survival
}// statistics
}// boost

#endif


#pragma once

// Armadillo
#include <armadillo>

// Mantella
#include "mantella_bits/optimisationProblem/kinematicallyRedundantMachines.hpp"

namespace mant {
  namespace krm {
    class ParallelKinematicMachine6PUPS : public KinematicallyRedundantMachines {
     public:
      explicit ParallelKinematicMachine6PUPS();

     protected:
      const arma::mat::fixed<3, 6> redundantJointsPosition_;
      const arma::mat::fixed<3, 6> redundantJointsAngles_;
      const arma::rowvec::fixed<6> middleJointsMinimalLength_;
      const arma::rowvec::fixed<6> middleJointsMaximalLength_;
      const arma::mat::fixed<3, 6> endEffectorJointsRelativePosition_;
    };
  }
}


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_ARITHMETIC_FUNCTIONS_SIMD_COMMON_CORRECT_FMA_HPP_INCLUDED
#define BOOST_SIMD_ARITHMETIC_FUNCTIONS_SIMD_COMMON_CORRECT_FMA_HPP_INCLUDED
#include <boost/simd/arithmetic/functions/correct_fma.hpp>
#include <boost/simd/include/functions/simd/multiplies.hpp>
#include <boost/simd/include/functions/simd/plus.hpp>
#include <boost/simd/include/functions/simd/split.hpp>
#include <boost/simd/include/functions/simd/group.hpp>
#include <boost/dispatch/meta/upgrade.hpp>

namespace boost { namespace simd { namespace ext
{

  BOOST_SIMD_FUNCTOR_IMPLEMENTATION( boost::simd::tag::correct_fma_, tag::cpu_
                                   , (A0)(X)
                                   , ((simd_<single_<A0>,X>))
                                     ((simd_<single_<A0>,X>))
                                     ((simd_<single_<A0>,X>))
                                   )
  {
    typedef A0 result_type;
    BOOST_SIMD_FUNCTOR_CALL_REPEAT(3)
    {
      typedef typename dispatch::meta::upgrade<A0>::type ivtype;
      ivtype a0l, a0h, a1l, a1h, a2l, a2h;
      split(a0, a0l, a0h);
      split(a1, a1l, a1h);
      split(a2, a2l, a2h);
      return group(a0l*a1l+a2l, a0h*a1h+a2h);
    }
  };

} } }

#endif


#include "pre.h"

#include "label.h"

#ifdef CMAKE_USE_DLIB
#include <dlib/statistics.h>
#include <dlib/svm.h>
#endif

#include <cassert>
#include <cmath>

namespace pre
{

dat::Dataset logarithm( const dat::Dataset & d )
{
    double min {};
    dat::apply( [ & ] ( label::Num, const dat::Spectrum & s )
        {
            for( const auto & point : s._y )
            {
                if( point < min )
                {
                    min = point;
                }
            }
        }     , d );

    dat::Dataset ret{ {}, d.second };
    dat::apply( [ & ] ( label::Num l, const dat::Spectrum & s )
        {
            dat::Spectrum transformed{};
            unsigned i {};
            for( const auto & intensity : s._y )
            {
                const auto positive = intensity - min + 1;
                const auto point = std::log( positive );
                transformed._y[ i++ ] = point;
            }
            ret.first[ l ].push_back( transformed );
        }      , ret );

    return ret;
}

Log::Log( const dat::Dataset & )
{
}

dat::Dataset Log::operator()( const dat::Dataset & d ) const
{
    return logarithm( d );
}

void normalize( dat::Dataset & d )
{
    dat::Spectrum mean {};
    dat::apply( [ & ] ( label::Num, const dat::Spectrum & s )
    {
        auto src = s._y.cbegin();
        const auto end = s._y.cend();
        auto dest = mean._y.begin();
        while( src < end )
        {
            * dest++ += * src++;
        }
    }      , d );
    const auto c = dat::count( d );
    for( auto & point : mean._y )
    {
        point /= c;
    }

    dat::Spectrum variance;
    dat::apply( [ & ] ( label::Num, const dat::Spectrum & s )
    {
        auto sp = s._y.cbegin();
        const auto end = s._y.cend();
        auto dest = variance._y.begin();
        while( sp < end )
        {
            const auto a = mean._y[ sp - s._y.cbegin()] ;
            const auto diff = * sp - a;
            * dest += diff * diff;
            assert( diff * diff >= 0 );
            ++ sp;
            ++ dest;
        }
    }      , d );
    for( auto & point : variance._y )
    {
        point = sqrt( point / ( c - 1 ) );
        assert( point >= 0 );
    }

    dat::mutate( [ & ] ( label::Num, dat::Spectrum & s )
    {
        auto src = s._y.begin();
        const auto end = s._y.cend();
        auto m_it = mean._y.cbegin();
        auto v_it = variance._y.cbegin();
        while( src < end )
        {
            if( * v_it < 1e-12  )
            {
                * src = * m_it;
            }
            else
            {
                * src = ( * src - * m_it ) / * v_it;
            }

            ++src;
            ++m_it;
            ++v_it;
        }
    }      , d );
}

Norm::Norm( const dat::Dataset & )
{
}

dat::Dataset Norm::operator()( const dat::Dataset & d ) const
{
    dat::Dataset ret{ d };
    normalize( ret );
    return ret;
}

std::vector< size_t > rank_features( const dat::Dataset & )
{
    return {};
}

#ifdef CMAKE_USE_DLIB
dat::Dataset lda( const dat::Dataset & d )
{
    dlib::matrix< double > samples;
    samples.set_size( static_cast< long >( dat::count( d ) )
                    , dat::Spectrum::_num_points );
    std::vector< unsigned long > labels;
    auto i = 0u;
    dat::apply( [ & ] ( const label::Num l, const dat::Spectrum & s )
        {
            labels.push_back( static_cast< unsigned long >( l ) );
            std::copy( s._y.cbegin()
                     , s._y.cend()
                     , samples.begin() + dat::Spectrum::_num_points * i++ );
        }     , d );
    const auto samples2 = samples;

    dlib::matrix< double, 0, 1 > means;
    dlib::compute_lda_transform( samples, means, labels );

    dlib::matrix< double > ret = samples2 * samples - means; //crash
    std::cout << samples.nc() << ", " << samples.nr() << std::endl;
    std::cout << samples2.nc() << ", " << samples2.nr() << std::endl;
    std::cout << ret.nc() << ", " << ret.nr() << std::endl;

    return {};
}
#endif // CMAKE_USE_DLIB

shark::LinearModel<>
train_encoder( std::vector< shark::RealVector > & inputs
             , unsigned N
             )
{
    const auto dataset{ shark::createUnlabeledDataFromRange( inputs ) };
    shark::PCA pca{ dataset };
    shark::LinearModel<> enc;
    pca.encoder( enc, N );
    return enc;
}

shark::LinearModel<> train_encoder( const dat::Dataset & train
                                  , unsigned N
                                  )
{
    std::vector< shark::RealVector > inputs;
    dat::apply( [ & ] ( auto, const dat::Spectrum & s )
    {
        inputs.push_back( dat::to_shark_vector( s ) );
    }
    , train
    );

    return train_encoder( inputs, N );
}

shark::LinearModel<>
train_encoder( const shark::ClassificationDataset & train
             , unsigned N
             )
{
    std::vector< shark::RealVector > vec;
    for( auto e{ train.elements().begin()}; e < train.elements().end(); ++e )
    {
        vec.push_back( e->input );
    }

    return train_encoder( vec, N );
}

PCA::PCA( const dat::Dataset & train, unsigned dim )
    : _enc{ train_encoder( train, dim ) }
{
}

PCA::PCA( const shark::ClassificationDataset & train, unsigned dim )
    : _enc{ train_encoder( train, dim ) }
{
}

dat::Dataset PCA::operator()( const dat::Dataset & d ) const
{
    if( d.first.empty() )
    {
        return {};
    }

    std::vector< shark::RealVector > inputs;
    std::vector< label::Num > labels;
    dat::apply( [&] ( label::Num l, const dat::Spectrum & s )
    {
        shark::RealVector enc;
        const auto vec{ dat::to_shark_vector( s ) };
        _enc.eval( vec, enc );
        inputs.push_back( std::move( enc ) );
        labels.push_back( l );
    }
              , d );

    const auto sharked{ shark::createLabeledDataFromRange( inputs, labels ) };
    return dat::from_shark_dataset( sharked, d.second );
}

const std::vector< std::string > ALL_PRE{ "log"
                                        , "norm"

#if defined(CMAKE_USE_OPENCV) || defined(CMAKE_USE_SHARK)
                                         , "pca"
#endif
#if defined (CMAKE_USE_OPENCV) || defined (CMAKE_USE_DLIB)
                                        , "lda"
#endif
                                        };

}  // namespace pre


#ifndef AGG_HPP
#define AGG_HPP
//TODO - compilation is slow and produces large binaries due to the debug symbols from BOOST.  Determine
//if tehre is some way to disable those symbols.
extern "C"  {
	#include "postgres.h"
	#include "catalog/pg_type.h"
	#include "utils/array.h"
	#include "fmgr.h"

}
#include "../array.h"

#include <algorithm>
#include <functional>
#include <boost/accumulators/numeric/functional/vector.hpp>

using namespace boost::numeric::operators;

#include <boost/accumulators/accumulators.hpp>

using namespace boost::accumulators;

//////////////////////////////////////////////////////////////////////////////////////
//  Array Aggregation Functions
//  Compute the aggregation across a single array perhaps in the future with support
//  for aggregating along an axis.
/////////////////////////////////////////////////////////////////////////////////////

//Create an aceclerator for the target function and apply on the dataset.
template<class V, class T> static Datum _apply(T* ptr, size_t n) {
  accumulator_set<T, features<V> > acc;
  std::for_each(ptr, ptr + n, std::ref(acc));
  return _ret(extract_result<V>(acc));
}

template<class V> static Datum _op(PG_FUNCTION_ARGS) {
    ArrayType* arr = PG_GETARG_ARRAYTYPE_P(0);
    Oid type  = ARR_ELEMTYPE(arr);
    const size_t n = arr_cnt(arr);
    void* ptr = ARR_DATA_PTR(arr);
    switch(type) {
      case BOOLOID: return _apply<V> ( (bool*)ptr, n);
      case FLOAT4OID: return _apply<V>( (float4*)ptr, n );
      case FLOAT8OID: return _apply<V>( (float8*)ptr, n );
      case INT2OID: return _apply<V>( (int16*)ptr, n );
      case INT4OID: return _apply<V>( (int32*)ptr, n );
      case INT8OID: return _apply<V>( (int64*)ptr, n );
      default:
        elog(ERROR, "Unsupported array type");
        PG_RETURN_NULL();
    }
}

//Define a function for each aggregation which internally has a switch between the basic types.
//This is needed.  Todo - investigate if it is possible to generate more of this using C++ templates
//where the debuging is slightly easier then C macros.
#define ARRAY_AGGREGATE_FNC(NAME, OP)  \
  extern "C" { PG_FUNCTION_INFO_V1(NAME); Datum NAME(PG_FUNCTION_ARGS); } \
  Datum NAME(PG_FUNCTION_ARGS) { return _op<OP>(fcinfo); }

//////////////////////////////////////////////////////////////////////////////////////
//  SQL Aggregation Functions
//  Compute the aggregation across multiple arrays using an SQL group by clause.
//  Normally we would expect that all aggregartes would have both.
/////////////////////////////////////////////////////////////////////////////////////

struct AggInternal  {
  size_t cnt;
  void*  acc;
  ArrayType* (*fnc)(AggInternal*);
};

template<class V, class T> ArrayType* to_array(AggInternal* a) {
    typedef accumulator_set< std::vector<T>, features<V> > Acc_T;

    if (a->cnt == 0) {
      return construct_empty_array(FLOAT4OID);
    } else {
      Acc_T* acc = (Acc_T*)(a->acc);

      //Figure out result type automatically
      auto v = extract_result<V>(*acc);
      Datum* arr = (Datum*)palloc(a->cnt * sizeof(Datum));
      for (unsigned int i = 0; i < a->cnt; ++i) {
        arr[i] = Float4GetDatum((float)v[i]);
      }

      //Now delete the aggregator and set the value to nullptr.
      acc->~Acc_T();
      a->acc = nullptr;

      return construct_array(arr, a->cnt, FLOAT4OID, 4, true, 'i');
    }
}

//Apply takes the vector we have and assuming that the vector isn't empty and matches previous vector sizes
//will apply the accumulator for that vector.
template<class V, class T> Datum _sql_apply(MemoryContext& ctx, AggInternal* state, T* arr_ptr, const size_t n) {
  typedef accumulator_set< std::vector<T>, features<V> > Acc_T;

  if (!state) {
    //Use C memory allocator so that Postgres can manage the memory
    //critical that internal representation have trivial destructor
    //we need to force call the destructor on Acc_T.
    void *acc = MemoryContextAlloc(ctx, sizeof(Acc_T));
    acc = new (acc) Acc_T(std::vector<T>(n));

    state = (AggInternal*)MemoryContextAlloc(ctx, sizeof(AggInternal));
    state->cnt = n;
    state->acc = acc;
    state->fnc = &to_array<V,T>;
  }
  Acc_T* acc = (Acc_T*)(state->acc);

  //special case - if the array is empty allow the aggregation to continue
  //excluding it.
  if (n == 0) {
    PG_RETURN_POINTER(state);
  } else if ( n != state->cnt ) {
    elog(ERROR, "All vectors to be aggregated must have same number of elements");
    PG_RETURN_POINTER(state);
  }

  std::vector<T> v(arr_ptr, arr_ptr+n);
  (*acc)(v);
  PG_RETURN_POINTER(state);
};

template<class V, class T, bool ALL> struct Call {
  Datum operator()(MemoryContext&, AggInternal*, T* ptr, size_t n) { V::unimplimeneted_function; return Datum(); }
};

template<class V, class T> struct Call<V, T, true>{
  Datum operator()(MemoryContext& ctx, AggInternal* state, T* ptr, size_t n) {
    return _sql_apply<V>(ctx, state, ptr, n);
  }
};

template<class V, class T> struct Call<V, T, false>{
  Datum operator()(MemoryContext& ctx, AggInternal* state, T* ptr, size_t n) {
    std::vector<double> v(ptr, ptr+n);
    return _sql_apply<V>(ctx, state, v.data(), n);
  }
};

template<class V, bool ALL> static Datum _sql_op(PG_FUNCTION_ARGS) {
  MemoryContext ctx;
  AggInternal * state = nullptr;

  //Since we don't have strict on the array pointer could be
  //null
  if (PG_ARGISNULL(1)) {
     if (PG_ARGISNULL(0)) { PG_RETURN_NULL(); }
     else { PG_RETURN_POINTER(PG_GETARG_POINTER(0)); }
  }

  ArrayType* arr = PG_GETARG_ARRAYTYPE_P(1);
  Oid type  = ARR_ELEMTYPE(arr);
  const size_t n = arr_cnt(arr);
  void* ptr = ARR_DATA_PTR(arr);

  //Block calls when not in aggregation mode.
  if (!AggCheckCallContext(fcinfo, &ctx)) {
    elog(ERROR,  "Called in non-aggregate context");
    PG_RETURN_NULL();
  }

  //If we have a state variable already return that.
  if (!PG_ARGISNULL(0)) {
    state = (AggInternal*)PG_GETARG_POINTER(0);
  } else if ( n == 0 ) {
     // special case if we don't yet have a state, and the
     // size is 0 then return null
     PG_RETURN_NULL();
  }

  if (state && state->acc == nullptr) {
     elog(ERROR, "Aggregation has already been finalized - cannot continue aggregating");
     PG_RETURN_NULL();
  }

  switch(type) {
      case FLOAT4OID: return Call<V, float, ALL>()( ctx, state, (float4*)ptr, n);
      case FLOAT8OID: return Call<V, double, ALL>()( ctx, state, (float8*)ptr, n);
      case INT2OID: return Call<V, short, ALL>()( ctx, state, (int16*)ptr, n);
      case INT4OID: return Call<V, int, ALL>()( ctx, state, (int32*)ptr, n);
      case INT8OID: return Call<V, long, ALL>()( ctx, state, (int64*)ptr, n);
      default:
         elog(ERROR, "Unsupported Array Type");
         PG_RETURN_NULL();
  }
}

#define SQL_AGGREGATE_FNC(NAME, OP)  \
  extern "C" { PG_FUNCTION_INFO_V1(NAME); Datum NAME(PG_FUNCTION_ARGS); } \
  Datum NAME(PG_FUNCTION_ARGS) { return _sql_op<OP, true>(fcinfo); }

#define SQL_AGGREGATE_DBL_ONLY_FNC(NAME, OP)  \
  extern "C" { PG_FUNCTION_INFO_V1(NAME); Datum NAME(PG_FUNCTION_ARGS); } \
  Datum NAME(PG_FUNCTION_ARGS) { return _sql_op<OP, false>(fcinfo); }

#endif


/*
 * DeltaMeasure.cpp
 *
 *  Created on: 31.08.2017
 *      Author: thies
 */

#include <deal.II/base/logstream.h>
#include <deal.II/base/timer.h>
#include <deal.II/grid/grid_tools.h>

#include <base/ConstantMesh.h>
#include <base/Norm.h>
#include <measurements/DeltaMeasure.h>
#include <measurements/SensorValues.h>

#include <stddef.h>
#include <cmath>
#include <iostream>
#include <string>
#include <vector>

namespace wavepi {
namespace measurements {

using namespace dealii;
using namespace wavepi::base;

template <int dim>
DeltaMeasure<dim>::DeltaMeasure(std::shared_ptr<SpaceTimeMesh<dim>> mesh,
                                std::shared_ptr<SensorDistribution<dim>> points,
                                std::shared_ptr<Norm<DiscretizedFunction<dim>>> norm)
    : mesh(mesh), sensor_distribution(points), norm(norm) {
  AssertThrow(mesh && norm, ExcNotInitialized());
}

template <int dim>
SensorValues<dim> DeltaMeasure<dim>::evaluate(const DiscretizedFunction<dim>& field) {
  AssertThrow(sensor_distribution && sensor_distribution->size(), ExcNotInitialized());
  AssertThrow(mesh == field.get_mesh(), ExcMessage("DeltaMeasure called with different meshes"));
  AssertThrow(*norm == *field.get_norm(), ExcMessage("DeltaMeasure called with different norms"));

  SensorValues<dim> res(sensor_distribution);
  auto mapping = StaticMappingQ1<dim>::mapping;

  if (std::dynamic_pointer_cast<ConstantMesh<dim>, SpaceTimeMesh<dim>>(mesh) &&
      sensor_distribution->times_per_point_available()) {
    // specialized implementation that is ordered by points, not time
    // (most expensive operation is find_active_cell_around_point(...))
    auto dof_handler = mesh->get_dof_handler(0);

    for (size_t mpi = 0; mpi < sensor_distribution->get_points().size(); mpi++) {
      auto pos = sensor_distribution->get_points()[mpi];

      std::pair<typename DoFHandler<dim>::active_cell_iterator, Point<dim>> cell_point =
          GridTools::find_active_cell_around_point(mapping, *dof_handler, pos);

      Quadrature<dim> q(GeometryInfo<dim>::project_to_unit_cell(cell_point.second));
      FEValues<dim> fe_values(mapping, dof_handler->get_fe(), q, UpdateFlags(update_values));
      fe_values.reinit(cell_point.first);

      const unsigned int dofs_per_cell = dof_handler->get_fe().dofs_per_cell;

      std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
      cell_point.first->get_dof_indices(local_dof_indices);

      for (size_t mti = 0; mti < sensor_distribution->get_times_per_point(mpi).size(); mti++) {
        double mtime = sensor_distribution->get_times_per_point(mpi)[mti];
        size_t ti    = mesh->nearest_time(mtime);

        // (φ_i, δ_p) = fe_values.shape_value(i, 0)
        // this loop calculates the dot product between field[ti] and δ_p directly
        for (unsigned int i = 0; i < dofs_per_cell; i++)
          res[sensor_distribution->index_times_per_point(mpi, mti)] +=
              field[ti][local_dof_indices[i]] * fe_values.shape_value(i, 0);
      }
    }
  } else {
    size_t offset = 0;

    for (size_t mti = 0; mti < sensor_distribution->get_times().size(); mti++) {
      double mtime = sensor_distribution->get_times()[mti];

      size_t ti        = mesh->nearest_time(mtime);
      auto dof_handler = mesh->get_dof_handler(ti);

      for (size_t msi = 0; msi < sensor_distribution->get_points_per_time(mti).size(); msi++) {
        auto pos = sensor_distribution->get_points_per_time(mti)[msi];

        std::pair<typename DoFHandler<dim>::active_cell_iterator, Point<dim>> cell_point =
            GridTools::find_active_cell_around_point(mapping, *dof_handler, pos);

        Quadrature<dim> q(GeometryInfo<dim>::project_to_unit_cell(cell_point.second));
        FEValues<dim> fe_values(mapping, dof_handler->get_fe(), q, UpdateFlags(update_values));
        fe_values.reinit(cell_point.first);

        const unsigned int dofs_per_cell = dof_handler->get_fe().dofs_per_cell;

        std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
        cell_point.first->get_dof_indices(local_dof_indices);

        // (φ_i, δ_p) = fe_values.shape_value(i, 0)
        // this loop calculates the dot product between field[ti] and δ_p directly
        for (unsigned int i = 0; i < dofs_per_cell; i++)
          res[msi + offset] += field[ti][local_dof_indices[i]] * fe_values.shape_value(i, 0);
      }

      offset += sensor_distribution->get_points_per_time(mti).size();
    }
  }

  return res;
}

template <int dim>
SensorValues<dim> DeltaMeasure<dim>::zero() {
  return SensorValues<dim>(sensor_distribution);
}

template <int dim>
DiscretizedFunction<dim> DeltaMeasure<dim>::adjoint(const SensorValues<dim>& measurements) {
  AssertThrow(mesh && sensor_distribution && sensor_distribution->size(), ExcNotInitialized());

  DiscretizedFunction<dim> res(mesh);
  auto mapping  = StaticMappingQ1<dim>::mapping;
  size_t offset = 0;

  if (std::dynamic_pointer_cast<ConstantMesh<dim>, SpaceTimeMesh<dim>>(mesh) &&
      sensor_distribution->times_per_point_available()) {
    // specialized implementation that is ordered by points, not time
    // (most expensive operation is find_active_cell_around_point(...))
    auto dof_handler = mesh->get_dof_handler(0);

    for (size_t mpi = 0; mpi < sensor_distribution->get_points().size(); mpi++) {
      auto pos = sensor_distribution->get_points()[mpi];

      std::pair<typename DoFHandler<dim>::active_cell_iterator, Point<dim>> cell_point =
          GridTools::find_active_cell_around_point(mapping, *dof_handler, pos);

      Quadrature<dim> q(GeometryInfo<dim>::project_to_unit_cell(cell_point.second));
      FEValues<dim> fe_values(mapping, dof_handler->get_fe(), q, UpdateFlags(update_values));
      fe_values.reinit(cell_point.first);

      const unsigned int dofs_per_cell = dof_handler->get_fe().dofs_per_cell;

      std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
      cell_point.first->get_dof_indices(local_dof_indices);

      for (size_t mti = 0; mti < sensor_distribution->get_times_per_point(mpi).size(); mti++) {
        double mtime = sensor_distribution->get_times_per_point(mpi)[mti];
        size_t ti    = mesh->nearest_time(mtime);

        // (φ_i, δ_p) = fe_values.shape_value(i, 0)
        // this loop calculates the dot product between field[ti] and δ_p directly
        for (unsigned int i = 0; i < dofs_per_cell; i++)
          res[ti][local_dof_indices[i]] +=
              measurements[sensor_distribution->index_times_per_point(mpi, mti)] * fe_values.shape_value(i, 0);
      }
    }
  } else {
    for (size_t mti = 0; mti < sensor_distribution->get_times().size(); mti++) {
      double mtime = sensor_distribution->get_times()[mti];

      size_t ti        = mesh->nearest_time(mtime);
      auto dof_handler = mesh->get_dof_handler(ti);

      for (size_t msi = 0; msi < sensor_distribution->get_points_per_time(mti).size(); msi++) {
        auto pos = sensor_distribution->get_points_per_time(mti)[msi];

        std::pair<typename DoFHandler<dim>::active_cell_iterator, Point<dim>> cell_point =
            GridTools::find_active_cell_around_point(mapping, *dof_handler, pos);

        Quadrature<dim> q(GeometryInfo<dim>::project_to_unit_cell(cell_point.second));
        FEValues<dim> fe_values(mapping, dof_handler->get_fe(), q, UpdateFlags(update_values));
        fe_values.reinit(cell_point.first);

        const unsigned int dofs_per_cell = dof_handler->get_fe().dofs_per_cell;

        std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
        cell_point.first->get_dof_indices(local_dof_indices);

        // (φ_i, δ_p) = fe_values.shape_value(i, 0)
        // this loop calculates dot products, not coefficients.
        for (unsigned int i = 0; i < dofs_per_cell; i++)
          res[ti][local_dof_indices[i]] += measurements[msi + offset] * fe_values.shape_value(i, 0);
      }

      offset += sensor_distribution->get_points_per_time(mti).size();
    }
  }

  // indicate which norm we used for the adjoint
  res.set_norm(norm);
  res.dot_transform_inverse();

  return res;
}

template class DeltaMeasure<1>;
template class DeltaMeasure<2>;
template class DeltaMeasure<3>;

}  // namespace measurements
}  // namespace wavepi


// This file is part of LatNet Builder.
//
// Copyright (C) 2012-2021  The LatNet Builder author's, supervised by Pierre L'Ecuyer, Universite de Montreal.
//
// 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.

#include "netbuilder/NetConstructionTraits.h"

#include "latbuilder/GenSeq/GeneratingValues-PLR.h"
#include "latbuilder/SeqCombiner.h"
#include "latbuilder/Util.h"

#include <NTL/GF2X.h>
#include <sstream>
#include <boost/algorithm/string/erase.hpp>

#include "latticetester/ntlwrap.h"

namespace NetBuilder {

    const std::string NetConstructionTraits<NetConstruction::POLYNOMIAL>::name = "Polynomial";

    typedef typename NetConstructionTraits<NetConstruction::POLYNOMIAL>::GenValue GenValue;

    typedef typename NetConstructionTraits<NetConstruction::POLYNOMIAL>::SizeParameter SizeParameter;

    bool NetConstructionTraits<NetConstruction::POLYNOMIAL>::checkGenValue(const GenValue& genValue, const SizeParameter& sizeParameter)
    {
        return IsOne(GCD(genValue,sizeParameter));
    }

    unsigned int NetConstructionTraits<NetConstruction::POLYNOMIAL>::nRows(const SizeParameter& sizeParameter) {return (unsigned int) deg(sizeParameter); }

    unsigned int NetConstructionTraits<NetConstruction::POLYNOMIAL>::nCols(const SizeParameter& sizeParameter) {return (unsigned int) deg(sizeParameter); }

    void expandSeries(const GenValue& genValue, const SizeParameter& sizeParameter, std::vector<unsigned int>& expansion, unsigned int expansion_limit){
        int m = (int) deg(sizeParameter);
        for(int l = 1; l<= (int) expansion_limit ; l++){
            int res =  (m-l >=0 && IsOne(coeff(genValue, m-l)))? 1 : 0;
            int start = (l-m > 1) ? (l-m) : 1;
            for( int p = start; p < l; p++){
                res = ( res + expansion[p-1] * NTL::conv<int>(coeff(sizeParameter, m-(l-p)))) %2;
            }
            expansion[l-1] = res;
        }
    }

    GeneratingMatrix*  NetConstructionTraits<NetConstruction::POLYNOMIAL>::createGeneratingMatrix(const GenValue& genValue, const SizeParameter& sizeParameter, const Dimension& dimension_j, const unsigned int nRows)
    {
        unsigned int m = (unsigned int) (deg(sizeParameter));
        unsigned int finalnRows = (nRows == 0)? m : nRows;
        GeneratingMatrix* genMat = new GeneratingMatrix(finalnRows, m);
        std::vector<unsigned int> expansion(finalnRows + m);
        expandSeries(genValue, sizeParameter, expansion, finalnRows + m);
        for(unsigned int c = 0; c < m; c++)
        {
            for(unsigned int row = 0; row < finalnRows; row++)
            {
                (*genMat)(row,c) = expansion[c + row];
            }
        }
        return genMat;
    }

    typename NetConstructionTraits<NetConstruction::POLYNOMIAL>::GenValueSpaceCoordSeq NetConstructionTraits<NetConstruction::POLYNOMIAL>::genValueSpaceCoord(Dimension coord, const SizeParameter& sizeParameter)
    {
        if (coord==0)
        {
            Polynomial fakeModulus;
            SetX(fakeModulus);
            return GenValueSpaceCoordSeq(fakeModulus);
        }
        else
        {
           return GenValueSpaceCoordSeq(sizeParameter);
        }
    }

    typename NetConstructionTraits<NetConstruction::POLYNOMIAL>::GenValueSpaceSeq NetConstructionTraits<NetConstruction::POLYNOMIAL>::genValueSpace(Dimension dimension, const SizeParameter& sizeParameter)
    {
        std::vector<GenValueSpaceCoordSeq> seqs;
        seqs.reserve(dimension);
        for(Dimension coord = 0; coord < dimension; ++coord)
        {
            seqs.push_back(genValueSpaceCoord(coord, sizeParameter));
        }
        return GenValueSpaceSeq(seqs);
    }

    std::string NetConstructionTraits<NetConstruction::POLYNOMIAL>::format(const std::vector<std::shared_ptr<GeneratingMatrix>>& genMatrices, const std::vector<std::shared_ptr<GenValue>>& genVals, const SizeParameter& sizeParameter, OutputStyle outputStyle, unsigned int interlacingFactor)
    {
        std::string res;
        std::ostringstream stream;

        if (outputStyle == OutputStyle::TERMINAL){
            stream << "Polynomial Digital Net - Modulus = " << LatBuilder::IndexOfPolynomial(sizeParameter) << " - GeneratingVector =" << std::endl;
            for (unsigned int coord = 0; coord < genVals.size(); coord++){
                stream << "  " << LatBuilder::IndexOfPolynomial(*(genVals[coord])) << std::endl;
            }
        }

        else if (outputStyle == OutputStyle::LATTICE){
            stream << "# Parameters for a polynomial lattice rule in base 2" << std::endl;
            stream << genVals.size() / interlacingFactor << "      #  s =  " << genVals.size() / interlacingFactor << " dimensions" << std::endl;
            if (interlacingFactor > 1){
                stream << interlacingFactor << "    # Interlacing factor" << std::endl;
                stream << genVals.size() << "    # Number of components = interlacing factor x dimension" << std::endl;
            }
            stream << (int) deg(sizeParameter) << "      # n = 2^";
            stream <<  (int) deg(sizeParameter) << " = " << (int)pow(2,deg(sizeParameter) ) << " points"<< std::endl;

            stream << LatBuilder::IndexOfPolynomial(sizeParameter) << "   # polynomial modulus" << std::endl;
            stream << "# Coordinates of generating vector, starting at j=1" << std::endl;
            std::string res;
            for (unsigned int coord = 0; coord < genVals.size(); coord++){
                res += std::to_string(LatBuilder::IndexOfPolynomial(*(genVals[coord]))) + "\n";
            }
            res.pop_back();
            stream << res;
        }

        res += stream.str();
        return res;
    }
}


// Boost.Geometry

// Copyright (c) 2015-2016 Oracle and/or its affiliates.

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_FORMULAS_ANDOYER_INVERSE_HPP
#define BOOST_GEOMETRY_FORMULAS_ANDOYER_INVERSE_HPP

#include <boost/math/constants/constants.hpp>

#include <boost/geometry/core/radius.hpp>
#include <boost/geometry/core/srs.hpp>

#include <boost/geometry/util/condition.hpp>
#include <boost/geometry/util/math.hpp>

#include <boost/geometry/algorithms/detail/flattening.hpp>

#include <boost/geometry/formulas/differential_quantities.hpp>
#include <boost/geometry/formulas/result_inverse.hpp>

namespace boost { namespace geometry { namespace formula
{

/*!
\brief The solution of the inverse problem of geodesics on latlong coordinates,
       Forsyth-Andoyer-Lambert type approximation with first order terms.
\author See
    - Technical Report: PAUL D. THOMAS, MATHEMATICAL MODELS FOR NAVIGATION SYSTEMS, 1965
      http://www.dtic.mil/docs/citations/AD0627893
    - Technical Report: PAUL D. THOMAS, SPHEROIDAL GEODESICS, REFERENCE SYSTEMS, AND LOCAL GEOMETRY, 1970
      http://www.dtic.mil/docs/citations/AD703541
*/
template <
    typename CT,
    bool EnableDistance,
    bool EnableAzimuth,
    bool EnableReverseAzimuth = false,
    bool EnableReducedLength = false,
    bool EnableGeodesicScale = false
>
class andoyer_inverse
{
    static const bool CalcQuantities = EnableReducedLength || EnableGeodesicScale;
    static const bool CalcAzimuths = EnableAzimuth || EnableReverseAzimuth || CalcQuantities;
    static const bool CalcFwdAzimuth = EnableAzimuth || CalcQuantities;
    static const bool CalcRevAzimuth = EnableReverseAzimuth || CalcQuantities;

public:
    typedef result_inverse<CT> result_type;

    template <typename T1, typename T2, typename Spheroid>
    static inline result_type apply(T1 const& lon1,
                                    T1 const& lat1,
                                    T2 const& lon2,
                                    T2 const& lat2,
                                    Spheroid const& spheroid)
    {
        result_type result;

        // coordinates in radians

        if ( math::equals(lon1, lon2) && math::equals(lat1, lat2) )
        {
            return result;
        }

        CT const c0 = CT(0);
        CT const c1 = CT(1);
        CT const pi = math::pi<CT>();
        CT const f = detail::flattening<CT>(spheroid);

        CT const dlon = lon2 - lon1;
        CT const sin_dlon = sin(dlon);
        CT const cos_dlon = cos(dlon);
        CT const sin_lat1 = sin(lat1);
        CT const cos_lat1 = cos(lat1);
        CT const sin_lat2 = sin(lat2);
        CT const cos_lat2 = cos(lat2);

        // H,G,T = infinity if cos_d = 1 or cos_d = -1
        // lat1 == +-90 && lat2 == +-90
        // lat1 == lat2 && lon1 == lon2
        CT cos_d = sin_lat1*sin_lat2 + cos_lat1*cos_lat2*cos_dlon;
        // on some platforms cos_d may be outside valid range
        if (cos_d < -c1)
            cos_d = -c1;
        else if (cos_d > c1)
            cos_d = c1;

        CT const d = acos(cos_d); // [0, pi]
        CT const sin_d = sin(d);  // [-1, 1]

        if ( BOOST_GEOMETRY_CONDITION(EnableDistance) )
        {
            CT const K = math::sqr(sin_lat1-sin_lat2);
            CT const L = math::sqr(sin_lat1+sin_lat2);
            CT const three_sin_d = CT(3) * sin_d;

            CT const one_minus_cos_d = c1 - cos_d;
            CT const one_plus_cos_d = c1 + cos_d;
            // cos_d = 1 or cos_d = -1 means that the points are antipodal

            CT const H = math::equals(one_minus_cos_d, c0) ?
                            c0 :
                            (d + three_sin_d) / one_minus_cos_d;
            CT const G = math::equals(one_plus_cos_d, c0) ?
                            c0 :
                            (d - three_sin_d) / one_plus_cos_d;

            CT const dd = -(f/CT(4))*(H*K+G*L);

            CT const a = get_radius<0>(spheroid);

            result.distance = a * (d + dd);
        }

        if ( BOOST_GEOMETRY_CONDITION(CalcAzimuths) )
        {
            // sin_d = 0 <=> antipodal points
            if (math::equals(sin_d, c0))
            {
                // T = inf
                // dA = inf
                // azimuth = -inf
                result.azimuth = lat1 <= lat2 ? c0 : pi;
            }
            else
            {
                CT const c2 = CT(2);

                CT A = c0;
                CT U = c0;
                if ( ! math::equals(cos_lat2, c0) )
                {
                    CT const tan_lat2 = sin_lat2/cos_lat2;
                    CT const M = cos_lat1*tan_lat2-sin_lat1*cos_dlon;
                    A = atan2(sin_dlon, M);
                    CT const sin_2A = sin(c2*A);
                    U = (f/ c2)*math::sqr(cos_lat1)*sin_2A;
                }

                CT B = c0;
                CT V = c0;
                if ( ! math::equals(cos_lat1, c0) )
                {
                    CT const tan_lat1 = sin_lat1/cos_lat1;
                    CT const N = cos_lat2*tan_lat1-sin_lat2*cos_dlon;
                    B = atan2(sin_dlon, N);
                    CT const sin_2B = sin(c2*B);
                    V = (f/ c2)*math::sqr(cos_lat2)*sin_2B;
                }

                CT const T = d / sin_d;

                // even with sin_d == 0 checked above if the second point
                // is somewhere in the antipodal area T may still be great
                // therefore dA and dB may be great and the resulting azimuths
                // may be some more or less arbitrary angles

                if (BOOST_GEOMETRY_CONDITION(CalcFwdAzimuth))
                {
                    CT const dA = V*T - U;
                    result.azimuth = A - dA;
                    normalize_azimuth(result.azimuth, A, dA);
                }

                if (BOOST_GEOMETRY_CONDITION(CalcRevAzimuth))
                {
                    CT const dB = -U*T + V;
                    result.reverse_azimuth = pi - B - dB;
                    if (result.reverse_azimuth > pi)
                    {
                        result.reverse_azimuth -= 2 * pi;
                    }
                    normalize_azimuth(result.reverse_azimuth, B, dB);
                }
            }
        }

        if (BOOST_GEOMETRY_CONDITION(CalcQuantities))
        {
            typedef differential_quantities<CT, EnableReducedLength, EnableGeodesicScale, 1> quantities;
            quantities::apply(dlon, sin_lat1, cos_lat1, sin_lat2, cos_lat2,
                              result.azimuth, result.reverse_azimuth,
                              get_radius<2>(spheroid), f,
                              result.reduced_length, result.geodesic_scale);
        }

        return result;
    }

private:
    static inline void normalize_azimuth(CT & azimuth, CT const& A, CT const& dA)
    {
        CT const c0 = 0;

        if (A >= c0) // A indicates Eastern hemisphere
        {
            if (dA >= c0) // A altered towards 0
            {
                if (azimuth < c0)
                {
                    azimuth = c0;
                }
            }
            else // dA < 0, A altered towards pi
            {
                CT const pi = math::pi<CT>();
                if (azimuth > pi)
                {
                    azimuth = pi;
                }
            }
        }
        else // A indicates Western hemisphere
        {
            if (dA <= c0) // A altered towards 0
            {
                if (azimuth > c0)
                {
                    azimuth = c0;
                }
            }
            else // dA > 0, A altered towards -pi
            {
                CT const minus_pi = -math::pi<CT>();
                if (azimuth < minus_pi)
                {
                    azimuth = minus_pi;
                }
            }
        }
    }
};

}}} // namespace boost::geometry::formula

#endif // BOOST_GEOMETRY_FORMULAS_ANDOYER_INVERSE_HPP


#include <vector>
#include <type_traits>

#include <boost/range/adaptor/transformed.hpp>
namespace ba = boost::adaptors;

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/functional.h>
namespace py = pybind11;

#include <dionysus/row-reduction.h>
#include <dionysus/ordinary-persistence.h>
#include <dionysus/standard-reduction.h>

#include "filtration.h"
#include "persistence.h"                // to get access to PyReducedMatrix::Chain
#include "zigzag-persistence.h"
#include "diagram.h"
#include "progress.h"

PYBIND11_MAKE_OPAQUE(PyReducedMatrix::Chain);      // we want to provide our own binding for Chain

struct Time
{
            Time(float t_, size_t i_, unsigned dim_, bool dir_):
                t(t_), i(i_), dim(dim_), dir(dir_)             {}

    bool    operator<(const Time& other) const
    {
        if (t == other.t)
        {
            if (dir && !other.dir)      // add comes before remove
                return true;
            else if (!dir && other.dir)
                return false;
            else if (dim == other.dim)
                return i < other.i;
            else if (dir)
                return dim < other.dim;
            else // if (!dir)
                return other.dim < dim;
        }
        else
            return t < other.t;
    }

    float       t;
    size_t      i;
    unsigned    dim;
    bool        dir;
};

class PyTimeIndexMap
{
    public:
        using ZZIndex           = PyZigzagPersistence::Index;
        using FIndex            = size_t;
        using Map               = std::unordered_map<ZZIndex, FIndex>;
        using const_iterator    = Map::const_iterator;

        void                    set(const ZZIndex& x, const FIndex& y)  { m_[x] = y; }
        void                    remove(const ZZIndex& x)                { m_.erase(x); }

        FIndex                  operator[](const ZZIndex& x) const      { return m_.find(x)->second; }
        size_t                  size() const                            { return m_.size(); }

        const_iterator          begin() const                           { return m_.begin(); }
        const_iterator          end() const                             { return m_.end(); }

    private:
        Map     m_;
};

using Times     = std::vector<std::vector<float>>;
using Callback  = std::function<void(size_t, float, bool, const PyZigzagPersistence*, const PyTimeIndexMap*)>;

std::tuple<PyZigzagPersistence, std::vector<PyDiagram>, PyTimeIndexMap>
zigzag_homology_persistence(const PyFiltration&     f,
                            const Times&            times_,
                            PyZpField::Element      prime,
                            const Callback&         callback,
                            bool                    show_progress)
{
    using Index          = PyZigzagPersistence::Index;
    using CellChainEntry = dionysus::ChainEntry<PyZpField, PySimplex>;
    using ChainEntry     = dionysus::ChainEntry<PyZpField, Index>;

    std::vector<Time> times;
    for (size_t i = 0; i < times_.size(); ++i)
    {
        int dim = f[i].dimension();
        bool dir = true;
        for (float t : times_[i])
        {
            times.emplace_back(t, i, dim, dir);
            dir = !dir;
        }
    }
    std::sort(times.begin(), times.end());

    std::unique_ptr<Progress> progress(new NoProgress);
    if (show_progress)
        progress = std::unique_ptr<Progress>(new ShowProgress(times.size()));

    std::vector<PyDiagram> diagrams;
    PyZpField field(prime);
    PyZigzagPersistence persistence(field);
    unsigned op = 0;
    unsigned cell = 0;
    std::vector<unsigned>   cells(f.size(), -1);
    PyTimeIndexMap          cells_inv_;
    for (auto& tt : times)
    {
        (*progress)();

        size_t i = tt.i; float t = tt.t; bool dir = tt.dir;

        auto& c = f[i];
        if (dir)
        {
            cells_inv_.set(cell, i);
            cells[i] = cell++;

            Index pair = persistence.add(c.boundary(persistence.field()) |
                                                    ba::transformed([&](const CellChainEntry& e)
                                                    {
                                                        auto idx = f.index(e.index());
                                                        return ChainEntry(e.element(), cells[idx]);
                                                    }));

            if (pair != persistence.unpaired())
            {
                auto t_birth = times[pair].t;
                if (t_birth != t)
                {
                    int dim = c.dimension()-1;
                    while (dim+1 > diagrams.size())
                        diagrams.emplace_back();
                    diagrams[dim].emplace_back(t_birth, t, pair);
                }
            }

            ++op;
        } else
        {
            Index pair = persistence.remove(cells[i]);
            cells_inv_.remove(cells[i]);
            cells[i] = -1;
            if (pair != persistence.unpaired())
            {
                auto t_birth = times[pair].t;
                if (t_birth != t)
                {
                    int dim = c.dimension();
                    while (dim+1 > diagrams.size())
                        diagrams.emplace_back();
                    diagrams[dim].emplace_back(t_birth, t, pair);
                }
            }
            ++op;
        }

        callback(i,t,dir,&persistence,&cells_inv_);
    }

    // add infinite points
    constexpr float inf = std::numeric_limits<float>::infinity();
    for (auto& birth_idx : persistence.alive_ops())
    {
        auto i_birth   = times[birth_idx].i;
        auto t_birth   = times[birth_idx].t;
        auto dir_birth = times[birth_idx].dir;

        int dim = f[i_birth].dimension();
        if (!dir_birth)     // born on removal
            dim -= 1;
        while (dim+1 > diagrams.size())
            diagrams.emplace_back();
        diagrams[dim].emplace_back(t_birth, inf, birth_idx);
    }

    return std::make_tuple(std::move(persistence), std::move(diagrams), std::move(cells_inv_));
}

// custom iterator that preserves right filtration order
struct PyZZAliveCycleIterator
{
            PyZZAliveCycleIterator(const PyZigzagPersistence& zz_, py::object ref_):
                zz(zz_), ref(ref_)
    {
        for (PyZigzagPersistence::Index x : zz.alive_cycles())
            indices.push_back(x);
        std::sort(indices.begin(), indices.end());
    }

    PyReducedMatrix::Chain  next()
    {
        if (idx == indices.size())
            throw py::stop_iteration();

        PyReducedMatrix::Chain c;
        for (auto& x : zz.cycle(indices[idx]))
            c.emplace_back(x.element(), std::get<0>(x.index()));
        ++idx;
        return c;
    }

    const PyZigzagPersistence&                  zz;
    py::object                                  ref;
    size_t                                      idx = 0;
    std::vector<PyZigzagPersistence::Index>     indices;
};

#include "chain.h"

void init_zigzag_persistence(py::module& m)
{
    using namespace pybind11::literals;
    m.def("zigzag_homology_persistence",   &zigzag_homology_persistence, "filtration"_a, "times"_a, "prime"_a = 2,
                                                                         "callback"_a = Callback([](size_t, float, bool, const PyZigzagPersistence*, const PyTimeIndexMap*){}),
                                                                         "progress"_a = false,
          R"(
          compute zigzag homology persistence of the filtration with respect to the given times

          Args:
              filtration: an instance of :class:`~dionysus._dionysus.Filtration` with the set of simplices used in the zigzag construction
              times:      a list of lists; the outer list runs parallel with the filtration;
                          the inner list specifies for each simplex when it enters and leaves the zigzag
                          (even entries, starting the indexing from 0, are interpreted as appearance times, odd entires as disappearance)
              prime:      prime modulo which to perform computation
              callback:   function to call after every step in the zigzag; it gets arguments `(i,t,d,zz,cells)`,
                          where `i` is the index of the simplex being added or removed, `t` is the time,
                          `d` is the "direction" (`True` if the simplex is being added, `False` if it`s being removed),
                          `zz` is the current state of the :class:`~dionysus._dionysus.ZigzagPersistence`,
                          `cells` is the map from the internal indices of the zigzag representation to the filtration indices,
              progress:   show a progress bar.

          Returns:
              A triple. The first element is an instance of
              :class:`~dionysus._dionysus.ZigzagPersistence`, which offers access to the cycles
              alive at the end of the zigzag; the second is a list of
              persistence diagrams; the third is an instance of
              :class:`~dionysus._dionysus.TimeIndexMap` for translating cycles
              from the internal representation to filtration indices.
          )");

   py::class_<PyZZAliveCycleIterator>(m, "ZZAliveCycleIterator")
        .def("__iter__", [](PyZZAliveCycleIterator& it) -> PyZZAliveCycleIterator& { return it; })
        .def("__next__", &PyZZAliveCycleIterator::next);

    py::class_<PyZigzagPersistence>(m, "ZigzagPersistence", "representation of the current homology basis")
        .def(py::init<PyZpField>())
        .def("__len__",     &PyZigzagPersistence::alive_size,       "number of alive cycles")
        .def("__iter__",    [](py::object zz)   { return PyZZAliveCycleIterator(zz.cast<const PyZigzagPersistence&>(), zz); }, "iterator over the alive cycles")
        .def("__repr__",    [](const PyZigzagPersistence& zz)
                            { std::ostringstream oss; oss << "Zigzag persistence with " << zz.alive_size() << " alive cycles"; return oss.str(); })
    ;

    py::class_<PyTimeIndexMap>(m, "TimeIndexMap", "map from the internal representation of zigzag persistence to filtration indices")
        .def("__len__",     &PyTimeIndexMap::size,                      "size of the map")
        .def("__getitem__", [](const PyTimeIndexMap& m, size_t i) { return m[i]; }, "access the filtration index of the given internal index")
        .def("__iter__",    [](const PyTimeIndexMap& m) { return py::make_iterator(m.begin(), m.end()); },
                                py::keep_alive<0, 1>() /* Essential: keep object alive while iterator exists */,
                                "iterate over the entries of the map")
    ;
}


#include "TensorLines.hh"

#include "TensorProductBezierTriangles.hh"
#include "TensorCoreLinesEvaluator.hh"
#include "TensorTopologyEvaluator.hh"
#include "ParallelEigenvectorsEvaluator.hh"

#include <Eigen/Eigenvalues>
#include <Eigen/LU>

#include <boost/range/algorithm/min_element.hpp>
#include <boost/range/algorithm_ext/insert.hpp>
#include <boost/optional.hpp>

#include <stack>
#include <queue>
#include <iterator>
#include <complex>
#include <type_traits>

using namespace cpp_utils;

namespace
{
using namespace tl;

/**
 * Representative Solution in a cluster of similar solutions
 */
template <typename Evaluator>
struct ClusterRepr
{
    std::size_t cluster_size;
    Evaluator eval;
};

/**
 * Cluster all triangles in a list that are closer than a given distance
 *
 * @param cands List of candidates generated by parallelEigenvectorSearch()
 * @param epsilon Maximum distance of triangles in a cluster
 *
 * @return List of clusters (each cluster is a list of candidates)
 */
template <typename CandList>
std::vector<CandList>
clusterTris(const CandList& cands, double epsilon)
{
    auto classes = std::vector<CandList>{};
    for(const auto& t : cands)
    {
        classes.push_back({t});
    }

    auto has_close_elements = [&](const CandList& c1,
                                  const CandList& c2) {
        if(c1 == c2) return false;
        for(const auto& t1 : c1)
        {
            for(const auto& t2 : c2)
            {
                if(distance(t1, t2) <= epsilon)
                {
                    return true;
                }
            }
        }
        return false;
    };

    auto changed = true;
    while(changed)
    {
        changed = false;
        for(auto it = std::begin(classes); it != std::end(classes); ++it)
        {
            auto jt = it;
            ++jt;
            for(; jt != std::end(classes); ++jt)
            {
                if(has_close_elements(*it, *jt))
                {
                    it->insert(std::end(*it), std::begin(*jt), std::end(*jt));
                    jt = classes.erase(jt);
                    --jt;
                    changed = true;
                }
            }
        }
    }
    return classes;
}

/**
 * @brief Select representative solutions for each cluster.
 * @details For each cluster, the solution candidate with the smallest error
 *      estimate is chosen as a representative. Also returns the number of
 *      elements in the cluster.
 *
 * @param clusters Vector of vectors of Evaluators representing solution
 *     clusters as produced by clusterTris().
 * @return A cluster representative for each input cluster.
 */
template <typename Evaluator>
std::vector<ClusterRepr<Evaluator>>
findRepresentatives(const std::vector<std::vector<Evaluator>>& clusters)
{
    static_assert(is_evaluator_v<Evaluator>,
                  "findRepresentatives requires a valid Evaluator!");
    auto result = std::vector<ClusterRepr<Evaluator>>{};
    for(const auto& c : clusters)
    {
        using namespace boost;
        using namespace boost::adaptors;
        result.push_back(
                {c.size(),
                 *min_element(c, [](const auto& c1, const auto& c2) {
                     return c1.error() < c2.error();
                 })});
    }
    return result;
}

/**
 * @brief Compute context info for representatives
 * @details Computes global point position, eigenvalue order, presence of other
 *      imaginary eigenvalues, and packs into result list together with point
 *      position, eigenvector direction, eigenvalues.
 *
 * @param representatives TriPairs selected by findRepresentatives()
 * @param s_interp First tensor field on the triangle
 * @param t_interp Second tensor field on the triangle
 * @param tri Spatial triangle
 * @return List of TLPoints with context info
 */
PointList
computeContextInfoPEV(
        const std::vector<
            ClusterRepr<ParallelEigenvectorsEvaluator>>& representatives,
        const TensorInterp& s_interp,
        const TensorInterp& t_interp,
        const Triangle& tri)
{
    auto points = PointList{};
    points.reserve(representatives.size());

    for(const auto& r : representatives)
    {
        const auto& pos_tri = r.eval.tris().pos_tri;
        const auto& dir_tri = r.eval.tris().dir_tri;

        auto result_center = pos_tri({1. / 3., 1. / 3., 1. / 3.});
        auto result_dir = dir_tri({1. / 3., 1. / 3., 1. / 3.}).normalized();

        // We want to know which eigenvector of each tensor field we have
        // found (i.e. corresponding to largest, middle, or smallest
        // eigenvalue)
        // Therefore we explicitly compute the eigenvalues at the result
        // position and check which ones the found eigenvector direction
        // corresponds to.
        // @todo: make this step optional

        auto s = s_interp(result_center);
        auto t = t_interp(result_center);

        // Get eigenvalues from our computed direction
        auto s_real_eigv = (s * result_dir).dot(result_dir);
        auto t_real_eigv = (t * result_dir).dot(result_dir);

        // Compute all eigenvalues using Eigen
        auto s_eigvs = s.eigenvalues().eval();
        auto t_eigvs = t.eigenvalues().eval();

        // Find index of eigenvalue that is closest to the one we computed
        using Vec3c = decltype(s_eigvs);
        auto s_closest_index = Vec3d::Index{0};
        (s_eigvs - Vec3c::Ones() * s_real_eigv)
                .cwiseAbs()
                .minCoeff(&s_closest_index);

        auto t_closest_index = Vec3d::Index{0};
        (t_eigvs - Vec3c::Ones() * t_real_eigv)
                .cwiseAbs()
                .minCoeff(&t_closest_index);

        // Find which of the (real) eigenvalues ours is
        auto count_larger_real = [](double ref,
                                    const std::complex<double>& val) {
            if(val.imag() != 0) return 0;
            if(std::abs(ref) >= std::abs(val.real())) return 0;
            return 1;
        };
        auto s_order = s_eigvs.unaryExpr([&](const std::complex<double>& val) {
                                  return count_larger_real(
                                          s_eigvs[s_closest_index].real(), val);
                              })
                               .sum();
        auto t_order = t_eigvs.unaryExpr([&](const std::complex<double>& val) {
                                  return count_larger_real(
                                          t_eigvs[t_closest_index].real(), val);
                              })
                               .sum();

        points.push_back(
                TLPoint{tri(result_center),
                         ERank(s_order),
                         ERank(t_order),
                         result_dir,
                         s_real_eigv,
                         t_real_eigv,
                         s_eigvs.sum().imag() != 0,
                         t_eigvs.sum().imag() != 0,
                         r.cluster_size,
                         (pos_tri[1] - pos_tri[0]).norm(),
                         (dir_tri[1] - dir_tri[0]).norm(),
                         0.});
    }
    return points;
}

PointList
computeContextInfoTCL(
        const std::vector<ClusterRepr<TensorCoreLinesEvaluator>>& representatives,
        const TensorInterp& t_interp,
        const TensorInterp& tx_interp,
        const TensorInterp& ty_interp,
        const TensorInterp& tz_interp,
        const Triangle& tri)
{
    auto points = PointList{};
    points.reserve(representatives.size());

    for(const auto& r : representatives)
    {
        const auto& pos_tri = r.eval.tris().pos_tri;
        const auto& dir_tri = r.eval.tris().dir_tri;

        auto result_center = pos_tri({1. / 3., 1. / 3., 1. / 3.});
        auto result_dir = dir_tri({1. / 3., 1. / 3., 1. / 3.}).normalized();

        // We want to know which eigenvector of each tensor field we have
        // found (i.e. corresponding to largest, middle, or smallest
        // eigenvalue)
        // Therefore we explicitly compute the eigenvalues at the result
        // position and check which ones the found eigenvector direction
        // corresponds to.
        // @todo: make this step optional

        auto t = t_interp(result_center);
        auto tx = tx_interp(result_center);
        auto ty = ty_interp(result_center);
        auto tz = tz_interp(result_center);
        auto dt = (tx * result_dir[0] + ty * result_dir[1] + tz * result_dir[2])
                          .eval();

        // Get eigenvalues from our computed direction
        auto t_real_eigv = (t * result_dir).dot(result_dir);
        auto dt_real_eigv = (dt * result_dir).dot(result_dir);

        // Compute all eigenvalues using Eigen
        auto t_eigvs = t.eigenvalues().eval();
        auto dt_eigvs = dt.eigenvalues().eval();

        // Find index of eigenvalue that is closest to the one we computed
        using Vec3c = decltype(t_eigvs);
        auto t_closest_index = Vec3d::Index{0};
        (t_eigvs - Vec3c::Ones() * t_real_eigv)
                .cwiseAbs()
                .minCoeff(&t_closest_index);

        auto dt_closest_index = Vec3d::Index{0};
        (dt_eigvs - Vec3c::Ones() * dt_real_eigv)
                .cwiseAbs()
                .minCoeff(&dt_closest_index);

        // Find which of the (real) eigenvalues ours is
        auto count_larger_real = [](double ref,
                                    const std::complex<double>& val) {
            if(val.imag() != 0) return 0;
            if(std::abs(ref) >= std::abs(val.real())) return 0;
            return 1;
        };
        auto t_order = t_eigvs.unaryExpr([&](const std::complex<double>& val) {
                                  return count_larger_real(
                                          t_eigvs[t_closest_index].real(), val);
                              })
                               .sum();
        auto dt_order =
                dt_eigvs.unaryExpr([&](const std::complex<double>& val) {
                            return count_larger_real(
                                    dt_eigvs[dt_closest_index].real(), val);
                        })
                        .sum();

        // det( (NablaT*R1)*R  ,  (NablaT*R2)*R ,  R )
        auto r2 = Vec3d::Random().normalized().eval();
        while(result_dir.cross(r2).norm() < 0.1)
        {
            r2 = Vec3d::Random().normalized().eval();
        }
        auto r1 = result_dir.cross(r2).normalized().eval();
        r2 = r1.cross(result_dir).normalized().eval();
        auto scale = t.operatorNorm();
        auto stability = std::log(std::abs(
                (Mat3d{} << ((tx * r1[0] + ty * r1[1] + tz * r1[2])
                             * result_dir)
                                    / scale,
                 ((tx * r2[0] + ty * r2[1] + tz * r2[2]) * result_dir) / scale,
                 result_dir)
                        .finished()
                        .determinant()));

        points.push_back(
                TLPoint{tri(result_center),
                         ERank(t_order),
                         ERank(dt_order),
                         result_dir,
                         t_real_eigv,
                         dt_real_eigv,
                         t_eigvs.sum().imag() != 0,
                         dt_eigvs.sum().imag() != 0,
                         r.cluster_size,
                         (pos_tri[1] - pos_tri[0]).norm(),
                         (dir_tri[1] - dir_tri[0]).norm(),
                         stability});
    }
    return points;
}

PointList computeContextInfoTopo(
        const std::vector<ClusterRepr<TensorTopologyEvaluator>>& representatives,
        const Triangle& tri)
{
    auto points = PointList{};
    points.reserve(representatives.size());

    for(const auto& r : representatives)
    {
        const auto& pos_tri = r.eval.tris().pos_tri;

        auto result_center = pos_tri({1. / 3., 1. / 3., 1. / 3.});

        points.push_back(
                TLPoint{tri(result_center),
                         ERank::First,
                         ERank::First,
                         Vec3d::Zero(),
                         0,
                         0,
                         false,
                         false,
                         r.cluster_size,
                         (pos_tri[1] - pos_tri[0]).norm(),
                         0,
                         0});
    }
    return points;
}

/**
 * @brief Perform the recursive root search using an evaluator.
 * @details Performs a breadth-first recursive search for solutions of the
 *      starting evaluator. Terminates when all solutions have been found or
 *      when more than @a max_candidates are in the queue. In the latter case,
 *      @c boost::none is returned.
 *
 * @param start_ev Starting evaluator
 * @param max_candidates Maximum number of triangles produced during subdivision
 *     before early termination
 * @param num_splits Optional output parameter for storing the number of split
 *     operations performed
 * @param max_level Optional output parameter for storing the maximum
 *     subdivision level reached
 * @return A vector of solution candidates represented by Evaluators at the lowest
 *      subdivision level, or boost::none if the search was terminated early.
 */
template <typename Evaluator>
boost::optional<std::vector<Evaluator>>
rootSearch(const Evaluator& start_ev,
           std::size_t max_candidates,
           uint64_t* num_splits = nullptr,
           uint64_t* max_level = nullptr)
{
    static_assert(is_evaluator_v<Evaluator>,
                  "rootSearch requires a valid Evaluator!");

    auto work_lst = std::queue<Evaluator>{};
    work_lst.push(start_ev);
    auto result = std::vector<Evaluator>{};

    while(!work_lst.empty())
    {
        if(work_lst.size() > max_candidates) return boost::none;
        auto ev = work_lst.front();
        work_lst.pop();
        if(num_splits) *num_splits += 1;
        if(max_level && *max_level < ev.splitLevel())
        {
            *max_level = ev.splitLevel();
        }

        switch(ev.eval())
        {
            case Result::Split:
                for(const auto& p : ev.split())
                {
                    work_lst.push(p);
                }
                break;
            case Result::Accept:
                result.push_back(ev);
                break;
            case Result::Discard:
                break;
        }
    }

    return result;
}

/**
 * Search for parallel eigenvector intersections with a triangle.
 *
 * @param s First tensor field (linear on a triangle)
 * @param t Second tensor field (linear on a triangle)
 * @param tri Physical location of the triangle
 * @param tolerance Error tolerance for subdivision
 * @param max_candidates Maximum number of triangles produced during subdivision
 *     before early termination
 * @param num_splits Optional output parameter for storing the number of split
 *     operations performed
 * @param max_level Optional output parameter for storing the maximum
 *     subdivision level reached
 * @return A vector of all found solution candidates and a vector of the
 *     rough eigenvector directions that resulted in early termination
 */
std::pair<std::vector<ParallelEigenvectorsEvaluator>, std::vector<Vec3d>>
parallelEigenvectorSearch(const TensorInterp& s,
                          const TensorInterp& t,
                          const Triangle& tri,
                          double tolerance,
                          std::size_t max_candidates,
                          uint64_t* num_splits = nullptr,
                          uint64_t* max_level = nullptr)
{
    auto result = std::vector<ParallelEigenvectorsEvaluator>{};
    // Stores directions for which the search was terminated because of
    // too many splits
    auto failed_dirs = std::vector<Vec3d>{};

    auto compute_tri = [&](const Triangle& r) {
        auto start_ev = ParallelEigenvectorsEvaluator(
                {tri, r}, s, t, {tolerance});
        auto solutions =
                rootSearch(start_ev, max_candidates, num_splits, max_level);
        if(solutions)
        {
            boost::insert(
                    result,
                    result.end(),
                    solutions.value());
        }
        else
        {
            failed_dirs.push_back(r({1./3, 1./3, 1./3}));
        }
    };

    // Four triangles covering hemisphere
    auto dir_tris = std::array<Triangle, 4>{
            Triangle{{Vec3d{1, 0, 0}, Vec3d{0, 1, 0}, Vec3d{0, 0, 1}}},
            Triangle{{Vec3d{0, 1, 0}, Vec3d{-1, 0, 0}, Vec3d{0, 0, 1}}},
            Triangle{{Vec3d{-1, 0, 0}, Vec3d{0, -1, 0}, Vec3d{0, 0, 1}}},
            Triangle{{Vec3d{0, -1, 0}, Vec3d{1, 0, 0}, Vec3d{0, 0, 1}}}};

    for(const auto& tri : dir_tris)
    {
        for(const auto& t : tri.split())
        {
            compute_tri(t);
        }
    }

    return {result, failed_dirs};
}

/**
 * Search for tensor core line intersections with a triangle.
 *
 * @param t Tensor field (linear on a triangle)
 * @param dt derivatives of the tensor field (constant on a triangle)
 * @param tri Physical location of the triangle
 * @param tolerance Error tolerance for subdivision
 * @param max_candidates Maximum number of triangles produced during subdivision
 *     before early termination
 * @param num_splits Optional output parameter for storing the number of split
 *     operations performed
 * @param max_level Optional output parameter for storing the maximum
 *     subdivision level reached
 * @return A vector of all found solution candidates and a vector of the
 *     rough eigenvector directions that resulted in early termination
 */
std::pair<std::vector<TensorCoreLinesEvaluator>, std::vector<Vec3d>>
tensorCoreLinesSearch(const TensorInterp& t,
                         const std::array<TensorInterp, 3>& dt,
                         const Triangle& tri,
                         double tolerance,
                         std::size_t max_candidates,
                         uint64_t* num_splits = nullptr,
                         uint64_t* max_level = nullptr)
{
    auto result = std::vector<TensorCoreLinesEvaluator>{};
    // Stores directions for which the search was terminated because of
    // too many splits
    auto failed_dirs = std::vector<Vec3d>{};

    auto compute_tri = [&](const Triangle& r) {
        auto start_ev = TensorCoreLinesEvaluator(
                {tri, r}, t, dt, {tolerance});
        auto solutions =
                rootSearch(start_ev, max_candidates, num_splits, max_level);
        if(solutions)
        {
            boost::insert(result, result.end(), solutions.value());
        }
        else
        {
            failed_dirs.push_back(r({1. / 3, 1. / 3, 1. / 3}));
        }
    };

    auto dir_tris = std::array<Triangle, 4>{
            Triangle{{Vec3d{1, 0, 0}, Vec3d{0, 1, 0}, Vec3d{0, 0, 1}}},
            Triangle{{Vec3d{0, 1, 0}, Vec3d{-1, 0, 0}, Vec3d{0, 0, 1}}},
            Triangle{{Vec3d{-1, 0, 0}, Vec3d{0, -1, 0}, Vec3d{0, 0, 1}}},
            Triangle{{Vec3d{0, -1, 0}, Vec3d{1, 0, 0}, Vec3d{0, 0, 1}}}};

    for(const auto& tri : dir_tris)
    {
        for(const auto& t : tri.split())
        {
            compute_tri(t);
        }
    }

    return {result, failed_dirs};
}

/**
 * Search for degenerate line intersections with a triangle.
 *
 * @param t Tensor field (linear on a triangle)
 * @param tri Physical location of the triangle
 * @param tolerance Error tolerance for subdivision
 * @param max_candidates Maximum number of triangles produced during subdivision
 *     before early termination
 * @param num_splits Optional output parameter for storing the number of split
 *     operations performed
 * @param max_level Optional output parameter for storing the maximum
 *     subdivision level reached
 * @return A vector of all found solution candidates and a vector of the
 *     rough eigenvector directions that resulted in early termination
 */
std::pair<std::vector<TensorTopologyEvaluator>, std::vector<Vec3d>>
tensorTopologySearch(const TensorInterp& t,
                     const Triangle& tri,
                     double tolerance,
                     std::size_t max_candidates,
                     uint64_t* num_splits = nullptr,
                     uint64_t* max_level = nullptr)
{
    auto start_ev = TensorTopologyEvaluator(
            {tri, Triangle{{Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero()}}},
            t,
            {tolerance});
    auto solutions =
            rootSearch(start_ev, max_candidates, num_splits, max_level);

    if(solutions)
    {
        return {solutions.value(), std::vector<Vec3d>{}};
    }
    else
    {
        return {std::vector<TensorTopologyEvaluator>{},
                std::vector<Vec3d>{1, Vec3d::Zero()}};
    }
}

} // namespace

namespace tl
{

TLResult findParallelEigenvectors(const std::array<Mat3d, 3>& s,
                                   const std::array<Mat3d, 3>& t,
                                   const std::array<Vec3d, 3>& x,
                                   const TLOptions& opts)
{
    auto start_tri =
            Triangle{{Vec3d{1., 0., 0.}, Vec3d{0., 1., 0.}, Vec3d{0., 0., 1.}}};

    auto st = TensorInterp{{s[0], s[1], s[2]}};
    auto tt = TensorInterp{{t[0], t[1], t[2]}};
    auto xt = Triangle{{x[0], x[1], x[2]}};

    auto num_splits = uint64_t{0};
    auto max_level = uint64_t{0};
    auto tris = parallelEigenvectorSearch(st,
                                          tt,
                                          start_tri,
                                          opts.tolerance,
                                          opts.max_candidates,
                                          &num_splits,
                                          &max_level);

    auto clustered_tris = clusterTris(tris.first, opts.cluster_epsilon);

    auto representatives = findRepresentatives(clustered_tris);

    return {computeContextInfoPEV(representatives, st, tt, xt),
            tris.second};
}

TLResult findParallelEigenvectors(const std::array<Mat3d, 3>& s,
                                   const std::array<Mat3d, 3>& t,
                                   const TLOptions& opts)
{
    return findParallelEigenvectors(
            s,
            t,
            {Vec3d{1., 0., 0.}, Vec3d{0., 1., 0.}, Vec3d{0., 0., 1.}},
            opts);
}

TLResult findTensorCoreLines(const std::array<Mat3d, 3>& t,
                                 const std::array<Mat3d, 3>& dt,
                                 const std::array<Vec3d, 3>& x,
                                 const TLOptions& opts)
{
    auto start_tri =
            Triangle{{Vec3d{1., 0., 0.}, Vec3d{0., 1., 0.}, Vec3d{0., 0., 1.}}};

    auto tt = TensorInterp{{t[0], t[1], t[2]}};
    auto tx = TensorInterp{{dt[0], dt[0], dt[0]}};
    auto ty = TensorInterp{{dt[1], dt[1], dt[1]}};
    auto tz = TensorInterp{{dt[2], dt[2], dt[2]}};
    auto xt = Triangle{{x[0], x[1], x[2]}};

    auto tolerance_scale = std::max({tt[0].operatorNorm(),
                                     tt[1].operatorNorm(),
                                     tt[2].operatorNorm()});

    auto num_splits = uint64_t{0};
    auto max_level = uint64_t{0};
    auto tris = tensorCoreLinesSearch(tt,
                                         {tx, ty, tz},
                                         start_tri,
                                         opts.tolerance*tolerance_scale,
                                         opts.max_candidates,
                                         &num_splits,
                                         &max_level);

    auto clustered_tris = clusterTris(tris.first, opts.cluster_epsilon);

    auto representatives = findRepresentatives(clustered_tris);

    return {computeContextInfoTCL(representatives, tt, tx, ty, tz, xt),
            tris.second};
}

TLResult findTensorCoreLines(const std::array<Mat3d, 3>& t,
                                 const std::array<Mat3d, 3>& dt,
                                 const TLOptions& opts)
{
    return findTensorCoreLines(
            t,
            dt,
            {Vec3d{1., 0., 0.}, Vec3d{0., 1., 0.}, Vec3d{0., 0., 1.}},
            opts);
}

TLResult findTensorTopology(const std::array<Mat3d, 3>& t,
                             const std::array<Vec3d, 3>& x,
                             const TLOptions& opts)
{
    auto start_tri =
            Triangle{{Vec3d{1., 0., 0.}, Vec3d{0., 1., 0.}, Vec3d{0., 0., 1.}}};

    auto tt = TensorInterp{{t[0], t[1], t[2]}};
    auto xt = Triangle{{x[0], x[1], x[2]}};

    auto tolerance_scale = std::max({tt[0].operatorNorm(),
                                     tt[1].operatorNorm(),
                                     tt[2].operatorNorm()});

    auto num_splits = uint64_t{0};
    auto max_level = uint64_t{0};
    auto tris = tensorTopologySearch(tt,
                                     start_tri,
                                     opts.tolerance * tolerance_scale,
                                     opts.max_candidates,
                                     &num_splits,
                                     &max_level);

    auto clustered_tris = clusterTris(tris.first, opts.cluster_epsilon);

    auto representatives = findRepresentatives(clustered_tris);

    return {computeContextInfoTopo(representatives, xt),
            tris.second};
}

TLResult findTensorTopology(const std::array<Mat3d, 3>& t,
                             const TLOptions& opts)
{
    return findTensorTopology(
            t,
            {Vec3d{1., 0., 0.}, Vec3d{0., 1., 0.}, Vec3d{0., 0., 1.}},
            opts);
}

} // namespace tl


// Copyright 2019 Lawrence Livermore National Security, LLC and other Metall Project Developers.
// See the top-level COPYRIGHT file for details.
//
// SPDX-License-Identifier: (Apache-2.0 OR MIT)

#include <iostream>
#include <random>
#include <boost/random/mersenne_twister.hpp>
#include <metall/utility/random.hpp>
#include <metall/detail/time.hpp>

template <typename rand_engine_type>
auto run_bench(const uint64_t num_generate) {
  rand_engine_type rand_engine(123);
  const auto s =  metall::mtlldetail::elapsed_time_sec();
  for (uint64_t i = 0; i < num_generate; ++i) {
    [[maybe_unused]] volatile const uint64_t x = rand_engine();
  }
  const auto t =  metall::mtlldetail::elapsed_time_sec(s);
  return t;
}

int main() {

  const uint64_t num_generate = (1ULL << 20ULL);
  std::cout << "Generate " << num_generate << " values" << std::endl;

  std::cout << "std::mt19937_64  \t" << run_bench<std::mt19937_64>(num_generate) << std::endl;
  std::cout << "boost::mt19937_64\t" << run_bench<boost::random::mt19937_64>(num_generate) << std::endl;
  std::cout << "xoshiro512++     \t" << run_bench<metall::utility::rand_512>(num_generate) << std::endl;
  std::cout << "xoshiro1024++    \t" << run_bench<metall::utility::rand_1024>(num_generate) << std::endl;

  return 0;
}


#include <boost/graph/howard_cycle_ratio.hpp>