/*
    Copyright (c) 2011-2014 University of Zurich

    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 <plll/config.hpp>
#include "lll2-internal.hpp"
#include "transform.cpp"

#include <sstream>
#include <boost/bind.hpp>

#include <plll/arithmetic.hpp>
#include <plll/rational.hpp>
#if !defined(PLLL_CONFIG_NO_ARITHMETIC_LONGDOUBLE) || !defined(PLLL_CONFIG_NO_ARITHMETIC_DOUBLE)
  #include "nfp-wrapper.hpp"
#endif
#if !defined(PLLL_CONFIG_NO_ARITHMETIC_QUADDOUBLE) || !defined(PLLL_CONFIG_NO_ARITHMETIC_DOUBLEDOUBLE)
  #include "ddqd-wrapper.hpp"
#endif

namespace plll
{
    class LRIISelector : public LRIInterface
    {
    private:
        class Interface
        {
        private:
            long d_max_bits;
            boost::function<LRIInterface*(linalg::math_matrix<arithmetic::Integer> &)> d_interface_generator;
            mutable const LatticeReduction::GramSchmidtInformer * d_gsi;

        public:
            Interface(long max_bits, boost::function<LRIInterface*(linalg::math_matrix<arithmetic::Integer> &)> interfacegen)
                : d_max_bits(max_bits), d_interface_generator(interfacegen), d_gsi(NULL)
            {
            }

            ~Interface()
            {
            }

            LRIInterface * generateInterface(linalg::math_matrix<arithmetic::Integer> & matrix) const
            {
                return d_interface_generator(matrix);
            }

            long maxSupportedBits() const
            {
                return d_max_bits;
            }

            bool isFine(unsigned bits, unsigned dimension) const
            {
                long bits_needed = 2 * bits + arithmetic::approxLog2(dimension) + 4;
                return bits_needed <= d_max_bits;
            }
        };

        std::list<Interface> d_interfaces;
        mutable std::list<Interface>::const_iterator d_interface_gen, d_better_interface_gen;
        mutable STD_AUTO_PTR<LRIInterface> d_interface;
        mutable bool d_continue, d_reselect;
        mutable MaxBitsCallbackFunction d_maxbitscf;

        inline bool isBetter(unsigned bits, unsigned dimension) const
        {
            return (d_better_interface_gen == d_interfaces.end()) ? false : d_better_interface_gen->isFine(bits, dimension);
        }

        void maxBitsCallbackFunction(unsigned dimension, unsigned maxbits)
        {
            if (!d_maxbitscf.empty())
                d_maxbitscf(dimension, maxbits);
            if (d_verbose_function && Verbose::yieldsOutput(d_verbose_outputlevel, LatticeReduction::VL_Chatter))
            {
                std::ostringstream s;
                s << "...base has " << maxbits << " bits...";
                d_verbose_function(LatticeReduction::VL_Chatter, s.str());
            }
            if (!d_interface_gen->isFine(maxbits, dimension) || isBetter(maxbits, dimension))
            {
                d_reselect = true;
                throw change_interface_exception();
            }
        }

        bool isContinuing() const
        {
            if (d_reselect)
                selectInterface(true);
            bool v = d_continue;
            d_continue = false;
            return v;
        }

        unsigned maxBits() const
        {
            unsigned maxbits = 0;
            for (unsigned i = 0; i < d_lattice.rows(); ++i)
                for (unsigned j = 0; j < d_lattice.cols(); ++j)
                {
                    long b = arithmetic::approxLog2(d_lattice(i, j));
                    if (b > maxbits)
                        maxbits = b;
                }
            return maxbits;
        }

        void selectInterface(bool withContinue = false) const
        {
            if (d_interface.get())
            {
                if (d_verbose_function && Verbose::yieldsOutput(d_verbose_outputlevel, LatticeReduction::VL_Information))
                    d_verbose_function(LatticeReduction::VL_Information, "Applying interface change");
                d_interface.reset();
            }

            d_continue = withContinue;
            d_reselect = false;
            unsigned bits = maxBits();
            d_interface_gen = d_interfaces.end();
            for (std::list<Interface>::const_iterator i = d_interfaces.begin(); i != d_interfaces.end(); ++i)
            {
                if (i->isFine(bits, d_lattice.rows()))
                    d_interface_gen = i;
            }
            assert(d_interface_gen != d_interfaces.end());

            if (d_verbose_function && Verbose::yieldsOutput(d_verbose_outputlevel, LatticeReduction::VL_Information))
            {
                std::ostringstream s;
                s << "Selecting interface which supports up to " << d_interface_gen->maxSupportedBits() << " bits";
                d_verbose_function(LatticeReduction::VL_Information, s.str());
            }

            // Generate interface
            d_interface.reset(d_interface_gen->generateInterface(d_lattice));
            if (d_ensure_min_prec)
                d_interface->ensureMinimumPrecision(d_min_prec);
            if (d_set_verbose)
                d_interface->setupVerbose(d_verbose_outputlevel, d_verbose_function);

            // Select next interface
            d_better_interface_gen = d_interface_gen;
            ++d_better_interface_gen;
        }

        const LRIInterface & getInterface() const
        {
            return *d_interface;
        }

        LRIInterface & getInterface()
        {
            return *d_interface;
        }

        void unselectInterface() const
        {
            d_interface.reset();
        }

        class GSI : public LatticeReduction::GramSchmidtInformer
        {
        private:
            LRIISelector & d_selector;

        public:
            GSI(LRIISelector & selector)
                : d_selector(selector)
            {
            }

            virtual ~GSI()
            {
            }

            virtual double getGSCoefficientD(unsigned i, unsigned j) const
            {
                d_selector.selectInterface();
                double r = d_selector.getInterface().getInformer()->getGSCoefficientD(i, j);
                d_selector.unselectInterface();
                return r;
            }

            virtual long double getGSCoefficientLD(unsigned i, unsigned j) const
            {
                d_selector.selectInterface();
                long double r = d_selector.getInterface().getInformer()->getGSCoefficientLD(i, j);
                d_selector.unselectInterface();
                return r;
            }

            virtual arithmetic::Real getGSCoefficientR(unsigned i, unsigned j, const arithmetic::RealContext & rc) const
            {
                d_selector.selectInterface();
                arithmetic::Real r = d_selector.getInterface().getInformer()->getGSCoefficientR(i, j, rc);
                d_selector.unselectInterface();
                return r;
            }

            virtual double getGSSqNormD(unsigned i) const
            {
                d_selector.selectInterface();
                double r = d_selector.getInterface().getInformer()->getGSSqNormD(i);
                d_selector.unselectInterface();
                return r;
            }

            virtual long double getGSSqNormLD(unsigned i) const
            {
                d_selector.selectInterface();
                long double r = d_selector.getInterface().getInformer()->getGSSqNormLD(i);
                d_selector.unselectInterface();
                return r;
            }

            virtual arithmetic::Real getGSSqNormR(unsigned i, const arithmetic::RealContext & rc) const
            {
                d_selector.selectInterface();
                arithmetic::Real r = d_selector.getInterface().getInformer()->getGSSqNormR(i, rc);
                d_selector.unselectInterface();
                return r;
            }

            virtual double computeProjectionLengthD(unsigned k, unsigned b, const linalg::math_rowvector<arithmetic::Integer> & vec) const
            {
                d_selector.selectInterface();
                double r = d_selector.getInterface().getInformer()->computeProjectionLengthD(k, b, vec);
                d_selector.unselectInterface();
                return r;
            }

            virtual long double computeProjectionLengthLD(unsigned k, unsigned b, const linalg::math_rowvector<arithmetic::Integer> & vec) const
            {
                d_selector.selectInterface();
                long double r = d_selector.getInterface().getInformer()->computeProjectionLengthLD(k, b, vec);
                d_selector.unselectInterface();
                return r;
            }

            virtual arithmetic::Real computeProjectionLengthR(unsigned k, unsigned b, const linalg::math_rowvector<arithmetic::Integer> & vec, const arithmetic::RealContext & rc) const
            {
                d_selector.selectInterface();
                arithmetic::Real r = d_selector.getInterface().getInformer()->computeProjectionLengthR(k, b, vec, rc);
                d_selector.unselectInterface();
                return r;
            }
        };

        friend class GSI;

        GSI d_gsi;

        bool d_ensure_min_prec;
        unsigned long d_min_prec;

        bool d_set_verbose;
        LatticeReduction::VerboseOutputLevel d_verbose_outputlevel;
        LatticeReduction::VerboseFunction d_verbose_function;

    public:
        LRIISelector(linalg::math_matrix<arithmetic::Integer> & lattice, LatticeReduction::VerboseOutputLevel vol, LatticeReduction::VerboseFunction vf)
            : LRIInterface(lattice), d_interface(), d_continue(false), d_reselect(false), d_gsi(*this),
              d_ensure_min_prec(false), d_min_prec(0), d_set_verbose(false), d_verbose_outputlevel(vol), d_verbose_function(vf)
        {
        }

        virtual ~LRIISelector()
        {
        }

        void addInterface(long max_bits, boost::function<LRIInterface*(linalg::math_matrix<arithmetic::Integer> &)> interfacegen)
        {
            d_interfaces.push_back(Interface(max_bits, interfacegen));
        }

        virtual void setupVerbose(LatticeReduction::VerboseOutputLevel vol, LatticeReduction::VerboseFunction vf)
        {
            d_set_verbose = true;
            d_verbose_outputlevel = vol;
            d_verbose_function = vf;
        }

        virtual const LatticeReduction::GramSchmidtInformer * getInformer() const
        {
            return &d_gsi;
        }

        virtual void ensureMinimumPrecision(unsigned long prec)
        {
            d_ensure_min_prec = prec;
            d_min_prec = prec;
        }

        virtual void forceGSRebuild(bool b)
        {
        }

        virtual double getGSCoefficientD(unsigned i, unsigned j) const
        {
            selectInterface();
            double r = getInterface().getGSCoefficientD(i, j);
            unselectInterface();
            return r;
        }

        virtual long double getGSCoefficientLD(unsigned i, unsigned j) const
        {
            selectInterface();
            long double r = getInterface().getGSCoefficientLD(i, j);
            unselectInterface();
            return r;
        }

        virtual arithmetic::Real getGSCoefficientR(unsigned i, unsigned j, const arithmetic::RealContext & rc) const
        {
            selectInterface();
            arithmetic::Real r = getInterface().getGSCoefficientR(i, j, rc);
            unselectInterface();
            return r;
        }

        virtual double getGSSqNormD(unsigned i) const
        {
            selectInterface();
            double r = getInterface().getGSSqNormD(i);
            unselectInterface();
            return r;
        }

        virtual long double getGSSqNormLD(unsigned i) const
        {
            selectInterface();
            long double r = getInterface().getGSSqNormLD(i);
            unselectInterface();
            return r;
        }

        virtual arithmetic::Real getGSSqNormR(unsigned i, const arithmetic::RealContext & rc) const
        {
            selectInterface();
            arithmetic::Real r = getInterface().getGSSqNormR(i, rc);
            unselectInterface();
            return r;
        }

        virtual void modFlip(Transform & transform, unsigned i)
        {
            selectInterface();
            getInterface().modFlip(transform, i);
            unselectInterface();
        }

        virtual void modSwap(Transform & transform, unsigned i, unsigned j)
        {
            selectInterface();
            getInterface().modSwap(transform, i, j);
            unselectInterface();
        }

        virtual void modAdd(Transform & transform, unsigned i, unsigned j, const arithmetic::Integer & m)
        {
            selectInterface();
            getInterface().modAdd(transform, i, j, m);
            unselectInterface();
        }

        virtual void sortProjected(unsigned & begin, unsigned & end, Transform & transform)
        {
            selectInterface();
            getInterface().sortProjected(begin, end, transform);
            unselectInterface();
        }

        virtual void sizereduction(unsigned & begin, unsigned & end, Transform & transform)
        {
            selectInterface();
            getInterface().sizereduction(begin, end, transform);
            unselectInterface();
        }

        virtual void lll(unsigned & begin, unsigned & end, Transform & transform, double alpha, LatticeReduction::LLLMode mode,
                         LatticeReduction::CallbackFunction cf, LatticeReduction::CallbackFunction_LI cf2, double cf_int,
                         LatticeReduction::MinCallbackFunction mcf, LatticeReduction::MinCallbackFunction_LI mcf2,
                         MaxBitsCallbackFunction mbcf, bool anneal, LatticeReduction::AnnealCallbackFunction acf,
                         LatticeReduction::LLL_AnnealFunction af, LatticeReduction::DIMethod di, LatticeReduction::DIMode di_mode,
                         LatticeReduction::DIChoice di_choice, unsigned di_bs)
        {
            selectInterface(true);
            d_maxbitscf = mbcf;
            while (isContinuing())
                getInterface().lll(begin, end, transform, alpha, mode, cf, cf2, cf_int, mcf, mcf2,
                                   boost::bind(&LRIISelector::maxBitsCallbackFunction, this, _1, _2),
                                   anneal, acf, af, di, di_mode, di_choice, di_bs);
            unselectInterface();
        }

        virtual void bkz(unsigned & begin, unsigned & end, Transform & transform, double alpha, unsigned blocksize, LatticeReduction::BKZMode mode,
                         LatticeReduction::CallbackFunction cf, LatticeReduction::CallbackFunction_LI cf2, double cf_int,
                         LatticeReduction::MinCallbackFunction mcf, LatticeReduction::MinCallbackFunction_LI mcf2, MaxBitsCallbackFunction mbcf,
                         LatticeReduction::EnumCallbackFunction ecf, LatticeReduction::EnumCallbackFunction_LI ecf2, bool anneal,
                         LatticeReduction::AnnealCallbackFunction acf, LatticeReduction::BKZ_AnnealFunction af,
                         LatticeReduction::DIMethod di, LatticeReduction::DIMode di_mode, LatticeReduction::DIChoice di_choice, unsigned di_bs)
        {
            selectInterface(true);
            d_maxbitscf = mbcf;
            while (isContinuing())
                getInterface().bkz(begin, end, transform, alpha, blocksize, mode, cf, cf2, cf_int, mcf, mcf2,
                                   boost::bind(&LRIISelector::maxBitsCallbackFunction, this, _1, _2),
                                   ecf, ecf2, anneal, acf, af, di, di_mode, di_choice, di_bs);
            unselectInterface();
        }

        virtual void hkz(unsigned & begin, unsigned & end, Transform & transform, bool dual,
                         LatticeReduction::CallbackFunction cf, LatticeReduction::CallbackFunction_LI cf2, double cf_int,
                         LatticeReduction::MinCallbackFunction mcf, LatticeReduction::MinCallbackFunction_LI mcf2,
                         MaxBitsCallbackFunction mbcf,
                         LatticeReduction::EnumCallbackFunction ecf, LatticeReduction::EnumCallbackFunction_LI ecf2)
        {
            selectInterface(true);
            d_maxbitscf = mbcf;
            while (isContinuing())
                getInterface().hkz(begin, end, transform, dual, cf, cf2, cf_int, mcf, mcf2,
                                   boost::bind(&LRIISelector::maxBitsCallbackFunction, this, _1, _2),
                                   ecf, ecf2);
            unselectInterface();
        }

        virtual void svp(unsigned & begin, unsigned & end, Transform & transform, bool make_basis, bool extreme, bool dual,
                         LatticeReduction::CallbackFunction cf, LatticeReduction::CallbackFunction_LI cf2, double cf_int,
                         LatticeReduction::MinCallbackFunction mcf, LatticeReduction::MinCallbackFunction_LI mcf2,
                         MaxBitsCallbackFunction mbcf,
                         LatticeReduction::EnumCallbackFunction ecf, LatticeReduction::EnumCallbackFunction_LI ecf2)
        {
            selectInterface(true);
            d_maxbitscf = mbcf;
            while (isContinuing())
                getInterface().svp(begin, end, transform, make_basis, extreme, dual, cf, cf2, cf_int, mcf, mcf2,
                                   boost::bind(&LRIISelector::maxBitsCallbackFunction, this, _1, _2),
                                   ecf, ecf2);
            unselectInterface();
        }

        virtual bool isSizeReduced(unsigned begin, unsigned end) const
        {
            selectInterface();
            bool ret = getInterface().isSizeReduced(begin, end);
            unselectInterface();
            return ret;
        }

        virtual bool isLLLBasis(unsigned begin, unsigned end, double alpha, LatticeReduction::LLLMode mode,
                                LatticeReduction::DIMethod di, LatticeReduction::DIChoice di_choice, unsigned di_bs) const
        {
            selectInterface();
            bool ret = getInterface().isLLLBasis(begin, end, alpha, mode, di, di_choice, di_bs);
            unselectInterface();
            return ret;
        }

        virtual bool isBKZBasis(unsigned begin, unsigned end, double alpha, unsigned blocksize, LatticeReduction::BKZMode mode,
                                LatticeReduction::DIMethod di, LatticeReduction::DIChoice di_choice, unsigned di_bs) const
        {
            selectInterface();
            bool ret = getInterface().isBKZBasis(begin, end, alpha, blocksize, mode, di, di_choice, di_bs);
            unselectInterface();
            return ret;
        }

        virtual bool isHKZBasis(unsigned begin, unsigned end, bool dual) const
        {
            selectInterface();
            bool ret = getInterface().isHKZBasis(begin, end, dual);
            unselectInterface();
            return ret;
        }

        virtual bool isSVPBasis(unsigned begin, unsigned end, bool dual) const
        {
            selectInterface();
            bool ret = getInterface().isSVPBasis(begin, end, dual);
            unselectInterface();
            return ret;
        }
    };

    template<class RealTypeContext, class IntTypeContext>
    LRIInterface * CreateLRIInterfaceWithContexts(LatticeReduction::VerboseOutputLevel vol, LatticeReduction::VerboseFunction vf,
                                                  linalg::math_matrix<arithmetic::Integer> & lattice, LatticeReduction::GramSchmidt gs, bool gsr,
                                                  LatticeReduction::SVPMode svp, unsigned max_cores,
                                                  const RealTypeContext & rc, const IntTypeContext & ic);

    template<class RealTypeContext>
    LRIInterface * CreateLRIInterface(LatticeReduction::VerboseOutputLevel vol, LatticeReduction::VerboseFunction vf,
                                      linalg::math_matrix<arithmetic::Integer> & lattice, LatticeReduction::GramSchmidt gs, bool gsr,
                                      LatticeReduction::SVPMode svp, unsigned max_cores,
                                      LatticeReduction::Integers ints, const RealTypeContext & rc)
    {
        switch (ints)
        {
#if !defined(PLLL_CONFIG_NO_ARITHMETIC_BIGINT) && !defined(PLLL_CONFIG_NO_ARITHMETIC_LONGINT)
        case LatticeReduction::I_Auto:
        {
            LRIISelector * sel = new LRIISelector(lattice, vol, vf);
            // Add in decreasing order of supported precisions
            sel->addInterface(std::numeric_limits<long>::max(),
                              boost::bind(CreateLRIInterfaceWithContexts<RealTypeContext, arithmetic::IntegerContext>,
                                          vol, vf, _1, gs, gsr, svp, max_cores, rc, arithmetic::IntegerContext()));
            sel->addInterface(std::numeric_limits<long int>::digits - 1,
                              boost::bind(CreateLRIInterfaceWithContexts<RealTypeContext, arithmetic::NIntContext<long int> >,
                                          vol, vf, _1, gs, gsr, svp, max_cores, rc, arithmetic::NIntContext<long int>()));
            return sel;
        }
#endif
#ifndef PLLL_CONFIG_NO_ARITHMETIC_BIGINT
        case LatticeReduction::I_ArbitraryPrecision:
            return CreateLRIInterfaceWithContexts(vol, vf, lattice, gs, gsr, svp, max_cores, rc, arithmetic::IntegerContext());
#endif
#ifndef PLLL_CONFIG_NO_ARITHMETIC_LONGINT
        case LatticeReduction::I_LongInt:
            return CreateLRIInterfaceWithContexts(vol, vf, lattice, gs, gsr, svp, max_cores, rc, arithmetic::NIntContext<long int>());
#endif
        }
        assert(!"Integer arithmetic not supported!");
        return NULL;
    }

    LRIInterface * CreateLRIInterface(LatticeReduction::VerboseOutputLevel vol, LatticeReduction::VerboseFunction vf,
                                      linalg::math_matrix<arithmetic::Integer> & lattice,
                                      LatticeReduction::Arithmetic arith, LatticeReduction::Integers ints,
                                      LatticeReduction::GramSchmidt gs, bool gsr,
                                      LatticeReduction::SVPMode svp, unsigned max_cores)
    {
        switch(arith)
        {
        default:
#ifndef PLLL_CONFIG_NO_ARITHMETIC_LONGDOUBLE
        case LatticeReduction::A_LongDouble:   return CreateLRIInterface(vol, vf, lattice, gs, gsr, svp, max_cores, ints, arithmetic::NFPContext<long double>());
#endif
#ifndef PLLL_CONFIG_NO_ARITHMETIC_REAL
        case LatticeReduction::A_Real:         return CreateLRIInterface(vol, vf, lattice, gs, gsr, svp, max_cores, ints, arithmetic::RealContext());
#endif
#ifndef PLLL_CONFIG_NO_ARITHMETIC_RATIONAL
        case LatticeReduction::A_Rational:     return CreateLRIInterface(vol, vf, lattice, gs, gsr, svp, max_cores, ints, arithmetic::RationalContext());
#endif
#ifndef PLLL_CONFIG_NO_ARITHMETIC_DOUBLE
        case LatticeReduction::A_Double:       return CreateLRIInterface(vol, vf, lattice, gs, gsr, svp, max_cores, ints, arithmetic::NFPContext<double>());
#endif
#ifndef PLLL_CONFIG_NO_ARITHMETIC_DOUBLEDOUBLE
        case LatticeReduction::A_DoubleDouble: return CreateLRIInterface(vol, vf, lattice, gs, gsr, svp, max_cores, ints, arithmetic::DDQDContext<dd_real>());
#endif
#ifndef PLLL_CONFIG_NO_ARITHMETIC_QUADDOUBLE
        case LatticeReduction::A_QuadDouble:   return CreateLRIInterface(vol, vf, lattice, gs, gsr, svp, max_cores, ints, arithmetic::DDQDContext<qd_real>());
#endif
        }
        assert(!"Real arithmetic not supported!");
        return NULL;
    }

    // Explicit instantiation of templates

#ifndef PLLL_CONFIG_NO_ARITHMETIC_BIGINT
    template void setUnit<arithmetic::IntegerContext>(linalg::math_matrix<arithmetic::Integer> &);
#endif

#ifndef PLLL_CONFIG_NO_ARITHMETIC_LONGINT
    template void setUnit<arithmetic::NIntContext<long> >(linalg::math_matrix<arithmetic::NInt<long> > &);
#endif
}


// This file is part of the dune-stuff project:
//   https://github.com/wwu-numerik/dune-stuff
// Copyright holders: Rene Milk, Felix Schindler
// License: BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)
//
// Contributors: Sven Kaulmann

#ifndef DUNE_STUFF_COMMON_MATRIX_HH
#define DUNE_STUFF_COMMON_MATRIX_HH

#include <boost/numeric/conversion/cast.hpp>

#include <dune/common/dynmatrix.hh>
#include <dune/common/fmatrix.hh>

#include <dune/stuff/common/exceptions.hh>
#include <dune/stuff/common/fmatrix.hh>
#include <dune/stuff/common/vector.hh>

namespace Dune {
namespace Stuff {
namespace Common {

/**
 * \brief Traits to statically extract the information of a (mathematical) matrix.
 *
 *        If you want your matrix class to benefit from the operators defined in this header you have to manually
 *        specify a specialization of this class in your code with is_matrix defined to true and an appropriate
 *        static methods and members (see the specializations below).
 */
template <class MatType>
struct MatrixAbstraction
{
  typedef MatType MatrixType;
  typedef MatType ScalarType;
  typedef MatType S;

  static const bool is_matrix = false;

  static const bool has_static_size = false;

  static const size_t static_rows = std::numeric_limits<size_t>::max();

  static const size_t static_cols = std::numeric_limits<size_t>::max();

  static inline /*MatrixType*/ void create(const size_t /*rows*/, const size_t /*cols*/)
  {
    static_assert(AlwaysFalse<MatType>::value, "Do not call me if is_matrix is false!");
  }

  static inline /*MatrixType*/ void create(const size_t /*rows*/, const size_t /*cols*/, const ScalarType& /*val*/)
  {
    static_assert(AlwaysFalse<MatType>::value, "Do not call me if is_matrix is false!");
  }

  static inline /*size_t*/ void rows(const MatrixType& /*mat*/)
  {
    static_assert(AlwaysFalse<MatType>::value, "Do not call me if is_matrix is false!");
  }

  static inline /*size_t*/ void cols(const MatrixType& /*mat*/)
  {
    static_assert(AlwaysFalse<MatType>::value, "Do not call me if is_matrix is false!");
  }

  static inline void set_entry(MatrixType& /*mat*/, const size_t /*row*/, const size_t /*col*/,
                               const ScalarType& /*val*/)
  {
    static_assert(AlwaysFalse<MatType>::value, "Do not call me if is_matrix is false!");
  }

  static inline /*ScalarType*/ void get_entry(const MatrixType& /*mat*/, const size_t /*row*/, const size_t /*col*/)
  {
    static_assert(AlwaysFalse<MatType>::value, "Do not call me if is_matrix is false!");
  }
};

template <class K>
struct MatrixAbstraction<Dune::DynamicMatrix<K>>
{
  typedef Dune::DynamicMatrix<K> MatrixType;
  typedef K ScalarType;
  typedef ScalarType S;

  static const bool is_matrix = true;

  static const bool has_static_size = false;

  static const size_t static_rows = std::numeric_limits<size_t>::max();

  static const size_t static_cols = std::numeric_limits<size_t>::max();

  static inline MatrixType create(const size_t rows, const size_t cols) { return MatrixType(rows, cols); }

  static inline MatrixType create(const size_t rows, const size_t cols, const ScalarType& val)
  {
    return MatrixType(rows, cols, val);
  }

  static inline size_t rows(const MatrixType& mat) { return mat.rows(); }

  static inline size_t cols(const MatrixType& mat) { return mat.cols(); }

  static inline void set_entry(MatrixType& mat, const size_t row, const size_t col, const ScalarType& val)
  {
    mat[row][col] = val;
  }

  static inline ScalarType get_entry(const MatrixType& mat, const size_t row, const size_t col)
  {
    return mat[row][col];
  }
};

template <class K, int N, int M>
struct MatrixAbstraction<Dune::FieldMatrix<K, N, M>>
{
  typedef Dune::FieldMatrix<K, N, M> MatrixType;
  typedef K ScalarType;
  typedef ScalarType S;

  static const bool is_matrix = true;

  static const bool has_static_size = true;

  static const size_t static_rows = N;

  static const size_t static_cols = M;

  static inline MatrixType create(const size_t rows, const size_t cols)
  {
    if (rows != N)
      DUNE_THROW(Dune::Stuff::Exceptions::shapes_do_not_match, "rows = " << rows << "\nN = " << int(N));
    if (cols != M)
      DUNE_THROW(Dune::Stuff::Exceptions::shapes_do_not_match, "cols = " << cols << "\nM = " << int(M));
    return MatrixType();
  }

  static inline MatrixType create(const size_t rows, const size_t cols, const ScalarType& val)
  {
    if (rows != N)
      DUNE_THROW(Dune::Stuff::Exceptions::shapes_do_not_match, "rows = " << rows << "\nN = " << int(N));
    if (cols != M)
      DUNE_THROW(Dune::Stuff::Exceptions::shapes_do_not_match, "cols = " << cols << "\nM = " << int(M));
    return MatrixType(val);
  }

  static inline size_t rows(const MatrixType& /*mat*/) { return boost::numeric_cast<size_t>(N); }

  static inline size_t cols(const MatrixType& /*mat*/) { return boost::numeric_cast<size_t>(M); }

  static inline void set_entry(MatrixType& mat, const size_t row, const size_t col, const ScalarType& val)
  {
    mat[row][col] = val;
  }

  static inline ScalarType get_entry(const MatrixType& mat, const size_t row, const size_t col)
  {
    return mat[row][col];
  }
};

template <class K, int N, int M>
struct MatrixAbstraction<Dune::Stuff::Common::FieldMatrix<K, N, M>>
{
  typedef Dune::Stuff::Common::FieldMatrix<K, N, M> MatrixType;
  typedef K ScalarType;
  typedef ScalarType S;

  static const bool is_matrix = true;

  static const bool has_static_size = true;

  static const size_t static_rows = N;

  static const size_t static_cols = M;

  static inline MatrixType create(const size_t rows, const size_t cols) { return MatrixType(rows, cols); }

  static inline MatrixType create(const size_t rows, const size_t cols, const ScalarType& val)
  {
    return MatrixType(rows, cols, val);
  }

  static inline size_t rows(const MatrixType& /*mat*/) { return N; }

  static inline size_t cols(const MatrixType& /*mat*/) { return M; }

  static inline void set_entry(MatrixType& mat, const size_t row, const size_t col, const ScalarType& val)
  {
    mat[row][col] = val;
  }

  static inline ScalarType get_entry(const MatrixType& mat, const size_t row, const size_t col)
  {
    return mat[row][col];
  }
};

template <class MatrixType>
struct is_matrix
{
  static const bool value = MatrixAbstraction<MatrixType>::is_matrix;
};

template <class MatrixType>
typename std::enable_if<is_matrix<MatrixType>::value, MatrixType>::type
    create(const size_t sz, const typename MatrixAbstraction<MatrixType>::S& val)
{
  return MatrixAbstraction<MatrixType>::create(sz, val);
}

} // namespace Common
} // namespace Stuff
} // namespace Dune

#endif // DUNE_STUFF_COMMON_MATRIX_HH


/*
All modification made by Cambricon Corporation: © 2018-2019 Cambricon Corporation
All rights reserved.
All other contributions:
Copyright (c) 2014--2019, the respective contributors
All rights reserved.
For the list of contributors go to https://github.com/BVLC/caffe/blob/master/CONTRIBUTORS.md
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of Intel Corporation nor the names of its contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <boost/math/special_functions/next.hpp>
#include <boost/random.hpp>

#include <limits>

#include "caffe/common.hpp"
#include "caffe/util/math_functions.hpp"
#include "caffe/util/rng.hpp"

namespace caffe {

template <>
void caffe_cpu_gemm<float>(const CBLAS_TRANSPOSE TransA,
                           const CBLAS_TRANSPOSE TransB, const int M,
                           const int N, const int K, const float alpha,
                           const float* A, const float* B, const float beta,
                           float* C) {
  int lda = (TransA == CblasNoTrans) ? K : M;
  int ldb = (TransB == CblasNoTrans) ? N : K;
  cblas_sgemm(CblasRowMajor, TransA, TransB, M, N, K, alpha, A, lda, B, ldb,
              beta, C, N);
}

template <>
void caffe_cpu_gemm<double>(const CBLAS_TRANSPOSE TransA,
                            const CBLAS_TRANSPOSE TransB, const int M,
                            const int N, const int K, const double alpha,
                            const double* A, const double* B, const double beta,
                            double* C) {
  int lda = (TransA == CblasNoTrans) ? K : M;
  int ldb = (TransB == CblasNoTrans) ? N : K;
  cblas_dgemm(CblasRowMajor, TransA, TransB, M, N, K, alpha, A, lda, B, ldb,
              beta, C, N);
}

template <>
void caffe_cpu_gemv<float>(const CBLAS_TRANSPOSE TransA, const int M,
                           const int N, const float alpha, const float* A,
                           const float* x, const float beta, float* y) {
  cblas_sgemv(CblasRowMajor, TransA, M, N, alpha, A, N, x, 1, beta, y, 1);
}

template <>
void caffe_cpu_gemv<double>(const CBLAS_TRANSPOSE TransA, const int M,
                            const int N, const double alpha, const double* A,
                            const double* x, const double beta, double* y) {
  cblas_dgemv(CblasRowMajor, TransA, M, N, alpha, A, N, x, 1, beta, y, 1);
}

template <>
void caffe_axpy<float>(const int N, const float alpha, const float* X,
                       float* Y) {
  cblas_saxpy(N, alpha, X, 1, Y, 1);
}

template <>
void caffe_axpy<double>(const int N, const double alpha, const double* X,
                        double* Y) {
  cblas_daxpy(N, alpha, X, 1, Y, 1);
}

template <typename Dtype>
void caffe_set(const int N, const Dtype alpha, Dtype* Y) {
  if (alpha == 0) {
    memset(Y, 0, sizeof(Dtype) * N);
    return;
  }
  for (int i = 0; i < N; ++i) {
    Y[i] = alpha;
  }
}

template void caffe_set<int>(const int N, const int alpha, int* Y);
template void caffe_set<float>(const int N, const float alpha, float* Y);
template void caffe_set<double>(const int N, const double alpha, double* Y);

template <>
void caffe_add_scalar(const int N, const float alpha, float* Y) {
  for (int i = 0; i < N; ++i) {
    Y[i] += alpha;
  }
}

template <>
void caffe_add_scalar(const int N, const double alpha, double* Y) {
  for (int i = 0; i < N; ++i) {
    Y[i] += alpha;
  }
}

template <typename Dtype>
void caffe_copy(const int N, const Dtype* X, Dtype* Y) {
  if (X != Y) {
    if (Caffe::mode() == Caffe::GPU) {
#ifdef USE_CUDA
      // NOLINT_NEXT_LINE(caffe/alt_fn)
      CUDA_CHECK(cudaMemcpy(Y, X, sizeof(Dtype) * N, cudaMemcpyDefault));
#else
      NO_GPU;
#endif
    } else {
      memcpy(Y, X, sizeof(Dtype) * N);
    }
  }
}

template void caffe_copy<int>(const int N, const int* X, int* Y);
template void caffe_copy<unsigned int>(const int N, const unsigned int* X,
                                       unsigned int* Y);
template void caffe_copy<float>(const int N, const float* X, float* Y);
template void caffe_copy<double>(const int N, const double* X, double* Y);

template <>
void caffe_scal<float>(const int N, const float alpha, float* X) {
  cblas_sscal(N, alpha, X, 1);
}

template <>
void caffe_scal<double>(const int N, const double alpha, double* X) {
  cblas_dscal(N, alpha, X, 1);
}

template <>
void caffe_cpu_axpby<float>(const int N, const float alpha, const float* X,
                            const float beta, float* Y) {
  cblas_saxpby(N, alpha, X, 1, beta, Y, 1);
}

template <>
void caffe_cpu_axpby<double>(const int N, const double alpha, const double* X,
                             const double beta, double* Y) {
  cblas_daxpby(N, alpha, X, 1, beta, Y, 1);
}

template <>
void caffe_add<float>(const int n, const float* a, const float* b, float* y) {
  vsAdd(n, a, b, y);
}

template <>
void caffe_add<double>(const int n, const double* a, const double* b,
                       double* y) {
  vdAdd(n, a, b, y);
}

template <>
void caffe_sub<float>(const int n, const float* a, const float* b, float* y) {
  vsSub(n, a, b, y);
}

template <>
void caffe_sub<double>(const int n, const double* a, const double* b,
                       double* y) {
  vdSub(n, a, b, y);
}

template <>
void caffe_mul<float>(const int n, const float* a, const float* b, float* y) {
  vsMul(n, a, b, y);
}

template <>
void caffe_mul<double>(const int n, const double* a, const double* b,
                       double* y) {
  vdMul(n, a, b, y);
}

template <>
void caffe_div<float>(const int n, const float* a, const float* b, float* y) {
  vsDiv(n, a, b, y);
}

template <>
void caffe_div<double>(const int n, const double* a, const double* b,
                       double* y) {
  vdDiv(n, a, b, y);
}

template <>
void caffe_powx<float>(const int n, const float* a, const float b, float* y) {
  vsPowx(n, a, b, y);
}

template <>
void caffe_powx<double>(const int n, const double* a, const double b,
                        double* y) {
  vdPowx(n, a, b, y);
}

template <>
void caffe_sqr<float>(const int n, const float* a, float* y) {
  vsSqr(n, a, y);
}

template <>
void caffe_sqr<double>(const int n, const double* a, double* y) {
  vdSqr(n, a, y);
}

template <>
void caffe_sqrt<float>(const int n, const float* a, float* y) {
  vsSqrt(n, a, y);
}

template <>
void caffe_sqrt<double>(const int n, const double* a, double* y) {
  vdSqrt(n, a, y);
}

template <>
void caffe_exp<float>(const int n, const float* a, float* y) {
  vsExp(n, a, y);
}

template <>
void caffe_exp<double>(const int n, const double* a, double* y) {
  vdExp(n, a, y);
}

template <>
void caffe_log<float>(const int n, const float* a, float* y) {
  vsLn(n, a, y);
}

template <>
void caffe_log<double>(const int n, const double* a, double* y) {
  vdLn(n, a, y);
}

template <>
void caffe_abs<float>(const int n, const float* a, float* y) {
  vsAbs(n, a, y);
}

template <>
void caffe_abs<double>(const int n, const double* a, double* y) {
  vdAbs(n, a, y);
}

unsigned int caffe_rng_rand() { return (*caffe_rng())(); }

template <typename Dtype>
Dtype caffe_nextafter(const Dtype b) {
  return boost::math::nextafter<Dtype>(b, std::numeric_limits<Dtype>::max());
}

template float caffe_nextafter(const float b);

template double caffe_nextafter(const double b);

template <typename Dtype>
void caffe_rng_uniform(const int n, const Dtype a, const Dtype b, Dtype* r) {
  CHECK_GE(n, 0);
  CHECK(r);
  CHECK_LE(a, b);
  boost::uniform_real<Dtype> random_distribution(a, caffe_nextafter<Dtype>(b));
  boost::variate_generator<caffe::rng_t*, boost::uniform_real<Dtype> >
      variate_generator(caffe_rng(), random_distribution);
  for (int i = 0; i < n; ++i) {
    r[i] = variate_generator();
  }
}

template void caffe_rng_uniform<float>(const int n, const float a,
                                       const float b, float* r);

template void caffe_rng_uniform<double>(const int n, const double a,
                                        const double b, double* r);

template <typename Dtype>
void caffe_rng_gaussian(const int n, const Dtype a, const Dtype sigma,
                        Dtype* r) {
  CHECK_GE(n, 0);
  CHECK(r);
  CHECK_GT(sigma, 0);
  boost::normal_distribution<Dtype> random_distribution(a, sigma);
  boost::variate_generator<caffe::rng_t*, boost::normal_distribution<Dtype> >
      variate_generator(caffe_rng(), random_distribution);
  for (int i = 0; i < n; ++i) {
    r[i] = variate_generator();
  }
}

template void caffe_rng_gaussian<float>(const int n, const float mu,
                                        const float sigma, float* r);

template void caffe_rng_gaussian<double>(const int n, const double mu,
                                         const double sigma, double* r);

template <typename Dtype>
void caffe_rng_bernoulli(const int n, const Dtype p, int* r) {
  CHECK_GE(n, 0);
  CHECK(r);
  CHECK_GE(p, 0);
  CHECK_LE(p, 1);
  boost::bernoulli_distribution<Dtype> random_distribution(p);
  boost::variate_generator<caffe::rng_t*, boost::bernoulli_distribution<Dtype> >
      variate_generator(caffe_rng(), random_distribution);
  for (int i = 0; i < n; ++i) {
    r[i] = variate_generator();
  }
}

template void caffe_rng_bernoulli<double>(const int n, const double p, int* r);

template void caffe_rng_bernoulli<float>(const int n, const float p, int* r);

template <typename Dtype>
void caffe_rng_bernoulli(const int n, const Dtype p, unsigned int* r) {
  CHECK_GE(n, 0);
  CHECK(r);
  CHECK_GE(p, 0);
  CHECK_LE(p, 1);
  boost::bernoulli_distribution<Dtype> random_distribution(p);
  boost::variate_generator<caffe::rng_t*, boost::bernoulli_distribution<Dtype> >
      variate_generator(caffe_rng(), random_distribution);
  for (int i = 0; i < n; ++i) {
    r[i] = static_cast<unsigned int>(variate_generator());
  }
}

template void caffe_rng_bernoulli<double>(const int n, const double p,
                                          unsigned int* r);

template void caffe_rng_bernoulli<float>(const int n, const float p,
                                         unsigned int* r);

template <>
float caffe_cpu_strided_dot<float>(const int n, const float* x, const int incx,
                                   const float* y, const int incy) {
  return cblas_sdot(n, x, incx, y, incy);
}

template <>
double caffe_cpu_strided_dot<double>(const int n, const double* x,
                                     const int incx, const double* y,
                                     const int incy) {
  return cblas_ddot(n, x, incx, y, incy);
}

template <typename Dtype>
Dtype caffe_cpu_dot(const int n, const Dtype* x, const Dtype* y) {
  return caffe_cpu_strided_dot(n, x, 1, y, 1);
}

template float caffe_cpu_dot<float>(const int n, const float* x,
                                    const float* y);

template double caffe_cpu_dot<double>(const int n, const double* x,
                                      const double* y);

template <>
float caffe_cpu_asum<float>(const int n, const float* x) {
  return cblas_sasum(n, x, 1);
}

template <>
double caffe_cpu_asum<double>(const int n, const double* x) {
  return cblas_dasum(n, x, 1);
}

template <>
void caffe_cpu_scale<float>(const int n, const float alpha, const float* x,
                            float* y) {
  cblas_scopy(n, x, 1, y, 1);
  cblas_sscal(n, alpha, y, 1);
}

template <>
void caffe_cpu_scale<double>(const int n, const double alpha, const double* x,
                             double* y) {
  cblas_dcopy(n, x, 1, y, 1);
  cblas_dscal(n, alpha, y, 1);
}

}  // namespace caffe


#include <boost/asio.hpp>
#include "vdf.h"
using boost::asio::ip::tcp;

const int max_length = 2048;
const int kMaxProcessesAllowed = 3;
std::mutex socket_mutex;

int process_number;

void PrintInfo(std::string input) {
    std::cout << "VDF Client: " << input << "\n";
}

void CreateAndWriteProof(integer D, form x, int64_t num_iterations, WesolowskiCallback& weso, bool& stop_signal, tcp::socket& sock) {
    Proof result = CreateProofOfTimeNWesolowski(D, x, num_iterations, 0, weso, 2, 0, stop_signal);
    if (stop_signal == true) {
        PrintInfo("Got stop signal before completing the proof!");
        return ;
    }
    std::vector<unsigned char> bytes = ConvertIntegerToBytes(integer(num_iterations), 8);
    bytes.insert(bytes.end(), result.y.begin(), result.y.end());
    bytes.insert(bytes.end(), result.proof.begin(), result.proof.end());
    std::string str_result = BytesToStr(bytes);
    std::lock_guard<std::mutex> lock(socket_mutex);
    PrintInfo("Generated proof = " + str_result);;
    boost::asio::write(sock, boost::asio::buffer(str_result.c_str(), str_result.size()));
}

void session(tcp::socket& sock) {
    try {
        char disc[350];
        char disc_size[5];
        boost::system::error_code error;

        memset(disc,0x00,sizeof(disc)); // For null termination
        memset(disc_size,0x00,sizeof(disc_size)); // For null termination

        boost::asio::read(sock, boost::asio::buffer(disc_size, 3), error);
        int disc_int_size = atoi(disc_size);

        boost::asio::read(sock, boost::asio::buffer(disc, disc_int_size), error);

        integer D(disc);
        PrintInfo("Discriminant = " + to_string(D.impl));

        int space_needed = kSwitchIters / 10 + (kMaxItersAllowed - kSwitchIters) / 100;
        forms = (form*) calloc(space_needed, sizeof(form));

        PrintInfo("Calloc'd " + to_string(space_needed * sizeof(form)) + " bytes");

        // Init VDF the discriminant...

        if (error == boost::asio::error::eof)
            return ; // Connection closed cleanly by peer.
        else if (error)
            throw boost::system::system_error(error); // Some other error.

        if (getenv( "warn_on_corruption_in_production" )!=nullptr) {
            warn_on_corruption_in_production=true;
        }
        if (is_vdf_test) {
            PrintInfo( "=== Test mode ===" );
        }
        if (warn_on_corruption_in_production) {
            PrintInfo( "=== Warn on corruption enabled ===" );
        }
        assert(is_vdf_test); //assertions should be disabled in VDF_MODE==0
        init_gmp();
        allow_integer_constructor=true; //make sure the old gmp allocator isn't used
        set_rounding_mode();

        integer L=root(-D, 4);
        form f=form::generator(D);

        bool stop_signal = false;
        // (iteration, thread_id)
        std::set<std::pair<uint64_t, uint64_t> > seen_iterations;
        bool stop_vector[100];

        std::vector<std::thread> threads;
        WesolowskiCallback weso(1000000);

        //mpz_init(weso.forms[0].a.impl);
        //mpz_init(weso.forms[0].b.impl);
        //mpz_init(weso.forms[0].c.impl);

        forms[0]=f;
        weso.D = D;
        weso.L = L;
        weso.kl = 10;

        bool stopped = false;
        bool got_iters = false;
        std::thread vdf_worker(repeated_square, f, D, L, std::ref(weso), std::ref(stopped));

        // Tell client that I'm ready to get the challenges.
        boost::asio::write(sock, boost::asio::buffer("OK", 2));
        char data[20];

        while (!stopped) {
            memset(data, 0, sizeof(data));
            boost::asio::read(sock, boost::asio::buffer(data, 2), error);
            int size = (data[0] - '0') * 10 + (data[1] - '0');
            memset(data, 0, sizeof(data));
            boost::asio::read(sock, boost::asio::buffer(data, size), error);
            int iters = atoi(data);
            got_iters = true;

            if (iters == 0) {
                PrintInfo("Got stop signal!");
                stopped = true;
                for (int i = 0; i < threads.size(); i++)
                    stop_vector[i] = true;
                for (int t = 0; t < threads.size(); t++) {
                    threads[t].join();
                }
                vdf_worker.join();
                free(forms);
            } else {
                int max_iter = 0;
                int max_iter_thread_id = -1;
                int min_iter = std::numeric_limits<int> :: max();
                bool unique = true;
                for (auto active_iter: seen_iterations) {
                    if (active_iter.first > max_iter) {
                        max_iter = active_iter.first;
                        max_iter_thread_id = active_iter.second;
                    }
                    if (active_iter.first < min_iter) {
                        min_iter = active_iter.first;
                    }
                    if (active_iter.first == iters) {
                        unique = false;
                        break;
                    }
                }
                if (!unique) {
                    PrintInfo("Duplicate iteration " + to_string(iters) + "... Ignoring.");
                    continue;
                }
                if (threads.size() < kMaxProcessesAllowed || iters < min_iter) {
                    seen_iterations.insert({iters, threads.size()});
                    PrintInfo("Running proving for iter: " + to_string(iters));
                    stop_vector[threads.size()] = false;
                    threads.push_back(std::thread(CreateAndWriteProof, D, f, iters, std::ref(weso),
                                      std::ref(stop_vector[threads.size()]), std::ref(sock)));
                    if (threads.size() > kMaxProcessesAllowed) {
                        PrintInfo("Stopping proving for iter: " + to_string(max_iter));
                        stop_vector[max_iter_thread_id] = true;
                        seen_iterations.erase({max_iter, max_iter_thread_id});
                    }
                }
            }
        }
    } catch (std::exception& e) {
        PrintInfo("Exception in thread: " + to_string(e.what()));
    }

    try {
        // Tell client I've stopped everything, wait for ACK and close.
        boost::system::error_code error;

        PrintInfo("Stopped everything! Ready for the next challenge.");

        std::lock_guard<std::mutex> lock(socket_mutex);
        boost::asio::write(sock, boost::asio::buffer("STOP", 4));

        char ack[5];
        memset(ack,0x00,sizeof(ack));
        boost::asio::read(sock, boost::asio::buffer(ack, 3), error);
        assert (strncmp(ack, "ACK", 3) == 0);
    } catch (std::exception& e) {
        PrintInfo("Exception in thread: " + to_string(e.what()));
    }
}

int main(int argc, char* argv[])
{
  try {
    if (argc != 4)
    {
      std::cerr << "Usage: ./vdf_client <host> <port> <process_number>\n";
      return 1;
    }

    boost::asio::io_service io_service;

    tcp::resolver resolver(io_service);
    tcp::resolver::query query(tcp::v6(), argv[1], argv[2], boost::asio::ip::resolver_query_base::v4_mapped);
    tcp::resolver::iterator iterator = resolver.resolve(query);

    tcp::socket s(io_service);
    boost::asio::connect(s, iterator);
    process_number = atoi(argv[3]);
    session(s);
  } catch (std::exception& e) {
    std::cerr << "Exception: " << e.what() << "\n";
  }
  return 0;
}


#include "LevelSet.h"

#include <array>

#include <Eigen/Dense>
#include <Eigen/SVD>

#include "tbb/blocked_range3d.h"
#include "tbb/tbb.h"

namespace FluidSim3D
{

LevelSet::LevelSet()
    : myNarrowBand(0)
    , myPhiGrid(Transform(0, Vec3d::Zero()), Vec3i::Zero())
    , myIsBackgroundNegative(false)
{
    exactinit();
}

LevelSet::LevelSet(const Transform& xform, const Vec3i& size)
    : LevelSet(xform, size, size[0] * size[1] * size[2])
{}

LevelSet::LevelSet(const Transform& xform, const Vec3i& size, double bandwidth, bool isBoundaryNegative)
    : myNarrowBand(bandwidth * xform.dx())
    , myIsBackgroundNegative(isBoundaryNegative)
    , myPhiGrid(xform, size, myIsBackgroundNegative ? -myNarrowBand : myNarrowBand)
{
    for (int axis : {0, 1, 2}) assert(size[axis] >= 0);

    // In order to deal with triangle meshes, we need to initialize
    // the geometric predicate library.
    exactinit();
}

void LevelSet::initFromMesh(const TriMesh& initialMesh, bool doResizeGrid)
{
	// The internal code can't handle a mesh that falls outside of the bounds.
	// If the mesh does, we need to create a new copy and clamp it into the bounds of the grid.

    if (!doResizeGrid)
    {
		bool outOfBounds = false;
		for (const Vec3d& vertex : initialMesh.vertices())
		{
			Vec3d indexPoint = worldToIndex(vertex);

            for (int axis : {0, 1, 2})
            {
			    if (indexPoint[axis] <= 0 || indexPoint[axis] >= size()[axis])
				    outOfBounds = true;
            }
		}

		if (outOfBounds)
		{
			TriMesh clampedMesh = initialMesh;

			// Clamp to be inside grid
			for (int vertIndex = 0; vertIndex < clampedMesh.vertexCount(); ++vertIndex)
			{
				const Vec3d& vertex = clampedMesh.vertex(vertIndex);
				Vec3d indexPoint = worldToIndex(vertex);

				double offset = 1e-5 * dx();

                for (int axis : {0, 1, 2})
                {
                    indexPoint[axis] = std::clamp(indexPoint[axis], offset, size()[axis] - 1. - offset);
                }

				clampedMesh.setVertex(vertIndex, indexToWorld(indexPoint));
			}

			initFromMeshImpl(clampedMesh, doResizeGrid);
			return;
		}
    }

    initFromMeshImpl(initialMesh, doResizeGrid);
}

void LevelSet::reinit()
{
    TriMesh tempMesh = buildMesh();
    initFromMeshImpl(tempMesh, false);
}

bool LevelSet::isGridMatched(const LevelSet& grid) const
{
    if (size() != grid.size()) return false;
    if (xform() != grid.xform()) return false;
    return true;
}

bool LevelSet::isGridMatched(const ScalarGrid<double>& grid) const
{
    if (grid.sampleType() != ScalarGridSettings::SampleType::CENTER) return false;
    if (size() != grid.size()) return false;
    if (xform() != grid.xform()) return false;
    return true;
}

void LevelSet::unionSurface(const LevelSet& unionPhi)
{
    assert(isGridMatched(unionPhi));

    tbb::parallel_for(tbb::blocked_range<int>(0, voxelCount(), tbbLightGrainSize), [&](const tbb::blocked_range<int>& range)
    {
        for (int index = range.begin(); index != range.end(); ++index)
        {
            Vec3i cell = myPhiGrid.unflatten(index);
            myPhiGrid(cell) = std::min(myPhiGrid(cell), unionPhi(cell));
        }
    });
}

bool LevelSet::isBackgroundNegative() const
{
    return myIsBackgroundNegative;
}

void LevelSet::setBackgroundNegative()
{
    myIsBackgroundNegative = true;
}

TriMesh LevelSet::buildMesh() const
{
    // Create grid to store index to dual contouring point. Note that phi is
    // center sampled so the DC grid must be node sampled and one cell shorter
    // in each dimension
    UniformGrid<int> dcPointIndices(size() - Vec3i::Ones(), -1);
    std::vector<std::pair<Vec3i, Vec3d>> dcPointPair;

    // Build list of dual contouring points
    {
        tbb::enumerable_thread_specific<std::vector<std::pair<Vec3i, Vec3d>>> parallelDCPoints;

        tbb::parallel_for(tbb::blocked_range<int>(0, dcPointIndices.voxelCount(), tbbHeavyGrainSize), [&](const tbb::blocked_range<int>& range)
        {
            auto& localDCPoints = parallelDCPoints.local();

            for (int cellIndex = range.begin(); cellIndex != range.end(); ++cellIndex)
            {
                Vec3i cell = dcPointIndices.unflatten(cellIndex);

                VecVec3d points;
                VecVec3d normals;

                Vec3d averagePoint = Vec3d::Zero();

                for (int edgeAxis : {0, 1, 2})
                    for (int edgeIndex = 0; edgeIndex < 4; ++edgeIndex)
                    {
                        Vec3i edge = cellToEdge(cell, edgeAxis, edgeIndex);

                        Vec3i backwardNode = edgeToNode(edge, edgeAxis, 0);
                        Vec3i forwardNode = edgeToNode(edge, edgeAxis, 1);

                        // Look for zero crossings.
                        // Note that nodes for the DC grid fall exactly on the cell centers
                        // of the level set grid.
                        if ((myPhiGrid(backwardNode) <= 0 && myPhiGrid(forwardNode) > 0) ||
                            (myPhiGrid(backwardNode) > 0 && myPhiGrid(forwardNode) <= 0))
                        {
                            // Find interface point
                            Vec3d point = interpolateInterface(backwardNode, forwardNode);

                            for (int axis : {0, 1, 2})
                                assert(point[axis] >= backwardNode[axis] && point[axis] <= forwardNode[axis]);

                            points.push_back(point);

                            averagePoint += point;

                            // Find associated surface normal
                            Vec3d localNormal = normal(indexToWorld(point));

                            normals.push_back(localNormal);
                        }
                    }

                if (points.size() > 0)
                {
                    averagePoint.array() /= double(points.size());

                    Matrix3x3d AtA = Matrix3x3d::Zero();

                    Vec3d rhs = Vec3d::Zero();

                    for (int pointIndex = 0; pointIndex < points.size(); ++pointIndex)
                    {
                        AtA += normals[pointIndex] * normals[pointIndex].transpose();
                        rhs += normals[pointIndex] * normals[pointIndex].dot(points[pointIndex] - averagePoint);
                    }

                    Eigen::SelfAdjointEigenSolver<Matrix3x3d> eigenSolver(AtA);
                    const Vec3d& eigenvalues = eigenSolver.eigenvalues();

                    // Clamp eigenvalues
                    double tolerance = 0.01 * eigenvalues.cwiseAbs().maxCoeff();

                    Vec3d invEigenvalues;
                    int clamped = 0;
                    for (int index : {0, 1, 2})
                    {
                        if (std::fabs(eigenvalues[index] < tolerance))
                        {
                            invEigenvalues[index] = 0;
                            ++clamped;
                        }
                        else
                        {
                            invEigenvalues[index] = 1. / eigenvalues[index];
                        }
                    }

                    Vec3d qefPoint;
                    if (clamped < 3)
                    {
                        qefPoint = averagePoint + eigenSolver.eigenvectors() * invEigenvalues.asDiagonal() * eigenSolver.eigenvectors().transpose() * rhs;
                    }
                    else
                    {
                        qefPoint = averagePoint;
                    }

                    // Clamp to cell
                    if (qefPoint[0] < cell[0] || qefPoint[0] > cell[0] + 1 ||
                        qefPoint[1] < cell[1] || qefPoint[1] > cell[1] + 1 ||
                        qefPoint[2] < cell[2] || qefPoint[2] > cell[2] + 1)
                    {
                        AlignedBox3d cellBbox(cell.cast<double>());
                        cellBbox.extend((cell + Vec3i::Ones()).cast<double>());
                        Vec3d rayDirection = averagePoint - qefPoint;
                        double alpha = computeRayBBoxIntersection(cellBbox, qefPoint, rayDirection);

                        if (alpha <= 1)
                        {
                            qefPoint += alpha * rayDirection;
                        }
                        else
                        {
                            qefPoint = averagePoint;
                        }
                    }

                    localDCPoints.emplace_back(cell, qefPoint);
                }
            }
        });

        mergeLocalThreadVectors(dcPointPair, parallelDCPoints);
    }

    VecVec3d vertices(dcPointPair.size());

    // Set DC point index for direct look up when building mesh and
    // convert points to world space.
    tbb::parallel_for(tbb::blocked_range<size_t>(0, dcPointPair.size(), tbbLightGrainSize), [&](const tbb::blocked_range<size_t>& range)
    {
        for (size_t index = range.begin(); index != range.end(); ++index)
        {
            const Vec3i& cell = dcPointPair[index].first;

            assert(dcPointIndices(cell) == -1);
            dcPointIndices(cell) = int(index);

            const Vec3d& point = dcPointPair[index].second;
            vertices[index] = indexToWorld(point);
        }
    });

    // Build triangle mesh using dual contouring points

    VecVec3i triangles;

    for (int edgeAxis : {0, 1, 2})
    {
        Vec3i start = Vec3i::Zero();
        ++start[(edgeAxis + 1) % 3];
        ++start[(edgeAxis + 2) % 3];

        Vec3i end = dcPointIndices.size();

        tbb::enumerable_thread_specific<VecVec3i> parallelTriangles;

        auto loopRange3d = tbb::blocked_range3d<int>(start[0], end[0], int(std::cbrt(tbbLightGrainSize)), start[1], end[1],
                                      int(std::cbrt(tbbLightGrainSize)), start[2], end[2], int(std::cbrt(tbbLightGrainSize)));

        tbb::parallel_for(loopRange3d, [&](const tbb::blocked_range3d<int>& range)
        {
            auto& localTriFaces = parallelTriangles.local();

            Vec3i edge;
            for (edge[0] = range.pages().begin(); edge[0] != range.pages().end(); ++edge[0])
                for (edge[1] = range.rows().begin(); edge[1] != range.rows().end(); ++edge[1])
                    for (edge[2] = range.cols().begin(); edge[2] != range.cols().end(); ++edge[2])
                    {
                        Vec3i backwardNode = edgeToNode(edge, edgeAxis, 0);
                        Vec3i forwardNode = edgeToNode(edge, edgeAxis, 1);

                        if ((myPhiGrid(backwardNode) <= 0 && myPhiGrid(forwardNode) > 0) ||
                            (myPhiGrid(backwardNode) > 0 && myPhiGrid(forwardNode) <= 0))
                        {
                            std::array<Vec3i, 4> dcCells;
                            std::array<int, 4> vertexIndices;

                            for (int cellIndex = 0; cellIndex < 4; ++cellIndex)
                            {
                                dcCells[cellIndex] = edgeToCellCCW(edge, edgeAxis, cellIndex);
                                vertexIndices[cellIndex] = dcPointIndices(dcCells[cellIndex]);
                                assert(vertexIndices[cellIndex] >= 0);
                            }

                            if (myPhiGrid(backwardNode) <= 0.)
                            {
                                localTriFaces.emplace_back(vertexIndices[0], vertexIndices[1], vertexIndices[2]);
                                localTriFaces.emplace_back(vertexIndices[0], vertexIndices[2], vertexIndices[3]);
                            }
                            else
                            {
                                localTriFaces.emplace_back(vertexIndices[0], vertexIndices[2], vertexIndices[1]);
                                localTriFaces.emplace_back(vertexIndices[0], vertexIndices[3], vertexIndices[2]);
                            }
                        }
                    }
        });

        mergeLocalThreadVectors(triangles, parallelTriangles);
    }

    return TriMesh(triangles, vertices);
}

void LevelSet::clear()
{
    myPhiGrid.clear();
}

void LevelSet::resize(const Vec3i& size)
{
    myPhiGrid.resize(size);
}

// Find the nearest point on the interface starting from the index position.
// If the position falls outside of the narrow band, there isn't a defined gradient
// to use. In this case, the original position will be returned.
Vec3d LevelSet::findSurface(const Vec3d& worldPoint, int iterationLimit, double tolerance) const
{
    assert(iterationLimit >= 0);

    double phi = myPhiGrid.triLerp(worldPoint);

    double epsilon = tolerance * dx();
    Vec3d tempPoint = worldPoint;

    int iterationCount = 0;
    if (std::fabs(phi) < myNarrowBand)
    {
        while (std::fabs(phi) > epsilon && iterationCount < iterationLimit)
        {
            tempPoint -= phi * .8 * normal(tempPoint);
            phi = myPhiGrid.triLerp(tempPoint);
            ++iterationCount;
        }
    }

    return tempPoint;
}

Vec3d LevelSet::interpolateInterface(const Vec3i& startPoint, const Vec3i& endPoint) const
{
    assert((myPhiGrid(startPoint) <= 0 && myPhiGrid(endPoint) > 0) ||
           (myPhiGrid(startPoint) > 0 && myPhiGrid(endPoint) <= 0));

    // Find weight to zero isosurface
    double theta = lengthFraction(myPhiGrid(startPoint), myPhiGrid(endPoint));

    assert(theta >= 0 && theta <= 1);

    if (myPhiGrid(startPoint) > 0)
        theta = 1. - theta;

    return startPoint.cast<double>() + theta * (endPoint - startPoint).cast<double>();
}

void LevelSet::drawGrid(Renderer& renderer, bool doOnlyNarrowBand) const
{
    if (doOnlyNarrowBand)
    {
        forEachVoxelRange(Vec3i::Zero().eval(), size(), [&](const Vec3i& cell)
        {
            if (std::fabs(myPhiGrid(cell)) < myNarrowBand) myPhiGrid.drawGridCell(renderer, cell);
        });
    }
    else
        myPhiGrid.drawGrid(renderer);
}

void LevelSet::drawGridPlane(Renderer& renderer, Axis planeAxis, double position, bool doOnlyNarrowBand) const
{
    position = std::clamp(position, double(0), double(1));

    Vec3i start = Vec3i::Zero();
    Vec3i end(myPhiGrid.size() - Vec3i::Ones());

    if (planeAxis == Axis::XAXIS)
    {
        start[0] = int(std::floor(position * double(myPhiGrid.size()[0] - 1)));
        end[0] = start[0] + 1;
    }
    else if (planeAxis == Axis::YAXIS)
    {
        start[1] = int(std::floor(position * double(myPhiGrid.size()[1] - 1)));
        end[1] = start[1] + 1;
    }
    else if (planeAxis == Axis::ZAXIS)
    {
        start[2] = int(std::floor(position * double(myPhiGrid.size()[2] - 1)));
        end[2] = start[2] + 1;
    }

    forEachVoxelRange(start, end, [&](const Vec3i& cell)
    {
        if (doOnlyNarrowBand)
        {
            if (std::fabs(myPhiGrid(cell)) < myNarrowBand) myPhiGrid.drawGridCell(renderer, cell);
        }
        else
            myPhiGrid.drawGridCell(renderer, cell);
    });
}

// Display a supersampled slice of the grid. The plane will have a normal in the plane_axis direction.
// The position is from [0,1] where 0 is at the grid origin and 1 is at the origin + size * dx.
void LevelSet::drawSupersampledValuesPlane(Renderer& renderer, Axis planeAxis, double position, double radius,
                                           int samples, double sampleSize) const
{
    myPhiGrid.drawSupersampledValuesPlane(renderer, planeAxis, position, radius, samples, sampleSize);
}
void LevelSet::drawSampleNormalsPlane(Renderer& renderer, Axis planeAxis, double position, const Vec3d& colour,
                                      double length) const
{
    myPhiGrid.drawSampleGradientsPlane(renderer, planeAxis, position, colour, length);
}

void LevelSet::drawSurface(Renderer& renderer, const Vec3d& colour, double lineWidth) const
{
    TriMesh tempMesh = buildMesh();
    tempMesh.drawMesh(renderer, true, colour, lineWidth);
}

//
// Private methods
//

void LevelSet::initFromMeshImpl(const TriMesh& initialMesh, bool doResizeGrid)
{
    if (doResizeGrid)
    {
        // Determine the bounding box of the mesh to build the underlying grids
		AlignedBox3d bbox = initialMesh.boundingBox();

		// Expand grid beyond the narrow band of the mesh
		double maxPadding = 50. * dx();
		maxPadding = std::min(2. * myNarrowBand, maxPadding);

		bbox.extend(bbox.min() - Vec3d::Constant(maxPadding));
		bbox.extend(bbox.max() + Vec3d::Constant(maxPadding));

		Vec3d origin = indexToWorld(floor(worldToIndex(bbox.min())).eval());
		Transform xform(dx(), origin);
		Vec3d topRight = indexToWorld(ceil(worldToIndex(bbox.max())).eval());

		// TODO: add the ability to reset grid so we don't have ot re-allocate memory
		myPhiGrid = ScalarGrid<double>(xform, ((topRight - origin) / dx()).cast<int>(), myIsBackgroundNegative ? -myNarrowBand : myNarrowBand);
    }

    // We want to track which cells in the level set contain valid distance information.
    // The first pass will set cells close to the mesh as FINISHED.
    UniformGrid<VisitedCellLabels> reinitializedCells(size(), VisitedCellLabels::UNVISITED_CELL);
    UniformGrid<int> meshCellParities(size(), 0);

    for (const Vec3i& tri : initialMesh.triangles())
    {
        // It's easier to work in our index space and just scale the distance later.
        std::array<Vec3d, 3> triVertices;
        for (int localVertexIndex : {0, 1, 2})
            triVertices[localVertexIndex] = worldToIndex(initialMesh.vertex(tri[localVertexIndex]));

		AlignedBox3d triBbox(triVertices[0]);
		triBbox.extend(triVertices[1]);
		triBbox.extend(triVertices[2]);

        Vec3i triCeilMin = ceil(triBbox.min()).cast<int>();
        Vec3i triFloorMin = floor(triBbox.min()).cast<int>() - Vec3i::Ones();
        Vec3i triFloorMax = floor(triBbox.max()).cast<int>();

        // Z-axis intersection tests. Iterate along an aligned set of grid edges
        // in decsending order, checking for intersections at each edge.
        // If an intersection is found then we can stop searching along the set.
        for (int i = triCeilMin[0]; i <= triFloorMax[0]; ++i)
            for (int j = triFloorMin[1]; j <= triFloorMax[1]; ++j)
                for (int k = triFloorMax[2]; k >= triFloorMin[2]; --k)
                {
                    Vec3d gridPoint(i, j, k);
                    IntersectionLabels intersectionResult = exactTriIntersect(gridPoint, triVertices[0], triVertices[1], triVertices[2], Axis::ZAXIS);

                    for (int axis : {0, 1, 2})
				        assert(gridPoint[axis] >= 0 && gridPoint[axis] < myPhiGrid.size()[axis] - 1);

                    if (intersectionResult == IntersectionLabels::NO) continue;

                    int parityChange = -1;
                    double qrs = orient2d(triVertices[0].data(), triVertices[1].data(), triVertices[2].data());
                    assert(qrs != 0);
                    if (qrs < 0)
                        parityChange = 1;

                    if (intersectionResult == IntersectionLabels::YES)
                        meshCellParities(i, j, k + 1) += parityChange;
                    else
                    {
                        assert(intersectionResult == IntersectionLabels::ON);

                        if (parityChange == 1)
                            meshCellParities(i, j, k) += parityChange;
                        else
                        {
                            meshCellParities(i, j, k + 1) += parityChange;
                        }
                    }

                    break;
                }
    }

    // Now that all the z-axis edge crossings have been found, we can compile the parity changes
    // and label grid nodes that are at the interface
    for (int i = 0; i < size()[0]; ++i)
        for (int j = 0; j < size()[1]; ++j)
        {
            int parity = myIsBackgroundNegative ? 1 : 0;

            for (int k = 0; k < size()[2]; ++k)
            {
                Vec3i cell(i, j, k);

                parity += meshCellParities(cell);
                meshCellParities(cell) = parity;

                if (parity > 0)
                    myPhiGrid(cell) = -myNarrowBand;
                else
                    myPhiGrid(cell) = myNarrowBand;
            }

            assert(myIsBackgroundNegative ? parity == 1 : parity == 0);
        }

    // With the parity assigned, loop over the grid once more and label nodes that have a sign change
    // with neighbouring nodes (this means parity goes from -'ve (and zero) to +'ve or vice versa).
    forEachVoxelRange(Vec3i::Ones(), size() - Vec3i::Ones(), [&](const Vec3i& cell)
    {
        bool isCellInside = meshCellParities(cell) > 0;

        for (int axis : {0, 1, 2})
            for (int direction : {0, 1})
            {
                Vec3i adjacentCell = cellToCell(cell, axis, direction);

                bool isAdjacentCellInside = meshCellParities(adjacentCell) > 0;

                if (isCellInside != isAdjacentCellInside)
                    reinitializedCells(cell) = VisitedCellLabels::FINISHED_CELL;
            }
    });

    // Loop over all the triangles in the mesh. Level set grid cells labelled as FINISHED will be
    // updated with the distance to the surface if it happens to be shorter than the current
    // distance to the surface.
    for (const Vec3i& tri : initialMesh.triangles())
    {
        std::array<Vec3d, 3> vertices;
        for (int localVertexIndex : {0, 1, 2})
            vertices[localVertexIndex] = worldToIndex(initialMesh.vertex(tri[localVertexIndex]));

        AlignedBox3d triBbox(vertices[0]);
        triBbox.extend(vertices[1]);
        triBbox.extend(vertices[2]);

        // Expand outward by 2-voxels in each direction
        triBbox.extend(triBbox.min() - Vec3d::Constant(2));
        triBbox.extend(triBbox.max() + Vec3d::Constant(2));

        AlignedBox3d clampBbox;
        clampBbox.extend(Vec3d::Zero());
        clampBbox.extend(size().cast<double>() - Vec3d::Ones());

        triBbox.clamp(clampBbox);

        for (int axis : {0, 1, 2})
            assert(triBbox.min()[axis] >= 0 && triBbox.max()[axis] < size()[axis]);

        forEachVoxelRange(triBbox.min().cast<int>(), triBbox.max().cast<int>() + Vec3i::Ones(), [&](const Vec3i& cell)
        {
            if (reinitializedCells(cell) != VisitedCellLabels::UNVISITED_CELL)
            {
                Vec3d cellPoint = cell.cast<double>();

                Vec3d triProjectionPoint = pointToTriangleProjection(cellPoint, vertices[0], vertices[1], vertices[2]);

                double surfaceDistance = (cellPoint - triProjectionPoint).norm() * dx();

                // If the new distance is closer than existing distance values, update cell
                if (std::fabs(myPhiGrid(cell)) > surfaceDistance)
                {
                    // If the parity says the node is inside, set it to be negative
                    myPhiGrid(cell) = (meshCellParities(cell) > 0) ? -surfaceDistance : surfaceDistance;
                }
            }
        });
    }

    reinitFastMarching(reinitializedCells);
}

void LevelSet::reinitFastMarching(UniformGrid<VisitedCellLabels>& reinitializedCells)
{
    assert(reinitializedCells.size() == size());

    auto solveEikonal2D = [&](double Ux, double Uy) -> double
    {
        if (std::fabs(Ux - Uy) >= dx())
            return std::min(Ux, Uy) + dx();
        else
        {
            // Quadratic equation from the Eikonal
            double rootEntry = std::pow(Ux + Uy, 2) - 2. * (std::pow(Ux, 2) + std::pow(Uy, 2) - std::pow(dx(), 2));
            assert(rootEntry >= 0);
            return .5 * (Ux + Uy + std::sqrt(rootEntry));
        }
    };

    auto solveEikonal = [&](const Vec3i& cell) -> double
    {
        double max = std::numeric_limits<double>::max();

        Vec3d Uaxis = Vec3d::Constant(max);
		for (int axis : {0, 1, 2})
			for (int direction : {0, 1})
			{
				Vec3i adjacentCell = cellToCell(cell, axis, direction);

				if (adjacentCell[axis] < 0 || adjacentCell[axis] >= size()[axis])
				{
					assert(myPhiGrid(cell) > 0 && !myIsBackgroundNegative || myPhiGrid(cell) < 0 && myIsBackgroundNegative);
					Uaxis[axis] = std::min(max, Uaxis[axis]);
				}
				else
				{
					Uaxis[axis] = std::min(std::fabs(myPhiGrid(adjacentCell)), Uaxis[axis]);
				}
			}

        double discrim = std::pow(Uaxis.sum(), 2) - 3. * (Uaxis.squaredNorm() - std::pow(dx(), 2));
        if (discrim < 0.)
        {
            double dist = std::min(std::min(solveEikonal2D(Uaxis[0], Uaxis[1]), solveEikonal2D(Uaxis[1], Uaxis[2])), solveEikonal2D(Uaxis[0], Uaxis[2]));

            assert(std::isfinite(dist));

            return dist;
        }
        else
        {
            double dist = (Uaxis.sum() + std::sqrt(discrim)) / 3.;
            assert(std::isfinite(dist));
            return dist;
        }
    };

    // Load up the BFS queue with the unvisited cells next to the finished ones
    using Node = std::pair<Vec3i, double>;
    auto cmp = [](const Node& a, const Node& b) -> bool { return std::fabs(a.second) > std::fabs(b.second); };
    std::priority_queue<Node, std::vector<Node>, decltype(cmp)> marchingQ(cmp);

    forEachVoxelRange(Vec3i::Zero(), reinitializedCells.size(), [&](const Vec3i& cell)
    {
        if (reinitializedCells(cell) == VisitedCellLabels::FINISHED_CELL)
        {
            for (int axis : {0, 1, 2})
                for (int direction : {0, 1})
                {
                    Vec3i adjacentCell = cellToCell(cell, axis, direction);

                    if (adjacentCell[axis] < 0 || adjacentCell[axis] >= reinitializedCells.size()[axis]) continue;

                    if (reinitializedCells(adjacentCell) == VisitedCellLabels::UNVISITED_CELL)
                    {
                        double dist = solveEikonal(adjacentCell);

                        if (!std::isfinite(dist))
                            int a = 0;
                        assert(dist >= 0);

                        myPhiGrid(adjacentCell) = myPhiGrid(adjacentCell) < 0 ? -dist : dist;

                        Node node(adjacentCell, dist);

                        marchingQ.push(node);
                        reinitializedCells(adjacentCell) = VisitedCellLabels::VISITED_CELL;
                    }
                }
        }
    });

    while (!marchingQ.empty())
    {
        Node localNode = marchingQ.top();
        Vec3i localCell = localNode.first;
        marchingQ.pop();

        // Since you can't just update parts of the priority queue,
        // it's possible that a cell has been solidified at a smaller distance
        // and an older insert if doubleing around.
        if (reinitializedCells(localCell) == VisitedCellLabels::FINISHED_CELL)
        {
            // Make sure that the distance assigned to the cell is smaller than
            // what is doubleing around
            assert(std::fabs(myPhiGrid(localCell)) <= std::fabs(localNode.second));
            continue;
        }
        assert(reinitializedCells(localCell) == VisitedCellLabels::VISITED_CELL);

        if (std::fabs(myPhiGrid(localCell)) < myNarrowBand)
        {
            // Debug check that there is indeed a FINISHED cell next to it
            bool foundFinishedCell = false;

            for (int axis : {0, 1, 2})
                for (int direction : {0, 1})
                {
                    Vec3i adjacentCell = cellToCell(localCell, axis, direction);

                    if (adjacentCell[axis] < 0 || adjacentCell[axis] >= reinitializedCells.size()[axis]) continue;

                    if (reinitializedCells(adjacentCell) == VisitedCellLabels::FINISHED_CELL)
                        foundFinishedCell = true;
                    else
                    {
                        double dist = solveEikonal(adjacentCell);
                        assert(dist >= 0);

                        if (dist > myNarrowBand) dist = myNarrowBand;

                        if (reinitializedCells(adjacentCell) == VisitedCellLabels::VISITED_CELL &&
                            dist > std::fabs(myPhiGrid(adjacentCell)))
                            continue;

                        myPhiGrid(adjacentCell) = myPhiGrid(adjacentCell) < 0 ? -dist : dist;

                        Node node(adjacentCell, dist);

                        marchingQ.push(node);
                        reinitializedCells(adjacentCell) = VisitedCellLabels::VISITED_CELL;
                    }
                }
            assert(foundFinishedCell);
        }
        else
            myPhiGrid(localCell) = myPhiGrid(localCell) < 0 ? -myNarrowBand : myNarrowBand;

        reinitializedCells(localCell) = VisitedCellLabels::FINISHED_CELL;
    }
}

Vec3d LevelSet::findSurfaceIndex(const Vec3d& indexPoint, int iterationLimit, double tolerance) const
{
    Vec3d worldPoint = indexToWorld(indexPoint);
    worldPoint = findSurface(worldPoint, iterationLimit, tolerance);
    return worldToIndex(worldPoint);
}

}


#include <iostream>
#include <fstream>
#include <sstream>
#include <stdlib.h>
#include <Structs/Graphs/dynamicGraph.h>
#include <Structs/Graphs/adjacencyListImpl.h>
#include <Algorithms/multicriteriaDijkstra.h>
#include <Algorithms/multicriteriaGraph.h>
#include <Algorithms/namoaStar.h>
#include <Algorithms/namoaStar2.h>
#include <Algorithms/multicriteriaArc.h>
#include <Heuristics/blind.h>
#include <Heuristics/ideal.h>
#include <Heuristics/boundedIdeal.h>
#include <Checkers/multiCriteriaChecker.h>
#include <Checkers/multiCriteriaResults.h>
#include <Checkers/Grids/multiCriteriaGridChecker.h>
#include <Utilities/timer.h>
#include <Utilities/colormod.h>
#include <boost/program_options.hpp>

namespace po = boost::program_options;

template< class algorithmVariant, typename GraphType>
void runQueries( GraphType& G, std::vector< std::pair<unsigned int,unsigned int> >& queries,
                 std::vector<typename GraphType::NodeDescriptor>& ids, GridChecker& gChecker, const unsigned int showOnScreen)
{
    typedef typename GraphType::NodeIterator NodeIterator;
    NodeIterator s,t;
    unsigned int sourceId, targetId;
    unsigned int timestamp = 0;
    //create algorithm
    algorithmVariant algorithm( G, NUM_CRITERIA, &timestamp);
    //run queries
    unsigned int query_n = 0;
    double numLabels = 0;
    double totalTime = 0;
    for( std::vector< std::pair<unsigned int,unsigned int> >::iterator it = queries.begin();
         it != queries.end(); ++it)
    {
        //clear nodes
        NodeIterator u, lastnode;
        for( u = G.beginNodes(), lastnode = G.endNodes(); u != lastnode; ++u)
        {
            u->g_op.clear();
            u->g_cl.clear();
        }
        sourceId = it->first;
        targetId = it->second;
        s = G.getNodeIterator( ids[sourceId]);
        t = G.getNodeIterator( ids[targetId]);
        Timer timer;
        timer.start();
        algorithm.init( s, t, NUM_CRITERIA);
        auto heuristicTime = 1000 * timer.getElapsedTime();
        timer.start();
        algorithm.runQuery( s, t);
        auto runtime = 1000 * timer.getElapsedTime();
        totalTime += runtime;
        // add Pareto efficient solutions to the results class
        MulticriteriaResults mResults( queries);
        mResults.addResults( t->g_cl);
        numLabels += algorithm.getGeneratedLabels();
        // Two things are checked: First, the set of solutions is the same; second, the solutions have been found in the same order.
        bool order = true;
        bool correctness = mResults.checkParetoCosts( gChecker, query_n, order);
        Color::Modifier def(Color::FG_DEFAULT);
        Color::Modifier red(Color::FG_RED);
        Color::Modifier green(Color::FG_GREEN);
        Color::Modifier yellow(Color::FG_YELLOW);
        Color::Modifier b_blue(Color::BG_BLUE);
        Color::Modifier b_def(Color::BG_DEFAULT);

        std::cout << b_blue << "(" << sourceId << "->" << targetId << ")" << b_def << "\t\t";

        if ( showOnScreen)
        {
            Color::Modifier blue(Color::FG_BLUE);
            Color::Modifier lblue(Color::FG_LIGHT_BLUE);
            Color::Modifier cyan(Color::FG_CYAN);
            Color::Modifier lcyan(Color::FG_LIGHT_CYAN);
            std::cout << blue << t->g_cl.size() << def << " efficient paths were found.\n";
            std::cout << "\t\t\t" << lblue << algorithm.getGeneratedLabels() << def << " labels were scanned.\n";
            std::cout << "\t\t\t" << cyan << heuristicTime << def << " msec. - calculation of heuristic.\n";
            std::cout << "\t\t\t" << lcyan << runtime << def << " msec. - algorithm runtime.\n";
        }
        std::cout << "Solutions ... ->\t";
        if ( !( correctness))
        {
            std::cout << red << "Different!!!\n" << def;
            t->printLabels( std::cout, G);
            exit (EXIT_FAILURE);
        }
        else
        {
            std::cout << green << "OK" << def << "\n";
            if (! (order)) std::cout << yellow << "Warning!" << def << " The same set of solutions was found, but in different order.\n";
        }
        std::cout << "---------------------------------------------------------------------\n";
        ++query_n;
    }

    if ( showOnScreen)
    {
        std::cout << "\tAlgorithm Runtime:\t" << totalTime << " msec. ( " << (totalTime / queries.size()) << " msec. per query)\n";
        std::cout << "\tGenerated labels:\t " << numLabels << std::endl;
    }
}

template< typename GraphType>
void runBenchmarks( GraphType& G, std::vector< std::pair<unsigned int,unsigned int> >& queries,
                    std::vector<typename GraphType::NodeDescriptor>& ids, GridChecker& gChecker,
                    const std::string& name, const unsigned int& algorithmVariant, const unsigned int showOnScreen)
{
    switch( algorithmVariant)
    {
    case 1:
        // output message for all queries
        std::cout << "with NAMOA* (blind) ...\n\n";
        runQueries<NamoaStar2<GraphType,BlindHeuristic> >( G, queries, ids, gChecker, showOnScreen);
        break;
    case 2:
        std::cout << "with NAMOA*_tc ...\n\n";
        runQueries<NamoaStar2<GraphType,TCHeuristic> >( G, queries, ids, gChecker, showOnScreen);
        break;
    case 3:
        std::cout << "with NAMOA*_bound_tc ...\n\n";
        runQueries<NamoaStar2<GraphType,BoundedTCHeuristic> >( G, queries, ids, gChecker, showOnScreen);
        //std::cout << "with NAMOA* Arc Flags (" << name << ") ...\n\n";
        //runQueries<NamoaStarArc<GraphType,GreatCircleHeuristic> >( G, queries, ids, gChecker, showOnScreen);
        break;
    case 0: // default
        std::cout << "with NAMOA* (blind) ...\n\n";
        runQueries<NamoaStar2<GraphType,BlindHeuristic> >( G, queries, ids, gChecker, showOnScreen);
        std::cout << "with NAMOA*_tc ...\n\n";
        runQueries<NamoaStar2<GraphType,TCHeuristic> >( G, queries, ids, gChecker, showOnScreen);
        std::cout << "with NAMOA*_bound_tc ...\n\n";
        runQueries<NamoaStar2<GraphType,BoundedTCHeuristic> >( G, queries, ids, gChecker, showOnScreen);
        break;
    }
}

typedef DynamicGraph< AdjacencyListImpl, Node, Edge>       Graph;
//typedef DynamicGraph< AdjacencyListImpl, LWNode, Edge>     LWGraph;
typedef Graph::NodeIterator                                NodeIterator;
typedef Graph::EdgeIterator                                EdgeIterator;
typedef Graph::NodeDescriptor                              NodeDescriptor;

int main( int argc, char* argv[])
{
    std::string gridBenchmarksPath = "/home/francis/Projects/Benchmarks/";
    std::string gridsPath = gridBenchmarksPath + "grids/";
    std::string gridQueriesPath = gridBenchmarksPath + "queries/";
    std::string gridSolutionsPath = gridBenchmarksPath + "solutions/";

    unsigned int benchmarkVariant = 2;
    unsigned int algorithmVariant = 0;
    unsigned int showOnScreen = 1;

    // Declare the supported options.
    po::options_description desc("Allowed options");
    desc.add_options()
        ("benchmark,b", po::value< unsigned int>(), "Benchmark to run. Bicriteria[2],  Tri-criterion[3]. Default:2")
        ("algorithm,a", po::value< unsigned int>(), "Multicriteria Heuristic. All[0], Blind[1], Ideal Point[2], Bounded Ideal point[3]. Default:0")
        ("showOnScreen,s", po::value< unsigned int>(), "Display stats on screen. Yes[1], No[0]. Default:1");

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

    if (vm.empty()) {
        std::cout << desc << "\n";
        return 0;
    }
    if (vm.count("benchmark"))
    {
        benchmarkVariant = vm["benchmark"].as<unsigned int>();
    }
    if (vm.count("algorithm"))
    {
        algorithmVariant = vm["algorithm"].as<unsigned int>();
    }
    if (vm.count("showOnScreen"))
    {
        showOnScreen = vm["showOnScreen"].as<unsigned int>();
    }
    unsigned int nqueries;
    if ( benchmarkVariant == 2)
    {
        NUM_CRITERIA = 2;
        nqueries = NQUERIES_2;
    }
    else if ( benchmarkVariant == 3)
    {
        NUM_CRITERIA = 3;
        nqueries = NQUERIES_3;
    }
    else
    {
        std::cerr << "BenchmarkVariant provided not implemented yet\n";
    }

    for ( unsigned int grid_n = 0; grid_n < NGRIDS; ++grid_n)
    {
        GridChecker gChecker( nqueries, benchmarkVariant,
                              gridQueriesPath + "queries" + std::to_string( benchmarkVariant) + ".txt",
                              gridSolutionsPath + "p" + std::to_string( grid_n) + "/" +
                              std::to_string( benchmarkVariant) + ".txt",
                              GRIDDIMSIZE);
        std::vector< std::pair< unsigned int ,unsigned int> > queries;
        queries = gChecker.getQueries (benchmarkVariant);
        Graph G;
        std::string gridProblemPath = gridsPath + "Grid" + std::to_string( grid_n)+ ".txt";
        std::cout << "\nBenchmark at " << gridProblemPath;
        GridReader<Graph> reader( gridProblemPath, benchmarkVariant);
        Timer timer;
        timer.start();
        G.read(&reader);
        std::cout << "Graph has " << (double)G.memUsage()/1048576 << " Mbytes. Time spent to read:\t" << timer.getElapsedTime() << "sec" << std::endl;
        std::cout << "Checking correctness of " << queries.size() << " queries \n";
        timer.start();
        runBenchmarks( G, queries, reader.getIds(), gChecker, "ADJ", algorithmVariant, showOnScreen);
        std::cout << "\tBenchmark runtime: \t" << timer.getElapsedTime() << " sec" << std::endl;
    }
    return 0;
}


// Boost.Geometry - gis-projections (based on PROJ4)

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

// This file was modified by Oracle on 2017, 2018.
// Modifications copyright (c) 2017-2018, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle.

// 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

// Last updated version of proj: 5.0.0

// 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.

#ifndef BOOST_GEOMETRY_PROJECTIONS_EQDC_HPP
#define BOOST_GEOMETRY_PROJECTIONS_EQDC_HPP

#include <boost/geometry/srs/projections/impl/base_static.hpp>
#include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>
#include <boost/geometry/srs/projections/impl/pj_mlfn.hpp>
#include <boost/geometry/srs/projections/impl/pj_msfn.hpp>
#include <boost/geometry/srs/projections/impl/pj_param.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>

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

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

namespace boost { namespace geometry
{

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace eqdc
    {

            static const double epsilon10 = 1.e-10;

            template <typename T>
            struct par_eqdc
            {
                T    phi1;
                T    phi2;
                T    n;
                T    rho0;
                T    c;
                detail::en<T> en;
                bool ellips;
            };

            // template class, using CRTP to implement forward/inverse
            template <typename T, typename Parameters>
            struct base_eqdc_ellipsoid
                : public base_t_fi<base_eqdc_ellipsoid<T, Parameters>, T, Parameters>
            {
                par_eqdc<T> m_proj_parm;

                inline base_eqdc_ellipsoid(const Parameters& par)
                    : base_t_fi<base_eqdc_ellipsoid<T, Parameters>, T, Parameters>(*this, par)
                {}

                // FORWARD(e_forward)  sphere & ellipsoid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T lp_lon, T const& lp_lat, T& xy_x, T& xy_y) const
                {
                    T rho = 0.0;

                    rho = this->m_proj_parm.c - (this->m_proj_parm.ellips ? pj_mlfn(lp_lat, sin(lp_lat),
                        cos(lp_lat), this->m_proj_parm.en) : lp_lat);
                    xy_x = rho * sin( lp_lon *= this->m_proj_parm.n );
                    xy_y = this->m_proj_parm.rho0 - rho * cos(lp_lon);
                }

                // INVERSE(e_inverse)  sphere & ellipsoid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T xy_x, T xy_y, T& lp_lon, T& lp_lat) const
                {
                    static T const half_pi = detail::half_pi<T>();

                    T rho = 0.0;

                    if ((rho = boost::math::hypot(xy_x, xy_y = this->m_proj_parm.rho0 - xy_y)) != 0.0 ) {
                        if (this->m_proj_parm.n < 0.) {
                            rho = -rho;
                            xy_x = -xy_x;
                            xy_y = -xy_y;
                        }
                        lp_lat = this->m_proj_parm.c - rho;
                        if (this->m_proj_parm.ellips)
                            lp_lat = pj_inv_mlfn(lp_lat, this->m_par.es, this->m_proj_parm.en);
                        lp_lon = atan2(xy_x, xy_y) / this->m_proj_parm.n;
                    } else {
                        lp_lon = 0.;
                        lp_lat = this->m_proj_parm.n > 0. ? half_pi : -half_pi;
                    }
                }

                static inline std::string get_name()
                {
                    return "eqdc_ellipsoid";
                }

            };

            // Equidistant Conic
            template <typename Params, typename Parameters, typename T>
            inline void setup_eqdc(Params const& params, Parameters& par, par_eqdc<T>& proj_parm)
            {
                T cosphi, sinphi;
                int secant;

                proj_parm.phi1 = pj_get_param_r<T, srs::spar::lat_1>(params, "lat_1", srs::dpar::lat_1);
                proj_parm.phi2 = pj_get_param_r<T, srs::spar::lat_2>(params, "lat_2", srs::dpar::lat_2);

                if (fabs(proj_parm.phi1 + proj_parm.phi2) < epsilon10)
                    BOOST_THROW_EXCEPTION( projection_exception(error_conic_lat_equal) );

                proj_parm.en = pj_enfn<T>(par.es);

                proj_parm.n = sinphi = sin(proj_parm.phi1);
                cosphi = cos(proj_parm.phi1);
                secant = fabs(proj_parm.phi1 - proj_parm.phi2) >= epsilon10;
                if( (proj_parm.ellips = (par.es > 0.)) ) {
                    double ml1, m1;

                    m1 = pj_msfn(sinphi, cosphi, par.es);
                    ml1 = pj_mlfn(proj_parm.phi1, sinphi, cosphi, proj_parm.en);
                    if (secant) { /* secant cone */
                        sinphi = sin(proj_parm.phi2);
                        cosphi = cos(proj_parm.phi2);
                        proj_parm.n = (m1 - pj_msfn(sinphi, cosphi, par.es)) /
                            (pj_mlfn(proj_parm.phi2, sinphi, cosphi, proj_parm.en) - ml1);
                    }
                    proj_parm.c = ml1 + m1 / proj_parm.n;
                    proj_parm.rho0 = proj_parm.c - pj_mlfn(par.phi0, sin(par.phi0),
                        cos(par.phi0), proj_parm.en);
                } else {
                    if (secant)
                        proj_parm.n = (cosphi - cos(proj_parm.phi2)) / (proj_parm.phi2 - proj_parm.phi1);
                    proj_parm.c = proj_parm.phi1 + cos(proj_parm.phi1) / proj_parm.n;
                    proj_parm.rho0 = proj_parm.c - par.phi0;
                }
            }

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

    /*!
        \brief Equidistant Conic projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
         - Ellipsoid
        \par Projection parameters
         - lat_1: Latitude of first standard parallel (degrees)
         - lat_2: Latitude of second standard parallel (degrees)
        \par Example
        \image html ex_eqdc.gif
    */
    template <typename T, typename Parameters>
    struct eqdc_ellipsoid : public detail::eqdc::base_eqdc_ellipsoid<T, Parameters>
    {
        template <typename Params>
        inline eqdc_ellipsoid(Params const& params, Parameters const& par)
            : detail::eqdc::base_eqdc_ellipsoid<T, Parameters>(par)
        {
            detail::eqdc::setup_eqdc(params, this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::spar::proj_eqdc, eqdc_ellipsoid, eqdc_ellipsoid)

        // Factory entry(s)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(eqdc_entry, eqdc_ellipsoid)

        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_BEGIN(eqdc_init)
        {
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(eqdc, eqdc_entry);
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_EQDC_HPP