// Copyright (c) 2020 Chris Richardson & Matthew Scroggs
// FEniCS Project
// SPDX-License-Identifier:    MIT

#include "nce-rtc.h"
#include "core/dof-permutations.h"
#include "core/element-families.h"
#include "core/log.h"
#include "core/mappings.h"
#include "core/moments.h"
#include "core/polyset.h"
#include "core/quadrature.h"
#include "lagrange.h"
#include <Eigen/Dense>
#include <numeric>
#include <vector>

using namespace basix;

//----------------------------------------------------------------------------
FiniteElement basix::create_rtc(cell::type celltype, int degree)
{
  if (celltype != cell::type::quadrilateral
      and celltype != cell::type::hexahedron)
    throw std::runtime_error("Unsupported cell type");

  if (degree > 4)
  {
    // TODO: suggest alternative with non-uniform points once implemented
    LOG(WARNING) << "RTC spaces with high degree using equally spaced"
                 << " points are unstable.";
  }

  const int tdim = cell::topological_dimension(celltype);

  const cell::type facettype
      = (tdim == 2) ? cell::type::interval : cell::type::quadrilateral;

  // Evaluate the expansion polynomials at the quadrature points
  auto [Qpts, Qwts]
      = quadrature::make_quadrature("default", celltype, 2 * degree);
  Eigen::ArrayXXd polyset_at_Qpts
      = polyset::tabulate(celltype, degree, 0, Qpts)[0];

  // The number of order (degree) polynomials
  const int psize = polyset_at_Qpts.cols();

  const int facet_count = tdim == 2 ? 4 : 6;
  const int facet_dofs = polyset::dim(facettype, degree - 1);
  const int internal_dofs = tdim == 2 ? 2 * degree * (degree - 1)
                                      : 3 * degree * degree * (degree - 1);
  const int ndofs = facet_count * facet_dofs + internal_dofs;

  // Create coefficients for order (degree-1) vector polynomials
  Eigen::MatrixXd wcoeffs = Eigen::MatrixXd::Zero(ndofs, psize * tdim);

  const int nv_interval = polyset::dim(cell::type::interval, degree);
  const int ns_interval = polyset::dim(cell::type::interval, degree - 1);
  int dof = 0;
  if (tdim == 2)
  {
    for (int d = 0; d < tdim; ++d)
      for (int i = 0; i < ns_interval; ++i)
        for (int j = 0; j < ns_interval; ++j)
          wcoeffs(dof++, psize * d + i * nv_interval + j) = 1;
  }
  else
  {
    for (int d = 0; d < tdim; ++d)
      for (int i = 0; i < ns_interval; ++i)
        for (int j = 0; j < ns_interval; ++j)
          for (int k = 0; k < ns_interval; ++k)
            wcoeffs(dof++, psize * d + i * nv_interval * nv_interval
                               + j * nv_interval + k)
                = 1;
  }

  // Create coefficients for additional polynomials in the div space
  for (int i = 0; i < pow(degree, tdim - 1); ++i)
  {
    std::vector<int> indices(tdim - 1);
    if (tdim == 2)
      indices[0] = i;
    else
    {
      indices[0] = i / degree;
      indices[1] = i % degree;
    }
    for (int d = 0; d < tdim; ++d)
    {
      int n = 0;
      Eigen::ArrayXd integrand = Qpts.col(d);
      for (int j = 1; j < degree; ++j)
        integrand *= Qpts.col(d);
      for (int c = 0; c < tdim; ++c)
      {
        if (c != d)
        {
          for (int j = 0; j < indices[n]; ++j)
            integrand *= Qpts.col(c);
          ++n;
        }
      }
      for (int k = 0; k < psize; ++k)
      {
        const double w_sum = (Qwts * integrand * polyset_at_Qpts.col(k)).sum();
        wcoeffs(dof, k + psize * d) = w_sum;
      }
      ++dof;
    }
  }

  // Dual space
  Eigen::MatrixXd dual = Eigen::MatrixXd::Zero(ndofs, psize * tdim);

  // quadrature degree
  int quad_deg = 2 * degree;

  // Add rows to dualmat for integral moments on facets
  dual.block(0, 0, facet_count * facet_dofs, psize * tdim)
      = moments::make_normal_integral_moments(
          create_dlagrange(facettype, degree - 1), celltype, tdim, degree,
          quad_deg);

  // Add rows to dualmat for integral moments on interior
  if (degree > 1)
  {
    // Interior integral moment
    dual.block(facet_count * facet_dofs, 0, internal_dofs, psize * tdim)
        = moments::make_dot_integral_moments(create_nce(celltype, degree - 1),
                                             celltype, tdim, degree, quad_deg);
  }

  const std::vector<std::vector<std::vector<int>>> topology
      = cell::topology(celltype);

  int perm_count = 0;
  for (int i = 1; i < tdim; ++i)
    perm_count += topology[i].size() * i;

  std::vector<Eigen::MatrixXd> base_permutations(
      perm_count, Eigen::MatrixXd::Identity(ndofs, ndofs));
  if (tdim == 2)
  {
    Eigen::ArrayXi edge_ref = dofperms::interval_reflection(degree);
    Eigen::ArrayXXd edge_dir
        = dofperms::interval_reflection_tangent_directions(degree);
    for (int edge = 0; edge < facet_count; ++edge)
    {
      const int start = edge_ref.size() * edge;
      for (int i = 0; i < edge_ref.size(); ++i)
      {
        base_permutations[edge](start + i, start + i) = 0;
        base_permutations[edge](start + i, start + edge_ref[i]) = 1;
      }
      Eigen::MatrixXd directions = Eigen::MatrixXd::Identity(ndofs, ndofs);
      directions.block(edge_dir.rows() * edge, edge_dir.cols() * edge,
                       edge_dir.rows(), edge_dir.cols())
          = edge_dir;
      base_permutations[edge] *= directions;
    }
  }
  else if (tdim == 3)
  {
    Eigen::ArrayXi face_ref = dofperms::quadrilateral_reflection(degree);
    Eigen::ArrayXi face_rot = dofperms::quadrilateral_rotation(degree);

    for (int face = 0; face < facet_count; ++face)
    {
      const int start = face_ref.size() * face;
      for (int i = 0; i < face_rot.size(); ++i)
      {
        base_permutations[12 + 2 * face](start + i, start + i) = 0;
        base_permutations[12 + 2 * face](start + i, start + face_rot[i]) = 1;
        base_permutations[12 + 2 * face + 1](start + i, start + i) = 0;
        base_permutations[12 + 2 * face + 1](start + i, start + face_ref[i])
            = -1;
      }
    }
  }

  std::vector<std::vector<int>> entity_dofs(topology.size());
  for (int i = 0; i < tdim - 1; ++i)
    entity_dofs[i].resize(topology[i].size(), 0);
  entity_dofs[tdim - 1].resize(topology[tdim - 1].size(), facet_dofs);
  entity_dofs[tdim] = {internal_dofs};

  Eigen::MatrixXd coeffs = compute_expansion_coefficients(wcoeffs, dual);
  return FiniteElement(element::family::RT, celltype, degree, {tdim}, coeffs,
                       entity_dofs, base_permutations, {}, {},
                       mapping::type::contravariantPiola);
}
//-----------------------------------------------------------------------------
FiniteElement basix::create_nce(cell::type celltype, int degree)
{
  if (celltype != cell::type::quadrilateral
      and celltype != cell::type::hexahedron)
    throw std::runtime_error("Unsupported cell type");

  if (degree > 4)
  {
    // TODO: suggest alternative with non-uniform points once implemented
    LOG(WARNING) << "NC spaces with high degree using equally spaced"
                 << " points are unstable.";
  }

  const int tdim = cell::topological_dimension(celltype);

  // Evaluate the expansion polynomials at the quadrature points
  auto [Qpts, Qwts]
      = quadrature::make_quadrature("default", celltype, 2 * degree);
  Eigen::ArrayXXd polyset_at_Qpts
      = polyset::tabulate(celltype, degree, 0, Qpts)[0];

  // The number of order (degree) polynomials
  const int psize = polyset_at_Qpts.cols();

  const int edge_count = tdim == 2 ? 4 : 12;
  const int edge_dofs = polyset::dim(cell::type::interval, degree - 1);
  const int face_count = tdim == 2 ? 1 : 6;
  const int face_dofs = 2 * degree * (degree - 1);
  const int volume_count = tdim == 2 ? 0 : 1;
  const int volume_dofs = 3 * degree * (degree - 1) * (degree - 1);

  const int ndofs = edge_count * edge_dofs + face_count * face_dofs
                    + volume_count * volume_dofs;

  // Create coefficients for order (degree-1) vector polynomials
  Eigen::MatrixXd wcoeffs = Eigen::MatrixXd::Zero(ndofs, psize * tdim);

  const int nv_interval = polyset::dim(cell::type::interval, degree);
  const int ns_interval = polyset::dim(cell::type::interval, degree - 1);
  int dof = 0;
  if (tdim == 2)
  {
    for (int d = 0; d < tdim; ++d)
      for (int i = 0; i < ns_interval; ++i)
        for (int j = 0; j < ns_interval; ++j)
          wcoeffs(dof++, psize * d + i * nv_interval + j) = 1;
  }
  else
  {
    for (int d = 0; d < tdim; ++d)
      for (int i = 0; i < ns_interval; ++i)
        for (int j = 0; j < ns_interval; ++j)
          for (int k = 0; k < ns_interval; ++k)
            wcoeffs(dof++, psize * d + i * nv_interval * nv_interval
                               + j * nv_interval + k)
                = 1;
  }

  // Create coefficients for additional polynomials in the curl space
  if (tdim == 2)
  {
    for (int i = 0; i < degree; ++i)
    {
      for (int d = 0; d < tdim; ++d)
      {
        Eigen::ArrayXd integrand = Qpts.col(1 - d);
        for (int j = 1; j < degree; ++j)
          integrand *= Qpts.col(1 - d);
        for (int j = 0; j < i; ++j)
          integrand *= Qpts.col(d);

        for (int k = 0; k < psize; ++k)
        {
          const double w_sum
              = (Qwts * integrand * polyset_at_Qpts.col(k)).sum();
          wcoeffs(dof, k + psize * d) = w_sum;
        }
        ++dof;
      }
    }
  }
  else
  {
    for (int i = 0; i < degree; ++i)
    {
      for (int j = 0; j < degree + 1; ++j)
      {
        for (int c = 0; c < tdim; ++c)
        {
          for (int d = 0; d < tdim; ++d)
          {
            if (d != c)
            {
              const int e
                  = (c == 0 || d == 0) ? ((c == 1 || d == 1) ? 2 : 1) : 0;
              if (c < e and j == degree)
                continue;
              Eigen::ArrayXd integrand = Qpts.col(e);
              for (int k = 1; k < degree; ++k)
                integrand *= Qpts.col(e);
              for (int k = 0; k < i; ++k)
                integrand *= Qpts.col(d);
              for (int k = 0; k < j; ++k)
                integrand *= Qpts.col(c);

              for (int k = 0; k < psize; ++k)
              {
                const double w_sum
                    = (Qwts * integrand * polyset_at_Qpts.col(k)).sum();
                wcoeffs(dof, k + psize * d) = w_sum;
              }
              ++dof;
            }
          }
        }
      }
    }
  }

  // Dual space
  Eigen::MatrixXd dual = Eigen::MatrixXd::Zero(ndofs, psize * tdim);
  Eigen::ArrayXXd points;
  Eigen::MatrixXd matrix;

  // quadrature degree
  int quad_deg = 2 * degree;

  // Add rows to dualmat for integral moments on facets
  dual.block(0, 0, edge_count * edge_dofs, psize * tdim)
      = moments::make_tangent_integral_moments(
          create_dlagrange(cell::type::interval, degree - 1), celltype, tdim,
          degree, quad_deg);

  Eigen::ArrayXXd points_1d;
  Eigen::MatrixXd matrix_1d;
  std::tie(points_1d, matrix_1d)
      = moments::make_tangent_integral_moments_interpolation(
          create_dlagrange(cell::type::interval, degree - 1), celltype, tdim,
          degree, quad_deg);

  if (degree == 1)
  {
    points = points_1d;
    matrix = matrix_1d;
  }

  // Add rows to dualmat for integral moments on interior
  if (degree > 1)
  {
    // Face integral moment
    dual.block(edge_count * edge_dofs, 0, face_count * face_dofs, psize * tdim)
        = moments::make_dot_integral_moments(
            create_rtc(cell::type::quadrilateral, degree - 1), celltype, tdim,
            degree, quad_deg);

    Eigen::ArrayXXd points_2d;
    Eigen::MatrixXd matrix_2d;
    std::tie(points_2d, matrix_2d)
        = moments::make_dot_integral_moments_interpolation(
            create_rtc(cell::type::quadrilateral, degree - 1), celltype, tdim,
            degree, quad_deg);

    if (tdim == 2)
    {
      points.resize(points_1d.rows() + points_2d.rows(), tdim);
      matrix.resize(matrix_1d.rows() + matrix_2d.rows(),
                    matrix_1d.cols() + matrix_2d.cols());
      matrix.setZero();

      points.block(0, 0, points_1d.rows(), tdim) = points_1d;
      points.block(points_1d.rows(), 0, points_2d.rows(), tdim) = points_2d;

      const int r1d = matrix_1d.rows();
      const int r2d = matrix_2d.rows();
      const int c1d = matrix_1d.cols() / tdim;
      const int c2d = matrix_2d.cols() / tdim;
      for (int i = 0; i < tdim; ++i)
      {
        matrix.block(0, i * (c1d + c2d), r1d, c1d)
            = matrix_1d.block(0, i * c1d, r1d, c1d);
        matrix.block(r1d, i * (c1d + c2d) + c1d, r2d, c2d)
            = matrix_2d.block(0, i * c2d, r2d, c2d);
      }
    }

    if (tdim == 3)
    {
      // Interior integral moment
      dual.block(edge_count * edge_dofs + face_count * face_dofs, 0,
                 volume_dofs, psize * tdim)
          = moments::make_dot_integral_moments(
              create_rtc(cell::type::hexahedron, degree - 1), celltype, tdim,
              degree, quad_deg);

      Eigen::ArrayXXd points_3d;
      Eigen::MatrixXd matrix_3d;
      std::tie(points_3d, matrix_3d)
          = moments::make_dot_integral_moments_interpolation(
              create_rtc(cell::type::hexahedron, degree - 1), celltype, tdim,
              degree, quad_deg);

      points.resize(points_1d.rows() + points_2d.rows() + points_3d.rows(),
                    tdim);
      matrix.resize(matrix_1d.rows() + matrix_2d.rows() + matrix_3d.rows(),
                    matrix_1d.cols() + matrix_2d.cols() + matrix_3d.cols());
      matrix.setZero();

      points.block(0, 0, points_1d.rows(), tdim) = points_1d;
      points.block(points_1d.rows(), 0, points_2d.rows(), tdim) = points_2d;
      points.block(points_1d.rows() + points_2d.rows(), 0, points_3d.rows(),
                   tdim)
          = points_3d;

      const int r1d = matrix_1d.rows();
      const int r2d = matrix_2d.rows();
      const int r3d = matrix_3d.rows();
      const int c1d = matrix_1d.cols() / tdim;
      const int c2d = matrix_2d.cols() / tdim;
      const int c3d = matrix_3d.cols() / tdim;
      for (int i = 0; i < tdim; ++i)
      {
        matrix.block(0, i * (c1d + c2d + c3d), r1d, c1d)
            = matrix_1d.block(0, i * c1d, r1d, c1d);
        matrix.block(r1d, i * (c1d + c2d + c3d) + c1d, r2d, c2d)
            = matrix_2d.block(0, i * c2d, r2d, c2d);
        matrix.block(r1d + r2d, i * (c1d + c2d + c3d) + c1d + c2d, r3d, c3d)
            = matrix_3d.block(0, i * c3d, r3d, c3d);
      }
    }
  }

  const std::vector<std::vector<std::vector<int>>> topology
      = cell::topology(celltype);

  int perm_count = 0;
  for (int i = 1; i < tdim; ++i)
    perm_count += topology[i].size() * i;

  std::vector<Eigen::MatrixXd> base_permutations(
      perm_count, Eigen::MatrixXd::Identity(ndofs, ndofs));

  Eigen::ArrayXi edge_ref = dofperms::interval_reflection(degree);
  Eigen::ArrayXXd edge_dir
      = dofperms::interval_reflection_tangent_directions(degree);

  for (int edge = 0; edge < edge_count; ++edge)
  {
    const int start = edge_ref.size() * edge;
    for (int i = 0; i < edge_ref.size(); ++i)
    {
      base_permutations[edge](start + i, start + i) = 0;
      base_permutations[edge](start + i, start + edge_ref[i]) = 1;
    }
    Eigen::MatrixXd directions = Eigen::MatrixXd::Identity(ndofs, ndofs);
    directions.block(edge_dir.rows() * edge, edge_dir.cols() * edge,
                     edge_dir.rows(), edge_dir.cols())
        = edge_dir;
    base_permutations[edge] *= directions;
  }

  if (tdim == 3 and degree > 1)
  {
    Eigen::MatrixXd face_ref
        = dofperms::quadrilateral_rtc_reflection(degree - 1);
    Eigen::MatrixXd face_rot = dofperms::quadrilateral_rtc_rotation(degree - 1);

    for (int face = 0; face < face_count; ++face)
    {
      const int start = edge_ref.size() * edge_count + face_ref.rows() * face;
      const int p = edge_count + 2 * face;

      base_permutations[p].block(start, start, face_rot.rows(), face_rot.cols())
          = face_rot;
      base_permutations[p + 1].block(start, start, face_ref.rows(),
                                     face_ref.cols())
          = face_ref;
    }
  }

  std::vector<std::vector<int>> entity_dofs(topology.size());
  entity_dofs[0].resize(topology[0].size(), 0);
  entity_dofs[1].resize(topology[1].size(), edge_dofs);
  entity_dofs[2].resize(topology[2].size(), face_dofs);
  if (tdim == 3)
    entity_dofs[3].resize(topology[3].size(), volume_dofs);

  Eigen::MatrixXd coeffs = compute_expansion_coefficients(wcoeffs, dual);
  return FiniteElement(element::family::N1E, celltype, degree, {tdim}, coeffs,
                       entity_dofs, base_permutations, points, matrix,
                       mapping::type::covariantPiola);
}
//-----------------------------------------------------------------------------
Eigen::MatrixXd basix::dofperms::quadrilateral_rtc_rotation(int degree)
{
  const int n = 2 * degree * (degree + 1);
  Eigen::MatrixXd perm = Eigen::MatrixXd::Zero(n, n);

  // Permute functions on edges
  for (int i = 0; i < degree; ++i)
  {
    perm(i, 2 * degree - 1 - i) = -1;
    perm(degree + i, 3 * degree + i) = 1;
    perm(2 * degree + i, i) = 1;
    perm(3 * degree + i, 3 * degree - 1 - i) = -1;
  }
  if (degree > 1)
    perm.block(4 * degree, 4 * degree, n - 4 * degree, n - 4 * degree)
        = quadrilateral_nce_rotation(degree - 1);
  return perm;
}
//-----------------------------------------------------------------------------
Eigen::MatrixXd basix::dofperms::quadrilateral_nce_rotation(int degree)
{
  const int n = 2 * degree * (degree + 1);
  Eigen::MatrixXd perm = Eigen::MatrixXd::Zero(n, n);

  // Permute functions on edges
  for (int i = 0; i < degree; ++i)
  {
    perm(i, 2 * degree - 1 - i) = -1;
    perm(degree + i, 3 * degree + i) = 1;
    perm(2 * degree + i, i) = 1;
    perm(3 * degree + i, 3 * degree - 1 - i) = -1;
  }
  if (degree > 1)
    perm.block(4 * degree, 4 * degree, n - 4 * degree, n - 4 * degree)
        = quadrilateral_rtc_rotation(degree - 1);
  return perm;
}
//-----------------------------------------------------------------------------
Eigen::MatrixXd basix::dofperms::quadrilateral_rtc_reflection(int degree)
{
  const int n = 2 * degree * (degree + 1);
  Eigen::MatrixXd perm = Eigen::MatrixXd::Zero(n, n);

  // Permute functions on edges
  for (int i = 0; i < degree; ++i)
  {
    perm(i, degree + i) = -1;
    perm(degree + i, i) = -1;
    perm(2 * degree + i, 3 * degree + i) = -1;
    perm(3 * degree + i, 2 * degree + i) = -1;
  }
  if (degree > 1)
    perm.block(4 * degree, 4 * degree, n - 4 * degree, n - 4 * degree)
        = quadrilateral_nce_reflection(degree - 1);

  return perm;
}
//-----------------------------------------------------------------------------
Eigen::MatrixXd basix::dofperms::quadrilateral_nce_reflection(int degree)
{
  const int n = 2 * degree * (degree + 1);
  Eigen::MatrixXd perm = Eigen::MatrixXd::Zero(n, n);

  // Permute functions on edges
  for (int i = 0; i < degree; ++i)
  {
    perm(i, degree + i) = 1;
    perm(degree + i, i) = 1;
    perm(2 * degree + i, 3 * degree + i) = 1;
    perm(3 * degree + i, 2 * degree + i) = 1;
  }
  if (degree > 1)
    perm.block(4 * degree, 4 * degree, n - 4 * degree, n - 4 * degree)
        = quadrilateral_rtc_reflection(degree - 1);

  return perm;
}
//-----------------------------------------------------------------------------


/*
 *  tutte.hpp
 *
 *
 *  Created by Andrea Bedini on 24/Nov/2011.
 *  Copyright (c) 2011-2014, Andrea Bedini <andrea.bedini@gmail.com>.
 *
 *  Distributed under the terms of the Modified BSD License.
 *  The full license is in the file COPYING, distributed as part of
 *  this software.
 *
 */

#ifndef TUTTE_HPP
#define TUTTE_HPP

#include "connectivity/connectivity.hpp"
#include <boost/unordered/unordered_map.hpp>

template<class Weight>
class tutte
{
  const Weight Q;
  const Weight v;

public:
  using weight_type = Weight ;
  using table_type = boost::unordered_map<connectivity, weight_type>;

  template<class T, class U>
  tutte(T const& Q_, U const& v_) : Q(Q_), v(v_) {}

  table_type empty_state(unsigned int size) const
  {
    table_type tmp_table;
    tmp_table[connectivity(size)] = Weight(1);
    return tmp_table;
  }

  table_type
  join_operator(unsigned int i, unsigned int j, table_type const& t) const
  {
    table_type tmp_table;
    for (auto const & e : t) {
      tmp_table[e.first] += e.second;
      tmp_table[connectivity(e.first).connect(i, j).canonicalize()]
        += e.second * v;
    }
    return tmp_table;
  }

  table_type
  delete_operator(unsigned int i, table_type const& t) const
  {
    table_type tmp_table;
    for (auto const& e : t) {
      tmp_table[connectivity(e.first).delete_node(i).canonicalize()]
        += (e.first.singleton(i) ? (e.second * Q) : e.second);
    }
    return tmp_table;
  }

  template<class Mapping>
  table_type
  table_fusion(Mapping A_to_B,
    table_type const& A_table,
    table_type const& B_table) const
  {
    table_type tmp_table;
    for (auto const & eA : A_table) {
      for (auto const & eB : B_table) {
        connectivity c = eB.first;
        eA.first.decompose([&](unsigned int i, unsigned int j) {
          c.connect(A_to_B[i], A_to_B[j]);
        });
        tmp_table[c.canonicalize()] += eA.second * eB.second;
      }
    }
    return tmp_table;
  }
};
#endif


/*
 Copyright (C) 2021 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include "toplevelfixture.hpp"

#include <qle/termstructures/blackdeltautilities.hpp>
#include <qle/termstructures/blackvolsurfacebfrr.hpp>

#include <ql/experimental/fx/blackdeltacalculator.hpp>
#include <ql/pricingengines/blackformula.hpp>
#include <ql/quotes/simplequote.hpp>
#include <ql/termstructures/yield/flatforward.hpp>
#include <ql/time/calendars/nullcalendar.hpp>
#include <ql/time/daycounters/actual365fixed.hpp>

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

using namespace QuantLib;
using namespace QuantExt;
using namespace boost::unit_test_framework;

struct BFRRVolFixture : public qle::test::TopLevelFixture {
public:
    BFRRVolFixture() { Settings::instance().evaluationDate() = refDate; }
    Date refDate = Date(13, April, 2021);
    std::vector<Date> dates = {refDate + 1 * Years, refDate + 3 * Years};
    std::vector<Real> deltas = {0.10, 0.25};
    std::vector<std::vector<Real>> bfQuotes = {{0.02, 0.01}, {0.01, 0.0050}};
    std::vector<std::vector<Real>> rrQuotes = {{-0.015, -0.012}, {-0.011, -0.009}};
    std::vector<Real> atmQuotes = {0.09, 0.08};
    Actual365Fixed dc;
    NullCalendar cal;
    Handle<Quote> spot = Handle<Quote>(boost::make_shared<SimpleQuote>(1.2));
    Size spotDays = 2;
    Handle<YieldTermStructure> domesticTS =
        Handle<YieldTermStructure>(boost::make_shared<FlatForward>(refDate, 0.01, dc));
    Handle<YieldTermStructure> foreignTS =
        Handle<YieldTermStructure>(boost::make_shared<FlatForward>(refDate, 0.015, dc));
    DeltaVolQuote::DeltaType dt = DeltaVolQuote::DeltaType::PaSpot;
    DeltaVolQuote::AtmType at = DeltaVolQuote::AtmType::AtmDeltaNeutral;
    Period switchTenor = 2 * Years;
    DeltaVolQuote::DeltaType ltdt = DeltaVolQuote::DeltaType::PaFwd;
    DeltaVolQuote::AtmType ltat = DeltaVolQuote::AtmType::AtmDeltaNeutral;
    Option::Type rrInFavorOf = Option::Call;
    BlackVolatilitySurfaceBFRR::SmileInterpolation smileInterpolation =
        BlackVolatilitySurfaceBFRR::SmileInterpolation::Cubic;
};

BOOST_AUTO_TEST_SUITE(QuantExtTestSuite)

BOOST_FIXTURE_TEST_SUITE(BFRRVolSurfaceTest, BFRRVolFixture)

BOOST_AUTO_TEST_CASE(testSmileBF) {

    BOOST_TEST_MESSAGE("Testing bf/rr vol surface with smile bf quotes...");

    Real tol1 = 1E-5;

    auto vol1 = boost::make_shared<BlackVolatilitySurfaceBFRR>(
        refDate, dates, deltas, bfQuotes, rrQuotes, atmQuotes, dc, cal, spot, spotDays, cal, domesticTS, foreignTS, dt,
        at, switchTenor, ltdt, ltat, rrInFavorOf, false, smileInterpolation);

    Real t1 = vol1->timeFromReference(dates[0]);
    Real domDisc1 = domesticTS->discount(dates[0] + spotDays) / domesticTS->discount(refDate + spotDays);
    Real forDisc1 = foreignTS->discount(dates[0] + spotDays) / foreignTS->discount(refDate + spotDays);
    Real k1_1_10p = getStrikeFromDelta(Option::Put, -deltas[0], dt, spot->value(), domDisc1, forDisc1, vol1, t1);
    Real k1_1_25p = getStrikeFromDelta(Option::Put, -deltas[1], dt, spot->value(), domDisc1, forDisc1, vol1, t1);
    Real k1_1_atm = getAtmStrike(dt, at, spot->value(), domDisc1, forDisc1, vol1, t1);
    Real k1_1_25c = getStrikeFromDelta(Option::Call, deltas[1], dt, spot->value(), domDisc1, forDisc1, vol1, t1);
    Real k1_1_10c = getStrikeFromDelta(Option::Call, deltas[0], dt, spot->value(), domDisc1, forDisc1, vol1, t1);

    BOOST_CHECK_SMALL(vol1->blackVol(dates[0], k1_1_10p) - (atmQuotes[0] + bfQuotes[0][0] - 0.5 * rrQuotes[0][0]),
                      tol1);
    BOOST_CHECK_SMALL(vol1->blackVol(dates[0], k1_1_25p) - (atmQuotes[0] + bfQuotes[0][1] - 0.5 * rrQuotes[0][1]),
                      tol1);
    BOOST_CHECK_SMALL(vol1->blackVol(dates[0], k1_1_atm) - atmQuotes[0], tol1);
    BOOST_CHECK_SMALL(vol1->blackVol(dates[0], k1_1_25c) - (atmQuotes[0] + bfQuotes[0][1] + 0.5 * rrQuotes[0][1]),
                      tol1);
    BOOST_CHECK_SMALL(vol1->blackVol(dates[0], k1_1_10c) - (atmQuotes[0] + bfQuotes[0][0] + 0.5 * rrQuotes[0][0]),
                      tol1);

    Real t2 = vol1->timeFromReference(dates[1]);
    Real domDisc2 = domesticTS->discount(dates[1] + spotDays) / domesticTS->discount(refDate + spotDays);
    Real forDisc2 = foreignTS->discount(dates[1] + spotDays) / foreignTS->discount(refDate + spotDays);
    Real k1_2_10p = getStrikeFromDelta(Option::Put, -deltas[0], ltdt, spot->value(), domDisc2, forDisc2, vol1, t2);
    Real k1_2_25p = getStrikeFromDelta(Option::Put, -deltas[1], ltdt, spot->value(), domDisc2, forDisc2, vol1, t2);
    Real k1_2_atm = getAtmStrike(ltdt, ltat, spot->value(), domDisc2, forDisc2, vol1, t2);
    Real k1_2_25c = getStrikeFromDelta(Option::Call, deltas[1], ltdt, spot->value(), domDisc2, forDisc2, vol1, t2);
    Real k1_2_10c = getStrikeFromDelta(Option::Call, deltas[0], ltdt, spot->value(), domDisc2, forDisc2, vol1, t2);

    BOOST_CHECK_SMALL(vol1->blackVol(dates[1], k1_2_10p) - (atmQuotes[1] + bfQuotes[1][0] - 0.5 * rrQuotes[1][0]),
                      tol1);
    BOOST_CHECK_SMALL(vol1->blackVol(dates[1], k1_2_25p) - (atmQuotes[1] + bfQuotes[1][1] - 0.5 * rrQuotes[1][1]),
                      tol1);
    BOOST_CHECK_SMALL(vol1->blackVol(dates[1], k1_2_atm) - atmQuotes[1], tol1);
    BOOST_CHECK_SMALL(vol1->blackVol(dates[1], k1_2_25c) - (atmQuotes[1] + bfQuotes[1][1] + 0.5 * rrQuotes[1][1]),
                      tol1);
    BOOST_CHECK_SMALL(vol1->blackVol(dates[1], k1_2_10c) - (atmQuotes[1] + bfQuotes[1][0] + 0.5 * rrQuotes[1][0]),
                      tol1);
}

BOOST_AUTO_TEST_CASE(testBrokerBF) {

    BOOST_TEST_MESSAGE("Testing bf/rr vol surface with broker bf quotes...");

    Real tol1 = 1E-5;
    Real tol2 = 1E-5;

    auto vol2 = boost::make_shared<BlackVolatilitySurfaceBFRR>(
        refDate, dates, deltas, bfQuotes, rrQuotes, atmQuotes, dc, cal, spot, spotDays, cal, domesticTS, foreignTS, dt,
        at, switchTenor, ltdt, ltat, rrInFavorOf, true, smileInterpolation);

    Real t1 = vol2->timeFromReference(dates[0]);
    Real domDisc1 = domesticTS->discount(dates[0] + spotDays) / domesticTS->discount(refDate + spotDays);
    Real forDisc1 = foreignTS->discount(dates[0] + spotDays) / foreignTS->discount(refDate + spotDays);
    Real t2 = vol2->timeFromReference(dates[1]);
    Real domDisc2 = domesticTS->discount(dates[1] + spotDays) / domesticTS->discount(refDate + spotDays);
    Real forDisc2 = foreignTS->discount(dates[1] + spotDays) / foreignTS->discount(refDate + spotDays);

    // checks for expiry 1

    Real bfvol2_1_10_broker = atmQuotes[0] + bfQuotes[0][0];
    Real bfvol2_1_25_broker = atmQuotes[0] + bfQuotes[0][1];

    BlackDeltaCalculator bdc_1_10_p(Option::Put, dt, spot->value(), domDisc1, forDisc1,
                                    bfvol2_1_10_broker * std::sqrt(t1));
    Real k2_1_10p_broker = bdc_1_10_p.strikeFromDelta(-deltas[0]);

    BlackDeltaCalculator bdc_1_10_c(Option::Call, dt, spot->value(), domDisc1, forDisc1,
                                    bfvol2_1_10_broker * std::sqrt(t1));
    Real k2_1_10c_broker = bdc_1_10_c.strikeFromDelta(deltas[0]);

    BlackDeltaCalculator bdc_1_25_p(Option::Put, dt, spot->value(), domDisc1, forDisc1,
                                    bfvol2_1_25_broker * std::sqrt(t1));
    Real k2_1_25p_broker = bdc_1_25_p.strikeFromDelta(-deltas[1]);

    BlackDeltaCalculator bdc_1_25_c(Option::Call, dt, spot->value(), domDisc1, forDisc1,
                                    bfvol2_1_25_broker * std::sqrt(t1));
    Real k2_1_25c_broker = bdc_1_25_c.strikeFromDelta(deltas[1]);

    Real bfPrice_1_10_broker = blackFormula(Option::Put, k2_1_10p_broker, spot->value() / domDisc1 * forDisc1,
                                            bfvol2_1_10_broker * std::sqrt(t1)) +
                               blackFormula(Option::Call, k2_1_10c_broker, spot->value() / domDisc1 * forDisc1,
                                            bfvol2_1_10_broker * std::sqrt(t1));

    Real bfPrice_1_25_broker = blackFormula(Option::Put, k2_1_25p_broker, spot->value() / domDisc1 * forDisc1,
                                            bfvol2_1_25_broker * std::sqrt(t1)) +
                               blackFormula(Option::Call, k2_1_25c_broker, spot->value() / domDisc1 * forDisc1,
                                            bfvol2_1_25_broker * std::sqrt(t1));

    Real k2_1_10p = getStrikeFromDelta(Option::Put, -deltas[0], dt, spot->value(), domDisc1, forDisc1, vol2, t1);
    Real k2_1_25p = getStrikeFromDelta(Option::Put, -deltas[1], dt, spot->value(), domDisc1, forDisc1, vol2, t1);
    Real k2_1_atm = getAtmStrike(dt, at, spot->value(), domDisc1, forDisc1, vol2, t1);
    Real k2_1_25c = getStrikeFromDelta(Option::Call, deltas[1], dt, spot->value(), domDisc1, forDisc1, vol2, t1);
    Real k2_1_10c = getStrikeFromDelta(Option::Call, deltas[0], dt, spot->value(), domDisc1, forDisc1, vol2, t1);

    Real bfPrice_1_10_smile = blackFormula(Option::Put, k2_1_10p_broker, spot->value() / domDisc1 * forDisc1,
                                           std::sqrt(vol2->blackVariance(dates[0], k2_1_10p_broker))) +
                              blackFormula(Option::Call, k2_1_10c_broker, spot->value() / domDisc1 * forDisc1,
                                           std::sqrt(vol2->blackVariance(dates[0], k2_1_10c_broker)));

    Real bfPrice_1_25_smile = blackFormula(Option::Put, k2_1_25p_broker, spot->value() / domDisc1 * forDisc1,
                                           std::sqrt(vol2->blackVariance(dates[0], k2_1_25p_broker))) +
                              blackFormula(Option::Call, k2_1_25c_broker, spot->value() / domDisc1 * forDisc1,
                                           std::sqrt(vol2->blackVariance(dates[0], k2_1_25c_broker)));

    // check broker bf premium = smile bf premium

    BOOST_CHECK_SMALL(bfPrice_1_10_smile - bfPrice_1_10_broker, tol2);
    BOOST_CHECK_SMALL(bfPrice_1_25_smile - bfPrice_1_25_broker, tol2);

    // check rr and atm quotes are reproduced on smile

    BOOST_CHECK_SMALL(vol2->blackVol(dates[0], k2_1_10c) - vol2->blackVol(dates[0], k2_1_10p) - rrQuotes[0][0], tol1);
    BOOST_CHECK_SMALL(vol2->blackVol(dates[0], k2_1_25c) - vol2->blackVol(dates[0], k2_1_25p) - rrQuotes[0][1], tol1);
    BOOST_CHECK_SMALL(vol2->blackVol(dates[0], k2_1_atm) - atmQuotes[0], tol1);

    // checks for expiry 2

    Real bfvol2_2_10_broker = atmQuotes[1] + bfQuotes[1][0];
    Real bfvol2_2_25_broker = atmQuotes[1] + bfQuotes[1][1];

    BlackDeltaCalculator bdc_2_10_p(Option::Put, ltdt, spot->value(), domDisc2, forDisc2,
                                    bfvol2_2_10_broker * std::sqrt(t2));
    Real k2_2_10p_broker = bdc_2_10_p.strikeFromDelta(-deltas[0]);

    BlackDeltaCalculator bdc_2_10_c(Option::Call, ltdt, spot->value(), domDisc2, forDisc2,
                                    bfvol2_2_10_broker * std::sqrt(t2));
    Real k2_2_10c_broker = bdc_2_10_c.strikeFromDelta(deltas[0]);

    BlackDeltaCalculator bdc_2_25_p(Option::Put, ltdt, spot->value(), domDisc2, forDisc2,
                                    bfvol2_2_25_broker * std::sqrt(t2));
    Real k2_2_25p_broker = bdc_2_25_p.strikeFromDelta(-deltas[1]);

    BlackDeltaCalculator bdc_2_25_c(Option::Call, ltdt, spot->value(), domDisc2, forDisc2,
                                    bfvol2_2_25_broker * std::sqrt(t2));
    Real k2_2_25c_broker = bdc_2_25_c.strikeFromDelta(deltas[1]);

    Real bfPrice_2_10_broker = blackFormula(Option::Put, k2_2_10p_broker, spot->value() / domDisc2 * forDisc2,
                                            bfvol2_2_10_broker * std::sqrt(t2)) +
                               blackFormula(Option::Call, k2_2_10c_broker, spot->value() / domDisc2 * forDisc2,
                                            bfvol2_2_10_broker * std::sqrt(t2));

    Real bfPrice_2_25_broker = blackFormula(Option::Put, k2_2_25p_broker, spot->value() / domDisc2 * forDisc2,
                                            bfvol2_2_25_broker * std::sqrt(t2)) +
                               blackFormula(Option::Call, k2_2_25c_broker, spot->value() / domDisc2 * forDisc2,
                                            bfvol2_2_25_broker * std::sqrt(t2));

    Real k2_2_10p = getStrikeFromDelta(Option::Put, -deltas[0], ltdt, spot->value(), domDisc2, forDisc2, vol2, t2);
    Real k2_2_25p = getStrikeFromDelta(Option::Put, -deltas[1], ltdt, spot->value(), domDisc2, forDisc2, vol2, t2);
    Real k2_2_atm = getAtmStrike(ltdt, ltat, spot->value(), domDisc2, forDisc2, vol2, t2);
    Real k2_2_25c = getStrikeFromDelta(Option::Call, deltas[1], ltdt, spot->value(), domDisc2, forDisc2, vol2, t2);
    Real k2_2_10c = getStrikeFromDelta(Option::Call, deltas[0], ltdt, spot->value(), domDisc2, forDisc2, vol2, t2);

    Real bfPrice_2_10_smile = blackFormula(Option::Put, k2_2_10p_broker, spot->value() / domDisc2 * forDisc2,
                                           std::sqrt(vol2->blackVariance(dates[1], k2_2_10p_broker))) +
                              blackFormula(Option::Call, k2_2_10c_broker, spot->value() / domDisc2 * forDisc2,
                                           std::sqrt(vol2->blackVariance(dates[1], k2_2_10c_broker)));

    Real bfPrice_2_25_smile = blackFormula(Option::Put, k2_2_25p_broker, spot->value() / domDisc2 * forDisc2,
                                           std::sqrt(vol2->blackVariance(dates[1], k2_2_25p_broker))) +
                              blackFormula(Option::Call, k2_2_25c_broker, spot->value() / domDisc2 * forDisc2,
                                           std::sqrt(vol2->blackVariance(dates[1], k2_2_25c_broker)));

    // check broker bf premium = smile bf premium

    BOOST_CHECK_SMALL(bfPrice_2_10_smile - bfPrice_2_10_broker, tol2);
    BOOST_CHECK_SMALL(bfPrice_2_25_smile - bfPrice_2_25_broker, tol2);

    // check rr and atm quotes are reproduced on smile

    BOOST_CHECK_SMALL(vol2->blackVol(dates[1], k2_2_10c) - vol2->blackVol(dates[1], k2_2_10p) - rrQuotes[1][0], tol1);
    BOOST_CHECK_SMALL(vol2->blackVol(dates[1], k2_2_25c) - vol2->blackVol(dates[1], k2_2_25p) - rrQuotes[1][1], tol1);
    BOOST_CHECK_SMALL(vol2->blackVol(dates[1], k2_2_atm) - atmQuotes[1], tol1);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()


//
// Copyright (c) 2018 CNRS
//

#include <cppad/cg.hpp>

#include "pinocchio/fwd.hpp"
#include "pinocchio/multibody/model.hpp"
#include "pinocchio/multibody/data.hpp"

#include "pinocchio/algorithm/kinematics.hpp"
#include "pinocchio/algorithm/jacobian.hpp"
#include "pinocchio/algorithm/crba.hpp"
#include "pinocchio/algorithm/rnea.hpp"
#include "pinocchio/algorithm/aba.hpp"
#include "pinocchio/algorithm/joint-configuration.hpp"

#include "pinocchio/parsers/sample-models.hpp"

#include <iostream>

#include <boost/test/unit_test.hpp>
#include <boost/utility/binary.hpp>

BOOST_AUTO_TEST_SUITE(BOOST_TEST_MODULE)

  BOOST_AUTO_TEST_CASE(test_crba_code_generation)
  {
    typedef double Scalar;
    typedef CppAD::cg::CG<Scalar> CGScalar;
    typedef CppAD::AD<CGScalar> ADScalar;

    typedef Eigen::Matrix<ADScalar,Eigen::Dynamic,1> ADVector;

    typedef pinocchio::ModelTpl<Scalar> Model;
    typedef Model::Data Data;

    typedef pinocchio::ModelTpl<ADScalar> ADModel;
    typedef ADModel::Data ADData;

    Model model;
    pinocchio::buildModels::humanoidRandom(model);
    model.lowerPositionLimit.head<3>().fill(-1.);
    model.upperPositionLimit.head<3>().fill(1.);
    Data data(model);

    ADModel ad_model = model.cast<ADScalar>();
    ADData ad_data(ad_model);

    // Sample random configuration
    typedef Model::ConfigVectorType CongigVectorType;
    typedef Model::TangentVectorType TangentVectorType;
    CongigVectorType q(model.nq);
    q = pinocchio::randomConfiguration(model);

    TangentVectorType v(TangentVectorType::Random(model.nv));
    TangentVectorType a(TangentVectorType::Random(model.nv));

    typedef ADModel::ConfigVectorType ADCongigVectorType;
    typedef ADModel::TangentVectorType ADTangentVectorType;

    ADCongigVectorType ad_q = q.cast<ADScalar>();
    ADTangentVectorType ad_v = v.cast<ADScalar>();
    ADTangentVectorType ad_a = a.cast<ADScalar>();

    ADTangentVectorType & X = ad_a;
    CppAD::Independent(X);

    pinocchio::rnea(ad_model,ad_data,ad_q,ad_v,ad_a);
    ADVector Y(model.nv); Y = ad_data.tau;

    CppAD::ADFun<CGScalar> fun(X,Y);

    // generates source code
    CppAD::cg::ModelCSourceGen<Scalar> cgen(fun, "rnea");
    cgen.setCreateJacobian(true);
    cgen.setCreateForwardZero(true);
    cgen.setCreateForwardOne(true);
    cgen.setCreateReverseOne(true);
    cgen.setCreateReverseTwo(true);
    CppAD::cg::ModelLibraryCSourceGen<Scalar> libcgen(cgen);

    // compile source code
    CppAD::cg::DynamicModelLibraryProcessor<Scalar> p(libcgen);

    CppAD::cg::GccCompiler<Scalar> compiler;
    std::unique_ptr<CppAD::cg::DynamicLib<Scalar>> dynamicLib = p.createDynamicLibrary(compiler);

    // save to files (not really required)
    CppAD::cg::SaveFilesModelLibraryProcessor<Scalar> p2(libcgen);
    p2.saveSources();

    // use the generated code
    std::unique_ptr<CppAD::cg::GenericModel<Scalar> > rnea_generated = dynamicLib->model("rnea");

    CPPAD_TESTVECTOR(Scalar) x((size_t)model.nv);
    Eigen::Map<TangentVectorType>(x.data(),model.nv,1) = a;

    CPPAD_TESTVECTOR(Scalar) tau = rnea_generated->ForwardZero(x);

    Eigen::Map<TangentVectorType> tau_map(tau.data(),model.nv,1);
    Data::TangentVectorType tau_ref = pinocchio::rnea(model,data,q,v,a);
    BOOST_CHECK(tau_map.isApprox(tau_ref));

    pinocchio::crba(model,data,q);
    data.M.triangularView<Eigen::StrictlyLower>()
    = data.M.transpose().triangularView<Eigen::StrictlyLower>();

    CPPAD_TESTVECTOR(Scalar) dtau_da = rnea_generated->Jacobian(x);
    Eigen::Map<PINOCCHIO_EIGEN_PLAIN_ROW_MAJOR_TYPE(Data::MatrixXs)> M_map(dtau_da.data(),model.nv,model.nv);
    BOOST_CHECK(M_map.isApprox(data.M));
  }

BOOST_AUTO_TEST_SUITE_END()


/* vim: set tabstop=4 expandtab shiftwidth=4 softtabstop=4: */

/**
 * \file boost/numeric/ublasx/operation/dot.hpp
 *
 * \brief Vector dot product.
 *
 * \author Marco Guazzone (marco.guazzone@gmail.com)
 *
 * <hr/>
 *
 * Copyright (c) 2011, Marco Guazzone
 *
 * 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_NUMERIC_UBLASX_OPERATION_DOT_HPP
#define BOOST_NUMERIC_UBLASX_OPERATION_DOT_HPP

#include <boost/numeric/ublas/expression_types.hpp>
#include <boost/numeric/ublas/traits.hpp>
#include <boost/numeric/ublas/vector_expression.hpp>
#include <boost/numeric/ublasx/operation/sum.hpp>

namespace boost { namespace numeric { namespace ublasx {

using namespace ::boost::numeric::ublas;

template <typename VecExpr1T, typename VecExpr2T>
struct vdot_traits_type
{
    typedef typename vector_scalar_binary_traits<
                    VecExpr1T,
                    VecExpr2T,
                    vector_inner_prod<
                        VecExpr1T,
                        VecExpr2T,
                        typename promote_traits<
                            typename vector_traits<VecExpr1T>::value_type,
                            typename vector_traits<VecExpr2T>::value_type
                        >::promote_type
                    >
            >::result_type result_type;
};

template <typename MatExpr1T, typename MatExpr2T>
struct mdot_traits_type
{
    typedef vector<
                typename promote_traits<
                    typename matrix_traits<MatExpr1T>::value_type,
                    typename matrix_traits<MatExpr2T>::value_type
                >::promote_type
//              typename vector_traits<
//                  typename matrix_binary_traits<
//                          MatExpr1T,
//                          MatExpr2T,
//                          scalar_multiplies<
//                                  typename MatExpr1T::value_type,
//                                  typename MatExpr2T::value_type
//                          >
//                  >::result_type
//              >::value_type
            > result_type;
};

/**
 * \brief Scalar product of two vectors.
 *
 * \tparam VecExpr1T The type of the first vector.
 * \tparam VecExpr2T The type of the second vector.
 * \param v1 The first vector.
 * \param v2 The second vector.
 * \return The scalar product of vectors \a v1 and \a v2.
 *
 * The scalar product of two vectors \f$u\f$ and \f$v\f$ is defined as:
 * \f[
 *   \sum_{i} u_{i}v_{i}
 * \f]
 */
template <typename VecExpr1T, typename VecExpr2T>
BOOST_UBLAS_INLINE
typename vdot_traits_type<VecExpr1T,VecExpr2T>::result_type dot(vector_expression<VecExpr1T> const& v1,
                                                                vector_expression<VecExpr2T> const& v2)
{
    return inner_prod(v1, v2);
}

/**
 * \brief Scalar product of two matrices along a given dimension.
 *
 * \tparam Dim The dimension used for computing the scalar product.
 * \tparam MatExpr1T The type of the first matrix.
 * \tparam MatExpr2T The type of the second matrix.
 * \param M1 The first matrix.
 * \param M2 The second matrix.
 * \return A vector representing the scalar product of vectors \a M1 and \a M2
 *  along dimension \a Dim.
 *
 * The scalar product of two matrices \f$A\f$ and \f$B\f$ along dimensino
 * \f$d\f$ is defined as:
 * - If \f$d=1\f$:
 *  \f[
 *    \sum_{j} A_{ij}B_{ij}, i=1,2,\ldots
 *  \f]
 * - If \f$d=2\f$:
 *  \f[
 *    \sum_{i} A_{ij}B_{ij}, j=1,2,\ldots
 *  \f]
 * .
 */
template <std::size_t Dim, typename MatExpr1T, typename MatExpr2T>
typename mdot_traits_type<MatExpr1T,MatExpr2T>::result_type dot(matrix_expression<MatExpr1T> const& M1,
                                                                matrix_expression<MatExpr2T> const& M2)
{
    return sum<Dim>(element_prod(M1, M2));
}

}}} // Namespace boost::numeric::ublas

#endif // BOOST_NUMERIC_UBLASX_OPERATION_DOT_HPP


/* Boost interval/arith3.hpp template implementation file
 *
 * This headers provides arithmetical functions
 * which compute an interval given some base
 * numbers. The resulting interval encloses the
 * real result of the arithmetic operation.
 *
 * Copyright 2003 Guillaume Melquiond
 *
 * 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_NUMERIC_INTERVAL_ARITH3_HPP
#define BOOST_NUMERIC_INTERVAL_ARITH3_HPP

#include <boost/numeric/interval/detail/interval_prototype.hpp>
#include <boost/numeric/interval/detail/test_input.hpp>

namespace boost {
namespace numeric {
namespace interval_lib {

template<class I> inline
I add(const typename I::base_type& x, const typename I::base_type& y)
{
  typedef typename I::traits_type Policies;
  if (detail::test_input<typename I::base_type, Policies>(x, y))
    return I::empty();
  typename Policies::rounding rnd;
  return I(rnd.add_down(x, y), rnd.add_up(x, y), true);
}

template<class I> inline
I sub(const typename I::base_type& x, const typename I::base_type& y)
{
  typedef typename I::traits_type Policies;
  if (detail::test_input<typename I::base_type, Policies>(x, y))
    return I::empty();
  typename Policies::rounding rnd;
  return I(rnd.sub_down(x, y), rnd.sub_up(x, y), true);
}

template<class I> inline
I mul(const typename I::base_type& x, const typename I::base_type& y)
{
  typedef typename I::traits_type Policies;
  if (detail::test_input<typename I::base_type, Policies>(x, y))
    return I::empty();
  typename Policies::rounding rnd;
  return I(rnd.mul_down(x, y), rnd.mul_up(x, y), true);
}

template<class I> inline
I div(const typename I::base_type& x, const typename I::base_type& y)
{
  typedef typename I::traits_type Policies;
  if (detail::test_input<typename I::base_type, Policies>(x, y) || user::is_zero(y))
    return I::empty();
  typename Policies::rounding rnd;
  return I(rnd.div_down(x, y), rnd.div_up(x, y), true);
}

} // namespace interval_lib
} // namespace numeric
} // namespace boost

#endif // BOOST_NUMERIC_INTERVAL_ARITH3_HPP


//Author: Dr. Shantanu Shahane
#ifndef class_H_ /* Include guard */
#define class_H_
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
#include <float.h>
#include <string.h>
#include <iostream>
#include <vector>
#include <Eigen/Dense>
#include <unsupported/Eigen/SparseExtra>
#include <Eigen/SparseLU>
#include <Eigen/OrderingMethods>
#include <Eigen/Core>
#include "_hypre_utilities.h"
#include "HYPRE_krylov.h"
#include "HYPRE.h"
#include "HYPRE_parcsr_ls.h"
#include "general_functions.hpp"
using namespace std;

const int INTERIOR_TAG = -1;
// const int INLET_BC_TAG = 1;
// const int OUTLET_BC_TAG = 2;
// const int WALL_BC_TAG = 3;
// const int SYMMETRY_BC_TAG = 4;

template <typename T>
struct PointCloud
{ //taken from Nanoflann library: "utils.h": https://github.com/jlblancoc/nanoflann/blob/master/examples/utils.h
    struct Point
    {
        T x, y, z;
    };
    std::vector<Point> pts;
    inline size_t kdtree_get_point_count() const { return pts.size(); } // Must return the number of data points
    // Returns the dim'th component of the idx'th point in the class:
    // Since this is inlined and the "dim" argument is typically an immediate value, the
    //  "if/else's" are actually solved at compile time.
    inline T kdtree_get_pt(const size_t idx, const size_t dim) const
    {
        if (dim == 0)
            return pts[idx].x;
        else if (dim == 1)
            return pts[idx].y;
        else
            return pts[idx].z;
    }
    // Optional bounding-box computation: return false to default to a standard bbox computation loop.
    //   Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again.
    //   Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds)
    template <class BBOX>
    bool kdtree_get_bbox(BBOX & /* bb */) const { return false; }
};

class PARAMETERS
{
public:
    int cloud_size;                            //cloud size
    double cloud_size_multiplier;              //cloud size: cloud_size_multiplier * num_poly_terms
    int phs_deg;                               //degree of the polyharmonic spline: r^(phs_deg)
    int poly_deg;                              //degree of the polynomial to be appended
    int num_poly_terms;                        //number of the polynomial terms to be appended
    int dimension;                             //problem dimension (can be either 2 or 3 only)
    Eigen::MatrixXi polynomial_term_exponents; //exponents of the polynomial terms
    string meshfile;                           //meshfile name
    string output_file_prefix;                 //used to write all output files
    double max_dx, min_dx, avg_dx;             //characteristic length scales of mesh
    vector<int> periodic_bc_index;             //periodic BC wrt 0:'x', 1:'y', 2:'z'; : size [no. of periodic axes]; ex. ['z','x']:[2,0]; ['y']:[1]
    double dt;
    double rho = -10.0, mu = rho; //should be set in the main file
    double steady_tolerance, solver_tolerance, Courant, precond_droptol;
    int nt, euclid_precond_level_hypre, gmres_kdim, n_iter;
    string solver_type; //hypre_ilu_gmres, eigen_direct, eigen_ilu_bicgstab

    double grad_x_eigval_real, grad_x_eigval_imag, grad_y_eigval_real, grad_y_eigval_imag, grad_z_eigval_real, grad_z_eigval_imag, laplace_eigval_real, laplace_eigval_imag;
    double cloud_id_timer, rcm_timer, cloud_misc_timer, points_timer, grad_laplace_coeff_timer, factoring_timer, solve_timer, total_timer;
    int nt_actual = 0;

    vector<double> rel_res_log, abs_res_log, regul_alpha_log, steady_error_log; //logs of size nt_actual
    vector<int> n_iter_actual;                                                  //logs of size nt_actual

public:
    PARAMETERS(string parameter_file, string gmsh_file);
    void read_calc_parameters(string parameter_file);
    void verify_parameters();
    void calc_cloud_num_points();
    void get_problem_dimension_msh();
    void calc_polynomial_term_exponents();
    void calc_dt(Eigen::SparseMatrix<double, Eigen::RowMajor> &grad_x, Eigen::SparseMatrix<double, Eigen::RowMajor> &grad_y, Eigen::SparseMatrix<double, Eigen::RowMajor> &grad_z, Eigen::SparseMatrix<double, Eigen::RowMajor> &laplacian, double u0, double v0, double w0, double alpha);
};

class POINTS
{ //uses Compressed Row Format of Sparse Matrices
public:
    vector<bool> corner_edge_vertices;                                   //true at corner (for 2D, 3D problems) and edge (for 3D problems only): size [nv]
    int nv;                                                              //Number of vertices in original msh file
    int nv_original;                                                     //Number of vertices
    vector<double> xyz_min, xyz_max, xyz_length;                         //each of size:[dim]
    vector<double> xyz;                                                  //vertex co-ordinates: size [nv X dim]
    vector<double> xyz_original;                                         //all vertex co-ordinates in original msh file: size [nv_original X dim]; nv_original can be greater than nv
    int nelem_original;                                                  //no. of elements in the original file
    vector<bool> elem_boundary_flag_original;                            //boundary_flag=1 if elem is on boundary; else boundary_flag=0: size [nelem_original]
    vector<vector<int>> elem_vert_original;                              //connectivity in original msh file
    vector<double> boundary_face_area_original;                          //area or length of boundary elements for 3D or 2D (used to compute fluxes at boundaries): size [nelem_original]
    vector<int> elem_bc_tag_original;                                    //bc_tag associated with each element (helps to identify various boundary areas and internal region): size [nelem_original]
    vector<int> iv_original_nearest_vert;                                //nearest vertex no for vertices in xyz_original: size [nv_original]; nv_original can be greater than nv
    vector<double> normal;                                               //vertex co-ordinates (relavant only for boundary vertices): size [nv X dim]
    vector<bool> boundary_flag;                                          //boundary_flag=1 if it is on boundary; else boundary_flag=0: size [nv]
    vector<vector<bool>> periodic_bc_flag;                               //periodic_bc_flag=1 if it is on periodic boundary; else periodic_bc_flag=0: size [nv][no. of periodic axes]
    vector<vector<int>> periodic_bc_section;                             //takes values [-1,0,1] for [near_min,middle,near_max] sections respectively: size [nv][no. of periodic axes]
    vector<int> bc_tag;                                                  //bc_tag associated with each vertex (helps to identify various boundary areas and internal region): size [nv]
    Eigen::SparseMatrix<double, Eigen::RowMajor> grad_x_matrix_EIGEN;    //used for convection source term size [points.nv X points.nv]
    Eigen::SparseMatrix<double, Eigen::RowMajor> grad_y_matrix_EIGEN;    //used for convection source term size [points.nv X points.nv]
    Eigen::SparseMatrix<double, Eigen::RowMajor> grad_z_matrix_EIGEN;    //used for convection source term size [points.nv X points.nv]
    Eigen::SparseMatrix<double, Eigen::RowMajor> laplacian_matrix_EIGEN; //used for diffusion source term size [points.nv X points.nv]

    Eigen::SparseMatrix<double, Eigen::RowMajor> grad_x_matrix_EIGEN_boundary, grad_x_matrix_EIGEN_internal;       //[points.nv X points.nv]
    Eigen::SparseMatrix<double, Eigen::RowMajor> grad_y_matrix_EIGEN_boundary, grad_y_matrix_EIGEN_internal;       //[points.nv X points.nv]
    Eigen::SparseMatrix<double, Eigen::RowMajor> grad_z_matrix_EIGEN_boundary, grad_z_matrix_EIGEN_internal;       //[points.nv X points.nv]
    Eigen::SparseMatrix<double, Eigen::RowMajor> laplacian_matrix_EIGEN_boundary, laplacian_matrix_EIGEN_internal; //[points.nv X points.nv]

public:
    POINTS(PARAMETERS &parameters);
    void read_points_xyz_msh(PARAMETERS &parameters);
    void read_points_flag_msh(PARAMETERS &parameters);
    void calc_vert_normal(PARAMETERS &parameters);
    void calc_boundary_face_area(PARAMETERS &parameters);
    void calc_elem_bc_tag(PARAMETERS &parameters);
    void read_elem_vert_complete_msh(PARAMETERS &parameters, vector<vector<int>> &elem_vert, vector<bool> &elem_boundary_flag);
    void calc_vert_nb_cv(PARAMETERS &parameters, vector<vector<int>> &vert_nb_cv, vector<vector<int>> &elem_vert);
    void calc_elem_normal_2D(vector<double> &elem_normal, vector<vector<int>> &vert_nb_cv, vector<vector<int>> &elem_vert, vector<bool> &elem_boundary_flag);
    void calc_elem_normal_3D(vector<double> &elem_normal, vector<vector<int>> &vert_nb_cv, vector<vector<int>> &elem_vert, vector<bool> &elem_boundary_flag);
    void delete_corner_edge_vertices(PARAMETERS &parameters);
    void delete_periodic_bc_vertices(PARAMETERS &parameters);
    void set_periodic_bc(PARAMETERS &parameters, vector<string> periodic_axis);
};

class CLOUD
{
public:
    vector<int> nb_points_row;      //neighboring points of each point: size: nv+1
    vector<int> nb_points_col;      //neighboring points of each point: size: nb_points_row[nv]
    vector<double> grad_x_coeff;    //coefficient for grad_x at each point: base: (CLOUD.nb_points_row, CLOUD.nb_points_col)
    vector<double> grad_y_coeff;    //coefficient for grad_y at each point: base: (CLOUD.nb_points_row, CLOUD.nb_points_col)
    vector<double> grad_z_coeff;    //coefficient for grad_z at each point: base: (CLOUD.nb_points_row, CLOUD.nb_points_col)
    vector<double> laplacian_coeff; //coefficient for laplacian at each point: base: (CLOUD.nb_points_row, CLOUD.nb_points_col)
    vector<double> cond_num_RBF;    //condition number of RBF A matrix for each point: size [nv]
    double cond_num_RBF_max;        //statistics of cond_num_RBF
    double cond_num_RBF_min;        //statistics of cond_num_RBF
    double cond_num_RBF_avg;        //statistics of cond_num_RBF
    vector<int> rcm_points_order;   //reordering list obtained by RCM algorithm
public:
    CLOUD(POINTS &points, PARAMETERS &parameters);
    void calc_cloud_points_slow(POINTS &points, PARAMETERS &parameters);
    void calc_cloud_points_fast(POINTS &points, PARAMETERS &parameters);
    void calc_cloud_points_fast_periodic_bc(POINTS &points, PARAMETERS &parameters);
    void calc_cloud_points_fast_periodic_bc_shifted(POINTS &points, PARAMETERS &parameters, vector<double> &xyz_shifted, vector<int> &periodic_bc_section_value);
    void re_order_points_reverse_cuthill_mckee(POINTS &points, PARAMETERS &parameters);
    void re_order_points(POINTS &points, PARAMETERS &parameters);
    void calc_iv_original_nearest_vert(POINTS &points, PARAMETERS &parameters);
    void calc_charac_dx(POINTS &points, PARAMETERS &parameters);
    void calc_grad_laplace_coeffs(POINTS &points, PARAMETERS &parameters);
    void EIGEN_set_grad_laplace_matrix(POINTS &points, PARAMETERS &parameters);
    void EIGEN_set_grad_laplace_matrix_separate(POINTS &points, PARAMETERS &parameters);
};

class SOLVER
{
public:
    // Functions
    void init(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, vector<bool> &dirichlet_flag, double unsteady_term_coeff, double conv_term_coeff, double diff_term_coeff, bool log_flag);
    void HYPRE_set_coeff(PARAMETERS &parameters);
    void EIGEN_set_coeff();
    void general_solve(POINTS &points, PARAMETERS &parameters, Eigen::VectorXd &field_new, Eigen::VectorXd &field_old, Eigen::VectorXd &rhs);
    void set_solve_parameters();
    void calc_coeff_matrix(POINTS &points, CLOUD &cloud, PARAMETERS &parameters);
    void scale_coeff(POINTS &points);

    // Variables
    HYPRE_IJMatrix coeff_HYPRE;
    HYPRE_ParCSRMatrix parcsr_coeff_HYPRE;
    HYPRE_IJVector source_HYPRE;
    HYPRE_ParVector par_source_HYPRE;
    HYPRE_IJVector X_HYPRE;
    HYPRE_ParVector par_X_HYPRE;
    HYPRE_Solver solver_X_HYPRE, precond_X_HYPRE;
    Eigen::SparseMatrix<double, Eigen::RowMajor> coeff_EIGEN;
    Eigen::SparseLU<Eigen::SparseMatrix<double, Eigen::RowMajor>, Eigen::COLAMDOrdering<int>> solver_eigen_direct;
    Eigen::BiCGSTAB<Eigen::SparseMatrix<double, Eigen::RowMajor>, Eigen::IncompleteLUT<double>> solver_eigen_ilu_bicgstab;

    vector<tuple<int, int, double>> coeff_matrix; //store coefficient matrix with sparsity structure of (row, col, value)
    vector<double> scale;

    int *rows_HYPRE; //array of size ncv going from 0 to ncv-1
    double *X;       //unknown vector
    double *source;  //source term

    string solver_type; //hypre_ilu_gmres, eigen_direct, eigen_ilu_bicgstab
    double unsteady_term_coeff_1, conv_term_coeff_1, diff_term_coeff_1;
    int print_flag = 0;             //decide how much to print during solution
    int n_iter;                     //max solver iterations
    int euclid_precond_level_hypre; //level setting in Euclid pre-conditioner
    double precond_droptol;         //setting in Euclid pre-conditioner
    int gmres_kdim;                 //GMRES max size of Krylov subspace
    double solver_tolerance;        //solver tolerance
    double l2_norm;
    vector<bool> dirichlet_flag_1; //true: use Dirichlet BC, else use Neumann BC
    int system_size;               //no. of unknowns
    bool log_flag_1 = true;
};

class FRACTIONAL_STEP_1
{ //hat velocity formulation
public:
    // Functions
    FRACTIONAL_STEP_1(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, vector<bool> &u_dirichlet_flag1, vector<bool> &v_dirichlet_flag1, vector<bool> &p_dirichlet_flag1, int temporal_order1);
    void check_bc(POINTS &points, PARAMETERS &parameters);
    void single_timestep_2d(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, Eigen::VectorXd &u_new, Eigen::VectorXd &v_new, Eigen::VectorXd &p_new, Eigen::VectorXd &u_old, Eigen::VectorXd &v_old, Eigen::VectorXd &p_old, int it1);
    void single_timestep_2d(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, Eigen::VectorXd &u_new, Eigen::VectorXd &v_new, Eigen::VectorXd &p_new, Eigen::VectorXd &u_old, Eigen::VectorXd &v_old, Eigen::VectorXd &p_old, Eigen::VectorXd &body_force_x, Eigen::VectorXd &body_force_y, int it1);
    void calc_vel_hat(POINTS &points, PARAMETERS &parameters, Eigen::VectorXd &u_old, Eigen::VectorXd &v_old, Eigen::VectorXd &p_old);
    void calc_vel_hat(POINTS &points, PARAMETERS &parameters, Eigen::VectorXd &u_old, Eigen::VectorXd &v_old, Eigen::VectorXd &p_old, Eigen::VectorXd &body_force_x, Eigen::VectorXd &body_force_y);
    void calc_pressure(POINTS &points, PARAMETERS &parameters, Eigen::VectorXd &u_new, Eigen::VectorXd &v_new, Eigen::VectorXd &p_new, Eigen::VectorXd &u_old, Eigen::VectorXd &v_old, Eigen::VectorXd &p_old);
    void calc_vel_corr(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, Eigen::VectorXd &u_new, Eigen::VectorXd &v_new, Eigen::VectorXd &p_new, Eigen::VectorXd &u_old, Eigen::VectorXd &v_old);
    void extras(POINTS &points, PARAMETERS &parameters, Eigen::VectorXd &u_new, Eigen::VectorXd &v_new, Eigen::VectorXd &p_new, Eigen::VectorXd &u_old, Eigen::VectorXd &v_old, Eigen::VectorXd &p_old);

    // Variables
    SOLVER solver_p;
    Eigen::VectorXd zero_vector, zero_vector_1;
    Eigen::VectorXd uh, vh;
    Eigen::VectorXd p_source;
    Eigen::VectorXd u_source_old, v_source_old;
    Eigen::VectorXd u_source_old_old, v_source_old_old; //used only for multi-step method
    vector<bool> u_dirichlet_flag, v_dirichlet_flag, p_dirichlet_flag;
    bool p_bc_full_neumann;
    int temporal_order = -1, it;
};

class IMPLICIT_SCALAR_TRANSPORT_SOLVER
{ //scalar transport with spatially varying velocity field: BDF2
public:
    // Functions
    IMPLICIT_SCALAR_TRANSPORT_SOLVER(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, vector<bool> &dirichlet_flag1, int precond_freq_it1, double unsteady_coeff1, double conv_coeff1, double diff_coeff1, bool solver_log_flag1);
    void single_timestep_2d(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, Eigen::VectorXd &phi_new, Eigen::VectorXd &phi_old, Eigen::VectorXd &u_new, Eigen::VectorXd &v_new, int it1);
    void set_matrix(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, Eigen::VectorXd &u_new, Eigen::VectorXd &v_new);
    void modify_matrix(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, Eigen::VectorXd &u_new, Eigen::VectorXd &v_new);
    void calc_nb_points_col_matrix(POINTS &points, CLOUD &cloud, PARAMETERS &parameters);

    // Variables
    Eigen::SparseMatrix<double, Eigen::RowMajor> matrix;
    Eigen::BiCGSTAB<Eigen::SparseMatrix<double, Eigen::RowMajor>, Eigen::IncompleteLUT<double>> solver_eigen;
    Eigen::VectorXd zero_vector, source, phi_old_old;
    vector<bool> dirichlet_flag;
    vector<int> nb_points_col_matrix;
    bool bc_full_neumann, solver_log_flag;
    int it, precond_freq_it;
    double unsteady_coeff, conv_coeff, diff_coeff;
    double bdf2_alpha_1 = 1.5, bdf2_alpha_2 = -2.0, bdf2_alpha_3 = 0.5; //https://en.wikipedia.org/wiki/Backward_differentiation_formula
};

class SEMI_IMPLICIT_SPLIT_SOLVER
{ //iterative split solver for Navier-Stokes equations
public:
    // Functions
    SEMI_IMPLICIT_SPLIT_SOLVER(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, vector<bool> &u_dirichlet_flag1, vector<bool> &v_dirichlet_flag1, vector<bool> &p_dirichlet_flag1, int n_outer_iter1, double iterative_tolerance1, int precond_freq_it1);
    void check_bc(POINTS &points, PARAMETERS &parameters);
    void single_timestep_2d(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, Eigen::VectorXd &u_new, Eigen::VectorXd &v_new, Eigen::VectorXd &p_new, Eigen::VectorXd &u_old, Eigen::VectorXd &v_old, Eigen::VectorXd &p_old, int it1, vector<int> &n_outer_iter_log, vector<double> &iterative_l1_err_log, vector<double> &iterative_max_err_log);
    void single_timestep_2d(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, Eigen::VectorXd &u_new, Eigen::VectorXd &v_new, Eigen::VectorXd &p_new, Eigen::VectorXd &u_old, Eigen::VectorXd &v_old, Eigen::VectorXd &p_old, Eigen::VectorXd &body_force_x, Eigen::VectorXd &body_force_y, int it1, vector<int> &n_outer_iter_log, vector<double> &iterative_l1_err_log, vector<double> &iterative_max_err_log);
    void set_vel_matrix(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, Eigen::VectorXd &u_new, Eigen::VectorXd &v_new);
    void calc_nb_points_col_matrix(POINTS &points, CLOUD &cloud, PARAMETERS &parameters);
    void modify_vel_matrix(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, Eigen::VectorXd &u_new, Eigen::VectorXd &v_new);
    void calc_vel(POINTS &points, PARAMETERS &parameters, Eigen::VectorXd &u_new, Eigen::VectorXd &v_new, Eigen::VectorXd &p_new, Eigen::VectorXd &u_old, Eigen::VectorXd &v_old, Eigen::VectorXd &p_old);
    void calc_vel(POINTS &points, PARAMETERS &parameters, Eigen::VectorXd &u_new, Eigen::VectorXd &v_new, Eigen::VectorXd &p_new, Eigen::VectorXd &u_old, Eigen::VectorXd &v_old, Eigen::VectorXd &p_old, Eigen::VectorXd &body_force_x, Eigen::VectorXd &body_force_y);
    void calc_pressure(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, Eigen::VectorXd &u_new, Eigen::VectorXd &v_new, Eigen::VectorXd &p_new, Eigen::VectorXd &u_old, Eigen::VectorXd &v_old, Eigen::VectorXd &p_old);
    void calc_vel_corr(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, Eigen::VectorXd &u_new, Eigen::VectorXd &v_new);
    void extras(POINTS &points, PARAMETERS &parameters, Eigen::VectorXd &u_new, Eigen::VectorXd &v_new, Eigen::VectorXd &p_new, Eigen::VectorXd &u_old, Eigen::VectorXd &v_old, Eigen::VectorXd &p_old);

    // Variables
    SOLVER solver_p;
    Eigen::SparseMatrix<double, Eigen::RowMajor> matrix_u, matrix_v;
    Eigen::BiCGSTAB<Eigen::SparseMatrix<double, Eigen::RowMajor>, Eigen::IncompleteLUT<double>> solver_eigen_u, solver_eigen_v;
    Eigen::VectorXd zero_vector, zero_vector_1;
    Eigen::VectorXd normal_mom_x, normal_mom_y;
    Eigen::VectorXd p_prime, p_source, u_source, v_source, u_prime, v_prime, u_iter_old, v_iter_old, u_old_old, v_old_old;
    vector<bool> u_dirichlet_flag, v_dirichlet_flag, p_dirichlet_flag;
    vector<int> nb_points_col_matrix_u, nb_points_col_matrix_v;
    bool p_bc_full_neumann;
    double iterative_tolerance, iterative_l1_err, iterative_max_err;
    int it, n_outer_iter, outer_iter, precond_freq_it;
    double bdf2_alpha_1 = 1.5, bdf2_alpha_2 = -2.0, bdf2_alpha_3 = 0.5; //https://en.wikipedia.org/wiki/Backward_differentiation_formula
};

class SOLIDIFICATION
{
public:
    // Functions
    SOLIDIFICATION(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, int temporal_order1);
    void single_timestep_2d(POINTS &points, CLOUD &cloud, PARAMETERS &parameters, Eigen::VectorXd &T_new, Eigen::VectorXd &T_old, Eigen::VectorXd &fs_new, Eigen::VectorXd &fs_old, int it);

    // Variables
    Eigen::VectorXd dfs_dT, T_source;
    Eigen::VectorXd T_old_old, fs_old_old; //used only for multi-step method
    double Tliq = 915.0;
    double Tsol = 850.0;
    double Tf = 935.2;
    double Teps = 2.0;
    double k_partition = 0.13;
    double Lf = 390000.0;
    double Cp = 1006.0;
    double alpha = 5E-5;
    int temporal_order = -1;
};

#endif


#include <calibrator/processes/generalhullwhiteprocess.hpp>

#include <boost/bind.hpp>

#include <ql/compounding.hpp>

namespace HJCALIBRATOR
{
	GeneralizedHullWhiteProcess::GeneralizedHullWhiteProcess( const Handle<YieldTermStructure>& h,
															  const IntegrableParameter& a,
															  const Parameter& sigma )
		: termStructure_( h ), a_( a ), sigma_( sigma )
		, r0_( h->forwardRate( 0.0, 0.0, Continuous, NoFrequency ) )
		, integrator_( SimpsonIntegral( 0.00001, 1000 ) )
		, Vrintegrand_( boost::bind( &GeneralizedHullWhiteProcess::VrIntegrand, this, _1 ) )
		, OneOverEintegrand_( boost::bind( &GeneralizedHullWhiteProcess::E, this, _1, -1. ) )
	{}
}


// Boost.Geometry (aka GGL, Generic Geometry Library)
// Quickbook Examples, for main page

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2012 Mateusz Loskot, London, UK.

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

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

#if defined(_MSC_VER)
// We deliberately mix float/double's here so turn off warning
//#pragma warning( disable : 4244 )
#endif // defined(_MSC_VER)

#include <iostream>

//[quickstart_include

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

using namespace boost::geometry;
//]

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

//[quickstart_register_c_array
#include <boost/geometry/geometries/adapted/c_array.hpp>

BOOST_GEOMETRY_REGISTER_C_ARRAY_CS(cs::cartesian)
//]

//[quickstart_register_boost_tuple
#include <boost/geometry/geometries/adapted/boost_tuple.hpp>

BOOST_GEOMETRY_REGISTER_BOOST_TUPLE_CS(cs::cartesian)
//]

// Small QRect simulations following http://doc.trolltech.com/4.4/qrect.html
// Todo: once work the traits out further, would be nice if there is a real example of this.
// However for the example it makes no difference, it will work any way.
struct QPoint
{
    int x, y;
    // In Qt these are methods but for example below it makes no difference
};

struct QRect
{
    int x, y, width, height;
    QRect(int _x, int _y, int w, int h)
        : x(_x), y(_y), width(w), height(h)
    {}
    // In Qt these are methods but that will work as well, requires changing traits below
};

// Would be get/set with x(),y(),setX(),setY()
BOOST_GEOMETRY_REGISTER_POINT_2D(QPoint, int, cs::cartesian, x, y)

// Register the QT rectangle. The macro(s) does not offer (yet) enough flexibility to do this in one line,
// but the traits classes do their job perfectly.
namespace boost { namespace geometry { namespace traits
{

template <> struct tag<QRect> { typedef box_tag type; };
template <> struct point_type<QRect> { typedef QPoint type; };

template <size_t C, size_t D>
struct indexed_access<QRect, C, D>
{
    static inline int get(const QRect& qr)
    {
        // Would be: x(), y(), width(), height()
        return C == min_corner && D == 0 ? qr.x
                : C == min_corner && D == 1 ? qr.y
                : C == max_corner && D == 0 ? qr.x + qr.width
                : C == max_corner && D == 1 ? qr.y + qr.height
                : 0;
    }

    static inline void set(QRect& qr, const int& value)
    {
        // Would be: setX, setY, setWidth, setHeight
        if (C == min_corner && D == 0) qr.x = value;
        else if (C == min_corner && D == 1) qr.y = value;
        else if (C == max_corner && D == 0) qr.width = value - qr.x;
        else if (C == max_corner && D == 1) qr.height = value - qr.y;
    }
};

}}}

int main(void)
{
    //[quickstart_distance
    model::d2::point_xy<int> p1(1, 1), p2(2, 2);
    std::cout << "Distance p1-p2 is: " << distance(p1, p2) << std::endl;
    //]

    //[quickstart_distance_c_array
    int a[2] = {1,1};
    int b[2] = {2,3};
    double d = distance(a, b);
    std::cout << "Distance a-b is: " << d << std::endl;
    //]

    //[quickstart_point_in_polygon
    double points[][2] = {{2.0, 1.3}, {4.1, 3.0}, {5.3, 2.6}, {2.9, 0.7}, {2.0, 1.3}};
    model::polygon<model::d2::point_xy<double> > poly;
    append(poly, points);
    boost::tuple<double, double> p = boost::make_tuple(3.7, 2.0);
    std::cout << "Point p is in polygon? " << std::boolalpha << within(p, poly) << std::endl;
    //]

    //[quickstart_area
    std::cout << "Area: " << area(poly) << std::endl;
    //]

    //[quickstart_distance_mixed
    double d2 = distance(a, p);
    std::cout << "Distance a-p is: " << d2 << std::endl;
    //]

    //[quick_start_spherical
    typedef boost::geometry::model::point
        <
            double, 2, boost::geometry::cs::spherical_equatorial<boost::geometry::degree>
        > spherical_point;

    spherical_point amsterdam(4.90, 52.37);
    spherical_point paris(2.35, 48.86);

    double const earth_radius = 3959; // miles
    std::cout << "Distance in miles: " << distance(amsterdam, paris) * earth_radius << std::endl;
    //]

    /***
    Now extension
    point_ll_deg  amsterdam, paris;
    parse(amsterdam, "52 22 23 N", "4 53 32 E");
    parse(paris, "48 52 0 N", "2 19 59 E");
    std::cout << "Distance A'dam-Paris: " << distance(amsterdam, paris) / 1000.0 << " kilometers " << std::endl;
    ***/

    //[quickstart_qt
    QRect r1(100, 200, 15, 15);
    QRect r2(110, 210, 20, 20);
    if (overlaps(r1, r2))
    {
        assign_values(r2, 200, 300, 220, 320);
    }
    //]

    return 0;
}


#include <blitz/array.h>

/**
  Simple example of a function dealing with a blitz array
*/
blitz::Array<double,1> reverse (const blitz::Array<double,1>& array){
  // create new array in the desired shape
  blitz::Array<double,1> retval(array.shape());
  // copy data
  for (int i = 0, j = array.extent(0)-1; i < array.extent(0); ++i, --j){
    retval(j) = array(i);
  }
  // return the copied data
  return retval;
}


/**
 * \file      algebraic-sensor.hpp
 * \author    Mehdi Benallegue
 * \date       2013
 * \brief     Gives a base class for algebraic sensors
 *
 *
 */

#ifndef SIMULATIONALGEBRAICSENSORHPP
#define SIMULATIONALGEBRAICSENSORHPP

#include <boost/assert.hpp>
#include <Eigen/Core>

#include <state-observation/api.h>
#include <state-observation/sensors-simulation/sensor-base.hpp>

namespace stateObservation
{
/**
 * \class  AlgebraicSensor
 * \brief  The base class for algebraic sensors. Algebraic sensors are sensors
 *         which depend only on the state value and the current time
 *          and do not have internal dynamics
 *         (or a dynamics which converges fast enough to be ignored). This class
 *         implements mostly the containers and the interface to algebraic sensors.
 *         Algebraic sensors must be derived from this class.
 *
 * \details
 *
 */

class STATE_OBSERVATION_DLLAPI AlgebraicSensor : public SensorBase
{
public:
  /// Default constructor
  AlgebraicSensor();

  /// virtual destructor
  virtual ~AlgebraicSensor() {}

  /// gets the measurement of the current time. We can choose to consider
  /// noise or not (default is noisy)
  virtual Vector getMeasurements(bool noisy = true);

  /// Sets the value of the state at instant k
  virtual void setState(const Vector & state, TimeIndex k);

  /// gets the current time
  virtual TimeIndex getTime() const;

  /// gets the state vector size. Pure virtual method.
  virtual Index getStateSize() const;

  /// get the size of the measurements. Pure virtual method.
  virtual Index getMeasurementSize() const;

  /// concatenates the n last components of the state in the measurement
  ///(useful when the measurements are already computed or
  /// when they come from external source)
  virtual Index concatenateWithInput(Index n);

protected:
  /// the actual algorithm for the computation of the measurements, must
  /// be overloaded to implement any sensor
  virtual Vector computeNoiselessMeasurement_() = 0;

  virtual Index getStateSize_() const = 0;

  virtual Index getMeasurementSize_() const = 0;

  Vector computeNoisyMeasurement_();

  virtual void checkState_(const Vector &);

  TimeIndex time_;

  Index concat_;

  Vector state_;

  Vector directInputToOutput_;

  bool storedNoisyMeasurement_;

  Vector noisyMeasurement_;

  bool storedNoiselessMeasurement_;

  Vector noiselessMeasurement_;
};

} // namespace stateObservation

#endif // SIMULATIONALGEBRAICSENSORHPP


//==================================================================================================
/*
  Copyright 2016 NumScale SAS

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

//! [threshold]
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <limits>
#include <vector>

#include <boost/simd/pack.hpp>

#include <boost/simd/function/aligned_store.hpp>
#include <boost/simd/function/group.hpp>
#include <boost/simd/function/if_zero_else_one.hpp>
#include <boost/simd/function/is_less.hpp>
#include <boost/simd/memory/allocator.hpp>

int main()
{
  namespace bs = boost::simd;
  int image_size = 2560 * 2560;
  std::vector<std::int16_t, bs::allocator<std::int16_t>> image(image_size);
  std::vector<std::int16_t, bs::allocator<std::int16_t>> binary(image_size);
  std::generate(image.begin(), image.end(),
                []() { return std::rand() % std::numeric_limits<std::int16_t>::max(); });
  // select arbitrary threshold
  std::int16_t threshold = 5000;
  //! [scalar-threshold]
  for (int i = 0; i < image.size(); ++i) {
    if (image[i] < threshold) {
      binary[i] = 0;
    } else {
      binary[i] = 1;
    }
  }
  //! [scalar-threshold]

  //! [simd-threshold]
  using pack_t    = bs::pack<std::int16_t>;
  using logical_t = bs::pack<bs::logical<std::int16_t>>;

  pack_t v_threshold{threshold};
  for (int i = 0; i < image.size(); i += pack_t::static_size) {
    pack_t v_image(&image[i]);
    logical_t v_res = bs::is_less(v_image, v_threshold);
    pack_t v_binary = bs::if_zero_else_one(v_res);
    bs::aligned_store(v_binary, &binary[i]);
  }
  //! [simd-threshold]
}
// This code can be compiled using (for instance for gcc)
// g++ thresholding.cpp -msse4.2 -std=c++11 -O3 -DNDEBUG -o thresholding
// -I/path_to/boost_simd/ -I/path_to/boost/

//! [threshold]