/*!
 \file fasttransforms.h
 \brief fasttransforms.h is the main header file for FastTransforms.
*/
#ifndef FASTTRANSFORMS_H
#define FASTTRANSFORMS_H

#include <cblas.h>
#include <fftw3.h>

typedef double ft_complex[2];

#ifdef _OPENMP
    #include <omp.h>
    #define FT_GET_THREAD_NUM() omp_get_thread_num()
    #define FT_GET_NUM_THREADS() omp_get_num_threads()
    #define FT_GET_MAX_THREADS() omp_get_max_threads()
    #define FT_SET_NUM_THREADS(x) omp_set_num_threads(x)
#else
    #define FT_GET_THREAD_NUM() 0
    #define FT_GET_NUM_THREADS() 1
    #define FT_GET_MAX_THREADS() 1
    #define FT_SET_NUM_THREADS(x)
#endif

#define FT_CONCAT(prefix, name, suffix) prefix ## name ## suffix

void ft_horner(const int n, const double * c, const int incc, const int m, double * x, double * f);
void ft_hornerf(const int n, const float * c, const int incc, const int m, float * x, float * f);

void ft_clenshaw(const int n, const double * c, const int incc, const int m, double * x, double * f);
void ft_clenshawf(const int n, const float * c, const int incc, const int m, float * x, float * f);

void ft_orthogonal_polynomial_clenshaw(const int n, const double * c, const int incc, const double * A, const double * B, const double * C, const int m, double * x, double * phi0, double * f);
void ft_orthogonal_polynomial_clenshawf(const int n, const float * c, const int incc, const float * A, const float * B, const float * C, const int m, float * x, float * phi0, float * f);

void ft_eigen_eval(const int n, const double * c, const int incc, const double * A, const double * B, const double * C, const int m, double * x, const int sign, double * f);
void ft_eigen_evalf(const int n, const float * c, const int incc, const float * A, const float * B, const float * C, const int m, float * x, const int sign, float * f);
void ft_eigen_evall(const int n, const long double * c, const int incc, const long double * A, const long double * B, const long double * C, const int m, long double * x, const int sign, long double * f);
#if defined(FT_QUADMATH)
    #include <quadmath.h>
    typedef __float128 quadruple;
    void ft_eigen_evalq(const int n, const quadruple * c, const int incc, const quadruple * A, const quadruple * B, const quadruple * C, const int m, quadruple * x, const int sign, quadruple * f);
#endif

#define FT_SN (1U << 0)
#define FT_CN (1U << 1)
#define FT_DN (1U << 2)

#include "tdc.h"

/*!
  \brief Pre-compute a factorization of the connection coefficients between Legendre and Chebyshev polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Legendre}} P_\ell(x) = \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Chebyshev}} T_\ell(x).
  \f]
  `normleg` and `normcheb` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_legendre_to_chebyshevf, \ref ft_plan_legendre_to_chebyshevl, and \ref ft_mpfr_plan_legendre_to_chebyshev.
*/
ft_tb_eigen_FMM * ft_plan_legendre_to_chebyshev(const int normleg, const int normcheb, const int n);
/*!
  \brief Pre-compute a factorization of the connection coefficients between Chebyshev and Legendre polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Chebyshev}} T_\ell(x) = \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Legendre}} P_\ell(x).
  \f]
  `normcheb` and `normleg` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_chebyshev_to_legendref, \ref ft_plan_chebyshev_to_legendrel, and \ref ft_mpfr_plan_chebyshev_to_legendre.
*/
ft_tb_eigen_FMM * ft_plan_chebyshev_to_legendre(const int normcheb, const int normleg, const int n);
/*!
  \brief Pre-compute a factorization of the connection coefficients between ultraspherical polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{(1)} C_\ell^{(\lambda)}(x) = \sum_{\ell=0}^{n-1} c_\ell^{(2)} C_\ell^{(\mu)}(x).
  \f]
  `norm1` and `norm2` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_ultraspherical_to_ultrasphericalf, \ref ft_plan_ultraspherical_to_ultrasphericall, and \ref ft_mpfr_plan_ultraspherical_to_ultraspherical.
*/
ft_tb_eigen_FMM * ft_plan_ultraspherical_to_ultraspherical(const int norm1, const int norm2, const int n, const double lambda, const double mu);
/*!
  \brief Pre-compute a factorization of the connection coefficients between Jacobi polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{(1)} P_\ell^{(\alpha,\beta)}(x) = \sum_{\ell=0}^{n-1} c_\ell^{(2)} P_\ell^{(\gamma,\delta)}(x).
  \f]
  `norm1` and `norm2` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_jacobi_to_jacobif, \ref ft_plan_jacobi_to_jacobil, and \ref ft_mpfr_plan_jacobi_to_jacobi.
*/
ft_tb_eigen_FMM * ft_plan_jacobi_to_jacobi(const int norm1, const int norm2, const int n, const double alpha, const double beta, const double gamma, const double delta);
/*!
  \brief Pre-compute a factorization of the connection coefficients between Laguerre polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{(1)} L_\ell^{(\alpha)}(x) = \sum_{\ell=0}^{n-1} c_\ell^{(2)} L_\ell^{(\beta)}(x).
  \f]
  `norm1` and `norm2` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_laguerre_to_laguerref, \ref ft_plan_laguerre_to_laguerrel, and \ref ft_mpfr_plan_laguerre_to_laguerre.
*/
ft_tb_eigen_FMM * ft_plan_laguerre_to_laguerre(const int norm1, const int norm2, const int n, const double alpha, const double beta);
/*!
  \brief Pre-compute a factorization of the connection coefficients between Jacobi and ultraspherical polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Jacobi}} P_\ell^{(\alpha,\beta)}(x) = \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{ultraspherical}} C_\ell^{(\lambda)}(x).
  \f]
  `normjac` and `normultra` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_jacobi_to_ultrasphericalf, \ref ft_plan_jacobi_to_ultrasphericall, and \ref ft_mpfr_plan_jacobi_to_ultraspherical.
*/
ft_tb_eigen_FMM * ft_plan_jacobi_to_ultraspherical(const int normjac, const int normultra, const int n, const double alpha, const double beta, const double lambda);
/*!
  \brief Pre-compute a factorization of the connection coefficients between ultraspherical and Jacobi polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{ultraspherical}} C_\ell^{(\lambda)}(x) = \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Jacobi}} P_\ell^{(\alpha,\beta)}(x).
  \f]
  `normultra` and `normjac` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_ultraspherical_to_jacobif, \ref ft_plan_ultraspherical_to_jacobil, and \ref ft_mpfr_plan_ultraspherical_to_jacobi.
*/
ft_tb_eigen_FMM * ft_plan_ultraspherical_to_jacobi(const int normultra, const int normjac, const int n, const double lambda, const double alpha, const double beta);
/*!
  \brief Pre-compute a factorization of the connection coefficients between Jacobi and Chebyshev polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Jacobi}} P_\ell^{(\alpha,\beta)}(x) = \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Chebyshev}} T_\ell(x).
  \f]
  `normjac` and `normcheb` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_jacobi_to_chebyshevf, \ref ft_plan_jacobi_to_chebyshevl, and \ref ft_mpfr_plan_jacobi_to_chebyshev.
*/
ft_tb_eigen_FMM * ft_plan_jacobi_to_chebyshev(const int normjac, const int normcheb, const int n, const double alpha, const double beta);
/*!
  \brief Pre-compute a factorization of the connection coefficients between Chebyshev and Jacobi polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Chebyshev}} T_\ell(x) = \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Jacobi}} P_\ell^{(\alpha,\beta)}(x).
  \f]
  `normcheb` and `normjac` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_chebyshev_to_jacobif, \ref ft_plan_chebyshev_to_jacobil, and \ref ft_mpfr_plan_chebyshev_to_jacobi.
*/
ft_tb_eigen_FMM * ft_plan_chebyshev_to_jacobi(const int normcheb, const int normjac, const int n, const double alpha, const double beta);
/*!
  \brief Pre-compute a factorization of the connection coefficients between ultraspherical and Chebyshev polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{ultraspherical}} C_\ell^{(\lambda)}(x) = \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Chebyshev}} T_\ell(x).
  \f]
  `normultra` and `normcheb` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_ultraspherical_to_chebyshevf, \ref ft_plan_ultraspherical_to_chebyshevl, and \ref ft_mpfr_plan_ultraspherical_to_chebyshev.
*/
ft_tb_eigen_FMM * ft_plan_ultraspherical_to_chebyshev(const int normultra, const int normcheb, const int n, const double lambda);
/*!
  \brief Pre-compute a factorization of the connection coefficients between Chebyshev and ultraspherical polynomials in double precision so that ft_bfmv converts between expansions:
  \f[
  \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{Chebyshev}} T_\ell(x) = \sum_{\ell=0}^{n-1} c_\ell^{\mathrm{ultraspherical}} C_\ell^{(\lambda)}(x).
  \f]
  `normcheb` and `normultra` govern the normalizations, either standard ( == 0) or orthonormalized ( == 1).\n
  See also \ref ft_plan_chebyshev_to_ultrasphericalf, \ref ft_plan_chebyshev_to_ultrasphericall, and \ref ft_mpfr_plan_chebyshev_to_ultraspherical.
*/
ft_tb_eigen_FMM * ft_plan_chebyshev_to_ultraspherical(const int normcheb, const int normultra, const int n, const double lambda);

ft_btb_eigen_FMM * ft_plan_associated_jacobi_to_jacobi(const int norm1, const int norm2, const int n, const int c, const double alpha, const double beta, const double gamma, const double delta);
ft_btb_eigen_FMM * ft_plan_associated_laguerre_to_laguerre(const int norm1, const int norm2, const int n, const int c, const double alpha, const double beta);
ft_btb_eigen_FMM * ft_plan_associated_hermite_to_hermite(const int norm1, const int norm2, const int n, const int c);

/// A single precision version of \ref ft_plan_legendre_to_chebyshev.
ft_tb_eigen_FMMf * ft_plan_legendre_to_chebyshevf(const int normleg, const int normcheb, const int n);
/// A single precision version of \ref ft_plan_chebyshev_to_legendre.
ft_tb_eigen_FMMf * ft_plan_chebyshev_to_legendref(const int normcheb, const int normleg, const int n);
/// A single precision version of \ref ft_plan_ultraspherical_to_ultraspherical.
ft_tb_eigen_FMMf * ft_plan_ultraspherical_to_ultrasphericalf(const int norm1, const int norm2, const int n, const float lambda, const float mu);
/// A single precision version of \ref ft_plan_jacobi_to_jacobi.
ft_tb_eigen_FMMf * ft_plan_jacobi_to_jacobif(const int norm1, const int norm2, const int n, const float alpha, const float beta, const float gamma, const float delta);
/// A single precision version of \ref ft_plan_laguerre_to_laguerre.
ft_tb_eigen_FMMf * ft_plan_laguerre_to_laguerref(const int norm1, const int norm2, const int n, const float alpha, const float beta);
/// A single precision version of \ref ft_plan_jacobi_to_ultraspherical.
ft_tb_eigen_FMMf * ft_plan_jacobi_to_ultrasphericalf(const int normjac, const int normultra, const int n, const float alpha, const float beta, const float lambda);
/// A single precision version of \ref ft_plan_ultraspherical_to_jacobi.
ft_tb_eigen_FMMf * ft_plan_ultraspherical_to_jacobif(const int normultra, const int normjac, const int n, const float lambda, const float alpha, const float beta);
/// A single precision version of \ref ft_plan_jacobi_to_chebyshev.
ft_tb_eigen_FMMf * ft_plan_jacobi_to_chebyshevf(const int normjac, const int normcheb, const int n, const float alpha, const float beta);
/// A single precision version of \ref ft_plan_chebyshev_to_jacobi.
ft_tb_eigen_FMMf * ft_plan_chebyshev_to_jacobif(const int normcheb, const int normjac, const int n, const float alpha, const float beta);
/// A single precision version of \ref ft_plan_ultraspherical_to_chebyshev.
ft_tb_eigen_FMMf * ft_plan_ultraspherical_to_chebyshevf(const int normultra, const int normcheb, const int n, const float lambda);
/// A single precision version of \ref ft_plan_chebyshev_to_ultraspherical.
ft_tb_eigen_FMMf * ft_plan_chebyshev_to_ultrasphericalf(const int normcheb, const int normultra, const int n, const float lambda);

ft_btb_eigen_FMMf * ft_plan_associated_jacobi_to_jacobif(const int norm1, const int norm2, const int n, const int c, const float alpha, const float beta, const float gamma, const float delta);
ft_btb_eigen_FMMf * ft_plan_associated_laguerre_to_laguerref(const int norm1, const int norm2, const int n, const int c, const float alpha, const float beta);
ft_btb_eigen_FMMf * ft_plan_associated_hermite_to_hermitef(const int norm1, const int norm2, const int n, const int c);

/// A long double precision version of \ref ft_plan_legendre_to_chebyshev.
ft_tb_eigen_FMMl * ft_plan_legendre_to_chebyshevl(const int normleg, const int normcheb, const int n);
/// A long double precision version of \ref ft_plan_chebyshev_to_legendre.
ft_tb_eigen_FMMl * ft_plan_chebyshev_to_legendrel(const int normcheb, const int normleg, const int n);
/// A long double precision version of \ref ft_plan_ultraspherical_to_ultraspherical.
ft_tb_eigen_FMMl * ft_plan_ultraspherical_to_ultrasphericall(const int norm1, const int norm2, const int n, const long double lambda, const long double mu);
/// A long double precision version of \ref ft_plan_jacobi_to_jacobi.
ft_tb_eigen_FMMl * ft_plan_jacobi_to_jacobil(const int norm1, const int norm2, const int n, const long double alpha, const long double beta, const long double gamma, const long double delta);
/// A long double precision version of \ref ft_plan_laguerre_to_laguerre.
ft_tb_eigen_FMMl * ft_plan_laguerre_to_laguerrel(const int norm1, const int norm2, const int n, const long double alpha, const long double beta);
/// A long double precision version of \ref ft_plan_jacobi_to_ultraspherical.
ft_tb_eigen_FMMl * ft_plan_jacobi_to_ultrasphericall(const int normjac, const int normultra, const int n, const long double alpha, const long double beta, const long double lambda);
/// A long double precision version of \ref ft_plan_ultraspherical_to_jacobi.
ft_tb_eigen_FMMl * ft_plan_ultraspherical_to_jacobil(const int normultra, const int normjac, const int n, const long double lambda, const long double alpha, const long double beta);
/// A long double precision version of \ref ft_plan_jacobi_to_chebyshev.
ft_tb_eigen_FMMl * ft_plan_jacobi_to_chebyshevl(const int normjac, const int normcheb, const int n, const long double alpha, const long double beta);
/// A long double precision version of \ref ft_plan_chebyshev_to_jacobi.
ft_tb_eigen_FMMl * ft_plan_chebyshev_to_jacobil(const int normcheb, const int normjac, const int n, const long double alpha, const long double beta);
/// A long double precision version of \ref ft_plan_ultraspherical_to_chebyshev.
ft_tb_eigen_FMMl * ft_plan_ultraspherical_to_chebyshevl(const int normultra, const int normcheb, const int n, const long double lambda);
/// A long double precision version of \ref ft_plan_chebyshev_to_ultraspherical.
ft_tb_eigen_FMMl * ft_plan_chebyshev_to_ultrasphericall(const int normcheb, const int normultra, const int n, const long double lambda);

ft_btb_eigen_FMMl * ft_plan_associated_jacobi_to_jacobil(const int norm1, const int norm2, const int n, const int c, const long double alpha, const long double beta, const long double gamma, const long double delta);
ft_btb_eigen_FMMl * ft_plan_associated_laguerre_to_laguerrel(const int norm1, const int norm2, const int n, const int c, const long double alpha, const long double beta);
ft_btb_eigen_FMMl * ft_plan_associated_hermite_to_hermitel(const int norm1, const int norm2, const int n, const int c);

#include <mpfr.h>

typedef struct {
    mpfr_t * data;
    int n;
    int b;
} ft_mpfr_triangular_banded;

void ft_mpfr_destroy_plan(mpfr_t * A, int n);
void ft_mpfr_trmv(char TRANS, int n, mpfr_t * A, int LDA, mpfr_t * x, mpfr_rnd_t rnd);
void ft_mpfr_trsv(char TRANS, int n, mpfr_t * A, int LDA, mpfr_t * x, mpfr_rnd_t rnd);
void ft_mpfr_trmm(char TRANS, int n, mpfr_t * A, int LDA, mpfr_t * B, int LDB, int N, mpfr_rnd_t rnd);
void ft_mpfr_trsm(char TRANS, int n, mpfr_t * A, int LDA, mpfr_t * B, int LDB, int N, mpfr_rnd_t rnd);
// C -- Julia interoperability. Julia `BigFloat` does not have the same size as `mpfr_t`.
// So we give all Julia-owned data its own address, and dereference to retrieve the number.
void ft_mpfr_trmv_ptr(char TRANS, int n, mpfr_t * A, int LDA, mpfr_t ** x, mpfr_rnd_t rnd);
void ft_mpfr_trsv_ptr(char TRANS, int n, mpfr_t * A, int LDA, mpfr_t ** x, mpfr_rnd_t rnd);
void ft_mpfr_trmm_ptr(char TRANS, int n, mpfr_t * A, int LDA, mpfr_t ** B, int LDB, int N, mpfr_rnd_t rnd);
void ft_mpfr_trsm_ptr(char TRANS, int n, mpfr_t * A, int LDA, mpfr_t ** B, int LDB, int N, mpfr_rnd_t rnd);

/// A multi-precision version of \ref ft_plan_legendre_to_chebyshev that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_legendre_to_chebyshev(const int normleg, const int normcheb, const int n, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_chebyshev_to_legendre that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_chebyshev_to_legendre(const int normcheb, const int normleg, const int n, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_ultraspherical_to_ultraspherical that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_ultraspherical_to_ultraspherical(const int norm1, const int norm2, const int n, mpfr_srcptr lambda, mpfr_srcptr mu, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_jacobi_to_jacobi that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_jacobi_to_jacobi(const int norm1, const int norm2, const int n, mpfr_srcptr alpha, mpfr_srcptr beta, mpfr_srcptr gamma, mpfr_srcptr delta, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_laguerre_to_laguerre that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_laguerre_to_laguerre(const int norm1, const int norm2, const int n, mpfr_srcptr alpha, mpfr_srcptr beta, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_jacobi_to_ultraspherical that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_jacobi_to_ultraspherical(const int normjac, const int normultra, const int n, mpfr_srcptr alpha, mpfr_srcptr beta, mpfr_srcptr lambda, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_ultraspherical_to_jacobi that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_ultraspherical_to_jacobi(const int normultra, const int normjac, const int n, mpfr_srcptr lambda, mpfr_srcptr alpha, mpfr_srcptr beta, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_jacobi_to_chebyshev that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_jacobi_to_chebyshev(const int normjac, const int normcheb, const int n, mpfr_srcptr alpha, mpfr_srcptr beta, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_chebyshev_to_jacobi that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_chebyshev_to_jacobi(const int normcheb, const int normjac, const int n, mpfr_srcptr alpha, mpfr_srcptr beta, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_ultraspherical_to_chebyshev that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_ultraspherical_to_chebyshev(const int normultra, const int normcheb, const int n, mpfr_srcptr lambda, mpfr_prec_t prec, mpfr_rnd_t rnd);
/// A multi-precision version of \ref ft_plan_chebyshev_to_ultraspherical that returns a dense array of connection coefficients.
mpfr_t * ft_mpfr_plan_chebyshev_to_ultraspherical(const int normcheb, const int normultra, const int n, mpfr_srcptr lambda, mpfr_prec_t prec, mpfr_rnd_t rnd);

/// Set the number of OpenMP threads.
void ft_set_num_threads(const int n);

/// Data structure to store sines and cosines of Givens rotations.
typedef struct ft_rotation_plan_s ft_rotation_plan;

/// Destroy a \ref ft_rotation_plan.
void ft_destroy_rotation_plan(ft_rotation_plan * RP);

ft_rotation_plan * ft_plan_rotsphere(const int n);

/// Convert a single vector of spherical harmonic coefficients in A with stride S from order m2 down to order m1.
void ft_kernel_sph_hi2lo(const ft_rotation_plan * RP, const int m1, const int m2, double * A, const int S);
/// Convert a single vector of spherical harmonic coefficients in A with stride S from order m1 up to order m2.
void ft_kernel_sph_lo2hi(const ft_rotation_plan * RP, const int m1, const int m2, double * A, const int S);

ft_rotation_plan * ft_plan_rottriangle(const int n, const double alpha, const double beta, const double gamma);

/// Convert a single vector of triangular harmonic coefficients in A with stride S from order m2 down to order m1.
void ft_kernel_tri_hi2lo(const ft_rotation_plan * RP, const int m1, const int m2, double * A, const int S);
/// Convert a single vector of triangular harmonic coefficients in A with stride S from order m1 up to order m2.
void ft_kernel_tri_lo2hi(const ft_rotation_plan * RP, const int m1, const int m2, double * A, const int S);

ft_rotation_plan * ft_plan_rotdisk(const int n, const double alpha, const double beta);

/// Convert a single vector of disk harmonic coefficients in A with stride S from order m2 down to order m1.
void ft_kernel_disk_hi2lo(const ft_rotation_plan * RP, const int m1, const int m2, double * A, const int S);
/// Convert a single vector of disk harmonic coefficients in A with stride S from order m1 up to order m2.
void ft_kernel_disk_lo2hi(const ft_rotation_plan * RP, const int m1, const int m2, double * A, const int S);

ft_rotation_plan * ft_plan_rotrectdisk(const int n, const double beta);

/// Convert a single vector of rectangularized disk harmonic coefficients in A with stride S from order m2 down to order m1.
void ft_kernel_rectdisk_hi2lo(const ft_rotation_plan * RP, const int m1, const int m2, double * A, const int S);
/// Convert a single vector of rectangularized disk harmonic coefficients in A with stride S from order m1 up to order m2.
void ft_kernel_rectdisk_lo2hi(const ft_rotation_plan * RP, const int m1, const int m2, double * A, const int S);

void ft_kernel_tet_hi2lo(const ft_rotation_plan * RP, const int L, const int m, double * A);
void ft_kernel_tet_lo2hi(const ft_rotation_plan * RP, const int L, const int m, double * A);

/// Data structure to store sines and cosines of Givens rotations for spin-weighted harmonics.
typedef struct ft_spin_rotation_plan_s ft_spin_rotation_plan;

/// Destroy a \ref ft_spin_rotation_plan.
void ft_destroy_spin_rotation_plan(ft_spin_rotation_plan * SRP);

ft_spin_rotation_plan * ft_plan_rotspinsphere(const int n, const int s);

/// Convert a single vector of spin-weighted spherical harmonic coefficients in A with stride S from order m down to order m%2.
void ft_kernel_spinsph_hi2lo(const ft_spin_rotation_plan * SRP, const int m, ft_complex * A, const int S);
/// Convert a single vector of spin-weighted spherical harmonic coefficients in A with stride S from order m%2 up to order m.
void ft_kernel_spinsph_lo2hi(const ft_spin_rotation_plan * SRP, const int m, ft_complex * A, const int S);

void ft_execute_sph_hi2lo(const ft_rotation_plan * RP, double * A, double * B, const int M);
void ft_execute_sph_lo2hi(const ft_rotation_plan * RP, double * A, double * B, const int M);

void ft_execute_sphv_hi2lo(const ft_rotation_plan * RP, double * A, double * B, const int M);
void ft_execute_sphv_lo2hi(const ft_rotation_plan * RP, double * A, double * B, const int M);

void ft_execute_tri_hi2lo(const ft_rotation_plan * RP, double * A, double * B, const int M);
void ft_execute_tri_lo2hi(const ft_rotation_plan * RP, double * A, double * B, const int M);

void ft_execute_disk_hi2lo(const ft_rotation_plan * RP, double * A, double * B, const int M);
void ft_execute_disk_lo2hi(const ft_rotation_plan * RP, double * A, double * B, const int M);

void ft_execute_rectdisk_hi2lo(const ft_rotation_plan * RP, double * A, double * B, const int M);
void ft_execute_rectdisk_lo2hi(const ft_rotation_plan * RP, double * A, double * B, const int M);

void ft_execute_tet_hi2lo(const ft_rotation_plan * RP1, const ft_rotation_plan * RP2, double * A, const int L, const int M);
void ft_execute_tet_lo2hi(const ft_rotation_plan * RP1, const ft_rotation_plan * RP2, double * A, const int L, const int M);

void ft_execute_spinsph_hi2lo(const ft_spin_rotation_plan * SRP, ft_complex * A, ft_complex * B, const int M);
void ft_execute_spinsph_lo2hi(const ft_spin_rotation_plan * SRP, ft_complex * A, ft_complex * B, const int M);

/// Data structure to store \ref ft_rotation_plan "ft_rotation_plan"s, arrays to represent 1D orthogonal polynomial transforms and their inverses, and Greek parameters.
typedef struct {
    ft_rotation_plan ** RP;
    double * B;
    double ** P;
    double ** Pinv;
    double alpha;
    double beta;
    double gamma;
    double delta;
    int NRP;
    int NP;
} ft_harmonic_plan;

/// Destroy a \ref ft_harmonic_plan.
void ft_destroy_harmonic_plan(ft_harmonic_plan * P);

/// Plan a spherical harmonic transform.
ft_harmonic_plan * ft_plan_sph2fourier(const int n);

/// Transform a spherical harmonic expansion to a bivariate Fourier series.
void ft_execute_sph2fourier(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);
/// Transform a bivariate Fourier series to a spherical harmonic expansion.
void ft_execute_fourier2sph(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);

void ft_execute_sphv2fourier(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);
void ft_execute_fourier2sphv(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);

/// Plan a triangular harmonic transform.
ft_harmonic_plan * ft_plan_tri2cheb(const int n, const double alpha, const double beta, const double gamma);

/// Transform a triangular harmonic expansion to a bivariate Chebyshev series.
void ft_execute_tri2cheb(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);
/// Transform a bivariate Chebyshev series to a triangular harmonic expansion.
void ft_execute_cheb2tri(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);

/// Plan a disk harmonic transform.
ft_harmonic_plan * ft_plan_disk2cxf(const int n, const double alpha, const double beta);

/// Transform a disk harmonic expansion to a Chebyshev--Fourier series.
void ft_execute_disk2cxf(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);
/// Transform a Chebyshev--Fourier series to a disk harmonic expansion.
void ft_execute_cxf2disk(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);

/// Plan a rectangularized disk harmonic transform.
ft_harmonic_plan * ft_plan_rectdisk2cheb(const int n, const double beta);

/// Transform a rectangularized disk harmonic expansion to a Chebyshev series.
void ft_execute_rectdisk2cheb(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);
/// Transform a Chebyshev series to a rectangularized disk harmonic expansion.
void ft_execute_cheb2rectdisk(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int M);

/// Plan a tetrahedral harmonic transform.
ft_harmonic_plan * ft_plan_tet2cheb(const int n, const double alpha, const double beta, const double gamma, const double delta);

/// Transform a tetrahedral harmonic expansion to a trivariate Chebyshev series.
void ft_execute_tet2cheb(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int L, const int M);
/// Transform a trivariate Chebyshev series to a tetrahedral harmonic expansion.
void ft_execute_cheb2tet(const char TRANS, const ft_harmonic_plan * P, double * A, const int N, const int L, const int M);

/// Data structure to store a \ref ft_spin_rotation_plan, and various arrays to represent 1D orthogonal polynomial transforms.
typedef struct {
    ft_spin_rotation_plan * SRP;
    ft_complex * B;
    ft_complex * P1;
    ft_complex * P2;
    ft_complex * P1inv;
    ft_complex * P2inv;
    int s;
} ft_spin_harmonic_plan;

/// Destroy a \ref ft_spin_harmonic_plan.
void ft_destroy_spin_harmonic_plan(ft_spin_harmonic_plan * P);

/// Plan a spin-weighted spherical harmonic transform.
ft_spin_harmonic_plan * ft_plan_spinsph2fourier(const int n, const int s);

/// Transform a spin-weighted spherical harmonic expansion to a bivariate Fourier series.
void ft_execute_spinsph2fourier(const char TRANS, const ft_spin_harmonic_plan * P, ft_complex * A, const int N, const int M);
/// Transform a bivariate Fourier series to a spin-weighted spherical harmonic expansion.
void ft_execute_fourier2spinsph(const char TRANS, const ft_spin_harmonic_plan * P, ft_complex * A, const int N, const int M);

int ft_fftw_init_threads(void);
void ft_fftw_plan_with_nthreads(const int n);

typedef struct {
    fftw_plan plantheta1;
    fftw_plan plantheta2;
    fftw_plan plantheta3;
    fftw_plan plantheta4;
    fftw_plan planphi;
    double * Y;
} ft_sphere_fftw_plan;

/// Destroy a \ref ft_sphere_fftw_plan.
void ft_destroy_sphere_fftw_plan(ft_sphere_fftw_plan * P);

ft_sphere_fftw_plan * ft_plan_sph_with_kind(const int N, const int M, const fftw_r2r_kind kind[3][1]);
/// Plan FFTW synthesis on the sphere.
ft_sphere_fftw_plan * ft_plan_sph_synthesis(const int N, const int M);
/// Plan FFTW analysis on the sphere.
ft_sphere_fftw_plan * ft_plan_sph_analysis(const int N, const int M);
ft_sphere_fftw_plan * ft_plan_sphv_synthesis(const int N, const int M);
ft_sphere_fftw_plan * ft_plan_sphv_analysis(const int N, const int M);

/// Execute FFTW synthesis on the sphere.
void ft_execute_sph_synthesis(const char TRANS, const ft_sphere_fftw_plan * P, double * X, const int N, const int M);
/// Execute FFTW analysis on the sphere.
void ft_execute_sph_analysis(const char TRANS, const ft_sphere_fftw_plan * P, double * X, const int N, const int M);

void ft_execute_sphv_synthesis(const char TRANS, const ft_sphere_fftw_plan * P, double * X, const int N, const int M);
void ft_execute_sphv_analysis(const char TRANS, const ft_sphere_fftw_plan * P, double * X, const int N, const int M);

typedef struct {
    fftw_plan planxy;
} ft_triangle_fftw_plan;

/// Destroy a \ref ft_triangle_fftw_plan.
void ft_destroy_triangle_fftw_plan(ft_triangle_fftw_plan * P);

ft_triangle_fftw_plan * ft_plan_tri_with_kind(const int N, const int M, const fftw_r2r_kind kind0, const fftw_r2r_kind kind1);
/// Plan FFTW synthesis on the triangle.
ft_triangle_fftw_plan * ft_plan_tri_synthesis(const int N, const int M);
/// Plan FFTW analysis on the triangle.
ft_triangle_fftw_plan * ft_plan_tri_analysis(const int N, const int M);

/// Execute FFTW synthesis on the triangle.
void ft_execute_tri_synthesis(const char TRANS, const ft_triangle_fftw_plan * P, double * X, const int N, const int M);
/// Execute FFTW analysis on the triangle.
void ft_execute_tri_analysis(const char TRANS, const ft_triangle_fftw_plan * P, double * X, const int N, const int M);

typedef struct {
    fftw_plan planxyz;
} ft_tetrahedron_fftw_plan;

void ft_destroy_tetrahedron_fftw_plan(ft_tetrahedron_fftw_plan * P);

ft_tetrahedron_fftw_plan * ft_plan_tet_with_kind(const int N, const int L, const int M, const fftw_r2r_kind kind0, const fftw_r2r_kind kind1, const fftw_r2r_kind kind2);
ft_tetrahedron_fftw_plan * ft_plan_tet_synthesis(const int N, const int L, const int M);
ft_tetrahedron_fftw_plan * ft_plan_tet_analysis(const int N, const int L, const int M);

void ft_execute_tet_synthesis(const char TRANS, const ft_tetrahedron_fftw_plan * P, double * X, const int N, const int L, const int M);
void ft_execute_tet_analysis(const char TRANS, const ft_tetrahedron_fftw_plan * P, double * X, const int N, const int L, const int M);

typedef struct {
    fftw_plan planr1;
    fftw_plan planr2;
    fftw_plan planr3;
    fftw_plan planr4;
    fftw_plan plantheta;
    double * Y;
} ft_disk_fftw_plan;

/// Destroy a \ref ft_disk_fftw_plan.
void ft_destroy_disk_fftw_plan(ft_disk_fftw_plan * P);

ft_disk_fftw_plan * ft_plan_disk_with_kind(const int N, const int M, const fftw_r2r_kind kind[3][1]);
/// Plan FFTW synthesis on the disk.
ft_disk_fftw_plan * ft_plan_disk_synthesis(const int N, const int M);
/// Plan FFTW analysis on the disk.
ft_disk_fftw_plan * ft_plan_disk_analysis(const int N, const int M);

/// Execute FFTW synthesis on the disk.
void ft_execute_disk_synthesis(const char TRANS, const ft_disk_fftw_plan * P, double * X, const int N, const int M);
/// Execute FFTW analysis on the disk.
void ft_execute_disk_analysis(const char TRANS, const ft_disk_fftw_plan * P, double * X, const int N, const int M);

typedef struct {
    fftw_plan planx1;
    fftw_plan planx2;
    fftw_plan plany;
} ft_rectdisk_fftw_plan;

/// Destroy a \ref ft_rectdisk_fftw_plan.
void ft_destroy_rectdisk_fftw_plan(ft_rectdisk_fftw_plan * P);

ft_rectdisk_fftw_plan * ft_plan_rectdisk_with_kind(const int N, const int M, const fftw_r2r_kind kind[3][1]);
/// Plan FFTW synthesis on the rectangularized disk.
ft_rectdisk_fftw_plan * ft_plan_rectdisk_synthesis(const int N, const int M);
/// Plan FFTW analysis on the rectangularized disk.
ft_rectdisk_fftw_plan * ft_plan_rectdisk_analysis(const int N, const int M);

/// Execute FFTW synthesis on the rectangularized disk.
void ft_execute_rectdisk_synthesis(const char TRANS, const ft_rectdisk_fftw_plan * P, double * X, const int N, const int M);
/// Execute FFTW analysis on the rectangularized disk.
void ft_execute_rectdisk_analysis(const char TRANS, const ft_rectdisk_fftw_plan * P, double * X, const int N, const int M);

typedef struct {
    fftw_plan plantheta1;
    fftw_plan plantheta2;
    fftw_plan plantheta3;
    fftw_plan plantheta4;
    fftw_plan planphi;
    double * Y;
    int S;
} ft_spinsphere_fftw_plan;

/// Destroy a \ref ft_spinsphere_fftw_plan.
void ft_destroy_spinsphere_fftw_plan(ft_spinsphere_fftw_plan * P);

int ft_get_spin_spinsphere_fftw_plan(const ft_spinsphere_fftw_plan * P);

ft_spinsphere_fftw_plan * ft_plan_spinsph_with_kind(const int N, const int M, const int S, const fftw_r2r_kind kind[2][1], const int sign);
/// Plan FFTW synthesis on the sphere with spin.
ft_spinsphere_fftw_plan * ft_plan_spinsph_synthesis(const int N, const int M, const int S);
/// Plan FFTW analysis on the sphere with spin.
ft_spinsphere_fftw_plan * ft_plan_spinsph_analysis(const int N, const int M, const int S);

/// Execute FFTW synthesis on the sphere with spin.
void ft_execute_spinsph_synthesis(const char TRANS, const ft_spinsphere_fftw_plan * P, ft_complex * X, const int N, const int M);
/// Execute FFTW analysis on the sphere with spin.
void ft_execute_spinsph_analysis(const char TRANS, const ft_spinsphere_fftw_plan * P, ft_complex * X, const int N, const int M);

typedef struct {
    ft_banded ** B;
    ft_triangular_banded ** T;
    int n;
} ft_gradient_plan;

void ft_destroy_gradient_plan(ft_gradient_plan * P);

ft_gradient_plan * ft_plan_sph_gradient(const int n);

void ft_execute_sph_gradient(ft_gradient_plan * P, double * U, double * Ut, double * Up, const int N, const int M);
void ft_execute_sph_curl(ft_gradient_plan * P, double * U, double * Ut, double * Up, const int N, const int M);

typedef struct {
    ft_triangular_banded ** T;
    ft_banded_qr ** F;
    double * X;
    int n;
} ft_helmholtzhodge_plan;

void ft_destroy_helmholtzhodge_plan(ft_helmholtzhodge_plan * P);

ft_helmholtzhodge_plan * ft_plan_sph_helmholtzhodge(const int n);

void ft_execute_sph_helmholtzhodge(ft_helmholtzhodge_plan * P, double * U1, double * U2, double * V1, double * V2, const int N, const int M);

/*!
  \brief A static struct to store an orthogonal matrix \f$Q \in \mathbb{R}^{3\times3}\f$, such that \f$Q^\top Q = I\f$.
  \f$Q\f$ has column-major storage.
*/
typedef struct {
    double Q[9];
} ft_orthogonal_transformation;

/*!
  \brief Every orthogonal matrix \f$Q \in \mathbb{R}^{3\times3}\f$ can be decomposed as a product of \f$ZYZ\f$ Euler angles and, if necessary, a reflection \f$R\f$ about the \f$xy\f$-plane.
  \f[
  Q = ZYZR,
  \f]
  where the \f$z\f$-axis rotations are:
  \f[
  Z = \begin{pmatrix} c & -s & 0\\ s & c & 0\\ 0 & 0 & 1\end{pmatrix},
  \f]
  the \f$y\f$-axis rotation is:
  \f[
  Y = \begin{pmatrix} c & 0 & -s\\ 0 & 1 & 0\\ s & 0 & c\end{pmatrix},
  \f]
  and the potential reflection is:
  \f[
  R = \begin{pmatrix} 1 & 0 & 0\\ 0 & 1 & 0\\ 0 & 0 & \pm 1\end{pmatrix}.
  \f]
  The reflection is stored as an integer `sign` corresponding to the bottom right entry.
*/
typedef struct {
    double s[3];
    double c[3];
    int sign;
} ft_ZYZR;

/*!
  \brief A static struct to store a reflection about the plane \f$w\cdot x = 0\f$ in \f$\mathbb{R}^3\f$.
*/
typedef struct {
    double w[3];
} ft_reflection;

ft_ZYZR ft_create_ZYZR(ft_orthogonal_transformation Q);

void ft_apply_ZYZR(ft_ZYZR Q, ft_orthogonal_transformation * U);

void ft_apply_reflection(ft_reflection Q, ft_orthogonal_transformation * U);

void ft_execute_sph_polar_rotation(double * A, const int N, const int M, double s, double c);

void ft_execute_sph_polar_reflection(double * A, const int N, const int M);

typedef struct {
    ft_symmetric_tridiagonal_symmetric_eigen * F11;
    ft_symmetric_tridiagonal_symmetric_eigen * F21;
    ft_symmetric_tridiagonal_symmetric_eigen * F12;
    ft_symmetric_tridiagonal_symmetric_eigen * F22;
    int l;
} ft_partial_sph_isometry_plan;

typedef struct {
    ft_partial_sph_isometry_plan ** F;
    int n;
} ft_sph_isometry_plan;

void ft_destroy_partial_sph_isometry_plan(ft_partial_sph_isometry_plan * F);

void ft_destroy_sph_isometry_plan(ft_sph_isometry_plan * F);

ft_partial_sph_isometry_plan * ft_plan_partial_sph_isometry(const int l);

ft_sph_isometry_plan * ft_plan_sph_isometry(const int n);

void ft_execute_sph_yz_axis_exchange(ft_sph_isometry_plan * J, double * A, const int N, const int M);

void ft_execute_sph_isometry(ft_sph_isometry_plan * J, ft_ZYZR Q, double * A, const int N, const int M);

void ft_execute_sph_rotation(ft_sph_isometry_plan * J, const double alpha, const double beta, const double gamma, double * A, const int N, const int M);

void ft_execute_sph_reflection(ft_sph_isometry_plan * J, ft_reflection W, double * A, const int N, const int M);

void ft_execute_sph_orthogonal_transformation(ft_sph_isometry_plan * J, ft_orthogonal_transformation Q, double * A, const int N, const int M);

#endif // FASTTRANSFORMS_H


/*
 Copyright (C) 2002 M. Marques, A. Castro, A. Rubio, G. Bertsch

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2, or (at your option)
 any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 02110-1301, USA.

*/

#include <gsl/gsl_complex_math.h>
#include <gsl/gsl_sf_erf.h>
#include <math.h>
#include <stdlib.h>

/* ------------------------------------------------------ */
gsl_complex gsl_complex_step_real (gsl_complex a)
{
  gsl_complex z;

  if (GSL_REAL(a) < 0)
    {
      GSL_SET_COMPLEX (&z, 0, 0);
    }
  else
    {
      GSL_SET_COMPLEX (&z, 1, 0);
    }

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_min_real (gsl_complex a, gsl_complex b)
{
  gsl_complex z;
  double min;

  /* just consider real parts */
  min = GSL_REAL(a) < GSL_REAL(b) ? GSL_REAL(a) : GSL_REAL(b);
  GSL_SET_COMPLEX (&z, min, 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_max_real (gsl_complex a, gsl_complex b)
{
  gsl_complex z;
  double max;

  /* just consider real parts */
  max = GSL_REAL(a) > GSL_REAL(b) ? GSL_REAL(a) : GSL_REAL(b);
  GSL_SET_COMPLEX (&z, max, 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_carg (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, gsl_complex_arg(a), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_cabs (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, gsl_complex_abs(a), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_cabs2 (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, gsl_complex_abs2(a), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_clogabs (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, gsl_complex_logabs(a), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_erf (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, gsl_sf_erf(GSL_REAL(a)), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_arctan2 (gsl_complex a, gsl_complex b)
{
  gsl_complex z, p;

  if(GSL_REAL(b) != 0.0)
    {
      z = gsl_complex_arctan(gsl_complex_div(a, b));
      if(GSL_REAL(b) < 0.0){
	GSL_SET_COMPLEX (&p, M_PI, 0);
	if(GSL_REAL(a) >= 0.0)
	  z = gsl_complex_add(z, p);
	else
	  z = gsl_complex_sub(z, p);
      }
    }
  else
    {
      if(GSL_REAL(a) >= 0.0)
	{
	  GSL_SET_COMPLEX (&z, M_PI/2.0, 0.0);
	}
      else
	{
	  GSL_SET_COMPLEX (&z, -M_PI/2.0, 0.0);
	}
    }

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_realpart (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, GSL_REAL(a), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_imagpart (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, GSL_IMAG(a), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_round (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, trunc(GSL_REAL(a)), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_ceiling (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, ceil(GSL_REAL(a)), 0);

  return z;
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_floor (gsl_complex a)
{
  gsl_complex z;

  GSL_SET_COMPLEX (&z, floor(GSL_REAL(a)), 0);

  return z;
}

/* ------------------------------------------------------ */
void gsl_complex_rand_seed(long a)
{
  srandom(a);
}

/* ------------------------------------------------------ */
gsl_complex gsl_complex_rand (gsl_complex a)
{

  double r = random()/((double) RAND_MAX);
  return gsl_complex_rect(r, 0.0);
}


#include <stdio.h>
#include "sweeny_dc.h"
#include <gsl/gsl_rng.h>
#include <math.h>
#include "../src/dyncon.h"
#include <ctype.h>

#define  MIN(a,b) a<=b ? a: b
#define MAX(a,b) a>=b? a:b
static char verbose = 0;
static char equilibration=1;
static char setup=0;

static __u32 DX;
static __u32 seed;
static __u32 cutoff;
static __u32 steps;
static double beta;
static double coupling;
static double q;
static double rnd_num;

static double v;
static __u32 j=0;
static __s8 dB = 0,dN=0;
static gsl_rng *r;
static Edge *cEdge;
static double rcWeights[4]; // array with precalculated mc weights
static double p_min_del,p_max_del, p_min_ins,p_max_ins;

static __u32 *num_bonds, *num_cluster, *size_giant;
static __u64 *sec_cs_moment,*four_cs_moment;

/******************************************************************************
 *****************************************************************************/
static void extract_observables(__u32 i) {
    static __u32 clust_cnt=0;
    static __u64 sum=0,sum_2=0;
    static __u32 maxc=0;
    static s_tree *c;
    num_bonds[i] = nte + te;
    c = levels[0]->head;
    while(c) {
        clust_cnt++;
        sum += pow(c->root->n,2);//*c->root->n;
        sum_2 += pow(c->root->n,4);
        if(c->root->n > maxc)
            maxc = c->root->n;
        c = c->next;
    }
    num_cluster[i] = clust_cnt;
    size_giant[i] = maxc;
    sec_cs_moment[i] = sum;
    four_cs_moment[i] = sum_2;
    sum=sum_2=0;
    maxc = 0;
    clust_cnt=0;
}

/******************************************************************************
 *****************************************************************************/

static inline void mcStep(void) {
    // get a randomly selected edge
    cEdge = &adjMatrix[gsl_rng_uniform_int(r,2*DX*DX)];

	rnd_num = gsl_rng_uniform(r);
 	if(ACTIVE_EDGE(cEdge)) { //edge is active hence delete it
	  dB = -1;
	  if(rnd_num < p_min_del) {
	    delete_dc(cEdge);
	  }
	  else {
	      if(rnd_num < p_max_del) {
		dN = delete_dc(cEdge);
		if(rnd_num >= rcWeights[dB == -1? dN : 2-dN])
		  insert_dc(cEdge);
	      }

	  }

	}
      else {
        // Insert cedge. If adjacent vertices are already connected, the new
        // edge will be a non-tree edge, i.e. dN = 0
        dB = 1;
        if(rnd_num < p_min_ins) {
	  insert_dc(cEdge);
	}
	else {
	  if(rnd_num < p_max_ins) {
	    dN = connected_dc_e(cEdge,1) ? 0: -1;
	    dB = 1;
	    if(rnd_num < rcWeights[dB == -1? dN : 2-dN])
		insert_dc(cEdge);
	  }

	}
      }

}
/******************************************************************************
 *****************************************************************************/
static inline void sweep() {
    for(j=0;j<2*DX*DX;j++)mcStep();
}
/******************************************************************************
 *****************************************************************************/
char init_sweeny_dc(double _q,unsigned int _l,double _beta,double _coupl,
        unsigned int _cutoff,unsigned _tslength,unsigned int rng_seed,
        void *ts_0,void *ts_1,void * ts_2,void *ts_3, void *ts_4) {

    q = _q;
    DX = _l;
    beta = _beta;
    coupling = _coupl;
    cutoff = _cutoff;
    steps = _tslength;
    seed = rng_seed;
    num_bonds = (__u32 *)ts_0;
    num_cluster = (__u32 *)ts_1;
    size_giant = (__u32 *)ts_2;
    sec_cs_moment = (__u64 *)ts_3;
    four_cs_moment = (__u64 *)ts_4;

    r = gsl_rng_alloc(gsl_rng_mt19937);
    gsl_rng_set(r,seed);
    v = exp(coupling*beta) - 1.; // >0
    rcWeights[0] = pow(v,-1); //db==-1,dN==0
    rcWeights[1] = rcWeights[0]*q; //db==-1,dN=1
    rcWeights[2] = v; //db==1,dN==0
    rcWeights[3] = v*pow(q,-1); //db==1,dN==-1
    p_min_del = MIN(rcWeights[0],rcWeights[1]);
    p_max_del = MAX(rcWeights[0],rcWeights[1]);
    p_min_ins = MIN(rcWeights[2],rcWeights[3]);
    p_max_ins = MAX(rcWeights[2],rcWeights[3]);
    return setup=(r && init_dc(DX));

}

/******************************************************************************
 *****************************************************************************/
void destroy_sweeny_dc(void) {
    if(setup) {
        gsl_rng_free(r);
        destroy_dc();
    }
    setup=0;
}
/******************************************************************************
 *****************************************************************************/
char simulate_sweeny_dc(void) {
    if(!setup) return 0;
    __u32 i;
    for(i=0;i<cutoff;i++)sweep();
    if(verbose)
        printf("Equilibration done!\n");
    equilibration=0;
    for(i=0;i<steps;i++){
        sweep();
        extract_observables(i);
    }
    return 1;
}
/******************************************************************************
 *****************************************************************************/


/* multimin/steepest_descent.c
 *
 * Copyright (C) 1996, 1997, 1998, 1999, 2000 Fabrice Rossi
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/* steepest_descent.c -- the steepest descent algorithm */

#include <config.h>
#include <gsl/gsl_multimin.h>
#include <gsl/gsl_blas_types.h>
#include <gsl/gsl_blas.h>

typedef struct
{
  double step;
  double max_step;
  gsl_vector *x1;
  gsl_vector *g1;
}
steepest_descent_state_t;

static int
steepest_descent_alloc (void *vstate, size_t n)
{
  steepest_descent_state_t *state = (steepest_descent_state_t *) vstate;

  state->x1 = gsl_vector_alloc (n);

  if (state->x1 == NULL)
    {
      GSL_ERROR ("failed to allocate space for x1", GSL_ENOMEM);
    }

  state->g1 = gsl_vector_alloc (n);

  if (state->g1 == NULL)
    {
      gsl_vector_free (state->x1);
      GSL_ERROR ("failed to allocate space for g1", GSL_ENOMEM);
    }

  return GSL_SUCCESS;
}

static int
steepest_descent_set (void *vstate, gsl_multimin_function_fdf * fdf,
                      const gsl_vector * x, double *f,
                      gsl_vector * gradient, double step_size)
{
  steepest_descent_state_t *state = (steepest_descent_state_t *) vstate;

  GSL_MULTIMIN_FN_EVAL_F_DF (fdf, x, f, gradient);

  state->step = step_size;
  state->max_step = step_size;

  return GSL_SUCCESS;
}

static void
steepest_descent_free (void *vstate)
{
  steepest_descent_state_t *state = (steepest_descent_state_t *) vstate;

  gsl_vector_free (state->x1);
  gsl_vector_free (state->g1);
}

static int
steepest_descent_restart (void *vstate)
{
  steepest_descent_state_t *state = (steepest_descent_state_t *) vstate;

  state->step = state->max_step;

  return GSL_SUCCESS;
}

static int
steepest_descent_iterate (void *vstate, gsl_multimin_function_fdf * fdf,
                          gsl_vector * x, double *f,
                          gsl_vector * gradient, gsl_vector * dx)
{
  steepest_descent_state_t *state = (steepest_descent_state_t *) vstate;

  gsl_vector *x1 = state->x1;
  gsl_vector *g1 = state->g1;

  double f0 = *f;
  double f1, fm, f_trial;
  double step0 = 0.0, step1, stepm, step_trial, step = state->step;
  size_t iter = 0;

  /* compute new trial point at x1= x - step * dir, where dir is the
     normalized gradient */

  double gnorm = gsl_blas_dnrm2 (gradient);

  gsl_vector_set_zero (dx);
  gsl_blas_daxpy (-step / gnorm, gradient, dx);

  gsl_vector_memcpy (x1, x);
  gsl_blas_daxpy (1.0, dx, x1);

  /* evaluate function and gradient at new point x1 */

  GSL_MULTIMIN_FN_EVAL_F_DF (fdf, x1, &f1, g1);

  if (f1 < f0)
    {
      state->step = 2.0 * step;

      gsl_vector_memcpy (x, x1);
      gsl_vector_memcpy (gradient, g1);

      *f = f1;

      return GSL_SUCCESS;
    }

  step1 = step;

trial:

  stepm = 0.5 * step1;

  gsl_vector_set_zero (dx);
  gsl_blas_daxpy (-stepm / gnorm, gradient, dx);

  gsl_vector_memcpy (x1, x);
  gsl_blas_daxpy (1.0, dx, x1);

  fm = GSL_MULTIMIN_FN_EVAL_F (fdf, x1);
  printf("trying stepm = %.18e fm=%g\n", stepm, fm);
  if (fm >= f0)
    {
      /* downhill step failed, reduce step-size and try again */
      f1 = fm;
      step1 = stepm;
      goto trial;
    }

  /* We now have a triplet (0,f0) (stepm, fm) (step1, f1) */

minimize:
  iter++;

  if ((stepm - step0) > (step1 - stepm))
    {
      step_trial = stepm - 0.38 * (stepm - step0);
    }
  else
    {
      step_trial = stepm + 0.38 * (step1 - stepm);
    }

  gsl_vector_set_zero (dx);
  gsl_blas_daxpy (-step_trial / gnorm, gradient, dx);

  gsl_vector_memcpy (x1, x);
  gsl_blas_daxpy (1.0, dx, x1);

  f_trial = GSL_MULTIMIN_FN_EVAL_F (fdf, x1);

  if (f_trial > fm)
    {
      if (step_trial < stepm)
        {
          step0 = step_trial;
          f0 = f_trial;
        }
      else
        {
          step1 = step_trial;
          f1 = f_trial;
        }
    }
  else
    {
      if (step_trial < stepm)
        {
          step1 = stepm;
          f1 = fm;
        }
      else
        {
          step0 = stepm;
          f0 = fm;
        }

      stepm = step_trial;
      fm = f_trial;
    }

  printf("f = %.18e\n", fm);

  if (iter > 100)
    {
      gsl_vector_set_zero (dx);
      gsl_blas_daxpy (-stepm / gnorm, gradient, dx);
      gsl_blas_daxpy (1.0, dx, x);

      GSL_MULTIMIN_FN_EVAL_DF (fdf, x, gradient);
      *f = fm;
      state->step = stepm;

      return GSL_SUCCESS;
    }

  goto minimize;
}

static const gsl_multimin_fdfminimizer_type steepest_descent_type =
  { "steepest_descent",         /* name */
  sizeof (steepest_descent_state_t),
  &steepest_descent_alloc,
  &steepest_descent_set,
  &steepest_descent_iterate,
  &steepest_descent_restart,
  &steepest_descent_free
};

const gsl_multimin_fdfminimizer_type
  *gsl_multimin_fdfminimizer_steepest_descent = &steepest_descent_type;


/*
 * C version of Diffusive Nested Sampling (DNest4) by Brendon J. Brewer
 *
 * Yan-Rong Li, liyanrong@mail.ihep.ac.cn
 * Jun 30, 2016
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#include <string.h>
#include <mpi.h>
#include <gsl/gsl_rng.h>

#include "dnest.h"
#include "model2.h"

int which_level_update;
int num_data_points;
int num_params;
DNestFptrSet *fptrset_thismodel2;

DataType *data;
void *best_model_thismodel, *best_model_std_thismodel;

void model2()
{
  int i, argc=0, narg=6;
  char **argv;

  argv = malloc(narg*sizeof(char *));
  for(i=0; i<narg; i++)
  {
    argv[i] = malloc(200*sizeof(char));
  }
  strcpy(argv[argc++], "dnest");
  strcpy(argv[argc++], "-s");
  strcpy(argv[argc++], "restart_dnest2.txt");
  strcpy(argv[argc++], "-l"); //level-dependnet sampling
  strcpy(argv[argc++], "-g"); //tag
  strcpy(argv[argc++], "2");

  /* setup szie of modeltype, which is used for dnest */
  num_params = 3;

  /* setup number of data points and allocate memory */
  num_data_points = 30;
  data = (DataType *)malloc(num_data_points * sizeof(DataType));
  fptrset_thismodel2 = dnest_malloc_fptrset();

  /* setup functions used for dnest*/
  fptrset_thismodel2->from_prior = from_prior_thismodel2;
  fptrset_thismodel2->log_likelihoods_cal = log_likelihoods_cal_thismodel2;
  fptrset_thismodel2->log_likelihoods_cal_initial = log_likelihoods_cal_thismodel2;
  fptrset_thismodel2->log_likelihoods_cal_restart = log_likelihoods_cal_thismodel2;
  fptrset_thismodel2->perturb = perturb_thismodel2;
  fptrset_thismodel2->print_particle = print_particle_thismodel2;
  fptrset_thismodel2->restart_action = restart_action_model2;

  /* load data */
  if(thistask == 0)
  {
    data_load_thismodel2();
  }
  MPI_Bcast(data, num_data_points*sizeof(DataType), MPI_BYTE, 0, MPI_COMM_WORLD);

  /* run dnest */
  dnest(argc, argv, fptrset_thismodel2, num_params, NULL, NULL, NULL, "./", "OPTIONS2", NULL, NULL);

  /* free memory */
  free(data);
  dnest_free_fptrset(fptrset_thismodel2);

  for(i=0; i<narg; i++)
    free(argv[i]);
  free(argv);
}

/*====================================================*/
/* users responsible for following struct definitions */

void from_prior_thismodel2(void *model)
{
  double *params = (double *)model;

  params[0] = 1E3*dnest_randn();
  params[1] = 1E3*dnest_randn();

	// Log-uniform prior
  params[2] = exp(-10. + 20.*dnest_rand());

}

double log_likelihoods_cal_thismodel2(const void *model)
{
  double *params = (double *)model;
  double logL = 0.0;
  double var = params[2] * params[2];

  int i;
  double mu;

	// Conventional gaussian sampling distribution
  for(i=0; i<num_data_points; i++)
  {
    mu = params[0] * data[i].x + params[1];
    logL += -0.5*log(2*M_PI*var) - 0.5*pow(data[i].y - mu, 2.0)/var;
  }

  return logL;
}

double perturb_thismodel2(void *model)
{
  double *params = (double *)model;
  double logH = 0.0, width, limit1, limit2;
  int which = dnest_rand_int(num_params), which_level;
  int size_levels;

  which_level_update = dnest_get_which_level_update();
  size_levels = dnest_get_size_levels();

  which_level = which_level_update > (size_levels - 20)?(size_levels-20):which_level_update;
  if(which_level > 0)
  {
    limit1 = limits[(which_level-1) * num_params *2 + which *2 ];
    limit2 = limits[(which_level-1) * num_params *2 + which *2 + 1];
    width = (limit2 - limit1);
  }

	if(which == 0)
	{
    if(which_level_update == 0)
    {
      width = 1E3;
    }
		logH -= -0.5*pow(params[0]/1E3, 2);
		params[0] += width*dnest_randh();
		logH += -0.5*pow(params[0]/1E3, 2);
	}
	else if(which == 1)
	{
    if(which_level_update == 0)
    {
      width = 1E3;
    }

		logH -= -0.5*pow(params[1]/1E3, 2);
		params[1] += width*dnest_randh();
		logH += -0.5*pow(params[1]/1E3, 2);
	}
	else
	{
    if(which_level_update == 0)
    {
      limit1 = -10.0;
      limit2 = 10.0;
      width = 20.0;
    }
    else
    {
      limit1 = log(limit1);
      limit2 = log(limit2);
      width = limit2 - limit1;
    }

		// Usual log-uniform prior trick
		params[2] = log(params[2]);
		params[2] += width*dnest_randh();
		dnest_wrap(&(params[2]), -10.0, 10.0);
		params[2] = exp(params[2]);
	}

  return logH;
}
/*=======================================================*/

void print_particle_thismodel2(FILE *fp, const void *model)
{
  int i;
  double *params = (double *)model;
  for(i=0; i<num_params; i++)
  {
    fprintf(fp, "%f ", params[i]);
  }
  fprintf(fp, "\n");
}

void data_load_thismodel2()
{
  FILE *fp;
  int i;

  fp = fopen("road.txt", "r");
  if(fp == NULL)
  {
    fprintf(stderr, "ERROR: Cannot open file road.txt.\n");
    exit(0);
  }

  for(i=0; i<num_data_points; i++)
  {
    fscanf(fp, "%lf %lf\n", &data[i].x, &data[i].y);
    //printf("%f %f\n", data[i].x, data[i].y);
  }
  fclose(fp);
}

/*========================================================*/

void restart_action_model2(int iflag)
{
  return;
}


//Gets deltas (first order differences) of X,
//which is assumed to be pre-allocated of double size
//so that the appended rows/cols will hold the deltas.
//Thus, for dim==0, C must be even, with C/2 the original ncols of X.
//And,  for dim==1, R must be even, with R/2 the original nrows of X.

//I implement this just like FIR for speed and shorter code,
//except non-causal and mid-sample of B is 0,
//so I don't explicitly make B (e.g., B[n] just equals sc*n).

//Note that this may treat edge samples differently than other code.
//But this could be changed with a few lines of additional code here,
//without changing the super-efficient FIR implementation.
//That is, since out-of-range samps were assumed to be 0, nothing was
//added to Y for them, so can just add something later.

#include <stdio.h>
#include <cblas.h>

#ifdef __cplusplus
namespace ov {
extern "C" {
#endif

int add_deltas_s (float *X, const int iscolmajor, const int R, const int C, const int dim, const int N);
int add_deltas_d (double *X, const int iscolmajor, const int R, const int C, const int dim, const int N);

int add_deltas_s (float *X, const int iscolmajor, const int R, const int C, const int dim, const int N)
{
    const float z = 0.0f;
    const int No = R*C/2;
    int r, c, n;
    float sc = 1.0f;

    //Checks
    if (R<1) { fprintf(stderr,"error in add_deltas_s: R (nrows X) must be positive\n"); return 1; }
    if (C<1) { fprintf(stderr,"error in add_deltas_s: C (ncols X) must be positive\n"); return 1; }
    if (N<1) { fprintf(stderr,"error in add_deltas_s: N (delta winlength) must be positive\n"); return 1; }
    if (dim==0 && C%2!=0) { fprintf(stderr,"error in add_deltas_s: C (ncols X) must be even for dim==0\n"); return 1; }
    if (dim==1 && R%2!=0) { fprintf(stderr,"error in add_deltas_s: R (nrows X) must be even for dim==1\n"); return 1; }

    //Get sc (normalizer)
    for (n=2; n<=N; n++) { sc += n*n; }
    sc = 0.5f/sc;

    if (dim==0)
    {
        if (iscolmajor)
        {
            cblas_scopy(No,&z,0,&X[No],1);
            for (c=0; c<C/2; c++)
            {
                for (n=1; n<=N; n++)
                {
                    cblas_saxpy(n,-sc*n,&X[c*R],0,&X[No+c*R],1);        //beg edge samps
                    cblas_saxpy(R-n,-sc*n,&X[c*R],1,&X[No+c*R+n],1);    //past samps
                    cblas_saxpy(R-n,sc*n,&X[c*R+n],1,&X[No+c*R],1);     //future samps
                    cblas_saxpy(n,sc*n,&X[c*R+R-1],0,&X[No+c*R+R-n],1); //end edge samps
                }
            }
        }
        else
        {
            for (c=0; c<C/2; c++)
            {
                cblas_scopy(R,&z,0,&X[C/2+c],C);
                for (n=1; n<=N; n++)
                {
                    cblas_saxpy(n,-sc*n,&X[c],0,&X[C/2+c],C);                //beg edge samps
                    cblas_saxpy(R-n,-sc*n,&X[c],C,&X[C/2+c+n*C],C);          //past samps
                    cblas_saxpy(R-n,sc*n,&X[c+n*C],C,&X[C/2+c],C);           //future samps
                    cblas_saxpy(n,sc*n,&X[c+C*(R-1)],0,&X[C/2+c+C*(R-n)],C); //end edge samps
                }
            }
        }
    }
    else if (dim==1)
    {
        if (iscolmajor)
        {
            for (r=0; r<R/2; r++)
            {
                cblas_scopy(C,&z,0,&X[R/2+r],R);
                for (n=1; n<=N; n++)
                {
                    cblas_saxpy(n,-sc*n,&X[r],0,&X[R/2+r],R);                //beg edge samps
                    cblas_saxpy(C-n,-sc*n,&X[r],R,&X[R/2+r+n*R],R);          //past samps
                    cblas_saxpy(C-n,sc*n,&X[r+n*R],R,&X[R/2+r],R);           //future samps
                    cblas_saxpy(n,sc*n,&X[r+R*(C-1)],0,&X[R/2+r+R*(C-n)],R); //end edge samps
                }
            }
        }
        else
        {
            cblas_scopy(No,&z,0,&X[No],1);
            for (r=0; r<R/2; r++)
            {
                for (n=1; n<=N; n++)
                {
                    cblas_saxpy(n,-sc*n,&X[r*C],0,&X[No+r*C],1);        //beg edge samps
                    cblas_saxpy(C-n,-sc*n,&X[r*C],1,&X[No+r*C+n],1);    //past samps
                    cblas_saxpy(C-n,sc*n,&X[r*C+n],1,&X[No+r*C],1);     //future samps
                    cblas_saxpy(n,sc*n,&X[r*C+C-1],0,&X[No+r*C+C-n],1); //end edge samps
                }
            }
        }
    }
    else
    {
        fprintf(stderr,"error in add_deltas_s: dim must be 0 or 1.\n"); return 1;
    }

    return 0;
}

int add_deltas_d (double *X, const int iscolmajor, const int R, const int C, const int dim, const int N)
{
    const double z = 0.0;
    const int No = R*C/2;
    int r, c, n;
    double sc = 1.0;

    //Checks
    if (R<1) { fprintf(stderr,"error in add_deltas_d: R (nrows X) must be positive\n"); return 1; }
    if (C<1) { fprintf(stderr,"error in add_deltas_d: C (ncols X) must be positive\n"); return 1; }
    if (N<1) { fprintf(stderr,"error in add_deltas_d: N (delta winlength) must be positive\n"); return 1; }
    if (dim==0 && C%2!=0) { fprintf(stderr,"error in add_deltas_d: C (ncols X) must be even for dim==0\n"); return 1; }
    if (dim==1 && R%2!=0) { fprintf(stderr,"error in add_deltas_d: R (nrows X) must be even for dim==1\n"); return 1; }

    //Get sc (normalizer)
    for (n=2; n<=N; n++) { sc += n*n; }
    sc = 0.5/sc;

    if (dim==0)
    {
        if (iscolmajor)
        {
            cblas_dcopy(No,&z,0,&X[No],1);
            for (c=0; c<C/2; c++)
            {
                for (n=1; n<=N; n++)
                {
                    cblas_daxpy(n,-sc*n,&X[c*R],0,&X[No+c*R],1);        //beg edge samps
                    cblas_daxpy(R-n,-sc*n,&X[c*R],1,&X[No+c*R+n],1);    //past samps
                    cblas_daxpy(R-n,sc*n,&X[c*R+n],1,&X[No+c*R],1);     //future samps
                    cblas_daxpy(n,sc*n,&X[c*R+R-1],0,&X[No+c*R+R-n],1); //end edge samps
                }
            }
        }
        else
        {
            for (c=0; c<C/2; c++)
            {
                cblas_dcopy(R,&z,0,&X[C/2+c],C);
                for (n=1; n<=N; n++)
                {
                    cblas_daxpy(n,-sc*n,&X[c],0,&X[C/2+c],C);                //beg edge samps
                    cblas_daxpy(R-n,-sc*n,&X[c],C,&X[C/2+c+n*C],C);          //past samps
                    cblas_daxpy(R-n,sc*n,&X[c+n*C],C,&X[C/2+c],C);           //future samps
                    cblas_daxpy(n,sc*n,&X[c+C*(R-1)],0,&X[C/2+c+C*(R-n)],C); //end edge samps
                }
            }
        }
    }
    else if (dim==1)
    {
        if (iscolmajor)
        {
            for (r=0; r<R/2; r++)
            {
                cblas_dcopy(C,&z,0,&X[R/2+r],R);
                for (n=1; n<=N; n++)
                {
                    cblas_daxpy(n,-sc*n,&X[r],0,&X[R/2+r],R);                //beg edge samps
                    cblas_daxpy(C-n,-sc*n,&X[r],R,&X[R/2+r+n*R],R);          //past samps
                    cblas_daxpy(C-n,sc*n,&X[r+n*R],R,&X[R/2+r],R);           //future samps
                    cblas_daxpy(n,sc*n,&X[r+R*(C-1)],0,&X[R/2+r+R*(C-n)],R); //end edge samps
                }
            }
        }
        else
        {
            cblas_dcopy(No,&z,0,&X[No],1);
            for (r=0; r<R/2; r++)
            {
                for (n=1; n<=N; n++)
                {
                    cblas_daxpy(n,-sc*n,&X[r*C],0,&X[No+r*C],1);        //beg edge samps
                    cblas_daxpy(C-n,-sc*n,&X[r*C],1,&X[No+r*C+n],1);    //past samps
                    cblas_daxpy(C-n,sc*n,&X[r*C+n],1,&X[No+r*C],1);     //future samps
                    cblas_daxpy(n,sc*n,&X[r*C+C-1],0,&X[No+r*C+C-n],1); //end edge samps
                }
            }
        }
    }
    else
    {
        fprintf(stderr,"error in add_deltas_d: dim must be 0 or 1.\n"); return 1;
    }

    return 0;
}

#ifdef __cplusplus
}
}
#endif


//Converts LSFs (line spectral frequencies) to polynomial representation along cols or rows of X.
//Works by getting p_sym and p_asym and then roots and then lsf2poly.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <cblas.h>

#ifdef __cplusplus
namespace ov {
extern "C" {
#endif

int lsf2poly_s (float *Y, const float *X, const int iscolmajor, const int R, const int C, const int dim);
int lsf2poly_d (double *Y, const double *X, const int iscolmajor, const int R, const int C, const int dim);

int lsf2poly_s (float *Y, const float *X, const int iscolmajor, const int R, const int C, const int dim)
{
    const float z = 0.0f, o = 1.0f;
    const int F = (dim==0) ? R : C;  //num LSFs
    const int nr = 2*F + 2;          //length roots
    const int P = F + 2;             //length psym and pasym
    const int Po = P - 1;            //num output poly coeffs
    int r, c, p, f;
    float *roots, *psym, *pasym;

    //Checks
    if (R<1) { fprintf(stderr,"error in lsf2poly_s: nrows X must be positive\n"); return 1; }
    if (C<1) { fprintf(stderr,"error in lsf2poly_s: ncols X must be positive\n"); return 1; }

    //Initialize
    if (!(psym=(float *)malloc((size_t)(2*P)*sizeof(float)))) { fprintf(stderr,"error in lsf2poly_s: problem with malloc. "); perror("malloc"); return 1; }
    if (!(pasym=(float *)malloc((size_t)(2*P)*sizeof(float)))) { fprintf(stderr,"error in lsf2poly_s: problem with malloc. "); perror("malloc"); return 1; }
    if (!(roots=(float *)malloc((size_t)(2*nr)*sizeof(float)))) { fprintf(stderr,"error in lsf2poly_s: problem with malloc. "); perror("malloc"); return 1; }
    roots[0] = o; roots[nr] = -o; roots[1] = roots[nr+1] = z;

    if (dim==0)
    {
        if (iscolmajor)
        {
            //cblas_scopy(C,&o,0,Y,Po);
            for (c=0; c<C; c++)
            {
                for (f=0; f<F; f++)
                {
                    roots[2*f+2] = roots[4*F-2*f+2] = cosf(X[f+c*R]);
                    roots[2*f+3] = sinf(X[f+c*R]); roots[4*F-2*f+3] = -roots[2*f+3];
                }
                cblas_scopy(2*P,&z,0,psym,1); cblas_scopy(2*P,&z,0,pasym,1);
                psym[0] = pasym[0] = o;
                for (r=0; r<P-1; r++)
                {
                    for (p=r+1; p>0; p--)
                    {
                        pasym[2*p] = fmaf(-roots[4*r],pasym[2*p-2],fmaf(roots[4*r+1],pasym[2*p-1],pasym[2*p]));
                        pasym[2*p+1] = fmaf(-roots[4*r],pasym[2*p-1],fmaf(-roots[4*r+1],pasym[2*p-2],pasym[2*p+1]));
                        psym[2*p] = fmaf(-roots[4*r+2],psym[2*p-2],fmaf(roots[4*r+3],psym[2*p-1],psym[2*p]));
                        psym[2*p+1] = fmaf(-roots[4*r+2],psym[2*p-1],fmaf(-roots[4*r+3],psym[2*p-2],psym[2*p+1]));
                    }
                }
                for (r=0; r<Po; r++) { Y[r+c*Po] = 0.5f*(psym[2*(Po-r)]-pasym[2*(Po-r)]); }
            }
        }
        else
        {
            //cblas_scopy(C,&o,0,Y,1);
            for (c=0; c<C; c++)
            {
                for (f=0; f<F; f++)
                {
                    roots[2*f+2] = roots[4*F-2*f+2] = cosf(X[c+f*C]);
                    roots[2*f+3] = sinf(X[c+f*C]); roots[4*F-2*f+3] = -roots[2*f+3];
                }
                cblas_scopy(2*P,&z,0,psym,1); cblas_scopy(2*P,&z,0,pasym,1);
                psym[0] = pasym[0] = o;
                for (r=0; r<P-1; r++)
                {
                    for (p=r+1; p>0; p--)
                    {
                        pasym[2*p] = fmaf(-roots[4*r],pasym[2*p-2],fmaf(roots[4*r+1],pasym[2*p-1],pasym[2*p]));
                        pasym[2*p+1] = fmaf(-roots[4*r],pasym[2*p-1],fmaf(-roots[4*r+1],pasym[2*p-2],pasym[2*p+1]));
                        psym[2*p] = fmaf(-roots[4*r+2],psym[2*p-2],fmaf(roots[4*r+3],psym[2*p-1],psym[2*p]));
                        psym[2*p+1] = fmaf(-roots[4*r+2],psym[2*p-1],fmaf(-roots[4*r+3],psym[2*p-2],psym[2*p+1]));
                    }
                }
                for (r=0; r<Po; r++) { Y[c+r*C] = 0.5f*(psym[2*(Po-r)]-pasym[2*(Po-r)]); }
            }
        }
    }
    else if (dim==1)
    {
        if (iscolmajor)
        {
            //cblas_scopy(R,&o,0,Y,1);
            for (r=0; r<R; r++)
            {
                for (f=0; f<F; f++)
                {
                    roots[2*f+2] = roots[4*F-2*f+2] = cosf(X[r+f*R]);
                    roots[2*f+3] = sinf(X[r+f*R]); roots[4*F-2*f+3] = -roots[2*f+3];
                }
                cblas_scopy(2*P,&z,0,psym,1); cblas_scopy(2*P,&z,0,pasym,1);
                psym[0] = pasym[0] = o;
                for (c=0; c<P-1; c++)
                {
                    for (p=c+1; p>0; p--)
                    {
                        pasym[2*p] = fmaf(-roots[4*c],pasym[2*p-2],fmaf(roots[4*c+1],pasym[2*p-1],pasym[2*p]));
                        pasym[2*p+1] = fmaf(-roots[4*c],pasym[2*p-1],fmaf(-roots[4*c+1],pasym[2*p-2],pasym[2*p+1]));
                        psym[2*p] = fmaf(-roots[4*c+2],psym[2*p-2],fmaf(roots[4*c+3],psym[2*p-1],psym[2*p]));
                        psym[2*p+1] = fmaf(-roots[4*c+2],psym[2*p-1],fmaf(-roots[4*c+3],psym[2*p-2],psym[2*p+1]));
                    }
                }
                for (c=0; c<Po; c++) { Y[r+c*R] = 0.5f*(psym[2*(Po-c)]-pasym[2*(Po-c)]); }
            }
        }
        else
        {
            //cblas_scopy(R,&o,0,Y,Po);
            for (r=0; r<R; r++)
            {
                for (f=0; f<F; f++)
                {
                    roots[2*f+2] = roots[4*F-2*f+2] = cosf(X[f+r*C]);
                    roots[2*f+3] = sinf(X[f+r*C]); roots[4*F-2*f+3] = -roots[2*f+3];
                }
                cblas_scopy(2*P,&z,0,psym,1); cblas_scopy(2*P,&z,0,pasym,1);
                psym[0] = pasym[0] = o;
                for (c=0; c<P-1; c++)
                {
                    for (p=c+1; p>0; p--)
                    {
                        pasym[2*p] = fmaf(-roots[4*c],pasym[2*p-2],fmaf(roots[4*c+1],pasym[2*p-1],pasym[2*p]));
                        pasym[2*p+1] = fmaf(-roots[4*c],pasym[2*p-1],fmaf(-roots[4*c+1],pasym[2*p-2],pasym[2*p+1]));
                        psym[2*p] = fmaf(-roots[4*c+2],psym[2*p-2],fmaf(roots[4*c+3],psym[2*p-1],psym[2*p]));
                        psym[2*p+1] = fmaf(-roots[4*c+2],psym[2*p-1],fmaf(-roots[4*c+3],psym[2*p-2],psym[2*p+1]));
                    }
                }
                for (c=0; c<Po; c++) { Y[c+r*Po] = 0.5f*(psym[2*(Po-c)]-pasym[2*(Po-c)]); }
            }
        }
    }
    else
    {
        fprintf(stderr,"error in lsf2poly_s: dim must be 0 or 1.\n"); return 1;
    }

    //Exit
    free(roots); free(psym); free(pasym);
    return 0;
}

int lsf2poly_d (double *Y, const double *X, const int iscolmajor, const int R, const int C, const int dim)
{
    const double z = 0.0, o = 1.0;
    const int F = (dim==0) ? R : C;  //num LSFs
    const int nr = 2*F + 2;          //length roots
    const int P = F + 2;             //length psym and pasym
    const int Po = P - 1;            //num output poly coeffs
    int r, c, p, f;
    double *roots, *psym, *pasym;

    //Checks
    if (R<1) { fprintf(stderr,"error in lsf2poly_d: nrows X must be positive\n"); return 1; }
    if (C<1) { fprintf(stderr,"error in lsf2poly_d: ncols X must be positive\n"); return 1; }

    //Initialize
    if (!(psym=(double *)malloc((size_t)(2*P)*sizeof(double)))) { fprintf(stderr,"error in lsf2poly_d: problem with malloc. "); perror("malloc"); return 1; }
    if (!(pasym=(double *)malloc((size_t)(2*P)*sizeof(double)))) { fprintf(stderr,"error in lsf2poly_d: problem with malloc. "); perror("malloc"); return 1; }
    if (!(roots=(double *)malloc((size_t)(2*nr)*sizeof(double)))) { fprintf(stderr,"error in lsf2poly_d: problem with malloc. "); perror("malloc"); return 1; }
    roots[0] = o; roots[nr] = -o; roots[1] = roots[nr+1] = z;

    if (dim==0)
    {
        if (iscolmajor)
        {
            for (c=0; c<C; c++)
            {
                for (f=0; f<F; f++)
                {
                    roots[2*f+2] = roots[4*F-2*f+2] = cos(X[f+c*R]);
                    roots[2*f+3] = sin(X[f+c*R]); roots[4*F-2*f+3] = -roots[2*f+3];
                }
                cblas_dcopy(2*P,&z,0,psym,1); cblas_dcopy(2*P,&z,0,pasym,1);
                psym[0] = pasym[0] = o;
                for (r=0; r<P-1; r++)
                {
                    for (p=r+1; p>0; p--)
                    {
                        pasym[2*p] = fma(-roots[4*r],pasym[2*p-2],fma(roots[4*r+1],pasym[2*p-1],pasym[2*p]));
                        pasym[2*p+1] = fma(-roots[4*r],pasym[2*p-1],fma(-roots[4*r+1],pasym[2*p-2],pasym[2*p+1]));
                        psym[2*p] = fma(-roots[4*r+2],psym[2*p-2],fma(roots[4*r+3],psym[2*p-1],psym[2*p]));
                        psym[2*p+1] = fma(-roots[4*r+2],psym[2*p-1],fma(-roots[4*r+3],psym[2*p-2],psym[2*p+1]));
                    }
                }
                for (r=0; r<Po; r++) { Y[r+c*Po] = 0.5*(psym[2*(Po-r)]-pasym[2*(Po-r)]); }
            }
        }
        else
        {
            for (c=0; c<C; c++)
            {
                for (f=0; f<F; f++)
                {
                    roots[2*f+2] = roots[4*F-2*f+2] = cos(X[c+f*C]);
                    roots[2*f+3] = sin(X[c+f*C]); roots[4*F-2*f+3] = -roots[2*f+3];
                }
                cblas_dcopy(2*P,&z,0,psym,1); cblas_dcopy(2*P,&z,0,pasym,1);
                psym[0] = pasym[0] = o;
                for (r=0; r<P-1; r++)
                {
                    for (p=r+1; p>0; p--)
                    {
                        pasym[2*p] = fma(-roots[4*r],pasym[2*p-2],fma(roots[4*r+1],pasym[2*p-1],pasym[2*p]));
                        pasym[2*p+1] = fma(-roots[4*r],pasym[2*p-1],fma(-roots[4*r+1],pasym[2*p-2],pasym[2*p+1]));
                        psym[2*p] = fma(-roots[4*r+2],psym[2*p-2],fma(roots[4*r+3],psym[2*p-1],psym[2*p]));
                        psym[2*p+1] = fma(-roots[4*r+2],psym[2*p-1],fma(-roots[4*r+3],psym[2*p-2],psym[2*p+1]));
                    }
                }
                for (r=0; r<Po; r++) { Y[c+r*C] = 0.5*(psym[2*(Po-r)]-pasym[2*(Po-r)]); }
            }
        }
    }
    else if (dim==1)
    {
        if (iscolmajor)
        {
            for (r=0; r<R; r++)
            {
                for (f=0; f<F; f++)
                {
                    roots[2*f+2] = roots[4*F-2*f+2] = cos(X[r+f*R]);
                    roots[2*f+3] = sin(X[r+f*R]); roots[4*F-2*f+3] = -roots[2*f+3];
                }
                cblas_dcopy(2*P,&z,0,psym,1); cblas_dcopy(2*P,&z,0,pasym,1);
                psym[0] = pasym[0] = o;
                for (c=0; c<P-1; c++)
                {
                    for (p=c+1; p>0; p--)
                    {
                        pasym[2*p] = fma(-roots[4*c],pasym[2*p-2],fma(roots[4*c+1],pasym[2*p-1],pasym[2*p]));
                        pasym[2*p+1] = fma(-roots[4*c],pasym[2*p-1],fma(-roots[4*c+1],pasym[2*p-2],pasym[2*p+1]));
                        psym[2*p] = fma(-roots[4*c+2],psym[2*p-2],fma(roots[4*c+3],psym[2*p-1],psym[2*p]));
                        psym[2*p+1] = fma(-roots[4*c+2],psym[2*p-1],fma(-roots[4*c+3],psym[2*p-2],psym[2*p+1]));
                    }
                }
                for (c=0; c<Po; c++) { Y[r+c*R] = 0.5*(psym[2*(Po-c)]-pasym[2*(Po-c)]); }
            }
        }
        else
        {
            for (r=0; r<R; r++)
            {
                for (f=0; f<F; f++)
                {
                    roots[2*f+2] = roots[4*F-2*f+2] = cos(X[f+r*C]);
                    roots[2*f+3] = sin(X[f+r*C]); roots[4*F-2*f+3] = -roots[2*f+3];
                }
                cblas_dcopy(2*P,&z,0,psym,1); cblas_dcopy(2*P,&z,0,pasym,1);
                psym[0] = pasym[0] = o;
                for (c=0; c<P-1; c++)
                {
                    for (p=c+1; p>0; p--)
                    {
                        pasym[2*p] = fma(-roots[4*c],pasym[2*p-2],fma(roots[4*c+1],pasym[2*p-1],pasym[2*p]));
                        pasym[2*p+1] = fma(-roots[4*c],pasym[2*p-1],fma(-roots[4*c+1],pasym[2*p-2],pasym[2*p+1]));
                        psym[2*p] = fma(-roots[4*c+2],psym[2*p-2],fma(roots[4*c+3],psym[2*p-1],psym[2*p]));
                        psym[2*p+1] = fma(-roots[4*c+2],psym[2*p-1],fma(-roots[4*c+3],psym[2*p-2],psym[2*p+1]));
                    }
                }
                for (c=0; c<Po; c++) { Y[c+r*Po] = 0.5*(psym[2*(Po-c)]-pasym[2*(Po-c)]); }
            }
        }
    }
    else
    {
        fprintf(stderr,"error in lsf2poly_d: dim must be 0 or 1.\n"); return 1;
    }

    //Exit
    free(roots); free(psym); free(pasym);
    return 0;
}

#ifdef __cplusplus
}
}
#endif