// ---------------------------------------------------------------------
//
// Copyright (c) 2019 - 2021 by the IBAMR developers
// All rights reserved.
//
// This file is part of IBAMR.
//
// IBAMR is free software and is distributed under the 3-clause BSD
// license. The full text of the license can be found in the file
// COPYRIGHT at the top level directory of IBAMR.
//
// ---------------------------------------------------------------------

#include "ibtk/FEMapping.h"
#include "ibtk/libmesh_utilities.h"

#include "tbox/Utilities.h"

#include "libmesh/dense_matrix.h"
#include "libmesh/elem.h"
#include "libmesh/enum_elem_type.h"
#include "libmesh/enum_fe_family.h"
#include "libmesh/enum_order.h"
#include "libmesh/enum_quadrature_type.h"
#include "libmesh/type_vector.h"
#include <libmesh/libmesh_version.h>
#include <libmesh/point.h>
#include <libmesh/quadrature.h>

#include <Eigen/Dense>

#include <algorithm>
#include <cmath>
#include <memory>

#include "ibtk/namespaces.h"

namespace IBTK
{
//
// Helper functions
//
template <int dim, int spacedim>
inline Eigen::Matrix<double, spacedim, dim>
getCovariant(const Eigen::Matrix<double, spacedim, dim>& contravariant)
{
    return contravariant * (contravariant.transpose() * contravariant).inverse();
}

template <int dim>
inline Eigen::Matrix<double, dim, dim>
getCovariant(const Eigen::Matrix<double, dim, dim>& contravariant)
{
    return contravariant.inverse().transpose();
}

//
// PointMap
//

template <int dim, int spacedim, int n_nodes>
PointMap<dim, spacedim, n_nodes>::PointMap(const libMesh::ElemType elem_type,
                                           const std::vector<libMesh::Point>& q_points)
    : d_reference_q_points(q_points)
{
    const int n_nodes_ = n_nodes == -1 ? get_n_nodes(elem_type) : n_nodes;
    const auto elem_order = get_default_order(elem_type);
    d_phi.resize(n_nodes_, d_reference_q_points.size());
    for (int i = 0; i < n_nodes_; ++i)
    {
        for (unsigned int q = 0; q < d_reference_q_points.size(); ++q)
        {
            using FE = libMesh::FE<dim, libMesh::LAGRANGE>;
            d_phi(i, q) = FE::shape(elem_type, elem_order, i, d_reference_q_points[q]);
        }
    }
}

template <int dim, int spacedim, int n_nodes>
void
PointMap<dim, spacedim, n_nodes>::getMappedQuadraturePoints(const libMesh::Point* nodes,
                                                            const libMesh::Point* nodes_end,
                                                            std::vector<libMesh::Point>& physical_q_points)
{
    if (n_nodes != -1) TBOX_ASSERT(nodes_end - nodes == n_nodes);
    const int n_nodes_ = n_nodes == -1 ? nodes_end - nodes : n_nodes;
    TBOX_ASSERT(d_reference_q_points.size() == physical_q_points.size());
    TBOX_ASSERT(n_nodes_ == d_phi.rows());
    // assumes same node ordering in the input node array as is stored in d_phi
    for (unsigned int q = 0; q < d_reference_q_points.size(); ++q)
    {
        physical_q_points[q] = 0.0;
        for (int i = 0; i < n_nodes_; ++i)
        {
            for (int d = 0; d < spacedim; ++d)
            {
                physical_q_points[q](d) += d_phi(i, q) * nodes[i](d);
            }
        }
    }
}

//
// QuadratureData
//

QuadratureData::QuadratureData(const QuadratureData::key_type quad_key) : d_key(quad_key)
{
    const ElemType elem_type = std::get<0>(d_key);
    const QuadratureType quad_type = std::get<1>(d_key);
    const Order order = std::get<2>(d_key);

    const int dim = get_dim(elem_type);

    std::unique_ptr<QBase> quad_rule = QBase::build(quad_type, dim, order);
    quad_rule->init(elem_type);
    d_points = quad_rule->get_points();
    d_weights = quad_rule->get_weights();
}

//
// FEMapping
//

template <>
std::unique_ptr<FEMapping<2, 2> >
FEMapping<2, 2>::build(const key_type key, const FEUpdateFlags update_flags)
{
    switch (std::get<0>(key))
    {
    case libMesh::ElemType::TRI3:
        return std::unique_ptr<FEMapping<2, 2> >(new Tri3Mapping(key, update_flags));
    case libMesh::ElemType::TRI6:
        return std::unique_ptr<FEMapping<2, 2> >(new Tri6Mapping(key, update_flags));
    case libMesh::ElemType::QUAD4:
        return std::unique_ptr<FEMapping<2, 2> >(new Quad4Mapping(key, update_flags));
    case libMesh::ElemType::QUAD9:
        return std::unique_ptr<FEMapping<2, 2> >(new Quad9Mapping(key, update_flags));
    default:
        return std::unique_ptr<FEMapping<2, 2> >(new FELagrangeMapping<2, 2>(key, std::get<0>(key), update_flags));
    }

    return {};
}

template <>
std::unique_ptr<FEMapping<3, 3> >
FEMapping<3, 3>::build(const key_type key, const FEUpdateFlags update_flags)
{
    switch (std::get<0>(key))
    {
    case libMesh::ElemType::TET4:
        return std::unique_ptr<FEMapping<3, 3> >(new Tet4Mapping(key, update_flags));
    case libMesh::ElemType::TET10:
        return std::unique_ptr<FEMapping<3, 3> >(new Tet10Mapping(key, update_flags));
    case libMesh::ElemType::HEX8:
        return std::unique_ptr<FEMapping<3, 3> >(
            new FELagrangeMapping<3, 3, 8>(key, libMesh::ElemType::HEX8, update_flags));
    case libMesh::ElemType::HEX27:
        return std::unique_ptr<FEMapping<3, 3> >(new Hex27Mapping(key, update_flags));
    default:
        return std::unique_ptr<FEMapping<3, 3> >(new FELagrangeMapping<3, 3>(key, std::get<0>(key), update_flags));
    }

    return {};
}

template <int dim, int spacedim>
std::unique_ptr<FEMapping<dim, spacedim> >
FEMapping<dim, spacedim>::build(const key_type key, const FEUpdateFlags update_flags)
{
    return std::unique_ptr<FEMapping<dim, spacedim> >(
        new FELagrangeMapping<dim, spacedim>(key, std::get<0>(key), update_flags));
}

//
// FENodalMapping
//

template <int dim, int spacedim, int n_nodes>
FENodalMapping<dim, spacedim, n_nodes>::FENodalMapping(
    const typename FENodalMapping<dim, spacedim, n_nodes>::key_type quad_key,
    const libMesh::ElemType element_mapping_type,
    const FEUpdateFlags update_flags)
    : d_quadrature_data(quad_key), d_point_map(element_mapping_type, d_quadrature_data.d_points)
{
    d_update_flags = update_flags;

    // make sure dependencies are satisfied. These dependencies are only true
    // for Lagrange-type mappings.
    {
        if (d_update_flags | FEUpdateFlags::update_JxW) d_update_flags |= update_jacobians;

        if (d_update_flags | FEUpdateFlags::update_jacobians) d_update_flags |= update_contravariants;

        if (d_update_flags | FEUpdateFlags::update_covariants) d_update_flags |= update_contravariants;
    }

    if (d_update_flags | FEUpdateFlags::update_contravariants) d_contravariants.resize(this->d_quadrature_data.size());
    if (d_update_flags | FEUpdateFlags::update_covariants) d_covariants.resize(this->d_quadrature_data.size());

    if (d_update_flags | FEUpdateFlags::update_jacobians) d_Jacobians.resize(this->d_quadrature_data.size());

    if (d_update_flags | FEUpdateFlags::update_JxW) d_JxW.resize(this->d_quadrature_data.size());

    if (d_update_flags | FEUpdateFlags::update_quadrature_points)
        d_quadrature_points.resize(this->d_quadrature_data.size());
}

template <int dim, int spacedim, int n_nodes>
void
FENodalMapping<dim, spacedim, n_nodes>::reinit(const libMesh::Elem* elem)
{
    if (d_update_flags & FEUpdateFlags::update_contravariants || d_update_flags & FEUpdateFlags::update_covariants)
        this->fillTransforms(elem);
    if (d_update_flags & FEUpdateFlags::update_jacobians) this->fillJacobians();
    if (d_update_flags & FEUpdateFlags::update_JxW) this->fillJxW();
    if (d_update_flags & FEUpdateFlags::update_quadrature_points) this->fillQuadraturePoints(elem);
}

template <int dim, int spacedim, int n_nodes>
bool
FENodalMapping<dim, spacedim, n_nodes>::isAffine() const
{
    return false;
}

template <int dim, int spacedim, int n_nodes>
void
FENodalMapping<dim, spacedim, n_nodes>::fillJacobians()
{
    for (unsigned int q = 0; q < d_contravariants.size(); ++q)
    {
        if (dim == spacedim)
        {
            d_Jacobians[q] = d_contravariants[q].determinant();
        }
        else
        {
            Eigen::Matrix<double, dim, dim> Jac = d_contravariants[q].transpose() * d_contravariants[q];
            d_Jacobians[q] = std::sqrt(Jac.determinant());
        }
        TBOX_ASSERT(d_Jacobians[q] > 0.0);

        if (isAffine()) break;
    }

    if (isAffine()) std::fill(d_Jacobians.begin() + 1, d_Jacobians.end(), d_Jacobians[0]);

    return;
}

template <int dim, int spacedim, int n_nodes>
void
FENodalMapping<dim, spacedim, n_nodes>::fillJxW()
{
    for (unsigned int q = 0; q < d_Jacobians.size(); ++q) d_JxW[q] = d_quadrature_data.d_weights[q] * d_Jacobians[q];
}

template <int dim, int spacedim, int n_nodes>
void
FENodalMapping<dim, spacedim, n_nodes>::fillQuadraturePoints(const libMesh::Elem* elem)
{
    libMesh::Point nodes[27];

    // We occasionally (e.g., TET10 and TET4) want to call the lower-order
    // mapping from the higher-order mapping, so permit elements with more
    // nodes in that code
    if (n_nodes != -1) TBOX_ASSERT(n_nodes <= static_cast<int>(elem->n_nodes()));
    const int n_nodes_ = n_nodes == -1 ? elem->n_nodes() : n_nodes;
    TBOX_ASSERT(n_nodes_ <= 27);
    for (int node_n = 0; node_n < n_nodes_; ++node_n)
    {
        nodes[node_n] = static_cast<const libMesh::Point&>(*elem->node_ptr(node_n));
    }

    d_point_map.getMappedQuadraturePoints(std::begin(nodes), std::begin(nodes) + n_nodes_, d_quadrature_points);
}

//
// FELagrangeMapping
//

template <int dim, int spacedim, int n_nodes>
FELagrangeMapping<dim, spacedim, n_nodes>::FELagrangeMapping(
    const typename FELagrangeMapping<dim, spacedim, n_nodes>::key_type quad_key,
    const libMesh::ElemType element_mapping_type,
    const FEUpdateFlags update_flags)
    : FENodalMapping<dim, spacedim, n_nodes>(quad_key, element_mapping_type, update_flags),
      d_n_nodes(n_nodes == -1 ? get_n_nodes(std::get<0>(quad_key)) : n_nodes)
{
    if (n_nodes != -1) TBOX_ASSERT(d_n_nodes == n_nodes);
#if LIBMESH_VERSION_LESS_THAN(1, 4, 0)
    TBOX_ASSERT(d_n_nodes <= 27);
#else
    TBOX_ASSERT(d_n_nodes <= static_cast<int>(libMesh::Elem::max_n_nodes));
#endif
    typename decltype(d_dphi)::extent_gen extents;
    d_dphi.resize(extents[d_n_nodes][this->d_quadrature_data.size()]);

    for (int node_n = 0; node_n < d_n_nodes; ++node_n)
    {
        for (unsigned int q = 0; q < this->d_quadrature_data.size(); ++q)
        {
            for (unsigned int d = 0; d < dim; ++d)
                d_dphi[node_n][q][d] =
                    libMesh::FE<dim, libMesh::LAGRANGE>::shape_deriv(element_mapping_type,
                                                                     get_default_order(element_mapping_type),
                                                                     node_n,
                                                                     d,
                                                                     this->d_quadrature_data.d_points[q]);
        }
    }
}

template <int dim, int spacedim, int n_nodes>
void
FELagrangeMapping<dim, spacedim, n_nodes>::fillTransforms(const libMesh::Elem* elem)
{
    TBOX_ASSERT(this->d_update_flags & FEUpdateFlags::update_contravariants);
    TBOX_ASSERT(d_n_nodes <= static_cast<int>(elem->n_nodes()));

    // max_n_nodes is a constant defined by libMesh - currently 27
#if LIBMESH_VERSION_LESS_THAN(1, 4, 0)
    double xs[27][spacedim];
#else
    double xs[libMesh::Elem::max_n_nodes][spacedim];
#endif

    const int n_nodes_ = n_nodes == -1 ? d_n_nodes : n_nodes;
    for (int i = 0; i < n_nodes_; ++i)
    {
        const libMesh::Point p = elem->point(i);
        for (unsigned int j = 0; j < spacedim; ++j) xs[i][j] = p(j);
    }

    for (unsigned int q = 0; q < this->d_JxW.size(); ++q)
    {
        auto& contravariant = this->d_contravariants[q];
        contravariant.setZero();
        for (int node_n = 0; node_n < n_nodes_; ++node_n)
        {
            for (unsigned int i = 0; i < spacedim; ++i)
            {
                for (unsigned int j = 0; j < dim; ++j)
                {
                    contravariant(i, j) += xs[node_n][i] * d_dphi[node_n][q][j];
                }
            }
        }
    }

    if (this->d_update_flags & FEUpdateFlags::update_covariants)
    {
        for (unsigned int q = 0; q < this->d_JxW.size(); ++q)
        {
            this->d_covariants[q] = getCovariant(this->d_contravariants[q]);
        }
    }

    return;
}

//
// Tri3Mapping
//

Tri3Mapping::Tri3Mapping(const key_type quad_key, const FEUpdateFlags update_flags)

    : FENodalMapping<2, 2, 3>(quad_key, libMesh::TRI3, update_flags)
{
}

void
Tri3Mapping::fillTransforms(const libMesh::Elem* elem)
{
    TBOX_ASSERT(this->d_update_flags & FEUpdateFlags::update_contravariants);
    // also permit TRI6
    const auto type = elem->type();
    TBOX_ASSERT(type == libMesh::TRI3 || type == libMesh::TRI6);

    const libMesh::Point p0 = elem->point(0);
    const libMesh::Point p1 = elem->point(1);
    const libMesh::Point p2 = elem->point(2);

    Eigen::Matrix<double, 2, 2> contravariant;
    contravariant(0, 0) = p1(0) - p0(0);
    contravariant(0, 1) = p2(0) - p0(0);
    contravariant(1, 0) = p1(1) - p0(1);
    contravariant(1, 1) = p2(1) - p0(1);
    std::fill(this->d_contravariants.begin(), this->d_contravariants.end(), contravariant);

    if (this->d_update_flags & FEUpdateFlags::update_covariants)
    {
        const Eigen::Matrix<double, 2, 2> covariant = getCovariant(contravariant);
        std::fill(this->d_covariants.begin(), this->d_covariants.end(), covariant);
    }

    return;
}

bool
Tri3Mapping::isAffine() const
{
    return true;
}

//
// Tri6Mapping
//

Tri6Mapping::Tri6Mapping(const key_type quad_key, const FEUpdateFlags update_flags)
    : FELagrangeMapping<2, 2, 6>(quad_key, libMesh::ElemType::TRI6, update_flags), tri3_mapping(quad_key, update_flags)
{
}

void
Tri6Mapping::reinit(const libMesh::Elem* elem)
{
    if (elem_is_affine(elem))
    {
        tri3_mapping.reinit(elem);
        // If we ever add more fields to the mapping classes we will need to
        // duplicate them here
        std::swap(d_contravariants, tri3_mapping.d_contravariants);
        std::swap(d_covariants, tri3_mapping.d_covariants);
        std::swap(d_Jacobians, tri3_mapping.d_Jacobians);
        std::swap(d_JxW, tri3_mapping.d_JxW);
        std::swap(d_quadrature_points, tri3_mapping.d_quadrature_points);
    }
    else
        FELagrangeMapping<2, 2, 6>::reinit(elem);
}

bool
Tri6Mapping::elem_is_affine(const libMesh::Elem* elem)
{
    std::array<libMesh::Point, 6> nodes;
    for (unsigned int n = 0; n < nodes.size(); ++n) nodes[n] = elem->node_ref(n);

    // try to determine the size of the coordinates to use as the tolerance.
    double characteristic_point_size = 0.0;
    for (int d = 0; d < LIBMESH_DIM; ++d)
    {
        characteristic_point_size += std::abs(nodes[0](d));
        characteristic_point_size += std::abs(nodes[4](d));
    }
    const double tol = 1e-16 * characteristic_point_size;

    return nodes[3].absolute_fuzzy_equals(0.5 * (nodes[0] + nodes[1]), tol) &&
           nodes[4].absolute_fuzzy_equals(0.5 * (nodes[1] + nodes[2]), tol) &&
           nodes[5].absolute_fuzzy_equals(0.5 * (nodes[0] + nodes[2]), tol);
}

//
// Quad4Mapping
//

Quad4Mapping::Quad4Mapping(const key_type quad_key, const FEUpdateFlags update_flags)

    : FENodalMapping<2, 2, 4>(quad_key, libMesh::QUAD4, update_flags)
{
}

void
Quad4Mapping::fillTransforms(const libMesh::Elem* elem)
{
    TBOX_ASSERT(this->d_update_flags & FEUpdateFlags::update_contravariants);
    TBOX_ASSERT(elem->type() == std::get<0>(this->d_quadrature_data.d_key));

    // calculate constants in Jacobians here
    const libMesh::Point p0 = elem->point(0);
    const libMesh::Point p1 = elem->point(1);
    const libMesh::Point p2 = elem->point(2);
    const libMesh::Point p3 = elem->point(3);

    const double a_1 = 0.25 * (-p0(0) + p1(0) + p2(0) - p3(0));
    const double b_1 = 0.25 * (-p0(0) - p1(0) + p2(0) + p3(0));
    const double c_1 = 0.25 * (p0(0) - p1(0) + p2(0) - p3(0));
    const double a_2 = 0.25 * (-p0(1) + p1(1) + p2(1) - p3(1));
    const double b_2 = 0.25 * (-p0(1) - p1(1) + p2(1) + p3(1));
    const double c_2 = 0.25 * (p0(1) - p1(1) + p2(1) - p3(1));

    for (unsigned int i = 0; i < this->d_JxW.size(); i++)
    {
        // calculate Jacobians here
        const double x = d_quadrature_data.d_points[i](0);
        const double y = d_quadrature_data.d_points[i](1);

        Eigen::Matrix<double, 2, 2>& contravariant = d_contravariants[i];
        contravariant(0, 0) = a_1 + c_1 * y;
        contravariant(0, 1) = b_1 + c_1 * x;
        contravariant(1, 0) = a_2 + c_2 * y;
        contravariant(1, 1) = b_2 + c_2 * x;
    }

    if (this->d_update_flags & FEUpdateFlags::update_covariants)
    {
        for (unsigned int q = 0; q < this->d_JxW.size(); ++q)
        {
            const auto& contravariant = this->d_contravariants[q];
            d_covariants[q] = getCovariant(contravariant);
        }
    }

    return;
}

//
// Quad9Mapping
//

Quad9Mapping::Quad9Mapping(const Quad9Mapping::key_type quad_key, FEUpdateFlags update_flags)
    : FENodalMapping<2, 2, 9>(quad_key, std::get<0>(quad_key), update_flags)
{
    // This code utilizes an implementation detail of
    // QBase::tensor_product_quad where the x coordinate increases fastest to
    // reconstruct the 1D quadrature rule
    d_n_oned_q_points = static_cast<std::size_t>(std::round(std::sqrt(d_quadrature_data.d_points.size())));
    TBOX_ASSERT(d_n_oned_q_points * d_n_oned_q_points == d_quadrature_data.d_points.size());
    std::vector<libMesh::Point> oned_points(d_n_oned_q_points);
    for (unsigned int q = 0; q < d_n_oned_q_points; ++q)
    {
        oned_points[q] = d_quadrature_data.d_points[q](0);
    }

    // verify that we really do have a tensor product rule
    unsigned int q = 0;
    for (unsigned int j = 0; j < d_n_oned_q_points; ++j)
    {
        for (unsigned int i = 0; i < d_n_oned_q_points; ++i)
        {
            TBOX_ASSERT(d_quadrature_data.d_points[q] == libMesh::Point(oned_points[i](0), oned_points[j](0)));
            ++q;
        }
    }

    d_phi.resize(3, d_n_oned_q_points);
    d_dphi.resize(3, d_n_oned_q_points);
    for (unsigned int i = 0; i < 3u; ++i)
    {
        for (unsigned int q = 0; q < oned_points.size(); ++q)
        {
            // This class orders the vertices in a different way to make
            // writing tensor products easier: we do a left-to-right ordering
            // 0 - 1 - 2 instead of 0 - 2 - 1.
            constexpr int ibamr_to_libmesh_ordering[3] = { 0, 2, 1 };
            using FE = libMesh::FE<1, libMesh::LAGRANGE>;
            d_phi(i, q) = FE::shape(libMesh::EDGE3, libMesh::SECOND, ibamr_to_libmesh_ordering[i], oned_points[q]);
            d_dphi(i, q) =
                FE::shape_deriv(libMesh::EDGE3, libMesh::SECOND, ibamr_to_libmesh_ordering[i], 0, oned_points[q]);
        }
    }
}

void
Quad9Mapping::fillTransforms(const libMesh::Elem* elem)
{
    TBOX_ASSERT(this->d_update_flags & FEUpdateFlags::update_contravariants);
    TBOX_ASSERT(elem->type() == std::get<0>(this->d_quadrature_data.d_key));

    constexpr std::size_t n_oned_shape_functions = 3;

    // We index points in the following way:
    //
    // i = 2 +--+--+
    //       |     |
    // i = 1 +  +  +
    //       |     |
    // i = 0 +--+--+
    //      j=0 1  2
    //
    // All 2D arrays created here are in row-major order.

    double xs[n_oned_shape_functions][n_oned_shape_functions];
    double ys[n_oned_shape_functions][n_oned_shape_functions];

    libMesh::Point points[n_oned_shape_functions][n_oned_shape_functions];
    points[0][0] = elem->point(0);
    points[0][1] = elem->point(4);
    points[0][2] = elem->point(1);
    points[1][0] = elem->point(7);
    points[1][1] = elem->point(8);
    points[1][2] = elem->point(5);
    points[2][0] = elem->point(3);
    points[2][1] = elem->point(6);
    points[2][2] = elem->point(2);

    // j is the x index, i is the y index
    for (unsigned int i = 0; i < n_oned_shape_functions; ++i)
    {
        for (unsigned int j = 0; j < n_oned_shape_functions; ++j)
        {
            xs[i][j] = points[i][j](0);
            ys[i][j] = points[i][j](1);
        }
    }

    for (unsigned int q = 0; q < this->d_JxW.size(); q++)
    {
        Eigen::Matrix<double, 2, 2>& contravariant = d_contravariants[q];
        contravariant.setZero();
        // Exploit the fact that Quad9 is a tensor product element by indexing
        // the x component of each tensor product shape function with j and
        // the y component with i
        const unsigned int q_point_x = q % d_n_oned_q_points;
        const unsigned int q_point_y = q / d_n_oned_q_points;
        for (unsigned int i = 0; i < n_oned_shape_functions; ++i)
        {
            for (unsigned int j = 0; j < n_oned_shape_functions; ++j)
            {
                contravariant(0, 0) += xs[i][j] * d_dphi(j, q_point_x) * d_phi(i, q_point_y);
                contravariant(0, 1) += xs[i][j] * d_phi(j, q_point_x) * d_dphi(i, q_point_y);
                contravariant(1, 0) += ys[i][j] * d_dphi(j, q_point_x) * d_phi(i, q_point_y);
                contravariant(1, 1) += ys[i][j] * d_phi(j, q_point_x) * d_dphi(i, q_point_y);
            }
        }
    }

    if (this->d_update_flags & FEUpdateFlags::update_covariants)
    {
        for (unsigned int q = 0; q < this->d_JxW.size(); ++q)
        {
            const auto& contravariant = this->d_contravariants[q];
            d_covariants[q] = getCovariant(contravariant);
        }
    }

    return;
}

//
// Tet4Mapping
//

Tet4Mapping::Tet4Mapping(const key_type quad_key, const FEUpdateFlags update_flags)

    : FENodalMapping<3, 3, 4>(quad_key, libMesh::TET4, update_flags)
{
}

void
Tet4Mapping::fillTransforms(const libMesh::Elem* elem)
{
    TBOX_ASSERT(this->d_update_flags & FEUpdateFlags::update_contravariants);
    // also permit TET10
    const auto type = elem->type();
    TBOX_ASSERT(type == libMesh::TET4 || type == libMesh::TET10);

    // calculate Jacobians here
    const libMesh::Point p0 = elem->point(0);
    const libMesh::Point p1 = elem->point(1);
    const libMesh::Point p2 = elem->point(2);
    const libMesh::Point p3 = elem->point(3);

    Eigen::Matrix<double, 3, 3> contravariant;
    contravariant(0, 0) = p1(0) - p0(0);
    contravariant(0, 1) = p2(0) - p0(0);
    contravariant(0, 2) = p3(0) - p0(0);
    contravariant(1, 0) = p1(1) - p0(1);
    contravariant(1, 1) = p2(1) - p0(1);
    contravariant(1, 2) = p3(1) - p0(1);
    contravariant(2, 0) = p1(2) - p0(2);
    contravariant(2, 1) = p2(2) - p0(2);
    contravariant(2, 2) = p3(2) - p0(2);
    std::fill(d_contravariants.begin(), d_contravariants.end(), contravariant);

    if (this->d_update_flags & FEUpdateFlags::update_covariants)
    {
        const Eigen::Matrix<double, 3, 3> covariant = getCovariant(contravariant);
        std::fill(this->d_covariants.begin(), this->d_covariants.end(), covariant);
    }

    return;
}

bool
Tet4Mapping::isAffine() const
{
    return true;
}

//
// Tet10Mapping
//

Tet10Mapping::Tet10Mapping(const key_type quad_key, const FEUpdateFlags update_flags)
    : FELagrangeMapping<3, 3, 10>(quad_key, libMesh::ElemType::TET10, update_flags),
      tet4_mapping(quad_key, update_flags)
{
}

void
Tet10Mapping::reinit(const libMesh::Elem* elem)
{
    if (elem_is_affine(elem))
    {
        tet4_mapping.reinit(elem);
        // If we ever add more fields to the mapping classes we will need to
        // duplicate them here
        std::swap(d_contravariants, tet4_mapping.d_contravariants);
        std::swap(d_covariants, tet4_mapping.d_covariants);
        std::swap(d_Jacobians, tet4_mapping.d_Jacobians);
        std::swap(d_JxW, tet4_mapping.d_JxW);
        std::swap(d_quadrature_points, tet4_mapping.d_quadrature_points);
    }
    else
        FELagrangeMapping<3, 3, 10>::reinit(elem);
}

bool
Tet10Mapping::elem_is_affine(const libMesh::Elem* elem)
{
    std::array<libMesh::Point, 10> nodes;
    for (unsigned int n = 0; n < nodes.size(); ++n) nodes[n] = elem->node_ref(n);

    // try to determine the size of the coordinates to use as the tolerance.
    double characteristic_point_size = 0.0;
    for (int d = 0; d < LIBMESH_DIM; ++d)
    {
        characteristic_point_size += std::abs(nodes[0](d));
        characteristic_point_size += std::abs(nodes[3](d));
    }
    const double tol = 1e-16 * characteristic_point_size;

    return nodes[4].absolute_fuzzy_equals(0.5 * (nodes[0] + nodes[1]), tol) &&
           nodes[5].absolute_fuzzy_equals(0.5 * (nodes[1] + nodes[2]), tol) &&
           nodes[6].absolute_fuzzy_equals(0.5 * (nodes[0] + nodes[2]), tol) &&
           nodes[7].absolute_fuzzy_equals(0.5 * (nodes[0] + nodes[3]), tol) &&
           nodes[8].absolute_fuzzy_equals(0.5 * (nodes[1] + nodes[3]), tol) &&
           nodes[9].absolute_fuzzy_equals(0.5 * (nodes[2] + nodes[3]), tol);
}

//
// Hex27Mapping
//

Hex27Mapping::Hex27Mapping(const key_type quad_key, const FEUpdateFlags update_flags)
    : FELagrangeMapping<3, 3, 27>(quad_key, libMesh::HEX27, update_flags),
      hex8_mapping(quad_key, libMesh::HEX8, update_flags)
{
}

void
Hex27Mapping::reinit(const libMesh::Elem* elem)
{
    if (elem_is_trilinear(elem))
    {
        hex8_mapping.reinit(elem);
        // If we ever add more fields to the mapping classes we will need to
        // duplicate them here
        std::swap(d_contravariants, hex8_mapping.d_contravariants);
        std::swap(d_covariants, hex8_mapping.d_covariants);
        std::swap(d_Jacobians, hex8_mapping.d_Jacobians);
        std::swap(d_JxW, hex8_mapping.d_JxW);
        std::swap(d_quadrature_points, hex8_mapping.d_quadrature_points);
    }
    else
        FELagrangeMapping<3, 3, 27>::reinit(elem);
}

bool
Hex27Mapping::elem_is_trilinear(const libMesh::Elem* elem)
{
    std::array<libMesh::Point, 27> nodes;
    for (unsigned int n = 0; n < nodes.size(); ++n) nodes[n] = elem->node_ref(n);

    // try to determine the size of the coordinates to use as the tolerance.
    double characteristic_point_size = 0.0;
    for (int d = 0; d < LIBMESH_DIM; ++d)
    {
        characteristic_point_size += std::abs(nodes[0](d));
        characteristic_point_size += std::abs(nodes[6](d));
    }
    const double tol = 1e-16 * characteristic_point_size;

    return
        // line midpoints
        nodes[8].absolute_fuzzy_equals(0.5 * (nodes[0] + nodes[1]), tol) &&
        nodes[9].absolute_fuzzy_equals(0.5 * (nodes[1] + nodes[2]), tol) &&
        nodes[10].absolute_fuzzy_equals(0.5 * (nodes[2] + nodes[3]), tol) &&
        nodes[11].absolute_fuzzy_equals(0.5 * (nodes[0] + nodes[3]), tol) &&
        nodes[12].absolute_fuzzy_equals(0.5 * (nodes[0] + nodes[4]), tol) &&
        nodes[13].absolute_fuzzy_equals(0.5 * (nodes[1] + nodes[5]), tol) &&
        nodes[14].absolute_fuzzy_equals(0.5 * (nodes[2] + nodes[6]), tol) &&
        nodes[15].absolute_fuzzy_equals(0.5 * (nodes[3] + nodes[7]), tol) &&
        nodes[16].absolute_fuzzy_equals(0.5 * (nodes[4] + nodes[5]), tol) &&
        nodes[17].absolute_fuzzy_equals(0.5 * (nodes[5] + nodes[6]), tol) &&
        nodes[18].absolute_fuzzy_equals(0.5 * (nodes[6] + nodes[7]), tol) &&
        nodes[19].absolute_fuzzy_equals(0.5 * (nodes[4] + nodes[7]), tol) &&
        // face midpoints
        nodes[20].absolute_fuzzy_equals(0.5 * (nodes[8] + nodes[10]), tol) &&
        nodes[21].absolute_fuzzy_equals(0.5 * (nodes[8] + nodes[16]), tol) &&
        nodes[22].absolute_fuzzy_equals(0.5 * (nodes[9] + nodes[17]), tol) &&
        nodes[23].absolute_fuzzy_equals(0.5 * (nodes[10] + nodes[18]), tol) &&
        nodes[24].absolute_fuzzy_equals(0.5 * (nodes[11] + nodes[19]), tol) &&
        nodes[25].absolute_fuzzy_equals(0.5 * (nodes[16] + nodes[18]), tol) &&
        // cell center
        nodes[26].absolute_fuzzy_equals(0.5 * (nodes[20] + nodes[25]), tol);
}

//
// Instantiations
//

template class FEMapping<1, 1>;
template class FEMapping<1, 2>;
template class FEMapping<1, 3>;
template class FEMapping<2, 2>;
template class FEMapping<2, 3>;
template class FEMapping<3, 3>;

template class FENodalMapping<1, 1>;
template class FENodalMapping<1, 2>;
template class FENodalMapping<1, 3>;
template class FENodalMapping<2, 2>;
template class FENodalMapping<2, 3>;
template class FENodalMapping<3, 3>;

template class FELagrangeMapping<1, 1>;
template class FELagrangeMapping<1, 2>;
template class FELagrangeMapping<1, 3>;
template class FELagrangeMapping<2, 2>;
template class FELagrangeMapping<2, 3>;
template class FELagrangeMapping<3, 3>;

template class FELagrangeMapping<2, 2, 6>;
template class FELagrangeMapping<3, 3, 10>;

template class FENodalMapping<2, 2, 3>;
template class FENodalMapping<2, 2, 4>;
template class FENodalMapping<2, 2, 6>;
template class FENodalMapping<2, 2, 9>;
template class FENodalMapping<3, 3, 4>;

} // namespace IBTK


// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2013 Alec Jacobson <alecjacobson@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.
#include "grad.h"
#include <Eigen/Geometry>
#include <vector>

template <typename DerivedV, typename DerivedF>
IGL_INLINE void igl::grad(const Eigen::PlainObjectBase<DerivedV>&V,
                     const Eigen::PlainObjectBase<DerivedF>&F,
                    Eigen::SparseMatrix<typename DerivedV::Scalar> &G)
{
  Eigen::PlainObjectBase<DerivedV > eperp21, eperp13;
  eperp21.resize(F.rows(),3);
  eperp13.resize(F.rows(),3);

  for (int i=0;i<F.rows();++i)
  {
    // renaming indices of vertices of triangles for convenience
    int i1 = F(i,0);
    int i2 = F(i,1);
    int i3 = F(i,2);

    // #F x 3 matrices of triangle edge vectors, named after opposite vertices
    Eigen::Matrix<typename DerivedV::Scalar, 1, 3> v32 = V.row(i3) - V.row(i2);
    Eigen::Matrix<typename DerivedV::Scalar, 1, 3> v13 = V.row(i1) - V.row(i3);
    Eigen::Matrix<typename DerivedV::Scalar, 1, 3> v21 = V.row(i2) - V.row(i1);

    // area of parallelogram is twice area of triangle
    // area of parallelogram is || v1 x v2 ||
    Eigen::Matrix<typename DerivedV::Scalar, 1, 3> n  = v32.cross(v13);

    // This does correct l2 norm of rows, so that it contains #F list of twice
    // triangle areas
    double dblA = std::sqrt(n.dot(n));

    // now normalize normals to get unit normals
    Eigen::Matrix<typename DerivedV::Scalar, 1, 3> u = n / dblA;

    // rotate each vector 90 degrees around normal
    double norm21 = std::sqrt(v21.dot(v21));
    double norm13 = std::sqrt(v13.dot(v13));
    eperp21.row(i) = u.cross(v21);
    eperp21.row(i) = eperp21.row(i) / std::sqrt(eperp21.row(i).dot(eperp21.row(i)));
    eperp21.row(i) *= norm21 / dblA;
    eperp13.row(i) = u.cross(v13);
    eperp13.row(i) = eperp13.row(i) / std::sqrt(eperp13.row(i).dot(eperp13.row(i)));
    eperp13.row(i) *= norm13 / dblA;
  }

  std::vector<int> rs;
  rs.reserve(F.rows()*4*3);
  std::vector<int> cs;
  cs.reserve(F.rows()*4*3);
  std::vector<double> vs;
  vs.reserve(F.rows()*4*3);

  // row indices
  for(int r=0;r<3;r++)
  {
    for(int j=0;j<4;j++)
    {
      for(int i=r*F.rows();i<(r+1)*F.rows();i++) rs.push_back(i);
    }
  }

  // column indices
  for(int r=0;r<3;r++)
  {
    for(int i=0;i<F.rows();i++) cs.push_back(F(i,1));
    for(int i=0;i<F.rows();i++) cs.push_back(F(i,0));
    for(int i=0;i<F.rows();i++) cs.push_back(F(i,2));
    for(int i=0;i<F.rows();i++) cs.push_back(F(i,0));
  }

  // values
  for(int i=0;i<F.rows();i++) vs.push_back(eperp13(i,0));
  for(int i=0;i<F.rows();i++) vs.push_back(-eperp13(i,0));
  for(int i=0;i<F.rows();i++) vs.push_back(eperp21(i,0));
  for(int i=0;i<F.rows();i++) vs.push_back(-eperp21(i,0));
  for(int i=0;i<F.rows();i++) vs.push_back(eperp13(i,1));
  for(int i=0;i<F.rows();i++) vs.push_back(-eperp13(i,1));
  for(int i=0;i<F.rows();i++) vs.push_back(eperp21(i,1));
  for(int i=0;i<F.rows();i++) vs.push_back(-eperp21(i,1));
  for(int i=0;i<F.rows();i++) vs.push_back(eperp13(i,2));
  for(int i=0;i<F.rows();i++) vs.push_back(-eperp13(i,2));
  for(int i=0;i<F.rows();i++) vs.push_back(eperp21(i,2));
  for(int i=0;i<F.rows();i++) vs.push_back(-eperp21(i,2));

  // create sparse gradient operator matrix
  G.resize(3*F.rows(),V.rows());
  std::vector<Eigen::Triplet<typename DerivedV::Scalar> > triplets;
  for (int i=0;i<(int)vs.size();++i)
  {
    triplets.push_back(Eigen::Triplet<typename DerivedV::Scalar>(rs[i],cs[i],vs[i]));
  }
  G.setFromTriplets(triplets.begin(), triplets.end());
}

#ifdef IGL_STATIC_LIBRARY
// Explicit template specialization
// template void igl::grad<double, int>(Eigen::Matrix<double, -1, -1, 0, -1,-1> const&, Eigen::Matrix<int, -1, -1, 0, -1, -1> const&,Eigen::SparseMatrix<double, 0, int>&);
template void igl::grad<Eigen::Matrix<double, -1, 3, 0, -1, 3>, Eigen::Matrix<int, -1, 3, 0, -1, 3> >(Eigen::PlainObjectBase<Eigen::Matrix<double, -1, 3, 0, -1, 3> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, 3, 0, -1, 3> > const&, Eigen::SparseMatrix<Eigen::Matrix<double, -1, 3, 0, -1, 3>::Scalar, 0, int>&);
//template void igl::grad<Eigen::Matrix<double, -1, 3, 0, -1, 3>, Eigen::Matrix<int, -1, 3, 0, -1, 3> >(Eigen::PlainObjectBase<Eigen::Matrix<double, -1, 3, 0, -1, 3> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, 3, 0, -1, 3> > const&, Eigen::SparseMatrix<Eigen::Matrix<double, -1, 3, 0, -1, 3>::Scalar, 0, int>&);
template void igl::grad<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&, Eigen::SparseMatrix<Eigen::Matrix<double, -1, -1, 0, -1, -1>::Scalar, 0, int>&);
#endif


#pragma once

#include <string>
#include <armadillo>

#include "equationofstate/equationofstatebase.hpp"

/*!
 * \brief Implements the Benedict-Webb-Rubin-Starling (BWRS) equation of state.
 *
 * This class implements the
 * Benedict-Webb-Rubin-Starling
 * (BWRS) equation of state. BWRS depends on 11 mixture parameters B_0 and A_0,
 * which are calculated from the pure component parameters \f$A_{0i}\f$ and
 * \f$B_{0i}\f$, as well as the binary interaction coefficients \f$k_{ij}\f$.
 *
 * More information on BWRS can be found in
 * Fluid Properties for Light Petroleum Systems
 * (Kenneth E. Starling, 1973, ISBN 978-0872012936).
 *
 * Critical properties refer to the critical temperature (`m_Tc`), critical
 * density (`m_rhoc`), critical pressure (`m_pc`), and accentric factor (`m_w`),
 * which are used in the evaluation of the BWRS equation. These are found in
 * Table 5 at page 223 of Fluid Properties for Light Petroleum Systems.
 */
class BWRS : public EquationOfStateBase
{
public:
    /*!
     * \brief Constructor that uses a string to select which mixture parameters
     * \f$A_0\f$ and \f$B_0\f$, binary interaction coefficients \f$k_{ij}\f$,
     * and critical properties \f$T_c\f$ etc. to use. The options are "Calsep",
     * "JFH" and "Starling".
     *
     * "Calsep" refers to the parameters tuned for Statpipe, see
     * Tuning af parametre i BWRS-tilstandsligningen til brug i STATPIPES termodynamiske programpakke
     * (Jan Munch, 1985) for more info.
     *
     * "JFH" refer to the parameters found in the original code by Helgaker.
     * These have been shown to give bad results after the solver was rewritten
     * to use molar density (the original form used regular density), so I would
     * recommend not using this.
     *
     * "Staring" refers to the parameters given by Starling in
     * Fluid Properties for Light Petroleum Systems
     * (Kenneth E. Starling, 1973, ISBN 978-0872012936).
     *
     * \param composition Gas composition fraction, in order C1, C2, C3, iC4, nC4, iC5, nC5, C6, N2, CO2.
     * \param parameterSet Which parameter set to use ("Starling", "Calsep" or "JFH").
     * \return An instance of BWRS.
     */
    BWRS(
        const arma::vec& composition = Composition::defaultComposition,
        const std::string& parameterSet = "Calsep"
    );

    /*!
     * \brief Explicit BWRS constructor which allows specifying which files
     * to load mixture parameters \f$A_0\f$ and \f$B_0\f$ and binary interaction
     * coefficients \f$k_{ij}\f$ from, and which critical properties to use.
     *
     * \see loadJFHCriticalProperties()
     * \see loadCalsepCriticalProperties()
     * \see loadStarlingCriticalProperties()
     *
     * \param composition Gas composition fractions, in order C1, C2, C3, iC4, nC4, iC5, nC5, C6, N2, CO2.
     * \param ABparameterFile Path to file which contains the pure component parameters \f$A_{0i}\f$ and \f$B_{0i}\f$
     * \param binaryInteractionTableFile Path to file which contains the interaction parameters \f$k_{ij}\f$
     * \param criticalProperties Which set of critical gas properties should be used.
     */
    static BWRS fromFilePaths(
        const arma::vec& composition = Composition::defaultComposition,
        const std::string& ABparameterFile = std::string(TRANSFLOW_RESOURCE_PATH) + "/equationofstate/bwrs/calsepABparameters.csv",
        const std::string& binaryInteractionTableFile = std::string(TRANSFLOW_RESOURCE_PATH) + "/equationofstate/bwrs/calsepBinaryInteraction.csv",
        const std::string& criticalProperties = "Starling"
    );

    /*!
     * \brief Evaluate the BWRS equation of state at the given pressure and temperature.
     * \param pressure Gas pressure [Pa]
     * \param temperature Gas temperature [K]
     * \return See EquationOfStateBase::evaluate().
     */
    virtual arma::vec evaluate(
            const double pressure,
            const double temperature) const override;

    /*!
     * \brief Calculate the compressibility factor (Z) of the gas at a given pressure and temperature.
     *
     * This function is useful if we just need the compressibility and not all
     * the other properties we can find from the EOS.
     *
     * It contains a call to
     * BWRS::findMolarDensity(),
     * which contains some Newton-Raphson root finding, which are among the
     * heaviest part of this whole class, so I don't expect much time-save over
     * a call to evaluate().
     *
     * \param pressure Gas pressure [Pa].
     * \param temperature Gas temperature [K].
     * \return Compressibility factor Z [-].
     */
    virtual double calculateCompressibility(
            const double pressure,
            const double temperature) const override;

    /*!
     * \brief Set the composition of the EOS.
     *
     * This calls EquationOfStateBase::setComposition(), then updates the non-zero
     * component indices BWRS::m_indices, calculates the new critical pressure
     * and temperature of the gas mixture, before finally updating all the
     * coefficients `m_A0`, `m_B0`, through `m_GAMMA`.
     *
     * \param composition New gas composition.
     * \param force If the composition should be changed even if it's within machine precision of the previous composition.
     * \return True if composition was changed, else false.
     */
    virtual bool setComposition(const arma::vec& composition, const bool force = true) override;

    /*!
     * \brief loadCriticalProperties Load a set of critical properties. Either
     * "Calsep", "JFH" or "Starling".
     * \param name Name of critical properties. Either "Calsep", "JFH" or
     * "Starling".
     */
    void loadCriticalProperties(const std::string name);

    /*!
     * \brief loadParametersAndCriticalProperties Load parameters and critical
     * properties.
     * \param parameterSet Name of parameter set. Either "Calsep", "JFH" or
     * "Starling".
     */
    void loadParametersAndCriticalProperties(const std::string parameterSet);

    /*!
     * \brief Load the critical properties from Starling.
     *
     * This function sets the values of `m_Tc`, `m_rhoc`, `m_w`, `m_pc`,
     * `m_molarMass`, `m_expW`, and `m_R` according to the values found in
     * Fluid Properties for Light Petroleum Systems
     * (Kenneth E. Starling, 1973, ISBN 978-0872012936).
     *
     * The values set by this function have been found to produce the best results.
     */
    void loadStarlingCriticalProperties();

    /*!
     * \brief Load the critical properties from Helgaker.
     *
     * This function sets the values of `m_Tc`, `m_rhoc`, `m_w`, `m_pc`,
     * `m_molarMass`, `m_expW`, and `m_R` according to the values found in the
     * original Helgaker Matlab code.
     *
     * \warning The values set by this function have been found to produce bad results.
     * This is likely due to the rewrite to a molar density based solver, as
     * the equations are originally given by Starling.
     * Use loadStarlingCriticalProperties() instead.
     */
    void loadJFHCriticalProperties();

    /*!
     * \brief Load the critical properties from the Calsep report.
     *
     * This function sets the values of `m_Tc`, `m_rhoc`, `m_w`, `m_pc`,
     * `m_molarMass`, `m_expW`, and `m_R` according to the values found in the
     * original Helgaker Matlab code.
     *
     * \warning The values set by this function have been found to produce bad results.
     * This is likely due to the rewrite to a molar density based solver, as
     * the equations are originally given by Starling.
     * Use loadStarlingCriticalProperties() instead.
     */
    void loadCalsepCriticalProperties();

    /*! Load the mixture parameters \f$A_0\f$ and \f$B_0\f$, and binary
     * interaction coefficients \f$k_{ij}\f$ from Gassco.
     */
    void loadGasscoParameters();

    /*! Load the mixture parameters \f$A_0\f$ and \f$B_0\f$, and binary
     * interaction coefficients \f$k_{ij}\f$ from the Calsep report.
     */
    void loadCalsepParameters();

    /*! Load the mixture parameters \f$A_0\f$ and \f$B_0\f$, and binary
     * interaction coefficients \f$k_{ij}\f$ from the Starling BWRS book.
     */
    void loadStarlingParameters();

    /*!
     * \brief Load mixture parameters \f$A_0\f$ and \f$B_0\f$, and binary
     * interaction coefficients \f$k_{ij}\f$ coefficients from specific files.
     * \param ABparameterFile Path to AB parameter file.
     * \param binaryInteractionTableFile Path to binary interaction coefficients file.
     */
    void loadParameterFiles(
            const std::string& ABparameterFile,
            const std::string& binaryInteractionTableFile);

    //! Enable constant heat capacity \f$c_p\f$ and \f$c_v\f$
    void enableConstantHeatCapacities();

    /*!
     * \brief Find the gas density at a given pressure and temperature.
     *
     * This is just a wrapper around findMolarDensity() and a conversion from
     * molar density [mol/m3] to density [kg/m3]
     *
     * \param pressure Gas pressure [Pa].
     * \param temperature Gas temperature [K].
     * \param tolerance Newton-Raphson tolerance.
     * \return Gas density [kg/m3].
     */
    double findDensity(
            const double pressure,
            const double temperature,
            const double tolerance = 1e-4) const;

//    double getDerivativeOfZwrtDensityAtConstantTemperature(const double density, const double temperature) const;

    double getGasConstant() const { return m_R; } //!< Get the gas constant R.

    //! Get the critical pressure of the gas mixture [Pa]
    double getMixtureCriticalPressure() const { return m_criticalPressureOfMixture; }

    //! Get the critical temperature of the gas mixture [K]
    double getMixtureCriticalTemperature() const { return m_criticalTemperatureOfMixture; }

protected:

    // parameters from Starling book
    //                      C1          C2          C3          iC4         nC4         iC5         nC5         C6          N2          CO2

    //! Critical temperature [K]
    arma::vec m_Tc        = arma::vec({ 190.69,     305.39,     369.89,     408.13,     425.19,     460.37,     469.49,     507.29,     126.15,     304.15});           // critical temperature
    //! Critical density [kg/m3]
    arma::vec m_rhoc      = arma::vec({ 1.00500e+4, 6.75659e+3, 4.99936e+3, 3.80118e+3, 3.92132e+3, 3.24694e+3, 3.21491e+3, 2.71673e+3, 1.10992e+4, 1.06379e+4});       // critical molar density [mol/m3]
    //! Accentric factor [-]
    arma::vec m_w         = arma::vec({ 0.013,      0.1018,     0.157,      0.183,      0.197,      0.226,      0.252,      0.302,      0.035,      0.21});             // accentric factor
    //! Critical pressure [Pa]
    arma::vec m_pc        = arma::vec({ 45.96,      48.839,     42.5,       36.48,      37.96,      33.81,      33.69,      27.34,      33.99,      73.825})*1e5;       // critical pressure
    //! Molar mass of the different gas components [g/mol]
    arma::vec m_molarMass = arma::vec({ 16.042,     30.068,     44.094,     58.12,      58.12,      72.146,     72.146,     86.172,     28.016,     44.01});            // [g/mol]
    //! Exponential of the accentric factor.
    arma::vec m_expW = exp(-3.8*m_w);

    /*!
     * \brief The gas constant [J/(K mol)]
     *
     * The exact value of the gas constant is approx 8.3145 J/Kmol,
     * but if we are using the critical properties and other parameters from
     * Starling, these have been determined using an older definition of the
     * gas constant, which is 8.3160 J/Kmol.
     */
    double m_R = 8.3160; // gas constant from Starling [m3 Pa / K mol]

    /*!
     * \brief Binary interaction coefficients \f$k_{ij}\f$.
     *
     * The binary interaction coefficients are stored in matrix of size 10x10,
     * where the binary interactions between component i and j are stored at
     * location \f$(i, j)\f$ in the matrix. The matrix is symmetric, so
     * \f$k(i, j) == k(j, i)\f$. The components are in the usual order
     * (C1, C2, C3, iC4, nC4, iC5, nC5, C6, N2, CO2).
     *
     * These can be found in Table 1 at page 227 of Fluid Properties for Light Petroleum Systems.
     */
    arma::mat m_binaryInteractionParameterTable;

    /*!
     * \brief Pure component parameters Bi.
     *
     * From Table 1 at page 221 of Fluid Properties for Light Petroleum Systems.
     */
    arma::vec m_Bi;

    /*!
     * \brief Pure component parameters Ai.
     *
     * From Table 1 at page 221 of Fluid Properties for Light Petroleum Systems.
     */
    arma::vec m_Ai;

    double m_A0; //!< A coefficient used when evaluating the BWRS-equation. Independent of pressure and temperature.
    double m_B0; //!< A coefficient used when evaluating the BWRS-equation. Independent of pressure and temperature.
    double m_C0; //!< A coefficient used when evaluating the BWRS-equation. Independent of pressure and temperature.
    double m_D0; //!< A coefficient used when evaluating the BWRS-equation. Independent of pressure and temperature.
    double m_E0; //!< A coefficient used when evaluating the BWRS-equation. Independent of pressure and temperature.
    double m_a; //!< A coefficient used when evaluating the BWRS-equation. Independent of pressure and temperature.
    double m_b; //!< A coefficient used when evaluating the BWRS-equation. Independent of pressure and temperature.
    double m_c; //!< A coefficient used when evaluating the BWRS-equation. Independent of pressure and temperature.
    double m_d; //!< A coefficient used when evaluating the BWRS-equation. Independent of pressure and temperature.
    double m_ALPHA; //!< A coefficient used when evaluating the BWRS-equation. Independent of pressure and temperature.
    double m_GAMMA; //!< A coefficient used when evaluating the BWRS-equation. Independent of pressure and temperature.

    double m_criticalPressureOfMixture; //!< Critical pressure of the gas mixture.
    double m_criticalTemperatureOfMixture; //!< Critical temperature of the gas mixture.

    bool m_useConstantHeatCapacities = false; //!< Flag to set if we want to use constant heat capacity \f$c_p\f$ and \f$c_v\f$.

    /*!
     * \brief Calculates all the coefficients used for evaluating BWRS that are independent of pressure and temperature.
     *
     * A lot of different coefficients are used when evaluating the BWRS
     * equation to find the compressibility and the partial derivatives of Z.
     * Many of these are independent of pressure and temperature, so they can be
     * tabulated for the current gas composition.
     *
     * This optimization is most effective if we use constant composition, but
     * should also improve the evaluation of the formulas otherwise.
     */
    void calculateCoefficients();

    /*!
     * \brief Set up BWRS::m_indices to reflect which gas fractions are non-zero.
     *
     * The BWRS equation has some issues if it is evaluated with gas fractions
     * of zero, so to avoid this we store the indices of the non-zero fractions
     * and only loop over those when evaluating the equation.
     *
     * Call this function to find the non-zero indices and store them in BWRS::m_indices.
     */
    void findNonZeroComponents();

    arma::uvec m_indices; //!< The indices of the non-zero gas fractions (components).

    /*!
     * \brief Find the molar density of the gas at a given pressure and temperature.
     *
     * This is most likely the most computationally heavy function in this
     * class, since it contains a Newton-Raphson root finding, meaning that the
     * BWRS equation will be evaluated multiple times until it reaches
     * convergence.
     *
     * The convergence criterion is implemented as
     *
     *     if (std::abs(change/previous) < tolerance) break;
     *
     * \param pressure Gas pressure [Pa].
     * \param temperature Gas temperature [K].
     * \param tolerance Convergence criterion for the Newton-Raphson method.
     * \return Molar density [mol/m3]
     */
    double findMolarDensity(
            const double pressure,
            const double temperature,
            const double tolerance = 1e-4) const;
};


#include <iostream>
#include <vector>
#include <fstream>
#include <functional>
#include <numeric>
#include <cmath>
#include <cstdlib>
#include <time.h>
#include <string>
#include <tr1/unordered_map>
#include <Eigen/Core>
#include <random>

#include "utils.h"

#define RHO 0.95
#define EPSILON 0.000001
#define RATE 0.05

using namespace std;
using namespace Eigen;

template <typename T> int sgn(T val) {
  return (T(0) < val) - (val < T(0));
}

/* General parameters of the model */
template <typename T>
class Param {

 public:
  T var;

  void Init(const int& rows, const int& cols) {
    if (cols == 1) {
      var = (0.6 / sqrt (rows)) * T::Random(rows, 1);
      _del_var = T::Zero(rows, 1);
      _del_grad = T::Zero(rows, 1);
    }
    var = (0.6 / sqrt (rows + cols)) * T::Random(rows, cols);
    _del_var = T::Zero(rows, cols);
    _del_grad = T::Zero(rows, cols);
    _grad_sum = T::Zero(rows, cols);
    _epsilon = EPSILON * T::Ones(rows, cols);
  }

  void AdagradUpdate(const double& rate, const T& grad) {
    _del_grad += grad.cwiseAbs2();
    _grad_sum += grad;
    var -= rate * grad.cwiseQuotient(_del_grad.cwiseSqrt());
  }

  void AdagradUpdateWithL1Reg(const double& rate, const T& grad,
                              const double& l1_reg) {
    _update_num += 1;
    _del_grad += grad.cwiseAbs2();
    _grad_sum += grad;
    for (int i = 0; i < var.rows(); ++i) {
      for (int j = 0; j < var.cols(); ++j) {
        double diff = abs(_grad_sum(i, j)) - _update_num * l1_reg;
        if (diff <= 0)
          var(i, j) = 0;
        else
          var(i, j) = -sgn(_grad_sum(i, j)) * rate * diff / sqrt(_del_grad(i, j));
      }
    }
  }

  void AdagradUpdateWithL1RegNonNeg(const double& rate, const T& grad,
                                    const double& l1_reg) {
    _update_num += 1;
    _del_grad += grad.cwiseAbs2();
    _grad_sum += grad;
    for (int i = 0; i < var.rows(); ++i) {
      for (int j = 0; j < var.cols(); ++j) {
        double diff = abs(_grad_sum(i, j)) - _update_num * l1_reg;
        if (diff <= 0)
          var(i, j) = 0;
        else {
          double temp = -sgn(_grad_sum(i, j)) * rate * diff /
                        sqrt(_del_grad(i, j));
          if (temp >= 0) var(i, j) = temp;
          else var(i, j) = 0;
        }
      }
    }
  }

  void WriteToFile(ofstream& out) {
    out << var.rows() << " " << var.cols() << " ";
    for (unsigned i = 0; i < var.rows(); ++i) {
      for(unsigned j = 0; j < var.cols(); ++j)
        out << var(i, j) << " ";
    }
    out << endl;
  }

  void ReadFromFile(ifstream& in) {
    string line;
    getline(in, line);
    vector<string> data = split_line(line, ' ');
    int rows = stoi(data[0]), cols = stoi(data[1]);
    var = T::Zero(rows, cols);
    for (int i = 2; i < data.size(); ++i)
      var((i-2)/cols, (i-2)%cols) = stod(data[i]);
  }

 private:
  T _del_var, _del_grad, _grad_sum;  // updates/gradient memory
  T _epsilon;
  int _update_num = 0;
};

/* Main class definition that learns the word vectors */
class Model {

 public:
  /* The parameters of the model */
  vector<Param<Col> > atom;
  Param<Mat> dict;
  int vec_len, factor;

  Model(const int& times, const int& vector_len, const int& vocab_len) {
    vec_len = vector_len;
    factor = times;
    dict.Init(vec_len, factor * vec_len);
    /* Params initialization */
    for (int i = 0; i < vocab_len; ++i) {
      Param<Col> vec;
      vec.Init(factor * vec_len, 1);
      atom.push_back(vec);
    }
  }

  template<typename T> void NonLinearity(T* vec) { ElemwiseHardTanh(vec); }

  void PredictVector(const Col& word_vec, const int& word_index,
                     Col* pred_vec) {
    *pred_vec = dict.var * atom[word_index].var;
  }

  void UpdateParams(const int& word_index, const double& rate,
                    const Col& diff_vec, const double& l1_reg,
                    const double& l2_reg) {
    Mat dict_grad = -2 * diff_vec * atom[word_index].var.transpose() +
                    2 * l2_reg * dict.var;
    dict.AdagradUpdate(rate, dict_grad);
    Col atom_elem_grad = -2 * dict.var.transpose() * diff_vec;
    atom[word_index].AdagradUpdateWithL1RegNonNeg(rate, atom_elem_grad,
                                                  l1_reg);
  }

  void WriteVectorsToFile(const string& filename,
                          const mapUnsignedStr& vocab) {
    ofstream outfile(filename);
    if (outfile.is_open()) {
      outfile.precision(3);
      for(unsigned i = 0; i < atom.size(); ++i) {
        auto it = vocab.find(i);
        outfile << it->second << " ";
        for (unsigned j = 0; j < atom[i].var.rows(); ++j)
          outfile << atom[i].var[j] << " ";
        outfile << endl;
      }
      outfile.close();
      cerr << "\nWritten vectors to: " << filename;
    } else {
      cerr << "\nFailed to open " << filename;
    }
  }

  void WriteDictToFile(const string& filename) {
    ofstream outfile(filename);
    if (outfile.is_open()) {
      outfile.precision(3);
      dict.WriteToFile(outfile);
      outfile.close();
      cerr << "\nWritten atom to: " << filename;
    } else {
      cerr << "\nFailed to open " << filename;
    }
  }

};

void Train(const string& out_file, const int& factor,
           const int& cores, const double& l1_reg, const double& l2_reg,
           const vector<Col>& word_vecs, const mapUnsignedStr& vocab) {
  Model model(factor, word_vecs[0].size(), word_vecs.size());
  double avg_error = 1, prev_avg_err = 0;
  int iter = 0;
  while (iter < 20 || (avg_error > 0.05 && iter < 75 && abs(avg_error - prev_avg_err) > 0.001)) {
    iter += 1;
    cerr << "\nIteration: " << iter << endl;
    unsigned num_words = 0;
    double total_error = 0, atom_l1_norm = 0;
    int word_id;
    #pragma omp parallel num_threads(cores) shared(total_error,atom_l1_norm)
    #pragma omp for nowait private(word_id)
    for (int word_id = 0; word_id < word_vecs.size(); ++word_id) {
      /* Predict the i-th word and compute error */
      Col pred_vec;
      model.PredictVector(word_vecs[word_id], word_id, &pred_vec);
      Col diff_vec = word_vecs[word_id] - pred_vec;
      double error = diff_vec.squaredNorm();
      #pragma omp critical
      {
        total_error += error;
        num_words += 1;
        atom_l1_norm += model.atom[word_id].var.lpNorm<1>();
        cerr << num_words << "\r";
      }
      model.UpdateParams(word_id, RATE, diff_vec, l1_reg, l2_reg);
    }
    prev_avg_err = avg_error;
    avg_error = total_error / num_words;
    cerr << "\nError per example: "<< total_error / num_words;
    cerr << "\nDict L2 norm: " << model.dict.var.lpNorm<2>();
    cerr << "\nAvg Atom L1 norm: " << atom_l1_norm / num_words;
  }
  model.WriteVectorsToFile(out_file, vocab);
  model.WriteDictToFile(out_file + "_dict");
}

int main(int argc, char **argv) {
  mapUnsignedStr vocab;
  vector<Col> word_vecs;
  if (argc == 7) {
    string vec_corpus = argv[1];
    int factor = stoi(argv[2]);
    double l1_reg = stod(argv[3]), l2_reg = stod(argv[4]);
    int num_cores = stoi(argv[5]);
    string outfilename = argv[6];

    ReadVecsFromFile(vec_corpus, &vocab, &word_vecs);

    cerr << "Model specification" << endl;
    cerr << "----------------" << endl;
    cerr << "Vector length: " << word_vecs[0].size() << endl;
    cerr << "Dictionary length: " << factor * word_vecs[0].size() << endl;
    cerr << "L2 Reg (Dict): " << l2_reg << endl;
    cerr << "L1 Reg (Atom): " << l1_reg << endl;
    cerr << "Number of Cores: " << num_cores << endl;
    cerr << "----------------" << endl;

    Train(outfilename, factor, num_cores, l1_reg, l2_reg, word_vecs, vocab);
  } else {
    cerr << "Usage: "<< argv[0] << " vec_corpus factor l1_reg l2_reg "
         << "num_cores outfilename\n";
  }
  return 1;
}


// Copyright (c) 2012-2017 VideoStitch SAS
// Copyright (c) 2018 stitchEm

#ifndef ROTATION_ESTIMATION_HPP_
#define ROTATION_ESTIMATION_HPP_

#include "util/lmfit/lmmin.hpp"

#include <opencv2/core/core.hpp>
#include <Eigen/Dense>

namespace VideoStitch {
namespace Calibration {

typedef std::pair<Eigen::Vector3d, Eigen::Vector3d> SpherePointMatch;
typedef std::vector<SpherePointMatch> MatchList;

class RotationEstimationProblem : public Util::SolverProblem {
 public:
  explicit RotationEstimationProblem(const MatchList& matchList) : matchList(matchList) {}

  virtual void eval(const double* params, int m_dat, double* fvec, const char* fFilter, int /*iterationNumber*/,
                    bool* /* requestBreak */) const {
    Eigen::Matrix3d R;
    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 3; j++) {
        R(i, j) = params[i * 3 + j];
      }
    }

    for (auto i = 0; i < m_dat; ++i) {
      if (!fFilter || fFilter[i]) {
        Eigen::Vector3d rotatedPoint = (Eigen::Vector3d)(R * matchList[i].first);
        double y = (rotatedPoint.cross(matchList[i].second)).squaredNorm();
        double x = rotatedPoint.dot(matchList[i].second);
        // angular distance on the sphere
        double error = atan2(y, x);
        fvec[i] = error;
      } else {
        fvec[i] = 0.0;
      }
    }
  }

  virtual int numParams() const { return 9; }

  virtual int getNumInputSamples() const { return (int)matchList.size(); }

  virtual int getNumValuesPerSample() const { return 1; }

  virtual int getNumAdditionalValues() const { return 0; }

  MatchList getMatchList() const { return matchList; }

 private:
  MatchList matchList;
};

class RotationEstimationSolver : public Util::Solver<RotationEstimationProblem> {
 public:
  RotationEstimationSolver(const RotationEstimationProblem& problem, const char* const sampleFilter,
                           bool /*debug = false*/, bool /*useFloatPrecision = false*/)
      : Solver(problem), sampleFilter(sampleFilter) {}

  virtual bool run(std::vector<double>& params) {
    MatchList matchList = problem.getMatchList();
    const auto numberMatches = matchList.size();

    auto numberSamples = 0;
    // First count the number of samples that will be used
    for (uint32_t i = 0; i < numberMatches; ++i) {
      if (!sampleFilter || sampleFilter[i]) {
        numberSamples++;
      }
    }

    if (numberSamples < 3) {
      return false;
    }

    /*
     * Estimate the optimal rotation using the selected samples
     *
     * Least-Squares Rigid Motion Using SVD, Olga Sorkine
     * http://igl.ethz.ch/projects/ARAP/svd_rot.pdf
     */

    /*Build the covariance matrix*/
    Eigen::Matrix3d M;
    M.fill(0);
    for (uint32_t i = 0; i < numberMatches; ++i) {
      if (!sampleFilter || sampleFilter[i]) {
        Eigen::Vector3d first = matchList[i].first;
        Eigen::Vector3d second = matchList[i].second;

        for (int y = 0; y < 3; y++) {
          for (int x = 0; x < 3; x++) {
            M(y, x) += first(y) * second(x);
          }
        }
      }
    }

    Eigen::JacobiSVD<Eigen::Matrix3d> svd(M, Eigen::ComputeFullU | Eigen::ComputeFullV);
    Eigen::Matrix3d U = svd.matrixU();
    Eigen::Matrix3d V = svd.matrixV();

    /*Make sure we create a matrix with unit determinant*/
    double determinant = (U * V.transpose()).determinant();
    Eigen::Matrix3d D;
    D.setIdentity();
    D(2, 2) = 1.0 / determinant;
    Eigen::Matrix3d R = U * D * V.transpose();
    rotationToParam(params, R);

    return true;
  }

 private:
  void rotationToParam(std::vector<double>& params, const Eigen::Matrix3d& R) const {
    for (auto i = 0; i < 3; ++i) {
      for (auto j = 0; j < 3; ++j) {
        params[i + 3 * j] = R(i, j);
      }
    }
  }

  const char* const sampleFilter;
};

}  // namespace Calibration
}  // namespace VideoStitch

#endif


#include <iostream>
#include <fstream>
#include <stdio.h>
#include <math.h>
#include <omp.h>

#include <random>
#include <map>
#include <string>
#include <iomanip>

#include <unistd.h>
#include <string>
#include <algorithm>
#include <random>

#include <Eigen/Core>
#include <Eigen/Dense>

#include <boost/random.hpp>
#include <boost/program_options.hpp>
#include <iterator>
#include <armadillo>

#include "Sampling_functions.h"

using namespace std;
using namespace Eigen;
using namespace arma;
namespace po = boost::program_options;

int main(int argc, char *argv[])
{

	po::options_description desc("Options");
	desc.add_options()
	("M", po::value<int>()->required(), "No. of markers")
	("N", po::value<int>()->required(), "No. of individuals")
	("num_feat", po::value<int>()->default_value(3), "No. of factors")
	("iter", po::value<int>()->default_value(100), "No. of Gibbs iterations")
	("burnin", po::value<int>()->default_value(10), "No. burnin iterations")
	("b0_m", po::value<double>()->default_value(2), "b0_m")
	("b0_u", po::value<double>()->default_value(2), "b0_u")
	("input", po::value<std::string>()->required(),"Input filename")
	("out", po::value<std::string>()->default_value("BayesFactors_out"),"Output filename")
	("scale", "perform scaling")
	("missing", "missing data included?")
	;

	srand(time(0));

	po::variables_map vm;
	po::store(po::parse_command_line(argc,argv,desc),vm);
	po::notify(vm);

	int M=vm["M"].as<int>();
	int N=vm["N"].as<int>();
	int num_feat=vm["num_feat"].as<int>();
	int iter=vm["iter"].as<int>();
	int burnin=vm["burnin"].as<int>();
	double b0_m=vm["b0_m"].as<double>();
	double b0_u=vm["b0_u"].as<double>();
	string input=vm["input"].as<string>();
	string output=vm["out"].as<string>();

	MatrixXd X(N,M);

	int i,j,k,l,m=0;
	auto timenow = chrono::system_clock::to_time_t(chrono::system_clock::now());
	cout<<"Started analysis!"<<endl;
	timenow = chrono::system_clock::to_time_t(chrono::system_clock::now());
	cout << ctime(&timenow) << endl;

//Read Genotype Matrix
	ifstream f1(input+".X");
	if (f1){
		for (int i = 0; i < N; i++)
		{
			for (int j = 0; j < M; j++)
			{
				f1 >> X(i,j);
				//cout<<X(i,j)<<endl;
			}
		}
		f1.close();
		cout<<"finished reading matrix X!"<<endl;
		timenow = chrono::system_clock::to_time_t(chrono::system_clock::now());
		cout << ctime(&timenow) << endl;
	}else{
		cout<<"the "+input+".X"+" file does not exist/cannot be opened!"<<endl;
		return 0;
	}
//Read missing data indicator matrix
	MatrixXd Indicator(N,M);

	if (vm.count("missing")) {
	ifstream f2(input+".Indicator");
	if (f2){
		for (int i = 0; i < N; i++)
		{
			for (int j = 0; j < M; j++)
			{
				f2 >> Indicator(i,j);
				//cout<<X(i,j)<<endl;
			}
		}
		f2.close();
		cout<<"finished reading matrix Indicator!"<<endl;
		timenow = chrono::system_clock::to_time_t(chrono::system_clock::now());
		cout << ctime(&timenow) << endl;
	}else{
		cout<<"the "+input+".Indicator"+" file does not exist/cannot be opened!"<<endl;
		return 0;
	}
	}

	//Normalize matrix X
	if (vm.count("scale")) {
		if (vm.count("missing")) {
			X=X.cwiseProduct(Indicator);
			RowVectorXd mean = X.colwise().sum().array()/Indicator.colwise().sum().array();
			RowVectorXd sqsum(M);
			RowVectorXd sd(M);
			sqsum.setZero();
			sd.setZero();
			//to calculate sd I add the squared deviations only for non-missing entries as specified in the Indicator matrix
			for (int j = 0; j < M; j++)
			{
				for (int i = 0; i < N; i++)
				{
					sqsum[j] +=pow((X(i,j)-mean[j]),2)*Indicator(i,j);
				}
				sd[j]=sqrt((sqsum[j]/(Indicator.col(j).sum() - 1)));
			}
			//Divide by sd but make sure that sd!=0
//Should be possible to integrate following code so that only one loop through M is done
			for (int j = 0; j < M; j++)
			{
				if (sd[j]==0){
					for (int i = 0; i < N; i++)
					{
						X(i,j) -= mean[j];
					}
				}else{
					for (int i = 0; i < N; i++)
					{
						X(i,j) -= mean[j];
						X(i,j) /= sd[j];
					}
				}
			}
		}else{

			RowVectorXd mean = X.colwise().mean();
			RowVectorXd sd = ((X.rowwise() - mean).array().square().colwise().sum() / (X.rows() - 1)).sqrt();
			X = (X.rowwise() - mean).array().rowwise() / sd.array();
		}
	}

	double Xglobalmean=X.mean();
	//Factor analysis
	//Initialization of latent variables. It can be done with ML estimates. Here its done just by sampling normal deviates.
	MatrixXd w1_M1_sample(M,num_feat);
	for (int i = 0; i < M; i++)
	{
		for (int j = 0; j < num_feat; j++)
		{
			w1_M1_sample(i,j)=rnorm(0,1);
		}

	}
	MatrixXd w1_P1_sample(N,num_feat);
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < num_feat; j++)
		{
			w1_P1_sample(i,j)=rnorm(0,1);
		}

	}

	//Initialization of hyperparameters
	MatrixXd WI_u(num_feat,num_feat);
	VectorXd mu_u(num_feat);
	MatrixXd lambda_u(num_feat,num_feat);
	VectorXd mu0_u(num_feat);

	MatrixXd WI_m(num_feat,num_feat);
	VectorXd mu_m(num_feat);
	MatrixXd lambda_m(num_feat,num_feat);
	VectorXd mu0_m(num_feat);

	WI_m.setIdentity();
	lambda_m.setIdentity();
	lambda_u.setIdentity();
	WI_u.setIdentity();

	mu_m.setZero();
	mu0_m.setZero();
	mu_u.setZero();
	mu0_u.setZero();

	int df_m=num_feat;
	int df_u=num_feat;

	//Initialization of residual variance
	MatrixXd epsilon_t(N,M);
	double Xm=X.mean();
	epsilon_t=X-w1_P1_sample*w1_M1_sample.transpose();
	//double sigma2_e=2;
	double sigma2_e=sample_residual_variance_gamma(epsilon_t);
	VectorXd sigma2_e_rowvec(N);
	sample_residual_row_variance_gamma(epsilon_t, sigma2_e_rowvec);

	//Intialize running means of latent variables
	MatrixXd Ew1_M1_sample(M,num_feat);
	MatrixXd Ew1_P1_sample(N,num_feat);

	Ew1_M1_sample=w1_M1_sample;
	Ew1_P1_sample=w1_P1_sample;

	//create folder to put output
	int systemRet = system("mkdir -p BayesFactors_out");
	if(systemRet == -1){
		cout<<"system command to create folder to place output FAILED!"<<endl;
	}

	//declare filestreams for output
	ofstream file_lambda_u;
	ofstream file_lambda_m;
	ofstream file_mu_u;
	ofstream file_mu_m;
	ofstream file_latentInd;
	ofstream file_latentSNPs;
	ofstream file_sigma2_e;
	ofstream file_sigma2_e_rowvec;

	ofstream file_ElatentInd;
	ofstream file_ElatentSNPs;

	//clear files for hyper-parameters
	file_lambda_m.open ("BayesFactors_out/"+output+"_lambda_m.txt");
	file_lambda_u.open ("BayesFactors_out/"+output+"_lambda_u.txt");
	file_mu_m.open ("BayesFactors_out/"+output+"_mu_m.txt");
	file_mu_u.open ("BayesFactors_out/"+output+"_mu_u.txt");
	file_sigma2_e.open ("BayesFactors_out/"+output+"_sigma2_e.txt");
	file_sigma2_e_rowvec.open ("BayesFactors_out/"+output+"_sigma2_e_rowvec.txt");

	file_lambda_u.close();
	file_lambda_m.close();
	file_mu_u.close();
	file_mu_m.close();
	file_sigma2_e.close();
	file_sigma2_e_rowvec.close();

	//appending sigma2_e
	file_sigma2_e.open ("BayesFactors_out/"+output+"_sigma2_e.txt", std::ios_base::app);
	file_sigma2_e_rowvec.open ("BayesFactors_out/"+output+"_sigma2_e_rowvec.txt", std::ios_base::app);
	//appending hyper-parameters
	file_lambda_m.open ("BayesFactors_out/"+output+"_lambda_m.txt", std::ios_base::app);
	file_lambda_u.open ("BayesFactors_out/"+output+"_lambda_u.txt", std::ios_base::app);
	file_mu_m.open ("BayesFactors_out/"+output+"_mu_m.txt", std::ios_base::app);
	file_mu_u.open ("BayesFactors_out/"+output+"_mu_u.txt", std::ios_base::app);

	//Burnin iterations
	cout<<"Starting burning "<<burnin<<" iterations"<<endl;
	for (int i = 0; i < burnin; i++)
	{
		//update residual variance
		epsilon_t=X-w1_P1_sample*w1_M1_sample.transpose();
		sigma2_e=sample_residual_variance_gamma(epsilon_t);
		sample_residual_row_variance_gamma(epsilon_t, sigma2_e_rowvec);

		//update SNP hyperparameters
		sample_hyper(w1_M1_sample,WI_m,b0_m,mu0_m,df_m,mu_m,lambda_m);
		//update individual hyperparameters
		sample_hyper(w1_P1_sample,WI_u,b0_u,mu0_u,df_u,mu_u,lambda_u);
		//update individual parameters

		//sample_ind_missing (Xglobalmean,w1_M1_sample,w1_P1_sample,Indicator,X,N,M,num_feat,lambda_u,mu_u,sigma2_e_rowvec);
		//sample_SNP_missing (Xglobalmean,w1_P1_sample,w1_M1_sample,Indicator,X,N,M,num_feat,lambda_m,mu_m,sigma2_e);

		//update SNP parameters
		sample_ind (Xglobalmean,w1_M1_sample,w1_P1_sample,X,N,num_feat,lambda_u,mu_u,sigma2_e_rowvec);
		sample_SNP (Xglobalmean,w1_P1_sample,w1_M1_sample,X,M,num_feat,lambda_m,mu_m,sigma2_e);
		//update rows and columns together
		//sample_ind_SNP (w1_M1_sample,w1_P1_sample, X,N,M,num_feat,lambda_u,mu_u,lambda_m,mu_m,sigma2_e);

	}

	//Sampling iterations
	cout<<"Finished burnin, starting sampling "<<iter<<" iterations" <<endl;

	for (int i = 0; i < iter; i++)
	{

		//write out for each iteration the factors
		file_latentInd.open ("BayesFactors_out/"+output+".iter"+to_string(i+1)+".factors");
		file_latentInd << w1_P1_sample << ' ';

		file_latentInd << endl;
		file_latentInd.close();

		//write out for each iteration the scores
		file_latentSNPs.open ("BayesFactors_out/"+output+".iter"+to_string(i+1)+".scores");
		file_latentSNPs << w1_M1_sample << ' ';

		file_latentSNPs<< endl;
		file_latentSNPs.close();

		//write-out hyperparameters (covariance matrices)
		//file_lambda_m << i <<" ";
		for (int j = 0; j < lambda_m.rows(); j++){
			file_lambda_m << lambda_m.row(j) << " ";
		}
		file_lambda_m<<endl;
		//file_lambda_u << i<<" ";
		for (int j = 0; j <lambda_u.rows(); j++){
			file_lambda_u  << lambda_u.row(j) << " ";
		}
		file_lambda_u<<endl;

		//write-out hyperparameters (means)
		for (int j = 0; j <num_feat; j++){
			file_mu_m<<mu_m[j]<<" ";
			file_mu_u<<mu_u[j]<<" ";
		}
		file_mu_m<<endl;
		file_mu_u<<endl;

		//write-out residual variance
		file_sigma2_e<<sigma2_e<<endl;

		for (int j = 0; j <sigma2_e_rowvec.rows(); j++){
			file_sigma2_e_rowvec  << sigma2_e_rowvec.row(j) << " ";
		}
		file_sigma2_e_rowvec<<endl;

		//GIBBS UPDATES
		//update residual variance
		epsilon_t=X-w1_P1_sample*w1_M1_sample.transpose();
		sigma2_e=sample_residual_variance_gamma(epsilon_t);
		sample_residual_row_variance_gamma(epsilon_t, sigma2_e_rowvec);

		//update SNP hyperparameters
		sample_hyper(w1_M1_sample,WI_m,b0_m,mu0_m,df_m,mu_m,lambda_m);
		//update individual hyperparameters
		sample_hyper(w1_P1_sample,WI_u,b0_u,mu0_u,df_u,mu_u,lambda_u);

		//update individual parameters
		sample_ind (Xglobalmean,w1_M1_sample,w1_P1_sample,X,N,num_feat,lambda_u,mu_u,sigma2_e_rowvec);
		//sample_ind_missing (Xglobalmean,w1_M1_sample,w1_P1_sample,Indicator,X,N,M,num_feat,lambda_u,mu_u,sigma2_e_rowvec);
		//sample_SNP_missing (Xglobalmean,w1_P1_sample,w1_M1_sample,Indicator,X,N,M,num_feat,lambda_m,mu_m,sigma2_e);
		//update SNP parameters
		sample_SNP (Xglobalmean,w1_P1_sample,w1_M1_sample,X,M,num_feat,lambda_m,mu_m,sigma2_e);

		//running average has +1 value (1 from burnin)
		Ew1_M1_sample=Ew1_M1_sample+(w1_M1_sample-Ew1_M1_sample)/(i+2);
		Ew1_P1_sample=Ew1_P1_sample+(w1_P1_sample-Ew1_P1_sample)/(i+2);

	}

	//write-out means for latent variables
	file_ElatentInd.open ("BayesFactors_out/"+output+"_Efactors.txt");
	file_ElatentSNPs.open ("BayesFactors_out/"+output+"_Escores.txt");
	file_ElatentInd<<Ew1_P1_sample<<endl;
	file_ElatentSNPs<<Ew1_M1_sample<<endl;
	file_ElatentInd.close();
	file_ElatentSNPs.close();

	file_lambda_u.close();
	file_lambda_m.close();

	file_mu_u.close();
	file_mu_m.close();

	file_sigma2_e.close();
	file_sigma2_e_rowvec.close();

	cout<<"Finished!"<<endl;
	timenow = chrono::system_clock::to_time_t(chrono::system_clock::now());
	cout << ctime(&timenow) << endl;

	return 0;
}


/**
 * \file LinkwitzRileyFilter.hxx
 */

#include "LinkwitzRileyFilter.h"

#include <boost/math/tools/polynomial.hpp>

namespace ATK
{
  template<typename DataType>
  LinkwitzRileyLowPassCoefficients<DataType>::LinkwitzRileyLowPassCoefficients(gsl::index nb_channels)
    :Parent(nb_channels)
  {
  }

  template <typename DataType>
  void LinkwitzRileyLowPassCoefficients<DataType>::setup()
  {
    Parent::setup();

    CoeffDataType omega = boost::math::constants::pi<CoeffDataType>() * cut_frequency;
    CoeffDataType kappa = omega / std::tan(omega / input_sampling_rate);
    CoeffDataType delta = kappa * kappa + omega * omega + 2 * kappa * omega;

    coefficients_in[2] = omega * omega / delta;
    coefficients_in[1] = 2 * omega * omega / delta;
    coefficients_in[0] = omega * omega / delta;
    coefficients_out[1] = 2 * (kappa * kappa - omega * omega) / delta;
    coefficients_out[0] = -(omega * omega + kappa * kappa - 2 * kappa * omega) / delta;
  }

  template<typename DataType>
  LinkwitzRileyHighPassCoefficients<DataType>::LinkwitzRileyHighPassCoefficients(gsl::index nb_channels)
    :Parent(nb_channels)
  {
  }

  template <typename DataType>
  void LinkwitzRileyHighPassCoefficients<DataType>::setup()
  {
    Parent::setup();

    CoeffDataType omega = boost::math::constants::pi<CoeffDataType>() * cut_frequency;
    CoeffDataType kappa = omega / std::tan(omega / input_sampling_rate);
    CoeffDataType delta = kappa * kappa + omega * omega + 2 * kappa * omega;

    coefficients_in[2] = kappa * kappa / delta;
    coefficients_in[1] = - 2 * kappa * kappa / delta;
    coefficients_in[0] = kappa * kappa / delta;
    coefficients_out[1] = 2 * (kappa * kappa - omega * omega) / delta;
    coefficients_out[0] = -(omega * omega + kappa * kappa - 2 * kappa * omega) / delta;
  }

  template<typename DataType>
  LinkwitzRiley4LowPassCoefficients<DataType>::LinkwitzRiley4LowPassCoefficients(gsl::index nb_channels)
  :Parent(nb_channels)
  {
  }

  template <typename DataType>
  void LinkwitzRiley4LowPassCoefficients<DataType>::setup()
  {
    Parent::setup();

    auto wc=2*boost::math::constants::pi<CoeffDataType>() * cut_frequency;
    auto wc2=wc*wc;
    auto wc3=wc2*wc;
    auto wc4=wc2*wc2;
    auto k=wc/std::tan(wc/2/input_sampling_rate);
    auto k2=k*k;
    auto k3=k2*k;
    auto k4=k2*k2;
    auto sqrt2=std::sqrt(CoeffDataType(2));
    auto sq_tmp1=sqrt2*wc3*k;
    auto sq_tmp2=sqrt2*wc*k3;
    auto a_tmp=4*wc2*k2+2*sq_tmp1+k4+2*sq_tmp2+wc4;

    coefficients_out[3]=-(4*(wc4+sq_tmp1-k4-sq_tmp2))/a_tmp;
    coefficients_out[2]=-(6*wc4-8*wc2*k2+6*k4)/a_tmp;
    coefficients_out[1]=-(4*(wc4-sq_tmp1+sq_tmp2-k4))/a_tmp;
    coefficients_out[0]=-(k4-2*sq_tmp1+wc4-2*sq_tmp2+4*wc2*k2)/a_tmp;

    coefficients_in[0] = coefficients_in[4] = wc4/a_tmp;
    coefficients_in[1] = coefficients_in[3] = 4*wc4/a_tmp;
    coefficients_in[2] = 6*wc4/a_tmp;
  }

  template<typename DataType>
  LinkwitzRiley4HighPassCoefficients<DataType>::LinkwitzRiley4HighPassCoefficients(gsl::index nb_channels)
  :Parent(nb_channels)
  {
  }

  template <typename DataType>
  void LinkwitzRiley4HighPassCoefficients<DataType>::setup()
  {
    Parent::setup();

    auto wc=2*boost::math::constants::pi<CoeffDataType>() * cut_frequency;
    auto wc2=wc*wc;
    auto wc3=wc2*wc;
    auto wc4=wc2*wc2;
    auto k=wc/std::tan(wc/2/input_sampling_rate);
    auto k2=k*k;
    auto k3=k2*k;
    auto k4=k2*k2;
    auto sqrt2=std::sqrt(CoeffDataType(2));
    auto sq_tmp1=sqrt2*wc3*k;
    auto sq_tmp2=sqrt2*wc*k3;
    auto a_tmp=4*wc2*k2+2*sq_tmp1+k4+2*sq_tmp2+wc4;

    coefficients_out[3]=-(4*(wc4+sq_tmp1-k4-sq_tmp2))/a_tmp;
    coefficients_out[2]=-(6*wc4-8*wc2*k2+6*k4)/a_tmp;
    coefficients_out[1]=-(4*(wc4-sq_tmp1+sq_tmp2-k4))/a_tmp;
    coefficients_out[0]=-(k4-2*sq_tmp1+wc4-2*sq_tmp2+4*wc2*k2)/a_tmp;

    coefficients_in[0] = coefficients_in[4] = k4/a_tmp;
    coefficients_in[1] = coefficients_in[3] = -4*k4/a_tmp;
    coefficients_in[2] = 6*k4/a_tmp;
  }
}


//  Copyright (c) 2017 Zahra Khatami
//
// Train your data, then record them in an output file stated in "retrieving_weights_two_classes_into_txt_file"

#include <limits>
#include <math.h>
#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <Eigen/Core>
#include <Eigen/Dense>
#include <Eigen/Eigen>
#include <Eigen/LU>

using namespace Eigen;
#define MAX_FLOAT (std::numeric_limits<float>::max())

class learning_binary_regression_model {

	std::size_t number_of_experiments;
	std::size_t number_of_features;
	float threshold; 							//the convergence for estimating the final weights
	MatrixXf experimental_results; 				//the experimental values of the features of the training data
	MatrixXf experimental_results_trans;		//transpose of experimental_results
	MatrixXf execution_times;					//execution time for each class for each experiment
	MatrixXf weightsb; 							//weights of our learning network
	MatrixXf new_weightsb;						//updated weights after each step
	MatrixXf targets_two_class;					//outputs of the training data
	MatrixXf diag_weightsb; 					//used for updating weights
	MatrixXf M; 								//used for updating weights
	int* predicted_output_two_class;			//predicted class of each experimental results
	float* averages;							//parameters for normalization
	float* averages_2;							//parameters for normalization
	float* var;									//parameters for normalization

	void normalizing_samples_two_class();
	void updating_values_of_M_and_diag_weights();
	void updating_values_of_weights_two_class();
	void new_values_for_weightsb();
	float computing_new_least_squared_err_two_class();
	void learning_weights_two_classes();
	void printing_weights_two_class();
	void estimating_output_two_class();
	void printing_computed_values(std::size_t row, std::size_t col, MatrixXf& mat);

public:
	learning_binary_regression_model(std::size_t number_of_expr, std::size_t number_of_ftrs,
									float th, float** expr_results, int* target_expr, float** exec_time) {

		number_of_experiments = number_of_expr;
		number_of_features = number_of_ftrs;
		threshold = th;

		weightsb = Eigen::MatrixXf::Random((number_of_features +  1), 1);
		new_weightsb = Eigen::MatrixXf::Random((number_of_features +  1), 1);
		targets_two_class = Eigen::MatrixXf::Random(number_of_experiments, 1);
		diag_weightsb = Eigen::MatrixXf::Random(number_of_experiments, number_of_experiments);
		M = Eigen::MatrixXf::Random(number_of_experiments, 1);
		experimental_results = Eigen::MatrixXf::Random(number_of_experiments, (number_of_features +  1));
		experimental_results_trans = Eigen::MatrixXf::Random((number_of_features + 1), number_of_experiments);
		execution_times = MatrixXf::Random(number_of_experiments, 2);
		predicted_output_two_class = new int[number_of_experiments];

		//variance and average of each features value for normalization
		averages = new float[number_of_features];
		averages_2 = new float[number_of_features];
		var = new float[number_of_features];

		//initializing weights
		for(std::size_t i = 0; i < number_of_features +  1; i++) {
			weightsb(i, 0) = 0.1;
		}

		for(std::size_t n = 0; n < number_of_experiments; n++) {
			targets_two_class(n, 0) = (float)target_expr[n];
			M(n, 0) = 0.0;

			//initializing experimental_results
			experimental_results(n, 0) = 1.0; // 1 + wf + ....
			for(std::size_t f = 1; f < number_of_features + 1; f++) {
				experimental_results(n, f) = expr_results[n][f - 1];
			}

			//initializing execution_times
			for(std::size_t c = 0; c < 2; c++) {
				execution_times(n, c) = exec_time[n][c];
			}

			//initializing diag_weightsb
			for(std::size_t j = 0; j < number_of_experiments; j++) {
				diag_weightsb(n, j) = 0.0;
			}
		}
	}

	//two-class logistic regression
	void learning_two_classes();
	void retrieving_weights_two_classes_into_txt_file();
	void finalizing_two_classes();
	void printing_predicted_output_two_class();
	void finalizing_step();
};

//it prints computed values : for testing
void learning_binary_regression_model::printing_computed_values(std::size_t row, std::size_t col, MatrixXf& mat) {
	if(row != 0 && col != 0) {
		for(std::size_t r = 0; r < row; r++) {
			for(std::size_t c = 0; c < col; c++) {
				printf("%f, ", mat(r, c));
			}
			std::cout<<std::endl;
		}
	}
	else if(row == 0 && col != 0){
		for(std::size_t c = 0; c < col; c++) {
			printf("%f, ", mat(0, c));
		}
	}
	else {
		for(std::size_t r = 0; r < row; r++) {
			printf("%f, ", mat(r, 0));
		}
	}
	std::cout<<std::endl;
}

void learning_binary_regression_model::updating_values_of_M_and_diag_weights() {
	MatrixXf WX = MatrixXf::Random(1, number_of_experiments);
	MatrixXf weightsb_transpose = MatrixXf::Random(1, (number_of_features + 1));
	WX = weightsb_transpose * experimental_results_trans;

	for(std::size_t n = 0; n < number_of_experiments; n++) {
		M(n, 0) = float(1.0 / (1.0 + exp((-1.0) *WX(0, n))));
		diag_weightsb(n, n) = M(n, 0) * (1.0 - M(n, 0));
	}
}

void learning_binary_regression_model::new_values_for_weightsb() {
	//X^T * S
	MatrixXf X_TS = Eigen::MatrixXf::Random((number_of_features + 1), number_of_experiments);
	X_TS = experimental_results_trans * diag_weightsb;

	//X^T * S * X
	MatrixXf XSX = Eigen::MatrixXf::Random((number_of_features + 1), (number_of_features + 1));
	XSX = X_TS * experimental_results;

	//(X^T * S * X) ^ -1
	MatrixXf XSX_inv = Eigen::MatrixXf::Random((number_of_features + 1), (number_of_features + 1));
	XSX_inv = XSX.inverse();

	//(X^T * S * X) ^ -1  * X^T
	MatrixXf XSX_inv_X_T = Eigen::MatrixXf::Random((number_of_features + 1), number_of_experiments);
	XSX_inv_X_T = XSX_inv * experimental_results_trans;

	//S * X
	MatrixXf SX = Eigen::MatrixXf::Random(number_of_experiments, (number_of_features + 1));
	SX = diag_weightsb * experimental_results;

	//S * X * W
	MatrixXf SXW = Eigen::MatrixXf::Random(number_of_experiments, 1);
	SXW = SX * weightsb;

	//S * X * W + y - M
	MatrixXf SXW_y_M = Eigen::MatrixXf::Random(number_of_experiments, 1);
	SXW_y_M = SXW + targets_two_class - M;

	//W
	new_weightsb = XSX_inv_X_T * SXW_y_M;
}

float learning_binary_regression_model::computing_new_least_squared_err_two_class() {
	float num_err = 0.0;
	for(int n = 0; n < number_of_experiments; n++) {
		if(abs(execution_times(n, predicted_output_two_class[n]) - execution_times(n, targets_two_class(n, 0))) > 0.2) {
			num_err++;
		}
	}
	float prec = float(num_err / number_of_experiments);
	return prec;
}

void learning_binary_regression_model::updating_values_of_weights_two_class() {
	for(std::size_t f = 0; f < number_of_features + 1; f++) {
		weightsb(f, 0) = new_weightsb(f, 0);
	}
}

void learning_binary_regression_model::printing_weights_two_class() {
	printing_computed_values(number_of_features + 1, 0, weightsb);
}

void learning_binary_regression_model::estimating_output_two_class(){
	for(std::size_t n = 0; n < number_of_experiments; n++) {
		float temp = 0.0;
		for(std::size_t f = 0; f < number_of_features + 1; f++) {
			temp += weightsb(f, 0) * experimental_results(n, f);
		}
		if(temp >= 0) {
			predicted_output_two_class[n] = 1;
		}
		else {
			predicted_output_two_class[n] = 0;
		}
	}
}

//this func applys experimental values of the training data on our learning network
void learning_binary_regression_model::learning_weights_two_classes() {
	float least_squared_err = MAX_FLOAT;
	std::size_t itr = 1;
	while(threshold < least_squared_err) {
		updating_values_of_M_and_diag_weights();
		new_values_for_weightsb();
		updating_values_of_weights_two_class();
		estimating_output_two_class();
		least_squared_err = computing_new_least_squared_err_two_class();
		std::cout<<"\n("<<itr<<"),"<<"Least_squared_err =\t" << least_squared_err<<std::endl;
		printing_weights_two_class();
		itr++;
	}
}

void learning_binary_regression_model::normalizing_samples_two_class() {

	//initializing
	for(std::size_t i = 0; i < number_of_features; i++) {
		averages[i] = 0;
		averages_2[i] = 0;
		var[i] = 0;
	}

	//computing average and variance values for each feature
	for(std::size_t i = 0; i < number_of_experiments; i++) {
		for(std::size_t j = 1; j < number_of_features + 1; j++) {
			averages[j - 1] += experimental_results(i, j);
			averages_2[j - 1] += (pow(experimental_results(i, j), 2.0));
		}
	}
	for(std::size_t i = 0; i < number_of_features; i++) {
		averages[i] = float(averages[i]/number_of_experiments);
		averages_2[i] = float(averages_2[i]/number_of_experiments);
		var[i] = sqrt(averages_2[i] - pow(averages[i], 2.0));
	}

	for(std::size_t n = 0; n < number_of_experiments; n++) {
		for(std::size_t f = 1; f < number_of_features + 1; f++) {
			if(var[f - 1] != 0) {
				experimental_results(n, f) = float((experimental_results(n, f) - averages[f - 1])/var[f - 1]);
			}
		}
	}
}

void learning_binary_regression_model::learning_two_classes() {
	normalizing_samples_two_class();
	experimental_results_trans = experimental_results.transpose();
	learning_weights_two_classes();
}

//writes computed weight into the text file
void learning_binary_regression_model::retrieving_weights_two_classes_into_txt_file() {
	std::ofstream outputFile("inputs/par_if.dat");

	//normalization parameters (variance and average) in the first line
	outputFile << "1 0 ";
	for(std::size_t p = 0; p < number_of_features - 1; p++) {
		outputFile << var[p] << " " << averages[p] << " ";
	}
	outputFile << var[number_of_features - 1] << " " << averages[number_of_features - 1] << std::endl;

	for(std::size_t w = 0; w < number_of_features; w++) {
		outputFile<<weightsb(w, 0)<<" ";
	}
	outputFile<<weightsb(number_of_features, 0);
}

void learning_binary_regression_model::printing_predicted_output_two_class() {
	std::size_t num_err = 0;
	for(std::size_t n = 0; n < number_of_experiments; n++) {
		if(abs(execution_times(n, predicted_output_two_class[n]) - execution_times(n, targets_two_class(n, 0))) > 0.2) {
			num_err++;
			std::cout<<"["<<n<<"]\t"<<predicted_output_two_class[n]<<","<<targets_two_class(n, 0)<<std::endl;
		}
	}
	std::cout<<"\n number of error predicted is\t"<<num_err<<" out of "<<number_of_experiments<<std::endl;
}

void learning_binary_regression_model::finalizing_step() {

	//releasing memory
	delete[] averages;
	delete[] averages_2;
	delete[] var;
	delete[] predicted_output_two_class;
}


// Copyright (c) 2015
// Author: Chrono Law
#include <std.hpp>
#include <type_traits>
using namespace std;

//////////////////////////////////////////

#include <boost/rational.hpp>
using namespace boost;

void case1()
{
    rational<int> a;
    rational<int> b(20);
    rational<int> c(31415, 10000);

    rational<int> r;
    r = 0x31;
    r.assign(7, 8);
}

//////////////////////////////////////////

void case2()
{
#if 0
    rational<int> r(1.0);
    cout << r << endl;

    r = 3.14;
    cout << r << endl;

    r.assign(7.23, 100);
    cout << r << endl;
#endif
}

//////////////////////////////////////////

void case3()
{
    rational<int>   a(3),b(65534),c(22,7);

    b += a;
    c -= a;
    if (c >= 0)
    {
        c = c * b;
        ++a;
    }
    assert(a == 4);
}

//////////////////////////////////////////

void case4()
{
    rational<int> r(10);
    if (r)
    {
        r -= 10;
    }
    assert(!r);
}

//////////////////////////////////////////

void case5()
{
    rational<int> r(2718, 1000);
    cout << rational_cast<int>(r) << endl;
    cout << rational_cast<double>(r) << endl;

    //double x = r;
}

//////////////////////////////////////////

void case6()
{
    rational<int> r(22,7);
    cout << r.numerator() << ":" << r.denominator()
          << "=" << rational_cast<double>(r);
    cout << endl;
}

//////////////////////////////////////////

void case7()
{
    rational<int> a(-1414,1000), pi(314, 100);

    cout << "abs=" << abs(a) << endl;
    cout << pow(rational_cast<double>(a), 2) << endl;
    cout << cos(rational_cast<double>(pi)) << endl;

}

//////////////////////////////////////////
#include <boost/format.hpp>

void case8()
{
    int a = 37, b = 62;
    format fmt("gcd(%1%, %2%) = %3%. lcm(%1%, %2%) = %4%\n");
    cout << fmt % a % b % gcd(a,b) % lcm(a,b);
}

int main()
{
    case1();
    case2();
    case3();
    case4();
    case5();
    case6();
    case7();
    case8();
}


#include "stdafx.h"
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include "contest_types.h"
#include "solver_registry.h"
#include "judge.h"

namespace FlipAnnealingSolver {

namespace bg = boost::geometry;
using BoostPoint = bg::model::d2::point_xy<double>;
using BoostPolygon = bg::model::polygon<BoostPoint>;
using BoostLinestring = bg::model::linestring<BoostPoint>;

template <typename T>
BoostPoint ToBoostPoint(const T& point) {
  const auto [x, y] = point;
  return BoostPoint(x, y);
}

template <typename T>
BoostPolygon ToBoostPolygon(const std::vector<T>& points) {
  BoostPolygon polygon;
  for (std::size_t i = 0; i <= points.size(); ++i) {
    polygon.outer().push_back(ToBoostPoint(points[i % points.size()]));
  }
  if (bg::area(polygon) < 0.0) {
    bg::reverse(polygon);
  }
  return polygon;
}

template <typename T, typename U>
double SquaredDistance(const T& vertex0, const U& vertex1) {
  const auto [x0, y0] = vertex0;
  const auto [x1, y1] = vertex1;
  return (x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1);
}

template <typename T>
double SquaredEdgeLength(const T& vertices, const Edge& edge) {
  const auto [a, b] = edge;
  return SquaredDistance(vertices[a], vertices[b]);
}

class Solver : public SolverBase {
 public:
  SolverOutputs solve(const SolverArguments& args) override {
    hole_ = args.problem->hole_polygon;
    vertices_ = args.problem->vertices;
    edges_ = args.problem->edges;
    epsilon_ = args.problem->epsilon;
    hole_polygon_ = ToBoostPolygon(hole_);

    const int N = vertices_.size();
    auto pose = vertices_;
    double cost = std::numeric_limits<double>::infinity();
    double best_feasible_cost = std::numeric_limits<double>::infinity();
    std::vector<Point> best_feasible_pose;

    const int num_iters = 100000;
    const double T0 = 1.0e1;
    const double T1 = 1.0e-2;
    for (int iter = 0; iter < num_iters; ++iter) {
      const double progress = 1.0 * iter / num_iters;
      if (std::uniform_real_distribution(0.0, 1.0)(rng_) < 0.01) {
        const int v0 = std::uniform_int_distribution(0, N - 1)(rng_);
        const int v1 = std::uniform_int_distribution(0, N - 1)(rng_);
        if (v0 == v1) continue;
        auto pose_bak = pose;
        auto reflect = [](Point a, Point c, Point v) {
          return v - 2 * double(dot(v - c, a)) / double(dot(a, a)) * a;
        };
        auto diff = pose[v1] - pose[v0];
        Point n = {get_y(diff), -get_x(diff)};
        for (int v2 = 0; v2 < pose.size(); ++v2) {
          if (ccw(pose[v0], pose[v1], pose[v2])) {
            pose[v2] = reflect(n, pose[v0], pose[v2]);
          }
        }
        const auto [feasible, updated_cost] = Evaluate(pose);
        if (feasible && updated_cost < best_feasible_cost) {
          best_feasible_cost = updated_cost;
          best_feasible_pose = pose;
        }
        const double T = std::pow(T0, 1.0 - progress) * std::pow(T1, progress);
        if (std::uniform_real_distribution(0.0, 1.0)(rng_) < std::exp(-(updated_cost - cost) / T)) {
          cost = updated_cost;
        } else {
          pose = pose_bak;
        }
        continue;
      }
      const int v = std::uniform_int_distribution(0, N - 1)(rng_);
      const int dx = std::uniform_int_distribution(-3, 3)(rng_);
      const int dy = std::uniform_int_distribution(-3, 3)(rng_);
      auto& [x, y] = pose[v];
      x += dx;
      y += dy;
      const auto [feasible, updated_cost] = Evaluate(pose);
      if (feasible && updated_cost < best_feasible_cost) {
        best_feasible_cost = updated_cost;
        best_feasible_pose = pose;
      }
      const double T = std::pow(T0, 1.0 - progress) * std::pow(T1, progress);
      if (std::uniform_real_distribution(0.0, 1.0)(rng_) < std::exp(-(updated_cost - cost) / T)) {
        cost = updated_cost;
      } else {
        x -= dx;
        y -= dy;
      }
    }

    SolverOutputs outputs;
    if (best_feasible_pose.empty()) {
      outputs.solution = args.problem->create_solution(pose);
    } else {
      outputs.solution = args.problem->create_solution(best_feasible_pose);
    }
    return outputs;
  }

  template <typename P>
  std::tuple<bool, double> Evaluate(const std::vector<P>& pose) const {
    double deformation_cost = 0.0;
    double protrusion_cost = 0.0;
    double dislikes_cost = 0.0;

    const double tolerance = epsilon_ / 1'000'000.0;
    for (const auto& vertex : pose) {
      protrusion_cost += bg::distance(ToBoostPoint(vertex), hole_polygon_);
    }
    for (const auto& edge : edges_) {
      const auto [a, b] = edge;
      BoostLinestring linestring{ToBoostPoint(pose[a]), ToBoostPoint(pose[b])};
      std::vector<BoostLinestring> differences;
      bg::difference(linestring, hole_polygon_, differences);
      for (const auto& segment : differences) {
        protrusion_cost += 1.0e-2 * bg::length(segment);
      }

      const auto d0 = SquaredEdgeLength(vertices_, edge);
      const auto d1 = SquaredEdgeLength(pose, edge);
      deformation_cost += 1.0e1 * std::max(0.0, std::abs(d1 / d0 - 1.0) - tolerance);
    }

    for (const auto h : hole_) {
      double best = std::numeric_limits<double>::infinity();
      for (const auto v : pose) {
        best = std::min(best, SquaredDistance(h, v));
      }
      dislikes_cost += best * 1.0e-5;
    }

    const bool feasible = deformation_cost + protrusion_cost == 0.0;
    const double cost = deformation_cost + protrusion_cost + dislikes_cost;

    return {feasible, cost};
  }

 private:
  std::mt19937 rng_;
  std::vector<Point> hole_;
  std::vector<Point> vertices_;
  std::vector<Edge> edges_;
  integer epsilon_;
  BoostPolygon hole_polygon_;
};

}

REGISTER_SOLVER("FlipAnnealingSolver", FlipAnnealingSolver::Solver);
// vim:ts=2 sw=2 sts=2 et ci