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

The Medical Imaging Interaction Toolkit (MITK)

Copyright (c) German Cancer Research Center (DKFZ)
All rights reserved.

Use of this source code is governed by a 3-clause BSD license that can be
found in the LICENSE file.

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

//Qmitk
#include "QmitkRegistrationManipulationWidget.h"

//MatchPoint
#include <mapRegistrationManipulator.h>
#include <mapPreCachedRegistrationKernel.h>
#include <mapCombinedRegistrationKernel.h>
#include <mapNullRegistrationKernel.h>
#include <mapRegistrationCombinator.h>

#include <itkCompositeTransform.h>

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

QmitkRegistrationManipulationWidget::QmitkRegistrationManipulationWidget(QWidget *parent)
  : QWidget(parent), m_CenterOfRotationIsRelativeToTarget(false), m_internalUpdate(false)
{
  this->setupUi(this);

  connect(this->slideRotX, SIGNAL(valueChanged(int)), this, SLOT(OnRotXSlideChanged(int)));
  connect(this->sbRotX, SIGNAL(valueChanged(double)), this, SLOT(OnRotXChanged(double)));
  connect(this->slideRotY, SIGNAL(valueChanged(int)), this, SLOT(OnRotYSlideChanged(int)));
  connect(this->sbRotY, SIGNAL(valueChanged(double)), this, SLOT(OnRotYChanged(double)));
  connect(this->slideRotZ, SIGNAL(valueChanged(int)), this, SLOT(OnRotZSlideChanged(int)));
  connect(this->sbRotZ, SIGNAL(valueChanged(double)), this, SLOT(OnRotZChanged(double)));

  connect(this->slideTransX, SIGNAL(valueChanged(int)), this, SLOT(OnTransXSlideChanged(int)));
  connect(this->sbTransX, SIGNAL(valueChanged(double)), this, SLOT(OnTransXChanged(double)));
  connect(this->slideTransY, SIGNAL(valueChanged(int)), this, SLOT(OnTransYSlideChanged(int)));
  connect(this->sbTransY, SIGNAL(valueChanged(double)), this, SLOT(OnTransYChanged(double)));
  connect(this->slideTransZ, SIGNAL(valueChanged(int)), this, SLOT(OnTransZSlideChanged(int)));
  connect(this->sbTransZ, SIGNAL(valueChanged(double)), this, SLOT(OnTransZChanged(double)));

  this->groupScale->setVisible(false);
}

QmitkRegistrationManipulationWidget::~QmitkRegistrationManipulationWidget()
= default;

void QmitkRegistrationManipulationWidget::Initialize()
{
  this->ResetTransforms();
  this->InitControls();
};

void QmitkRegistrationManipulationWidget::Initialize(MAPRegistrationType* precedingRegistration)
{
  this->ResetTransforms();
  this->m_PreRegistration = precedingRegistration;

  ::map::core::RegistrationManipulator<MAPRegistrationType> manipulator(m_CurrentRegistration);
  ::map::core::PreCachedRegistrationKernel<3, 3>::Pointer kernel = ::map::core::PreCachedRegistrationKernel<3, 3>::New();

  const map::core::RegistrationKernel<3, 3>* preKernel = dynamic_cast<const map::core::RegistrationKernel<3, 3>*>(&this->m_PreRegistration->getInverseMapping());
  itk::CompositeTransform < ::map::core::continuous::ScalarType, 3>::Pointer compTransform = itk::CompositeTransform < ::map::core::continuous::ScalarType, 3>::New();
  compTransform->AddTransform(preKernel->getTransformModel()->Clone());
  compTransform->AddTransform(this->m_InverseCurrentTransform);

  kernel->setTransformModel(compTransform);
  manipulator.setInverseMapping(kernel);

  this->InitControls();
};

void QmitkRegistrationManipulationWidget::Initialize(const mitk::Point3D& movingReference, const mitk::Point3D& targetReference)
{
  this->ResetTransforms();

  auto offset = targetReference - movingReference;
  m_DirectCurrentTransform->SetOffset(offset);
  m_DirectCurrentTransform->GetInverse(m_InverseCurrentTransform);

  this->InitControls();
};

void QmitkRegistrationManipulationWidget::ResetTransforms()
{
  this->m_CenterOfRotation.Fill(0.0);
  this->m_PreRegistration = nullptr;

  this->m_InverseCurrentTransform = TransformType::New();
  this->m_InverseCurrentTransform->SetIdentity();
  this->m_DirectCurrentTransform = TransformType::New();
  this->m_DirectCurrentTransform->SetIdentity();

  m_CurrentRegistration = MAPRegistrationType::New();

  ::map::core::RegistrationManipulator<MAPRegistrationType> manipulator(m_CurrentRegistration);
  ::map::core::PreCachedRegistrationKernel<3, 3>::Pointer kernel = ::map::core::PreCachedRegistrationKernel<3, 3>::New();
  kernel->setTransformModel(m_InverseCurrentTransform);
  manipulator.setInverseMapping(kernel);

  manipulator.setDirectMapping(::map::core::NullRegistrationKernel < 3, 3 >::New());
};

void QmitkRegistrationManipulationWidget::SetCenterOfRotation(const mitk::Point3D& center)
{
  this->m_CenterOfRotation = center;
  this->ConfigureTransformCenter();
  this->UpdateTransformWidgets();
};

/** Sets the internal m_CenterOfRotationIsRelativeToTarget. see below.*/
void QmitkRegistrationManipulationWidget::SetCenterOfRotationIsRelativeToTarget(bool targetRelative)
{
  this->m_CenterOfRotationIsRelativeToTarget = targetRelative;
  this->ConfigureTransformCenter();
  this->UpdateTransformWidgets();
};

void QmitkRegistrationManipulationWidget::InitControls()
{
  this->ConfigureTransformCenter();

  //set bounds of the translation slider widget to have sensible ranges
  auto currenttrans = m_DirectCurrentTransform->GetTranslation();
  this->slideTransX->setMinimum(currenttrans[0] - 250);
  this->slideTransY->setMinimum(currenttrans[1] - 250);
  this->slideTransZ->setMinimum(currenttrans[2] - 250);
  this->slideTransX->setMaximum(currenttrans[0] + 250);
  this->slideTransY->setMaximum(currenttrans[1] + 250);
  this->slideTransZ->setMaximum(currenttrans[2] + 250);
};

void QmitkRegistrationManipulationWidget::UpdateTransformWidgets()
{
  this->m_internalUpdate = true;
  this->sbTransX->setValue(this->m_DirectCurrentTransform->GetTranslation()[0]);
  this->sbTransY->setValue(this->m_DirectCurrentTransform->GetTranslation()[1]);
  this->sbTransZ->setValue(this->m_DirectCurrentTransform->GetTranslation()[2]);
  this->slideTransX->setValue(this->m_DirectCurrentTransform->GetTranslation()[0]);
  this->slideTransY->setValue(this->m_DirectCurrentTransform->GetTranslation()[1]);
  this->slideTransZ->setValue(this->m_DirectCurrentTransform->GetTranslation()[2]);

  this->sbRotX->setValue(this->m_DirectCurrentTransform->GetAngleX()*(180 / boost::math::double_constants::pi));
  this->sbRotY->setValue(this->m_DirectCurrentTransform->GetAngleY()*(180 / boost::math::double_constants::pi));
  this->sbRotZ->setValue(this->m_DirectCurrentTransform->GetAngleZ()*(180 / boost::math::double_constants::pi));
  this->slideRotX->setValue(this->m_DirectCurrentTransform->GetAngleX()*(180 / boost::math::double_constants::pi));
  this->slideRotY->setValue(this->m_DirectCurrentTransform->GetAngleY()*(180 / boost::math::double_constants::pi));
  this->slideRotZ->setValue(this->m_DirectCurrentTransform->GetAngleZ()*(180 / boost::math::double_constants::pi));
  this->m_internalUpdate = false;
};

void QmitkRegistrationManipulationWidget::UpdateTransform(bool updateRotation)
{
  if (updateRotation)
  {
    if (this->m_CenterOfRotationIsRelativeToTarget)
    {
     ConfigureTransformCenter();
    }

    this->m_DirectCurrentTransform->SetRotation(this->sbRotX->value()*(boost::math::double_constants::pi / 180),
      this->sbRotY->value()*(boost::math::double_constants::pi / 180),
      this->sbRotZ->value()*(boost::math::double_constants::pi / 180));
  }
  else
  {
    TransformType::OutputVectorType trans;
    trans[0] = this->sbTransX->value();
    trans[1] = this->sbTransY->value();
    trans[2] = this->sbTransZ->value();

    this->m_DirectCurrentTransform->SetTranslation(trans);
  }

  this->m_DirectCurrentTransform->GetInverse(this->m_InverseCurrentTransform);

  this->UpdateTransformWidgets();

  emit RegistrationChanged(this->m_CurrentRegistration);
};

map::core::RegistrationBase* QmitkRegistrationManipulationWidget::GetInterimRegistration() const
{
  return this->m_CurrentRegistration.GetPointer();
};

map::core::RegistrationBase::Pointer QmitkRegistrationManipulationWidget::GenerateRegistration() const
{
  MAPRegistrationType::Pointer newReg = MAPRegistrationType::New();

  ::map::core::RegistrationManipulator<MAPRegistrationType> manipulator(newReg);

  ::map::core::PreCachedRegistrationKernel<3, 3>::Pointer kernel = ::map::core::PreCachedRegistrationKernel<3, 3>::New();
  kernel->setTransformModel(m_InverseCurrentTransform);

  ::map::core::PreCachedRegistrationKernel<3, 3>::Pointer kernel2 = ::map::core::PreCachedRegistrationKernel<3, 3>::New();
  kernel2->setTransformModel(m_InverseCurrentTransform->GetInverseTransform());

  manipulator.setInverseMapping(kernel);
  manipulator.setDirectMapping(kernel2);

  if (this->m_PreRegistration.IsNotNull())
  { //compine registration with selected pre registration as baseline
    typedef ::map::core::RegistrationCombinator<MAPRegistrationType, MAPRegistrationType> CombinatorType;
    CombinatorType::Pointer combinator = CombinatorType::New();
    newReg = combinator->process(*m_PreRegistration, *newReg);
  }

  return newReg.GetPointer();
};

void QmitkRegistrationManipulationWidget::OnRotXChanged(double x)
{
  if (!m_internalUpdate)
  {
    m_internalUpdate = true;
    this->slideRotX->setValue(x);
    m_internalUpdate = false;
    this->UpdateTransform(true);
  }
};

void QmitkRegistrationManipulationWidget::OnRotXSlideChanged(int x)
{
  if (!m_internalUpdate)
  {
    this->sbRotX->setValue(x);
  }
};

void QmitkRegistrationManipulationWidget::OnRotYChanged(double y)
{
  if (!m_internalUpdate)
  {
    m_internalUpdate = true;
    this->slideRotY->setValue(y);
    m_internalUpdate = false;
    this->UpdateTransform(true);
  }
};

void QmitkRegistrationManipulationWidget::OnRotYSlideChanged(int y)
{
  if (!m_internalUpdate)
  {
    this->sbRotY->setValue(y);
  }
};

void QmitkRegistrationManipulationWidget::OnRotZChanged(double z)
{
  if (!m_internalUpdate)
  {
    m_internalUpdate = true;
    this->slideRotZ->setValue(z);
    m_internalUpdate = false;
    this->UpdateTransform(true);
  }
};

void QmitkRegistrationManipulationWidget::OnRotZSlideChanged(int z)
{
  if (!m_internalUpdate)
  {
    this->sbRotZ->setValue(z);
  }
};

void QmitkRegistrationManipulationWidget::OnTransXChanged(double x)
{
  if (!m_internalUpdate)
  {
    m_internalUpdate = true;
    this->slideTransX->setValue(x);
    m_internalUpdate = false;
    this->UpdateTransform();
  }
};

void QmitkRegistrationManipulationWidget::OnTransXSlideChanged(int x)
{
  if (!m_internalUpdate)
  {
    this->sbTransX->setValue(x);
  }
};

void QmitkRegistrationManipulationWidget::OnTransYChanged(double y)
{
  if (!m_internalUpdate)
  {
    m_internalUpdate = true;
    this->slideTransY->setValue(y);
    m_internalUpdate = false;
    this->UpdateTransform();
  }
};

void QmitkRegistrationManipulationWidget::OnTransYSlideChanged(int y)
{
  if (!m_internalUpdate)
  {
    this->sbTransY->setValue(y);
  }
};

void QmitkRegistrationManipulationWidget::OnTransZChanged(double z)
{
  if (!m_internalUpdate)
  {
    m_internalUpdate = true;
    this->slideTransZ->setValue(z);
    m_internalUpdate = false;
    this->UpdateTransform();
  }
};

void QmitkRegistrationManipulationWidget::OnTransZSlideChanged(int z)
{
  if (!m_internalUpdate)
  {
    this->sbTransZ->setValue(z);
  }
};

void QmitkRegistrationManipulationWidget::ConfigureTransformCenter()
{
  auto offset = m_DirectCurrentTransform->GetOffset();

  if (this->m_CenterOfRotationIsRelativeToTarget)
  {
    auto newCenter = m_InverseCurrentTransform->TransformPoint(m_CenterOfRotation);
    m_DirectCurrentTransform->SetCenter(newCenter);
  }
  else
  {
    m_DirectCurrentTransform->SetCenter(m_CenterOfRotation);
  }

  m_DirectCurrentTransform->SetOffset(offset);
  m_DirectCurrentTransform->GetInverse(m_InverseCurrentTransform);
};


#include "callable.hpp"
#include "xforms.hpp"
#include <boost/yap/print.hpp>
using namespace boost::yap;

auto squared = [](unsigned x) { return x * x; };
auto times2  = [](unsigned x) { return 2 * x; };
auto plus1   = [](unsigned x) { return x + 1; };

int main()
{
    auto e = apply(times2) *= apply(squared);

//    auto t = transform(e, reduce_to_callable{});
//    return t(2);
}


#include "bipedal.h"
#include "recovery/ZMPFallDetector.h"

#include "utils/Kinematics.h"
#include "utils/Estimation.h"
#include "utils/Walking.h"
#include "utils/Interpolation.h"

#include <VirtualRobot/VirtualRobot.h>
#include <VirtualRobot/MathTools.h>

#include <boost/make_shared.hpp>

namespace Bipedal
{

ZMPFallDetector::ZMPFallDetector(double mass, double gravity,
                                 const VirtualRobot::RobotNodePtr& leftFoot,
                                 const VirtualRobot::RobotNodePtr& rightFoot,
                                 const VirtualRobot::ContactSensorPtr& leftFootContactSensor,
                                 const VirtualRobot::ContactSensorPtr& rightFootContactSensor,
                                 const VirtualRobot::MathTools::ConvexHull2DPtr& leftSupportHull,
                                 const VirtualRobot::MathTools::ConvexHull2DPtr& rightSupportHull)
: zmpEstimator(mass, gravity)
, leftFoot(leftFoot)
, rightFoot(rightFoot)
, leftSupportHull(leftSupportHull)
, rightSupportHull(rightSupportHull)
, supportPhaseSensor(new SupportPhaseSensor(leftFootContactSensor, rightFootContactSensor))
, maxHullDist(4)
, minFallingFrames(30)
, fallingFrameCounter(0)
, lastSupportPhase(Bipedal::SUPPORT_NONE)
, contactPoint(Eigen::Vector3f::Zero())
, falling(false)
{
}

const Eigen::Vector3f& ZMPFallDetector::getContactPoint() const { return contactPoint; }

bool ZMPFallDetector::isFalling() const
{
    return falling;
}

bool ZMPFallDetector::getStabilityInidcator(SupportPhase phase,
                                            const Eigen::Matrix4f& leftFootPose,
                                            const Eigen::Matrix4f& rightFootPose)
{
    // If not foot touches the ground, we are fucked.
    bool stillFalling = phase == SUPPORT_NONE;

    if (stillFalling)
        return true;

    const auto& supportHull = [this, phase, &leftFootPose, &rightFootPose]() {
        if (phase == Bipedal::SUPPORT_LEFT)
            return leftSupportHull;
        if (phase == Bipedal::SUPPORT_RIGHT)
            return rightSupportHull;

        BOOST_ASSERT(phase == Bipedal::SUPPORT_BOTH);

        // Foot positions changed
        //if (lastSupportPhase != phase)
        {
            dualSupportHull = computeSupportPolygone(leftFootPose,
                                                     rightFootPose,
                                                     leftSupportHull,
                                                     rightSupportHull,
                                                     phase);
        }
        lastSupportPhase = phase;

        return dualSupportHull;
    }();
    // center of convex hull and orientation should correspond with ground frame
    const auto& groundFrame = Bipedal::computeGroundFrame(leftFootPose, rightFootPose, phase);
    Eigen::Vector3f zmp = Eigen::Vector3f::Zero();
    zmp.head(2) = zmpEstimator.estimation * 1000;

    Eigen::Vector3f zmpConvexHull = VirtualRobot::MathTools::transformPosition(zmp, groundFrame.inverse());

    // if zmp is inside of the CH, we are not falling
    if (VirtualRobot::MathTools::isInside(zmpConvexHull.head(2), supportHull))
    {
        stillFalling = false;
        //std::cout << "(" << phase << ") ZMP is inside the CH. " << std::endl;
    }
    else
    {
        Eigen::Vector3f contact = Eigen::Vector3f::Zero();
        contact.head(2) = Bipedal::computeHullContactPoint(zmpConvexHull.head(2), supportHull);

        contactPoint = VirtualRobot::MathTools::transformPosition(contact, groundFrame);

        double dist = (contact-zmpConvexHull).norm();
        //std::cout << "(" << phase << ") ZMP is outside the CH: " << dist << std::endl;
        stillFalling = dist > maxHullDist;
    }

    return stillFalling;
}

void ZMPFallDetector::update(const Eigen::Vector3f& com,
                             const Eigen::Vector3f& comVel,
                             const Eigen::Vector3f& linearMomentum,
                             const Eigen::Vector3f& angularMomentum,
                             double dt)
{
    supportPhaseSensor->update(dt);
    SupportPhase phase = supportPhaseSensor->phase;

    // update the dynamics estimation
    zmpEstimator.update(com/1000.0,
                        linearMomentum,
                        angularMomentum,
                        dt);

    bool stillFalling = getStabilityInidcator(phase,
                                              leftFoot->getGlobalPose(),
                                              rightFoot->getGlobalPose());

    if (stillFalling && !falling)
    {
        fallingFrameCounter++;
        if (fallingFrameCounter > minFallingFrames)
        {
            falling = true;
        }
    }

    if (!stillFalling)
    {
        if (fallingFrameCounter > fallingFrameCounter/2)
        {
            fallingFrameCounter -= 2;
        }
        else
        {
            fallingFrameCounter = 0;
            falling = false;
        }
    }
}

}


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

#ifndef __pinocchio_lie_group_collection_hpp__
#define __pinocchio_lie_group_collection_hpp__

#include "pinocchio/multibody/liegroup/vector-space.hpp"
#include "pinocchio/multibody/liegroup/cartesian-product.hpp"
#include "pinocchio/multibody/liegroup/special-orthogonal.hpp"
#include "pinocchio/multibody/liegroup/special-euclidean.hpp"

#include <boost/variant.hpp>

namespace pinocchio
{
  template<typename _Scalar, int _Options = 0>
  struct LieGroupCollectionDefaultTpl
  {
    typedef _Scalar Scalar;
    enum { Options = _Options };

    typedef boost::variant<
     SpecialOrthogonalOperationTpl<2,Scalar,Options>
    ,SpecialOrthogonalOperationTpl<3,Scalar,Options>
    ,SpecialEuclideanOperationTpl<2,Scalar,Options>
    ,SpecialEuclideanOperationTpl<3,Scalar,Options>
    ,VectorSpaceOperationTpl<1,Scalar,Options>
    ,VectorSpaceOperationTpl<2,Scalar,Options>
    ,VectorSpaceOperationTpl<3,Scalar,Options>
    ,VectorSpaceOperationTpl<Eigen::Dynamic,Scalar,Options>
    > LieGroupVariant;

  };

  typedef LieGroupCollectionDefaultTpl<double> LieGroupCollectionDefault;

}

#endif // ifndef __pinocchio_lie_group_collection_hpp__


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
//
// Distributed under the Boost Software License, Version 1.0
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_MD5_FUNCTIONS_HPP
#define CRYPTO3_MD5_FUNCTIONS_HPP

#include <boost/crypto3/detail/basic_functions.hpp>

namespace boost {
    namespace crypto3 {
        namespace block {
            namespace detail {
                struct md5_functions : public ::boost::crypto3::detail::basic_functions<32> {
                    static word_type ff(word_type x, word_type y, word_type z) {
                        return (x & y) | (~x & z);
                    }

                    static word_type gg(word_type x, word_type y, word_type z) {
                        return (x & z) | (y & ~z);
                        // return F(z, x, y);
                    }

                    static word_type hh(word_type x, word_type y, word_type z) {
                        return x ^ y ^ z;
                    }

                    static word_type ii(word_type x, word_type y, word_type z) {
                        return y ^ (x | ~z);
                    }
                };
            }    // namespace detail
        }        // namespace block
    }            // namespace crypto3
}    // namespace boost

#endif    // CRYPTO3_MD5_FUNCTIONS_HPP


#ifndef BOOST_GEOMETRY_PROJECTIONS_SCONICS_HPP
#define BOOST_GEOMETRY_PROJECTIONS_SCONICS_HPP

// Boost.Geometry - extensions-gis-projections (based on PROJ4)
// This file is automatically generated. DO NOT EDIT.

// Copyright (c) 2008-2012 Barend Gehrels, Amsterdam, the Netherlands.

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

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Original copyright notice:

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#include <boost/concept_check.hpp>
#include <boost/math/special_functions/hypot.hpp>

#include <boost/geometry/extensions/gis/projections/impl/base_static.hpp>
#include <boost/geometry/extensions/gis/projections/impl/base_dynamic.hpp>
#include <boost/geometry/extensions/gis/projections/impl/projects.hpp>
#include <boost/geometry/extensions/gis/projections/impl/factory_entry.hpp>

namespace boost { namespace geometry { namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace sconics{
            static const int EULER = 0;
            static const int MURD1 = 1;
            static const int MURD2 = 2;
            static const int MURD3 = 3;
            static const int PCONIC = 4;
            static const int TISSOT = 5;
            static const int VITK1 = 6;
            static const double EPS10 = 1.e-10;
            static const double EPS = 1e-10;

            struct par_sconics
            {
                double    n;
                double    rho_c;
                double    rho_0;
                double    sig;
                double    c1, c2;
                int        type;
            };
            /* get common factors for simple conics */
            template <typename Parameters>
                inline int
            phi12(Parameters& par, par_sconics& proj_parm, double *del) {
                double p1, p2;
                int err = 0;

                if (!pj_param(par.params, "tlat_1").i ||
                    !pj_param(par.params, "tlat_2").i) {
                    err = -41;
                } else {
                    p1 = pj_param(par.params, "rlat_1").f;
                    p2 = pj_param(par.params, "rlat_2").f;
                    *del = 0.5 * (p2 - p1);
                    proj_parm.sig = 0.5 * (p2 + p1);
                    err = (fabs(*del) < EPS || fabs(proj_parm.sig) < EPS) ? -42 : 0;
                    *del = *del;
                }
                return err;
            }

            // template class, using CRTP to implement forward/inverse
            template <typename Geographic, typename Cartesian, typename Parameters>
            struct base_sconics_spheroid : public base_t_fi<base_sconics_spheroid<Geographic, Cartesian, Parameters>,
                     Geographic, Cartesian, Parameters>
            {

                 typedef double geographic_type;
                 typedef double cartesian_type;

                par_sconics m_proj_parm;

                inline base_sconics_spheroid(const Parameters& par)
                    : base_t_fi<base_sconics_spheroid<Geographic, Cartesian, Parameters>,
                     Geographic, Cartesian, Parameters>(*this, par) {}

                inline void fwd(geographic_type& lp_lon, geographic_type& lp_lat, cartesian_type& xy_x, cartesian_type& xy_y) const
                {
                    double rho;

                    switch (this->m_proj_parm.type) {
                    case MURD2:
                        rho = this->m_proj_parm.rho_c + tan(this->m_proj_parm.sig - lp_lat);
                        break;
                    case PCONIC:
                        rho = this->m_proj_parm.c2 * (this->m_proj_parm.c1 - tan(lp_lat - this->m_proj_parm.sig));
                        // rho = this->m_proj_parm.c2 * (this->m_proj_parm.c1 - tan(lp_lat)); BUG STILL IN proj (reported 2012-03-03)
                        break;
                    default:
                        rho = this->m_proj_parm.rho_c - lp_lat;
                        break;
                    }
                    xy_x = rho * sin( lp_lon *= this->m_proj_parm.n );
                    xy_y = this->m_proj_parm.rho_0 - rho * cos(lp_lon);
                }

                inline void inv(cartesian_type& xy_x, cartesian_type& xy_y, geographic_type& lp_lon, geographic_type& lp_lat) const
                {
                    double rho;

                    rho = boost::math::hypot(xy_x, xy_y = this->m_proj_parm.rho_0 - xy_y);
                    if (this->m_proj_parm.n < 0.) {
                        rho = - rho;
                        xy_x = - xy_x;
                        xy_y = - xy_y;
                    }
                    lp_lon = atan2(xy_x, xy_y) / this->m_proj_parm.n;
                    switch (this->m_proj_parm.type) {
                    case PCONIC:
                        lp_lat = atan(this->m_proj_parm.c1 - rho / this->m_proj_parm.c2) + this->m_proj_parm.sig;
                        break;
                    case MURD2:
                        lp_lat = this->m_proj_parm.sig - atan(rho - this->m_proj_parm.rho_c);
                        break;
                    default:
                        lp_lat = this->m_proj_parm.rho_c - rho;
                    }
                }
            };

            template <typename Parameters>
            void setup(Parameters& par, par_sconics& proj_parm)
            {
                boost::ignore_unused_variable_warning(par);
                boost::ignore_unused_variable_warning(proj_parm);
                double del, cs;
                int i;
                if( (i = phi12(par, proj_parm, &del)) )
                    throw proj_exception(i);
                switch (proj_parm.type) {
                case TISSOT:
                    proj_parm.n = sin(proj_parm.sig);
                    cs = cos(del);
                    proj_parm.rho_c = proj_parm.n / cs + cs / proj_parm.n;
                    proj_parm.rho_0 = sqrt((proj_parm.rho_c - 2 * sin(par.phi0))/proj_parm.n);
                    break;
                case MURD1:
                    proj_parm.rho_c = sin(del)/(del * tan(proj_parm.sig)) + proj_parm.sig;
                    proj_parm.rho_0 = proj_parm.rho_c - par.phi0;
                    proj_parm.n = sin(proj_parm.sig);
                    break;
                case MURD2:
                    proj_parm.rho_c = (cs = sqrt(cos(del))) / tan(proj_parm.sig);
                    proj_parm.rho_0 = proj_parm.rho_c + tan(proj_parm.sig - par.phi0);
                    proj_parm.n = sin(proj_parm.sig) * cs;
                    break;
                case MURD3:
                    proj_parm.rho_c = del / (tan(proj_parm.sig) * tan(del)) + proj_parm.sig;
                    proj_parm.rho_0 = proj_parm.rho_c - par.phi0;
                    proj_parm.n = sin(proj_parm.sig) * sin(del) * tan(del) / (del * del);
                    break;
                case EULER:
                    proj_parm.n = sin(proj_parm.sig) * sin(del) / del;
                    del *= 0.5;
                    proj_parm.rho_c = del / (tan(del) * tan(proj_parm.sig)) + proj_parm.sig;

                    proj_parm.rho_0 = proj_parm.rho_c - par.phi0;
                    break;
                case PCONIC:
                    proj_parm.n = sin(proj_parm.sig);
                    proj_parm.c2 = cos(del);
                    proj_parm.c1 = 1./tan(proj_parm.sig);
                    if (fabs(del = par.phi0 - proj_parm.sig) - EPS10 >= HALFPI)
                        throw proj_exception(-43);
                    proj_parm.rho_0 = proj_parm.c2 * (proj_parm.c1 - tan(del));
                    break;
                case VITK1:
                    proj_parm.n = (cs = tan(del)) * sin(proj_parm.sig) / del;
                    proj_parm.rho_c = del / (cs * tan(proj_parm.sig)) + proj_parm.sig;
                    proj_parm.rho_0 = proj_parm.rho_c - par.phi0;
                    break;
                }
                // par.inv = s_inverse;
                // par.fwd = s_forward;
                par.es = 0;
            }

            // Tissot
            template <typename Parameters>
            void setup_tissot(Parameters& par, par_sconics& proj_parm)
            {
                proj_parm.type = TISSOT;
                setup(par, proj_parm);
            }

            // Murdoch I
            template <typename Parameters>
            void setup_murd1(Parameters& par, par_sconics& proj_parm)
            {
                proj_parm.type = MURD1;
                setup(par, proj_parm);
            }

            // Murdoch II
            template <typename Parameters>
            void setup_murd2(Parameters& par, par_sconics& proj_parm)
            {
                proj_parm.type = MURD2;
                setup(par, proj_parm);
            }

            // Murdoch III
            template <typename Parameters>
            void setup_murd3(Parameters& par, par_sconics& proj_parm)
            {
                proj_parm.type = MURD3;
                setup(par, proj_parm);
            }

            // Euler
            template <typename Parameters>
            void setup_euler(Parameters& par, par_sconics& proj_parm)
            {
                proj_parm.type = EULER;
                setup(par, proj_parm);
            }

            // Perspective Conic
            template <typename Parameters>
            void setup_pconic(Parameters& par, par_sconics& proj_parm)
            {
                proj_parm.type = PCONIC;
                setup(par, proj_parm);
            }

            // Vitkovsky I
            template <typename Parameters>
            void setup_vitk1(Parameters& par, par_sconics& proj_parm)
            {
                proj_parm.type = VITK1;
                setup(par, proj_parm);
            }

        }} // namespace detail::sconics
    #endif // doxygen

    /*!
        \brief Tissot projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
         - lat_1= and lat_2=
        \par Example
        \image html ex_tissot.gif
    */
    template <typename Geographic, typename Cartesian, typename Parameters = parameters>
    struct tissot_spheroid : public detail::sconics::base_sconics_spheroid<Geographic, Cartesian, Parameters>
    {
        inline tissot_spheroid(const Parameters& par) : detail::sconics::base_sconics_spheroid<Geographic, Cartesian, Parameters>(par)
        {
            detail::sconics::setup_tissot(this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Murdoch I projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
         - lat_1= and lat_2=
        \par Example
        \image html ex_murd1.gif
    */
    template <typename Geographic, typename Cartesian, typename Parameters = parameters>
    struct murd1_spheroid : public detail::sconics::base_sconics_spheroid<Geographic, Cartesian, Parameters>
    {
        inline murd1_spheroid(const Parameters& par) : detail::sconics::base_sconics_spheroid<Geographic, Cartesian, Parameters>(par)
        {
            detail::sconics::setup_murd1(this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Murdoch II projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
         - lat_1= and lat_2=
        \par Example
        \image html ex_murd2.gif
    */
    template <typename Geographic, typename Cartesian, typename Parameters = parameters>
    struct murd2_spheroid : public detail::sconics::base_sconics_spheroid<Geographic, Cartesian, Parameters>
    {
        inline murd2_spheroid(const Parameters& par) : detail::sconics::base_sconics_spheroid<Geographic, Cartesian, Parameters>(par)
        {
            detail::sconics::setup_murd2(this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Murdoch III projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
         - lat_1= and lat_2=
        \par Example
        \image html ex_murd3.gif
    */
    template <typename Geographic, typename Cartesian, typename Parameters = parameters>
    struct murd3_spheroid : public detail::sconics::base_sconics_spheroid<Geographic, Cartesian, Parameters>
    {
        inline murd3_spheroid(const Parameters& par) : detail::sconics::base_sconics_spheroid<Geographic, Cartesian, Parameters>(par)
        {
            detail::sconics::setup_murd3(this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Euler projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
         - lat_1= and lat_2=
        \par Example
        \image html ex_euler.gif
    */
    template <typename Geographic, typename Cartesian, typename Parameters = parameters>
    struct euler_spheroid : public detail::sconics::base_sconics_spheroid<Geographic, Cartesian, Parameters>
    {
        inline euler_spheroid(const Parameters& par) : detail::sconics::base_sconics_spheroid<Geographic, Cartesian, Parameters>(par)
        {
            detail::sconics::setup_euler(this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Perspective Conic projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
         - lat_1= and lat_2=
        \par Example
        \image html ex_pconic.gif
    */
    template <typename Geographic, typename Cartesian, typename Parameters = parameters>
    struct pconic_spheroid : public detail::sconics::base_sconics_spheroid<Geographic, Cartesian, Parameters>
    {
        inline pconic_spheroid(const Parameters& par) : detail::sconics::base_sconics_spheroid<Geographic, Cartesian, Parameters>(par)
        {
            detail::sconics::setup_pconic(this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Vitkovsky I projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
         - lat_1= and lat_2=
        \par Example
        \image html ex_vitk1.gif
    */
    template <typename Geographic, typename Cartesian, typename Parameters = parameters>
    struct vitk1_spheroid : public detail::sconics::base_sconics_spheroid<Geographic, Cartesian, Parameters>
    {
        inline vitk1_spheroid(const Parameters& par) : detail::sconics::base_sconics_spheroid<Geographic, Cartesian, Parameters>(par)
        {
            detail::sconics::setup_vitk1(this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Factory entry(s)
        template <typename Geographic, typename Cartesian, typename Parameters>
        class tissot_entry : public detail::factory_entry<Geographic, Cartesian, Parameters>
        {
            public :
                virtual projection<Geographic, Cartesian>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<tissot_spheroid<Geographic, Cartesian, Parameters>, Geographic, Cartesian, Parameters>(par);
                }
        };

        template <typename Geographic, typename Cartesian, typename Parameters>
        class murd1_entry : public detail::factory_entry<Geographic, Cartesian, Parameters>
        {
            public :
                virtual projection<Geographic, Cartesian>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<murd1_spheroid<Geographic, Cartesian, Parameters>, Geographic, Cartesian, Parameters>(par);
                }
        };

        template <typename Geographic, typename Cartesian, typename Parameters>
        class murd2_entry : public detail::factory_entry<Geographic, Cartesian, Parameters>
        {
            public :
                virtual projection<Geographic, Cartesian>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<murd2_spheroid<Geographic, Cartesian, Parameters>, Geographic, Cartesian, Parameters>(par);
                }
        };

        template <typename Geographic, typename Cartesian, typename Parameters>
        class murd3_entry : public detail::factory_entry<Geographic, Cartesian, Parameters>
        {
            public :
                virtual projection<Geographic, Cartesian>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<murd3_spheroid<Geographic, Cartesian, Parameters>, Geographic, Cartesian, Parameters>(par);
                }
        };

        template <typename Geographic, typename Cartesian, typename Parameters>
        class euler_entry : public detail::factory_entry<Geographic, Cartesian, Parameters>
        {
            public :
                virtual projection<Geographic, Cartesian>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<euler_spheroid<Geographic, Cartesian, Parameters>, Geographic, Cartesian, Parameters>(par);
                }
        };

        template <typename Geographic, typename Cartesian, typename Parameters>
        class pconic_entry : public detail::factory_entry<Geographic, Cartesian, Parameters>
        {
            public :
                virtual projection<Geographic, Cartesian>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<pconic_spheroid<Geographic, Cartesian, Parameters>, Geographic, Cartesian, Parameters>(par);
                }
        };

        template <typename Geographic, typename Cartesian, typename Parameters>
        class vitk1_entry : public detail::factory_entry<Geographic, Cartesian, Parameters>
        {
            public :
                virtual projection<Geographic, Cartesian>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<vitk1_spheroid<Geographic, Cartesian, Parameters>, Geographic, Cartesian, Parameters>(par);
                }
        };

        template <typename Geographic, typename Cartesian, typename Parameters>
        inline void sconics_init(detail::base_factory<Geographic, Cartesian, Parameters>& factory)
        {
            factory.add_to_factory("tissot", new tissot_entry<Geographic, Cartesian, Parameters>);
            factory.add_to_factory("murd1", new murd1_entry<Geographic, Cartesian, Parameters>);
            factory.add_to_factory("murd2", new murd2_entry<Geographic, Cartesian, Parameters>);
            factory.add_to_factory("murd3", new murd3_entry<Geographic, Cartesian, Parameters>);
            factory.add_to_factory("euler", new euler_entry<Geographic, Cartesian, Parameters>);
            factory.add_to_factory("pconic", new pconic_entry<Geographic, Cartesian, Parameters>);
            factory.add_to_factory("vitk1", new vitk1_entry<Geographic, Cartesian, Parameters>);
        }

    } // namespace detail
    #endif // doxygen

}}} // namespace boost::geometry::projections

#endif // BOOST_GEOMETRY_PROJECTIONS_SCONICS_HPP


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

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

#include <boost/simd/detail/overload.hpp>
#include <boost/simd/function/saturated.hpp>
#include <boost/simd/detail/dispatch/meta/as_integer.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;
  namespace bs = boost::simd;

  BOOST_DISPATCH_OVERLOAD ( toint_
                          , (typename A0)
                          , bs::vmx_
                          , bs::saturated_tag
                          , bs::pack_< bd::single_<A0>, bs::vmx_>
                          )
  {
    using result =  bd::as_integer_t<A0>;

    BOOST_FORCEINLINE result operator()(bs::saturated_tag const&, const A0& a0) const BOOST_NOEXCEPT
    {
      return vec_cts(a0.storage(),0);
    }
  };
} } }

#endif


#include "./faheel/BigInt.hpp"
#include "./kasparsklavins/bigint.h"
#include "./kedixa/unsigned_bigint.h"
#include <boost/multiprecision/cpp_int.hpp>

#include <gmpxx.h>

#include <chrono>
#include <string>
#include <random>
#include <iostream>

typedef std::chrono::steady_clock Clock;
typedef std::chrono::microseconds Microsec;

#define TEST_TIMES 10
#define BASE_BIT 10000
#define MUL_BIT 1000

#define TIME_BEG last = Clock::now()
#define TIME_END(name, what)                                         \
    do                                                               \
    {                                                                \
        curr = Clock::now();                                         \
        auto ms = std::chrono::duration_cast<Microsec>(curr - last); \
        std::cout << name << " " << what << " time elapsed "         \
                  << ms.count() << "us" << std::endl;                \
    } while (0)

template <class T>
void test(const char *name,
          const std::string &base, const std::string &mul, int seed)
{
    Clock::time_point last;
    Clock::time_point curr;

    const T b(base);
    const T m(mul);

    // test string to big int
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        T v(base);
    }
    TIME_END(name, "create");

    // test add
    TIME_BEG;
    T sum;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        sum += b;
    }
    TIME_END(name, "add");

    // test dec
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        sum -= b;
    }
    TIME_END(name, "dec");

    // test mul
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        T v(base);
        v = v * m;
    }
    TIME_END(name, "mul");

    // test div
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        T v(base);
        v = v / m;
    }
    TIME_END(name, "div");

    // test to_string
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        b.to_string();
    }
    TIME_END(name, "to_string");
}

template <class T>
void test_boost(const char *name,
                const std::string &base, const std::string &mul, int seed)
{
    Clock::time_point last;
    Clock::time_point curr;

    const T b(base);
    const T m(mul);

    // test string to big int
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        T v(base);
    }
    TIME_END(name, "create");

    // test add
    TIME_BEG;
    T sum;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        sum += b;
    }
    TIME_END(name, "add");

    // test dec
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        sum -= b;
    }
    TIME_END(name, "dec");

    // test mul
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        T v(base);
        v = v * m;
    }
    TIME_END(name, "mul");

    // test div
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        T v(base);
        v = v / m;
    }
    TIME_END(name, "div");

    // test to_string
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        b.str();
    }
    TIME_END(name, "to_string");
}

void test_kasparsklavins(
    const std::string &base, const std::string &mul, int seed)
{
    Clock::time_point last;
    Clock::time_point curr;

    const char *name = "kasparsklavins";

    const Dodecahedron::Bigint b(base);
    const Dodecahedron::Bigint m(mul);

    // test string to big int
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        Dodecahedron::Bigint v(base);
    }
    TIME_END(name, "create");

    // test add
    TIME_BEG;
    Dodecahedron::Bigint sum;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        sum += b;
    }
    TIME_END(name, "add");

    // test dec
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        sum -= b;
    }
    TIME_END(name, "dec");

    // test mul
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        Dodecahedron::Bigint v(base);
        v = v * m;
    }
    TIME_END(name, "mul");
    /*
    // no division in kasparsklavins
    // test div
    TIME_BEG;
    for (int i = 0;i < TEST_TIMES;i ++)
    {
        T v(base);
        v = v / m;
    }
    TIME_END(name, "div");
*/
    // test to_string
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        Dodecahedron::to_string(b);
    }
    TIME_END(name, "to_string");
}

template <class T>
void test_gmp(const char *name,
              const std::string &base, const std::string &mul, int seed)
{
    Clock::time_point last;
    Clock::time_point curr;

    const T b(base);
    const T m(mul);

    // test string to big int
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        T v(base);
    }
    TIME_END(name, "create");

    // test add
    TIME_BEG;
    T sum;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        sum += b;
    }
    TIME_END(name, "add");

    // test dec
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        sum -= b;
    }
    TIME_END(name, "dec");

    // test mul
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        T v(base);
        v = v * m;
    }
    TIME_END(name, "mul");

    // test div
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        T v(base);
        v = v / m;
    }
    TIME_END(name, "div");

    // test to_string
    TIME_BEG;
    for (int i = 0; i < TEST_TIMES; i++)
    {
        b.get_str();
    }
    TIME_END(name, "to_string");
}

int main()
{
    // random a very big number, make sure the first one is not 0
    std::string base = "9";

    std::default_random_engine e;
    std::uniform_real_distribution<float> u(0, 9);
    for (size_t i = 0; i < BASE_BIT; ++i)
    {
        base.append(std::to_string(int(u(e))));
    }

    std::string mul = "1";
    for (size_t i = 0; i < MUL_BIT; ++i)
    {
        mul.append(std::to_string(int(u(e))));
    }

    int seed = int(u(e));

    // 测试加减法
    // 测试乘除法
    // 测试to_string

    test<BigInt>("faheel", base, mul, seed);
    // test<Dodecahedron::Bigint>("kasparsklavins", base, mul, seed);
    test_kasparsklavins(base, mul, seed);
    test<kedixa::unsigned_bigint>("kedixa", base, mul, seed);
    test_boost<boost::multiprecision::cpp_int>("boost", base, mul, seed);
    test_gmp<mpz_class>("gmp", base, mul, seed);

    std::cout << "test done, TIMES = " << TEST_TIMES
              << ", BASE BIT = " << BASE_BIT
              << ", MUL BIT = " << MUL_BIT << std::endl;

    // gmp allow this complie，but the result is incorrect
    // mpz_class b(1000000000);
    // std::cout << b << " ==>> " << b * 0.01 << std::endl;

    return 0;
}


/* Copyright (c) 2016 - 2020, the adamantine authors.
 *
 * This file is subject to the Modified BSD License and may not be distributed
 * without copyright and license information. Please refer to the file LICENSE
 * for the text and further information on this license.
 */

#ifndef ADAMANTINE_HH
#define ADAMANTINE_HH

#include <Geometry.hh>
#include <PostProcessor.hh>
#include <ThermalPhysics.hh>
#include <Timer.hh>
#include <utils.hh>

#include <deal.II/base/mpi.h>
#include <deal.II/distributed/solution_transfer.h>
#include <deal.II/grid/filtered_iterator.h>
#include <deal.II/grid/grid_refinement.h>
#include <deal.II/numerics/error_estimator.h>

#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
#include <boost/property_tree/info_parser.hpp>
#include <boost/property_tree/ptree.hpp>

#include <cmath>
#include <iostream>

template <int dim, typename MemorySpaceType,
          std::enable_if_t<
              std::is_same<MemorySpaceType, dealii::MemorySpace::Host>::value,
              int> = 0>
void output_pvtu(
    adamantine::PostProcessor<dim> &post_processor, unsigned int cycle,
    unsigned int n_time_step, double time,
    dealii::AffineConstraints<double> const &affine_constraints,
    dealii::LinearAlgebra::distributed::Vector<double, MemorySpaceType>
        &solution)
{
  affine_constraints.distribute(solution);
  post_processor.output_pvtu(cycle, n_time_step, time, solution);
}

#ifdef ADAMANTINE_HAVE_CUDA
template <int dim, typename MemorySpaceType,
          std::enable_if_t<
              std::is_same<MemorySpaceType, dealii::MemorySpace::CUDA>::value,
              int> = 0>
void output_pvtu(
    adamantine::PostProcessor<dim> &post_processor, unsigned int cycle,
    unsigned int n_time_step, double time,
    dealii::AffineConstraints<double> const &affine_constraints,
    dealii::LinearAlgebra::distributed::Vector<double, MemorySpaceType>
        &solution)
{
  dealii::LinearAlgebra::distributed::Vector<double, dealii::MemorySpace::Host>
      solution_host(solution.get_partitioner());
  solution_host.import(solution, dealii::VectorOperation::insert);
  affine_constraints.distribute(solution_host);
  post_processor.output_pvtu(cycle, n_time_step, time, solution_host);
}
#endif

template <int dim, typename MemorySpaceType,
          std::enable_if_t<
              std::is_same<MemorySpaceType, dealii::MemorySpace::Host>::value,
              int> = 0>
dealii::Vector<float> estimate_error(
    dealii::parallel::distributed::Triangulation<dim> const &triangulation,
    dealii::DoFHandler<dim> const &dof_handler, int fe_degree,
    dealii::LA::distributed::Vector<double, MemorySpaceType> const &solution)
{
  dealii::Vector<float> estimated_error_per_cell(
      triangulation.n_active_cells());
  dealii::KellyErrorEstimator<dim>::estimate(
      dof_handler, dealii::QGauss<dim - 1>(fe_degree + 1),
      std::map<dealii::types::boundary_id,
               const dealii::Function<dim, double> *>(),
      solution, estimated_error_per_cell, dealii::ComponentMask(), nullptr, 0,
      triangulation.locally_owned_subdomain());

  return estimated_error_per_cell;
}

#ifdef ADAMANTINE_HAVE_CUDA
template <int dim, typename MemorySpaceType,
          std::enable_if_t<
              std::is_same<MemorySpaceType, dealii::MemorySpace::CUDA>::value,
              int> = 0>
dealii::Vector<float> estimate_error(
    dealii::parallel::distributed::Triangulation<dim> const &triangulation,
    dealii::DoFHandler<dim> const &dof_handler, int fe_degree,
    dealii::LA::distributed::Vector<double, MemorySpaceType> const &solution)
{
  dealii::LA::distributed::Vector<double, dealii::MemorySpace::Host>
      solution_host(solution.get_partitioner());
  solution_host.import(solution, dealii::VectorOperation::insert);
  dealii::Vector<float> estimated_error_per_cell(
      triangulation.n_active_cells());
  dealii::KellyErrorEstimator<dim>::estimate(
      dof_handler, dealii::QGauss<dim - 1>(fe_degree + 1),
      std::map<dealii::types::boundary_id,
               const dealii::Function<dim, double> *>(),
      solution_host, estimated_error_per_cell, dealii::ComponentMask(), nullptr,
      0, triangulation.locally_owned_subdomain());

  return estimated_error_per_cell;
}
#endif

// inlining this function so we can have in the header
inline void initialize_timers(MPI_Comm const &communicator,
                              std::vector<adamantine::Timer> &timers)
{
  timers.push_back(adamantine::Timer(communicator, "Main"));
  timers.push_back(adamantine::Timer(communicator, "Refinement"));
  timers.push_back(adamantine::Timer(communicator, "Evolve One Time Step"));
  timers.push_back(adamantine::Timer(
      communicator, "Evolve One Time Step: evaluate_thermal_physics"));
  timers.push_back(adamantine::Timer(
      communicator, "Evolve One Time Step: id_minus_tau_J_inverse"));
  timers.push_back(adamantine::Timer(
      communicator, "Evolve One Time Step: evaluate_material_properties"));
}

template <int dim, int fe_degree, typename MemorySpaceType,
          typename QuadratureType>
std::vector<std::unique_ptr<adamantine::ElectronBeam<dim>>> &initialize(
    MPI_Comm const &communicator, boost::property_tree::ptree const &database,
    adamantine::Geometry<dim> &geometry,
    std::unique_ptr<adamantine::Physics<dim, MemorySpaceType>> &thermal_physics)
{
  thermal_physics.reset(
      new adamantine::ThermalPhysics<dim, fe_degree, MemorySpaceType,
                                     QuadratureType>(communicator, database,
                                                     geometry));
  return static_cast<adamantine::ThermalPhysics<dim, fe_degree, MemorySpaceType,
                                                QuadratureType> *>(
             thermal_physics.get())
      ->get_electron_beams();
}

template <int dim, int fe_degree, typename MemorySpaceType>
std::vector<std::unique_ptr<adamantine::ElectronBeam<dim>>> &
initialize_quadrature(
    std::string const &quadrature_type, MPI_Comm const &communicator,
    boost::property_tree::ptree const &database,
    adamantine::Geometry<dim> &geometry,
    std::unique_ptr<adamantine::Physics<dim, MemorySpaceType>> &thermal_physics)
{
  if (quadrature_type.compare("gauss") == 0)
    return initialize<dim, fe_degree, MemorySpaceType, dealii::QGauss<1>>(
        communicator, database, geometry, thermal_physics);
  else
  {
    adamantine::ASSERT_THROW(quadrature_type.compare("lobatto") == 0,
                             "quadrature should be Gauss or Lobatto.");
    return initialize<dim, fe_degree, MemorySpaceType,
                      dealii::QGaussLobatto<1>>(communicator, database,
                                                geometry, thermal_physics);
  }
}

template <int dim, typename MemorySpaceType>
std::vector<std::unique_ptr<adamantine::ElectronBeam<dim>>> &
initialize_thermal_physics(
    unsigned int fe_degree, std::string const &quadrature_type,
    MPI_Comm const &communicator, boost::property_tree::ptree const &database,
    adamantine::Geometry<dim> &geometry,
    std::unique_ptr<adamantine::Physics<dim, MemorySpaceType>> &thermal_physics)
{
  switch (fe_degree)
  {
  case 1:
  {
    return initialize_quadrature<dim, 1, MemorySpaceType>(
        quadrature_type, communicator, database, geometry, thermal_physics);
  }
  case 2:
  {
    return initialize_quadrature<dim, 2, MemorySpaceType>(
        quadrature_type, communicator, database, geometry, thermal_physics);
  }
  case 3:
  {
    return initialize_quadrature<dim, 3, MemorySpaceType>(
        quadrature_type, communicator, database, geometry, thermal_physics);
  }
  case 4:
  {
    return initialize_quadrature<dim, 4, MemorySpaceType>(
        quadrature_type, communicator, database, geometry, thermal_physics);
  }
  case 5:
  {
    return initialize_quadrature<dim, 5, MemorySpaceType>(
        quadrature_type, communicator, database, geometry, thermal_physics);
  }
  case 6:
  {
    return initialize_quadrature<dim, 6, MemorySpaceType>(
        quadrature_type, communicator, database, geometry, thermal_physics);
  }
  case 7:
  {
    return initialize_quadrature<dim, 7, MemorySpaceType>(
        quadrature_type, communicator, database, geometry, thermal_physics);
  }
  case 8:
  {
    return initialize_quadrature<dim, 8, MemorySpaceType>(
        quadrature_type, communicator, database, geometry, thermal_physics);
  }
  case 9:
  {
    return initialize_quadrature<dim, 9, MemorySpaceType>(
        quadrature_type, communicator, database, geometry, thermal_physics);
  }
  default:
  {
    adamantine::ASSERT_THROW(fe_degree == 10,
                             "fe_degree should be between 1 and 10.");
    return initialize_quadrature<dim, 10, MemorySpaceType>(
        quadrature_type, communicator, database, geometry, thermal_physics);
  }
  }
}

template <int dim, typename MemorySpaceType,
          std::enable_if_t<
              std::is_same<MemorySpaceType, dealii::MemorySpace::Host>::value,
              int> = 0>
void refine_and_transfer(
    std::unique_ptr<adamantine::Physics<dim, MemorySpaceType>> &thermal_physics,
    dealii::DoFHandler<dim> &dof_handler,
    dealii::LA::distributed::Vector<double, MemorySpaceType> &solution)
{
  dealii::parallel::distributed::Triangulation<dim> &triangulation =
      dynamic_cast<dealii::parallel::distributed::Triangulation<dim> &>(
          const_cast<dealii::Triangulation<dim> &>(
              dof_handler.get_triangulation()));

  std::shared_ptr<adamantine::MaterialProperty<dim>> material_property =
      thermal_physics->get_material_property();

  dealii::parallel::distributed::SolutionTransfer<
      dim, dealii::LA::distributed::Vector<double, MemorySpaceType>>
      solution_transfer(dof_handler);
  std::vector<dealii::parallel::distributed::SolutionTransfer<
      dim, dealii::LA::distributed::Vector<double, MemorySpaceType>>>
  material_state(
      static_cast<unsigned int>(adamantine::MaterialState::SIZE),
      dealii::parallel::distributed::SolutionTransfer<
          dim, dealii::LA::distributed::Vector<double, MemorySpaceType>>(
          material_property->get_dof_handler()));

  // We need to update the ghost values before we can do the interpolation on
  // the new mesh.
  solution.update_ghost_values();

  // Prepare the Triangulation and the diffent SolutionTransfers for refinement
  triangulation.prepare_coarsening_and_refinement();
  solution_transfer.prepare_for_coarsening_and_refinement(solution);
  for (unsigned int i = 0;
       i < static_cast<unsigned int>(adamantine::MaterialState::SIZE); ++i)
    material_state[i].prepare_for_coarsening_and_refinement(
        material_property->get_state()[i]);

  // Execute the refinement
  triangulation.execute_coarsening_and_refinement();

  // Update the AffineConstraints and resize the solution
  thermal_physics->setup_dofs();
  thermal_physics->initialize_dof_vector(solution);

  // Update MaterialProperty DoFHandler and resize the state vectors
  material_property->reinit_dofs();

  // Interpolate the solution and the state onto the new mesh
  solution_transfer.interpolate(solution);
  for (unsigned int i = 0;
       i < static_cast<unsigned int>(adamantine::MaterialState::SIZE); ++i)
    material_state[i].interpolate(material_property->get_state()[i]);

#if ADAMANTINE_DEBUG
  // Check that we are not losing material
  std::array<dealii::LA::distributed::Vector<double, dealii::MemorySpace::Host>,
             static_cast<unsigned int>(adamantine::MaterialState::SIZE)>
      state = material_property->get_state();
  unsigned int const local_size = state[0].local_size();
  unsigned int constexpr n_material_states =
      static_cast<unsigned int>(adamantine::MaterialState::SIZE);
  for (unsigned int i = 0; i < local_size; ++i)
  {
    double material_ratio = 0.;
    for (unsigned int j = 0; j < n_material_states; ++j)
      material_ratio += state[j].local_element(i);
    adamantine::ASSERT(std::abs(material_ratio - 1.) < 1e-14,
                       "Material is lost.");
  }
#endif
}

#ifdef ADAMANTINE_HAVE_CUDA
template <int dim, typename MemorySpaceType,
          std::enable_if_t<
              std::is_same<MemorySpaceType, dealii::MemorySpace::CUDA>::value,
              int> = 0>
void refine_and_transfer(
    std::unique_ptr<adamantine::Physics<dim, MemorySpaceType>> &thermal_physics,
    dealii::DoFHandler<dim> &dof_handler,
    dealii::LA::distributed::Vector<double, MemorySpaceType> &solution)
{
  dealii::parallel::distributed::Triangulation<dim> &triangulation =
      dynamic_cast<dealii::parallel::distributed::Triangulation<dim> &>(
          const_cast<dealii::Triangulation<dim> &>(
              dof_handler.get_triangulation()));

  std::shared_ptr<adamantine::MaterialProperty<dim>> material_property =
      thermal_physics->get_material_property();

  dealii::parallel::distributed::SolutionTransfer<
      dim, dealii::LA::distributed::Vector<double, dealii::MemorySpace::Host>>
      solution_transfer(dof_handler);
  std::vector<dealii::parallel::distributed::SolutionTransfer<
      dim, dealii::LA::distributed::Vector<double, dealii::MemorySpace::Host>>>
  material_state(
      static_cast<unsigned int>(adamantine::MaterialState::SIZE),
      dealii::parallel::distributed::SolutionTransfer<
          dim,
          dealii::LA::distributed::Vector<double, dealii::MemorySpace::Host>>(
          material_property->get_dof_handler()));

  // We need to update the ghost values before we can do the interpolation on
  // the new mesh.
  solution.update_ghost_values();

  // Prepare the Triangulation and the diffent SolutionTransfers for refinement
  triangulation.prepare_coarsening_and_refinement();
  dealii::LA::distributed::Vector<double, dealii::MemorySpace::Host>
      solution_host(solution.get_partitioner());
  solution_host.import(solution, dealii::VectorOperation::insert);
  solution_transfer.prepare_for_coarsening_and_refinement(solution_host);
  for (unsigned int i = 0;
       i < static_cast<unsigned int>(adamantine::MaterialState::SIZE); ++i)
    material_state[i].prepare_for_coarsening_and_refinement(
        material_property->get_state()[i]);

  // Execute the refinement
  triangulation.execute_coarsening_and_refinement();

  // Update MaterialProperty DoFHandler and resize the state vectors
  material_property->reinit_dofs();

  // Update the AffineConstraints and resize the solution
  thermal_physics->setup_dofs();
  thermal_physics->initialize_dof_vector(solution);
  solution_host.reinit(solution.get_partitioner());

  // Interpolate the solution and the state onto the new mesh
  solution_transfer.interpolate(solution_host);
  solution.import(solution_host, dealii::VectorOperation::insert);
  for (unsigned int i = 0;
       i < static_cast<unsigned int>(adamantine::MaterialState::SIZE); ++i)
    material_state[i].interpolate(material_property->get_state()[i]);

#if ADAMANTINE_DEBUG
  // Check that we are not losing material
  std::array<dealii::LA::distributed::Vector<double, dealii::MemorySpace::Host>,
             static_cast<unsigned int>(adamantine::MaterialState::SIZE)>
      state = material_property->get_state();
  unsigned int const local_size = state[0].local_size();
  unsigned int constexpr n_material_states =
      static_cast<unsigned int>(adamantine::MaterialState::SIZE);
  for (unsigned int i = 0; i < local_size; ++i)
  {
    double material_ratio = 0.;
    for (unsigned int j = 0; j < n_material_states; ++j)
      material_ratio += state[j].local_element(i);
    adamantine::ASSERT(std::abs(material_ratio - 1.) < 1e-14,
                       "Material is lost.");
  }
#endif
}
#endif

template <int dim>
std::vector<typename dealii::parallel::distributed::Triangulation<
    dim>::active_cell_iterator>
compute_cells_to_refine(
    dealii::parallel::distributed::Triangulation<dim> &triangulation,
    double const time, double const next_refinement_time,
    unsigned int const n_time_steps,
    std::vector<std::unique_ptr<adamantine::ElectronBeam<dim>>> &electron_beams)
{
  // The save_time()/rewind_time() functions are used to save the current time
  // and row in the list of beam positions. It does nothing when the position is
  // given by an analytic formula.
  for (auto &beam : electron_beams)
    beam->save_time();

  // Compute the position of the beams between time and next_refinement_time and
  // refine the mesh where the source is greater than 1e-15. This cut-off is due
  // to the fact that the source is gaussian and thus never strictly zero. If
  // the beams intersect, some cells will appear twice in the vector. This is
  // not a problem.
  std::vector<typename dealii::parallel::distributed::Triangulation<
      dim>::active_cell_iterator>
      cells_to_refine;
  for (unsigned int i = 0; i < n_time_steps; ++i)
  {
    double const current_time = time + static_cast<double>(i) /
                                           static_cast<double>(n_time_steps) *
                                           (next_refinement_time - time);
    for (auto &beam : electron_beams)
    {
      beam->set_time(current_time);
      for (auto cell : dealii::filter_iterators(
               triangulation.active_cell_iterators(),
               dealii::IteratorFilters::LocallyOwnedCell()))
        if (beam->value(cell->center()) > 1e-15)
          cells_to_refine.push_back(cell);
    }
  }

  for (auto &beam : electron_beams)
    beam->rewind_time();

  return cells_to_refine;
}

template <int dim, int fe_degree, typename MemorySpaceType>
void refine_mesh(
    std::unique_ptr<adamantine::Physics<dim, MemorySpaceType>> &thermal_physics,
    dealii::LA::distributed::Vector<double, MemorySpaceType> &solution,
    std::vector<std::unique_ptr<adamantine::ElectronBeam<dim>>> &electron_beams,
    double const time, double const next_refinement_time,
    unsigned int const time_steps_refinement,
    boost::property_tree::ptree const &refinement_database)
{
  dealii::DoFHandler<dim> &dof_handler = thermal_physics->get_dof_handler();
  // Use the Kelly error estimator to refine the mesh. This is done so that the
  // part of the domain that were heated stay refined.
  unsigned int const n_kelly_refinements =
      refinement_database.get("n_heat_refinements", 2);
  double coarsening_fraction = 0.3;
  double refining_fraction = 0.6;
  double cells_fraction = refinement_database.get("heat_cell_ratio", 1.);
  dealii::parallel::distributed::Triangulation<dim> &triangulation =
      dynamic_cast<dealii::parallel::distributed::Triangulation<dim> &>(
          const_cast<dealii::Triangulation<dim> &>(
              dof_handler.get_triangulation()));
  // Number of times the mesh on the beam paths will be refined and maximum
  // number of time a cell can be refined.
  unsigned int const n_beam_refinements =
      refinement_database.get("n_beam_refinements", 2);
  int max_level = refinement_database.get<int>("max_level");
  for (unsigned int i = 0; i < n_kelly_refinements; ++i)
  {
    // Estimate the error. For simplicity, always use dealii::QGauss
    dealii::Vector<float> estimated_error_per_cell =
        estimate_error(triangulation, dof_handler, fe_degree, solution);

    // Flag the cells for refinement.
    unsigned int new_n_cells = static_cast<unsigned int>(
        cells_fraction *
        static_cast<double>(triangulation.n_global_active_cells()));
    dealii::GridRefinement::refine_and_coarsen_fixed_fraction(
        triangulation, estimated_error_per_cell, refining_fraction,
        coarsening_fraction, new_n_cells);

    // Don't refine cells that are already as much refined as it is allowed.
    for (auto cell :
         dealii::filter_iterators(triangulation.active_cell_iterators(),
                                  dealii::IteratorFilters::LocallyOwnedCell()))
      if (cell->level() >= max_level)
        cell->clear_refine_flag();

    // Execute the refinement and transfer the solution onto the new mesh.
    refine_and_transfer(thermal_physics, dof_handler, solution);
  }

  // Refine the mesh along the trajectory of the sources.
  for (unsigned int i = 0; i < n_beam_refinements; ++i)
  {
    // Compute the cells to be refined.
    std::vector<typename dealii::parallel::distributed::Triangulation<
        dim>::active_cell_iterator>
        cells_to_refine =
            compute_cells_to_refine(triangulation, time, next_refinement_time,
                                    time_steps_refinement, electron_beams);

    // Flag the cells for refinement.
    for (auto &cell : cells_to_refine)
      if (cell->level() < max_level)
        cell->set_refine_flag();

    // Execute the refinement and transfer the solution onto the new mesh.
    refine_and_transfer(thermal_physics, dof_handler, solution);
  }

  // Recompute the inverse of the mass matrix
  thermal_physics->compute_inverse_mass_matrix();
}

template <int dim, typename MemorySpaceType>
void refine_mesh(
    std::unique_ptr<adamantine::Physics<dim, MemorySpaceType>> &thermal_physics,
    dealii::LA::distributed::Vector<double, MemorySpaceType> &solution,
    std::vector<std::unique_ptr<adamantine::ElectronBeam<dim>>> &electron_beams,
    double const time, double const next_refinement_time,
    unsigned int const time_steps_refinement,
    boost::property_tree::ptree const &refinement_database,
    unsigned int const fe_degree)
{
  switch (fe_degree)
  {
  case 1:
  {
    refine_mesh<dim, 1>(thermal_physics, solution, electron_beams, time,
                        next_refinement_time, time_steps_refinement,
                        refinement_database);
    break;
  }
  case 2:
  {
    refine_mesh<dim, 2>(thermal_physics, solution, electron_beams, time,
                        next_refinement_time, time_steps_refinement,
                        refinement_database);
    break;
  }
  case 3:
  {
    refine_mesh<dim, 3>(thermal_physics, solution, electron_beams, time,
                        next_refinement_time, time_steps_refinement,
                        refinement_database);
    break;
  }
  case 4:
  {
    refine_mesh<dim, 4>(thermal_physics, solution, electron_beams, time,
                        next_refinement_time, time_steps_refinement,
                        refinement_database);
    break;
  }
  case 5:
  {
    refine_mesh<dim, 5>(thermal_physics, solution, electron_beams, time,
                        next_refinement_time, time_steps_refinement,
                        refinement_database);
    break;
  }
  case 6:
  {
    refine_mesh<dim, 6>(thermal_physics, solution, electron_beams, time,
                        next_refinement_time, time_steps_refinement,
                        refinement_database);
    break;
  }
  case 7:
  {
    refine_mesh<dim, 7>(thermal_physics, solution, electron_beams, time,
                        next_refinement_time, time_steps_refinement,
                        refinement_database);
    break;
  }
  case 8:
  {
    refine_mesh<dim, 8>(thermal_physics, solution, electron_beams, time,
                        next_refinement_time, time_steps_refinement,
                        refinement_database);
    break;
  }
  case 9:
  {
    refine_mesh<dim, 9>(thermal_physics, solution, electron_beams, time,
                        next_refinement_time, time_steps_refinement,
                        refinement_database);
    break;
  }
  case 10:
  {
    refine_mesh<dim, 10>(thermal_physics, solution, electron_beams, time,
                         next_refinement_time, time_steps_refinement,
                         refinement_database);
    break;
  }
  default:
  {
    adamantine::ASSERT_THROW(false, "fe_degree should be between 1 and 10.");
  }
  }
}

template <int dim, typename MemorySpaceType>
dealii::LinearAlgebra::distributed::Vector<double, MemorySpaceType>
run(MPI_Comm const &communicator, boost::property_tree::ptree const &database,
    std::vector<adamantine::Timer> &timers)
{
  // Extract property tree children
  boost::property_tree::ptree geometry_database =
      database.get_child("geometry");
  boost::property_tree::ptree discretization_database =
      database.get_child("discretization");
  boost::property_tree::ptree time_stepping_database =
      database.get_child("time_stepping");
  boost::property_tree::ptree post_processor_database =
      database.get_child("post_processor");
  boost::property_tree::ptree refinement_database =
      database.get_child("refinement");

  unsigned int const fe_degree =
      discretization_database.get<unsigned int>("fe_degree");
  std::string quadrature_type =
      discretization_database.get("quadrature", "gauss");
  std::transform(quadrature_type.begin(), quadrature_type.end(),
                 quadrature_type.begin(),
                 [](unsigned char c) { return std::tolower(c); });
  double const initial_temperature =
      database.get("materials.initial_temperature", 300.);

  adamantine::Geometry<dim> geometry(communicator, geometry_database);
  std::unique_ptr<adamantine::Physics<dim, MemorySpaceType>> thermal_physics;
  std::vector<std::unique_ptr<adamantine::ElectronBeam<dim>>> &electron_beams =
      initialize_thermal_physics<dim>(fe_degree, quadrature_type, communicator,
                                      database, geometry, thermal_physics);
  adamantine::PostProcessor<dim> post_processor(
      communicator, post_processor_database, thermal_physics->get_dof_handler(),
      thermal_physics->get_material_property());

  thermal_physics->setup_dofs();
  thermal_physics->compute_inverse_mass_matrix();
  dealii::LA::distributed::Vector<double, MemorySpaceType> solution;
  thermal_physics->initialize_dof_vector(initial_temperature, solution);
  unsigned int progress = 0;
  unsigned int cycle = 0;
  unsigned int n_time_step = 0;
  double time = 0.;
  // Output the initial solution
  dealii::AffineConstraints<double> &affine_constraints =
      thermal_physics->get_affine_constraints();
  output_pvtu(post_processor, cycle, n_time_step, time, affine_constraints,
              solution);
  ++n_time_step;

  bool const verbose_refinement = refinement_database.get("verbose", false);
  unsigned int const time_steps_refinement =
      refinement_database.get("time_steps_between_refinement", 10);
  double next_refinement_time = time;
  double time_step = time_stepping_database.get<double>("time_step");
  double const duration = time_stepping_database.get<double>("duration");
  while (time < duration)
  {
    if ((time + time_step) > duration)
      time_step = duration - time;
    unsigned int rank = dealii::Utilities::MPI::this_mpi_process(communicator);

    // Refine the mesh after time_steps_refinement time steps or when time is
    // greater or equal than the next predicted time for refinement. This is
    // necessary when using an embedded method.
    if (((n_time_step % time_steps_refinement) == 0) ||
        (time >= next_refinement_time))
    {
      next_refinement_time = time + time_steps_refinement * time_step;
      timers[adamantine::refine].start();
      refine_mesh(thermal_physics, solution, electron_beams, time,
                  next_refinement_time, time_steps_refinement,
                  refinement_database, fe_degree);
      timers[adamantine::refine].stop();
      if ((rank == 0) && (verbose_refinement == true))
        std::cout << "n_dofs: " << thermal_physics->get_dof_handler().n_dofs()
                  << std::endl;
    }

    // time can be different than time + time_step if an embedded scheme is
    // used.
    timers[adamantine::evol_time].start();
    time = thermal_physics->evolve_one_time_step(time, time_step, solution,
                                                 timers);
    timers[adamantine::evol_time].stop();

    // Get the new time step
    time_step = thermal_physics->get_delta_t_guess();

    // Output progress on screen
    if (rank == 0)
    {
      double adim_time = time / (duration / 10.);
      double int_part = 0;
      std::modf(adim_time, &int_part);
      if (int_part > progress)
      {
        std::cout << int_part * 10 << '%' << " completed" << std::endl;
        ++progress;
      }
    }

    // Output the solution
    output_pvtu(post_processor, cycle, n_time_step, time, affine_constraints,
                solution);
    ++n_time_step;
  }
  post_processor.output_pvd();

  // This is only used for integration test
  return solution;
}
#endif


#pragma once
#include <string>
#include <bitset>

#include <boost/proto/core.hpp>
#include <boost/variant.hpp>
#include <boost/format.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_comparison.hpp>

#include "../frontend/Logic.hpp"
#include "../frontend/QF_BV.hpp"
#include "../frontend/Array.hpp"
#include "../frontend/QF_UF.hpp"
#include "../support/SMT_Tag_Mapping.hpp"
#include "../support/boost_mpl_vector60.hpp"

namespace metaSMT {
  namespace expression {
    namespace proto = boost::proto;
    namespace predtags = metaSMT::logic::tag;
    namespace bvtags = metaSMT::logic::QF_BV::tag;
    namespace arraytags = metaSMT::logic::Array::tag;
    namespace uftags = metaSMT::logic::QF_UF::tag;

    // return types
    struct logic_tag {};
    struct bv_tag {};
    struct uf_tag {};

    template< typename ReturnTag, typename OpTag >
    struct unary_expression;

    template< typename ReturnTag, typename OpTag >
    struct binary_expression;

    template< typename ReturnTag, typename OpTag >
    struct ternary_expression;

    template< typename ReturnTag, typename OpTag >
    struct nary_expression;

    template < typename OpTag >
    struct extract_expression;

    template < typename OpTag >
    struct extend_expression;

    template < typename OpTag >
    struct bv_const;

    template < typename OpTag >
    struct bv_const {
      unsigned long value;
      unsigned long width;
      std::string str;
      bool overflow;

      static bv_const<bvtags::bvhex_tag> hex(std::string const &hex_string) {
        //std::cerr << "hex " << hex_string << '\n';
        typedef std::string::const_iterator ConstIterator;
        ConstIterator it = hex_string.begin(), ie = hex_string.end();

        static boost::spirit::qi::rule< ConstIterator, unsigned long() > hex_rule
          = boost::spirit::qi::uint_parser<unsigned long, 16, 1, 16>()
          ;

        unsigned long value;
        if ( boost::spirit::qi::parse(it, ie, hex_rule, value) ) {
          assert( it == ie && "Expression not completely consumed" );
          unsigned const width = (hex_string.size() - 2)*4;
          return bv_const<bvtags::bvhex_tag>(value, width, hex_string);
        }

        assert( false && "Unable to parse hex literal" );
        throw std::exception();
      }

      static bv_const<bvtags::bvbin_tag> bin(std::string const &bin_string) {
        //std::cerr << "bin " << std::bitset<(sizeof(unsigned long)*8)>(bin_string).to_ulong() << '\n';
        return bv_const<bvtags::bvbin_tag>(
                 std::bitset<(sizeof(unsigned long)*8)>(bin_string).to_ulong(),
                 bin_string.size(),
                 bin_string
               );
      }

      bv_const( unsigned long value,
                unsigned long width,
                std::string const &str,
                bool valid = true)
        : value( value )
        , width( width )
        , str( str )
        , overflow( false )
      {
        unsigned long const max_width = sizeof(unsigned long)*8;
        if ( max_width <= width ) {
          overflow = true;
        }
        //std::cerr << "bv_const: (" << value << "," << width << "," << str << ")\n";
      }

      bv_const( unsigned long value,
                unsigned long width)
        : value( value )
        , width( width )
        , str( boost::str(boost::format("%u") % value) )
      {
        //std::cerr << "bv_const: (" << value << "," << width << "," << str << ")\n";
      }

      bool operator==( bv_const<OpTag> const &rhs ) const {
        return (value == rhs.value && width == rhs.width);
      }

      template < typename RHS >
      bool operator==( RHS const & ) const {
        return false;
      }

    }; // bv_const

    struct select_expression;
    struct store_expression;

    typedef metaSMT::logic::QF_BV::QF_BV<
      proto::terminal<bvtags::bit0_tag>::type
    > bit0_const;

    typedef metaSMT::logic::QF_BV::QF_BV<
      proto::terminal<bvtags::bit1_tag>::type
    > bit1_const;

#if BOOST_VARIANT_VISITATION_UNROLLING_LIMIT < 56
#warning "BOOST_VARIANT_VISITATION_UNROLLING_LIMIT is too small!"
#warning "Visiting logic_expressions may unexpectedly fail."
#warning "Try to include <metaSMT/support/default_visitation_unrolling_limit.hpp>."
#endif // BOOST_VARIANT_VISITATION_UNROLLING_LIMIT

    typedef boost::mpl::vector57<
      // constants
        bool
      , bit0_const
      , bit1_const
      , bv_const<bvtags::bvuint_tag>
      , bv_const<bvtags::bvsint_tag>
      , bv_const<bvtags::bvhex_tag>
      , bv_const<bvtags::bvbin_tag>

      // variables
      , metaSMT::logic::predicate
      , metaSMT::logic::QF_BV::bitvector
      , metaSMT::logic::Array::array
      , metaSMT::logic::QF_UF::Uninterpreted_Function

      // unary expressions
      , boost::recursive_wrapper<unary_expression<logic_tag, predtags::not_tag> >
      , boost::recursive_wrapper<unary_expression<bv_tag, bvtags::bvnot_tag> >
      , boost::recursive_wrapper<unary_expression<bv_tag, bvtags::bvneg_tag> >

      // binary expressions
      , boost::recursive_wrapper<binary_expression<logic_tag, predtags::equal_tag> >
      , boost::recursive_wrapper<binary_expression<logic_tag, predtags::nequal_tag> >
      , boost::recursive_wrapper<binary_expression<logic_tag, predtags::distinct_tag> >
      , boost::recursive_wrapper<binary_expression<logic_tag, predtags::implies_tag> >
      , boost::recursive_wrapper<binary_expression<logic_tag, predtags::nand_tag> >
      , boost::recursive_wrapper<binary_expression<logic_tag, predtags::nor_tag> >
      , boost::recursive_wrapper<binary_expression<logic_tag, predtags::xor_tag> >
      , boost::recursive_wrapper<binary_expression<logic_tag, predtags::xnor_tag> >

      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::bvand_tag> >
      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::bvnand_tag> >
      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::bvor_tag> >
      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::bvnor_tag> >
      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::bvxor_tag> >
      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::bvxnor_tag> >

      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::bvadd_tag> >
      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::bvmul_tag> >
      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::bvsub_tag> >
      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::bvsdiv_tag> >
      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::bvsrem_tag> >
      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::bvudiv_tag> >
      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::bvurem_tag> >

      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::bvshl_tag> >
      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::bvshr_tag> >
      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::bvashr_tag> >

      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::bvcomp_tag> >
      , boost::recursive_wrapper<binary_expression<logic_tag, bvtags::bvslt_tag> >
      , boost::recursive_wrapper<binary_expression<logic_tag, bvtags::bvsgt_tag> >
      , boost::recursive_wrapper<binary_expression<logic_tag, bvtags::bvsle_tag> >
      , boost::recursive_wrapper<binary_expression<logic_tag, bvtags::bvsge_tag> >
      , boost::recursive_wrapper<binary_expression<logic_tag, bvtags::bvult_tag> >
      , boost::recursive_wrapper<binary_expression<logic_tag, bvtags::bvugt_tag> >
      , boost::recursive_wrapper<binary_expression<logic_tag, bvtags::bvule_tag> >
      , boost::recursive_wrapper<binary_expression<logic_tag, bvtags::bvuge_tag> >

      , boost::recursive_wrapper<binary_expression<bv_tag, bvtags::concat_tag> >

      // ternary expressions
      , boost::recursive_wrapper<ternary_expression<logic_tag, predtags::ite_tag> >

      // nary expressions
      , boost::recursive_wrapper<nary_expression<uf_tag, proto::tag::function> >
      , boost::recursive_wrapper<nary_expression<logic_tag, predtags::and_tag> >
      , boost::recursive_wrapper<nary_expression<logic_tag, predtags::or_tag> >

      // expressions with special syntax
      , boost::recursive_wrapper<extract_expression<bvtags::extract_tag> >
      , boost::recursive_wrapper<extend_expression<bvtags::zero_extend_tag> >
      , boost::recursive_wrapper<extend_expression<bvtags::sign_extend_tag> >
      , boost::recursive_wrapper<select_expression>
      , boost::recursive_wrapper<store_expression>
    >::type logic_expression_types;

    typedef boost::make_variant_over<logic_expression_types>::type logic_expression;

    template < typename ReturnTag, typename OpTag >
    struct unary_expression {
      logic_expression expr;

      unary_expression( logic_expression const &expr )
        : expr( expr )
      {}

      bool operator==( unary_expression<ReturnTag, OpTag> const &rhs ) const {
        return (expr == rhs.expr);
      }

      template < typename RHS >
      bool operator==( RHS const & ) const {
        return false;
      }

      std::string const name() const {
        return get_tag_name(OpTag());
      }
    }; // unary_expression

    template < typename ReturnTag, typename OpTag >
    struct binary_expression {
      logic_expression left;
      logic_expression right;

      binary_expression( logic_expression const &lhs,
                         logic_expression const &rhs )
        : left( lhs )
        , right( rhs )
      {}

      bool operator==( binary_expression<ReturnTag, OpTag> const &rhs ) const {
        return boost::tie(left, right) == boost::tie(rhs.left, rhs.right);
      }

      template < typename RHS >
      bool operator==( RHS const & ) const {
        return false;
      }

      std::string const name() const {
        return get_tag_name(OpTag());
      }
    }; // binary_expression

    template < typename ReturnTag, typename OpTag >
    struct ternary_expression {
      logic_expression expr1;
      logic_expression expr2;
      logic_expression expr3;

      ternary_expression( logic_expression const &expr1,
                          logic_expression const &expr2,
                          logic_expression const &expr3 )
        : expr1( expr1 )
        , expr2( expr2 )
        , expr3( expr3 )
      {}

      bool operator==( ternary_expression<ReturnTag, OpTag> const &rhs ) const {
        return boost::tie(expr1, expr2, expr3) == boost::tie(rhs.expr1, rhs.expr2, rhs.expr3);
      }

      template < typename RHS >
      bool operator==( RHS const & ) const {
        return false;
      }

      std::string const name() const {
        return get_tag_name(OpTag());
      }
    }; // ternary_expression

    template < typename ReturnTag, typename OpTag >
    struct nary_expression {
      typedef std::vector< logic_expression > ContainerType;
      ContainerType exprs;

      nary_expression( ContainerType const &exprs )
        : exprs(exprs)
      {}

      bool operator==( nary_expression<ReturnTag, OpTag> const &rhs ) const {
        if ( exprs.size() != rhs.exprs.size() ) {
          return false;
        }

        for (unsigned u = 0; u < exprs.size(); ++u) {
          if ( !(exprs[u] == rhs.exprs[u]) ) {
            return false;
          }
        }

        return true;
      }

      template < typename RHS >
      bool operator==( RHS const & ) const {
        return false;
      }

      typename ContainerType::iterator begin() {
        return exprs.begin();
      }

      typename ContainerType::iterator end() {
        return exprs.end();
      }

      typename ContainerType::const_iterator begin() const {
        return exprs.begin();
      }

      typename ContainerType::const_iterator end() const {
        return exprs.end();
      }

      std::string const name() const {
        return get_tag_name(OpTag());
      }
    }; // nary_expression

    template < typename OpTag >
    struct extend_expression {
      unsigned long by;
      logic_expression expr;

      extend_expression( unsigned long by,
                         logic_expression const &expr )
        : by( by )
        , expr( expr )
      {}

      bool operator==( extend_expression const &rhs ) const {
        return boost::tie(by, expr) == boost::tie(rhs.by, rhs.expr);
      }

      template < typename RHS >
      bool operator==( RHS const & ) const {
        return false;
      }

    }; // extend_expression

    template < typename OpTag >
    struct extract_expression {
      unsigned long from;
      unsigned long width;
      unsigned long to;
      logic_expression expr;

      extract_expression( unsigned long from,
                          unsigned long width,
                          logic_expression const &expr )
        : from( from )
        , width( width )
        , to( from-width )
        , expr( expr )
      {}

      bool operator==( extract_expression const &rhs ) const {
        return boost::tie(from, width, expr) == boost::tie(rhs.from, rhs.width, rhs.expr);
      }

      template < typename RHS >
      bool operator==( RHS const & ) const {
        return false;
      }

    }; // extract_expression

    struct select_expression {
      logic_expression array;
      logic_expression index;

      select_expression( logic_expression const &array,
                         logic_expression const &index )
        : array( array )
        , index( index )
      {}

      bool operator==( select_expression const &rhs ) const {
        return boost::tie(array, index) == boost::tie(rhs.array, rhs.index);
      }

      template < typename RHS >
      bool operator==( RHS const & ) const {
        return false;
      }

    }; // select_expression

    struct store_expression {
      logic_expression array;
      logic_expression index;
      logic_expression value;

      store_expression( logic_expression const &array,
                        logic_expression const &index,
                        logic_expression const &value )
        : array( array )
        , index( index )
        , value( value )
      {}

      bool operator==( store_expression const &rhs ) const {
        return boost::tie(array, index, value) == boost::tie(rhs.array, rhs.index, rhs.value);
      }

      template < typename RHS >
      bool operator==( RHS const & ) const {
        return false;
      }

    }; // store_expression

  } // expression
} // metaSMT