//---------------------------------------------------------------------------//
// Copyright (c) 2020 Mikhail Komarov <nemo@nil.foundation>
//
// 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_MULTIPRECISION_MODULAR_BACKENDS_INVERSE_HPP
#define BOOST_MULTIPRECISION_MODULAR_BACKENDS_INVERSE_HPP

#include <boost/container/vector.hpp>
#include <boost/type_traits/is_integral.hpp>

#include <nil/crypto3/multiprecision/detail/default_ops.hpp>

#include <nil/crypto3/multiprecision/cpp_int.hpp>
#include <nil/crypto3/multiprecision/cpp_int/cpp_int_config.hpp>

namespace nil {
    namespace crypto3 {
        namespace multiprecision {
            namespace backends {

                template<typename Backend>
                constexpr Backend eval_extended_euclidean_algorithm(Backend& a, Backend& b, Backend& x, Backend& y) {
                    if (eval_is_zero(a)) {
                        using ui_type = typename std::tuple_element<0, typename Backend::unsigned_types>::type;
                        x = ui_type(0u);
                        y = ui_type(1u);
                        return b;
                    }
                    Backend x1, y1, tmp = b;
                    eval_modulus(tmp, a);
                    Backend d = eval_extended_euclidean_algorithm(tmp, a, x1, y1);
                    tmp = b;
                    eval_divide(tmp, a);
                    eval_multiply(tmp, x1);
                    x = y1;
                    eval_subtract(x, tmp);
                    y = x1;
                    return d;
                }

                template<typename Backend>
                constexpr Backend eval_inverse_extended_euclidean_algorithm(const Backend& a, const Backend& m) {
                    using Backend_doubled = typename default_ops::double_precision_type<Backend>::type;

                    Backend aa = a, mm = m, x, y, g;
                    using ui_type = typename std::tuple_element<0, typename Backend::unsigned_types>::type;
                    g = eval_extended_euclidean_algorithm(aa, mm, x, y);
                    if (!eval_eq(g, ui_type(1u))) {
                        // BOOST_THROW_EXCEPTION(std::invalid_argument("eval_inverse_with_gcd: no inverse element"));
                        return ui_type(0u);
                    } else {
                        eval_modulus(x, m);
                        Backend_doubled tmp(x);
                        eval_add(tmp, m);
                        eval_modulus(tmp, m);
                        return static_cast<Backend>(tmp);
                    }
                }

                template<typename Backend>
                constexpr typename std::tuple_element<0, typename Backend::signed_types>::type
                    eval_monty_inverse(typename std::tuple_element<0, typename Backend::signed_types>::type a) {
                    using si_type = typename std::tuple_element<0, typename Backend::signed_types>::type;

                    if (a % 2 == 0) {
                        throw std::invalid_argument("monty_inverse only valid for odd integers");
                    }

                    /*
                     * From "A New Algorithm for Inversion mod p^k" by Çetin Kaya Koç
                     * https://eprint.iacr.org/2017/411.pdf sections 5 and 7.
                     */

                    si_type b = 1;
                    si_type r = 0;

                    for (size_t i = 0; i != sizeof(si_type) * CHAR_BIT; ++i) {
                        const si_type bi = b % 2;
                        r >>= 1;
                        r += bi << (sizeof(si_type) * CHAR_BIT - 1);

                        b -= a * bi;
                        b >>= 1;
                    }

                    // Now invert in addition space
                    r = (~static_cast<si_type>(0) - r) + 1;

                    return r;
                }

                template<typename Backend>
                constexpr void eval_monty_inverse(Backend& res, const Backend& a, const Backend& p, const Backend& k) {

                    using default_ops::eval_abs;
                    using default_ops::eval_gt;
                    using default_ops::eval_modulus;
                    using default_ops::eval_subtract;

                    using ui_type = typename std::tuple_element<0, typename Backend::unsigned_types>::type;
                    Backend zero = ui_type(0u);
                    Backend one = ui_type(1u);
                    Backend two = ui_type(2u);

                    /*
                     * From "A New Algorithm for Inversion mod p^k" by Çetin Kaya Koç
                     * https://eprint.iacr.org/2017/411.pdf sections 5 and 7.
                     */
                    Backend c, tmp;

                    // a^(-1) mod p:
                    c = eval_inverse_extended_euclidean_algorithm(a, p);

                    Backend bi = one, bt, i = zero, k_negone = k, xi, nextp = one;
                    eval_subtract(k_negone, one);
                    res = zero;

                    // ui_type kn = cpp_int(k_negone);

                    while (!eval_eq(i, k)) {
                        // xi:
                        xi = bi;
                        eval_multiply(xi, c);
                        eval_modulus(xi, p);

                        if (eval_get_sign(xi) < 0) {
                            tmp = xi;
                            eval_abs(tmp, tmp);
                            eval_modulus(tmp, p);
                            xi = p;
                            eval_subtract(xi, tmp);
                        }

                        // bi:
                        tmp = a;
                        eval_multiply(tmp, xi);
                        eval_subtract(bi, tmp);
                        eval_divide(bi, p);

                        // res:
                        tmp = xi;
                        eval_multiply(tmp, nextp);
                        eval_multiply(nextp, p);
                        eval_add(res, tmp);
                        eval_add(i, one);
                    }
                }

                /*
                                template <typename Backend>
                                inline void bigint_shr1(typename boost::mpl::front<typename
                   Backend::unsigned_types>::type x[], size_t x_size, size_t word_shift, size_t bit_shift)
                                {
                                   typedef typename boost::mpl::front<typename Backend::unsigned_types>::type ui_type;

                                   const size_t top = x_size >= word_shift ? (x_size - word_shift) : 0;

                                   if (top > 0)
                                      copy_mem(x, x + word_shift, top);
                                   clear_mem(x + top, std::min(word_shift, x_size));

                                   const auto   carry_mask  = CT::Mask<ui_type>::expand(bit_shift);
                                   const size_t carry_shift = carry_mask.if_set_return(BOTAN_MP_WORD_BITS - bit_shift);

                                   ui_type carry = 0;

                                   for (size_t i = 0; i != top; ++i)
                                   {
                                      const ui_type w = x[top - i - 1];
                                      x[top - i - 1]  = (w >> bit_shift) | carry;
                                      carry           = carry_mask.if_set_return(w << carry_shift);
                                   }
                                }

                                template <typename Backend>
                                inline typename boost::mpl::front<typename Backend::unsigned_types>::type
                   bigint_add2_nc( typename boost::mpl::front<typename Backend::unsigned_types>::type x[], size_t
                   x_size, const typename boost::mpl::front<typename Backend::unsigned_types>::type y[], size_t y_size)
                                {
                                   typedef typename boost::mpl::front<typename Backend::unsigned_types>::type ui_type;

                                   ui_type carry = 0;

                                   BOOST_ASSERT_MSG(x_size >= y_size, "Expected sizes");

                                   const size_t blocks = y_size - (y_size % 8);

                                   for (size_t i = 0; i != blocks; i += 8)
                                      carry = word8_add2(x + i, y + i, carry);

                                   for (size_t i = blocks; i != y_size; ++i)
                                      x[i] = word_add(x[i], y[i], &carry);

                                   for (size_t i = y_size; i != x_size; ++i)
                                      x[i] = word_add(x[i], 0, &carry);

                                   return carry;
                                }

                                template <typename Backend>
                                inline typename boost::mpl::front<typename Backend::unsigned_types>::type
                   bigint_cnd_sub( typename boost::mpl::front<typename Backend::unsigned_types>::type cnd, typename
                   boost::mpl::front<typename Backend::unsigned_types>::type x[], size_t x_size, const typename
                   boost::mpl::front<typename Backend::unsigned_types>::type y[], size_t y_size)
                                {
                                   BOOST_ASSERT_MSG(x_size >= y_size, "Expected sizes");

                                   typedef typename boost::mpl::front<typename Backend::unsigned_types>::type ui_type;

                                   const auto mask = CT::Mask<ui_type>::expand(cnd);

                                   ui_type carry = 0;

                                   const size_t blocks = y_size - (y_size % 8);
                                   ui_type      z[8]   = {0};

                                   for (size_t i = 0; i != blocks; i += 8)
                                   {
                                      carry = word8_sub3(z, x + i, y + i, carry);
                                      mask.select_n(x + i, z, x + i, 8);
                                   }

                                   for (size_t i = blocks; i != y_size; ++i)
                                   {
                                      z[0] = word_sub(x[i], y[i], &carry);
                                      x[i] = mask.select(z[0], x[i]);
                                   }

                                   for (size_t i = y_size; i != x_size; ++i)
                                   {
                                      z[0] = word_sub(x[i], 0, &carry);
                                      x[i] = mask.select(z[0], x[i]);
                                   }

                                   return mask.if_set_return(carry);
                                }

                                template <typename Backend>
                                inline typename boost::mpl::front<typename Backend::unsigned_types>::type
                   bigint_cnd_add( typename boost::mpl::front<typename Backend::unsigned_types>::type       cnd,
                                    typename boost::mpl::front<typename Backend::unsigned_types>::type       x[],
                                    typename boost::mpl::front<typename Backend::unsigned_types>::type       x_size,
                                    const typename boost::mpl::front<typename Backend::unsigned_types>::type y[], size_t
                   y_size)
                                {
                                   BOTAN_ASSERT(x_size >= y_size, "Expected sizes");

                                   typedef typename boost::mpl::front<typename Backend::unsigned_types>::type ui_type;

                                   const auto mask = CT::Mask<ui_type>::expand(cnd);

                                   ui_type carry = 0;

                                   const size_t blocks = y_size - (y_size % 8);
                                   ui_type      z[8]   = {0};

                                   for (size_t i = 0; i != blocks; i += 8)
                                   {
                                      carry = word8_add3(z, x + i, y + i, carry);
                                      mask.select_n(x + i, z, x + i, 8);
                                   }

                                   for (size_t i = blocks; i != y_size; ++i)
                                   {
                                      z[0] = word_add(x[i], y[i], &carry);
                                      x[i] = mask.select(z[0], x[i]);
                                   }

                                   for (size_t i = y_size; i != x_size; ++i)
                                   {
                                      z[0] = word_add(x[i], 0, &carry);
                                      x[i] = mask.select(z[0], x[i]);
                                   }

                                   return mask.if_set_return(carry);
                                }

                                template <typename Backend>
                                inline void bigint_cnd_abs(typename boost::mpl::front<typename
                   Backend::unsigned_types>::type cnd, typename boost::mpl::front<typename
                   Backend::unsigned_types>::type x[], size_t size)
                                {
                                   typedef typename boost::mpl::front<typename Backend::unsigned_types>::type ui_type;
                                   const auto                                                          mask =
                                CT::Mask<ui_type>::expand(cnd);

                                   ui_type carry = mask.if_set_return(1);
                                   for (size_t i = 0; i != size; ++i)
                                   {
                                      const ui_type z = word_add(~x[i], 0, &carry);
                                      x[i]            = mask.select(z, x[i]);
                                   }
                                }

                                template <typename Backend>
                                inline void bigint_cnd_swap(typename boost::mpl::front<typename
                   Backend::unsigned_types>::type cnd, typename boost::mpl::front<typename
                   Backend::unsigned_types>::type x[], typename boost::mpl::front<typename
                   Backend::unsigned_types>::type y[], size_t size)
                                {
                                   typedef typename boost::mpl::front<typename Backend::unsigned_types>::type ui_type;
                                   const auto                                                          mask =
                                CT::Mask<ui_type>::expand(cnd);

                                   for (size_t i = 0; i != size; ++i)
                                   {
                                      const ui_type a = x[i];
                                      const ui_type b = y[i];
                                      x[i]            = mask.select(b, a);
                                      y[i]            = mask.select(a, b);
                                   }
                                }

                                template <typename Backend>
                                void eval_inverse_mod_odd_modulus(Backend& res, const Backend& n, const Backend& mod)
                                {
                                   typedef typename boost::mpl::front<typename Backend::signed_types>::type   si_type;
                                   typedef typename boost::mpl::front<typename Backend::unsigned_types>::type ui_type;

                                   // Caller should assure these preconditions:
                                   BOOST_ASSERT(eval_gt(n, 0));
                                   BOOST_ASSERT(eval_gt(mod, 0));
                                   BOOST_ASSERT(eval_lt(n, mod));
                                   BOOST_ASSERT(eval_ge(mod, 3) && eval_modulus(mod, 2) == 1);*/

                /*
                                This uses a modular inversion algorithm designed by Niels Möller
                                and implemented in Nettle. The same algorithm was later also
                                adapted to GMP in mpn_sec_invert.
                                It can be easily implemented in a way that does not depend on
                                secret branches or memory lookups, providing resistance against
                                some forms of side channel attack.
                                There is also a description of the algorithm in Appendix 5 of "Fast
                                Software Polynomial Multiplication on ARM Processors using the NEON Engine"
                                by Danilo Câmara, Conrado P. L. Gouvêa, Julio López, and Ricardo
                                Dahab in LNCS 8182
                                   https://conradoplg.cryptoland.net/files/2010/12/mocrysen13.pdf
                                Thanks to Niels for creating the algorithm, explaining some things
                                about it, and the reference to the paper.
                                */
                /*
                                   const size_t mod_words = mod.size();
                                   BOOST_ASSERT_MSG(mod_words > 0, "Not empty");

                                   std::vector<ui_type> tmp_mem(5 * mod_words);

                                   ui_type* v_w   = &tmp_mem[0];
                                   ui_type* u_w   = &tmp_mem[1 * mod_words];
                                   ui_type* b_w   = &tmp_mem[2 * mod_words];
                                   ui_type* a_w   = &tmp_mem[3 * mod_words];
                                   ui_type* mp1o2 = &tmp_mem[4 * mod_words];

                                   //   ct::poison(tmp_mem.data(), tmp_mem.size());

                                   copy_mem(a_w, n.data(), std::min(n.size(), mod_words));
                                   copy_mem(b_w, mod.data(), std::min(mod.size(), mod_words));
                                   u_w[0] = 1;
                                   // v_w = 0

                                   // compute (mod + 1) / 2 which [because mod is odd] is equal to
                                   // (mod / 2) + 1
                                   copy_mem(mp1o2, mod.data(), std::min(mod.size(), mod_words));
                                   bigint_shr1(mp1o2, mod_words, 0, 1);
                                   ui_type carry = bigint_add2_nc(mp1o2, mod_words, u_w, 1);
                                   BOOST_ASSERT(carry == 0);

                                   // Only n.bits() + mod.bits() iterations are required, but avoid leaking the size of
                   n const size_t execs = 2 * eval_msb(mod);

                                   for (size_t i = 0; i != execs; ++i)
                                   {
                                      const ui_type odd_a = a_w[0] & 1;

                                      //if(odd_a) a -= b
                                      ui_type underflow = bigint_cnd_sub(odd_a, a_w, b_w, mod_words);

                                      //if(underflow) { b -= a; a = abs(a); swap(u, v); }
                                      bigint_cnd_add(underflow, b_w, a_w, mod_words);
                                      bigint_cnd_abs(underflow, a_w, mod_words);
                                      bigint_cnd_swap(underflow, u_w, v_w, mod_words);

                                      // a >>= 1
                                      bigint_shr1(a_w, mod_words, 0, 1);

                                      //if(odd_a) u -= v;
                                      ui_type borrow = bigint_cnd_sub(odd_a, u_w, v_w, mod_words);

                                      // if(borrow) u += p
                                      bigint_cnd_add(borrow, u_w, mod.data(), mod_words);

                                      const ui_type odd_u = u_w[0] & 1;

                                      // u >>= 1
                                      bigint_shr1(u_w, mod_words, 0, 1);

                                      //if(odd_u) u += mp1o2;
                                      bigint_cnd_add(odd_u, u_w, mp1o2, mod_words);
                                   }

                                   auto a_is_0 = CT::Mask<ui_type>::set();
                                   for (size_t i = 0; i != mod_words; ++i)
                                      a_is_0 &= CT::Mask<ui_type>::is_zero(a_w[i]);

                                   auto b_is_1 = CT::Mask<ui_type>::is_equal(b_w[0], 1);
                                   for (size_t i = 1; i != mod_words; ++i)
                                      b_is_1 &= CT::Mask<ui_type>::is_zero(b_w[i]);

                                   BOOST_ASSERT_MSG(a_is_0.is_set(), "A is zero");

                                   // if b != 1 then gcd(n,mod) > 1 and inverse does not exist
                                   // in which case zero out the result to indicate this
                                   (~b_is_1).if_set_zero_out(v_w, mod_words);*/

                /*
                 * We've placed the result in the lowest words of the temp buffer.
                 * So just clear out the other values and then give that buffer to a
                 * BigInt.
                 */
                /*
                                   clear_mem(&tmp_mem[mod_words], 4 * mod_words);

                                   CT::unpoison(tmp_mem.data(), tmp_mem.size());

                                   Backend r;
                                   r.swap_reg(tmp_mem);
                                   return r;
                                }*/

                /*
                                template <typename Backend, expression_template_option ExpressionTemplates>
                                void inverse_mod_odd_modulus(number<Backend, ExpressionTemplates>&       res,
                                                             const number<Backend, ExpressionTemplates>& n,
                                                             const number<Backend, ExpressionTemplates>& mod)
                                {
                                   eval_inverse_mod_odd_modulus(res.backend(), n.backend(), mod.backend());
                                }
                                 */

                /*
                                template <typename Backend>
                                std::size_t eval_almost_montgomery_inverse(Backend& result, const Backend& a,
                                                                           const Backend& p)
                                {
                                   size_t k = 0;

                                   Backend u = p, v = a, r = 0, s = 1;

                                   while (eval_gt(v, 0))
                                   {
                                      if (eval_integer_modulus(u, 2) == 0)
                                      {
                                         eval_right_shift(u, 1);
                                         eval_left_shift(s, 1);
                                      }
                                      else if (eval_integer_modulus(v, 2) == 0)
                                      {
                                         eval_right_shift(v, 1);
                                         eval_left_shift(r, 1);
                                      }
                                      else if (eval_gt(u, v))
                                      {
                                         eval_subtract(u, v);
                                         eval_right_shift(u, 1);
                                         eval_add(r, s);
                                         eval_left_shift(s, 1);
                                      }
                                      else
                                      {
                                         eval_subtract(v, u);
                                         eval_right_shift(v, 1);
                                         eval_add(s, r);
                                         eval_left_shift(r, 1);
                                      }

                                      k++;
                                   }

                                   if (!eval_gt(p, r))
                                   {
                                      eval_subtract(r, p);
                                   }

                                   result = p;

                                   eval_subtract(result, r);

                                   return k;
                                }
                                */

                /*
                                template <typename Backend, expression_template_option ExpressionTemplates>
                                std::size_t almost_montgomery_inverse(number<Backend, ExpressionTemplates>& result,
                                                                      const number<Backend, ExpressionTemplates>& a,
                                                                      const number<Backend, ExpressionTemplates>& p)
                                {
                                   return eval_almost_montgomery_inverse(result.backend(), a.backend(), p.backend());
                                }
                                */

                /*
                                template <typename Backend>
                                Backend eval_normalized_montgomery_inverse(const Backend& a, const Backend& p)
                                {
                                   Backend     r;
                                   std::size_t k = eval_almost_montgomery_inverse(r, a, p);

                                   for (std::size_t i = 0; i != k; ++i)
                                   {
                                      if (eval_integer_modulus(p, 2) == 1)
                                      {
                                         eval_add(r, p);
                                      }
                                      eval_right_shift(r, 1);
                                   }

                                   return r;
                                }
                                */

                /*
                                template <typename Backend, expression_template_option ExpressionTemplates>
                                number<Backend, ExpressionTemplates> normalized_montgomery_inverse(
                                    const number<Backend, ExpressionTemplates>& a,
                                    const number<Backend, ExpressionTemplates>& p)
                                {
                                   return number<Backend, ExpressionTemplates>(
                                       evaL_normalized_montgomery_inverse(a.backned(), p.backend()));
                                }
                                 */

                /*
                                template <typename Backend>
                                Backend eval_inverse_mod_pow2(Backend& a1, size_t k)
                                {
                                   typedef typename boost::mpl::front<typename Backend::unsigned_types>::type ui_type;*/
                /*
                 * From "A New Algorithm for Inversion mod p^k" by Çetin Kaya Koç
                 * https://eprint.iacr.org/2017/411.pdf sections 5 and 7.
                 */
                /*
                                   if (eval_integer_modulus(a1, 2) == 0)
                                      return 0;

                                   Backend a = a1;
                                   eval_bit_set(a, k);

                                   Backend b = 1, X = 0, newb;

                                   const std::size_t a_words = a.sig_words();

                                   X.grow_to(round_up(k, sizeof(ui_type) * CHAR_BIT) / sizeof(ui_type) * CHAR_BIT);
                                   b.grow_to(a_words);
                                   */
                /*
                                Hide the exact value of k. k is anyway known to word length
                                granularity because of the length of a, so no point in doing more
                                than this.
                                */
                /*

                                   const std::size_t iter = round_up(k, sizeof(ui_type) * CHAR_BIT);

                                   for (std::size_t i = 0; i != iter; ++i)
                                   {
                                      const bool b0 = eval_bit_test(b, 0);
                                      X.conditionally_set_bit(i, b0);
                                      newb = b;
                                      eval_subtract(newb, a);
                                      b.ct_cond_assign(b0, newb);
                                      eval_right_shift(b, 1);
                                   }
                                   eval_bit_set(X, k);
                                   X.const_time_unpoison();
                                   return X;
                                }
                                */

                /*
                                template <typename Backend, expression_template_option ExpressionTemplates>
                                number<Backend, ExpressionTemplates> inverse_mod_pow2(
                                    const number<Backend, ExpressionTemplates>& a1, size_t k)
                                {
                                   return number<Backend, ExpressionTemplates>(
                                       eval_inverse_mod_pow2(a1.backend(), k.backend()));
                                }
                                */

                /*
                                template <typename Backend>
                                Backend eval_inverse_mod(Backend& res, const Backend& n, const Backend& mod)
                                {
                                   if (eval_is_zero(mod))
                                   {
                                      BOOST_THROW_EXCEPTION(
                                          std::invalid_argument("eval_inverse_mod: mod must be non zero"));
                                   }
                                   if ((eval_get_sign(mod) < 0) || (eval_get_sign(n) < 0))
                                   {
                                      BOOST_THROW_EXCEPTION(
                                          std::invalid_argument("eval_inverse_mod: arguments must be non-negative"));
                                   }
                                   if (eval_is_zero(n) || (eval_integer_modulus(n, 2) == 0 && eval_integer_modulus(mod,
                   2) == 0))
                                   {
                                      return 0;
                                   }
                                   if (eval_integer_modulus(n, 2) == 1)
                                   {*/
                /*
                                Fastpath for common case. This leaks information if n > mod
                                but we don't guarantee const time behavior in that case.
                                */
                /*
                                      if (eval_gt(mod, n))
                                         return eval_inverse_mod_odd_modulus(n, mod);
                                      else
                                         return eval_inverse_mod_odd_modulus(ct_modulo(n, mod), mod);
                                   }

                                   const std::size_t mod_lz = eval_lsb(mod);
                                   BOOST_ASSERT(mod_lz > 0);
                                   const std::size_t mod_bits = eval_msb(mod);
                                   BOOST_ASSERT(mod_bits > mod_lz);

                                   if (mod_lz == mod_bits - 1)
                                   {
                                      // In this case we are performing an inversion modulo 2^k
                                      return eval_inverse_mod_pow2(n, mod_lz);
                                   }*/

                /*
                 * In this case we are performing an inversion modulo 2^k*o for
                 * some k > 1 and some odd (not necessarily prime) integer.
                 * Compute the inversions modulo 2^k and modulo o, then combine them
                 * using CRT, which is possible because 2^k and o are relatively prime.
                 */
                /*
                                   Backend o = mod;

                                   eval_right_shift(mod, mod_lz);

                                   Backend n_redc = ct_modulo(n, o);
                                   Backend inv_o  = eval_inverse_mod_odd_modulus(n_redc, o);
                                   Backend inv_2k = eval_inverse_mod_pow2(n, mod_lz);

                                   // No modular inverse in this case:
                                   if (eval_is_zero(o) || eval_is_zero(inv_2k))
                                      return 0;

                                   Backend m2k = mod_lz;
                                   eval_multiply(m2k, m2k);
                                   // Compute the CRT parameter
                                   Backend c = inverse_mod_pow2(o, mod_lz);

                                   // Compute h = c*(inv_2k-inv_o) mod 2^k
                                   Backend h = inv_2k;

                                   eval_subtract(h, inv_o);
                                   eval_multiply(h, c);

                                   const bool h_neg = (eval_get_sign(h) < 0);

                                   eval_abs(h); // h.set_sign(BigInt::Positive);
                                   eval_bit_set(h, mod_lz);

                                   const bool h_nonzero = !eval_is_zero(h);

                                   eval_subtracr(m2k, h);
                                   h.ct_cond_assign(h_nonzero && h_neg, m2k);

                                   // Return result inv_o + h * o
                                   eval_multiply(h, o);
                                   eval_add(h, inv_o);

                                   return h;
                                }
                                */
            }    // namespace backends
        }        // namespace multiprecision
    }            // namespace crypto3
}    // namespace nil

#endif


/* Greg Anderson
 *
 * Wrapper classes for Apron abstractions.
 */

#ifndef _ABSTRACT_H_
#define _ABSTRACT_H_

#include <ap_abstract0.h>
#include <ap_disjunction.h>
#include <t1p.h>
#include <box.h>
#include <pk.h>

#include <cstdlib>
#include <vector>
#include <map>
#include <memory>
#include <iostream>
#include <optional>
#include <Eigen/Dense>

class ArithExpr {
  private:
    ap_texpr0_t* expr;

  public:
    ArithExpr();
    ArithExpr(double constant);
    ArithExpr(int ind);
    ArithExpr(double lower, double upper);
    ArithExpr(ap_texpr0_t* expr);
    ArithExpr(const ArithExpr& other);
    ArithExpr(ArithExpr&& other);
    ArithExpr(const std::string& str);
    ~ArithExpr();
    ArithExpr& operator=(const ArithExpr& other);
    ArithExpr& operator=(ArithExpr&& other);
    ArithExpr negate() const;
    ArithExpr operator+(const ArithExpr& other) const;
    ArithExpr operator-(const ArithExpr& other) const;
    ArithExpr operator*(const ArithExpr& other) const;
    ArithExpr operator/(const ArithExpr& other) const;
    // Be careful about the precedence of ^. C++ uses this operator for XOR so
    // it has much lower precedence than you would expect for a power operator.
    ArithExpr operator^(int power) const;

    inline ap_texpr0_t* get_texpr() const {
      return expr;
    }
};

/**
 * A set of linear constraints. A point x satisfies these constraints if
 * `weights * x <= biases`.
 */
class LinCons {
  public:
    Eigen::MatrixXd weights;
    Eigen::VectorXd biases;
    LinCons();
    LinCons(const Eigen::MatrixXd& ws, const Eigen::VectorXd& bs);
    double distance_from(const Eigen::VectorXd& x) const;
};

enum class AbstractDomain { ZONOTOPE, INTERVAL, POLYHEDRA };

/**
 * An abstract value over some underlying Apron value. This is just a
 * convenient wrapper class around Apron values.
 */
class AbstractVal {
  protected:
    ap_manager_t* man;
    ap_abstract0_t* value;
    virtual std::unique_ptr<AbstractVal> make_new(ap_abstract0_t* a) const;
    AbstractDomain domain;

  public:
    AbstractVal();
    AbstractVal(ap_manager_t* man, ap_abstract0_t* v);
    // Construct a new abstract value in the given domain subject to the
    // set of linear constraints a x <= b
    AbstractVal(AbstractDomain dom, const std::vector<Eigen::VectorXd>& a,
        const std::vector<double>& b);
    AbstractVal(AbstractDomain dom, const LinCons& lc);

    // Construct a new abstract value in the given domain from the given
    // interval
    AbstractVal(AbstractDomain dom, const Eigen::VectorXd& lowers,
        const Eigen::VectorXd& uppers);
    AbstractVal(const AbstractVal& other);
    AbstractVal(AbstractVal&& other);
    virtual ~AbstractVal();
    AbstractVal& operator=(const AbstractVal& other) = delete;
    AbstractVal& operator=(AbstractVal&& other) = delete;

    inline ap_manager_t* get_manager() const {
      return man;
    }

    inline ap_abstract0_t* get_value() const {
      return value;
    }

    inline AbstractDomain get_domain() const {
      return domain;
    }

    std::unique_ptr<AbstractVal> add_trailing_dimensions(int n) const;
    std::unique_ptr<AbstractVal> add_leading_dimensions(int n) const;
    std::unique_ptr<AbstractVal> remove_trailing_dimensions(int n) const;

    /**
     * Meet this value with the linear constraints a x <= b.
     */
    virtual std::unique_ptr<AbstractVal> meet_linear_constraint(
        const Eigen::MatrixXd& a,
        const Eigen::VectorXd& b) const;

    /**
     * Perform a specific affine transformation.
     */
    virtual std::unique_ptr<AbstractVal> scalar_affine(
        const Eigen::MatrixXd& w,
        const Eigen::VectorXd& b) const;

    /**
     * Perform an abstract transformation where each coefficient is an interval.
     */
    virtual std::unique_ptr<AbstractVal> interval_affine(
        const Eigen::MatrixXd& wl,
        const Eigen::MatrixXd& wu,
        const Eigen::VectorXd& bl,
        const Eigen::VectorXd& bu) const;

    /**
     * A relu is computed as follows: for each dimension i, compute
     * x_l = meet(x, x_i < 0) and x_u = meet(x, x_i >= 0). Compute
     * x'_l = x_l[x_i <- 0]. Let x = join(x'_l, x_u).
     */
    virtual std::unique_ptr<AbstractVal> relu() const;

    virtual std::unique_ptr<AbstractVal> join(const AbstractVal& other) const;

    virtual std::unique_ptr<AbstractVal> meet(const AbstractVal& other) const;

    virtual std::unique_ptr<AbstractVal> widen(const AbstractVal& other) const;

    virtual bool operator==(const AbstractVal& other) const;

    /**
     * Create an abstract value by adding each dimension of b to this and
     * maintain the relations among variables in b.
     */
    virtual std::unique_ptr<AbstractVal> append(const AbstractVal& b) const;

    virtual std::unique_ptr<AbstractVal> arith_computation(
        const std::vector<ArithExpr>& exprs) const;

    inline bool is_bottom() const {
      return ap_abstract0_is_bottom(man, value);
    }

    inline bool is_top() const {
      return ap_abstract0_is_top(man, value);
    }

    bool contains_point(const Eigen::VectorXd& x) const;
    bool contains(const AbstractVal& x) const;
    Eigen::VectorXd get_center() const;
    virtual Eigen::VectorXd get_contained_point() const;

    /**
     * Get the number of dimensions of this abstract value.
     */
    inline size_t dims() const {
      return ap_abstract0_dimension(man, value).realdim;
    }

    virtual std::unique_ptr<AbstractVal> clone() const;

    virtual std::unique_ptr<AbstractVal> bottom() const;

    LinCons get_lincons() const;

    //virtual double distance_to_point(const Eigen::VectorXd& x) const;
    void print(FILE* out) const;
};

/**
 * Powerset is used for a bounded powerset domain. It is based on Aprons
 * disjunctive domain, but applies a consolidation step after each join or
 * merge.
 */
class Powerset: public AbstractVal {
  private:
    size_t size;

  protected:
    std::unique_ptr<AbstractVal> make_new(ap_abstract0_t* a) const override;

  public:
    Powerset(ap_manager_t* m, ap_abstract0_t* v, size_t s);
    Powerset(const Powerset& p);
    Powerset(AbstractDomain dom, size_t size,
        const std::vector<Eigen::VectorXd>& a,
        const std::vector<double>& b);
    Powerset(AbstractDomain dom, size_t size, const LinCons& lc);
    Powerset(AbstractDomain dom, size_t size,
        const Eigen::VectorXd& lowers,
        const Eigen::VectorXd& uppers);
    Powerset& operator=(const Powerset& other);
    std::unique_ptr<AbstractVal> join(const AbstractVal& other) const override;
    std::unique_ptr<AbstractVal> meet(const AbstractVal& other) const override;
    std::unique_ptr<AbstractVal> arith_computation(
        const std::vector<ArithExpr>& exprs) const override;
    Eigen::VectorXd get_contained_point() const;
    std::unique_ptr<AbstractVal> clone() const override;
    std::unique_ptr<AbstractVal> bottom() const override;
    //double distance_to_point(const Eigen::VectorXd& x) const;
};

//class Managers {
//  private:
//    ap_manager_t* t1p_man;
//    ap_manager_t* box_man;
//    std::map<ap_manager_t*, ap_manager_t*> disj_mans;
//
//  public:
//    Managers(ap_manager_t*, ap_manager_t*);
//    ~Managers();
//    inline ap_manager_t* get_t1p_manager() const {
//      return t1p_man;
//    }
//    inline ap_manager_t* get_box_manager() const {
//      return box_man;
//    }
//    inline ap_manager_t* get_disj_manager(ap_manager_t* base) const {
//      return disj_mans.at(base);
//    }
//};

#endif


#include <gtest/gtest.h>

#include "mfem.hpp"
using namespace mfem;

#include <iostream>
#include <Eigen/Core>

#include "../include/core/config.hpp"
#include "../include/mymfem/utilities.hpp"
#include "../include/uq/stats/pcells_hash_table.hpp"

//! Test creating a parallel cells hash table for a given mesh
//! Unit Square Mesh, coarse
TEST(ParCellsHashTable, test1)
{
    int nprocs, myrank;
    MPI_Comm global_comm = MPI_COMM_WORLD;
    MPI_Comm_size(global_comm, &nprocs);
    MPI_Comm_rank(global_comm, &myrank);

    int lx = 2;
    const std::string mesh_file
            = "../input/poisson_smooth_unitSquare/mesh_lx"
            +std::to_string(lx);

    int Nx = 4;
    double xl = 0;
    double xr = 1;

    int Ny = 4;
    double yl = 0;
    double yr = 1;

    //std::cout << mesh_file << std::endl;
    std::shared_ptr<Mesh> mesh
            = std::make_shared<Mesh>(mesh_file.c_str());

    auto cellsHashTable
            = make_hashTable(global_comm,
                             mesh, Nx, xl, xr, Ny, yl, yr);
    cellsHashTable->display();

    int numElements = cellsHashTable->get_numElements();
    int true_numElements = mesh->GetNE();
    ASSERT_EQ(numElements, true_numElements);

    Eigen::Vector2d coords;
    coords(0) = 0.51;
    coords(1) = 0.50;
    int true_idx = 2;
    int true_idy = 1;
    int idx, idy;
    std::tie(idx, idy) = cellsHashTable->search(coords);
    if (myrank == 0) {
        std::cout << "Coords: "
                  << coords(0) << "," << coords(1)
                  << " are in cell "
                  << idx << "," << idy << std::endl;
    }

    ASSERT_EQ(idx, true_idx);
    ASSERT_EQ(idy, true_idy);
}

TEST(ParCellsHashTable, test2)
{
    int nprocs, myrank;
    MPI_Comm global_comm = MPI_COMM_WORLD;
    MPI_Comm_size(global_comm, &nprocs);
    MPI_Comm_rank(global_comm, &myrank);

    const std::string mesh_file
            = "../meshes/channel_L1pt5/refined"
              "/tri_mesh_l0.msh";

    int Nx = 10;
    double xl = 0;
    double xr = 1.5;

    int Ny = 10;
    double yl = 0;
    double yr = 0.5;

    //std::cout << mesh_file << std::endl;
    std::shared_ptr<Mesh> mesh
            = std::make_shared<Mesh>(mesh_file.c_str());

    auto cellsHashTable
            = make_hashTable(global_comm,
                             mesh, Nx, xl, xr, Ny, yl, yr);

    int numElements = cellsHashTable->get_numElements();
    int true_numElements = mesh->GetNE();
    ASSERT_EQ(numElements, true_numElements);
}

// End of file


/**
 * @file
 * @brief NPDE homework CoupledSecondOrderBVP
 * @author Erick Schulz
 * @date 13/11/2019
 * @copyright Developed at ETH Zurich
 */

#include <cmath>
#include <iostream>
#include <memory>
#include <utility>

#include <Eigen/Core>

#include <lf/io/io.h>
#include <lf/mesh/hybrid2d/hybrid2d.h>

#include "coupledsecondorderbvp.h"

using namespace CoupledSecondOrderBVP;

int main(int /*argc*/, const char ** /*argv*/) {
  // Load mesh into a Lehrfem++ object
  auto mesh_factory = std::make_unique<lf::mesh::hybrid2d::MeshFactory>(2);
  const lf::io::GmshReader reader(std::move(mesh_factory),
                                  CURRENT_SOURCE_DIR "/../meshes/hex1.msh");
  auto mesh_p = reader.mesh();  // type shared_ptr< const lf::mesh::Mesh>

  // Load finite element space
  // We discretization by means of piecewise QUADRATIC lagrangian FE
  auto fe_space = std::make_shared<FeSpaceLagrangeO2<double>>(mesh_p);
  // Obtain local->global index mapping for current finite element space
  const lf::assemble::DofHandler &dofh{fe_space->LocGlobMap()};
  // Dimension of finite element space
  const lf::uscalfe::size_type N_dofs(dofh.NumDofs());

  /* Solve the coupled boundary value problem */
  double gamma = 1.0;  // reaction coefficientS
  // Right-hand side source function f
  auto f = lf::mesh::utils::MeshFunctionGlobal(
      [](Eigen::Vector2d x) -> double { return std::cos(x.norm()); });
  Eigen::VectorXd sol_vec = solveCoupledBVP(fe_space, gamma, f);

  /* Output results to vtk file */
  // We store data by keeping only the coefficients of nodal basis functions
  // In that sense, we are plotting the values of the solution at the vertices
  lf::io::VtkWriter vtk_writer(
      mesh_p, CURRENT_BINARY_DIR "/CoupledSecondOrderBVP_solution.vtk");
  // Write nodal data taking the values of the discrete solution at the vertices
  auto nodal_data = lf::mesh::utils::make_CodimMeshDataSet<double>(mesh_p, 2);
  for (int global_idx = 0; global_idx < N_dofs; global_idx++) {
    if (dofh.Entity(global_idx).RefEl() == lf::base::RefElType::kPoint) {
      nodal_data->operator()(dofh.Entity(global_idx)) = sol_vec[global_idx];
    }
  };
  vtk_writer.WritePointData("CoupledSecondOrderBVP_solution", *nodal_data);
  /* SAM_LISTING_END_1 */
  std::cout << "\n The solution vector was written to:" << std::endl;
  std::cout << ">> CoupledSecondOrderBVP_solution.vtk\n" << std::endl;
}


// Boost.Geometry (aka GGL, Generic Geometry Library)
// Unit test

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

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle

#ifndef BOOST_TEST_MODULE
#define BOOST_TEST_MODULE test_equals_on_spheroid
#endif

#include <iostream>

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

#include "test_equals.hpp"

#include <boost/geometry/geometries/geometries.hpp>

#include <boost/geometry/core/cs.hpp>

namespace bgm = bg::model;

template <typename P1, typename P2 = P1>
struct test_point_point
{
    static inline void apply(std::string const& header)
    {
        std::string const str = header + "-";

        test_geometry<P1, P2>(str + "pp_01", "POINT(0 0)", "POINT(0 0)", true);
        test_geometry<P1, P2>(str + "pp_02", "POINT(0 0)", "POINT(10 0)", false);

        // points whose longitudes differ by 360 degrees
        test_geometry<P1, P2>(str + "pp_03", "POINT(0 0)", "POINT(360 0)", true);
        test_geometry<P1, P2>(str + "pp_04", "POINT(10 0)", "POINT(370 0)", true);
        test_geometry<P1, P2>(str + "pp_05", "POINT(10 0)", "POINT(-350 0)", true);
        test_geometry<P1, P2>(str + "pp_06", "POINT(180 10)", "POINT(-180 10)", true);
        test_geometry<P1, P2>(str + "pp_06a", "POINT(540 10)", "POINT(-540 10)", true);

#ifdef BOOST_GEOMETRY_NORMALIZE_LATITUDE
        test_geometry<P1, P2>(str + "pp_06b", "POINT(540 370)", "POINT(-540 -350)", true);
        test_geometry<P1, P2>(str + "pp_06c", "POINT(1260 370)", "POINT(-1260 -350)", true);
        test_geometry<P1, P2>(str + "pp_06d", "POINT(2340 370)", "POINT(-2340 -350)", true);
#endif

        test_geometry<P1, P2>(str + "pp_06e", "POINT(-180 10)", "POINT(-540 10)", true);
        test_geometry<P1, P2>(str + "pp_06f", "POINT(180 10)", "POINT(-540 10)", true);

        // north & south pole
        test_geometry<P1, P2>(str + "pp_07", "POINT(0 90)", "POINT(0 90)", true);

#ifdef BOOST_GEOMETRY_NORMALIZE_LATITUDE
        test_geometry<P1, P2>(str + "pp_07a", "POINT(0 450)", "POINT(10 -270)", true);
        test_geometry<P1, P2>(str + "pp_07b", "POINT(0 270)", "POINT(10 90)", false);
        test_geometry<P1, P2>(str + "pp_07c", "POINT(0 -450)", "POINT(10 90)", false);
#endif

        test_geometry<P1, P2>(str + "pp_08", "POINT(0 90)", "POINT(10 90)", true);
        test_geometry<P1, P2>(str + "pp_09", "POINT(0 90)", "POINT(0 -90)", false);
        test_geometry<P1, P2>(str + "pp_10", "POINT(0 -90)", "POINT(0 -90)", true);
        test_geometry<P1, P2>(str + "pp_11", "POINT(0 -90)", "POINT(10 -90)", true);
        test_geometry<P1, P2>(str + "pp_11a", "POINT(0 -90)", "POINT(10 90)", false);
        test_geometry<P1, P2>(str + "pp_12", "POINT(0 -90)", "POINT(0 -85)", false);
        test_geometry<P1, P2>(str + "pp_13", "POINT(0 90)", "POINT(0 85)", false);
        test_geometry<P1, P2>(str + "pp_14", "POINT(0 90)", "POINT(10 85)", false);

        // symmetric wrt prime meridian
        test_geometry<P1, P2>(str + "pp_15", "POINT(-10 45)", "POINT(10 45)", false);
        test_geometry<P1, P2>(str + "pp_16", "POINT(-170 45)", "POINT(170 45)", false);

        // other points
        test_geometry<P1, P2>(str + "pp_17", "POINT(-10 45)", "POINT(10 -45)", false);
        test_geometry<P1, P2>(str + "pp_18", "POINT(-10 -45)", "POINT(10 45)", false);
        test_geometry<P1, P2>(str + "pp_19", "POINT(10 -135)", "POINT(10 45)", false);

#ifdef BOOST_GEOMETRY_NORMALIZE_LATITUDE
        test_geometry<P1, P2>(str + "pp_20", "POINT(190 135)", "POINT(10 45)", true);
        test_geometry<P1, P2>(str + "pp_21", "POINT(190 150)", "POINT(10 30)", true);
        test_geometry<P1, P2>(str + "pp_21a", "POINT(-170 150)", "POINT(10 30)", true);
        test_geometry<P1, P2>(str + "pp_22", "POINT(190 -135)", "POINT(10 -45)", true);
        test_geometry<P1, P2>(str + "pp_23", "POINT(190 -150)", "POINT(10 -30)", true);
        test_geometry<P1, P2>(str + "pp_23a", "POINT(-170 -150)", "POINT(10 -30)", true);
#endif
    }
};

template <typename P1, typename P2 = P1>
struct test_point_point_with_height
{
    static inline void apply(std::string const& header)
    {
        std::string const str = header + "-";

        test_geometry<P1, P2>(str + "pp_01",
                              "POINT(0 0 10)",
                              "POINT(0 0 20)",
                              true);

        test_geometry<P1, P2>(str + "pp_02",
                              "POINT(0 0 10)",
                              "POINT(10 0 10)",
                              false);

        // points whose longitudes differ by 360 degrees
        test_geometry<P1, P2>(str + "pp_03",
                              "POINT(0 0 10)",
                              "POINT(360 0 10)",
                              true);

        // points whose longitudes differ by 360 degrees
        test_geometry<P1, P2>(str + "pp_04",
                              "POINT(10 0 10)",
                              "POINT(370 0 10)",
                              true);

        test_geometry<P1, P2>(str + "pp_05",
                              "POINT(10 0 10)",
                              "POINT(10 0 370)",
                              false);
    }
};

template <typename P>
void test_segment_segment(std::string const& header)
{
    typedef bgm::segment<P> seg;

    std::string const str = header + "-";

    test_geometry<seg, seg>(str + "ss_01",
                            "SEGMENT(10 0,180 0)",
                            "SEGMENT(10 0,-180 0)",
                            true);
    test_geometry<seg, seg>(str + "ss_02",
                            "SEGMENT(0 90,180 0)",
                            "SEGMENT(10 90,-180 0)",
                            true);
    test_geometry<seg, seg>(str + "ss_03",
                            "SEGMENT(0 90,0 -90)",
                            "SEGMENT(10 90,20 -90)",
                            true);
    test_geometry<seg, seg>(str + "ss_04",
                            "SEGMENT(10 80,10 -80)",
                            "SEGMENT(10 80,20 -80)",
                            false);
    test_geometry<seg, seg>(str + "ss_05",
                            "SEGMENT(170 10,-170 10)",
                            "SEGMENT(170 10,350 10)",
                            false);
}

BOOST_AUTO_TEST_CASE( equals_point_point_se )
{
    typedef bg::cs::spherical_equatorial<bg::degree> cs_type;

    test_point_point<bgm::point<int, 2, cs_type> >::apply("se");
    test_point_point<bgm::point<double, 2, cs_type> >::apply("se");
    test_point_point<bgm::point<long double, 2, cs_type> >::apply("se");

    // mixed point types
    test_point_point
        <
            bgm::point<double, 2, cs_type>, bgm::point<int, 2, cs_type>
        >::apply("se");

    test_point_point
        <
            bgm::point<double, 2, cs_type>, bgm::point<long double, 2, cs_type>
        >::apply("se");
}

BOOST_AUTO_TEST_CASE( equals_point_point_with_height_se )
{
    typedef bg::cs::spherical_equatorial<bg::degree> cs_type;

    test_point_point<bgm::point<int, 3, cs_type> >::apply("seh");
    test_point_point<bgm::point<double, 3, cs_type> >::apply("seh");
    test_point_point<bgm::point<long double, 3, cs_type> >::apply("seh");

    // mixed point types
    test_point_point
        <
            bgm::point<double, 3, cs_type>, bgm::point<int, 3, cs_type>
        >::apply("seh");

    test_point_point
        <
            bgm::point<double, 3, cs_type>, bgm::point<long double, 3, cs_type>
        >::apply("seh");
}

BOOST_AUTO_TEST_CASE( equals_point_point_geo )
{
    typedef bg::cs::geographic<bg::degree> cs_type;

    test_point_point<bgm::point<int, 2, cs_type> >::apply("geo");
    test_point_point<bgm::point<double, 2, cs_type> >::apply("geo");
    test_point_point<bgm::point<long double, 2, cs_type> >::apply("geo");

    // mixed point types
    test_point_point
        <
            bgm::point<double, 2, cs_type>, bgm::point<int, 2, cs_type>
        >::apply("se");

    test_point_point
        <
            bgm::point<double, 2, cs_type>, bgm::point<long double, 2, cs_type>
        >::apply("se");
}

BOOST_AUTO_TEST_CASE( equals_segment_segment_se )
{
    typedef bg::cs::spherical_equatorial<bg::degree> cs_type;

    test_segment_segment<bgm::point<int, 2, cs_type> >("se");
    test_segment_segment<bgm::point<double, 2, cs_type> >("se");
    test_segment_segment<bgm::point<long double, 2, cs_type> >("se");
}

BOOST_AUTO_TEST_CASE( equals_segment_segment_geo )
{
    typedef bg::cs::geographic<bg::degree> cs_type;

    test_segment_segment<bgm::point<int, 2, cs_type> >("geo");
    test_segment_segment<bgm::point<double, 2, cs_type> >("geo");
    test_segment_segment<bgm::point<long double, 2, cs_type> >("geo");
}


#ifdef STAN_OPENCL

#include <stan/math/opencl/kernel_generator.hpp>
#include <stan/math/opencl/matrix_cl.hpp>
#include <stan/math/opencl/copy.hpp>
#include <test/unit/math/opencl/kernel_generator/reference_kernel.hpp>
#include <test/unit/util.hpp>
#include <Eigen/Dense>
#include <gtest/gtest.h>
#include <string>

TEST(KernelGenerator, indexing_test) {
  using Eigen::MatrixXd;
  using stan::math::matrix_cl;

  std::string kernel_filename = "indexing.cl";
  MatrixXd m = MatrixXd::Random(7, 9);

  Eigen::MatrixXi col_idx(3, 2);
  col_idx << 2, 5, 0, 1, 6, 3;
  Eigen::MatrixXi row_idx(3, 2);
  row_idx << 1, 2, 3, 4, 2, 0;

  matrix_cl<double> m_cl(m);
  matrix_cl<int> row_idx_cl(row_idx);
  matrix_cl<int> col_idx_cl(col_idx);

  auto tmp = stan::math::indexing(m_cl, row_idx_cl, col_idx_cl);

  matrix_cl<double> res_cl;
  std::string kernel_src = tmp.get_kernel_source_for_evaluating_into(res_cl);
  stan::test::store_reference_kernel_if_needed(kernel_filename, kernel_src);
  std::string expected_kernel_src
      = stan::test::load_reference_kernel(kernel_filename);
  EXPECT_EQ(expected_kernel_src, kernel_src);

  res_cl = tmp;

  MatrixXd res = stan::math::from_matrix_cl(res_cl);

  MatrixXd correct(3, 2);
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 2; j++) {
      correct(i, j) = m(row_idx(i, j), col_idx(i, j));
    }
  }
  EXPECT_MATRIX_EQ(res, correct);
}

TEST(KernelGenerator, indexing_multiple_operations_test) {
  using Eigen::MatrixXd;
  using Eigen::MatrixXi;
  using stan::math::matrix_cl;

  MatrixXd m = MatrixXd::Random(7, 9);

  Eigen::MatrixXi col_idx(3, 2);
  col_idx << 2, 5, 0, 1, 6, 3;
  Eigen::MatrixXi row_idx(3, 2);
  row_idx << 1, 2, 3, 4, 2, 0;

  Eigen::MatrixXi col_idx2(2, 2);
  col_idx2 << 0, 0, 1, 1;
  Eigen::MatrixXi row_idx2(2, 2);
  row_idx2 << 0, 1, 1, 2;

  matrix_cl<double> m_cl(m);
  matrix_cl<int> row_idx_cl(row_idx);
  matrix_cl<int> col_idx_cl(col_idx);
  matrix_cl<int> row_idx2_cl(row_idx2);
  matrix_cl<int> col_idx2_cl(col_idx2);

  matrix_cl<double> res_cl = stan::math::indexing(
      stan::math::indexing(m_cl, row_idx_cl, col_idx_cl),
      stan::math::indexing(row_idx2_cl, col_idx2_cl, col_idx2_cl), col_idx2_cl);

  MatrixXd res = stan::math::from_matrix_cl(res_cl);

  MatrixXd tmp(3, 2);
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 2; j++) {
      tmp(i, j) = m(row_idx(i, j), col_idx(i, j));
    }
  }

  MatrixXd correct(2, 2);
  for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 2; j++) {
      int a = col_idx2(i, j);
      correct(i, j) = tmp(row_idx2(a, a), a);
    }
  }
  EXPECT_MATRIX_EQ(res, correct);
}

TEST(KernelGenerator, indexing_lhs_test) {
  using Eigen::MatrixXd;
  using Eigen::MatrixXi;
  using stan::math::matrix_cl;

  MatrixXd m = MatrixXd::Zero(7, 9);
  MatrixXd m2 = MatrixXd::Random(3, 2);

  Eigen::MatrixXi col_idx(3, 2);
  col_idx << 2, 5, 0, 1, 6, 3;
  Eigen::MatrixXi row_idx(3, 2);
  row_idx << 1, 2, 3, 4, 2, 0;

  matrix_cl<double> m_cl(m);
  matrix_cl<double> m2_cl(m2);
  matrix_cl<int> row_idx_cl(row_idx);
  matrix_cl<int> col_idx_cl(col_idx);

  stan::math::indexing(m_cl, row_idx_cl, col_idx_cl) = m2_cl;

  MatrixXd res = stan::math::from_matrix_cl(m_cl);

  MatrixXd correct = m;

  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 2; j++) {
      correct(row_idx(i, j), col_idx(i, j)) = m2(i, j);
    }
  }
  EXPECT_MATRIX_EQ(res, correct);
}

TEST(KernelGenerator, indexing_repeat_lhs_rhs_test) {
  using Eigen::MatrixXd;
  using Eigen::MatrixXi;
  using stan::math::matrix_cl;

  MatrixXd m = MatrixXd::Zero(7, 9);
  MatrixXd correct = m;

  Eigen::MatrixXi col_idx(3, 2);
  col_idx << 2, 5, 0, 1, 6, 3;
  Eigen::MatrixXi row_idx(3, 2);
  row_idx << 1, 2, 3, 4, 2, 0;

  matrix_cl<double> m_cl(m);
  matrix_cl<int> row_idx_cl(row_idx);
  matrix_cl<int> col_idx_cl(col_idx);

  auto b = stan::math::indexing(m_cl, row_idx_cl, col_idx_cl);

  b = b + 1;
  MatrixXd res = stan::math::from_matrix_cl(m_cl);

  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 2; j++) {
      correct(row_idx(i, j), col_idx(i, j)) += 1;
    }
  }
  EXPECT_MATRIX_EQ(res, correct);
}

#endif


#include <scitbx/array_family/boost_python/flex_fwd.h>

#include <scitbx/math/parabolic_cylinder_d.h>
#include <scitbx/math/bessel.h>
#include <scitbx/math/chebyshev.h>
#include <scitbx/math/dihedral.h>
#include <scitbx/math/erf.h>
#include <scitbx/math/euler_angles.h>
#include <scitbx/math/floating_point_epsilon.h>
#include <scitbx/math/gamma.h>
#include <scitbx/math/gcd.h>
#include <scitbx/math/halton.h>
#include <scitbx/math/lambertw.h>
#include <scitbx/math/phase_error.h>
#include <scitbx/math/resample.h>
#include <scitbx/math/superpose.h>
#include <scitbx/math/utils.h>
#include <boost/rational.hpp> // for boost::gcd
#include <scitbx/math/approx_equal.h>
#include <scitbx/math/orthonormal_basis.h>
#include <scitbx/math/gaussian_fit_1d_analytical.h>
#include <scitbx/math/cubic_equation.h>
#include <scitbx/math/distance_difference.h>
#include <boost/python/module.hpp>
#include <boost/python/def.hpp>
#include <boost/python/class.hpp>
#include <boost/python/args.hpp>
#include <scitbx/math/fast_approx_math.h>
#include <scitbx/math/g_function.h>

namespace scitbx { namespace math {
namespace boost_python {

  void wrap_basic_statistics();
  void wrap_gaussian();
  void wrap_golay();
  void wrap_minimum_covering_sphere();
  void wrap_principal_axes_of_inertia();
  void wrap_row_echelon();
  void wrap_tensor_rank_2();
  void wrap_icosahedron();
  void wrap_chebyshev_base();
  void wrap_chebyshev_polynome();
  void wrap_chebyshev_fitter();
  void wrap_chebyshev_lsq();
  void wrap_slatec();
  void wrap_line_search();
  void wrap_r3_rotation();
  void wrap_resample();
  void wrap_quadrature();
  void wrap_unimodular_generator();
  void wrap_halton();
  void wrap_least_squares_plane();
  void wrap_continued_fraction();
  void wrap_numeric_limits();
  void wrap_distributions();
  void wrap_exp_functions();
  void wrap_zernike();
  void wrap_zernike_mom();
  void wrap_2d_zernike_mom();
  void wrap_weighted_covariance();
  void wrap_dmatrix();
  void wrap_correlation();
  void wrap_interpolation();
  void wrap_tetrahedron();
  void wrap_angle_derivative();

namespace {

  int
  time_gcd_int_boost(
    int n)
  {
    int result = 0;
    for(int a=0;a<n;a++) {
      for(int b=0;b<n;b++) {
        int c = boost::gcd(a, b);
        if (result < c) result = c;
      }
    }
    return result;
  }

  long
  time_gcd_long_boost(
    long n)
  {
    long result = 0;
    for(long a=0;a<n;a++) {
      for(long b=0;b<n;b++) {
        long c = boost::gcd(a, b);
        if (result < c) result = c;
      }
    }
    return result;
  }

  int
  time_gcd_int_simple(
    int n)
  {
    int result = 0;
    for(int a=0;a<n;a++) {
      for(int b=0;b<n;b++) {
        int c = gcd_int_simple(a, b);
        if (result < c) result = c;
      }
    }
    return result;
  }

  long
  time_gcd_long_simple(
    long n)
  {
    long result = 0;
    for(long a=0;a<n;a++) {
      for(long b=0;b<n;b++) {
        long c = gcd_long_simple(a, b);
        if (result < c) result = c;
      }
    }
    return result;
  }

  long
  time_gcd_unsigned_long_binary(
    unsigned long n)
  {
    unsigned long result = 0;
    for(unsigned long a=0;a<n;a++) {
      for(unsigned long b=0;b<n;b++) {
        unsigned long c = gcd_unsigned_long_binary(a, b);
        if (result < c) result = c;
      }
    }
    return result;
  }

  long
  time_gcd_long_binary(
    long n)
  {
    long result = 0;
    for(long a=0;a<n;a++) {
      for(long b=0;b<n;b++) {
        long c = gcd_long_binary(a, b);
        if (result < c) result = c;
      }
    }
    return result;
  }

#if defined(SCITBX_MATH_GCD_USING_ASM)
  int
  time_gcd_int32_asm(
    int n)
  {
    int result = 0;
    for(int a=0;a<n;a++) {
      for(int b=0;b<n;b++) {
        int c = gcd_int32_asm(a, b);
        if (result < c) result = c;
      }
    }
    return result;
  }

# if defined(__x86_64__)
  long
  time_gcd_int64_asm(
    long n)
  {
    long result = 0;
    for(long a=0;a<n;a++) {
      for(long b=0;b<n;b++) {
        long c = gcd_int64_asm(a, b);
        if (result < c) result = c;
      }
    }
    return result;
  }
# endif
#endif

  template <typename SitesType>
  boost::optional<double>
  dihedral_angle(
    SitesType const& sites,
    bool deg)
  {
    return dihedral(sites).angle(deg);
  }

  mat3<double>
  superpose_kearsley_rotation(
    af::const_ref<vec3<double> > const& reference_sites,
    af::const_ref<vec3<double> > const& other_sites)
  {
    return superpose::superposition<>::kearsley_rotation(
      reference_sites, other_sites);
  }

  mat3< double >
  euler_angles_xyz_matrix(
      const double& ax,
      const double& ay,
      const double& az )
  {
    return euler_angles::xyz_matrix( ax, ay, az );
  }

  vec3< double >
  euler_angles_xyz_angles(
      const mat3< double >& m,
      const double& eps = 1e-12 )
  {
    return euler_angles::xyz_angles( m, eps );
  }

  mat3< double >
  euler_angles_yzx_matrix(
      const double& ay,
      const double& az,
      const double& ax )
  {
    return euler_angles::yzx_matrix( ay, az, ax );
  }

  vec3< double >
  euler_angles_yzx_angles(
      const mat3< double >& m,
      const double& eps = 1e-12 )
  {
    return euler_angles::yzx_angles( m, eps );
  }

  mat3< double >
  euler_angles_zyz_matrix(
      const double& az1,
      const double& ay,
      const double& az3 )
  {
    return euler_angles::zyz_matrix( az1, ay, az3 );
  }

  vec3< double >
  euler_angles_zyz_angles(
      const mat3< double >& m,
      const double& eps = 1e-12 )
  {
    return euler_angles::zyz_angles( m, eps );
  }

  template <typename T>
  struct approx_equal_relatively_wrapper
  {
    typedef math::approx_equal_relatively<T> wt;
    typedef T arg_t;
    typedef typename wt::amplitude_type amplitude_t;
    static bool form_1(arg_t x, arg_t y, amplitude_t relative_error) {
      wt p(relative_error);
      return p(x, y);
    }
    static bool form_2(arg_t x, arg_t y, amplitude_t relative_error,
                       amplitude_t near_zero_threshold)
    {
      wt p(relative_error, near_zero_threshold);
      return p(x, y);
    }

    static void wrap() {
      using namespace boost::python;
      def("approx_equal_relatively", form_1,
          (arg("x"), arg("y"), arg("relative_error")));
      def("approx_equal_relatively", form_2,
          (arg("x"), arg("y"), arg("relative_error"),
           arg("near_zero_threshold")));
    }
  };

  void init_module()
  {
    using namespace boost::python;

    def("time_gcd_int_boost", time_gcd_int_boost);
    def("time_gcd_long_boost", time_gcd_long_boost);
    def("gcd_int_simple", gcd_int_simple, (arg("a"), arg("b")));
    def("time_gcd_int_simple", time_gcd_int_simple);
    def("gcd_long_simple", gcd_long_simple, (arg("a"), arg("b")));
    def("time_gcd_long_simple", time_gcd_long_simple);
    def("time_gcd_unsigned_long_binary", time_gcd_unsigned_long_binary);
    def("gcd_long_binary", gcd_long_binary, (arg("a"), arg("b")));
    def("time_gcd_long_binary", time_gcd_long_binary);
#if defined(SCITBX_MATH_GCD_USING_ASM)
    def("gcd_int32_asm", gcd_int32_asm, (arg("a"), arg("b")));
    def("time_gcd_int32_asm", time_gcd_int32_asm);
# if defined(__x86_64__)
    def("gcd_int64_asm", gcd_int64_asm, (arg("a"), arg("b")));
    def("time_gcd_int64_asm", time_gcd_int64_asm);
# endif
#endif

    def("floating_point_epsilon_float_get",
      &floating_point_epsilon<float>::get);
    def("floating_point_epsilon_double_get",
      &floating_point_epsilon<double>::get);

    def("erf", (double(*)(double const&)) erf);
    def("erf",
      (scitbx::af::shared<double>(*)(
        scitbx::af::const_ref<double> const&)) erf);
    def("erfc", (double(*)(double const&)) erfc);
    def("erfcx", (double(*)(double const&)) erfcx);

    def("parabolic_cylinder_d", (double(*)(double, double))
      parabolic_cylinder_d::dv);

    // G-function
    def("GfuncOfRSsqr_approx", (double(*)(double))
      g_function::GfuncOfRSsqr_approx);

    def("bessel_i1_over_i0", (double(*)(double const&)) bessel::i1_over_i0);
    def("bessel_i1_over_i0",
      (scitbx::af::shared<double>(*)(scitbx::af::const_ref<double> const&))
        bessel::i1_over_i0);
    def("bessel_inverse_i1_over_i0",
      (double(*)(double const&)) bessel::inverse_i1_over_i0);
    def("inverse_bessel_i1_over_i0", (scitbx::af::shared<double>(*)(
         scitbx::af::const_ref<double> const&)) bessel::inverse_i1_over_i0);
    def("bessel_i0", (double(*)(double const&)) bessel::i0);
    def("bessel_i1", (double(*)(double const&)) bessel::i1);
    def("bessel_ln_of_i0", (double(*)(double const&)) bessel::ln_of_i0);
    def("ei1", (double(*)(double const&)) bessel::ei1);
    def("ei0", (double(*)(double const&)) bessel::ei0);

    typedef return_value_policy<return_by_value> rbv;
    namespace smg=scitbx::math::gaussian_fit_1d_analytical;
    class_<smg::compute<> >("gaussian_fit_1d_analytical")
      .def(init<
           af::const_ref<double> const&,
           af::const_ref<double> const&,
           af::const_ref<double> const& >(
             (arg("x"),
              arg("y"),
              arg("z"))))
      .def(init<
           af::const_ref<double> const&,
           af::const_ref<double> const& >(
             (arg("x"),
              arg("y"))))
      .add_property("a", make_getter(&smg::compute<>::a, rbv()))
      .add_property("b", make_getter(&smg::compute<>::b, rbv()))
    ;

    //typedef return_value_policy<return_by_value> rbv;
    namespace cueq=scitbx::math::cubic_equation;
    class_<cueq::real<> >("cubic_equation_real")
      .def(init<
           double const&,
           double const&,
           double const&,
           double const& >(
             (arg("a"),
              arg("b"),
              arg("c"),
              arg("d"))))
      .def("residual", &cueq::real<>::residual)
      .add_property("x", make_getter(&cueq::real<>::x, rbv()))
      .add_property("A", make_getter(&cueq::real<>::A,  rbv()))
      .add_property("B", make_getter(&cueq::real<>::B,  rbv()))
      .add_property("D", make_getter(&cueq::real<>::D,  rbv()))
    ;

#if defined(SCITBX_MATH_BESSEL_HAS_SPHERICAL)
    def("spherical_bessel",
      (double(*)(int const&, double const&))
        bessel::spherical_bessel);
    def("spherical_bessel_array",
      (scitbx::af::shared< double> (*)(
        int const&, scitbx::af::shared<double> const&))
          bessel::spherical_bessel_array);
    def("bessel_J",
      (double(*)(int const&, double const&))
        bessel::bessel_J);
    def("bessel_J_array",
      (scitbx::af::shared< double> (*)(
        int const&, scitbx::af::shared<double> const&))
          bessel::bessel_J_array);
    def ("bessel_J_zeroes",
           (scitbx::af::shared< double> (*)(
        double const&, int const&))
        bessel::bessel_J_zeroes);
    def ("sph_bessel_j_zeroes",
           (scitbx::af::shared< double> (*)(
        double const&, int const&))
        bessel::sph_bessel_j_zeroes);

#endif

    def("gamma_complete", (double(*)(double const&, bool))
      gamma::complete, (
        arg("x"),
        arg("minimax")=true));
    def("gamma_incomplete", (double(*)(double const&,
                                       double const&,
                                       unsigned))
      gamma::incomplete, (arg("a"),
        arg("x"),
        arg("max_iterations")=500));
    def("gamma_incomplete_complement",(double(*)(double const&,
                                                 double const&,
                                                 unsigned))
      gamma::incomplete_complement, (
        arg("a"),
        arg("x"),
        arg("max_iterations")=500));
    def("exponential_integral_e1z", (double(*)(double const&))
         gamma::exponential_integral_e1z );

    def("lambertw", (double(*)(double const&, unsigned))
      lambertw, (
        arg("x"),
        arg("max_iterations")=100));

    wrap_basic_statistics();
    wrap_gaussian();
    wrap_golay();
    wrap_minimum_covering_sphere();
    wrap_principal_axes_of_inertia();
    wrap_row_echelon();
    wrap_tensor_rank_2();
    wrap_icosahedron();
    wrap_chebyshev_base();
    wrap_chebyshev_polynome();
    wrap_chebyshev_fitter();
    wrap_chebyshev_lsq();
    wrap_slatec();
    wrap_line_search();
    wrap_r3_rotation();
    wrap_resample();
    wrap_quadrature();
    wrap_unimodular_generator();
    wrap_halton();
    wrap_least_squares_plane();
    wrap_continued_fraction();
    wrap_numeric_limits();
    wrap_distributions();
    wrap_exp_functions();
    wrap_zernike();
    wrap_zernike_mom();
    wrap_2d_zernike_mom();
    wrap_weighted_covariance();
    wrap_dmatrix();
    wrap_correlation();
    wrap_interpolation();
    wrap_tetrahedron();
    wrap_angle_derivative();

    def("superpose_kearsley_rotation", superpose_kearsley_rotation, (
      arg("reference_sites"), arg("other_sites")));

    def("dihedral_angle",
      dihedral_angle<af::tiny<vec3<double>, 4> >, (
        arg("sites"), arg("deg")=false));
    def("dihedral_angle",
      dihedral_angle<af::const_ref<vec3<double> > >, (
        arg("sites"), arg("deg")=false));

    def( "euler_angles_xyz_matrix", euler_angles_xyz_matrix, (
      arg("ax"), arg("ay"), arg("az")));
    def( "euler_angles_xyz_angles", euler_angles_xyz_angles, (
      arg("m"), arg("eps")=1e-12));

    def( "euler_angles_yzx_matrix", euler_angles_yzx_matrix, (
      arg("ay"), arg("az"), arg("ax")));
    def( "euler_angles_yzx_angles", euler_angles_yzx_angles, (
      arg("m"), arg("eps")=1e-12));

    def( "euler_angles_zyz_matrix", euler_angles_zyz_matrix, (
      arg("az1"), arg("ay"), arg("az3")));
    def( "euler_angles_zyz_angles", euler_angles_zyz_angles, (
      arg("m"), arg("eps")=1e-12));

    def("approx_sqrt", math::approx_sqrt);

    def("cos_table",
      (double(*)(
         af::const_ref<double> const&,
         double,
         double const& ,
         int const& ,
         bool))
           math::cos_table, (
             arg("table"), arg("arg"), arg("step"), arg("n"),
             arg("interpolate")));

    def("sin_table",
      (double(*)(
         af::const_ref<double> const&,
         double,
         double const& ,
         int const& ,
         bool))
           math::sin_table, (
             arg("table"), arg("arg"), arg("step"), arg("n"),
             arg("interpolate")));

    def("signed_phase_error",
      (double(*)(
        double const&, double const&, bool))
          math::signed_phase_error, (
            arg("phi1"), arg("phi2"), arg("deg")=false));
    def("signed_phase_error",
      (af::shared<double>(*)(
        af::const_ref<double> const&, af::const_ref<double> const&, bool))
          math::signed_phase_error, (
            arg("phi1"), arg("phi2"), arg("deg")=false));
    def("phase_error",
      (double(*)(
        double const&, double const&, bool))
          math::phase_error, (
            arg("phi1"), arg("phi2"), arg("deg")=false));
    def("phase_error",
      (af::shared<double>(*)(
        af::const_ref<double> const&, af::const_ref<double> const&, bool))
          math::phase_error, (
            arg("phi1"), arg("phi2"), arg("deg")=false));
    def("nearest_phase",
      (double(*)(
        double const&, double const&, bool))
          math::nearest_phase, (
            arg("reference"), arg("other"), arg("deg")=false));
    def("nearest_phase",
      (af::shared<double>(*)(
        af::const_ref<double> const&, af::const_ref<double> const&, bool))
          math::nearest_phase, (
            arg("reference"), arg("other"), arg("deg")=false));
    def("divmod", math::divmod);
    approx_equal_relatively_wrapper<double>::wrap();
    approx_equal_relatively_wrapper<std::complex<double> >::wrap();
    {
      af::tiny<vec3<double>, 3> (*f1)(vec3<double> const &, vec3<double> const &,
                                      bool) = &orthonormal_basis;
      af::tiny<vec3<double>, 3> (*f2)(vec3<double> const &, int,
                                      vec3<double> const &, int,
                                      bool) = &orthonormal_basis;
      def("orthonormal_basis", f1, (arg("v0"), arg("v1"),
                                    arg("right_handed")=true));
      def("orthonormal_basis", f2, (arg("v0"), arg("axis_index_1"),
                                    arg("v1"), arg("axis_index_2"),
                                    arg("right_handed")=true));
    }
    def("distance_difference_matrix", distance_difference_matrix<double>, (
      arg("sites1"), arg("sites2")));
  }

}}}} // namespace scitbx::math::boost_python::<anonymous>

BOOST_PYTHON_MODULE(scitbx_math_ext)
{
  scitbx::math::boost_python::init_module();
}


#include <Eigen/Dense>
#include <boost/mpl/at.hpp>
#include <boost/program_options.hpp>
#include <chrono>
#include <cmath>
#include <cstdio>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <limits>
#include "bte_config.h"
#include "aux/rdtsc_timer.hpp"
#include "collision_tensor/collision_tensor_galerkin.hpp"
#include "collision_tensor/dense/collision_tensor_zlastAM_eigen.hpp"
#include "collision_tensor/dense/multi_slices_factory.hpp"
#include "collision_tensor/dense/storage/vbcrs_sparsity.hpp"
#include "collision_tensor/dense/cluster_vbcrs_sparsity.hpp"
#include "aux/filtered_range.hpp"
#include "spectral/basis/spectral_basis_factory_ks.hpp"

namespace po = boost::program_options;

#include <yaml-cpp/yaml.h>

using namespace std;
using namespace boltzmann;

typedef ct_dense::CollisionTensorZLastAMEigen ct_dense_t;
typedef SpectralBasisFactoryKS basis_factory_t;
typedef SpectralBasisFactoryKS::basis_type basis_type;

int
main(int argc, char* argv[])
{
  std::string version_id = GIT_SHA1;
  cout << "VersionID: " << version_id << "@" << GIT_BNAME << std::endl;

  int K;
  int min_blk_size;
  if (argc < 3) {
    cerr << "usage: " << argv[0] << " K blksize"
         << "\nK: polynomial degree"
         << "\nblksize: minimum admissible block size"
         << "\n";
    return 1;
  } else {
    K = atoi(argv[1]);
    min_blk_size = atoi(argv[2]);
    cerr << "K: " << K << "\n";
  }

  // create basis
  basis_type basis;
  SpectralBasisFactoryKS::create(basis, K);
  unsigned int N = basis.n_dofs();

  ct_dense::multi_slices_factory::container_t multi_slices;
  ct_dense::multi_slices_factory::create(multi_slices, basis);

  std::vector<ct_dense::VBCRSSparsity<>> vbcrs_sparsity_patterns(2 * K - 1);
  int i = 0;
  cout << setw(10) << "slice id" << setw(15) << "msize" << setw(15) << "dimz"
       << "\n";
  for (auto& mslice : multi_slices) {
    auto& vbcrs = vbcrs_sparsity_patterns[i++];
    vbcrs.init(mslice.second.data(), K);
    unsigned int msize = vbcrs.memsize();
    cout << setw(10) << i << setw(15) << msize << setw(15) << vbcrs.dimz() << "\n";
  }
  cout << "----------------------------------------------------------------------"
       << "\n";

  typedef SpectralBasisFactoryKS::elem_t elem_t;
  typedef typename boost::mpl::at_c<typename elem_t::types_t, 0>::type fa_type;
  typename elem_t::Acc::template get<fa_type> fa_accessor;
  // find elements to cluster (combine)
 struct block_t
  {
    int l;
    enum TRIG t;
    int index_first;
    int index_last;
  };
 auto cmp = [&fa_accessor](const elem_t& e, int l, enum TRIG t) {
   auto id = fa_accessor(e).get_id();
   return (id.l == l && TRIG(id.t) == t);
 };

  std::vector<block_t> blocks;
  int offset = 0;
  for (int l = 0; l < K; ++l) {
    for (auto t : {TRIG::COS, TRIG::SIN}) {
      auto range_z =
          filtered_range(basis.begin(), basis.end(), std::bind(cmp, std::placeholders::_1, l, t));
      std::vector<elem_t> elemsz(std::get<0>(range_z), std::get<1>(range_z));
      if (elemsz.size() == 0) continue;
      int size = elemsz.size();
      block_t block = {l, t, offset, offset + size};
      blocks.push_back(block);
      offset += size;
    }
  }

  struct super_block_t
  {
    int extent = 0;
    int index_first = std::numeric_limits<int>::max();
    int index_last = -1;
    std::vector<block_t> elems;
    void insert(const block_t& block)
    {
      elems.push_back(block);
      extent += block.index_last - block.index_first;
      index_first = std::min(block.index_first, index_first);
      index_last = std::max(block.index_last, index_last);
    }
  };

  std::vector<super_block_t> super_blocks;

  while (!blocks.empty()) {
    auto elem = blocks.back();
    int extent = elem.index_last - elem.index_first;
    auto& last_super_block = super_blocks.back();
    if (!super_blocks.empty() && last_super_block.extent < min_blk_size) {
      last_super_block.insert(elem);
    } else {
      super_block_t sblock;
      sblock.insert(elem);
      super_blocks.push_back(sblock);
    }
    // remove last elem
    blocks.pop_back();
  }

  std::sort(super_blocks.begin(),
            super_blocks.end(),
            [](const super_block_t& a, const super_block_t& b) {
              return a.index_first < b.index_first;
            });
  cout << "found " << super_blocks.size() << " super blocks"
       << "\n";

  for (auto sblock : super_blocks) {
    cout << "sblock.extent: " << sblock.extent << " count: " << sblock.elems.size() << ", "
         << sblock.index_first << " -> " << sblock.index_last << "\n";
  }

  ct_dense::MultiSlice::key_t k(4, TRIG::COS);
  ct_dense::VBCRSSparsity<> vbcrs_blocked;
  vbcrs_blocked.init(multi_slices[k].data(), super_blocks);
  cout << "memreq blocked: "
       << vbcrs_blocked.memsize() << "\n"
       << "nblocks: " << vbcrs_blocked.nblocks() << "\n"
       << "nrows: " << vbcrs_blocked.nblock_rows() << "\n";

  std::ofstream fout_blocked("vbcrs_blocked.dat");
  vbcrs_blocked.save(fout_blocked);
  fout_blocked.close();

  ct_dense::VBCRSSparsity<> vbcrs;
  vbcrs.init(multi_slices[k].data(), K);
  cout << "memreq: "
       << vbcrs.memsize() << "\n"
       << "nblocks: " << vbcrs.nblocks() << "\n"
       << "nrows: " << vbcrs.nblock_rows() << "\n";
  std::ofstream fout("vbcrs.dat");
  vbcrs.save(fout);
  fout.close();

  // ==================================================
  // use `cluster_vbcrs_sparsity`
  std::vector<ct_dense::VBCRSSparsity<>> vb_blocked;
  cluster_vbcrs_sparsity::cluster(vb_blocked, /* dst */
                                  vbcrs_sparsity_patterns,
                                  multi_slices,
                                  basis,
                                  min_blk_size);

  return 0;
}


#include "PCE.h"
#include "GeomCommonFunctions.h"
#include <Gui/Application.h>
#include <BRepBuilderAPI_GTransform.hxx>
#include <Eigen/Dense>
#include <Eigen/Sparse>
#include <tchar.h>
#include <iostream>

using namespace PartDesignGui;

LocalCS::LocalCS()
{
	origin = Vector2d(0.0,0.0);
	axis_x = Vector2d(1.0,0.0);
	axis_y = Math::Functs::RotationAxis2d(axis_x, -Math::Math_PI / 2.0, Vector2d(0.0, 0.0));
	Math::Functs::SetVectorLength(axis_x, 1.0);
	Math::Functs::SetVectorLength(axis_y, 1.0);
	angle = Math::Functs::GetAngleBetween(axis_x, Vector2d(1.0, 0.0));
	if (axis_x[1] < 0.0) angle = 2 * M_PI - angle;
	origin_max_y = 0.0;
	origin_min_y = 0.0;
	origin_min_x = 0.0;
	origin_max_x = 0.0;
	offset_max_y = 0.0;
	offset_min_y = 0.0;
	offset_min_x = 0.0;
	offset_max_x = 0.0;
}
LocalCS::LocalCS(const Vector2d& o, const Vector2d& x)
{
	origin = o;
	axis_x = x;
	axis_y = Math::Functs::RotationAxis2d(axis_x, -Math::Math_PI/2.0, Vector2d(0.0, 0.0));
	Math::Functs::SetVectorLength(axis_x, 1.0);
	Math::Functs::SetVectorLength(axis_y, 1.0);
	angle = Math::Functs::GetAngleBetween(axis_x, Vector2d(1.0, 0.0));
	if (axis_x[1] < 0.0) angle = 2 * M_PI - angle;
	origin_max_y = 0.0;
	origin_min_y = 0.0;
	origin_min_x = 0.0;
	origin_max_x = 0.0;
	offset_max_y = 0.0;
	offset_min_y = 0.0;
	offset_min_x = 0.0;
	offset_max_x = 0.0;
}

void LocalCS::SetCS(const Vector2d& o, const Vector2d& x)
{
	origin = o;
	axis_x = x;
	axis_y = Math::Functs::RotationAxis2d(axis_x, -Math::Math_PI / 2.0, Vector2d(0.0, 0.0));
	Math::Functs::SetVectorLength(axis_x, 1.0);
	Math::Functs::SetVectorLength(axis_y, 1.0);
	angle = Math::Functs::GetAngleBetween(axis_x, Vector2d(1.0, 0.0));
	if (axis_x[1] < 0.0) angle = 2 * M_PI - angle;
	origin_max_y = 0.0;
	origin_min_y = 0.0;
	origin_min_x = 0.0;
	origin_max_x = 0.0;
	offset_max_y = 0.0;
	offset_min_y = 0.0;
	offset_min_x = 0.0;
	offset_max_x = 0.0;
}

Vector2d LocalCS::GetLocal(const Vector2d& v) const
{
	Vector2d result = v - origin;
	result = Math::Functs::RotationAxis2d(result, angle, Vector2d(0.0, 0.0));
	return result;
}

Vector2d1 LocalCS::GetLocal(const Vector2d1& points) const
{
	Vector2d1 results;
	for (auto p : points)
		results.emplace_back(GetLocal(p));
	return results;
}

//need to check
Vector2d LocalCS::GetGlobalPos(const Vector2d& v) const
{
	Vector2d result = Math::Functs::RotationAxis2d(v, -angle, Vector2d(0.0, 0.0));
	result = result + origin;
	return result;
}

Vector2d LocalCS::GetGlobalVec(const Vector2d& v) const
{
	Vector2d result = Math::Functs::RotationAxis2d(v, -angle, Vector2d(0.0, 0.0));
	return result;
}

bool LocalCS::GetSegOrigin(const HMODULE& hModule, const double part_match_error, const Vector2d& s, const Vector2d& e, double& min_x, double& max_x) const
{
	auto cgal_2d_inter_line_line = (CGAL_2D_Intersection_Line_Line)GetProcAddress(hModule, "CGAL_2D_Intersection_Line_Line");
	Vector2d low_v = e;
	Vector2d upper_v = s;
	if (s[1] <= e[1])
	{
		low_v = s;
		upper_v = e;
	}

	//outside
	if (low_v[1] > origin_max_y || upper_v[1] < origin_min_y ||
		Math::Functs::IsAlmostZero_Double(low_v[1] - origin_max_y, part_match_error) || Math::Functs::IsAlmostZero_Double(upper_v[1] - origin_min_y, part_match_error))
		return false;

	//all in
	if ((low_v[1] > origin_min_y||Math::Functs::IsAlmostZero_Double(low_v[1]- origin_min_y, part_match_error)) &&
		(upper_v[1] < origin_max_y||Math::Functs::IsAlmostZero_Double(upper_v[1]- origin_max_y, part_match_error)))
	{
		min_x = low_v[0];
		max_x = upper_v[0];
		if (low_v[0] > upper_v[0])
		{
			min_x = upper_v[0];
			max_x = low_v[0];
		}
		return true;
	}

	//cutting lower
	if (low_v[1]< origin_min_y && upper_v[1]>origin_min_y && (upper_v[1] < origin_max_y||
		Math::Functs::IsAlmostZero_Double(upper_v[1]- origin_max_y, part_match_error)))
	{
		Vector2d inter;
		if (cgal_2d_inter_line_line(low_v, upper_v, Vector2d(0.0, origin_min_y), Vector2d(1.0, origin_min_y), inter))
		{
			min_x = inter[0];
			max_x = upper_v[0];
			if (inter[0] > upper_v[0])
			{
				min_x = upper_v[0];
				max_x = inter[0];
			}
			return true;
		}
	}

	//cutting upper
	if (upper_v[1] > origin_max_y && (low_v[1] > origin_min_y||Math::Functs::IsAlmostZero_Double(low_v[1]- origin_min_y, part_match_error)) && low_v[1] < origin_max_y)
	{
		Vector2d inter;
		if (cgal_2d_inter_line_line(low_v, upper_v, Vector2d(0.0, origin_max_y), Vector2d(1.0, origin_max_y), inter))
		{
			min_x = low_v[0];
			max_x = inter[0];
			if (low_v[0] > inter[0])
			{
				min_x = inter[0];
				max_x = low_v[0];
			}
			return true;
		}
	}

	if (upper_v[1] > origin_max_y && low_v[1] < origin_min_y)
	{
		Vector2d inter_0;
		bool b0 = cgal_2d_inter_line_line(low_v, upper_v, Vector2d(0.0, origin_min_y), Vector2d(1.0, origin_min_y), inter_0);
		Vector2d inter_1;
		bool b1 = cgal_2d_inter_line_line(low_v, upper_v, Vector2d(0.0, origin_max_y), Vector2d(1.0, origin_max_y), inter_1);

		if (b0 && b1)
		{
			min_x = inter_0[0];
			max_x = inter_1[0];
			if (inter_0[0] > inter_1[0])
			{
				min_x = inter_1[0];
				max_x = inter_0[0];
			}
			return true;
		}
	}
	return false;
}

bool LocalCS::GetSegOffset(const HMODULE& hModule, const double part_match_error, const Vector2d& s, const Vector2d& e, double& min_x, double& max_x) const
{
	auto cgal_2d_inter_line_line = (CGAL_2D_Intersection_Line_Line)GetProcAddress(hModule, "CGAL_2D_Intersection_Line_Line");

	Vector2d low_v = e;
	Vector2d upper_v = s;
	if (s[1] <= e[1])
	{
		low_v = s;
		upper_v = e;
	}

	//outside
	if (low_v[1] > offset_max_y || upper_v[1] < offset_min_y ||
		Math::Functs::IsAlmostZero_Double(low_v[1] - offset_max_y, part_match_error) ||
		Math::Functs::IsAlmostZero_Double(upper_v[1] - offset_min_y, part_match_error))
		return false;

	//all in
	if (low_v[1] >= offset_min_y && upper_v[1] <= offset_max_y)
	{
		min_x = low_v[0];
		max_x = upper_v[0];
		if (low_v[0] > upper_v[0])
		{
			min_x = upper_v[0];
			max_x = low_v[0];
		}
		return true;
	}

	//cutting lower
	if (low_v[1]< offset_min_y && upper_v[1]>offset_min_y && upper_v[1] <= offset_max_y)
	{
		Vector2d inter;
		if (cgal_2d_inter_line_line(low_v, upper_v, Vector2d(0.0, offset_min_y), Vector2d(1.0, offset_min_y), inter))
		{
			min_x = inter[0];
			max_x = upper_v[0];
			if (inter[0] > upper_v[0])
			{
				min_x = upper_v[0];
				max_x = inter[0];
			}
			return true;
		}
	}

	//cutting upper
	if (upper_v[1] > offset_max_y && low_v[1] >= offset_min_y && low_v[1] < offset_max_y)
	{
		Vector2d inter;
		if (cgal_2d_inter_line_line(low_v, upper_v, Vector2d(0.0, offset_max_y), Vector2d(1.0, offset_max_y), inter))
		{
			min_x = low_v[0];
			max_x = inter[0];
			if (low_v[0] > inter[0])
			{
				min_x = inter[0];
				max_x = low_v[0];
			}
			return true;
		}
	}

	if (upper_v[1] > offset_max_y && low_v[1] < offset_min_y)
	{
		Vector2d inter_0;
		bool b0 = cgal_2d_inter_line_line(low_v, upper_v, Vector2d(0.0, offset_min_y), Vector2d(1.0, offset_min_y), inter_0);
		Vector2d inter_1;
		bool b1 = cgal_2d_inter_line_line(low_v, upper_v, Vector2d(0.0, offset_max_y), Vector2d(1.0, offset_max_y), inter_1);

		if (b0 && b1)
		{
			min_x = inter_0[0];
			max_x = inter_1[0];
			if (inter_0[0] > inter_1[0])
			{
				min_x = inter_1[0];
				max_x = inter_0[0];
			}
			return true;
		}
	}
	return false;
}

Vector2d1 LocalCS::SortX(const Vector2d1& segs) const
{
	Vector2d1 sort_segs;
	if (segs.size() == 0) return sort_segs;
	double minimal_x = segs[0][0];
	double maximal_x = segs[0][1];

	for (int iter = 0; iter < segs.size(); iter++)
	{
		auto& seg = segs[iter];
		if (seg[0] >= minimal_x && seg[0] <= maximal_x)
		{
			if (seg[1] > maximal_x)
				maximal_x = seg[1];
		}
		else
		{
			sort_segs.emplace_back(minimal_x, maximal_x);
			minimal_x = segs[iter][0];
			maximal_x = segs[iter][1];
		}

		if (iter == segs.size() - 1)
		{
			sort_segs.emplace_back(minimal_x, maximal_x);
			break;
		}
	}
	return sort_segs;
}

Vector2d1 LocalCS::EmptyX(const Vector2d1& sort_segs) const
{
	Vector2d1 empty_segs;
	for (int i = 1; i < sort_segs.size(); i++)
		empty_segs.emplace_back(sort_segs[i - 1][1], sort_segs[i][0]);
	return empty_segs;
}