/* ----------------------------------------------------------------------- *//**
 *
 * @file multilogistic.cpp
 *
 * @brief Multinomial Logistic-Regression functions
 *
 * We implement the iteratively-reweighted-least-squares method.
 *
 *//* ----------------------------------------------------------------------- */

#include <dbconnector/dbconnector.hpp>

#include <modules/shared/HandleTraits.hpp>
#include <modules/prob/boost.hpp>
#include <boost/math/distributions.hpp>
#include <modules/prob/student.hpp>
#include "multilogistic.hpp"

#include <vector>
#include <fstream>

namespace madlib {

// Use Eigen
using namespace dbal::eigen_integration;

namespace modules {

// Import names from other MADlib modules
using dbal::NoSolutionFoundException;

namespace regress {

/**
 * @brief Logistic function
 */
inline double sigma(double x) {
    return 1. / (1. + std::exp(-x));
}

/**
 * @brief Inter- and intra-iteration state for iteratively-reweighted-least-
 *        squares method for logistic regression
 *
 * TransitionState encapsualtes the transition state during the
 * logistic-regression aggregate function. To the database, the state is
 * exposed as a single DOUBLE PRECISION array, to the C++ code it is a proper
 * object containing scalars, a vector, and a matrix.
 *
 * Note: We assume that the DOUBLE PRECISION array is initialized by the
 * database with length at least 4, and all elemenets are 0.
 */
template <class Handle>
class MLogRegrIRLSTransitionState {

    // By §14.5.3/9: "Friend declarations shall not declare partial
    // specializations." We do access protected members in operator+=().
    template <class OtherHandle>
    friend class MLogRegrIRLSTransitionState;

public:

    MLogRegrIRLSTransitionState(const AnyType &inArray)
        : mStorage(inArray.getAs<Handle>()) {
        rebind(static_cast<uint16_t>(mStorage[0]),static_cast<uint16_t>(mStorage[1]));
    }

    /**
     * @brief Convert to backend representation
     *
     * We define this function so that we can use State in the
     * argument list and as a return type.
     */
    inline operator AnyType() const {
        return mStorage;
    }

    /**
     * @brief Initialize the iteratively-reweighted-least-squares state.
     *
     * This function is only called for the first iteration, for the first row.
     */
    inline void initialize(
        const Allocator &inAllocator,
        uint16_t inWidthOfX,
        uint16_t inNumCategories, uint16_t inRefCategory) {

        size_t state_size = arraySize(inWidthOfX, inNumCategories);
        // GPDB limits the single array size to be 1GB, which means that the size
        // of a double array cannot be large than 134217727 because
        // (134217727 * 8) / (1024 * 1024) = 1023. And solve
        // state_size = x^2 + 2^x + 6 <= 134217727 will give x <= 11584.
        if(state_size > 134217727)
            throw std::domain_error(
                "The product of number of independent variables and number of "
                 "categories cannot be larger than 11584.");

        mStorage = inAllocator.allocateArray<double, dbal::AggregateContext,
            dbal::DoZero, dbal::ThrowBadAlloc>(state_size);
        rebind(inWidthOfX, inNumCategories);
        widthOfX = inWidthOfX;
        numCategories = inNumCategories;
        ref_category = inRefCategory;
    }

    /**
     * @brief We need to support assigning the previous state
     */
    template <class OtherHandle> MLogRegrIRLSTransitionState &operator=(
        const MLogRegrIRLSTransitionState<OtherHandle> &inOtherState) {

        for (size_t i = 0; i < mStorage.size(); i++)
            mStorage[i] = inOtherState.mStorage[i];
        return *this;
    }

    /**
     * @brief Merge with another State object by copying the intra-iteration
     *     fields
     */
    template <class OtherHandle> MLogRegrIRLSTransitionState &operator+=(
        const MLogRegrIRLSTransitionState<OtherHandle> &inOtherState) {
        if (mStorage.size() != inOtherState.mStorage.size() ||
            widthOfX != inOtherState.widthOfX)
            throw std::logic_error("Internal error: Incompatible transition "
                "states");

        numRows += inOtherState.numRows;
        gradient += inOtherState.gradient;
        X_transp_AX += inOtherState.X_transp_AX;
        logLikelihood += inOtherState.logLikelihood;
        return *this;
    }

    /**
     * @brief Reset the inter-iteration fields.
     */
    inline void reset() {
        numRows = 0;
        gradient.fill(0);
        X_transp_AX.fill(0);
        logLikelihood = 0;
    }

private:
    static inline uint32_t arraySize(const uint16_t inWidthOfX,
        const uint16_t inNumCategories) {
        return 6 + inWidthOfX * inWidthOfX * inNumCategories * inNumCategories
                                 + 2 * inWidthOfX * inNumCategories;
    }

    void rebind(uint16_t inWidthOfX = 0, uint16_t inNumCategories = 0) {
        widthOfX.rebind(&mStorage[0]);
        numCategories.rebind(&mStorage[1]);
        conditionNo.rebind(&mStorage[2]);

        coef.rebind(&mStorage[3], inWidthOfX*inNumCategories);

        numRows.rebind(&mStorage[3 + inWidthOfX*inNumCategories]);

        gradient.rebind(&mStorage[4 + inWidthOfX*inNumCategories],inWidthOfX*inNumCategories);
        X_transp_AX.rebind(&mStorage[4 + 2 * inWidthOfX*inNumCategories],
            inNumCategories*inWidthOfX, inWidthOfX*inNumCategories);
        logLikelihood.rebind(&mStorage[4 +
             inNumCategories*inNumCategories*inWidthOfX*inWidthOfX
             + 2 * inWidthOfX*inNumCategories]);
        ref_category.rebind(&mStorage[5 +
             inNumCategories*inNumCategories*inWidthOfX*inWidthOfX
             + 2 * inWidthOfX*inNumCategories]);
    }

    Handle mStorage;

public:
    typename HandleTraits<Handle>::ReferenceToUInt16 widthOfX;
    typename HandleTraits<Handle>::ReferenceToUInt16 numCategories;
    typename HandleTraits<Handle>::ReferenceToDouble conditionNo;

    typename HandleTraits<Handle>::ColumnVectorTransparentHandleMap coef;
    typename HandleTraits<Handle>::ReferenceToUInt64 numRows;
    typename HandleTraits<Handle>::ColumnVectorTransparentHandleMap gradient;
    typename HandleTraits<Handle>::MatrixTransparentHandleMap X_transp_AX;
    typename HandleTraits<Handle>::ReferenceToDouble logLikelihood;
    typename HandleTraits<Handle>::ReferenceToUInt16 ref_category;
};

/**
 * @brief Inter- and intra-iteration state for robust variance calculations
 *
 * TransitionState encapsualtes the transition state during the
 * logistic-regression robust variance calculation. To the database, the state is
 * exposed as a single DOUBLE PRECISION array, to the C++ code it is a proper
 * object containing scalars, a vector, and a matrix.
 *
 * Note: We assume that the DOUBLE PRECISION array is initialized by the
 * database with length at least 4, and all elemenets are 0.
 */
template <class Handle>
class MLogRegrRobustTransitionState {

    // By §14.5.3/9: "Friend declarations shall not declare partial
    // specializations." We do access protected members in operator+=().
    template <class OtherHandle>
    friend class MLogRegrRobustTransitionState;

public:
    MLogRegrRobustTransitionState(const AnyType &inArray)
        : mStorage(inArray.getAs<Handle>()) {
        rebind(static_cast<uint16_t>(mStorage[0]),static_cast<uint16_t>(mStorage[1]));
    }

    /**
     * @brief Convert to backend representation
     *
     * We define this function so that we can use State in the
     * argument list and as a return type.
     */
    inline operator AnyType() const {
        return mStorage;
    }

    /**
     * @brief Initialize the iteratively-reweighted-least-squares state.
     *
     * This function is only called for the first iteration, for the first row.
     */
    inline void initialize(const Allocator &inAllocator,
        uint16_t inWidthOfX, uint16_t inNumCategories, uint16_t inRefCategory) {
        mStorage = inAllocator.allocateArray<double, dbal::AggregateContext,
            dbal::DoZero, dbal::ThrowBadAlloc>(arraySize(inWidthOfX, inNumCategories));
        rebind(inWidthOfX, inNumCategories);
        widthOfX = inWidthOfX;
        numCategories = inNumCategories;
        ref_category = inRefCategory;
    }

    /**
     * @brief We need to support assigning the previous state
     */
    template <class OtherHandle> MLogRegrRobustTransitionState &operator=(
        const MLogRegrRobustTransitionState<OtherHandle> &inOtherState) {
        for (size_t i = 0; i < mStorage.size(); i++)
            mStorage[i] = inOtherState.mStorage[i];
        return *this;
    }

    /**
     * @brief Merge with another State object by copying the intra-iteration
     *     fields
     */
    template <class OtherHandle> MLogRegrRobustTransitionState &operator+=(
        const MLogRegrRobustTransitionState<OtherHandle> &inOtherState) {
        if (mStorage.size() != inOtherState.mStorage.size() ||
            widthOfX != inOtherState.widthOfX)
            throw std::logic_error("Internal error: Incompatible transition "
                "states");

        numRows += inOtherState.numRows;
        X_transp_AX += inOtherState.X_transp_AX;
        meat += inOtherState.meat;
        return *this;
    }

    /**
     * @brief Reset the inter-iteration fields.
     */
    inline void reset() {
        numRows = 0;
        meat.fill(0);
        X_transp_AX.fill(0);
    }

private:
    static inline uint32_t arraySize(const uint16_t inWidthOfX,
        const uint16_t inNumCategories) {
        return 4 + 2*inWidthOfX * inWidthOfX * inNumCategories * inNumCategories
                                 + inWidthOfX * inNumCategories;
    }

    /**
     * @brief Rebind to a new storage array
     *
     * @param inWidthOfX             The number of independent variables.
     * @param inNumCategories The number of categories of the dependant var

     * Array layout (iteration refers to one aggregate-function call):
     * Inter-iteration components (updated in final function):
     * - 0: widthOfX (number of independant variables)
     * - 1: numCategories (number of categories)
     * - 2: ref_category
     * - 3: coef (vector of coefficients)
     *
     * Intra-iteration components (updated in transition step):
     * - 3 + widthOfX*numCategories: numRows (number of rows already processed in this iteration)
     * - 4 + widthOfX * inNumCategories: X_transp_AX (X^T A X).
     * - 4 + widthOfX^2*numCategories^2: meat  (The meat matrix)
     */
    void rebind(uint16_t inWidthOfX = 0, uint16_t inNumCategories = 0) {
        widthOfX.rebind(&mStorage[0]);
        numCategories.rebind(&mStorage[1]);
        ref_category.rebind(&mStorage[2]);
        coef.rebind(&mStorage[3], inWidthOfX * inNumCategories);
        numRows.rebind(&mStorage[3 + inWidthOfX * inNumCategories]);
        X_transp_AX.rebind(&mStorage[4 + inWidthOfX * inNumCategories],
            inNumCategories * inWidthOfX, inWidthOfX * inNumCategories);
        meat.rebind(&mStorage[4 +
             inNumCategories * inNumCategories * inWidthOfX * inWidthOfX
             + inWidthOfX * inNumCategories], inWidthOfX * inNumCategories, inWidthOfX * inNumCategories);
    }

    Handle mStorage;

public:
    typename HandleTraits<Handle>::ReferenceToUInt16 widthOfX;
    typename HandleTraits<Handle>::ReferenceToUInt16 numCategories;
    typename HandleTraits<Handle>::ReferenceToUInt16 ref_category;

    typename HandleTraits<Handle>::ColumnVectorTransparentHandleMap coef;
    typename HandleTraits<Handle>::ReferenceToUInt64 numRows;
    typename HandleTraits<Handle>::MatrixTransparentHandleMap X_transp_AX;
    typename HandleTraits<Handle>::MatrixTransparentHandleMap meat;
};

/**
 * @brief IRLS Transition
 * @param args
 *
 * Arguments (Matched with PSQL wrapped)
 * - 0: Current State
 * - 1: y value (Integer)
 * - 2: numCategories (Integer)
 * - 3: ref_category (Integer)
 * - 4: X value (Column Vector)
 * - 5: Previous State

 */
AnyType
__mlogregr_irls_step_transition::run(AnyType &args) {
    MLogRegrIRLSTransitionState<MutableArrayHandle<double> > state = args[0];

    if (args[1].isNull() || args[2].isNull() || args[3].isNull() ||
            args[4].isNull()) {
        return args[0];
    }

    // Get x as a vector of double
    MappedColumnVector x;
    try{
        // an exception is raised in the backend if args[2] contains nulls
        MappedColumnVector xx = args[4].getAs<MappedColumnVector>();
        // x is a const reference, we can only rebind to change its pointer
        x.rebind(xx.memoryHandle(), xx.size());
    } catch (const ArrayWithNullException &e) {
        return args[0];
    }

    // Get the category & numCategories as integer
    int32_t category = args[1].getAs<int32_t>();
    // Number of categories after pivoting (we pivot around the first category)
    int32_t numCategories = (args[2].getAs<int32_t>() - 1);
    int32_t ref_category = args[3].getAs<int32_t>();

    // The following check was added with MADLIB-138.
    if (!x.is_finite())
            throw std::domain_error("Design matrix is not finite.");

    if (state.numRows == 0) {
        if (x.size() > std::numeric_limits<uint16_t>::max())
                throw std::domain_error("Number of independent variables cannot be "
                        "larger than 65535.");

        if (numCategories < 1)
                throw std::domain_error("Number of cateogires must be at least 2");

        // Init the state (requires x.size() and category.size())
        state.initialize(*this,
            static_cast<uint16_t>(x.size()) ,
            static_cast<uint16_t>(numCategories),
            static_cast<uint16_t>(ref_category));

        if (!args[5].isNull()) {
                MLogRegrIRLSTransitionState<ArrayHandle<double> >
                        previousState = args[5];
                state = previousState;
                state.reset();
        }
    }

    /*
     * This check should be done for each iteration. Only checking the first
     * run is not enough.
     */
    if (category > numCategories || category < 0)
        throw std::domain_error("Invalid category. Categories must be integer "
            "values between 0 and (number of categories - 1).");

    if (ref_category > numCategories || ref_category < 0)
        throw std::domain_error("Invalid reference category. Reference category must be integer "
            "value between 0 and (number of categories - 1).");

    // Now do the transition step
    state.numRows++;
    /*     Get y: Convert to 1/0 boolean vector
            Example: Category 4 : 0 0 0 1 0 0
            Storing it in this forms helps us get a nice closed form expression
    */

    //To pivot around the specified reference category
    ColumnVector y(numCategories);
    y.fill(0);
    if (category > ref_category) {
        y(category - 1) = 1;
    } else if (category < ref_category) {
        y(category) = 1;
    }

    /*
    Compute the parameter vector (the 'pi' vector in the documentation)
    for the data point being processed.
    Casting the coefficients into a matrix makes the calculation simple.
    */
    Matrix coef = state.coef;
    coef.resize(numCategories, state.widthOfX);

    //Store the intermediate calculations because we'll reuse them in the LLH
    ColumnVector t1 = x; //t1 is vector of size state.widthOfX
    t1 = coef*x;
    /* Note: The above 2 lines could have been written as:
        ColumnVector t1 = -coef*x;

        but this creates warnings. These warnings are somehow related to the factor
        that x is of an immutable type. The following alternative could resolve
        the warnings (although not necessary the best alternative):
    */

    ColumnVector t2 = t1.array().exp();
    double t3 = 1 + t2.sum();
    ColumnVector pi = t2/t3;

    //The gradient matrix has numCategories rows and widthOfX columns
    Matrix grad = -y*x.transpose() + pi*x.transpose();
    //We cast the gradient into a vector to make the Newton step calculations much easier.
    grad.resize(numCategories*state.widthOfX,1);

    /*
         a is a matrix of size JxJ where J is the number of categories
         a_j1j2 = -pi(j1)*(1-pi(j2))if j1 == j2
         a_j1j2 =  pi(j1)*pi(j2) if j1 != j2
    */
    Matrix a(numCategories,numCategories);
    // Compute the 'a' matrix.
    Matrix piDiag = pi.asDiagonal();
    a = pi * pi.transpose() - piDiag;
    state.gradient.noalias() += grad;

    //Start the Hessian calculations
    Matrix X_transp_AX(numCategories * state.widthOfX, numCategories * state.widthOfX);

    /*
        Again: The following 3 lines could have been written as
        Matrix XXTrans = x * x.transpose();
        but it creates warnings related to the type of x. Here is an easy fix
    */
    Matrix cv_x = x;
    Matrix XXTrans = trans(cv_x);
    XXTrans = cv_x * XXTrans;

    //Eigen doesn't supported outer-products for matrices, so we have to do our own.
    //This operation is also known as a tensor-product.
    for (int i1 = 0; i1 < state.widthOfX; i1++){
         for (int i2 = 0; i2 <state.widthOfX; i2++){
            int rowOffset = numCategories * i1;
            int colOffset = numCategories * i2;

            X_transp_AX.block(rowOffset, colOffset, numCategories,  numCategories) = XXTrans(i1,i2)*a;
        }
    }

    triangularView<Lower>(state.X_transp_AX) += X_transp_AX;

    state.logLikelihood += y.transpose()*t1 - log(t3);

    return state;

}

/**
 * @brief Perform the perliminary aggregation function: Merge transition states
     This merge step is the same as that of logistic regression.
 */
AnyType
__mlogregr_irls_step_merge_states::run(AnyType &args) {
    MLogRegrIRLSTransitionState<MutableArrayHandle<double> > stateLeft = args[0];
    MLogRegrIRLSTransitionState<ArrayHandle<double> > stateRight = args[1];

    // We first handle the trivial case where this function is called with one
    // of the states being the initial state
    if (stateLeft.numRows == 0)
        return stateRight;
    else if (stateRight.numRows == 0)
        return stateLeft;

    // Merge states together and return
    stateLeft += stateRight;
    return stateLeft;
}

/**
 * @brief Perform the logistic-regression final step
 */
AnyType
__mlogregr_irls_step_final::run(AnyType &args) {
    // We request a mutable object.
    // Depending on the backend, this might perform a deep copy.
    MLogRegrIRLSTransitionState<MutableArrayHandle<double> > state = args[0];

    // Aggregates that haven't seen any data just return Null.
    if (state.numRows == 0)
        return Null();

    // See MADLIB-138. At least on certain platforms and with certain versions,
    // LAPACK will run into an infinite loop if pinv() is called for non-finite
    // matrices. We extend the check also to the dependent variables.
    if (!state.X_transp_AX.is_finite() || !state.gradient.is_finite())
        throw NoSolutionFoundException("Over- or underflow in intermediate "
            "calculation. Input data is likely of poor numerical condition.");

    SymmetricPositiveDefiniteEigenDecomposition<Matrix> decomposition(
        -1 * state.X_transp_AX, EigenvaluesOnly, ComputePseudoInverse);

    // Precompute (X^T * A * X)^-1
    Matrix hessianInv = -1 * decomposition.pseudoInverse();

    state.coef.noalias() += hessianInv * state.gradient;

    if(!state.coef.is_finite())
        throw NoSolutionFoundException("Over- or underflow in Newton step, "
            "while updating coefficients. Input data is likely of poor "
            "numerical condition.");

    // We use the intra-iteration field gradient for storing the diagonal
    // of X^T A X, so that we don't have to recompute it in the result function.
    // Likewise, we store the condition number.
    // FIXME: This feels a bit like a hack.
    state.conditionNo = decomposition.conditionNo();
    state.X_transp_AX = -1 * hessianInv;

    return state;
}

/**
 * @brief Compute the diagnostic statistics
 *
 * This function wraps the common parts of mlogregr state into the result.
 * (the result data type is defined in multilogistic.sql_in)
 */
AnyType mLogstateToResult(
    const Allocator &inAllocator,
    MLogRegrIRLSTransitionState<ArrayHandle<double> > state) {

    int ref_category = state.ref_category;
    const HandleMap<const ColumnVector, TransparentHandle<double> > &inCoef = state.coef;
    double logLikelihood = state.logLikelihood;
    uint64_t num_processed = state.numRows;

    // Per the hack at the end of the final function we place the inverse
    // of the X_tranp_AX into the state.X_transp_AX
    const Matrix & X_transp_AX_inverse = state.X_transp_AX;
    const ColumnVector &diagonal_of_hessian = X_transp_AX_inverse.diagonal();

    MutableNativeColumnVector stdErr(
        inAllocator.allocateArray<double>(inCoef.size()));
    MutableNativeColumnVector waldZStats(
        inAllocator.allocateArray<double>(inCoef.size()));
    MutableNativeColumnVector waldPValues(
        inAllocator.allocateArray<double>(inCoef.size()));
    MutableNativeColumnVector oddsRatios(
        inAllocator.allocateArray<double>(inCoef.size()));

    for (Index i = 0; i < inCoef.size(); ++i) {
        stdErr(i) = std::sqrt(diagonal_of_hessian(i));
        waldZStats(i) = inCoef(i) / stdErr(i);
        waldPValues(i) = 2. * prob::cdf( prob::normal(),
            -std::abs(waldZStats(i)));
        oddsRatios(i) = std::exp( inCoef(i) );
    }
    int num_iterations = 0;
    // Return all coefficients, standard errors, etc. in a tuple
    AnyType tuple;
    tuple << ref_category << inCoef << logLikelihood << stdErr
          << waldZStats << waldPValues << oddsRatios
          << static_cast<double>(state.conditionNo) << num_iterations << num_processed;
    return tuple;
}

/**
 * @brief Return the difference in log-likelihood between two states
 */
AnyType
__internal_mlogregr_irls_step_distance::run(AnyType &args) {
    MLogRegrIRLSTransitionState<ArrayHandle<double> > stateLeft = args[0];
    MLogRegrIRLSTransitionState<ArrayHandle<double> > stateRight = args[1];

    return std::abs(stateLeft.logLikelihood - stateRight.logLikelihood);
}

/**
 * @brief Return the coefficients and diagnostic statistics of the state
 */
AnyType
__internal_mlogregr_irls_result::run(AnyType &args) {
    MLogRegrIRLSTransitionState<ArrayHandle<double> > state = args[0];

    return mLogstateToResult(*this, state);
    // state.ref_category, state.coef,
    // state.gradient, state.logLikelihood, state.X_transp_AX(0,0));
}

/**
 * @brief Return the coefficients and diagnostic statistics of the state
 */
AnyType
__internal_mlogregr_summary_results::run(AnyType &args) {
    MLogRegrIRLSTransitionState<ArrayHandle<double> > state = args[0];
    const Matrix & X_transp_AX_inverse = state.X_transp_AX;
    // X_transp_AX is actually it's inverse - this is a hack added at the end of
    // the final function
    AnyType tuple;
    Matrix coef = state.coef;
    coef.resize(state.numCategories, state.widthOfX);
    coef.transposeInPlace();
    tuple << coef << X_transp_AX_inverse;
    return tuple;
}
// ----------------- End of Multinomial Logistic Regression --------------------

// ---------------------------------------------------------------------------
//             Robust Variance Multi-Logistic
// ---------------------------------------------------------------------------

AnyType
mlogregr_robust_step_transition::run(AnyType &args) {
    using std::endl;

	MLogRegrRobustTransitionState<MutableArrayHandle<double> > state = args[0];

   if (args[1].isNull() || args[2].isNull() || args[3].isNull() ||
            args[4].isNull() || args[5].isNull()) {
        return args[0];
    }

    // Get x as a vector of double
    MappedColumnVector x;
    try{
        // an exception is raised in the backend if args[2] contains nulls
        MappedColumnVector xx = args[4].getAs<MappedColumnVector>();
        // x is a const reference, we can only rebind to change its pointer
        x.rebind(xx.memoryHandle(), xx.size());
    } catch (const ArrayWithNullException &e) {
        return args[0];
    }

    // Get the category & numCategories as integer
    int16_t category = static_cast<int16_t>(args[1].getAs<int>());
    // Number of categories after pivoting (We pivot around the first category)
    int16_t numCategories = static_cast<int16_t>(args[2].getAs<int>() - 1);
    int32_t ref_category = args[3].getAs<int32_t>();
	MappedMatrix coefMat = args[5].getAs<MappedMatrix>();

    // The following check was added with MADLIB-138.
    if (!x.is_finite())
            throw std::domain_error("Design matrix is not finite.");

    if (state.numRows == 0) {
        if (x.size() > std::numeric_limits<uint16_t>::max())
                throw std::domain_error("Number of independent variables cannot be "
                        "larger than 65535.");
        if (numCategories < 1)
                throw std::domain_error("Number of cateogires must be at least 2");
        if (category > numCategories)
                throw std::domain_error("You have entered a category > numCategories"
                    "Categories must be of values {0,1... numCategories-1}");

        // Init the state (requires x.size() and category.size())
        state.initialize(*this,
            static_cast<uint16_t>(x.size()) ,
            static_cast<uint16_t>(numCategories),
            static_cast<uint16_t>(ref_category));

        Matrix mat = coefMat;
        mat.transposeInPlace();
        mat.resize(coefMat.size(), 1);
        state.coef = mat;
    }

    // Now do the transition step
    state.numRows++;
    /*
        Get y: Convert to 1/0 boolean vector
        Example: Category 4 : 0 0 0 1 0 0
        Storing it in this forms helps us get a nice closed form expression
    */

    //To pivot around the specified reference category
    ColumnVector y(numCategories);
    y.fill(0);
    if (category > ref_category) {
        y(category - 1) = 1;
    } else if (category < ref_category) {
        y(category) = 1;
    }

    /*
    Compute the parameter vector (the 'pi' vector in the documentation)
    for the data point being processed.
    Casting the coefficients into a matrix makes the calculation simple.
    */

    Matrix coef = state.coef;
    coef.resize(numCategories, state.widthOfX);

    //Store the intermediate calculations because we'll reuse them in the LLH
    ColumnVector t1 = x; //t1 is vector of size state.widthOfX
    t1 = coef*x;
    /*
        Note: The above 2 lines could have been written as:
        ColumnVector t1 = -coef*x;

        but this creates warnings. These warnings are somehow related to the factor
        that x is an immutable type.
    */

    ColumnVector t2 = t1.array().exp();
    double t3 = 1 + t2.sum();
    ColumnVector pi = t2/t3;

	//The gradient matrix has numCategories rows and widthOfX columns
    Matrix grad = -y * x.transpose() + pi * x.transpose();
    //We cast the gradient into a vector to make the math easier.
    grad.resize(numCategories * state.widthOfX, 1);

    Matrix GradGradTranspose;
    GradGradTranspose = grad * grad.transpose();
	state.meat += GradGradTranspose;

    /*
         a is a matrix of size JxJ where J is the number of categories
         a_j1j2 = -pi(j1)*(1-pi(j2))if j1 == j2
         a_j1j2 =  pi(j1)*pi(j2) if j1 != j2
    */
    // Compute the 'a' matrix.
    Matrix a(numCategories,numCategories);
    Matrix piDiag = pi.asDiagonal();
    a = pi * pi.transpose() - piDiag;

    //Start the Hessian calculations
    Matrix X_transp_AX(numCategories * state.widthOfX, numCategories * state.widthOfX);

    /*
        Again: The following 3 lines could have been written as
        Matrix XXTrans = x * x.transpose();
        but it creates warnings related to the type of x. Here is an easy fix
    */
    Matrix cv_x = x;
    Matrix XXTrans = trans(cv_x);
    XXTrans = cv_x * XXTrans;

    //Eigen doesn't supported outer-products for matrices, so we have to do our own.
    //This operation is also known as a tensor-product.
    for (int i1 = 0; i1 < state.widthOfX; i1++){
         for (int i2 = 0; i2 <state.widthOfX; i2++){
            int rowOffset = numCategories * i1;
            int colOffset = numCategories * i2;

            X_transp_AX.block(rowOffset, colOffset, numCategories,  numCategories) = XXTrans(i1, i2) * a;
        }
    }

    triangularView<Lower>(state.X_transp_AX) += X_transp_AX;

    return state;

}

/**
 * @brief Perform the perliminary aggregation function: Merge transition states
     This merge step is the same as that of logistic regression.
 */
AnyType
mlogregr_robust_step_merge_states::run(AnyType &args) {
    MLogRegrRobustTransitionState<MutableArrayHandle<double> > stateLeft = args[0];
    MLogRegrRobustTransitionState<ArrayHandle<double> > stateRight = args[1];

    // We first handle the trivial case where this function is called with one
    // of the states being the initial state
    if (stateLeft.numRows == 0)
        return stateRight;
    else if (stateRight.numRows == 0)
        return stateLeft;

    // Merge states together and return
    stateLeft += stateRight;
    return stateLeft;
}

AnyType MLrobuststateToResult(
    const Allocator &inAllocator,
    int ref_category,
    const HandleMap<const ColumnVector, TransparentHandle<double> >& inCoef,
    const ColumnVector &diagonal_of_varianceMat) {

	MutableNativeColumnVector variance(
        inAllocator.allocateArray<double>(inCoef.size()));
    MutableNativeColumnVector stdErr(
        inAllocator.allocateArray<double>(inCoef.size()));
    MutableNativeColumnVector waldZStats(
        inAllocator.allocateArray<double>(inCoef.size()));
    MutableNativeColumnVector waldPValues(
        inAllocator.allocateArray<double>(inCoef.size()));

    for (Index i = 0; i < inCoef.size(); ++i) {
        variance(i) = diagonal_of_varianceMat(i);
        stdErr(i) = std::sqrt(diagonal_of_varianceMat(i));
        waldZStats(i) = inCoef(i) / stdErr(i);
        waldPValues(i) = 2. * prob::cdf(
            prob::normal(), -std::abs(waldZStats(i)));
    }

    // Return all coefficients, standard errors, etc. in a tuple
    AnyType tuple;
    tuple <<  ref_category << inCoef << stdErr << waldZStats << waldPValues;
    return tuple;
}

/**
 * @brief Perform the logistic-regression final step
 */
AnyType
mlogregr_robust_step_final::run(AnyType &args) {
    // We request a mutable object. Depending on the backend, this might perform
    // a deep copy.
    MLogRegrRobustTransitionState<ArrayHandle<double> > state = args[0];
    // Aggregates that haven't seen any data just return Null.
    if (state.numRows == 0)
        return Null();

    // See MADLIB-138. At least on certain platforms and with certain versions,
    // LAPACK will run into an infinite loop if pinv() is called for non-finite
    // matrices. We extend the check also to the dependent variables.
    if (!state.X_transp_AX.is_finite())
        throw NoSolutionFoundException("Over- or underflow in intermediate "
            "calculation. Input data is likely of poor numerical condition.");

    SymmetricPositiveDefiniteEigenDecomposition<Matrix> decomposition(
        -1 * state.X_transp_AX, EigenvaluesOnly, ComputePseudoInverse);

    // Precompute (X^T * A * X)^-1
    Matrix bread = decomposition.pseudoInverse();
	Matrix varianceMat;
    varianceMat = bread * state.meat * bread;

    if(!state.coef.is_finite())
        throw NoSolutionFoundException("Over- or underflow in Newton step, "
            "while updating coefficients. Input data is likely of poor "
            "numerical condition.");

    return MLrobuststateToResult(*this, state.ref_category, state.coef, varianceMat.diagonal());
}
// ------------------------ End of Robust Variance -----------------------------

AnyType __sub_array::run(AnyType &args) {
    if (args[0].isNull() || args[1].isNull())
        return Null();

    ArrayHandle<double> value = args[0].getAs<ArrayHandle<double> >();
    ArrayHandle<int32> index = args[1].getAs<ArrayHandle<int32> >();

    for (size_t i = 0; i < index.size(); i++) {
        if (index[i] < 1 || index[i] > static_cast<int>(value.size()))
            throw std::domain_error("Invalid indices - out of bound");
    }

    MutableArrayHandle<double> res =
        allocateArray<double, dbal::AggregateContext, dbal::DoZero,
            dbal::ThrowBadAlloc>(index.size());

    for (size_t i = 0; i < index.size(); i++)
        res[i] = value[index[i] - 1];

    return res;
}

// ----------------------------------------------------------------------------
typedef struct __sr_ctx{
    const double * inarray;
    int32_t maxcall;
    int32_t num_feature;
    int32_t num_category;
    int32_t ref_category;
    int32_t curcall;
} sr_ctx;

void *
__mlogregr_format::SRF_init(AnyType &args) {
    sr_ctx *ctx = new sr_ctx;
    ctx->curcall = 0;

    MutableArrayHandle<double> inarray = NULL;
    try{
        inarray = args[0].getAs<MutableArrayHandle<double> >();
    } catch (const ArrayWithNullException &e) {
        ctx->maxcall = 0;
        return ctx;
    }

    int32_t num_feature = args[1].getAs<int32_t>();
    int32_t num_category = args[2].getAs<int32_t>();
    int32_t ref_category = args[3].getAs<int32_t>();

    ctx->inarray = inarray.ptr();
    ctx->maxcall = num_category - 1;
    ctx->num_category = num_category - 1;
    ctx->num_feature = num_feature;
    ctx->ref_category = ref_category;

    if (num_feature * (num_category - 1) !=
            static_cast<int32_t>(inarray.size())) {
        throw std::runtime_error("num_feature * (num_category - 1) != "
                "inarray.size()");
    }

    if (ref_category >= num_category){
        throw std::runtime_error("ref_category >= num_category");
    }

    return ctx;
}

AnyType
__mlogregr_format::SRF_next(void * user_fctx, bool * is_last_call) {
    sr_ctx * ctx = (sr_ctx *) user_fctx;
    if (ctx->curcall >= ctx->maxcall) {
        *is_last_call = true;
        return Null();
    }

    MutableArrayHandle<double> outarray =
        allocateArray<double, dbal::FunctionContext,
            dbal::DoZero, dbal::ThrowBadAlloc>(ctx->num_feature);
    for(int i = 0; i < ctx->num_feature; i++) {
        outarray[i] = ctx->inarray[i * ctx->num_category + ctx->curcall];
    }

    AnyType tuple;
    tuple << (ctx->curcall < ctx->ref_category
            ? ctx->curcall
            : ctx->curcall + 1)
          << outarray;

    ctx->curcall++;

    return tuple;
}
/* ------------------------------------------------------------ */

AnyType mlogregr_predict_prob::run(AnyType &args)
{
    // dimension: N x (L - 1), where L is the number of categories
    MappedMatrix coef = args[0].getAs<MappedMatrix>();

    int ref_category = args[1].getAs<int>();
    MappedColumnVector x;
    try {
        MappedColumnVector xx = args[2].getAs<MappedColumnVector>();
        x.rebind(xx.memoryHandle(), xx.size());
    } catch (const ArrayWithNullException &e) {
        return Null();
    }

    int L = static_cast<int>(coef.cols()) + 1; // number of categories

    ColumnVector res(L);
    int cat_index = 0;
    for (int i = 0; i < L; i++) {
        if (i == ref_category) {
            res(i) = 1;
        } else {
            res(i) = exp(coef.col(cat_index).dot(x));
            cat_index++;
        }
    }
    res /= res.sum();
    return res;
}
/* ------------------------------------------------------------ */

AnyType mlogregr_predict_response::run(AnyType &args)
{
    // dimension: N x (L - 1), where L is the number of categories
    MappedMatrix coef = args[0].getAs<MappedMatrix>();

    int ref_category = args[1].getAs<int>();
    MappedColumnVector x;
    try {
        MappedColumnVector xx = args[2].getAs<MappedColumnVector>();
        x.rebind(xx.memoryHandle(), xx.size());
    } catch (const ArrayWithNullException &e) {
        return Null();
    }

    ColumnVector linear_predictors(x.transpose() * coef);
    Index max_cat = 0;
    linear_predictors.maxCoeff(&max_cat);
    double max_lp = linear_predictors(max_cat);

    if (exp(max_lp) < 1){
        // no category has high enough probability as reference category
        return ref_category;
    } else if (max_cat < ref_category){
        return static_cast<uint>(max_cat);
    }
    else{
        // since ref_category is not present in the coef matrix, index of
        // categories after ref_category is 1 less than the actual index
        return static_cast<uint>(max_cat + 1u);
    }
}

} // namespace regress

} // namespace modules

} // namespace madlib


// Copyright (c) 2014
// Akira Takahashi, Fumiki Fukuda.
// Released under the CC0 1.0 Universal license.

#include <iostream>
#include <cmath> // sqrt
#include <algorithm> // min
#include <utility> // pair
#include <boost/utility/enable_if.hpp>

struct point_category {};

template <class T, class Enable = void>
struct get_geometry_category;

template <class T, class Enable = void>
struct point_traits {
  static double x(const T& p) { return p.x(); }
  static double y(const T& p) { return p.y(); }

  static T subtract(const T& a, const T& b)
    { return T(a.x() - b.x(), a.y() - b.y()); }
};

class point {
  double x_ = 0;
  double y_ = 0;
public:
  point() = default;
  point(double x, double y)
    : x_(x), y_(y) {}

  // それぞれの座標を取得する
  double x() const { return x_; }
  double y() const { return y_; }
};

template <>
struct get_geometry_category<point> {
  typedef point_category type;
};

// 別の計算幾何ライブラリ
namespace geo {

template <class T>
struct is_point_category {
  static const bool value = false;
};

template <class T>
struct point_traits {
  static double getX(const T& p) { return p.getX(); }
  static double getY(const T& p) { return p.getY(); }

  static T subtract(const T& a, const T& b)
    { return T(a.getX() - b.getX(), a.getY() - b.getY()); }
};

class point {
  double x_ = 0;
  double y_ = 0;
public:
  point() = default;
  point(double x, double y)
    : x_(x), y_(y) {}

  // それぞれの座標を取得する
  double getX() const { return x_; }
  double getY() const { return y_; }
};

template <>
struct is_point_category<point> {
  static const bool value = true;
};

template <>
struct point_traits<std::pair<double, double>> {
  typedef std::pair<double, double> point_type;

  static double getX(const point_type& p)
    { return p.first; }

  static double getY(const point_type& p)
    { return p.second; }

  static point_type subtract(
                      const point_type& a,
                      const point_type& b)
  {
    return std::make_pair(
             a.first - b.first,
             a.second - b.second);
  }
};

template <>
struct is_point_category<std::pair<double, double>> {
  static const bool value = true;
};

} // namespace geo

template <class T>
struct get_geometry_category<
         T,
         typename boost::enable_if<
           geo::is_point_category<T>
         >::type
       > {
  typedef point_category type;
};

template <class T>
struct point_traits<
         T,
         typename boost::enable_if<
           geo::is_point_category<T>
         >::type
       > {
  static double x(const T& p) { return geo::point_traits<T>::getX(p); }
  static double y(const T& p) { return geo::point_traits<T>::getY(p); }

  static T subtract(const T& a, const T& b)
    { return geo::point_traits<T>::subtract(a, b); }
};

// 点と点
template <class Point>
double distance_impl(Point a, Point b,
                     point_category, point_category)
{
  typedef point_traits<Point> traits;
  const Point d = traits::subtract(a, b);
  return std::sqrt(traits::x(d) * traits::x(d) +
                   traits::y(d) * traits::y(d));
}

template <class Geometry1, class Geometry2>
double distance(Geometry1 a, Geometry2 b)
{
  return distance_impl(a, b,
           typename get_geometry_category<Geometry1>::type(),
           typename get_geometry_category<Geometry2>::type());
}

int main()
{
  {
    point p1(0.0, 0.0);
    point p2(3.0, 3.0);

    double d = distance(p1, p2);
    std::cout << d << std::endl;
  }
  {
    geo::point p1(0.0, 0.0);
    geo::point p2(3.0, 3.0);

    double d = distance(p1, p2);
    std::cout << d << std::endl;
  }
  {
    std::pair<double, double> p1(0.0, 0.0);
    std::pair<double, double> p2(3.0, 3.0);

    double d = distance(p1, p2);
    std::cout << d << std::endl;
  }
}


/******************************************************************************

  Modified Christiansen algorithm for tiling two contours.

  Copyright (c) 2013
  Alexander Rukletsov <rukletsov@gmail.com>
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions
  are met:
  1.  Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
  2.  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.

  THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.

*******************************************************************************/

#ifndef CHRISTIANSEN_TILING_HPP_D80E68A2_DF31_11E2_BAA7_4057033B4CBB
#define CHRISTIANSEN_TILING_HPP_D80E68A2_DF31_11E2_BAA7_4057033B4CBB

#include <vector>
#include <limits>
#include <stdexcept>
#include <utility>
#include <boost/function.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

#include "bo/distances/distances_3d.hpp"
#include "bo/surfaces/detail/container_traversers.hpp"
#include "bo/surfaces/detail/indexed_tstrip.hpp"

namespace bo {
namespace surfaces {

namespace detail {

// A struct incorporating contour data. Instances can be safely swapped.
template <typename PointsCont>
struct ContourDescriptor
{
    typedef boost::shared_ptr<PointsCont> PointsContPtr;

    ContourDescriptor(std::size_t id, PointsContPtr points_ptr, bool is_closed)
        : ID(id), PointsPtr(points_ptr), IsClosed(is_closed), Size(points_ptr->size())
    { }

    void swap(ContourDescriptor& other)
    {
        // Clone data before swapping in order not to spoil original contours.
        PointsContPtr new_this(new PointsCont(*PointsPtr));
        PointsContPtr new_other(new PointsCont(*other.PointsPtr));
        PointsPtr = new_other;
        other.PointsPtr = new_this;

        std::swap(ID, other.ID);
        std::swap(IsClosed, other.IsClosed);
        std::swap(Size, other.Size);
    }

    std::size_t ID;
    PointsContPtr PointsPtr;
    bool IsClosed;
    std::size_t Size;
};

} // namespace detail

// A class performing modified Christiansen tiling for two slices. Accepts both closed
// contours and contours with exactly one hole.
template <typename RealType>
class ChristiansenTiling: public boost::noncopyable
{
public:
    typedef ChristiansenTiling<RealType> this_type;

    typedef Vector<RealType, 3> Point3D;
    typedef boost::function<RealType (Point3D, Point3D)> Metric;
    typedef std::vector<Point3D> Contour;
    typedef boost::shared_ptr<Contour> ContourPtr;
    typedef detail::ContourDescriptor<Contour> ContourDescriptor;

    typedef detail::ContainerConstTraverser<Contour> ContourTraverser;
    typedef detail::TraverseRuleFactory<Contour> TraverseFactory;

public:
    ChristiansenTiling(std::size_t id1, ContourPtr contour1, bool closed1,
                  std::size_t id2, ContourPtr contour2, bool closed2)
        : contour_descr1_(id1, contour1, closed1), contour_descr2_(id2, contour2, closed2),
          metric_(&bo::distances::euclidean_distance<RealType, 3>)
    { }

    // Implementation for Christiansen algorithm for closed and opened contours.
    //
    // If a contour is closed, it is traversed cyclical starting and ending in the
    // same vertex.
    //
    // If a contour has exactly one hole (i.e. opened), it is traversed once
    // from the start to the end (from one hole edge to the other).
    detail::IndexedTStrip run()
    {
        // Check contours' length.
        if ((contour_descr1_.Size < 2) && (contour_descr2_.Size < 2))
            throw std::logic_error("Cannot run Christiansen triangulation for contours "
                                   "consisting of less than 2 vertices.");

        // If the first contour is closed but the second is opened, traverse direction
        // for the second contour may be determined incorrectly.
        if (contour_descr1_.IsClosed && !contour_descr2_.IsClosed)
            contour_descr1_.swap(contour_descr2_);

        // Create traversers for contours.
        ContourTraverser current1 = create_traverser1_();
        ContourTraverser candidate1 = current1 + 1;

        ContourTraverser current2 = create_traverser2_(current1);
        ContourTraverser candidate2 = current2 + 1;

        // Initialize output TStrip structure.
        detail::IndexedTStrip tstrip(contour_descr1_.ID, current1.index(),
                             contour_descr2_.ID, current2.index(),
                             contour_descr1_.Size + contour_descr2_.Size);

        // Iterate until both contours are exhausted.
        while (candidate1.is_valid() || candidate2.is_valid())
        {
            // Calculate span norms for candidate vertices.
            RealType span1_norm = span_norm(candidate1, current2);
            RealType span2_norm = span_norm(candidate2, current1);

            // Choose and add candidate vertex and corresponding face.
            if (span1_norm > span2_norm)
            {
                tstrip.add2(candidate2.index());
                current2 = candidate2;
                ++candidate2;
            }
            else
            {
                tstrip.add1(candidate1.index());
                current1 = candidate1;
                ++candidate1;
            }
        }

        return tstrip;
    }

private:
    // Creates an appropriate traverser for the first contour depending whether
    // it is closed or not.
    ContourTraverser create_traverser1_() const
    {
        ContourTraverser retvalue = contour_descr1_.IsClosed ?
            ContourTraverser(TraverseFactory::Create(contour_descr1_.PointsPtr, 0, true)) :
            ContourTraverser(TraverseFactory::Create(contour_descr1_.PointsPtr, true));
        return retvalue;
    }

    // Creates an appropriate traverser for the second contour depending whether
    // the contour is closed and how the first contour is oriented.
    ContourTraverser create_traverser2_(const ContourTraverser& traverser1) const
    {
        ContourTraverser retvalue;

        if (contour_descr2_.IsClosed)
        {
            // Find closest vertex on the second contour and determine direction.
            // No need of using kd-tree here, since the operation is done once.
            std::size_t c2min_idx = 0;
            RealType c2min_dist = std::numeric_limits<RealType>::max();

            for (std::size_t c2_idx = 0; c2_idx < contour_descr2_.Size; ++c2_idx)
            {
                RealType cur_dist = metric_(contour_descr2_.PointsPtr->at(c2_idx), *traverser1);
                if (cur_dist < c2min_dist)
                {
                    c2min_idx = c2_idx;
                    c2min_dist = cur_dist;
                }
            }

            // Create a default directed traverser. This is need to calculate the
            // direction if c2min_idx is the last element in the collection.
            retvalue = ContourTraverser(TraverseFactory::Create(contour_descr2_.PointsPtr,
                                                                c2min_idx, true));

            // Contour2 traverse direction should be swapped in order to correspond
            // with the contour1 direction.
            Point3D direction1 = *(traverser1 + 1) - *traverser1;
            Point3D direction2 = *(retvalue + 1) - *retvalue;
            if (direction1 * direction2 < 0)
                retvalue = ContourTraverser(TraverseFactory::Create(
                        contour_descr2_.PointsPtr, c2min_idx, false));
        }
        else
        {
            // Check if contour2_ traverse direction should be swapped in order to
            // correspond with the contour1_'s direction.
            RealType dist_to_first = metric_(contour_descr2_.PointsPtr->front(), *traverser1);
            RealType dist_to_last = metric_(contour_descr2_.PointsPtr->back(), *traverser1);
            bool is_forward2 = (dist_to_first < dist_to_last) ? true : false;

            retvalue = ContourTraverser(TraverseFactory::Create(contour_descr2_.PointsPtr,
                                                                is_forward2));
        }

        return retvalue;
    }

    // Calculates the euclidean norm of the span between candidate vertex on one
    // contour and current vertex on another. If candidate vertex is invalid (this
    // indicates that the corresponding contour has been exhausted), returns infinity.
    // This guarantees that vertices will be sampled solely from the other contour.
    RealType span_norm(const ContourTraverser& candidate,
                       const ContourTraverser& other_current) const
    {
        RealType norm = candidate.is_valid() ?
                    metric_(*candidate, *other_current):
                    std::numeric_limits<RealType>::max();
        return norm;
    }

private:
    ContourDescriptor contour_descr1_;
    ContourDescriptor contour_descr2_;
    const Metric metric_;
};

} // namespace surfaces
} // namespace bo

#endif // CHRISTIANSEN_TILING_HPP_D80E68A2_DF31_11E2_BAA7_4057033B4CBB


/* pcmsolver_copyright_start */
/*
 *     PCMSolver, an API for the Polarizable Continuum Model
 *     Copyright (C) 2013-2016 Roberto Di Remigio, Luca Frediani and contributors
 *
 *     This file is part of PCMSolver.
 *
 *     PCMSolver is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     PCMSolver is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with PCMSolver.  If not, see <http://www.gnu.org/licenses/>.
 *
 *     For information on the complete list of contributors to the
 *     PCMSolver API, see: <http://pcmsolver.readthedocs.io/>
 */
/* pcmsolver_copyright_end */

#ifndef INTERFACESIMPL_HPP
#define INTERFACESIMPL_HPP

#include <cmath>
#include <fstream>
#include <vector>

#include "Config.hpp"

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

// Boost.Odeint includes
#include <boost/numeric/odeint.hpp>

#include "utils/MathUtils.hpp"

namespace interfaces {
/*! \typedef StateType
 *  \brief state vector for the differential equation integrator
 */
typedef std::vector<double> StateType;

/*! \typedef ProfileEvaluator
 *  \brief sort of a function pointer to the dielectric profile evaluation function
 */
typedef pcm::function< pcm::tuple<double, double>(const double) > ProfileEvaluator;

/*! \struct IntegratorParameters
 *  \brief holds parameters for the integrator
 */
struct IntegratorParameters
{
    /*! Absolute tolerance level */
    double eps_abs_     ;
    /*! Relative tolerance level */
    double eps_rel_     ;
    /*! Weight of the state      */
    double factor_x_    ;
    /*! Weight of the state derivative */
    double factor_dxdt_ ;
    /*! Lower bound of the integration interval */
    double r_0_         ;
    /*! Upper bound of the integration interval */
    double r_infinity_  ;
    /*! Time step between observer calls */
    double observer_step_;
    IntegratorParameters(double e_abs, double e_rel, double f_x, double f_dxdt, double r0, double rinf, double step)
        : eps_abs_(e_abs), eps_rel_(e_rel), factor_x_(f_x),
        factor_dxdt_(f_dxdt), r_0_(r0), r_infinity_(rinf), observer_step_(step) {}
};

/*! \class LnTransformedRadial
 *  \brief system of ln-transformed first-order radial differential equations
 *  \author Roberto Di Remigio
 *  \date 2015
 *
 *  Provides a handle to the system of differential equations for the integrator.
 *  The dielectric profile comes in as a boost::function object.
 */
class LnTransformedRadial
{
    private:
        /*! Dielectric profile function and derivative evaluation */
        ProfileEvaluator eval_;
        /*! Angular momentum */
        int l_;
    public:
        /*! Constructor from profile evaluator and angular momentum */
        LnTransformedRadial(const ProfileEvaluator & e, int lval) : eval_(e), l_(lval) {}
        /*! Provides a functor for the evaluation of the system
         *  of first-order ODEs needed by Boost.Odeint
         *  The second-order ODE and the system of first-order ODEs
         *  are reported in the manuscript.
         *  \param[in] rho state vector holding the function and its first derivative
         *  \param[out] drhodr state vector holding the first and second derivative
         *  \param[in] r position on the integration grid
         */
        void operator()(const StateType & rho, StateType & drhodr, const double r)
        {
            // Evaluate the dielectric profile
            double eps = 0.0, epsPrime = 0.0;
            pcm::tie(eps, epsPrime) = eval_(r);
            if (numericalZero(eps)) throw std::domain_error("Division by zero!");
            double gamma_epsilon = epsPrime / eps;
            // System of equations is defined here
            drhodr[0] = rho[1];
            drhodr[1] = -rho[1] * (rho[1] + 2.0/r + gamma_epsilon) + l_ * (l_ + 1) / std::pow(r, 2);
        }
};
} // namespace interfaces

using interfaces::ProfileEvaluator;
using interfaces::IntegratorParameters;

/*! \file InterfacesImpl.hpp
 *  \class RadialFunction
 *  \brief represents solutions to the radial 2nd order ODE
 *  \author Roberto Di Remigio
 *  \date 2015
 *  \tparam StateVariable type of the state variable used in the ODE solver
 *  \tparam ODESystem system of 1st order ODEs replacing the 2nd order ODE
 *  \tparam IndependentSolution encodes which type of radial solution
 */
template <typename StateVariable,
          typename ODESystem,
          template <typename, typename> class IndependentSolution>
class RadialFunction __final
{
    public:
        RadialFunction() : solution_(IndependentSolution<StateVariable, ODESystem>()) {}
        RadialFunction(int l, double r0, double rinf, const ProfileEvaluator & eval, const IntegratorParameters & parms)
            : solution_(IndependentSolution<StateVariable, ODESystem>(l, r0, rinf, eval, parms)) {}
        ~RadialFunction() {}
        /*! \brief Returns value of function and its first derivative at given point
         *  \param[in] point evaluation point
         */
        pcm::tuple<double, double> operator()(double point) const {
            return solution_(point);
        }
        friend std::ostream & operator<<(std::ostream &os, RadialFunction & obj) {
            os << obj.solution_;
            return os;
        }
    private:
        /// Independent solution to the radial equation
        IndependentSolution<StateVariable, ODESystem> solution_;
};

/*! \file InterfacesImpl.hpp
 *  \class Zeta
 *  \brief 1st solution to the radial second order ODE, with r^l behaviour in the origin
 *  \author Roberto Di Remigio
 *  \date 2015
 *  \tparam StateVariable type of the state variable used in the ODE solver
 *  \tparam ODESystem system of 1st order ODEs replacing the 2nd order ODE
 */
template <typename StateVariable,
          typename ODESystem>
class Zeta __final
{
    public:
        Zeta() : L_(0), r_0_(0.0), r_infinity_(0.0) {}
        Zeta(int l, double r0, double rinf, const ProfileEvaluator & eval, const IntegratorParameters & parms)
            : L_(l), r_0_(r0), r_infinity_(rinf) { compute(eval, parms); }
        ~Zeta() {}
        pcm::tuple<double, double> operator()(double point) const {
            return pcm::make_tuple(function_impl(point), derivative_impl(point));
        }
        friend std::ostream & operator<<(std::ostream &os, Zeta & obj) {
            for (size_t i = 0; i < obj.function_[0].size(); ++i) {
                os << obj.function_[0][i] << "    "
                   << obj.function_[1][i] << "    "
                   << obj.function_[2][i] << std::endl;
            }
            return os;
        }
    private:
        typedef pcm::array<StateVariable, 3> RadialSolution;
        /// Angular momentum of the solution
        int L_;
        /// Lower bound of the integration interval
        double r_0_;
        /// Upper bound of the integration interval
        double r_infinity_;
        /// The actual data: grid, function value and first derivative values
        RadialSolution function_;
        /*! Reports progress of differential equation integrator */
        void push_back(const StateVariable & x, double r) {
            function_[0].push_back(r);
            function_[1].push_back(x[0]);
            function_[2].push_back(x[1]);
        }
        /*! \brief Calculates 1st radial solution, i.e. the one with r^l behavior
         *  \param[in] eval   dielectric profile evaluator function object
         *  \param[in] parms parameters for the integrator
         */
        void compute(const ProfileEvaluator & eval, const IntegratorParameters & parms) {
            namespace odeint = boost::numeric::odeint;
            odeint::bulirsch_stoer_dense_out<StateVariable> stepper(parms.eps_abs_, parms.eps_rel_, parms.factor_x_, parms.factor_dxdt_);
            ODESystem system(eval, L_);
            // Holds the initial conditions
            StateVariable init_zeta(2);
            // Set initial conditions
            init_zeta[0] = L_ * std::log(r_0_);
            init_zeta[1] = L_ / r_0_;
            odeint::integrate_adaptive(stepper, system, init_zeta,
                    r_0_, r_infinity_, parms.observer_step_,
                    pcm::bind(&Zeta<StateVariable, ODESystem>::push_back, this, pcm::_1, pcm::_2));
        }
        /*! \brief Returns value of function at given point
         *  \param[in] point evaluation point
         *
         *  We first check if point is below r_0_, if yes we use
         *  the asymptotic form L*log(r) in point.
         */
        double function_impl(double point) const {
            double zeta = 0.0;
            if (point <= r_0_) {
                zeta = L_ * std::log(point);
            } else {
                zeta = splineInterpolation(point, function_[0], function_[1]);
            }
            return zeta;
        }
        /*! \brief Returns value of 1st derivative of function at given point
         *  \param[in] point evaluation point
         *
         *  We first check if point is below r_0_, if yes we use
         *  the asymptotic form L / r in point.
         */
        double derivative_impl(double point) const {
            double zeta = 0.0;
            if (point <= r_0_) {
                zeta = L_ / point;
            } else {
                zeta = splineInterpolation(point, function_[0], function_[2]);
            }
            return zeta;
        }
};

/*! \file InterfacesImpl.hpp
 *  \class Omega
 *  \brief 2nd solution to the radial second order ODE, with r^(-l-1) behaviour at infinity
 *  \author Roberto Di Remigio
 *  \date 2015
 *  \tparam StateVariable type of the state variable used in the ODE solver
 *  \tparam ODESystem system of 1st order ODEs replacing the 2nd order ODE
 */
template <typename StateVariable,
          typename ODESystem>
class Omega __final
{
    public:
        Omega() : L_(0), r_0_(0.0), r_infinity_(0.0) {}
        Omega(int l, double r0, double rinf, const ProfileEvaluator & eval, const IntegratorParameters & parms)
            : L_(l), r_0_(r0), r_infinity_(rinf) { compute(eval, parms); }
        ~Omega() {}
        pcm::tuple<double, double> operator()(double point) const {
            return pcm::make_tuple(function_impl(point), derivative_impl(point));
        }
        friend std::ostream & operator<<(std::ostream &os, Omega & obj) {
            for (size_t i = 0; i < obj.function_[0].size(); ++i) {
                os << obj.function_[0][i] << "    "
                   << obj.function_[1][i] << "    "
                   << obj.function_[2][i] << std::endl;
            }
            return os;
        }
    private:
        typedef pcm::array<StateVariable, 3> RadialSolution;
        /// Angular momentum of the solution
        int L_;
        /// Lower bound of the integration interval
        double r_0_;
        /// Upper bound of the integration interval
        double r_infinity_;
        /// The actual data: grid, function value and first derivative values
        RadialSolution function_;
        /*! Reports progress of differential equation integrator */
        void push_back(const StateVariable & x, double r) {
            function_[0].push_back(r);
            function_[1].push_back(x[0]);
            function_[2].push_back(x[1]);
        }
        /*! \brief calculates 2nd radial solution, i.e. the one with r^(-l-1) behavior
         *  \param[in] eval   dielectric profile evaluator function object
         *  \param[in] parms parameters for the integrator
         */
        void compute(const ProfileEvaluator & eval, const IntegratorParameters & parms) {
            namespace odeint = boost::numeric::odeint;
            odeint::bulirsch_stoer_dense_out<StateVariable> stepper(parms.eps_abs_, parms.eps_rel_, parms.factor_x_, parms.factor_dxdt_);
            ODESystem system(eval, L_);
            // Holds the initial conditions
            StateVariable init_omega(2);
            // Set initial conditions
            init_omega[0] = -(L_ + 1) * std::log(r_infinity_);
            init_omega[1] = -(L_ + 1) / r_infinity_;
            // Notice that we integrate BACKWARDS, so we pass -step to integrate_adaptive
            boost::numeric::odeint::integrate_adaptive(stepper, system, init_omega,
                    r_infinity_, r_0_, -parms.observer_step_,
                    pcm::bind(&Omega<StateVariable, ODESystem>::push_back, this, pcm::_1, pcm::_2));
            // Reverse order of StateVariable-s in RadialSolution
            // this ensures that they are in ascending order, as later expected by function_impl and derivative_impl
            BOOST_FOREACH(StateVariable & comp, function_) {
                std::reverse(comp.begin(), comp.end());
            }
        }
        /*! \brief Returns value of function at given point
         *  \param[in] point evaluation point
         *
         * We first check if point is above r_infinity_, if yes we use
         * the asymptotic form -(L+1)*log(r) in point.
         */
        double function_impl(double point) const {
            double omega = 0.0;
            if (point >= r_infinity_) {
                omega = -(L_ + 1) * std::log(point);
            } else {
                omega = splineInterpolation(point, function_[0], function_[1]);
            }
            return omega;
        }
        /*! \brief Returns value of 1st derivative of function at given point
         *  \param[in] point evaluation point
         *
         * We first check if point is above r_infinity_, if yes we use
         * the asymptotic form -(L+1)/r in point.
         */
        double derivative_impl(double point) const {
            double omega = 0.0;
            if (point >= r_infinity_) {
                omega = -(L_ + 1) / point;
            } else {
                omega = splineInterpolation(point, function_[0], function_[2]);
            }
            return omega;
        }
};

/*! \brief Write contents of a RadialFunction to file
 *  \param[in] f RadialSolution whose contents have to be printed
 *  \param[in] fname name of the file
 *  \author Roberto Di Remigio
 *  \date 2015
 *  \tparam StateVariable type of the state variable used in the ODE solver
 *  \tparam ODESystem system of 1st order ODEs replacing the 2nd order ODE
 *  \tparam IndependentSolution encodes which type of radial solution
 */
template <typename StateVariable,
          typename ODESystem,
          template <typename, typename> class IndependentSolution>
void writeToFile(RadialFunction<StateVariable, ODESystem, IndependentSolution> & f, const std::string & fname) {
    std::ofstream fout;
    fout.open(fname.c_str());
    fout << f << std::endl;
    fout.close();
}

#endif // INTERFACESIMPL_HPP


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

// Copyright (c) 2008   Gerald I. Evenden

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

/* The code in this file is largly based upon procedures:
 *
 * Written by: Knud Poder and Karsten Engsager
 *
 * Based on math from: R.Koenig and K.H. Weise, "Mathematische
 * Grundlagen der hoeheren Geodaesie und Kartographie,
 * Springer-Verlag, Berlin/Goettingen" Heidelberg, 1951.
 *
 * Modified and used here by permission of Reference Networks
 * Division, Kort og Matrikelstyrelsen (KMS), Copenhagen, Denmark
*/

#ifndef BOOST_GEOMETRY_PROJECTIONS_ETMERC_HPP
#define BOOST_GEOMETRY_PROJECTIONS_ETMERC_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/function_overloads.hpp>
#include <boost/geometry/srs/projections/impl/pj_param.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>

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

namespace boost { namespace geometry
{

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace etmerc
    {

            static const int PROJ_ETMERC_ORDER = 6;

            template <typename T>
            struct par_etmerc
            {
                T    Qn;    /* Merid. quad., scaled to the projection */
                T    Zb;    /* Radius vector in polar coord. systems  */
                T    cgb[6]; /* Constants for Gauss -> Geo lat */
                T    cbg[6]; /* Constants for Geo lat -> Gauss */
                T    utg[6]; /* Constants for transv. merc. -> geo */
                T    gtu[6]; /* Constants for geo -> transv. merc. */
            };

            template <typename T>
            inline T log1py(T const& x) {              /* Compute log(1+x) accurately */
                volatile T
                  y = 1 + x,
                  z = y - 1;
                /* Here's the explanation for this magic: y = 1 + z, exactly, and z
                 * approx x, thus log(y)/z (which is nearly constant near z = 0) returns
                 * a good approximation to the true log(1 + x)/x.  The multiplication x *
                 * (log(y)/z) introduces little additional error. */
                return z == 0 ? x : x * log(y) / z;
            }

            template <typename T>
            inline T asinhy(T const& x) {              /* Compute asinh(x) accurately */
                T y = fabs(x);         /* Enforce odd parity */
                y = log1py(y * (1 + y/(boost::math::hypot(1.0, y) + 1)));
                return x < 0 ? -y : y;
            }

            template <typename T>
            inline T gatg(const T *p1, int len_p1, T const& B) {
                const T *p;
                T h = 0, h1, h2 = 0, cos_2B;

                cos_2B = 2*cos(2*B);
                for (p = p1 + len_p1, h1 = *--p; p - p1; h2 = h1, h1 = h)
                    h = -h2 + cos_2B*h1 + *--p;
                return (B + h*sin(2*B));
            }

            /* Complex Clenshaw summation */
            template <typename T>
            inline T clenS(const T *a, int size, T const& arg_r, T const& arg_i, T *R, T *I) {
                T      r, i, hr, hr1, hr2, hi, hi1, hi2;
                T      sin_arg_r, cos_arg_r, sinh_arg_i, cosh_arg_i;

                /* arguments */
                const T* p = a + size;
                sin_arg_r  = sin(arg_r);
                cos_arg_r  = cos(arg_r);
                sinh_arg_i = sinh(arg_i);
                cosh_arg_i = cosh(arg_i);
                r          =  2*cos_arg_r*cosh_arg_i;
                i          = -2*sin_arg_r*sinh_arg_i;
                /* summation loop */
                for (hi1 = hr1 = hi = 0, hr = *--p; a - p;) {
                    hr2 = hr1;
                    hi2 = hi1;
                    hr1 = hr;
                    hi1 = hi;
                    hr  = -hr2 + r*hr1 - i*hi1 + *--p;
                    hi  = -hi2 + i*hr1 + r*hi1;
                }
                r   = sin_arg_r*cosh_arg_i;
                i   = cos_arg_r*sinh_arg_i;
                *R  = r*hr - i*hi;
                *I  = r*hi + i*hr;
                return(*R);
            }

            /* Real Clenshaw summation */
            template <typename T>
            inline T clens(const T *a, int size, T const& arg_r) {
                T      r, hr, hr1, hr2, cos_arg_r;

                const T* p = a + size;
                cos_arg_r  = cos(arg_r);
                r          =  2*cos_arg_r;

                /* summation loop */
                for (hr1 = 0, hr = *--p; a - p;) {
                    hr2 = hr1;
                    hr1 = hr;
                    hr  = -hr2 + r*hr1 + *--p;
                }
                return(sin(arg_r)*hr);
            }

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

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

                // FORWARD(e_forward)  ellipsoid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T const& lp_lon, T const& lp_lat, T& xy_x, T& xy_y) const
                {
                    T sin_Cn, cos_Cn, cos_Ce, sin_Ce, dCn, dCe;
                    T Cn = lp_lat, Ce = lp_lon;

                    /* ell. LAT, LNG -> Gaussian LAT, LNG */
                    Cn  = gatg(this->m_proj_parm.cbg, PROJ_ETMERC_ORDER, Cn);
                    /* Gaussian LAT, LNG -> compl. sph. LAT */
                    sin_Cn = sin(Cn);
                    cos_Cn = cos(Cn);
                    sin_Ce = sin(Ce);
                    cos_Ce = cos(Ce);

                    Cn     = atan2(sin_Cn, cos_Ce*cos_Cn);
                    Ce     = atan2(sin_Ce*cos_Cn, boost::math::hypot(sin_Cn, cos_Cn*cos_Ce));

                    /* compl. sph. N, E -> ell. norm. N, E */
                    Ce  = asinhy(tan(Ce));     /* Replaces: Ce  = log(tan(fourth_pi + Ce*0.5)); */
                    Cn += clenS(this->m_proj_parm.gtu, PROJ_ETMERC_ORDER, 2*Cn, 2*Ce, &dCn, &dCe);
                    Ce += dCe;
                    if (fabs(Ce) <= 2.623395162778) {
                        xy_y  = this->m_proj_parm.Qn * Cn + this->m_proj_parm.Zb;  /* Northing */
                        xy_x  = this->m_proj_parm.Qn * Ce;  /* Easting  */
                    } else
                        xy_x = xy_y = HUGE_VAL;
                }

                // INVERSE(e_inverse)  ellipsoid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T const& xy_x, T const& xy_y, T& lp_lon, T& lp_lat) const
                {
                    T sin_Cn, cos_Cn, cos_Ce, sin_Ce, dCn, dCe;
                    T Cn = xy_y, Ce = xy_x;

                    /* normalize N, E */
                    Cn = (Cn - this->m_proj_parm.Zb)/this->m_proj_parm.Qn;
                    Ce = Ce/this->m_proj_parm.Qn;

                    if (fabs(Ce) <= 2.623395162778) { /* 150 degrees */
                        /* norm. N, E -> compl. sph. LAT, LNG */
                        Cn += clenS(this->m_proj_parm.utg, PROJ_ETMERC_ORDER, 2*Cn, 2*Ce, &dCn, &dCe);
                        Ce += dCe;
                        Ce = atan(sinh(Ce)); /* Replaces: Ce = 2*(atan(exp(Ce)) - fourth_pi); */
                        /* compl. sph. LAT -> Gaussian LAT, LNG */
                        sin_Cn = sin(Cn);
                        cos_Cn = cos(Cn);
                        sin_Ce = sin(Ce);
                        cos_Ce = cos(Ce);
                        Ce     = atan2(sin_Ce, cos_Ce*cos_Cn);
                        Cn     = atan2(sin_Cn*cos_Ce, boost::math::hypot(sin_Ce, cos_Ce*cos_Cn));
                        /* Gaussian LAT, LNG -> ell. LAT, LNG */
                        lp_lat = gatg(this->m_proj_parm.cgb,  PROJ_ETMERC_ORDER, Cn);
                        lp_lon = Ce;
                    }
                    else
                        lp_lat = lp_lon = HUGE_VAL;
                }

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

            };

            template <typename Parameters, typename T>
            inline void setup(Parameters& par, par_etmerc<T>& proj_parm)
            {
                T f, n, np, Z;

                if (par.es <= 0) {
                    BOOST_THROW_EXCEPTION( projection_exception(error_ellipsoid_use_required) );
                }

                f = par.es / (1 + sqrt(1 -  par.es)); /* Replaces: f = 1 - sqrt(1-par.es); */

                /* third flattening */
                np = n = f/(2 - f);

                /* COEF. OF TRIG SERIES GEO <-> GAUSS */
                /* cgb := Gaussian -> Geodetic, KW p190 - 191 (61) - (62) */
                /* cbg := Geodetic -> Gaussian, KW p186 - 187 (51) - (52) */
                /* PROJ_ETMERC_ORDER = 6th degree : Engsager and Poder: ICC2007 */

                proj_parm.cgb[0] = n*( 2 + n*(-2/3.0  + n*(-2      + n*(116/45.0 + n*(26/45.0 +
                            n*(-2854/675.0 ))))));
                proj_parm.cbg[0] = n*(-2 + n*( 2/3.0  + n*( 4/3.0  + n*(-82/45.0 + n*(32/45.0 +
                            n*( 4642/4725.0))))));
                np     *= n;
                proj_parm.cgb[1] = np*(7/3.0 + n*( -8/5.0  + n*(-227/45.0 + n*(2704/315.0 +
                            n*( 2323/945.0)))));
                proj_parm.cbg[1] = np*(5/3.0 + n*(-16/15.0 + n*( -13/9.0  + n*( 904/315.0 +
                            n*(-1522/945.0)))));
                np     *= n;
                /* n^5 coeff corrected from 1262/105 -> -1262/105 */
                proj_parm.cgb[2] = np*( 56/15.0  + n*(-136/35.0 + n*(-1262/105.0 +
                            n*( 73814/2835.0))));
                proj_parm.cbg[2] = np*(-26/15.0  + n*(  34/21.0 + n*(    8/5.0   +
                            n*(-12686/2835.0))));
                np     *= n;
                /* n^5 coeff corrected from 322/35 -> 332/35 */
                proj_parm.cgb[3] = np*(4279/630.0 + n*(-332/35.0 + n*(-399572/14175.0)));
                proj_parm.cbg[3] = np*(1237/630.0 + n*( -12/5.0  + n*( -24832/14175.0)));
                np     *= n;
                proj_parm.cgb[4] = np*(4174/315.0 + n*(-144838/6237.0 ));
                proj_parm.cbg[4] = np*(-734/315.0 + n*( 109598/31185.0));
                np     *= n;
                proj_parm.cgb[5] = np*(601676/22275.0 );
                proj_parm.cbg[5] = np*(444337/155925.0);

                /* Constants of the projections */
                /* Transverse Mercator (UTM, ITM, etc) */
                np = n*n;
                /* Norm. mer. quad, K&W p.50 (96), p.19 (38b), p.5 (2) */
                proj_parm.Qn = par.k0/(1 + n) * (1 + np*(1/4.0 + np*(1/64.0 + np/256.0)));
                /* coef of trig series */
                /* utg := ell. N, E -> sph. N, E,  KW p194 (65) */
                /* gtu := sph. N, E -> ell. N, E,  KW p196 (69) */
                proj_parm.utg[0] = n*(-0.5  + n*( 2/3.0 + n*(-37/96.0 + n*( 1/360.0 +
                            n*(  81/512.0 + n*(-96199/604800.0))))));
                proj_parm.gtu[0] = n*( 0.5  + n*(-2/3.0 + n*(  5/16.0 + n*(41/180.0 +
                            n*(-127/288.0 + n*(  7891/37800.0 ))))));
                proj_parm.utg[1] = np*(-1/48.0 + n*(-1/15.0 + n*(437/1440.0 + n*(-46/105.0 +
                            n*( 1118711/3870720.0)))));
                proj_parm.gtu[1] = np*(13/48.0 + n*(-3/5.0  + n*(557/1440.0 + n*(281/630.0 +
                            n*(-1983433/1935360.0)))));
                np      *= n;
                proj_parm.utg[2] = np*(-17/480.0 + n*(  37/840.0 + n*(  209/4480.0  +
                            n*( -5569/90720.0 ))));
                proj_parm.gtu[2] = np*( 61/240.0 + n*(-103/140.0 + n*(15061/26880.0 +
                            n*(167603/181440.0))));
                np      *= n;
                proj_parm.utg[3] = np*(-4397/161280.0 + n*(  11/504.0 + n*( 830251/7257600.0)));
                proj_parm.gtu[3] = np*(49561/161280.0 + n*(-179/168.0 + n*(6601661/7257600.0)));
                np     *= n;
                proj_parm.utg[4] = np*(-4583/161280.0 + n*(  108847/3991680.0));
                proj_parm.gtu[4] = np*(34729/80640.0  + n*(-3418889/1995840.0));
                np     *= n;
                proj_parm.utg[5] = np*(-20648693/638668800.0);
                proj_parm.gtu[5] = np*(212378941/319334400.0);

                /* Gaussian latitude value of the origin latitude */
                Z = gatg(proj_parm.cbg, PROJ_ETMERC_ORDER, par.phi0);

                /* Origin northing minus true northing at the origin latitude */
                /* i.e. true northing = N - proj_parm.Zb                         */
                proj_parm.Zb  = - proj_parm.Qn*(Z + clens(proj_parm.gtu, PROJ_ETMERC_ORDER, 2*Z));
            }

            // Extended Transverse Mercator
            template <typename Parameters, typename T>
            inline void setup_etmerc(Parameters& par, par_etmerc<T>& proj_parm)
            {
                setup(par, proj_parm);
            }

            // Universal Transverse Mercator (UTM)
            template <typename Params, typename Parameters, typename T>
            inline void setup_utm(Params const& params, Parameters& par, par_etmerc<T>& proj_parm)
            {
                static const T pi = detail::pi<T>();

                int zone;

                if (par.es == 0.0) {
                    BOOST_THROW_EXCEPTION( projection_exception(error_ellipsoid_use_required) );
                }

                par.y0 = pj_get_param_b<srs::spar::south>(params, "south", srs::dpar::south) ? 10000000. : 0.;
                par.x0 = 500000.;
                if (pj_param_i<srs::spar::zone>(params, "zone", srs::dpar::zone, zone)) /* zone input ? */
                {
                    if (zone > 0 && zone <= 60)
                        --zone;
                    else {
                        BOOST_THROW_EXCEPTION( projection_exception(error_invalid_utm_zone) );
                    }
                }
                else /* nearest central meridian input */
                {
                    zone = int_floor((adjlon(par.lam0) + pi) * 30. / pi);
                    if (zone < 0)
                        zone = 0;
                    else if (zone >= 60)
                        zone = 59;
                }
                par.lam0 = (zone + .5) * pi / 30. - pi;
                par.k0 = 0.9996;
                par.phi0 = 0.;

                setup(par, proj_parm);
            }

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

    /*!
        \brief Extended Transverse Mercator projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Cylindrical
         - Spheroid
        \par Projection parameters
         - lat_ts: Latitude of true scale
         - lat_0: Latitude of origin
        \par Example
        \image html ex_etmerc.gif
    */
    template <typename T, typename Parameters>
    struct etmerc_ellipsoid : public detail::etmerc::base_etmerc_ellipsoid<T, Parameters>
    {
        template <typename Params>
        inline etmerc_ellipsoid(Params const& , Parameters const& par)
            : detail::etmerc::base_etmerc_ellipsoid<T, Parameters>(par)
        {
            detail::etmerc::setup_etmerc(this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Universal Transverse Mercator (UTM) projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Cylindrical
         - Spheroid
        \par Projection parameters
         - zone: UTM Zone (integer)
         - south: Denotes southern hemisphere UTM zone (boolean)
        \par Example
        \image html ex_utm.gif
    */
    template <typename T, typename Parameters>
    struct utm_ellipsoid : public detail::etmerc::base_etmerc_ellipsoid<T, Parameters>
    {
        template <typename Params>
        inline utm_ellipsoid(Params const& params, Parameters const& par)
            : detail::etmerc::base_etmerc_ellipsoid<T, Parameters>(par)
        {
            detail::etmerc::setup_utm(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_etmerc, etmerc_ellipsoid, etmerc_ellipsoid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::spar::proj_utm, utm_ellipsoid, utm_ellipsoid)

        // Factory entry(s)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(etmerc_entry, etmerc_ellipsoid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(utm_entry, utm_ellipsoid)

        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_BEGIN(etmerc_init)
        {
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(etmerc, etmerc_entry);
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(utm, utm_entry);
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_ETMERC_HPP