/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*                                                                           */
/*  This file is part of the library KASKADE 7                               */
/*    see http://www.zib.de/projects/kaskade7-finite-element-toolbox         */
/*                                                                           */
/*  Copyright (C) 2002-2012 Zuse Institute Berlin                            */
/*                                                                           */
/*  KASKADE 7 is distributed under the terms of the ZIB Academic License.    */
/*    see $KASKADE/academic.txt                                              */
/*                                                                           */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#ifndef ALGORITHM_3_ERROR_ESTIMATOR_EE
#define ALGORITHM_3_ERROR_ESTIMATOR_EE

#include <algorithm>
#include <iostream>
#include <utility>
#include <vector>

#include <boost/timer/timer.hpp>
#include <boost/fusion/include/at_c.hpp>

#include "algorithm/newton_bridge.hh"
#include "algorithm/adaptationStrategy.hh"
#include "algorithm/errorDistribution.hh"
#include "linalg/jacobiPreconditioner.hh"
#include "fem/forEach.hh"
#include "fem/hierarchicErrorEstimator.hh"
#include "fem/variables.hh"
#include "utilities/enums.hh"
#include "errorEstimationTraits.hh"
#include "lagrangeLinearization.hh"

// forward declarations
namespace Dune
{
  struct InverseOperatorResult;
  template <class,int> class FieldVector;
}

namespace Kaskade
{
  struct Dummy{};

  template <template <class,class,class,bool> class Functional, class VariableSetDescription, class ExtensionVariableSetDescription, class ExtensionSpace,
  class NormFunctional, template <class> class RefinementStrategy = Adaptivity::ErrorEquilibration, bool lump=false, int components=1, class ReferenceSolution = Dummy, class ReferenceOperator=Dummy>
  class YetAnotherHBErrorEstimator : public ErrorEstimatorBase<VariableSetDescription,typename ErrorDistribution<NormFunctional,ExtensionVariableSetDescription>::AnsatzVars::VariableSet,ExtensionSpace,RefinementStrategy>
  {
    template <class AnsatzVars, class TestVars, class OriginVars> using EstimatorFunctional = Functional<AnsatzVars,TestVars,OriginVars,false>;
    template <class AnsatzVars, class TestVars, class OriginVars> using LumpedFunctional = Functional<AnsatzVars,TestVars,OriginVars,lump>;
    typedef ErrorEstimationTraits<EstimatorFunctional,VariableSetDescription,ExtensionVariableSetDescription> Traits;
    typedef ErrorEstimationTraits<LumpedFunctional,VariableSetDescription,ExtensionVariableSetDescription> LumpedTraits;
    typedef ErrorEstimatorBase<VariableSetDescription,typename ErrorDistribution<NormFunctional,ExtensionVariableSetDescription>::AnsatzVars::VariableSet,ExtensionSpace,RefinementStrategy> Base;
    using Base::squaredError;
    using Base::mde;
    using Base::errorDistribution;
    using Base::extensionSpace;
  public:
    // assemblers

    typedef typename Traits::Scalar Scalar;
    typedef typename VariableSetDescription::Grid Grid;
    static constexpr int dim = VariableSetDescription::Grid::dimension;

    typedef typename ExtensionVariableSetDescription::GridView::template Codim<0>::Iterator CellIterator;
    typedef typename ExtensionVariableSetDescription::GridView::template Codim<0>::Entity Cell;
    typedef ErrorDistribution<NormFunctional,ExtensionVariableSetDescription> EnergyError;
    typedef typename EnergyError::AnsatzVars::VariableSet ErrorRepresentation;

    YetAnotherHBErrorEstimator(NormFunctional& normFunctional_, VariableSetDescription& variableSetDescription_, ExtensionVariableSetDescription& extensionVariableSetDescription_,
        ExtensionSpace& extensionSpace_, Scalar fraction=0.7, bool verbose_=false)
    : Base(extensionSpace_,fraction,verbose), normFunctional(normFunctional_), variableSetDescription(variableSetDescription_), extensionVariableSetDescription(extensionVariableSetDescription_),
      squaredFraction(fraction*fraction), verbose(verbose_)
      {
        ass_EE.reset(new typename LumpedTraits::Assembler_EE(extensionVariableSetDescription.spaces));
        ass_HE.reset(new typename Traits::Assembler_HE(variableSetDescription.spaces));
        ass_EH.reset(new typename Traits::Assembler_EH(extensionVariableSetDescription.spaces));
        ass_HH.reset(new typename Traits::Assembler_HH(variableSetDescription.spaces));
      }

    virtual ~YetAnotherHBErrorEstimator(){}

    void operator()(AbstractLinearization const& lin, AbstractFunctionSpaceElement const& x_, AbstractFunctionSpaceElement const& dx_, int step, AbstractFunctionSpaceElement const&)
    {
      boost::timer::cpu_timer overallTimer;
      using boost::fusion::at_c;
      if(verbose) std::cout << "ERROR ESTIMATOR: Start." << std::endl;
      Bridge::Vector<typename Traits::VarSet> const& xl = dynamic_cast<const Bridge::Vector<typename Traits::VarSet>&>(x_);
      computeError(xl.get());
      Bridge::Vector<typename Traits::ExtensionVarSet> xe(extensionVariableSetDescription);
      boost::timer::cpu_timer atimer;
      assembleAt(xl,xe);
      if(verbose) std::cout << "ERROR ESTIMATOR: assembly time: " << boost::timer::format(atimer.elapsed())  << std::endl;
      Bridge::Vector<typename Traits::VarSet> const& dxl = dynamic_cast<const Bridge::Vector<typename Traits::VarSet>&>(dx_);

      /***************************************************************************************/
      // state equation -> DLY
      typename Traits::A_HH a_HH(*ass_HH,false);
      typename Traits::A_HE a_HE(*ass_HE,false);
      typename LumpedTraits::A_EE a_EE(*ass_EE,false);
      typename Traits::B_HH b_HH(*ass_HH,false);
      typename Traits::B_HE b_HE(*ass_HE,false);
      typename Traits::B_EH b_EH(*ass_EH,false);
      typename LumpedTraits::B_EE b_EE(*ass_EE,false);

      typename Traits::ExtensionVectorP rhsPE(ass_EE->template rhs<Traits::adjointId,Traits::adjointId+1>());
      rhsPE *= -1.0;
      typename Traits::VectorY dy(at_c<Traits::stateId>(dxl.get().data).coefficients()), solYH = dy; solYH *= 0;
      typename Traits::VectorU du(at_c<Traits::controlId>(dxl.get().data).coefficients());
      typename Traits::ExtensionVectorY solYE(Traits::ExtensionVectorY_Initializer::init(extensionVariableSetDescription));

      b_HE.applyscaleadd(-1.0,du,rhsPE);
      a_HE.applyscaleadd(-1.0,dy,rhsPE);

      JacobiPreconditionerForTriplets<Scalar,typename LumpedTraits::ExtensionVectorY,typename LumpedTraits::ExtensionVectorP> jacobi(a_EE);
      jacobi.apply(solYE,rhsPE);

      // error in adjoint equation
      typename Traits::Lyy_HH lyy_HH(*ass_HH,false);
      typename Traits::Lyy_HE lyy_HE(*ass_HE,false);
      typename Traits::Lyy_EH lyy_EH(*ass_EH,false);
      typename LumpedTraits::Lyy_EE lyy_EE(*ass_EE,false);
      TransposedOperator<typename Traits::A_HE> at_EH(a_HE);//(*ass_EH,false);

      typename Traits::VectorY rhsYH(ass_HH->template rhs<Traits::stateId,Traits::stateId+1>());
      rhsYH *= -1.0;
      typename Traits::ExtensionVectorY rhsYE(ass_EE->template rhs<Traits::stateId, Traits::stateId+1>());
      rhsYE *= -1.0;

      lyy_HH.applyscaleadd(-1.0,dy,rhsYH);
      lyy_HE.applyscaleadd(-1.0,dy,rhsYE);
      lyy_EH.applyscaleadd(-1.0,solYE,rhsYH);
      lyy_EE.applyscaleadd(-1.0,solYE,rhsYE);

      typename Traits::ExtensionVectorP solPE(Traits::ExtensionVectorP_Initializer::init(extensionVariableSetDescription));
      typename Traits::VectorP solPH(Traits::VectorP_Initializer::init(variableSetDescription));

      jacobi.apply(solPE,rhsYE);
      at_EH.applyscaleadd(-1.0,solPE,rhsYH);

      boost::timer::cpu_timer mgTimer;
      MultiGridSolver<Grid,components>( a_HH, boost::fusion::at_c<0>(ass_HH->spaces())->gridManager().grid(),
                                        typename MultiGridSolver<Grid,components>::Parameter(mgSteps,mgSmoothingSteps,relativeAccuracy), true ).apply(at_c<0>(solPH.data),
                                                                                at_c<0>(rhsYH.data));
      std::cout << "mg: " << boost::timer::format(mgTimer.elapsed()) << std::endl;

      // variational equality
      TransposedOperator<typename Traits::B_HH> bt_HH(b_HH);
      TransposedOperator<typename Traits::B_EH> bt_HE(b_EH);
      TransposedOperator<typename Traits::B_HE> bt_EH(b_HE);
      TransposedOperator<typename LumpedTraits::B_EE> bt_EE(b_EE);
      typename Traits::Luu_HH luu_HH(*ass_HH,false);
      typename Traits::Luu_EH luu_EH(*ass_EH,false);
      typename Traits::Luu_HE luu_HE(*ass_HE,false);
      typename LumpedTraits::Luu_EE luu_EE(*ass_EE,false);

      typename Traits::VectorU rhsUH(ass_HH->template rhs<Traits::controlId,Traits::controlId+1>());
      rhsUH *= -1;
      typename Traits::ExtensionVectorU rhsUE(ass_EE->template rhs<Traits::controlId,Traits::controlId+1>());
      rhsUE *= -1;

      luu_HH.applyscaleadd(-1.0,du,rhsUH);
      luu_HE.applyscaleadd(-1.0,du,rhsUE);

      bt_HH.applyscaleadd(-1.0,solPH,rhsUH);
      bt_HE.applyscaleadd(-1.0,solPH,rhsUE);
      bt_EH.applyscaleadd(-1.0,solPE,rhsUH);
      bt_EE.applyscaleadd(-1.0,solPE,rhsUE);

      typename Traits::VectorU solUH(Traits::VectorU_Initializer::init(variableSetDescription));
      typename Traits::ExtensionVectorU solUE(Traits::ExtensionVectorU_Initializer::init(extensionVariableSetDescription));

      JacobiPreconditionerForTriplets<Scalar,typename LumpedTraits::ExtensionVectorU,typename LumpedTraits::ExtensionVectorU>(luu_EE).apply(solUE,rhsUE);
      luu_EH.applyscaleadd(-1.0,solUE,rhsUH);
      boost::timer::cpu_timer chebTimer;
      ChebyshevPreconditioner<typename Traits::Luu_HH> cheb(luu_HH,chebySteps);
      cheb.initForMassMatrix_TetrahedralQ1Elements();
      cheb.apply(solUH,rhsUH);
      std::cout << "cheb: " << boost::timer::format(chebTimer.elapsed()) << std::endl;

      typename ExtensionVariableSetDescription::VariableSet errorEstimate_E(extensionVariableSetDescription);
      typename VariableSetDescription::VariableSet errorEstimate_H(variableSetDescription);
      solYH *= 0.0; // DLY in state equation -> no error transport into space of linear finite elements
      at_c<Traits::stateId>(errorEstimate_H.data) = at_c<0>(solYH.data);
      at_c<Traits::controlId>(errorEstimate_H.data) = at_c<0>(solUH.data);
      at_c<Traits::stateId>(errorEstimate_E.data) = at_c<0>(solYE.data);
      at_c<Traits::controlId>(errorEstimate_E.data) = at_c<0>(solUE.data);
//      std::string savefilename = createFileName("estimate_E",".vtu",false);
//      std::cout << "Error estimator: writing estimates" << std::endl;
//      writeVTKFile(extensionVariableSetDescription.gridView,errorEstimate_E,savefilename,IoOptions(),2);
//      std::cout << "Error estimator: writing lower order estimates" << std::endl;
//      writeVTKFile(variableSetDescription.gridView,errorEstimate_H,savefilename,IoOptions(),1);
      /***************************************************************************************/
      // Transfer error indicators to cells.
      auto const& is = extensionSpace.gridManager().grid().leafIndexSet();
      errorDistribution.clear();
      errorDistribution.resize(is.size(0),std::make_pair(0.0,0));

      EnergyError energyError(normFunctional,xl.get(),errorEstimate_H,errorEstimate_E);
      energyError.considerStateVariable(true);
      energyError.considerControlVariable(true);
      energyError.considerAdjointVariable(false);
      typedef VariationalFunctionalAssembler<LinearizationAt<EnergyError> > EnergyErrorAssembler;
      EnergyErrorAssembler eeAssembler(extensionSpace.gridManager(), energyError.getSpaces());

      eeAssembler.assemble(linearization(energyError,xl.get()), EnergyErrorAssembler::RHS);
      typename EnergyError::ErrorVector distError( eeAssembler.rhs() );
      mde.reset(new ErrorRepresentation(energyError.getVariableSetDescription()) );
      *mde = distError;

//      if(verbose)
//      {
//        std::string name = "errorDistribution_";
//        name += std::to_string(step);
//        writeVTKFile(mde->descriptions.gridView,*mde,name);
//      }

      CellIterator cend = extensionVariableSetDescription.gridView.template end<0>();
      for (CellIterator ci=extensionVariableSetDescription.gridView.template begin<0>(); ci!=cend; ++ci)
        errorDistribution[is.index(*ci)] = std::make_pair( fabs(boost::fusion::at_c<0>(mde->data).value(*ci,Dune::FieldVector<Scalar,dim>(0.3))) , is.index(*ci));

      squaredError = std::accumulate(errorDistribution.begin(), errorDistribution.end(), 0.0, ErrorEstimator_Detail::add);

      if(verbose) std::cout << "overall error estimation time: " << boost::timer::format(overallTimer.elapsed()) << std::endl;
    }

    template <typename... Args>
    void initFunctionals(const Args&... args)
    {
      F_HH.reset(new typename Traits::Functional_HH(args...));
      F_HE.reset(new typename Traits::Functional_HE(args...));
    }

    template <typename... Args>
    void initExtensionFunctionals(const Args&... args)
    {
      F_EH.reset(new typename Traits::Functional_EH(args...));
      F_EE.reset(new typename LumpedTraits::Functional_EE(args...));
    }

    void setReference(ReferenceOperator const& Aref, ReferenceSolution const& ref, typename Traits::Vector& refcv)
    {
      referenceSolution = &ref;
      referenceOperator = &Aref;
      referenceCoeffVec = &refcv;
    }

    void computeError(typename Traits::VarSet const& x)
    {
      if(referenceSolution == nullptr) return;
      ReferenceSolution tmp(*referenceSolution);
      interpolateGloballyFromFunctor<PlainAverage>(boost::fusion::at_c<0>(tmp.data),[&x](Cell const& cell, Dune::FieldVector<Scalar,dim> const& xLocal)
      {
        return boost::fusion::at_c<0>(x.data).value(cell.geometry().global(xLocal));
      });

      interpolateGloballyFromFunctor<PlainAverage>(boost::fusion::at_c<1>(tmp.data),[&x](Cell const& cell, Dune::FieldVector<Scalar,dim> const& xLocal)
      {
        return boost::fusion::at_c<1>(x.data).value(cell.geometry().global(xLocal));
      });
//      interpolateGloballyWeak<PlainAverage>(boost::fusion::at_c<2>(tmp.data),boost::fusion::at_c<2>(x.data));
      tmp -= *referenceSolution;

      boost::fusion::at_c<0>(referenceCoeffVec->data) = boost::fusion::at_c<0>(tmp.data).coefficients();
      boost::fusion::at_c<1>(referenceCoeffVec->data) = boost::fusion::at_c<1>(tmp.data).coefficients();
      // boost::fusion::at_c<2>(referenceCoeffVec->data) = boost::fusion::at_c<2>(tmp.data).coefficients();

      auto tmp2 = *referenceCoeffVec;
      referenceOperator->apply(*referenceCoeffVec,tmp2);
      std::cout << "REAL ERROR: " << (*referenceCoeffVec)*tmp2 << std::endl;
    }

  private:
    void assembleAt(const Bridge::Vector<typename Traits::VarSet>& xl, const Bridge::Vector<typename Traits::ExtensionVarSet>& xe)
    {
      ass_HH->assemble(linearization(*F_HH,xl.get()));
      ass_HE->assemble(linearization(*F_HE,xl.get()));
      ass_EH->assemble(linearization(*F_EH,xe.get()));
      ass_EE->assemble(linearization(*F_EE,xe.get()));
    }

    std::unique_ptr<typename Traits::Functional_HH> F_HH;
    std::unique_ptr<typename Traits::Functional_HE> F_HE;
    std::unique_ptr<typename Traits::Functional_EH> F_EH;
    std::unique_ptr<typename LumpedTraits::Functional_EE> F_EE;
    std::unique_ptr<typename Traits::Assembler_HH> ass_HH;
    //typename Traits::Assembler_HH* ass_HH;
    std::unique_ptr<typename Traits::Assembler_HE> ass_HE;
    std::unique_ptr<typename Traits::Assembler_EH> ass_EH;
    std::unique_ptr<typename LumpedTraits::Assembler_EE> ass_EE;
    NormFunctional& normFunctional;
    VariableSetDescription& variableSetDescription;
    ExtensionVariableSetDescription& extensionVariableSetDescription;
    Scalar squaredFraction;
    bool verbose;
    size_t chebySteps = 30, mgSteps = 25, mgSmoothingSteps = 20;
    Scalar relativeAccuracy = 1e-3;
    ReferenceSolution const* referenceSolution = nullptr;
    ReferenceOperator const* referenceOperator = nullptr;
    typename Traits::Vector* referenceCoeffVec = nullptr;
    //std::function<Scalar(typename Traits::VarSet)> compareError;
  };

  template <template <class,class,class,bool> class Functional, class VariableSetDescription, class ExtensionVariableSetDescription, class ExtensionSpace,
  class NormFunctional, template <class> class RefinementStrategy = Adaptivity::ErrorEquilibration, bool lump=false, int components=1>
  class YetAnotherHBErrorEstimator_Elasticity : public ErrorEstimatorBase<VariableSetDescription,typename ErrorDistribution<NormFunctional,ExtensionVariableSetDescription>::AnsatzVars::VariableSet,ExtensionSpace,RefinementStrategy>
  {
    template <class AnsatzVars, class TestVars, class OriginVars> using EstimatorFunctional = Functional<AnsatzVars,TestVars,OriginVars,false>;
    template <class AnsatzVars, class TestVars, class OriginVars> using LumpedFunctional = Functional<AnsatzVars,TestVars,OriginVars,lump>;
    typedef ErrorEstimationTraits<EstimatorFunctional,VariableSetDescription,ExtensionVariableSetDescription> Traits;
    typedef ErrorEstimationTraits<LumpedFunctional,VariableSetDescription,ExtensionVariableSetDescription> LumpedTraits;
    typedef ErrorEstimatorBase<VariableSetDescription,typename ErrorDistribution<NormFunctional,ExtensionVariableSetDescription>::AnsatzVars::VariableSet,ExtensionSpace,RefinementStrategy> Base;
    using Base::squaredError;
    using Base::mde;
    using Base::errorDistribution;
    using Base::extensionSpace;
  public:
    // assemblers

    typedef typename Traits::Scalar Scalar;
    typedef typename VariableSetDescription::Grid Grid;
    static constexpr int dim = VariableSetDescription::Grid::dimension;

    typedef typename ExtensionVariableSetDescription::GridView::template Codim<0>::Iterator CellIterator;
    typedef typename ExtensionVariableSetDescription::GridView::template Codim<0>::Entity Cell;
    typedef ErrorDistribution<NormFunctional,ExtensionVariableSetDescription> EnergyError;
    typedef typename EnergyError::AnsatzVars::VariableSet ErrorRepresentation;

    YetAnotherHBErrorEstimator_Elasticity(NormFunctional& normFunctional_, VariableSetDescription& variableSetDescription_, ExtensionVariableSetDescription& extensionVariableSetDescription_,
        ExtensionSpace& extensionSpace_, Scalar fraction=0.7, bool verbose_=false)
    : Base(extensionSpace_,fraction,verbose), normFunctional(normFunctional_), variableSetDescription(variableSetDescription_), extensionVariableSetDescription(extensionVariableSetDescription_),
      squaredFraction(fraction*fraction), verbose(verbose_)
      {
        ass_EE.reset(new typename LumpedTraits::Assembler_EE(extensionVariableSetDescription.spaces));
        ass_HE.reset(new typename Traits::Assembler_HE(variableSetDescription.spaces));
        ass_EH.reset(new typename Traits::Assembler_EH(extensionVariableSetDescription.spaces));
        ass_HH.reset(new typename Traits::Assembler_HH(variableSetDescription.spaces));
      }

    virtual ~YetAnotherHBErrorEstimator_Elasticity(){}

    void operator()(AbstractLinearization const& lin, AbstractFunctionSpaceElement const& x_, AbstractFunctionSpaceElement const& dx_, int step, AbstractFunctionSpaceElement const&)
    {
      boost::timer::cpu_timer overallTimer;
      using boost::fusion::at_c;
      if(verbose) std::cout << "ERROR ESTIMATOR: Start." << std::endl;
      Bridge::Vector<typename Traits::VarSet> const& xl = dynamic_cast<const Bridge::Vector<typename Traits::VarSet>&>(x_);
      Bridge::Vector<typename Traits::ExtensionVarSet> xe(extensionVariableSetDescription);
      boost::timer::cpu_timer atimer;
      assembleAt(xl,xe);
      if(verbose) std::cout << "ERROR ESTIMATOR: assembly time: " << boost::timer::format(atimer.elapsed())  << std::endl;
      Bridge::Vector<typename Traits::VarSet> const& dxl = dynamic_cast<const Bridge::Vector<typename Traits::VarSet>&>(dx_);

      /***************************************************************************************/
      // state equation -> DLY
      typename Traits::A_HH a_HH(*ass_HH,false);
      typename Traits::A_HE a_HE(*ass_HE,false);
      typename LumpedTraits::A_EE a_EE(*ass_EE,false);
      typename Traits::B_HH b_HH(*ass_HH,false);
      typename Traits::B_HE b_HE(*ass_HE,false);
      typename Traits::B_EH b_EH(*ass_EH,false);
      typename LumpedTraits::B_EE b_EE(*ass_EE,false);

      typename Traits::ExtensionVectorP rhsPE(ass_EE->template rhs<Traits::adjointId,Traits::adjointId+1>());
      rhsPE *= -1.0;
      typename Traits::VectorY dy(at_c<Traits::stateId>(dxl.get().data).coefficients()), solYH = dy; solYH *= 0;
      typename Traits::VectorU du(at_c<Traits::controlId>(dxl.get().data).coefficients());
      typename Traits::ExtensionVectorY solYE(Traits::ExtensionVectorY_Initializer::init(extensionVariableSetDescription));

      b_HE.applyscaleadd(-1.0,du,rhsPE);
      a_HE.applyscaleadd(-1.0,dy,rhsPE);

      JacobiPreconditionerForTriplets<Scalar,typename LumpedTraits::ExtensionVectorY,typename LumpedTraits::ExtensionVectorP> jacobi(a_EE);
      jacobi.apply(solYE,rhsPE);

      // error in adjoint equation
      typename Traits::Lyy_HH lyy_HH(*ass_HH,false);
      typename Traits::Lyy_HE lyy_HE(*ass_HE,false);
      typename Traits::Lyy_EH lyy_EH(*ass_EH,false);
      typename LumpedTraits::Lyy_EE lyy_EE(*ass_EE,false);
      typename Traits::Luy_HH luy_HH(*ass_HH,false);
      typename Traits::Luy_HE luy_HE(*ass_HE,false);
      typename Traits::Luy_EH luy_EH(*ass_EH,false);
      typename LumpedTraits::Luy_EE luy_EE(*ass_EE,false);
      TransposedOperator<typename Traits::Luy_HH> lyu_HH(luy_HH);
      TransposedOperator<typename Traits::Luy_EH> lyu_HE(luy_EH);
//      typename Traits::Lyu_HH lyu_HH(*ass_HH,false);
//      typename Traits::Lyu_HE lyu_HE(*ass_HE,false);
      TransposedOperator<typename Traits::A_HE> at_EH(a_HE);//(*ass_EH,false);

      typename Traits::VectorY rhsYH(ass_HH->template rhs<Traits::stateId,Traits::stateId+1>());
      rhsYH *= -1.0;
      typename Traits::ExtensionVectorY rhsYE(ass_EE->template rhs<Traits::stateId, Traits::stateId+1>());
      rhsYE *= -1.0;

      lyy_HH.applyscaleadd(-1.0,dy,rhsYH);
      lyy_HE.applyscaleadd(-1.0,dy,rhsYE);
      lyy_EH.applyscaleadd(-1.0,solYE,rhsYH);
      lyy_EE.applyscaleadd(-1.0,solYE,rhsYE);
      lyu_HH.applyscaleadd(-1.0,du,rhsYH);
      lyu_HE.applyscaleadd(-1.0,du,rhsYE);

      typename Traits::ExtensionVectorP solPE(Traits::ExtensionVectorP_Initializer::init(extensionVariableSetDescription));
      typename Traits::VectorP solPH(Traits::VectorP_Initializer::init(variableSetDescription));

      jacobi.apply(solPE,rhsYE);
      at_EH.applyscaleadd(-1.0,solPE,rhsYH);

      boost::timer::cpu_timer mgTimer;
      MultiGridSolver<Grid,components>( a_HH, boost::fusion::at_c<0>(ass_HH->spaces())->gridManager().grid(),
                                        typename MultiGridSolver<Grid,components>::Parameter(mgSteps,mgSmoothingSteps,relativeAccuracy), true ).apply(at_c<0>(solPH.data),
                                                                                at_c<0>(rhsYH.data));
      std::cout << "mg: " << boost::timer::format(mgTimer.elapsed()) << std::endl;

      // variational equality
      TransposedOperator<typename Traits::B_HH> bt_HH(b_HH);
      TransposedOperator<typename Traits::B_EH> bt_HE(b_EH);
      TransposedOperator<typename Traits::B_HE> bt_EH(b_HE);
      TransposedOperator<typename LumpedTraits::B_EE> bt_EE(b_EE);
      typename Traits::Luu_HH luu_HH(*ass_HH,false);
      typename Traits::Luu_EH luu_EH(*ass_EH,false);
      typename Traits::Luu_HE luu_HE(*ass_HE,false);
      typename LumpedTraits::Luu_EE luu_EE(*ass_EE,false);

      typename Traits::VectorU rhsUH(ass_HH->template rhs<Traits::controlId,Traits::controlId+1>());
      rhsUH *= -1;
      typename Traits::ExtensionVectorU rhsUE(ass_EE->template rhs<Traits::controlId,Traits::controlId+1>());
      rhsUE *= -1;

      luu_HH.applyscaleadd(-1.0,du,rhsUH);
      luu_HE.applyscaleadd(-1.0,du,rhsUE);

      bt_HH.applyscaleadd(-1.0,solPH,rhsUH);
      bt_HE.applyscaleadd(-1.0,solPH,rhsUE);
      bt_EH.applyscaleadd(-1.0,solPE,rhsUH);
      bt_EE.applyscaleadd(-1.0,solPE,rhsUE);

      luy_HH.applyscaleadd(-1.0,dy,rhsUH);
      luy_HE.applyscaleadd(-1.0,dy,rhsUE);
      luy_EH.applyscaleadd(-1.0,solYE,rhsUH);
      luy_EE.applyscaleadd(-1.0,solYE,rhsUE);

      typename Traits::VectorU solUH(Traits::VectorU_Initializer::init(variableSetDescription));
      typename Traits::ExtensionVectorU solUE(Traits::ExtensionVectorU_Initializer::init(extensionVariableSetDescription));

      JacobiPreconditionerForTriplets<Scalar,typename LumpedTraits::ExtensionVectorU,typename LumpedTraits::ExtensionVectorU>(luu_EE).apply(solUE,rhsUE);
      luu_EH.applyscaleadd(-1.0,solUE,rhsUH);
      boost::timer::cpu_timer chebTimer;
      ChebyshevPreconditioner<typename Traits::Luu_HH> cheb(luu_HH,chebySteps);
      cheb.initForMassMatrix_TetrahedralQ1Elements();
      cheb.apply(solUH,rhsUH);
      std::cout << "cheb: " << boost::timer::format(chebTimer.elapsed()) << std::endl;

      typename ExtensionVariableSetDescription::VariableSet errorEstimate_E(extensionVariableSetDescription);
      typename VariableSetDescription::VariableSet errorEstimate_H(variableSetDescription);
      solYH *= 0.0; // DLY in state equation -> no error transport into space of linear finite elements
      at_c<Traits::stateId>(errorEstimate_H.data) = at_c<0>(solYH.data);
      at_c<Traits::controlId>(errorEstimate_H.data) = at_c<0>(solUH.data);
      at_c<Traits::stateId>(errorEstimate_E.data) = at_c<0>(solYE.data);
      at_c<Traits::controlId>(errorEstimate_E.data) = at_c<0>(solUE.data);
//      std::string savefilename = createFileName("estimate_E",".vtu",false);
//      std::cout << "Error estimator: writing estimates" << std::endl;
//      writeVTKFile(extensionVariableSetDescription.gridView,errorEstimate_E,savefilename,IoOptions(),2);
//      std::cout << "Error estimator: writing lower order estimates" << std::endl;
//      writeVTKFile(variableSetDescription.gridView,errorEstimate_H,savefilename,IoOptions(),1);
      /***************************************************************************************/
      // Transfer error indicators to cells.
      auto const& is = extensionSpace.gridManager().grid().leafIndexSet();
      errorDistribution.clear();
      errorDistribution.resize(is.size(0),std::make_pair(0.0,0));

      EnergyError energyError(normFunctional,xl.get(),errorEstimate_H,errorEstimate_E);
      energyError.considerStateVariable(true);
      energyError.considerControlVariable(true);
      energyError.considerAdjointVariable(false);
      typedef VariationalFunctionalAssembler<LinearizationAt<EnergyError> > EnergyErrorAssembler;
      EnergyErrorAssembler eeAssembler(extensionSpace.gridManager(), energyError.getSpaces());

      eeAssembler.assemble(linearization(energyError,xl.get()), EnergyErrorAssembler::RHS);
      typename EnergyError::ErrorVector distError( eeAssembler.rhs() );
      mde.reset(new ErrorRepresentation(energyError.getVariableSetDescription()) );
      *mde = distError;

//      if(verbose)
//      {
//        std::string name = "errorDistribution_";
//        name += std::to_string(step);
//        writeVTKFile(mde->descriptions.gridView,*mde,name);
//      }

      CellIterator cend = extensionVariableSetDescription.gridView.template end<0>();
      for (CellIterator ci=extensionVariableSetDescription.gridView.template begin<0>(); ci!=cend; ++ci)
        errorDistribution[is.index(*ci)] = std::make_pair( fabs(boost::fusion::at_c<0>(mde->data).value(*ci,Dune::FieldVector<Scalar,dim>(0.3))) , is.index(*ci));

      squaredError = std::accumulate(errorDistribution.begin(), errorDistribution.end(), 0.0, ErrorEstimator_Detail::add);

      if(verbose) std::cout << "overall error estimation time: " << boost::timer::format(overallTimer.elapsed()) << std::endl;
    }

    template <typename... Args>
    void initFunctionals(const Args&... args)
    {
      F_HH.reset(new typename Traits::Functional_HH(args...));
      F_HE.reset(new typename Traits::Functional_HE(args...));
    }

        template <typename... Args>
    void initExtensionFunctionals(const Args&... args)
    {
      F_EH.reset(new typename Traits::Functional_EH(args...));
      F_EE.reset(new typename LumpedTraits::Functional_EE(args...));
    }

  private:
    void assembleAt(const Bridge::Vector<typename Traits::VarSet>& xl, const Bridge::Vector<typename Traits::ExtensionVarSet>& xe)
    {
      ass_HH->assemble(linearization(*F_HH,xl.get()));
      ass_HE->assemble(linearization(*F_HE,xl.get()));
      ass_EH->assemble(linearization(*F_EH,xe.get()));
      ass_EE->assemble(linearization(*F_EE,xe.get()));
    }

    std::unique_ptr<typename Traits::Functional_HH> F_HH;
    std::unique_ptr<typename Traits::Functional_HE> F_HE;
    std::unique_ptr<typename Traits::Functional_EH> F_EH;
    std::unique_ptr<typename LumpedTraits::Functional_EE> F_EE;
    std::unique_ptr<typename Traits::Assembler_HH> ass_HH;
    //typename Traits::Assembler_HH* ass_HH;
    std::unique_ptr<typename Traits::Assembler_HE> ass_HE;
    std::unique_ptr<typename Traits::Assembler_EH> ass_EH;
    std::unique_ptr<typename LumpedTraits::Assembler_EE> ass_EE;
    NormFunctional& normFunctional;
    VariableSetDescription& variableSetDescription;
    ExtensionVariableSetDescription& extensionVariableSetDescription;
    Scalar squaredFraction;
    bool verbose;
    size_t chebySteps = 30, mgSteps = 25, mgSmoothingSteps = 20;
    Scalar relativeAccuracy = 1e-3;
  };
}

#endif


#include "AffineTreeVisitor.h"
#include <boost/lexical_cast.hpp>
#include "TreeInfixPrinter.h"
#include <exception>
#include "Backend.h"
#include "Logger.h"
#include "kv/affine.hpp"

using namespace std;
using namespace hydla::symbolic_expression;
using namespace boost;

#define HYDLA_LOGGER_NODE_VALUE \
  ;
//  HYDLA_LOGGER_DEBUG("node: ", node->get_node_type_name(), ", expr: ", get_infix_string(node), ", current_val: ", current_val_)

#define HYDLA_LOGGER_NODE_VISIT \
  ;
//  HYDLA_LOGGER_DEBUG("visit node: ", node->get_node_type_name(), ", expr: ", get_infix_string(node))

namespace hydla {
namespace interval {

/// share constants to take advantage of dependency
itvd AffineTreeVisitor::pi = kv::constants<kv::interval<double> >::pi();
itvd AffineTreeVisitor::e = kv::constants<kv::interval<double> >::e();

class ApproximateException:public std::runtime_error{
public:
  ApproximateException(const std::string& msg):
    std::runtime_error("error occurred in approximation: " + msg){}
};

AffineTreeVisitor::AffineTreeVisitor(parameter_idx_map_t &map, variable_map_t &vm):parameter_idx_map_(&map), variable_map(vm)
{}

AffineTreeVisitor::AffineTreeVisitor(variable_map_t &vm): variable_map(vm), pm_external(true)
{
  parameter_idx_map_ = new parameter_idx_map_t();
  pm_external = true;
}

AffineTreeVisitor::~AffineTreeVisitor()
{
  if(pm_external)delete parameter_idx_map_;
}

void AffineTreeVisitor::set_current_time(itvd itv)
{
  ++time_idx;
  current_time = itv;
}

AffineMixedValue AffineTreeVisitor::approximate(const node_sptr &node)
{
  differential_count = 0;
  accept(node);
  return current_val_;
}

void AffineTreeVisitor::visit(boost::shared_ptr<hydla::symbolic_expression::Plus> node)
{
  HYDLA_LOGGER_NODE_VISIT;
  accept(node->get_lhs());
  AffineMixedValue lhs = current_val_;
  accept(node->get_rhs());
  AffineMixedValue rhs = current_val_;
  current_val_ = lhs + rhs;
  HYDLA_LOGGER_NODE_VALUE;
  return;
}

void AffineTreeVisitor::visit(boost::shared_ptr<hydla::symbolic_expression::Subtract> node)
{
  HYDLA_LOGGER_NODE_VISIT;
  accept(node->get_lhs());
  AffineMixedValue lhs = current_val_;
  accept(node->get_rhs());
  AffineMixedValue rhs = current_val_;
  current_val_ = lhs - rhs;
  HYDLA_LOGGER_NODE_VALUE;
  return;
}

void AffineTreeVisitor::visit(boost::shared_ptr<hydla::symbolic_expression::Times> node)
{
  HYDLA_LOGGER_NODE_VISIT;
  accept(node->get_lhs());
  AffineMixedValue lhs = current_val_;
  accept(node->get_rhs());
  AffineMixedValue rhs = current_val_;
  current_val_ = lhs * rhs;
  HYDLA_LOGGER_NODE_VALUE;
  return;
}

void AffineTreeVisitor::visit(boost::shared_ptr<hydla::symbolic_expression::Divide> node)
{
  HYDLA_LOGGER_NODE_VISIT;
  accept(node->get_lhs());
  AffineMixedValue lhs = current_val_;
  accept(node->get_rhs());
  AffineMixedValue rhs = current_val_;
  current_val_ = lhs / rhs;
  HYDLA_LOGGER_NODE_VALUE;
  return;
}

void AffineTreeVisitor::visit(boost::shared_ptr<hydla::symbolic_expression::Power> node)
{
  HYDLA_LOGGER_NODE_VISIT;
  accept(node->get_lhs());
  AffineMixedValue lhs = current_val_;
  // TODO: 文字列以外で判定する
  std::string rhs_str = get_infix_string(node->get_rhs());
  if(rhs_str == "1/2")
  {
    if(lhs.type == INTEGER)current_val_ = sqrt(itvd(lhs.integer));
    else if(lhs.type == INTERVAL)current_val_ = AffineMixedValue(sqrt(lhs.interval));
    else current_val_ = sqrt(lhs.affine_value);
  }
  else if(rhs_str == "(-1)/2" || rhs_str == "-1/2")
  {
    if(lhs.type == INTEGER)current_val_ = 1/sqrt(itvd(lhs.integer));
    else if(lhs.type == INTERVAL)current_val_ = 1/sqrt(lhs.interval);
    else current_val_ = 1/sqrt(lhs.affine_value);
  }
  else if(rhs_str == "2" && lhs.type == AFFINE)
  {
    current_val_ = square(lhs.affine_value);
  }
  else
  {
    accept(node->get_rhs());
    AffineMixedValue rhs = current_val_;
    current_val_ = lhs ^ rhs;
  }
  HYDLA_LOGGER_NODE_VALUE;
}

void AffineTreeVisitor::visit(boost::shared_ptr<hydla::symbolic_expression::Negative> node)
{
  HYDLA_LOGGER_NODE_VISIT;
  accept(node->get_child());
  current_val_ = -current_val_;
  HYDLA_LOGGER_NODE_VALUE;
  return;
}

void AffineTreeVisitor::visit(boost::shared_ptr<hydla::symbolic_expression::Positive> node)
{
  HYDLA_LOGGER_NODE_VISIT;
  accept(node->get_child());
  HYDLA_LOGGER_NODE_VALUE;
  return;
}

void AffineTreeVisitor::visit(boost::shared_ptr<hydla::symbolic_expression::Pi> node)
{
  current_val_.interval = pi;
  current_val_.type = INTERVAL;
}

void AffineTreeVisitor::visit(boost::shared_ptr<hydla::symbolic_expression::E> node)
{
  current_val_ = AffineMixedValue(e);
}

void AffineTreeVisitor::visit(boost::shared_ptr<hydla::symbolic_expression::Number> node)
{
  HYDLA_LOGGER_NODE_VISIT;
  std::string number_str = node->get_number();

  // try translation to int
  try{
    int integer = lexical_cast<int>(number_str);
    current_val_ = AffineMixedValue(integer);
    HYDLA_LOGGER_NODE_VALUE;
    return;
  }catch(const bad_lexical_cast &){
  }

  //try approximation as double with upper rounding
  kv::interval<double> itv = kv::interval<double>(number_str);
  current_val_ = AffineMixedValue(itv);
  HYDLA_LOGGER_NODE_VALUE;
}

void AffineTreeVisitor::visit(boost::shared_ptr<hydla::symbolic_expression::Float> node)
{
  HYDLA_LOGGER_NODE_VISIT;
  current_val_ = AffineMixedValue(itvd(node->get_number()));
  HYDLA_LOGGER_NODE_VALUE;
}

void AffineTreeVisitor::visit(boost::shared_ptr<hydla::symbolic_expression::Function> node)
{
  HYDLA_LOGGER_NODE_VISIT;
  std::string name = node->get_name();
  if(name == "log")
  {
    if(node->get_arguments_size() != 1)invalid_node(*node);
    accept(node->get_argument(0) );
    if(current_val_.type == INTEGER)current_val_.interval = log(itvd(current_val_.integer));
    else if(current_val_.type == INTERVAL)current_val_.interval = log(current_val_.interval);
    else current_val_.affine_value = log(current_val_.affine_value);
  }
  else if(name == "sin")
  {
    if(node->get_arguments_size() != 1)invalid_node(*node);
    accept(node->get_argument(0) );
    if(current_val_.type == INTEGER)current_val_.interval = sin(itvd(current_val_.integer));
    if(current_val_.type == INTERVAL)current_val_.interval = sin(current_val_.interval);
    else current_val_.affine_value = sin(current_val_.affine_value);
  }
  else if(name == "cos")
  {
    if(node->get_arguments_size() != 1)invalid_node(*node);
    accept(node->get_argument(0) );
    if(current_val_.type == INTEGER)current_val_.interval = cos(itvd(current_val_.integer));
    if(current_val_.type == INTERVAL)current_val_.interval = cos(current_val_.interval);
    else current_val_.affine_value = cos(current_val_.affine_value);
  }
  else if(name == "sinh")
  {
    if(node->get_arguments_size() != 1)invalid_node(*node);
    accept(node->get_argument(0) );
    if(current_val_.type == INTEGER)current_val_.interval = sinh(itvd(current_val_.integer));
    if(current_val_.type == INTERVAL)current_val_.interval = sinh(current_val_.interval);
    else current_val_.affine_value = sinh(current_val_.affine_value);
  }
  else if(name == "cosh")
  {
    if(node->get_arguments_size() != 1)invalid_node(*node);
    accept(node->get_argument(0) );
    if(current_val_.type == INTEGER)current_val_.interval = cosh(itvd(current_val_.integer));
    if(current_val_.type == INTERVAL)current_val_.interval = cosh(current_val_.interval);
    else current_val_.affine_value = cosh(current_val_.affine_value);
  }
  else
  {
    invalid_node(*node);
  }
  HYDLA_LOGGER_NODE_VALUE;
}

void AffineTreeVisitor::visit(boost::shared_ptr<hydla::symbolic_expression::Parameter> node)
{
  HYDLA_LOGGER_NODE_VISIT;
  current_val_ = affine_t();
  parameter_t param(node->get_name(),
                    node->get_differential_count(),
                    node->get_phase_id());
  parameter_idx_map_t::left_iterator it = parameter_idx_map_->left.find(param);
  int idx;
  if(it == parameter_idx_map_->left.end())
  {
    idx = ++affine_t::maxnum();
    parameter_idx_map_->insert(
      parameter_idx_t(param, affine_t::maxnum()));
  }
  else
  {
    idx = it->second;
  }
  current_val_.affine_value.a.resize(idx + 1);
  current_val_.affine_value.a(idx) = 1;
  current_val_.affine_value.er = 0;
  HYDLA_LOGGER_NODE_VALUE;
  return;
}

void AffineTreeVisitor::visit(boost::shared_ptr<symbolic_expression::Variable> node)
{
  HYDLA_LOGGER_NODE_VISIT;
  simulator::Variable variable(node->get_name(), differential_count);
  if(variable_map.find(variable) == variable_map.end())throw ApproximateException("unknown variable: " + variable.get_string() );
  if(!variable_map[variable].unique())throw ApproximateException("the value of a variable must be unique: "+ variable.get_string());
  accept(variable_map[variable].get_unique_value().get_node());
  HYDLA_LOGGER_NODE_VALUE;
  return;
}

void AffineTreeVisitor::visit(boost::shared_ptr<Differential> node)
{
  HYDLA_LOGGER_NODE_VISIT;
  differential_count++;
  // TODO: 変数以外の微分値は扱えないので、その判定もしたい。
  accept(node->get_child());
  differential_count--;
  return;
}

void AffineTreeVisitor::visit(boost::shared_ptr<SymbolicT> node)
{
  HYDLA_LOGGER_NODE_VISIT;
  current_val_ = AffineMixedValue(affine_t());
  parameter_t param("t",
                    -1,
                    time_idx);
  parameter_idx_map_t::left_iterator it = parameter_idx_map_->left.find(param);
  int idx;
  if(it == parameter_idx_map_->left.end())
  {
    idx = ++affine_t::maxnum();
    parameter_idx_map_->insert(
      parameter_idx_t(param, affine_t::maxnum()));
  }
  else
  {
    idx = it->second;
  }
  current_val_.affine_value = affine_t(current_time);
  HYDLA_LOGGER_NODE_VALUE;
  return;
}

void AffineTreeVisitor::invalid_node(symbolic_expression::Node& node)
{
  throw ApproximateException("invalid node: " + node.get_string());
}

#define DEFINE_INVALID_NODE(NODE_NAME)                           \
void AffineTreeVisitor::visit(boost::shared_ptr<NODE_NAME> node) \
{                                                                \
  HYDLA_LOGGER_DEBUG("");                                        \
  invalid_node(*node);                                           \
}

DEFINE_INVALID_NODE(ConstraintDefinition)
DEFINE_INVALID_NODE(ProgramDefinition)
DEFINE_INVALID_NODE(ConstraintCaller)
DEFINE_INVALID_NODE(ProgramCaller)
DEFINE_INVALID_NODE(Constraint)
DEFINE_INVALID_NODE(Ask)
DEFINE_INVALID_NODE(Tell)

DEFINE_INVALID_NODE(Equal)
DEFINE_INVALID_NODE(UnEqual)

DEFINE_INVALID_NODE(Less)
DEFINE_INVALID_NODE(LessEqual)

DEFINE_INVALID_NODE(Greater)
DEFINE_INVALID_NODE(GreaterEqual)

DEFINE_INVALID_NODE(LogicalAnd)
DEFINE_INVALID_NODE(LogicalOr)

DEFINE_INVALID_NODE(Weaker)
DEFINE_INVALID_NODE(Parallel)

DEFINE_INVALID_NODE(Always)

DEFINE_INVALID_NODE(Previous)

DEFINE_INVALID_NODE(Print)
DEFINE_INVALID_NODE(PrintPP)
DEFINE_INVALID_NODE(PrintIP)
DEFINE_INVALID_NODE(Scan)
DEFINE_INVALID_NODE(Exit)
DEFINE_INVALID_NODE(Abort)
DEFINE_INVALID_NODE(SVtimer)

DEFINE_INVALID_NODE(Not)

DEFINE_INVALID_NODE(UnsupportedFunction)

DEFINE_INVALID_NODE(ImaginaryUnit)
DEFINE_INVALID_NODE(Infinity)
DEFINE_INVALID_NODE(True)
DEFINE_INVALID_NODE(False)

DEFINE_INVALID_NODE(ProgramList)
DEFINE_INVALID_NODE(ConditionalProgramList)
DEFINE_INVALID_NODE(ExpressionList)
DEFINE_INVALID_NODE(ConditionalExpressionList)
DEFINE_INVALID_NODE(EachElement)
DEFINE_INVALID_NODE(DifferentVariable)
DEFINE_INVALID_NODE(ExpressionListElement)
DEFINE_INVALID_NODE(ExpressionListCaller)
DEFINE_INVALID_NODE(ExpressionListDefinition)
DEFINE_INVALID_NODE(ProgramListElement)
DEFINE_INVALID_NODE(ProgramListCaller)
DEFINE_INVALID_NODE(ProgramListDefinition)
DEFINE_INVALID_NODE(Range)
DEFINE_INVALID_NODE(Union)
DEFINE_INVALID_NODE(Intersection)
DEFINE_INVALID_NODE(SumOfList)
DEFINE_INVALID_NODE(MulOfList)
DEFINE_INVALID_NODE(SizeOfList)

}
}


#ifndef STAN_OPTIMIZATION_BFGS_HPP
#define STAN_OPTIMIZATION_BFGS_HPP

#include <stan/math/prim/mat.hpp>
#include <stan/model/log_prob_propto.hpp>
#include <stan/model/log_prob_grad.hpp>
#include <stan/optimization/bfgs_linesearch.hpp>
#include <stan/optimization/bfgs_update.hpp>
#include <stan/optimization/lbfgs_update.hpp>
#include <boost/math/special_functions/fpclassify.hpp>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <limits>
#include <string>
#include <vector>

namespace stan {
  namespace optimization {
    typedef enum {
      TERM_SUCCESS = 0,
      TERM_ABSX = 10,
      TERM_ABSF = 20,
      TERM_RELF = 21,
      TERM_ABSGRAD = 30,
      TERM_RELGRAD = 31,
      TERM_MAXIT = 40,
      TERM_LSFAIL = -1
    } TerminationCondition;

    template<typename Scalar = double>
    class ConvergenceOptions {
    public:
      ConvergenceOptions() {
        maxIts = 10000;
        fScale = 1.0;

        tolAbsX = 1e-8;
        tolAbsF = 1e-12;
        tolAbsGrad = 1e-8;

        tolRelF = 1e+4;
        tolRelGrad = 1e+3;
      }
      size_t maxIts;
      Scalar tolAbsX;
      Scalar tolAbsF;
      Scalar tolRelF;
      Scalar fScale;
      Scalar tolAbsGrad;
      Scalar tolRelGrad;
    };

    template<typename Scalar = double>
    class LSOptions {
    public:
      LSOptions() {
        c1 = 1e-4;
        c2 = 0.9;
        alpha0 = 1e-3;
        minAlpha = 1e-12;
        maxLSIts = 20;
        maxLSRestarts = 10;
      }
      Scalar c1;
      Scalar c2;
      Scalar alpha0;
      Scalar minAlpha;
      Scalar maxLSIts;
      Scalar maxLSRestarts;
    };
    template<typename FunctorType, typename QNUpdateType,
             typename Scalar = double, int DimAtCompile = Eigen::Dynamic>
    class BFGSMinimizer {
    public:
      typedef Eigen::Matrix<Scalar, DimAtCompile, 1> VectorT;
      typedef Eigen::Matrix<Scalar, DimAtCompile, DimAtCompile> HessianT;

    protected:
      FunctorType &_func;
      VectorT _gk, _gk_1, _xk_1, _xk, _pk, _pk_1;
      Scalar _fk, _fk_1, _alphak_1;
      Scalar _alpha, _alpha0;
      size_t _itNum;
      std::string _note;
      QNUpdateType _qn;

    public:
      LSOptions<Scalar> _ls_opts;
      ConvergenceOptions<Scalar> _conv_opts;

      QNUpdateType &get_qnupdate() { return _qn; }
      const QNUpdateType &get_qnupdate() const { return _qn; }

      const Scalar &curr_f() const { return _fk; }
      const VectorT &curr_x() const { return _xk; }
      const VectorT &curr_g() const { return _gk; }
      const VectorT &curr_p() const { return _pk; }

      const Scalar &prev_f() const { return _fk_1; }
      const VectorT &prev_x() const { return _xk_1; }
      const VectorT &prev_g() const { return _gk_1; }
      const VectorT &prev_p() const { return _pk_1; }
      Scalar prev_step_size() const { return _pk_1.norm()*_alphak_1; }

      inline Scalar rel_grad_norm() const {
        return -_pk.dot(_gk) / std::max(std::fabs(_fk), _conv_opts.fScale);
      }
      inline Scalar rel_obj_decrease() const {
        return std::fabs(_fk_1 - _fk) / std::max(std::fabs(_fk_1),
                                                 std::max(std::fabs(_fk),
                                                          _conv_opts.fScale));
      }

      const Scalar &alpha0() const { return _alpha0; }
      const Scalar &alpha() const { return _alpha; }
      const size_t iter_num() const { return _itNum; }

      const std::string &note() const { return _note; }

      std::string get_code_string(int retCode) {
        switch (retCode) {
          case TERM_SUCCESS:
            return std::string("Successful step completed");
          case TERM_ABSF:
            return std::string("Convergence detected: absolute change "
                               "in objective function was below tolerance");
          case TERM_RELF:
            return std::string("Convergence detected: relative change "
                               "in objective function was below tolerance");
          case TERM_ABSGRAD:
            return std::string("Convergence detected: "
                               "gradient norm is below tolerance");
          case TERM_RELGRAD:
            return std::string("Convergence detected: relative "
                               "gradient magnitude is below tolerance");
          case TERM_ABSX:
            return std::string("Convergence detected: "
                               "absolute parameter change was below tolerance");
          case TERM_MAXIT:
            return std::string("Maximum number of iterations hit, "
                               "may not be at an optima");
          case TERM_LSFAIL:
            return std::string("Line search failed to achieve a sufficient "
                               "decrease, no more progress can be made");
          default:
            return std::string("Unknown termination code");
        }
      }

      explicit BFGSMinimizer(FunctorType &f) : _func(f) { }

      void initialize(const VectorT &x0) {
        int ret;
        _xk = x0;
        ret = _func(_xk, _fk, _gk);
        if (ret) {
          throw std::runtime_error("Error evaluating initial BFGS point.");
        }
        _pk = -_gk;

        _itNum = 0;
        _note = "";
      }

      int step() {
        Scalar gradNorm, stepNorm;
        VectorT sk, yk;
        int retCode(0);
        int resetB(0);

        _itNum++;

        if (_itNum == 1) {
          resetB = 1;
          _note = "";
        } else {
          resetB = 0;
          _note = "";
        }

        while (true) {
          if (resetB) {
            // Reset the Hessian approximation
            _pk.noalias() = -_gk;
          }

          // Get an initial guess for the step size (alpha)
          if (_itNum > 1 && resetB != 2) {
            // use cubic interpolation based on the previous step
            _alpha0 = _alpha = std::min(1.0,
                                        1.01*CubicInterp(_gk_1.dot(_pk_1),
                                                         _alphak_1,
                                                         _fk - _fk_1,
                                                         _gk.dot(_pk_1),
                                                         _ls_opts.minAlpha,
                                                         1.0));
          } else {
            // On the first step (or, after a reset) use the default step size
            _alpha0 = _alpha = _ls_opts.alpha0;
          }

          // Perform the line search.  If successful, the results are in the
          // variables: _xk_1, _fk_1 and _gk_1.
          retCode = WolfeLineSearch(_func, _alpha, _xk_1, _fk_1, _gk_1,
                                    _pk, _xk, _fk, _gk,
                                    _ls_opts.c1, _ls_opts.c2,
                                    _ls_opts.minAlpha,
                                    _ls_opts.maxLSIts,
                                    _ls_opts.maxLSRestarts);
          if (retCode) {
            // Line search failed...
            if (resetB) {
              // did a Hessian reset and it still failed,
              // and nothing left to try
              retCode = TERM_LSFAIL;
              return retCode;
            } else {
              // try resetting the Hessian approximation
              resetB = 2;
              _note += "LS failed, Hessian reset";
              continue;
            }
          } else {
            break;
          }
        }

        // Swap things so that k is the most recent iterate
        std::swap(_fk, _fk_1);
        _xk.swap(_xk_1);
        _gk.swap(_gk_1);
        _pk.swap(_pk_1);

        sk.noalias() = _xk - _xk_1;
        yk.noalias() = _gk - _gk_1;

        gradNorm = _gk.norm();
        stepNorm = sk.norm();

        // Update QN approximation
        if (resetB) {
          // If the QN approximation was reset, automatically scale it
          // and update the step-size accordingly
          Scalar B0fact = _qn.update(yk, sk, true);
          _pk_1 /= B0fact;
          _alphak_1 = _alpha*B0fact;
        } else {
          _qn.update(yk, sk);
          _alphak_1 = _alpha;
        }
        // Compute search direction for next step
        _qn.search_direction(_pk, _gk);

        // Check for convergence
        if (std::fabs(_fk_1 - _fk) < _conv_opts.tolAbsF) {
          // Objective function improvement wasn't sufficient
          retCode = TERM_ABSF;
        } else if (gradNorm < _conv_opts.tolAbsGrad) {
          retCode = TERM_ABSGRAD;  // Gradient norm was below threshold
        } else if (stepNorm < _conv_opts.tolAbsX) {
          retCode = TERM_ABSX;  // Change in x was too small
        } else if (_itNum >= _conv_opts.maxIts) {
          retCode = TERM_MAXIT;  // Max number of iterations hit
        } else if (rel_obj_decrease()
                 < _conv_opts.tolRelF
                 * std::numeric_limits<Scalar>::epsilon()) {
          // Relative improvement in objective function wasn't sufficient
          retCode = TERM_RELF;
        } else if (rel_grad_norm()
                   < _conv_opts.tolRelGrad
                   * std::numeric_limits<Scalar>::epsilon()) {
          // Relative gradient norm was below threshold
          retCode = TERM_RELGRAD;
        } else {
          // Step was successful more progress to be made
          retCode = TERM_SUCCESS;
        }

        return retCode;
      }

      int minimize(VectorT &x0) {
        int retcode;
        initialize(x0);
        while (!(retcode = step()))
          continue;
        x0 = _xk;
        return retcode;
      }
    };

    template <class M>
    class ModelAdaptor {
    private:
      M& _model;
      std::vector<int> _params_i;
      std::ostream* _msgs;
      std::vector<double> _x, _g;
      size_t _fevals;

    public:
      ModelAdaptor(M& model,
                   const std::vector<int>& params_i,
                   std::ostream* msgs)
      : _model(model), _params_i(params_i), _msgs(msgs), _fevals(0) {}

      size_t fevals() const { return _fevals; }
      int operator()(const Eigen::Matrix<double, Eigen::Dynamic, 1> &x,
                     double &f) {
        using Eigen::Matrix;
        using Eigen::Dynamic;
        using stan::math::index_type;
        using stan::model::log_prob_propto;
        typedef typename index_type<Matrix<double, Dynamic, 1> >::type idx_t;

        _x.resize(x.size());
        for (idx_t i = 0; i < x.size(); i++)
          _x[i] = x[i];

        try {
          f = - log_prob_propto<false>(_model, _x, _params_i, _msgs);
        } catch (const std::exception& e) {
          if (_msgs)
            (*_msgs) << e.what() << std::endl;
          return 1;
        }

        if (boost::math::isfinite(f)) {
          return 0;
        } else {
          if (_msgs)
            *_msgs << "Error evaluating model log probability: "
                      "Non-finite function evaluation." << std::endl;
          return 2;
        }
      }
      int operator()(const Eigen::Matrix<double, Eigen::Dynamic, 1> &x,
                     double &f,
                     Eigen::Matrix<double, Eigen::Dynamic, 1> &g) {
        using Eigen::Matrix;
        using Eigen::Dynamic;
        using stan::math::index_type;
        using stan::model::log_prob_grad;
        typedef typename index_type<Matrix<double, Dynamic, 1> >::type idx_t;

        _x.resize(x.size());
        for (idx_t i = 0; i < x.size(); i++)
          _x[i] = x[i];

        _fevals++;

        try {
          f = - log_prob_grad<true, false>(_model, _x, _params_i, _g, _msgs);
        } catch (const std::exception& e) {
          if (_msgs)
            (*_msgs) << e.what() << std::endl;
          return 1;
        }

        g.resize(_g.size());
        for (size_t i = 0; i < _g.size(); i++) {
          if (!boost::math::isfinite(_g[i])) {
            if (_msgs)
              *_msgs << "Error evaluating model log probability: "
                                 "Non-finite gradient." << std::endl;
            return 3;
          }
          g[i] = -_g[i];
        }

        if (boost::math::isfinite(f)) {
          return 0;
        } else {
          if (_msgs)
            *_msgs << "Error evaluating model log probability: "
                   << "Non-finite function evaluation."
                   << std::endl;
          return 2;
        }
      }
      int df(const Eigen::Matrix<double, Eigen::Dynamic, 1> &x,
             Eigen::Matrix<double, Eigen:: Dynamic, 1> &g) {
        double f;
        return (*this)(x, f, g);
      }
    };

    template<typename M, typename QNUpdateType, typename Scalar = double,
             int DimAtCompile = Eigen::Dynamic>
    class BFGSLineSearch
      : public BFGSMinimizer<ModelAdaptor<M>, QNUpdateType,
                             Scalar, DimAtCompile> {
    private:
      ModelAdaptor<M> _adaptor;

    public:
      typedef BFGSMinimizer<ModelAdaptor<M>, QNUpdateType, Scalar, DimAtCompile>
      BFGSBase;
      typedef typename BFGSBase::VectorT vector_t;
      typedef typename stan::math::index_type<vector_t>::type idx_t;

      BFGSLineSearch(M& model,
                     const std::vector<double>& params_r,
                     const std::vector<int>& params_i,
                     std::ostream* msgs = 0)
        : BFGSBase(_adaptor),
          _adaptor(model, params_i, msgs) {
        initialize(params_r);
      }

      void initialize(const std::vector<double>& params_r) {
        Eigen::Matrix<double, Eigen::Dynamic, 1> x;
        x.resize(params_r.size());
        for (size_t i = 0; i < params_r.size(); i++)
          x[i] = params_r[i];
        BFGSBase::initialize(x);
      }

      size_t grad_evals() { return _adaptor.fevals(); }
      double logp() { return -(this->curr_f()); }
      double grad_norm() { return this->curr_g().norm(); }
      void grad(std::vector<double>& g) {
        const vector_t &cg(this->curr_g());
        g.resize(cg.size());
        for (idx_t i = 0; i < cg.size(); i++)
          g[i] = -cg[i];
      }
      void params_r(std::vector<double>& x) {
        const vector_t &cx(this->curr_x());
        x.resize(cx.size());
        for (idx_t i = 0; i < cx.size(); i++)
          x[i] = cx[i];
      }
    };

  }

}

#endif


/**
 *****************************************************************************
 * @author     This file is part of libsnark, developed by SCIPR Lab
 *             and contributors (see AUTHORS).
 * @copyright  MIT license (see LICENSE file)
 *****************************************************************************/
#include <fstream>
#include <iostream>
#ifndef MINDEPS
#include <boost/program_options.hpp>
#endif

#include "common/default_types/tinyram_ppzksnark_pp.hpp"
#include "zk_proof_systems/ppzksnark/ram_ppzksnark/ram_ppzksnark.hpp"
#include "relations/ram_computations/rams/tinyram/tinyram_params.hpp"

#ifndef MINDEPS
namespace po = boost::program_options;

bool process_verifier_command_line(const int argc, const char** argv,
                                   std::string &processed_assembly_fn,
                                   std::string &verification_key_fn,
                                   std::string &primary_input_fn,
                                   std::string &proof_fn,
                                   std::string &verification_result_fn)
{
    try
    {
        po::options_description desc("Usage");
        desc.add_options()
            ("help", "print this help message")
            ("processed_assembly", po::value<std::string>(&processed_assembly_fn)->required())
            ("verification_key", po::value<std::string>(&verification_key_fn)->required())
            ("primary_input", po::value<std::string>(&primary_input_fn)->required())
            ("proof", po::value<std::string>(&proof_fn)->required())
            ("verification_result", po::value<std::string>(&verification_result_fn)->required());

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

        if (vm.count("help"))
        {
            std::cout << desc << "\n";
            return false;
        }

        po::notify(vm);
    }
    catch(std::exception& e)
    {
        std::cerr << "Error: " << e.what() << "\n";
        return false;
    }

    return true;
}
#endif

using namespace libsnark;

int main(int argc, const char * argv[])
{
    default_tinyram_ppzksnark_pp::init_public_params();

#ifdef MINDEPS
    std::string processed_assembly_fn = "processed.txt";
    std::string verification_key_fn = "verification_key.txt";
    std::string proof_fn = "proof.txt";
    std::string primary_input_fn = "primary_input.txt";
    std::string verification_result_fn = "verification_result.txt";
#else
    std::string processed_assembly_fn;
    std::string verification_key_fn;
    std::string proof_fn;
    std::string primary_input_fn;
    std::string verification_result_fn;

    if (!process_verifier_command_line(argc, argv, processed_assembly_fn, verification_key_fn, primary_input_fn, proof_fn, verification_result_fn))
    {
        return 1;
    }
#endif
    start_profiling();

    ram_ppzksnark_verification_key<default_tinyram_ppzksnark_pp> vk;
    std::ifstream vk_file(verification_key_fn);
    vk_file >> vk;
    vk_file.close();

    std::ifstream processed(processed_assembly_fn);
    tinyram_program program = load_preprocessed_program(vk.ap, processed);

    std::ifstream f_primary_input(primary_input_fn);
    tinyram_input_tape primary_input = load_tape(f_primary_input);

    std::ifstream proof_file(proof_fn);
    ram_ppzksnark_proof<default_tinyram_ppzksnark_pp> pi;
    proof_file >> pi;
    proof_file.close();

    const ram_boot_trace<default_tinyram_ppzksnark_pp> boot_trace = tinyram_boot_trace_from_program_and_input(vk.ap, vk.primary_input_size_bound, program, primary_input);
    const bool bit = ram_ppzksnark_verifier<default_tinyram_ppzksnark_pp>(vk, boot_trace, pi);

    printf("================================================================================\n");
    printf("The verification result is: %s\n", (bit ? "PASS" : "FAIL"));
    printf("================================================================================\n");
    std::ofstream vr_file(verification_result_fn);
    vr_file << bit << "\n";
    vr_file.close();
}


// Boost.Geometry

// Copyright (c) 2020-2021, Oracle and/or its affiliates.

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

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

#ifndef BOOST_GEOMETRY_STRATEGIES_RELATE_GEOGRAPHIC_HPP
#define BOOST_GEOMETRY_STRATEGIES_RELATE_GEOGRAPHIC_HPP

// TEMP - move to strategy
#include <boost/geometry/strategies/agnostic/point_in_box_by_side.hpp>
#include <boost/geometry/strategies/cartesian/box_in_box.hpp>
#include <boost/geometry/strategies/geographic/intersection.hpp>
#include <boost/geometry/strategies/geographic/point_in_poly_winding.hpp>
#include <boost/geometry/strategies/spherical/point_in_point.hpp>
#include <boost/geometry/strategies/spherical/disjoint_box_box.hpp>

#include <boost/geometry/strategies/envelope/geographic.hpp>
#include <boost/geometry/strategies/relate/services.hpp>
#include <boost/geometry/strategies/detail.hpp>

#include <boost/geometry/strategy/geographic/area.hpp>
#include <boost/geometry/strategy/geographic/area_box.hpp>

#include <boost/geometry/util/type_traits.hpp>

namespace boost { namespace geometry
{

namespace strategies { namespace relate
{

template
<
    typename FormulaPolicy = strategy::andoyer,
    typename Spheroid = srs::spheroid<double>,
    typename CalculationType = void
>
class geographic
    : public strategies::envelope::geographic<FormulaPolicy, Spheroid, CalculationType>
{
    using base_t = strategies::envelope::geographic<FormulaPolicy, Spheroid, CalculationType>;

public:
    geographic() = default;

    explicit geographic(Spheroid const& spheroid)
        : base_t(spheroid)
    {}

    // area

    template <typename Geometry>
    auto area(Geometry const&,
              std::enable_if_t<! util::is_box<Geometry>::value> * = nullptr) const
    {
        return strategy::area::geographic
            <
                FormulaPolicy,
                strategy::default_order<FormulaPolicy>::value,
                Spheroid, CalculationType
            >(base_t::m_spheroid);
    }

    template <typename Geometry>
    auto area(Geometry const&,
              std::enable_if_t<util::is_box<Geometry>::value> * = nullptr) const
    {
        return strategy::area::geographic_box
            <
                Spheroid, CalculationType
            >(base_t::m_spheroid);
    }

    // covered_by

    template <typename Geometry1, typename Geometry2>
    static auto covered_by(Geometry1 const&, Geometry2 const&,
                           std::enable_if_t
                                <
                                    util::is_pointlike<Geometry1>::value
                                 && util::is_box<Geometry2>::value
                                > * = nullptr)
    {
        return strategy::covered_by::spherical_point_box();
    }

    template <typename Geometry1, typename Geometry2>
    static auto covered_by(Geometry1 const&, Geometry2 const&,
                           std::enable_if_t
                            <
                                util::is_box<Geometry1>::value
                             && util::is_box<Geometry2>::value
                            > * = nullptr)
    {
        return strategy::covered_by::spherical_box_box();
    }

    // disjoint

    template <typename Geometry1, typename Geometry2>
    static auto disjoint(Geometry1 const&, Geometry2 const&,
                         std::enable_if_t
                            <
                                util::is_box<Geometry1>::value
                             && util::is_box<Geometry2>::value
                            > * = nullptr)
    {
        return strategy::disjoint::spherical_box_box();
    }

    template <typename Geometry1, typename Geometry2>
    auto disjoint(Geometry1 const&, Geometry2 const&,
                  std::enable_if_t
                    <
                        util::is_segment<Geometry1>::value
                        && util::is_box<Geometry2>::value
                    > * = nullptr) const
    {
        // NOTE: Inconsistent name
        // The only disjoint(Seg, Box) strategy that takes CalculationType.
        return strategy::disjoint::segment_box_geographic
            <
                FormulaPolicy, Spheroid, CalculationType
            >(base_t::m_spheroid);
    }

    // relate

    template <typename Geometry1, typename Geometry2>
    static auto relate(Geometry1 const&, Geometry2 const&,
                       std::enable_if_t
                            <
                                util::is_pointlike<Geometry1>::value
                             && util::is_pointlike<Geometry2>::value
                            > * = nullptr)
    {
        return strategy::within::spherical_point_point();
    }

    template <typename Geometry1, typename Geometry2>
    auto relate(Geometry1 const&, Geometry2 const&,
                std::enable_if_t
                    <
                        util::is_pointlike<Geometry1>::value
                        && ( util::is_linear<Geometry2>::value
                        || util::is_polygonal<Geometry2>::value )
                    > * = nullptr) const
    {
        return strategy::within::geographic_winding
                <
                    void, void, FormulaPolicy, Spheroid, CalculationType
                >(base_t::m_spheroid);
    }

    //template <typename Geometry1, typename Geometry2>
    auto relate(/*Geometry1 const&, Geometry2 const&,
                std::enable_if_t
                    <
                        ( util::is_linear<Geometry1>::value
                        || util::is_polygonal<Geometry1>::value )
                        && ( util::is_linear<Geometry2>::value
                        || util::is_polygonal<Geometry2>::value )
                    > * = nullptr*/) const
    {
        return strategy::intersection::geographic_segments
            <
                FormulaPolicy,
                strategy::default_order<FormulaPolicy>::value,
                Spheroid, CalculationType
            >(base_t::m_spheroid);
    }

    // side

    auto side() const
    {
        return strategy::side::geographic
            <
                FormulaPolicy, Spheroid, CalculationType
            >(base_t::m_spheroid);
    }

    // within

    template <typename Geometry1, typename Geometry2>
    static auto within(Geometry1 const&, Geometry2 const&,
                       std::enable_if_t
                            <
                                util::is_pointlike<Geometry1>::value
                                && util::is_box<Geometry2>::value
                            > * = nullptr)
    {
        return strategy::within::spherical_point_box();
    }

    template <typename Geometry1, typename Geometry2>
    static auto within(Geometry1 const&, Geometry2 const&,
                       std::enable_if_t
                            <
                                util::is_box<Geometry1>::value
                             && util::is_box<Geometry2>::value
                            > * = nullptr)
    {
        return strategy::within::spherical_box_box();
    }
};

namespace services
{

template <typename Geometry1, typename Geometry2>
struct default_strategy<Geometry1, Geometry2, geographic_tag, geographic_tag>
{
    using type = strategies::relate::geographic<>;
};

template <typename FormulaPolicy, typename Spheroid, typename CalculationType>
struct strategy_converter<strategy::disjoint::segment_box_geographic<FormulaPolicy, Spheroid, CalculationType>>
{
    static auto get(strategy::disjoint::segment_box_geographic<FormulaPolicy, Spheroid, CalculationType> const& s)
    {
        return strategies::relate::geographic
            <
                FormulaPolicy,
                Spheroid,
                CalculationType
            >(s.model());
    }
};

template <typename P1, typename P2, typename FormulaPolicy, typename Spheroid, typename CalculationType>
struct strategy_converter<strategy::within::geographic_winding<P1, P2, FormulaPolicy, Spheroid, CalculationType>>
{
    static auto get(strategy::within::geographic_winding<P1, P2, FormulaPolicy, Spheroid, CalculationType> const& s)
    {
        return strategies::relate::geographic
            <
                FormulaPolicy,
                Spheroid,
                CalculationType
            >(s.model());
    }
};

template <typename FormulaPolicy, std::size_t SeriesOrder, typename Spheroid, typename CalculationType>
struct strategy_converter<strategy::intersection::geographic_segments<FormulaPolicy, SeriesOrder, Spheroid, CalculationType>>
{
    struct altered_strategy
        : strategies::relate::geographic<FormulaPolicy, Spheroid, CalculationType>
    {
        typedef strategies::relate::geographic<FormulaPolicy, Spheroid, CalculationType> base_t;

        explicit altered_strategy(Spheroid const& spheroid)
            : base_t(spheroid)
        {}

        template <typename Geometry>
        auto area(Geometry const&) const
        {
            return strategy::area::geographic
                <
                    FormulaPolicy, SeriesOrder, Spheroid, CalculationType
                >(base_t::m_spheroid);
        }

        using base_t::relate;

        auto relate(/*...*/) const
        {
            return strategy::intersection::geographic_segments
                <
                    FormulaPolicy, SeriesOrder, Spheroid, CalculationType
                >(base_t::m_spheroid);
        }
    };

    static auto get(strategy::intersection::geographic_segments<FormulaPolicy, SeriesOrder, Spheroid, CalculationType> const& s)
    {
        return altered_strategy(s.model());
    }
};

template <typename FormulaPolicy, typename Spheroid, typename CalculationType>
struct strategy_converter<strategy::within::geographic_point_box_by_side<FormulaPolicy, Spheroid, CalculationType>>
{
    struct altered_strategy
        : strategies::relate::geographic<FormulaPolicy, Spheroid, CalculationType>
    {
        altered_strategy(Spheroid const& spheroid)
            : strategies::relate::geographic<FormulaPolicy, Spheroid, CalculationType>(spheroid)
        {}

        template <typename Geometry1, typename Geometry2>
        auto covered_by(Geometry1 const&, Geometry2 const&,
                        std::enable_if_t
                            <
                                util::is_pointlike<Geometry1>::value
                                && util::is_box<Geometry2>::value
                            > * = nullptr) const
        {
            return strategy::covered_by::geographic_point_box_by_side
                <
                    FormulaPolicy, Spheroid, CalculationType
                >(this->model());
        }

        template <typename Geometry1, typename Geometry2>
        auto within(Geometry1 const&, Geometry2 const&,
                    std::enable_if_t
                        <
                            util::is_pointlike<Geometry1>::value
                            && util::is_box<Geometry2>::value
                        > * = nullptr) const
        {
            return strategy::within::geographic_point_box_by_side
                <
                    FormulaPolicy, Spheroid, CalculationType
                >(this->model());
        }
    };

    static auto get(strategy::covered_by::geographic_point_box_by_side<FormulaPolicy, Spheroid, CalculationType> const& s)
    {
        return altered_strategy(s.model());
    }

    static auto get(strategy::within::geographic_point_box_by_side<FormulaPolicy, Spheroid, CalculationType> const& s)
    {
        return altered_strategy(s.model());
    }
};

template <typename CalculationType>
struct strategy_converter<strategy::covered_by::geographic_point_box_by_side<CalculationType>>
    : strategy_converter<strategy::within::geographic_point_box_by_side<CalculationType>>
{};

} // namespace services

}} // namespace strategies::relate

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_RELATE_GEOGRAPHIC_HPP


// MIT License
//
// Copyright (c) 2021 Oliver Schick
//
// 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.

#ifndef MPO19_CLUSTERING_22072020
#define MPO19_CLUSTERING_22072020

#include <mpo19/typedefs.hpp>
#include <mpo19/cluster.hpp>
#include <boost/range/algorithm.hpp>

namespace mpo19{
template<typename Linkage>
struct hierarchical_clustering : public Linkage {

    using dendrogram_t  = std::vector<cluster>;

    using Linkage::Linkage;

    dendrogram_t operator()(size_t t)
    {
        using std::swap;

        size_t n = Linkage::get_input_size();

        assert(t < n && "t must be strictly smaller than n");

        dendrogram_t dendrogram;

        //Initialize dendrogram
        for(index_t i = 0; i != index_t(n); ++i) {
            dendrogram.emplace_back(cluster::leaf_t(i));
        }

        for(size_t l = 0; l < n - t; ++l) {
            //get indices of clusters to be merged
            auto p = get_merge_indices();

            size_t i = std::get<0>(p);
            size_t j = std::get<1>(p);

            assert(i < j && "By convention i should be smaller than j");
            assert(0 <= i && i < n && "i should be between 0 and n");
            assert(0 <= j && j < n && "j should be between 0 and n");

            //merge the two clusters at position i and j
            merge(dendrogram[i], dendrogram[j], l + 1);
        }

        assert(dendrogram.size() == n && "dendrogram should be of size n here");
        //Erase all clusters that only refer to other clusters through a pointer
        dendrogram.erase(
        boost::remove_if(dendrogram, [](cluster const& c) {
            return !c.is_cluster();
        })
        , dendrogram.end());

        assert(dendrogram.size() == t && "dendrogram should be of size t here");

        return dendrogram;
    }

    dendrogram_t partial_evaluation(size_t t, dendrogram_t dendrogram = dendrogram_t{})
    {
        using std::swap;
        using boost::range::count_if;

        size_t n = Linkage::get_input_size();

        assert(t < n && "t must be strictly smaller than n");

        size_t start;
        if(dendrogram.empty()) {
            //Initialize dendrogram
            for(index_t i = 0; i != index_t(n); ++i) {
                dendrogram.emplace_back(cluster::leaf_t(i));
            }
            start = 0;
        }
        else {
            assert(dendrogram.size() == n);
            start = n - count_if(dendrogram, [](cluster const& c){
                return c.is_cluster();
            });
        }

        for(size_t l = start; l < n - t; ++l) {
            //get indices of clusters to be merged
            auto p = get_merge_indices();

            size_t i = std::get<0>(p);
            size_t j = std::get<1>(p);

            assert(i < j && "By convention i should be smaller than j");
            assert(0 <= i && i < n && "i should be between 0 and n");
            assert(0 <= j && j < n && "j should be between 0 and n");

            //merge the two clusters at position i and j
            merge(dendrogram[i], dendrogram[j], l + 1);
        }

        assert(dendrogram.size() == n && "dendrogram should be of size n here");

        return dendrogram;
    }

private:
    using Linkage::get_merge_indices;
};

}
#endif //MPO19_CLUSTERING_22072020


// Copyright 2018 Hans Dembinski
//
// Distributed under the Boost Software License, version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_HISTOGRAM_ACCUMULATORS_SUM_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_SUM_HPP

#include <boost/histogram/fwd.hpp>
#include <cmath>
#include <type_traits>

namespace boost {
namespace histogram {
namespace accumulators {

/**
  Uses Neumaier algorithm to compute accurate sums.

  The algorithm uses memory for two floats and is three to
  five times slower compared to a simple floating point
  number used to accumulate a sum, but the relative error
  of the sum is at the level of the machine precision,
  independent of the number of samples.

  A. Neumaier, Zeitschrift fuer Angewandte Mathematik
  und Mechanik 54 (1974) 39-51.
*/
template <typename RealType>
class sum {
public:
  sum() = default;

  /// Initialize sum to value
  explicit sum(const RealType& value) noexcept : large_(value) {}

  /// Set sum to value
  sum& operator=(const RealType& value) noexcept {
    large_ = value;
    small_ = 0;
    return *this;
  }

  /// Increment sum by one
  sum& operator++() { return operator+=(1); }

  /// Increment sum by value
  sum& operator+=(const RealType& value) {
    auto temp = large_ + value; // prevent optimization
    if (std::abs(large_) >= std::abs(value))
      small_ += (large_ - temp) + value;
    else
      small_ += (value - temp) + large_;
    large_ = temp;
    return *this;
  }

  /// Scale by value
  sum& operator*=(const RealType& value) {
    large_ *= value;
    small_ *= value;
    return *this;
  }

  template <class T>
  bool operator==(const sum<T>& rhs) const noexcept {
    return large_ == rhs.large_ && small_ == rhs.small_;
  }

  template <class T>
  bool operator!=(const T& rhs) const noexcept {
    return !operator==(rhs);
  }

  /// Return large part of the sum.
  const RealType& large() const { return large_; }

  /// Return small part of the sum.
  const RealType& small() const { return small_; }

  // allow implicit conversion to RealType
  operator RealType() const { return large_ + small_; }

  template <class Archive>
  void serialize(Archive&, unsigned /* version */);

private:
  RealType large_ = RealType();
  RealType small_ = RealType();
};

} // namespace accumulators
} // namespace histogram
} // namespace boost

#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
namespace std {
template <class T, class U>
struct common_type<boost::histogram::accumulators::sum<T>,
                   boost::histogram::accumulators::sum<U>> {
  using type = boost::histogram::accumulators::sum<common_type_t<T, U>>;
};
} // namespace std
#endif

#endif


/*
 * Copyright Nick Thompson, 2020
 * Use, modification and distribution are 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)
 */

#ifndef BOOST_MATH_INTERPOLATORS_QUINTIC_HERMITE_HPP
#define BOOST_MATH_INTERPOLATORS_QUINTIC_HERMITE_HPP
#include <algorithm>
#include <stdexcept>
#include <memory>
#include <boost/math/interpolators/detail/quintic_hermite_detail.hpp>

namespace boost {
namespace math {
namespace interpolators {

template<class RandomAccessContainer>
class quintic_hermite {
public:
    using Real = typename RandomAccessContainer::value_type;
    quintic_hermite(RandomAccessContainer && x, RandomAccessContainer && y, RandomAccessContainer && dydx, RandomAccessContainer && d2ydx2)
     : impl_(std::make_shared<detail::quintic_hermite_detail<RandomAccessContainer>>(std::move(x), std::move(y), std::move(dydx), std::move(d2ydx2)))
    {}

    Real operator()(Real x) const
    {
        return impl_->operator()(x);
    }

    Real prime(Real x) const
    {
        return impl_->prime(x);
    }

    Real double_prime(Real x) const
    {
        return impl_->double_prime(x);
    }

    friend std::ostream& operator<<(std::ostream & os, const quintic_hermite & m)
    {
        os << *m.impl_;
        return os;
    }

    void push_back(Real x, Real y, Real dydx, Real d2ydx2)
    {
        impl_->push_back(x, y, dydx, d2ydx2);
    }

    int64_t bytes() const
    {
        return impl_->bytes() + sizeof(impl_);
    }

    std::pair<Real, Real> domain() const
    {
        return impl_->domain();
    }

private:
    std::shared_ptr<detail::quintic_hermite_detail<RandomAccessContainer>> impl_;
};

template<class RandomAccessContainer>
class cardinal_quintic_hermite {
public:
    using Real = typename RandomAccessContainer::value_type;
    cardinal_quintic_hermite(RandomAccessContainer && y, RandomAccessContainer && dydx, RandomAccessContainer && d2ydx2, Real x0, Real dx)
     : impl_(std::make_shared<detail::cardinal_quintic_hermite_detail<RandomAccessContainer>>(std::move(y), std::move(dydx), std::move(d2ydx2), x0, dx))
    {}

    inline Real operator()(Real x) const {
        return impl_->operator()(x);
    }

    inline Real prime(Real x) const {
        return impl_->prime(x);
    }

    inline Real double_prime(Real x) const
    {
        return impl_->double_prime(x);
    }

    int64_t bytes() const
    {
        return impl_->bytes() + sizeof(impl_);
    }

    std::pair<Real, Real> domain() const
    {
        return impl_->domain();
    }

private:
    std::shared_ptr<detail::cardinal_quintic_hermite_detail<RandomAccessContainer>> impl_;
};

template<class RandomAccessContainer>
class cardinal_quintic_hermite_aos {
public:
    using Point = typename RandomAccessContainer::value_type;
    using Real = typename Point::value_type;
    cardinal_quintic_hermite_aos(RandomAccessContainer && data, Real x0, Real dx)
     : impl_(std::make_shared<detail::cardinal_quintic_hermite_detail_aos<RandomAccessContainer>>(std::move(data), x0, dx))
    {}

    inline Real operator()(Real x) const
    {
        return impl_->operator()(x);
    }

    inline Real prime(Real x) const
    {
        return impl_->prime(x);
    }

    inline Real double_prime(Real x) const
    {
        return impl_->double_prime(x);
    }

    int64_t bytes() const
    {
        return impl_->bytes() + sizeof(impl_);
    }

    std::pair<Real, Real> domain() const
    {
        return impl_->domain();
    }
private:
    std::shared_ptr<detail::cardinal_quintic_hermite_detail_aos<RandomAccessContainer>> impl_;
};

}
}
}
#endif


#ifndef LOOKUP_TABLE_HH__
#define LOOKUP_TABLE_HH__

#include <vector>

#include <boost/math/distributions/chi_squared.hpp>

class LookupTable
{
public:

  /** Creates a new lookup table for the given marginals */
  LookupTable( unsigned n, unsigned n1 );

  /**
     Returns the minimum attainable $p$-value of a given marginal
     value.
  */

  long double operator[]( unsigned rs ) const noexcept
  {
    return _values[rs];
  }

  // Attributes --------------------------------------------------------

  unsigned n()  const noexcept { return _n;  }
  unsigned n1() const noexcept { return _n1; }

private:
  unsigned _n  = 0;
  unsigned _n1 = 0;

  /** Shared distribution for calculating $p$-values */
  static boost::math::chi_squared_distribution<long double> _chi2;

  /** Maps marginals to $p$-values */
  std::vector<long double> _values;
};

#endif


/* CirKit: A circuit toolkit
 * Copyright (C) 2009-2015  University of Bremen
 * Copyright (C) 2015-2017  EPFL
 *
 * 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 "write_numpy.hpp"

#include <fstream>

#include <boost/algorithm/string/join.hpp>
#include <boost/format.hpp>

#include <reversible/pauli_tags.hpp>
#include <reversible/target_tags.hpp>

namespace cirkit
{

/******************************************************************************
 * Types                                                                      *
 ******************************************************************************/

/******************************************************************************
 * Private functions                                                          *
 ******************************************************************************/

std::string identity_padding( const std::string& matrix, unsigned from, unsigned to, unsigned lines )
{
  std::vector<std::string> gates;

  if ( from > 0u )
  {
    gates.push_back( boost::str( boost::format( "np.identity(%d)" ) % ( 1 << from ) ) );
  }

  gates.push_back( matrix );

  if ( to + 1u < lines )
  {
    gates.push_back( boost::str( boost::format( "np.identity(%d)" ) % ( 1 << ( lines - to - 1u ) ) ) );
  }

  switch ( gates.size() )
  {
  case 1u:
    return gates.front();

  case 2u:
    return boost::str( boost::format( "np.kron(%s, %s)" ) % gates[0u] % gates[1u] );

  default:
    return boost::str( boost::format( "nkron(%s)" ) % boost::join( gates, ", " ) );
  }
}

/******************************************************************************
 * Public functions                                                           *
 ******************************************************************************/

void write_numpy( const circuit& circ, std::ostream& os )
{
  const auto n = circ.lines();

  os << "#!/usr/bin/env python3" << std::endl << std::endl;
  os << "import numpy as np" << std::endl;
  os << "from functools import reduce" << std::endl << std::endl;

  os << "X = np.array([[0, 1], [1, 0]])" << std::endl;
  os << "H = 1 / np.sqrt(2) * np.array([[1, 1], [1, -1]])" << std::endl;
  os << "T = np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]])" << std::endl;
  os << "Tdag = np.array([[1, 0], [0, -np.exp(1j * np.pi / 4)]])" << std::endl;
  os << "zc = np.array([[1, 0], [0, 0]])" << std::endl;
  os << "oc = np.array([[0, 0], [0, 1]])" << std::endl;
  os << std::endl;

  os << "def nkron(*args):" << std::endl;
  os << "  return reduce(np.kron, args)" << std::endl;
  os << std::endl;

  os << "gates = []" << std::endl << std::endl;

  for ( const auto& g : circ )
  {
    const auto target = g.targets().front();

    if ( is_toffoli( g ) && g.controls().size() == 1u && g.controls().front().polarity() )
    {
      const auto control = g.controls().front().line();

      if ( control < target )
      {
        const auto act = target - control;
        const auto gate = boost::str( boost::format( "np.kron(zc, np.identity(%d)) + nkron(oc, np.identity(%d), X)" ) % ( 1 << act ) % ( 1 << ( act - 1 ) ) );
        os << boost::format( "gates.append(%s)" ) % identity_padding( gate, control, target, n ) << std::endl;
      }
      else
      {
        const auto act = control - target;
        const auto gate = boost::str( boost::format( "np.kron(np.identity(%d), zc) + nkron(X, np.identity(%d), oc)" ) % ( 1 << act ) % ( 1 << ( act - 1 ) ) );
        os << boost::format( "gates.append(%s)" ) % identity_padding( gate, target, control, n ) << std::endl;
      }
    }
    else if ( is_hadamard( g ) )
    {
      os << boost::format( "gates.append(%s)" ) % identity_padding( "H", target, target, n ) << std::endl;
    }
    else if ( is_pauli( g ) )
    {
      const auto pauli = boost::any_cast<pauli_tag>( g.type() );
      switch ( pauli.axis )
      {
      case pauli_axis::Z:
        if ( pauli.root == 4u )
        {
          os << boost::format( "gates.append(%s)" ) % identity_padding( pauli.adjoint ? "Tdag" : "T", target, target, n ) << std::endl;
        }
        else
        {
          os << "# unsupported gate" << std::endl;
        }
        break;
      default:
        os << "# unsupported gate" << std::endl;
        break;
      }
    }
    else
    {
      os << "# unsupported gate" << std::endl;
    }
  }

  os << std::endl;
  os << "circuit = reduce(np.dot, reversed(gates))" << std::endl;
  os << "print(np.round(circuit))" << std::endl;
}

void write_numpy( const circuit& circ, const std::string& filename )
{
  std::ofstream os( filename.c_str(), std::ofstream::out );

  write_numpy( circ, os );

  os.close();
}

}

// Local Variables:
// c-basic-offset: 2
// eval: (c-set-offset 'substatement-open 0)
// eval: (c-set-offset 'innamespace 0)
// End: