/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2004 Ferdinando Ametrano
 Copyright (C) 2000, 2001, 2002, 2003 RiskMap srl

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 This program 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 license for more details.
*/

/*! \file actual365fixed.hpp
    \brief Actual/365 (Fixed) day counter
*/

#ifndef quantlib_actual365fixed_day_counter_h
#define quantlib_actual365fixed_day_counter_h

#include <ql/time/daycounter.hpp>

namespace QuantLib {

    //! Actual/365 (Fixed) day count convention
    /*! "Actual/365 (Fixed)" day count convention, also know as
        "Act/365 (Fixed)", "A/365 (Fixed)", or "A/365F".

        \warning According to ISDA, "Actual/365" (without "Fixed") is
                 an alias for "Actual/Actual (ISDA)" (see
                 ActualActual.)  If Actual/365 is not explicitly
                 specified as fixed in an instrument specification,
                 you might want to double-check its meaning.

        \ingroup daycounters
    */
    class Actual365Fixed : public DayCounter {
      public:
        enum Convention { Standard, Canadian, NoLeap };
        explicit Actual365Fixed(Convention c = Actual365Fixed::Standard)
        : DayCounter(implementation(c)) {}

      private:
        class Impl : public DayCounter::Impl {
          public:
            std::string name() const { return std::string("Actual/365 (Fixed)"); }
            Time yearFraction(const Date& d1,
                              const Date& d2,
                              const Date&,
                              const Date&) const {
                return daysBetween(d1,d2)/365.0;
            }
        };
        class CA_Impl : public DayCounter::Impl {
          public:
            std::string name() const {
                return std::string("Actual/365 (Fixed) Canadian Bond");
            }
            Time yearFraction(const Date& d1,
                              const Date& d2,
                              const Date& refPeriodStart,
                              const Date& refPeriodEnd) const;
        };
        class NL_Impl : public DayCounter::Impl {
          public:
            std::string name() const {
                return std::string("Actual/365 (No Leap)");
            }
            Date::serial_type dayCount(const Date& d1,
                                       const Date& d2) const;
            Time yearFraction(const Date& d1,
                              const Date& d2,
                              const Date& refPeriodStart,
                              const Date& refPeriodEnd) const;
        };
        static boost::shared_ptr<DayCounter::Impl> implementation(Convention);
    };

}

/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2000, 2001, 2002, 2003 RiskMap srl

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 This program 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 license for more details.
*/

#include <boost/make_shared.hpp>

namespace QuantLib {

    inline boost::shared_ptr<DayCounter::Impl>
    Actual365Fixed::implementation(Actual365Fixed::Convention c) {
        switch (c) {
          case Standard:
            return boost::shared_ptr<DayCounter::Impl>(new Impl);
          case Canadian:
            return boost::shared_ptr<DayCounter::Impl>(new CA_Impl);
          case NoLeap:
            return boost::shared_ptr<DayCounter::Impl>(new NL_Impl);
          default:
            QL_FAIL("unknown Actual/365 (Fixed) convention");
        }
    }

    inline Time Actual365Fixed::CA_Impl::yearFraction(const Date& d1,
                                               const Date& d2,
                                               const Date& refPeriodStart,
                                               const Date& refPeriodEnd) const {
        // Need the period to calculate frequency
        QL_REQUIRE(refPeriodStart != Date(),"invalid refPeriodStart");
        QL_REQUIRE(refPeriodEnd != Date(),"invalid refPeriodEnd");

        Time dcs = daysBetween(d1,d2);
        Time dcc = daysBetween(refPeriodStart,refPeriodEnd);
        Integer months = Integer(0.5+12*dcc/365);
        Integer frequency = Integer( 12/months);

        if ( dcs < 365/frequency)
            return dcs/365.0;

        return 1./frequency - (dcc-dcs)/365.0;

    }

    inline Date::serial_type Actual365Fixed::NL_Impl::dayCount(const Date& d1,
                                                        const Date& d2) const {

        static const Integer MonthOffset[] = {
            0,  31,  59,  90, 120, 151,  // Jan - Jun
            181, 212, 243, 273, 304, 334   // Jun - Dec
        };

        Date::serial_type s1 = d1.dayOfMonth()
                             + MonthOffset[d1.month()-1] + (d1.year() * 365);
        Date::serial_type s2 = d2.dayOfMonth()
                             + MonthOffset[d2.month()-1] + (d2.year() * 365);

        if (d1.month() == Feb && d1.dayOfMonth() == 29) {
            --s1;
        }

        if (d2.month() == Feb && d2.dayOfMonth() == 29) {
            --s2;
        }

        return s2 - s1;
    }

    inline Time Actual365Fixed::NL_Impl::yearFraction(const Date& d1,
                                               const Date& d2,
                                               const Date& d3,
                                               const Date& d4) const {
        return dayCount(d1, d2)/365.0;
    }

}

#endif


/*    Copyright (c) 2010-2015, Delft University of Technology
 *    All rights reserved.
 *
 *    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 the Delft University of Technology 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 HOLDER 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.
 *
 *    Changelog
 *      YYMMDD    Author            Comment
 *      120717    D. Dirkx          Creation of file.
 *      120921    M.I. Ganeff       Added the functions getTotalCountOfKernelsLoaded and
 *                                  clearSpiceKernels.
 *
 *    References
 *
 *    Notes
 *
 */

#include <boost/lexical_cast.hpp>

#include "Tudat/Astrodynamics/BasicAstrodynamics/unitConversions.h"
#include "Tudat/External/SpiceInterface/spiceInterface.h"

namespace tudat
{
namespace spice_interface
{

using basic_mathematics::Vector6d;

//! Convert a Julian date to ephemeris time (equivalent to TDB in Spice).
double convertJulianDateToEphemerisTime( const double julianDate )
{
    return ( julianDate - j2000_c( ) ) * spd_c( );
}

//! Convert ephemeris time (equivalent to TDB) to a Julian date.
double convertEphemerisTimeToJulianDate( const double ephemerisTime )
{
    return j2000_c( ) + ( ephemerisTime ) / spd_c( );
}

//! Converts a date string to ephemeris time.
double convertDateStringToEphemerisTime( const std::string& dateString )
{
    double ephemerisTime = 0.0;
    str2et_c( dateString.c_str( ), &ephemerisTime );
    return ephemerisTime;
}

//! Get Cartesian state of a body, as observed from another body.
Vector6d getBodyCartesianStateAtEpoch(
        const std::string& targetBodyName, const std::string& observerBodyName,
        const std::string& referenceFrameName, const std::string& abberationCorrections,
        const double ephemerisTime )
{

    // Declare variables for cartesian state and light-time to be determined by Spice.
    double stateAtEpoch[ 6 ];
    double lightTime;

    // Call Spice function to calculate state and light-time.
    spkezr_c( targetBodyName.c_str( ), ephemerisTime, referenceFrameName.c_str( ),
              abberationCorrections.c_str( ), observerBodyName.c_str( ), stateAtEpoch,
              &lightTime );

    // Put result in Eigen Vector.
    Vector6d cartesianStateVector;
    for ( unsigned int i = 0; i < 6 ; i++ )
    {
        cartesianStateVector( i ) = stateAtEpoch[ i ];
    }

    // Convert from km(/s) to m(/s).
    return unit_conversions::convertKilometersToMeters< Vector6d >(
                cartesianStateVector );
}

//! Get Cartesian position of a body, as observed from another body.
Eigen::Vector3d getBodyCartesianPositionAtEpoch( const std::string& targetBodyName,
                                                 const std::string& observerBodyName,
                                                 const std::string& referenceFrameName,
                                                 const std::string& abberationCorrections,
                                                 const double ephemerisTime )
{
    // Declare variables for cartesian position and light-time to be determined by Spice.
    double positionAtEpoch[ 3 ];
    double lightTime;

    // Call Spice function to calculate position and light-time.
    spkpos_c( targetBodyName.c_str( ), ephemerisTime, referenceFrameName.c_str( ),
              abberationCorrections.c_str( ), observerBodyName.c_str( ), positionAtEpoch,
              &lightTime );

    // Put result in Eigen Vector.
    Eigen::Vector3d cartesianPositionVector;
    for ( unsigned int i = 0; i < 3 ; i++ )
    {
        cartesianPositionVector( i ) = positionAtEpoch[ i ];
    }

    // Convert from km to m.
    return unit_conversions::convertKilometersToMeters< Eigen::Vector3d >(
                cartesianPositionVector );
}

//! Compute quaternion of rotation between two frames.
Eigen::Quaterniond computeRotationQuaternionBetweenFrames( const std::string& originalFrame,
                                                           const std::string& newFrame,
                                                           const double ephemerisTime )
{
    // Declare rotation matrix.
    double rotationArray[ 3 ][ 3 ];

    // Calculate rotation matrix.
    pxform_c( originalFrame.c_str( ), newFrame.c_str( ), ephemerisTime, rotationArray );

    // Put rotation matrix in Eigen Matrix3d.
    Eigen::Matrix3d rotationMatrix;
    for ( unsigned int i = 0; i < 3; i++ )
    {
        for ( unsigned int j = 0; j < 3; j++ )
        {
            rotationMatrix( i, j ) = rotationArray[ i ][ j ];
        }
    }

    // Convert matrix3d to Quaternion.
    return Eigen::Quaterniond( rotationMatrix );
}

//! Computes time derivative of rotation matrix between two frames.
Eigen::Matrix3d computeRotationMatrixDerivativeBetweenFrames( const std::string& originalFrame,
                                                              const std::string& newFrame,
                                                              const double ephemerisTime )
{
    double stateTransition[ 6 ][ 6 ];

    // Calculate state transition matrix.
    sxform_c( originalFrame.c_str( ), newFrame.c_str( ), ephemerisTime, stateTransition );

    // Put rotation matrix derivative in Eigen Matrix3d
    Eigen::Matrix3d matrixDerivative = Eigen::Matrix3d::Zero( );
    for( unsigned int i = 0; i < 3; i++ )
    {
        for( unsigned int j = 0; j < 3; j++ )
        {
            matrixDerivative( i, j ) = stateTransition[ i + 3 ][ j ];
        }
    }

    return matrixDerivative;
}

//! Computes the angular velocity of one frame w.r.t. to another frame.
Eigen::Vector3d getAngularVelocityVectorOfFrameInOriginalFrame( const std::string& originalFrame,
                                                                const std::string& newFrame,
                                                                const double ephemerisTime )
{
    double stateTransition[ 6 ][ 6 ];

    // Calculate state transition matrix.
    sxform_c( originalFrame.c_str( ), newFrame.c_str( ), ephemerisTime, stateTransition );

    double rotation[ 3 ][ 3 ];
    double angularVelocity[ 3 ];

    // Calculate angular velocity vector.
    xf2rav_c( stateTransition, rotation, angularVelocity );

    return ( Eigen::Vector3d( )<<angularVelocity[ 0 ], angularVelocity[ 1 ], angularVelocity[ 2 ] ).
            finished( );
}

std::pair< Eigen::Quaterniond, Eigen::Matrix3d > computeRotationQuaternionAndRotationMatrixDerivativeBetweenFrames(
        const std::string& originalFrame, const std::string& newFrame, const double ephemerisTime )
{
    double stateTransition[ 6 ][ 6 ];

    sxform_c( originalFrame.c_str( ), newFrame.c_str( ), ephemerisTime, stateTransition );

    Eigen::Matrix3d matrixDerivative;
    Eigen::Matrix3d rotationMatrix;

    for( unsigned int i = 0; i < 3; i++ )
    {
        for( unsigned int j = 0; j < 3; j++ )
        {
            rotationMatrix( i, j ) = stateTransition[ i ][ j ];
            matrixDerivative( i, j ) = stateTransition[ i + 3 ][ j ];
        }
    }
    return std::make_pair( Eigen::Quaterniond( rotationMatrix ), matrixDerivative );
}

//! Get property of a body from Spice.
std::vector< double > getBodyProperties( const std::string& body, const std::string& property,
                                         const int maximumNumberOfValues )
{
    // Delcare variable in which raw result is to be put by Spice function.
    double propertyArray[ maximumNumberOfValues ];

    // Call Spice function to retrieve property.
    SpiceInt numberOfReturnedParameters;
    bodvrd_c( body.c_str( ), property.c_str( ), maximumNumberOfValues, &numberOfReturnedParameters,
              propertyArray );

    // Put result in STL vector.
    std::vector< double > bodyProperties;
    bodyProperties.resize( numberOfReturnedParameters );
    for ( int i = 0; i < numberOfReturnedParameters; i++ )
    {
        bodyProperties.at( i ) = propertyArray[ i ];
    }
    return bodyProperties;
}

//! Get gravitational parameter of a body.
double getBodyGravitationalParameter( const std::string& body )
{
    // Delcare variable in which raw result is to be put by Spice function.
    double gravitationalParameter[ 1 ];

    // Call Spice function to retrieve gravitational parameter.
    SpiceInt numberOfReturnedParameters;
    bodvrd_c( body.c_str( ), "GM", 1, &numberOfReturnedParameters, gravitationalParameter );

    // Convert from km^3/s^2 to m^3/s^2
    return unit_conversions::convertKilometersToMeters< double >(
                unit_conversions::convertKilometersToMeters< double >(
                    unit_conversions::convertKilometersToMeters< double >(
                        gravitationalParameter[ 0 ] ) ) );
}

//! Get the (arithmetic) mean of the three principal axes of the tri-axial ellipsoid shape.
double getAverageRadius( const std::string& body )
{
    // Delcare variable in which raw result is to be put by Spice function.
    double radii[ 3 ];

    // Call Spice function to retrieve gravitational parameter.
    SpiceInt numberOfReturnedParameters;
    bodvrd_c( body.c_str( ), "RADII", 3, &numberOfReturnedParameters, radii );

    // Compute average and convert from km to m.
    return unit_conversions::convertKilometersToMeters< double >(
                radii[ 0 ] + radii[ 1 ] + radii[ 2 ] ) / 3.0;
}

//! Convert a body name to its NAIF identification number.
int convertBodyNameToNaifId( const std::string& bodyName )
{
    // Convert body name to NAIF ID number.
    SpiceInt bodyNaifId;
    SpiceBoolean isIdFound;
    bods2c_c( bodyName.c_str( ), &bodyNaifId, &isIdFound );

    // Convert SpiceInt (typedef for long) to int and return.
    return static_cast< int >( bodyNaifId );
}

//! Check if a certain property of a body is in the kernel pool.
bool checkBodyPropertyInKernelPool( const std::string& bodyName, const std::string& bodyProperty )
{
    // Convert body name to NAIF ID.
    const int naifId = convertBodyNameToNaifId( bodyName );

    // Determine if property is in pool.
    SpiceBoolean isPropertyInPool = bodfnd_c( naifId, bodyProperty.c_str( ) );
    return static_cast< bool >( isPropertyInPool );
}

//! Load a Spice kernel.
void loadSpiceKernelInTudat( const std::string& fileName )
{
    furnsh_c(  fileName.c_str( ) );
}

//! Get the amount of loaded Spice kernels.
int getTotalCountOfKernelsLoaded( )
{
    SpiceInt count;
    ktotal_c( "ALL", &count );
    return count;
}

//! Clear all Spice kernels.
void clearSpiceKernels( ) { kclear_c( ); }

} // namespace spice_interface
} // namespace tudat


// Copyright (c) 2021-2022, University of Colorado Denver. All rights reserved.
//
// This file is part of <T>LAPACK.
// <T>LAPACK is free software: you can redistribute it and/or modify it under
// the terms of the BSD 3-Clause license. See the accompanying LICENSE file.

#ifndef __TLAPACK_EIGEN_HH__
#define __TLAPACK_EIGEN_HH__

#include <Eigen/Core>
#include <type_traits>
#include "blas/arrayTraits.hpp"

namespace blas{

    // // -----------------------------------------------------------------------------
    // // is_EigenBlock

    // template< class >
    // struct is_EigenBlock : public std::false_type {};

    // template< typename T, int R, int C, bool P >
    // struct is_EigenBlock< Eigen::Block<T,R,C,P> > : public std::true_type {};

    // -----------------------------------------------------------------------------
    // Data traits for Eigen

    // Data type
    template<typename Scalar_, int Rows_, int Cols_, int Options_, int MaxRows_, int MaxCols_>
    struct type_trait< Eigen::Matrix<Scalar_, Rows_, Cols_, Options_, MaxRows_, MaxCols_> > {
        using type = Scalar_;
    };
    template<typename XprType, int BlockRows, int BlockCols, bool InnerPanel>
    struct type_trait< Eigen::Block<XprType, BlockRows, BlockCols, InnerPanel> > {
        using type = typename XprType::Scalar;
    };
    template<class T>
    struct type_trait< Eigen::VectorBlock<T> > {
        using type = typename T::Scalar;
    };
    template<class T, int idx>
    struct type_trait< Eigen::Diagonal<T,idx> > {
        using type = typename T::Scalar;
    };

    // Size type
    template<typename Scalar_, int Rows_, int Cols_, int Options_, int MaxRows_, int MaxCols_>
    struct sizet_trait< Eigen::Matrix<Scalar_, Rows_, Cols_, Options_, MaxRows_, MaxCols_> > {
        using type = Eigen::Index;
    };
    template<typename XprType, int BlockRows, int BlockCols, bool InnerPanel>
    struct sizet_trait< Eigen::Block<XprType, BlockRows, BlockCols, InnerPanel> > {
        using type = Eigen::Index;
    };
    template<class T>
    struct sizet_trait< Eigen::VectorBlock<T> > {
        using type = Eigen::Index;
    };
    template<class T, int idx>
    struct sizet_trait< Eigen::Diagonal<T,idx> > {
        using type = Eigen::Index;
    };

    // -----------------------------------------------------------------------------
    // blas functions to access Eigen properties

    // Size
    template<class T>
    inline constexpr auto
    size( const Eigen::EigenBase<T>& x ) {
        return x.size();
    }
    // Number of rows
    template<class T>
    inline constexpr auto
    nrows( const Eigen::EigenBase<T>& x ) {
        return x.rows();
    }
    // Number of columns
    template<class T>
    inline constexpr auto
    ncols( const Eigen::EigenBase<T>& x ) {
        return x.cols();
    }
    // Read policy
    template<class T>
    inline constexpr auto
    read_policy( const Eigen::DenseBase<T>& x ) {
        return lapack::dense;
    }
    // Write policy
    template<class T>
    inline constexpr auto
    write_policy( const Eigen::DenseBase<T>& x ) {
        return lapack::dense;
    }

    // -----------------------------------------------------------------------------
    // blas functions to access Eigen block operations

    #define isSlice(SliceSpec) !std::is_convertible< SliceSpec, Eigen::Index >::value

    // Slice
    template<class T, typename SliceSpecRow, typename SliceSpecCol,
        typename std::enable_if< isSlice(SliceSpecRow) && isSlice(SliceSpecCol), int >::type = 0
    >
    inline constexpr auto slice(
        const Eigen::DenseBase<T>& A, SliceSpecRow&& rows, SliceSpecCol&& cols ) noexcept
    {
        return A.block( rows.first, cols.first,
                        rows.second-rows.first, cols.second-cols.first );
    }
    template<class T, typename SliceSpecRow, typename SliceSpecCol,
        typename std::enable_if< isSlice(SliceSpecRow) && isSlice(SliceSpecCol), int >::type = 0
    >
    inline constexpr auto slice(
        Eigen::DenseBase<T>& A, SliceSpecRow&& rows, SliceSpecCol&& cols ) noexcept
    {
        return A.block( rows.first, cols.first,
                        rows.second-rows.first, cols.second-cols.first );
    }
    template< typename XprType, int BlockRows, int BlockCols, bool InnerPanel,
              typename SliceSpecRow, typename SliceSpecCol,
        typename std::enable_if< isSlice(SliceSpecRow) && isSlice(SliceSpecCol), int >::type = 0
    >
    inline constexpr auto slice(
        Eigen::Block<XprType, BlockRows, BlockCols, InnerPanel>&& A,
        SliceSpecRow&& rows, SliceSpecCol&& cols ) noexcept
    {
        return A.block( rows.first, cols.first,
                        rows.second-rows.first, cols.second-cols.first );
    }

    #undef isSlice

    // Slice
    template<class T, typename SliceSpecCol>
    inline constexpr auto slice(
        const Eigen::DenseBase<T>& A, Eigen::Index rowIdx, SliceSpecCol&& cols ) noexcept
    {
        return A.row( rowIdx ).segment( cols.first, cols.second-cols.first );
    }
    template<class T, typename SliceSpecCol>
    inline constexpr auto slice(
        Eigen::DenseBase<T>& A, Eigen::Index rowIdx, SliceSpecCol&& cols ) noexcept
    {
        return A.row( rowIdx ).segment( cols.first, cols.second-cols.first );
    }
    template< typename XprType, int BlockRows, int BlockCols, bool InnerPanel,
              typename SliceSpecCol >
    inline constexpr auto slice(
        Eigen::Block<XprType, BlockRows, BlockCols, InnerPanel>&& A,
        Eigen::Index rowIdx, SliceSpecCol&& cols ) noexcept
    {
        return A.row( rowIdx ).segment( cols.first, cols.second-cols.first );
    }

    // Slice
    template<class T, typename SliceSpecRow>
    inline constexpr auto slice(
        const Eigen::DenseBase<T>& A, SliceSpecRow&& rows, Eigen::Index colIdx = 0 ) noexcept
    {
        return A.col( colIdx ).segment( rows.first, rows.second-rows.first );
    }
    template<class T, typename SliceSpecRow>
    inline constexpr auto slice(
        Eigen::DenseBase<T>& A, SliceSpecRow&& rows, Eigen::Index colIdx = 0 ) noexcept
    {
        return A.col( colIdx ).segment( rows.first, rows.second-rows.first );
    }
    template< typename XprType, int BlockRows, int BlockCols, bool InnerPanel,
              typename SliceSpecRow >
    inline constexpr auto slice(
        Eigen::Block<XprType, BlockRows, BlockCols, InnerPanel>&& A,
        SliceSpecRow&& rows, Eigen::Index colIdx = 0 ) noexcept
    {
        return A.col( colIdx ).segment( rows.first, rows.second-rows.first );
    }

    // Rows
    template<class T, typename SliceSpec>
    inline constexpr auto rows(
        const Eigen::DenseBase<T>& A, SliceSpec&& rows ) noexcept
    {
        return A.middleRows( rows.first, rows.second-rows.first );
    }
    template<class T, typename SliceSpec>
    inline constexpr auto rows(
        Eigen::DenseBase<T>& A, SliceSpec&& rows ) noexcept
    {
        return A.middleRows( rows.first, rows.second-rows.first );
    }
    template<typename XprType, int BlockRows, int BlockCols, bool InnerPanel,
             typename SliceSpec>
    inline constexpr auto rows(
        Eigen::Block<XprType, BlockRows, BlockCols, InnerPanel>&& A, SliceSpec&& rows ) noexcept
    {
        return A.middleRows( rows.first, rows.second-rows.first );
    }

    // Row
    template<class T>
    inline constexpr auto row( const Eigen::DenseBase<T>& A, Eigen::Index rowIdx ) noexcept
    {
        return A.row( rowIdx );
    }
    template<class T>
    inline constexpr auto row( Eigen::DenseBase<T>& A, Eigen::Index rowIdx ) noexcept
    {
        return A.row( rowIdx );
    }
    template<typename XprType, int BlockRows, int BlockCols, bool InnerPanel>
    inline constexpr auto row(
        Eigen::Block<XprType, BlockRows, BlockCols, InnerPanel>&& A, Eigen::Index rowIdx ) noexcept
    {
        return A.row( rowIdx );
    }

    // Cols
    template<class T, typename SliceSpec>
    inline constexpr auto cols(
        const Eigen::DenseBase<T>& A, SliceSpec&& cols ) noexcept
    {
        return A.middleCols( cols.first, cols.second-cols.first );
    }
    template<class T, typename SliceSpec>
    inline constexpr auto cols(
        Eigen::DenseBase<T>& A, SliceSpec&& cols ) noexcept
    {
        return A.middleCols( cols.first, cols.second-cols.first );
    }
    template<typename XprType, int BlockRows, int BlockCols, bool InnerPanel,
             typename SliceSpec>
    inline constexpr auto cols(
        Eigen::Block<XprType, BlockRows, BlockCols, InnerPanel>&& A, SliceSpec&& cols ) noexcept
    {
        return A.middleCols( cols.first, cols.second-cols.first );
    }

    // Column
    template<class T>
    inline constexpr auto col( const Eigen::DenseBase<T>& A, Eigen::Index colIdx ) noexcept
    {
        return A.col( colIdx );
    }
    template<class T>
    inline constexpr auto col( Eigen::DenseBase<T>& A, Eigen::Index colIdx ) noexcept
    {
        return A.col( colIdx );
    }
    template<typename XprType, int BlockRows, int BlockCols, bool InnerPanel>
    inline constexpr auto col( Eigen::Block<XprType, BlockRows, BlockCols, InnerPanel>&& A, Eigen::Index colIdx ) noexcept
    {
        return A.col( colIdx );
    }

    // Diagonal
    template<class T, class int_t>
    inline constexpr auto diag( const Eigen::MatrixBase<T>& A, int_t diagIdx = 0 ) noexcept
    {
        return A.diagonal( diagIdx );
    }
    template<class T, class int_t>
    inline constexpr auto diag( Eigen::MatrixBase<T>& A, int_t diagIdx = 0 ) noexcept
    {
        return A.diagonal( diagIdx );
    }
    template<typename XprType, int BlockRows, int BlockCols, bool InnerPanel, typename SliceSpec, class int_t>
    inline constexpr auto diag( Eigen::Block<XprType, BlockRows, BlockCols, InnerPanel>&& A, int_t diagIdx = 0 ) noexcept
    {
        return A.diagonal( diagIdx );
    }

} // namespace blas

namespace lapack {

    using blas::size;
    using blas::nrows;
    using blas::ncols;
    using blas::read_policy;
    using blas::write_policy;

    using blas::slice;
    using blas::rows;
    using blas::row;
    using blas::cols;
    using blas::col;
    using blas::diag;

} // namespace lapack

#endif // __TLAPACK_EIGEN_HH__


/* $Id: step-49.cc 28599 2013-02-27 04:25:41Z bangerth $
 *
 * Copyright (C) 2013 by the deal.II authors
 *
 * This file is subject to QPL and may not be  distributed
 * without copyright and license information. Please refer
 * to the file deal.II/doc/license.html for the  text  and
 * further information on this license.
 */

// This tutorial program is odd in the sense that, unlike for most other
// steps, the introduction already provides most of the information on how to
// use the various strategies to generate meshes. Consequently, there is
// little that remains to be commented on here, and we intersperse the code
// with relatively little text. In essence, the code here simply provides a
// reference implementation of what has already been described in the
// introduction.

// @sect3{Include files}

#include <deal.II/grid/tria.h>
#include <deal.II/grid/tria_accessor.h>
#include <deal.II/grid/tria_iterator.h>
#include <deal.II/grid/grid_generator.h>
#include <deal.II/grid/grid_tools.h>
#include <deal.II/grid/tria_boundary_lib.h>
#include <deal.II/grid/grid_out.h>
#include <deal.II/grid/grid_in.h>

#include <fstream>

#include <map>

using namespace dealii;

// @sect3{Generating output for a given mesh}

// The following function generates some output for any of the meshes we will
// be generating in the remainder of this program. In particular, it generates
// the following information:
//
// - Some general information about the number of space dimensions in which
//   this mesh lives and its number of cells.
// - The number of boundary faces that use each boundary indicator, so that
//   it can be compared with what we expect.
//
// Finally, the function outputs the mesh in encapsulated postscript (EPS)
// format that can easily be visualized in the same way as was done in step-1.
template<int dim>
void mesh_info(const Triangulation<dim> &tria,
	       const std::string        &filename)
{
  std::cout << "Mesh info:" << std::endl
            << " dimension: " << dim << std::endl
            << " no. of cells: " << tria.n_active_cells() << std::endl;

  // Next loop over all faces of all cells and find how often each boundary
  // indicator is used:
  {
    std::map<unsigned int, unsigned int> boundary_count;
    typename Triangulation<dim>::active_cell_iterator
    cell = tria.begin_active(),
    endc = tria.end();
    for (; cell!=endc; ++cell)
      {
        for (unsigned int face=0; face<GeometryInfo<dim>::faces_per_cell; ++face)
          {
            if (cell->face(face)->at_boundary())
              boundary_count[cell->face(face)->boundary_indicator()]++;
          }
      }

    std::cout << " boundary indicators: ";
    for (std::map<unsigned int, unsigned int>::iterator it=boundary_count.begin();
         it!=boundary_count.end();
         ++it)
      {
        std::cout << it->first << "(" << it->second << " times) ";
      }
    std::cout << std::endl;
  }

  // Finally, produce a graphical representation of the mesh to an output
  // file:
  std::ofstream out (filename.c_str());
  GridOut grid_out;
  grid_out.write_eps (tria, out);
  std::cout << " written to " << filename
	    << std::endl
	    << std::endl;
}

// @sect3{Main routines}

// @sect4{grid_1: Loading a mesh generated by gmsh}

// In this first example, we show how to load the mesh for which we have
// discussed in the introduction how to generate it. This follows the same
// pattern as used in step-5 to load a mesh, although there it was written in
// a different file format (UCD instead of MSH).
void grid_1 ()
{
  Triangulation<2> triangulation;

  GridIn<2> gridin;
  gridin.attach_triangulation(triangulation);
  std::ifstream f("untitled.msh");
  gridin.read_msh(f);

  mesh_info(triangulation, "grid-1.eps");

}

// @sect4{grid_2: Merging triangulations}

// Here, we first create two triangulations and then merge them into one.  As
// discussed in the introduction, it is important to ensure that the vertices
// at the common interface are located at the same coordinates.
void grid_2 ()
{
  Triangulation<2> tria1;
  GridGenerator::hyper_cube_with_cylindrical_hole (tria1, 0.25, 1.0);

  Triangulation<2> tria2;
  std::vector< unsigned int > repetitions(2);
  repetitions[0]=3;
  repetitions[1]=2;
  GridGenerator::subdivided_hyper_rectangle (tria2, repetitions,
                                             Point<2>(1.0,-1.0),
					     Point<2>(4.0,1.0));

  Triangulation<2> triangulation;
  GridGenerator::merge_triangulations (tria1, tria2, triangulation);

  mesh_info(triangulation, "grid-2.eps");
}

// @sect4{grid_3: Moving vertices}

// In this function, we move vertices of a mesh. This is simpler than one
// usually expects: if you ask a cell using cell-@>vertex(i) for
// the coordinates of its ith vertex, it doesn't just provide the
// location of this vertex but in fact a reference to the location where these
// coordinates are stored. We can then modify the value stored there.
//
// So this is what we do in the first part of this function: We create a
// square of geometry $[-1,1]^2$ with a circular hole with radius 0.25 located
// at the origin. We then loop over all cells and all vertices and if a vertex
// has a $y$ coordinate equal to one, we move it upward by 0.5.
//
// Note that this sort of procedure does not usually work this way because one
// will typically encounter the same vertices multiple times and may move them
// more than once. It works here because we select the vertices we want to use
// based on their geometric location, and a vertex moved once will fail this
// test in the future. A more general approach to this problem would have been
// to keep a std::set of of those vertex indices that we have already moved
// (which we can obtain using cell-@>vertex_index(i) and only
// move those vertices whose index isn't in the set yet.
void grid_3 ()
{
  Triangulation<2> triangulation;
  GridGenerator::hyper_cube_with_cylindrical_hole (triangulation, 0.25, 1.0);

  Triangulation<2>::active_cell_iterator
  cell = triangulation.begin_active(),
  endc = triangulation.end();
  for (; cell!=endc; ++cell)
    {
      for (unsigned int i=0; i<GeometryInfo<2>::vertices_per_cell; ++i)
        {
          Point<2> &v = cell->vertex(i);
          if (std::abs(v(1)-1.0)<1e-5)
            v(1) += 0.5;
        }
    }

  // In the second step we will refine the mesh twice. To do this correctly,
  // we have to associate a geometry object with the boundary of the hole;
  // since the boundary of the hole has boundary indicator 1 (see the
  // documentation of the function that generates the mesh), we need to create
  // an object that describes a circle (i.e., a hyper ball) with appropriate
  // center and radius and assign it to the triangulation. We can then refine
  // twice:
  const HyperBallBoundary<2> boundary_description(Point<2>(0,0), 0.25);
  triangulation.set_boundary (1, boundary_description);
  triangulation.refine_global(2);

  // The mesh so generated is then passed to the function that generates
  // output. In a final step we remove the boundary object again so that it is
  // no longer in use by the triangulation when it is destroyed (the boundary
  // object is destroyed first in this function since it was declared after
  // the triangulation).
  mesh_info (triangulation, "grid-3.eps");
  triangulation.set_boundary (1);
}

// There is one snag to doing things as shown above: If one moves the nodes on
// the boundary as shown here, one often ends up with cells in the interior
// that are badly distorted since the interior nodes were not moved around. This
// is not that much of a problem in the current case since the mesh did not
// contain any internal nodes when the nodes were moved -- it was the coarse
// mesh and it so happened that all vertices are at the boundary. It's also
// the case that the movement we had here was, compared to the average cell
// size not overly dramatic. Nevertheless, sometimes one does want to move
// vertices by a significant distance, and in that case one needs to move
// internal nodes as well. One way to do that automatically is to call the
// function GridTools::laplace_transform that takes a set of transformed
// vertex coordinates and moves all of the other vertices in such a way that the
// resulting mesh has, in some sense, a small distortion.

// @sect4{grid_4: Demonstrating extrude_triangulation}

// This example takes the initial grid from the previous function and simply extrudes it into the third space dimension:
void grid_4()
{
  Triangulation<2> triangulation;
  Triangulation<3> out;
  GridGenerator::hyper_cube_with_cylindrical_hole (triangulation, 0.25, 1.0);

  GridGenerator::extrude_triangulation (triangulation, 3, 2.0, out);
  mesh_info(out, "grid-4.eps");

}

// @sect4{grid_5: Demonstrating GridTools::transform, part 1}

// This and the next example first create a mesh and then transform it by
// moving every node of the mesh according to a function that takes a point
// and returns a mapped point. In this case, we transform $(x,y) \mapsto
// (x,y+\sin(\pi x/5))$.
//
// GridTools::transform takes a triangulation and any kind of object that can
// be called like a function as arguments. This function-like argument can be
// simply the address of a function as in the current case, or an object that
// has an operator() as in the next example, or for example a
// std::function@<Point@<2@>(const Point@<2@>)@> object one can get
// via std::bind in more complex cases.
Point<2> grid_5_transform (const Point<2> &in)
{
  return Point<2>(in(0),
		  in(1) + std::sin(in(0)/5.0*3.14159));
}

void grid_5()
{
  Triangulation<2> tria;
  std::vector<unsigned int> repetitions(2);
  repetitions[0] = 14;
  repetitions[1] = 2;
  GridGenerator::subdivided_hyper_rectangle (tria, repetitions,
                                             Point<2>(0.0,0.0),
					     Point<2>(10.0,1.0));

  GridTools::transform(&grid_5_transform, tria);
  mesh_info(tria, "grid-5.eps");
}

// @sect4{grid_6: Demonstrating GridTools::transform, part 2}

// In this second example of transforming points from an original to a new
// mesh, we will use the mapping $(x,y) \mapsto (x,\tanh(2y)/\tanh(2))$. To
// make things more interesting, rather than doing so in a single function as
// in the previous example, we here create an object with an
// operator() that will be called by GridTools::transform. Of
// course, this object may in reality be much more complex: the object may
// have member variables that play a role in computing the new locations of
// vertices.
struct Grid6Func
{
    double trans(const double y) const
      {
	return std::tanh(2*y)/tanh(2);
      }

    Point<2> operator() (const Point<2> & in) const
      {
	return Point<2> (in(0),
			 trans(in(1)));
      }
};

void grid_6()
{
  Triangulation<2> tria;
  std::vector< unsigned int > repetitions(2);
  repetitions[0] = repetitions[1] = 40;
  GridGenerator::subdivided_hyper_rectangle (tria, repetitions,
                                             Point<2>(0.0,0.0),
					     Point<2>(1.0,1.0));

  GridTools::transform(Grid6Func(), tria);
  mesh_info(tria, "grid-6.eps");
}

// @sect4{grid_7: Demonstrating distort_random}

// In this last example, we create a mesh and then distort its (interior)
// vertices by a random perturbation. This is not something you want to do for
// production computations, but it is a useful tool for testing
// discretizations and codes to make sure they don't work just by accident
// because the mesh happens to be uniformly structured and supporting
// super-convergence properties.
void grid_7()
{
  Triangulation<2> tria;
  std::vector<unsigned int> repetitions(2);
  repetitions[0] = repetitions[1] = 16;
  GridGenerator::subdivided_hyper_rectangle (tria, repetitions,
                                             Point<2>(0.0,0.0),
					     Point<2>(1.0,1.0));

  GridTools::distort_random (0.3, tria, true);
  mesh_info(tria, "grid-7.eps");
}

// @sect3{The main function}

// Finally, the main function. There isn't much to do here, only to call the
// subfunctions.
int main ()
{
  grid_1 ();
  grid_2 ();
  grid_3 ();
  grid_4 ();
  grid_5 ();
  grid_6 ();
  grid_7 ();
}


#ifndef FVMSCALAR1D_RECONSTRUCTION_HPP
#define FVMSCALAR1D_RECONSTRUCTION_HPP

#include <Eigen/Dense>
#include <cmath>
#include <memory>

#include <ancse/grid.hpp>
#include <ancse/model.hpp>
#include <ancse/rate_of_change.hpp>
#include <ancse/simulation_time.hpp>

inline double sign(double a) { return copysign(1.0, a); }

class PWConstantReconstruction {
  public:
    /// Compute the left and right trace at the interface i + 1/2.
    /** Note: This API is agnostic to the number of cell-averages required
     *        by the method. Therefore, reconstructions with different stencil
     *        sizes can implement this API; and this call can be used in parts
     *        of the code that do not need to know about the details of the
     *        reconstruction.
     */
    inline std::pair<double, double> operator()(const Eigen::VectorXd &u,
                                                int i) const {
        return (*this)(u[i], u[i + 1]);
    }

    /// Compute the left and right trace at the interface.
    /** Piecewise constant reconstruction of the left and right trace only
     *  requires the cell-average to the left and right of the interface.
     *
     *  Note: Compared to the other overload this reduces the assumption on
     *        how the cell-averages are stored. This is useful when testing and
     *        generally makes the function useful in more situations.
     */
    inline std::pair<double, double> operator()(double ua, double ub) const {
        return {ua, ub};
    }
};

#endif // FVMSCALAR1D_RATE_OF_CHANGE_HPP


#ifndef CCSR_HPP
#define CCSR_HPP

/*
The MIT License

Copyright (c) 2013 Denis Demidov <ddemidov@ksu.ru>

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

/**
 * \file   ccsr.hpp
 * \author Denis Demidov <ddemidov@ksu.ru>
 * \brief  Lossy compression for CSR sparse matrix format.
 *
 * Each row is stored as a pointer to a table of unique matrix rows, where
 * uniqueness is determined approximately.  This may result in significant
 * storage space savings in case matrix has regular structure (e.g. it
 * represents a Poisson problem in a domain with piecewise-constant or slowly
 * changing properties).  The precision loss is possible, but may not be
 * important (e.g.  coefficients come from an experiment with incorporated
 * observation error).
 */

#include <vector>
#include <deque>
#include <type_traits>
#include <memory>
#include <functional>
#include <numeric>
#include <cassert>

#include <boost/unordered_set.hpp>
#include <boost/iterator/transform_iterator.hpp>
#include <boost/iterator/zip_iterator.hpp>

namespace ccsr {

namespace detail {

template <typename T>
struct hash_impl {
    static inline size_t get(T v) {
        return std::hash<T>()(v);
    }
};

template <>
struct hash_impl<double> {
    static inline size_t get(double v) {
        static const uint64_t mask = 0xffffffffff000000;
                                    // eeefffffffffffff;
        // Zero-out least significant bits and hash as uint64_t:
        return std::hash<uint64_t>()(*reinterpret_cast<const uint64_t*>(&v) & mask);
    }
};

template <>
struct hash_impl<float> {
    static inline size_t get(float v) {
        static const uint32_t mask = 0xfffffc00;
                                    // eeffffff;
        // Zero-out least significant bits and hash as uint64_t:
        return std::hash<uint32_t>()(*reinterpret_cast<const uint32_t*>(&v) & mask);
    }
};

}

template <class T>
inline size_t hash(const T &v) {
    return detail::hash_impl<T>::get(v);
}

/// Lossy compression for CSR sparse matrix format.
/**
 * Each row is stored as a pointer to a table of unique matrix rows, where
 * uniqueness is determined approximately.  This may result in significant
 * storage space savings in case matrix has regular structure (e.g. it
 * represents a Poisson problem in a domain with piecewise-constant or slowly
 * changing properties).  The precision loss is possible, but may not be
 * important (e.g.  coefficients come from an experiment with incorporated
 * observation error).
*/
template <
    typename val_t = double,
    typename row_t = size_t,
    typename col_t = ptrdiff_t,
    typename idx_t = row_t
    >
class matrix {
    static_assert(std::is_signed<col_t>::value, "Column type should be signed!");

    private:
        size_t nrows, ncols, nnz;
        val_t  eps;

        std::vector< idx_t > idx;
        std::vector< row_t > row;
        std::vector< col_t > col;
        std::vector< val_t > val;

        // Returns operand incremented by a given value.
        struct shift {
            typedef col_t result_type;

            col_t s;

            shift(col_t s) : s(s) {}

            col_t operator()(col_t v) const {
                return v + s;
            }
        };

        // Extracts and stores unique rows.
        struct builder_t {
            std::deque< idx_t > idx;
            std::deque< row_t > row;
            std::deque< col_t > col;
            std::deque< val_t > val;

            // Hashes and compares matrix rows.
            struct row_hasher {
                val_t eps;

                row_hasher(val_t eps) : eps(eps) {}

                template <class Range>
                size_t operator()(const Range &r) const {
                    using boost::get;

                    auto begin = get<0>(r);
                    auto end   = get<1>(r);

                    size_t h = hash(end - begin);

                    for(auto i = begin; i != end; ++i)
                        h ^= hash(get<0>(*i)) ^ hash(get<1>(*i));

                    return h;
                }

                template <class Range1, class Range2>
                bool operator()(const Range1 &r1, const Range2 &r2) const {
                    using boost::get;

                    auto i1 = get<0>(r1);
                    auto e1 = get<1>(r1);
                    auto i2 = get<0>(r2);
                    auto e2 = get<1>(r2);

                    if (e1 - i1 != e2 - i2) return false;

                    for(; i1 != e1; ++i1, ++i2) {
                        if (get<0>(*i1) != get<0>(*i2))
                            return false;

                        if (fabs(get<1>(*i1) - get<1>(*i2)) > eps)
                            return false;
                    }

                    return true;
                }
            } hasher;

            typedef boost::zip_iterator<
                        boost::tuple<
                            typename std::deque< col_t >::const_iterator,
                            typename std::deque< val_t >::const_iterator
                        >
                    > row_iterator;

            typedef boost::tuple< row_iterator, row_iterator, size_t > row_range;

            boost::unordered_set<row_range, row_hasher, row_hasher> index;

            builder_t(size_t nrows, val_t eps)
                : idx(nrows, 0), hasher(eps), index(1979, hasher, hasher)
            {
                // Artificial empty row:
                row.push_back(0);
                row.push_back(0);

                index.insert(
                        boost::make_tuple(
                            boost::make_zip_iterator(
                                boost::make_tuple(col.begin(), val.begin())
                                ),
                            boost::make_zip_iterator(
                                boost::make_tuple(col.end(), val.end())
                                ),
                            index.size()
                            )
                        );
            }

            void insert(col_t row_begin, col_t row_end,
                    const row_t *r, const col_t *c, const val_t *v)
            {
                for(col_t i = row_begin, j = 0; i < row_end; ++i, ++j) {
                    shift s(-i);

                    auto range = boost::make_tuple(
                            boost::make_zip_iterator(
                                boost::make_tuple(
                                    boost::make_transform_iterator(c + r[j], s),
                                    v + r[j])
                                ),
                            boost::make_zip_iterator(
                                boost::make_tuple(
                                    boost::make_transform_iterator(c + r[j+1], s),
                                    v + r[j+1])
                                )
                            );

                    auto pos = index.find(range, hasher, hasher);

                    if (pos == index.end()) {
                        idx[i] = index.size();

                        size_t start = val.size();

                        for(size_t k = r[j]; k < r[j+1]; ++k) {
                            col.push_back( c[k] - i );
                            val.push_back( v[k] );
                        }

                        index.insert(
                                boost::make_tuple(
                                    boost::make_zip_iterator(
                                        boost::make_tuple(
                                            col.begin() + start,
                                            val.begin() + start)
                                        ),
                                    boost::make_zip_iterator(
                                        boost::make_tuple(
                                            col.end(),
                                            val.end()
                                            )
                                        ),
                                    index.size()
                                    )
                                );

                        row.push_back(val.size());
                    } else {
                        idx[i] = boost::get<2>(*pos);
                    }
                }
            }
        };

        std::unique_ptr<builder_t> builder;

    public:
        typedef boost::zip_iterator<
                    boost::tuple<
                        boost::transform_iterator<
                            shift, typename std::vector<col_t>::const_iterator
                        >,
                        typename std::vector<val_t>::const_iterator
                    >
                > const_row_iterator;

        /// Constructor.
        matrix(size_t nrows, size_t ncols, val_t eps = 1e-5)
            : nrows(nrows), ncols(ncols), nnz(0), eps(eps),
              builder(new builder_t(nrows, eps))
        {
        }

        /// Store matrix slice.
        /**
         * May accept whole matrix, or just a slice of matrix rows.
         */
        void insert(col_t row_begin, col_t row_end,
                const row_t *r, const col_t *c, const val_t *v)
        {
            assert(builder);

            builder->insert(row_begin, row_end, r, c, v);
        }

        /// All rows have been processed; finalize the construction phase.
        void finish() {
            assert(builder);

            idx.assign(builder->idx.begin(), builder->idx.end());
            row.assign(builder->row.begin(), builder->row.end());
            col.assign(builder->col.begin(), builder->col.end());
            val.assign(builder->val.begin(), builder->val.end());

            builder.reset();

            nnz = std::accumulate(idx.begin(), idx.end(), static_cast<size_t>(0),
                    [&](size_t tot, size_t p) {
                        return tot + row[p + 1] - row[p];
                    });
        }

        /// Number of unique rows in the matrix.
        size_t unique_rows() const {
            return row.size() - 1;
        }

        /// Returns boost::zip_iterator to start of columns/values range for a given row.
        const_row_iterator begin(size_t i) const {
            assert(!builder && i < nrows);

            return boost::make_zip_iterator(
                    boost::make_tuple(
                        boost::make_transform_iterator(
                            col.begin() + row[idx[i]],
                            shift(i)
                            ),
                        val.begin() + row[idx[i]]
                        )
                    );
        }

        /// Returns boost::zip_iterator to end of columns/values range for a given row.
        const_row_iterator end(size_t i) const {
            assert(!builder && i < nrows);

            return boost::make_zip_iterator(
                    boost::make_tuple(
                        boost::make_transform_iterator(
                            col.begin() + row[idx[i] + 1],
                            shift(i)
                            ),
                        val.begin() + row[idx[i] + 1]
                        )
                    );
        }

        /// Number of rows.
        size_t rows() const {
            return nrows;
        }

        /// Number of cols.
        size_t cols() const {
            return ncols;
        }

        /// Number of nonzeros in the matrix.
        size_t non_zeros() const {
            assert(!builder);

            return nnz;
        }

        /// Compression ratio.
        double compression() const {
            assert(!builder);
            return 1.0 *
                (
                 sizeof(idx[0]) * idx.size() +
                 sizeof(row[0]) * row.size() +
                 sizeof(col[0]) * col.size() +
                 sizeof(val[0]) * val.size()
                ) /
                (
                    sizeof(row[0]) * (nrows + 1) +
                    sizeof(col[0]) * nnz        +
                    sizeof(val[0]) * nnz
                );
        }

        /// Matrix transpose.
        friend matrix transp(const matrix &A) {
            const col_t chunk_size = 4096;

            col_t lw = 0, rw = 0;
            for(size_t i = 0, n = A.unique_rows(); i < n; ++i) {
                for(row_t j = A.row[i]; j < A.row[i + 1]; ++j) {
                    lw = std::max(lw, -A.col[j]);
                    rw = std::max(rw,  A.col[j]);
                }
            }

            matrix T(A.ncols, A.nrows, A.eps);

            std::vector<row_t> row;
            std::vector<col_t> col;
            std::vector<val_t> val;

            for(col_t chunk = 0; chunk < A.nrows; chunk += chunk_size) {
                col_t row_start = std::max(chunk - rw,              static_cast<col_t>(0));
                col_t row_end   = std::min(chunk + chunk_size + lw, static_cast<col_t>(A.nrows));
                col_t chunk_end = std::min(chunk + chunk_size,      static_cast<col_t>(A.nrows));

                row.clear();
                col.clear();
                val.clear();

                row.resize(chunk_size + 1, 0);

                for(col_t i = row_start; i < row_end; ++i)
                    for(auto j = A.begin(i), e = A.end(i); j != e; ++j) {
                        col_t c = boost::get<0>(*j);
                        if (c >= chunk && c < chunk_end)
                            ++( row[c - chunk + 1] );
                    }

                std::partial_sum(row.begin(), row.end(), row.begin());

                col.resize(row.back());
                val.resize(row.back());

                for(size_t i = row_start; i < row_end; ++i) {
                    for(auto j = A.begin(i), e = A.end(i); j != e; ++j) {
                        col_t c = boost::get<0>(*j);
                        val_t v = boost::get<1>(*j);

                        if (c >= chunk && c < chunk_end) {
                            row_t head = row[c - chunk]++;

                            col[head] = i;
                            val[head] = v;
                        }
                    }
                }

                std::rotate(row.begin(), row.end() - 1, row.end());
                row[0] = 0;

                T.insert(chunk, chunk_end, row.data(), col.data(), val.data());
            }

            T.finish();

            return T;
        }

        /// Matrix-matrix product.
        friend matrix prod(const matrix &A, const matrix &B) {
            matrix C(A.nrows, B.ncols, std::max(A.eps, B.eps));

            std::vector<col_t> marker(B.ncols, -1);

            row_t row[2] = {0, 0};
            std::vector<col_t> col;
            std::vector<val_t> val;

            for(size_t ia = 0; ia < A.nrows; ++ia) {
                col.clear();
                val.clear();

                for(auto ja = A.begin(ia), ea = A.end(ia); ja != ea; ++ja) {
                    col_t ca = boost::get<0>(*ja);
                    val_t va = boost::get<1>(*ja);

                    for(auto jb = B.begin(ca), eb = B.end(ca); jb != eb; ++jb) {
                        col_t cb = boost::get<0>(*jb);
                        val_t vb = boost::get<1>(*jb);

                        if (marker[cb] < 0) {
                            marker[cb] = col.size();
                            col.push_back(cb);
                            val.push_back(va * vb);
                        } else {
                            val[marker[cb]] += va * vb;
                        }
                    }
                }

                for(auto c = col.begin(); c != col.end(); ++c)
                    marker[*c] = -1;

                row[1] = col.size();

                C.insert(ia, ia + 1, row, col.data(), val.data());
            }

            C.finish();
            return C;
        }

};

} // namespace ccsr

#endif


#pragma once

#include <stdexcept>
#include <algorithm>
#include <iostream>
#include <complex>
#include <cmath>
#include <vector>
#include <set>
#include <assert.h>
#include <memory>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <complex>
#include <hdf5.h>

#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/math/constants/constants.hpp>
#include <boost/math/special_functions/bessel.hpp>

//debug
//#include <chrono>
//
//#ifdef IRBASIS_USE_EIGEN3
//#include <Eigen/Core>
//#endif

namespace irbasis {
namespace mp = boost::multiprecision;
typedef mp::number<mp::cpp_dec_float<30> > mpf;

namespace internal {

// Simple implementation without meta programming...
template<typename T, int DIM>
class multi_array {

  template<typename T2, int DIM2>
  friend class multi_array;

public:
  multi_array() : owner_(true), p_data_(NULL), num_elements_(0) {
    for (int i = 0; i < DIM; ++i) {
      extents_[i] = 0;
    }
  }

  multi_array(int N1) : owner_(true), p_data_(new T[N1]), num_elements_(N1) {
    assert(DIM == 1);
    extents_[0] = N1;
  }

  multi_array(int N1, int N2) : owner_(true), p_data_(new T[N1 * N2]), num_elements_(N1 * N2) {
    assert(DIM == 2);
    extents_[0] = N1;
    extents_[1] = N2;
  }

  multi_array(int N1, int N2, int N3) : owner_(true), p_data_(new T[N1 * N2 * N3]), num_elements_(N1 * N2 * N3) {
    assert(DIM == 3);
    extents_[0] = N1;
    extents_[1] = N2;
    extents_[2] = N3;
  }

  multi_array(std::size_t *dims) {
    resize(dims);
  }

  multi_array(const multi_array<T, DIM> &other) : p_data_(NULL) {
    owner_ = true;
    resize(&other.extents_[0]);
    std::copy(other.origin(), other.origin() + other.num_elements(), origin());
  }

  ~multi_array() {
    if (this->owner_) {
      delete[] p_data_;
    }
  }

  multi_array<T, DIM> &operator=(const multi_array<T, DIM> &other) {
    if (!this->owner_) {
      throw std::logic_error("Error: assignment to a view is not supported.");
    }

    this->owner_ = other.owner_;
    for (int i = 0; i < DIM; ++i) {
      this->extents_[i] = other.extents_[i];
    }
    this->num_elements_ = other.num_elements_;

    // allocate memoery and copy data
    if (this->p_data_ != NULL) {
      delete[] this->p_data_;
      this->p_data_ = NULL;
    }
    this->p_data_ = new T[this->num_elements_];

    for (int i = 0; i < this->num_elements_; ++i) {
      *(this->p_data_ + i) = *(other.p_data_ + i);
    }

    return *this;
  }

  std::size_t extent(int i) const {
    assert(i >= 0);
    assert(i < DIM);
    return extents_[i];
  }

  const std::size_t* extents() const {
      return &(extents_[0]);
  }

  void resize(const std::size_t *const dims) {
    if (!owner_) {
      throw std::runtime_error("resize is not permitted for a view");
    }

    std::size_t tot_size = std::accumulate(dims, dims + DIM, 1, std::multiplies<std::size_t>());
    delete[] p_data_;
    p_data_ = new T[tot_size];
    num_elements_ = tot_size;
    for (int i = 0; i < DIM; ++i) {
      extents_[i] = dims[i];
    }
  }

  void fill(T value) {
    if (!owner_) {
      throw std::runtime_error("resize is not permitted for a view");
    }
    for (int i = 0; i < this->num_elements_; ++i) {
      *(this->p_data_ + i) = value;
    }
  }

  multi_array<T, DIM - 1> make_view(std::size_t most_left_index) const {
    multi_array<T, DIM - 1> view;
    view.owner_ = false;
    std::size_t new_size = 1;
    for (int i = 0; i < DIM - 1; ++i) {
      view.extents_[i] = this->extents_[i + 1];
      new_size *= view.extents_[i];
    }
    view.num_elements_ = new_size;
    view.p_data_ = p_data_ + most_left_index * new_size;

    return view;
  }

  multi_array<T, 2> make_matrix_view(std::size_t size1, std::size_t size2) const {
    multi_array<T, 2> view;
    view.owner_ = false;
    view.extents_[0] = size1;
    view.extents_[1] = size2;
    view.num_elements_ = num_elements_;
    view.p_data_ = p_data_;

    return view;
  }

  std::size_t num_elements() const {
    return num_elements_;
  }

  bool is_view() const {
    return !owner_;
  }

  T *origin() const {
    return p_data_;
  }

  inline T &operator()(int i) {
    assert(DIM == 1);
    int idx = i;
    assert(idx >= 0 && idx < num_elements());
    return *(p_data_ + idx);
  }

  inline const T &operator()(int i) const {
    assert(DIM == 1);
    int idx = i;
    assert(idx >= 0 && idx < num_elements());
    return *(p_data_ + idx);
  }

  inline T &operator()(int i, int j) {
    assert(DIM == 2);
    int idx = extents_[1] * i + j;
    assert(idx >= 0 && idx < num_elements());
    return *(p_data_ + idx);
  }

  inline const T &operator()(int i, int j) const {
    assert(DIM == 2);
    int idx = extents_[1] * i + j;
    assert(idx >= 0 && idx < num_elements());
    return *(p_data_ + idx);
  }

  inline T &operator()(int i, int j, int k) {
    assert(DIM == 3);
    int idx = (i * extents_[1] + j) * extents_[2] + k;
    assert(idx >= 0 && idx < num_elements());
    return *(p_data_ + idx);
  }

  inline const T &operator()(int i, int j, int k) const {
    assert(DIM == 3);
    int idx = (i * extents_[1] + j) * extents_[2] + k;
    assert(idx >= 0 && idx < num_elements());
    return *(p_data_ + idx);
  }

private:
  bool owner_;
  T *p_data_;
  std::size_t num_elements_;
  std::size_t extents_[DIM];
};

template<typename T1, typename T2, typename T3>
void multiply(const multi_array<T1, 2> &A, const multi_array<T2, 2> &B, multi_array<T3, 2> &AB) {
  std::size_t N1 = A.extent(0);
  std::size_t N2 = A.extent(1);
  std::size_t N3 = B.extent(1);

  assert(B.extent(0) == N2);

  if (AB.extent(0) != N1 || AB.extent(1) != N3) {
    std::size_t dims[2];
    dims[0] = N1;
    dims[1] = N3;
    AB.resize(dims);
  }

  AB.fill(0);
  for (int i = 0; i < N1; ++i) {
    for (int k = 0; k < N2; ++k) {
      for (int j = 0; j < N3; ++j) {
        AB(i, j) += A(i, k) * B(k, j);
      }
    }
  }

}

// https://www.physics.ohio-state.edu/~wilkins/computing/HDF/hdf5tutorial/examples/C/h5_rdwt.c
// https://support.hdfgroup.org/ftp/HDF5/current/src/unpacked/examples/h5_read.c
template<typename T> hid_t get_native_type();

template<>
inline
hid_t
get_native_type<double>() {
  return H5T_NATIVE_DOUBLE;
}

template<>
inline
hid_t
get_native_type<int>() {
  return H5T_NATIVE_INT;
}

typedef struct {
  double re;   /*real part*/
  double im;   /*imaginary part*/
} complex_t;

template<>
inline
hid_t
get_native_type<std::complex<double> >() {
  hid_t complex_id = H5Tcreate(H5T_COMPOUND, sizeof(complex_t));
  H5Tinsert(complex_id, "r", HOFFSET(complex_t, re), H5T_NATIVE_DOUBLE);
  H5Tinsert(complex_id, "i", HOFFSET(complex_t, im), H5T_NATIVE_DOUBLE);
  return complex_id;
}

// read a scalar
template<typename T>
T hdf5_read_scalar(hid_t &file, const std::string &name) {
  hid_t dataset = H5Dopen2(file, name.c_str(), H5P_DEFAULT);
  if (dataset < 0) {
    throw std::runtime_error("Failed to load dataset" + name);
  }
  T data;
  H5Dread(dataset, get_native_type<T>(), H5S_ALL, H5S_ALL, H5P_DEFAULT, &data);
  H5Dclose(dataset);
  return data;
}

// read array of double
template<int DIM>
void hdf5_read_double_array(hid_t &file, const std::string &name, std::vector<std::size_t> &extents,
                            std::vector<double> &data) {
  hid_t dataset = H5Dopen2(file, name.c_str(), H5P_DEFAULT);
  if (dataset < 0) {
    throw std::runtime_error("Failed to load dataset" + name);
  }
  hid_t space = H5Dget_space(dataset);
  std::vector<hsize_t> dims(DIM);
  int n_dims = H5Sget_simple_extent_dims(space, &dims[0], NULL);
  assert(n_dims == DIM);
  std::size_t tot_size = std::accumulate(dims.begin(), dims.end(), 1, std::multiplies<std::size_t>());
  data.resize(tot_size);
  extents.resize(DIM);
  for (int i = 0; i < DIM; ++i) {
    extents[i] = static_cast<std::size_t>(dims[i]);
  }
  H5Dread(dataset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, &data[0]);
  H5Dclose(dataset);
}

template<int DIM>
void hdf5_read_mpf_array(hid_t &file, const std::string &name, std::vector<std::size_t> &extents,
                            std::vector<mpf> &data) {
  std::vector<double> data_tmp;
  hdf5_read_double_array<DIM>(file, name, extents, data_tmp);
  data.resize(data_tmp.size());
  for (int i = 0; i< data.size(); ++i) {
    data[i] = data_tmp[i];
  }

  hdf5_read_double_array<DIM>(file, name+"_corr", extents, data_tmp);
  for (int i = 0; i< data.size(); ++i) {
    data[i] += data_tmp[i];
  }

}

// read a multi_array
template<typename T, int DIM>
multi_array<T, DIM> load_multi_array(hid_t &file, const std::string &name) {
  hid_t dataset = H5Dopen2(file, name.c_str(), H5P_DEFAULT);
  if (dataset < 0) {
    throw std::runtime_error("Faild to open a dataset.");
  }
  hid_t space = H5Dget_space(dataset);
  std::vector<hsize_t> dims(DIM);
  int n_dims = H5Sget_simple_extent_dims(space, &dims[0], NULL);
  assert(n_dims == DIM);
  std::size_t
      tot_size = static_cast<std::size_t>(std::accumulate(dims.begin(), dims.end(), 1, std::multiplies<std::size_t>()));
  std::vector<std::size_t> extents(DIM);
  for (int i = 0; i < DIM; ++i) {
    extents[i] = static_cast<std::size_t>(dims[i]);
  }
  multi_array<T, DIM> a;
  a.resize(&extents[0]);
  H5Dread(dataset, get_native_type<T>(), H5S_ALL, H5S_ALL, H5P_DEFAULT, a.origin());
  std::vector<T> data(tot_size);
  H5Dread(dataset, get_native_type<T>(), H5S_ALL, H5S_ALL, H5P_DEFAULT, &data[0]);
  H5Dclose(dataset);
  return a;
}

template<int DIM>
multi_array<mpf,DIM> load_mpf_multi_array(hid_t &file, const std::string &name) {
  multi_array<double,DIM> data = load_multi_array<double,DIM>(file, name);
  multi_array<double,DIM> data_corr = load_multi_array<double,DIM>(file, name + "_corr");

  multi_array<mpf,DIM> result;
  result.resize(data.extents());

  for (int i=0; i<result.num_elements(); ++i) {
      *(result.origin()+i) = *(data.origin()+i);
      *(result.origin()+i) += *(data_corr.origin()+i);
  }
  return result;
}

inline
std::size_t find_section(const multi_array<mpf, 1> &section_edges, double x) {
  std::size_t idx = std::upper_bound(
      section_edges.origin(),
      section_edges.origin() + section_edges.num_elements(),
      x) - section_edges.origin() - 1;

  return std::min(idx, section_edges.num_elements() - 2);
}

inline
void compute_legendre(double x, std::vector<double> &val) {
  for (int l = 0; l < val.size(); l++) {
    if (l == 0) {
      val[l] = 1;
    } else if (l == 1) {
      val[l] = x;
    } else {
      val[l] = ((2 * l - 1) * x * val[l - 1] - (l - 1) * val[l - 2]) / l;
    }
  }
}

inline
int even_odd_sign(const int l) {
  return (l % 2 == 0 ? 1 : -1);
}

inline
multi_array<std::complex<double>, 2>
compute_unl_tail(std::vector<double> &w_vec,
                 const std::string &statistics,
                 const multi_array<double, 2> &derive_x1,
                 const int n) {
  // n : target number to calculate
  int sign = statistics == "B" ? 1 : -1;
  std::size_t n_iw = w_vec.size();
  std::size_t Nl = derive_x1.extent(0);
  std::size_t num_deriv = derive_x1.extent(1);
  multi_array<std::complex<double>, 2> result(n_iw, Nl);
  result.fill(std::complex<double>(0.0, 0.0));
  if (n > 0)
    num_deriv -= n;
  multi_array<std::complex<double>, 2> coeffs_nm(n_iw, num_deriv);
  coeffs_nm.fill(std::complex<double>(0.0, 0.0));

  //coeffs_nm
  for (int i_iw = 0; i_iw < n_iw; i_iw++) {
    if (statistics == "B" && w_vec[i_iw] == 0) {
      continue;
    }
    std::complex<double> fact = std::complex<double>(0.0, 1.0 / w_vec[i_iw]);
    coeffs_nm(i_iw, 0) = fact;
    for (int m = 1; m < num_deriv; m++) {
      coeffs_nm(i_iw, m) = fact * coeffs_nm(i_iw, m - 1);
    }
  }

  //coeffs_lm ()
  multi_array<std::complex<double>, 2> coeffs_lm(Nl, num_deriv);
  coeffs_lm.fill(std::complex<double>(0.0, 0.0));
  for (int l = 0; l < Nl; l++) {
    for (int m = 0; m < num_deriv; m++) {
      coeffs_lm(l, m) = (1.0 - sign * even_odd_sign(l + m)) * derive_x1(l, m);
    }
  }

  for (int i = 0; i < n_iw; i++) {
    for (int k = 0; k < Nl; k++) {
      for (int j = 0; j < num_deriv; j++) {
        result(i, k) += coeffs_nm(i, j) * coeffs_lm(k, j);
      }
      result(i, k) *= -sign / sqrt(2.0);
    }
  }
  return result;
}

struct func {
  void load_from_h5(hid_t file, const std::string &prefix) {
    data = internal::load_multi_array<double, 3>(file, prefix + std::string("/data"));
    np = internal::hdf5_read_scalar<int>(file, prefix + std::string("/np"));
    ns = internal::hdf5_read_scalar<int>(file, prefix + std::string("/ns"));
    nl = data.extent(0);
    section_edges = internal::load_mpf_multi_array<1>(file, prefix + std::string("/section_edges"));

    std::size_t extents[3];
    extents[0] = ns;
    extents[1] = np;
    extents[2] = nl;
    data_for_vec.resize(&extents[0]);
    for (int l = 0; l < nl; ++l) {
      for (int s = 0; s < ns; ++s) {
        for (int p = 0; p < np; ++p) {
          data_for_vec(s, p, l) = data(l, s, p);
        }
      }
    }
  }
  multi_array<mpf, 1> section_edges;
  multi_array<double, 3> data; //(nl, ns, np)
  multi_array<double, 3> data_for_vec; //(ns, np, nl). Just a copy of data.
  int np;
  int ns;
  int nl;
};

struct ref {
  multi_array<double, 2> data;
  multi_array<double, 1> max;
};

}

class basis {
public:
  basis() {}

  basis(
      const std::string &file_name,
      const std::string &prefix = ""
  ) throw(std::runtime_error) {
    hid_t file = H5Fopen(file_name.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);

    if (file < 0) {
      throw std::runtime_error("Failed to open " + file_name + "!");
    }

    //read info
    Lambda_ = internal::hdf5_read_scalar<double>(file, prefix + std::string("/info/Lambda"));
    dim_ = internal::hdf5_read_scalar<int>(file, prefix + std::string("/info/dim"));
    statistics_ = internal::hdf5_read_scalar<int>(file, prefix + std::string("/info/statistics")) == 0 ? "B" : "F";

    //read sl
    sl_ = internal::load_multi_array<double, 1>(file, prefix + std::string("/sl"));

    //read ulx
    ulx_.load_from_h5(file, prefix + "/ulx");

    //read ref_ulx
    ref_ulx_.data = internal::load_multi_array<double, 2>(file, prefix + std::string("/ulx/ref/data"));
    ref_ulx_.max = internal::load_multi_array<double, 1>(file, prefix + std::string("/ulx/ref/max"));

    //read vly
    vly_.load_from_h5(file, prefix + "/vly");

    //read ref_vly
    ref_vly_.data = internal::load_multi_array<double, 2>(file, prefix + std::string("/vly/ref/data"));
    ref_vly_.max = internal::load_multi_array<double, 1>(file, prefix + std::string("/vly/ref/max"));

    H5Fclose(file);

    std::size_t np = ulx_.np;
    {
      std::vector<double> coeffs(np);
      for (int p=0; p<np; ++p) {
        //Conversion between \tilde{P}_l and P_l
        coeffs[p] = std::sqrt(p + 0.5);
      }

      std::size_t dims[2] = {np, np};
      deriv_mat_.resize(dims);
      deriv_mat_.fill(0.0);
      for (int l=0; l<np; ++l) {
        for (int m=l-1; m>=0; m-=2) {
          deriv_mat_(m, l) = (1/coeffs[m]) * (2*m + 1) * coeffs[l];
        }
      }
    }

    norm_coeff_.resize(np);
    for (int p=0; p<np; ++p) {
      norm_coeff_[p] = std::sqrt(p + 0.5);
    }

  }

  /**
    * Return number of basis functions
    * @return  number of basis functions
    */
  int dim() const { return dim_; }

  /**
    * Return Lambda
    * @return  Lambda
    */
  double Lambda() const { return Lambda_; }

  /**
    * Return statistics
    * @return  string representing statistics "F" or "B"
    */
  std::string statistics() const { return statistics_; }

  double sl(int l) const throw(std::runtime_error) {
    assert(l >= 0 && l < dim());
    return sl_(l);
  }

  double ulx(int l, double x) const throw(std::runtime_error) {
    using namespace internal;

    if (std::abs(x) > 1) {
        throw std::runtime_error("x must be in [-1,1]!");
    }

    if (x >= 0) {
      return eval(x, ulx_.data.make_view(l), ulx_.section_edges);
    } else {
      return eval(-x, ulx_.data.make_view(l), ulx_.section_edges) * even_odd_sign(l);
    }
  }

  std::vector<std::vector<double> > check_ulx() const {
    double ulx_max = ref_ulx_.max(2);
    std::vector<std::vector<double> > ref_data(ref_ulx_.data.extent(0));
    int count = 0;
    for (int i = 0; i < ref_ulx_.data.extent(0); i++) {
      if (ref_ulx_.data(i, 2) == 0) {
        ref_data[i].push_back(ref_ulx_.data(i, 0));
        ref_data[i].push_back(ref_ulx_.data(i, 1));
        ref_data[i].push_back(
            fabs(ulx(ref_ulx_.data(i, 0) - 1, ref_ulx_.data(i, 1)) - ref_ulx_.data(i, 3)) / ulx_max);
        count++;
      }
    }
    ref_data.resize(count);
    return ref_data;
  }

  std::vector<std::vector<double> > check_vly() const {
    double vly_max = ref_vly_.max(2);
    std::vector<std::vector<double> > ref_data(ref_vly_.data.extent(0));
    int count = 0;
    for (int i = 0; i < ref_vly_.data.extent(0); i++) {
      if (ref_vly_.data(i, 2) == 0) {
        ref_data[i].push_back(ref_vly_.data(i, 0));
        ref_data[i].push_back(ref_vly_.data(i, 1));
        ref_data[i].push_back(
            fabs(vly(ref_vly_.data(i, 0) - 1, ref_vly_.data(i, 1)) - ref_vly_.data(i, 3)) / vly_max);
        count++;
      }
    }
    ref_data.resize(count);
    return ref_data;
  }

  double d_ulx(int l, double x, std::size_t order) const throw(std::runtime_error) {
    using namespace internal;

    if (std::abs(x) > 1) {
        throw std::runtime_error("x must be in [-1,1]!");
    }

    if (x >= 0) {
      return eval_derivative(x, order, ulx_.data.make_view(l), ulx_.section_edges);
    } else {
      return eval_derivative(-x, order, ulx_.data.make_view(l), ulx_.section_edges) * even_odd_sign(l + order);
    }
  }

  double vly(int l, double y) const throw(std::runtime_error) {
    using namespace internal;

    if (std::abs(y) > 1) {
        throw std::runtime_error("y must be in [-1,1]!");
    }
    if (y >= 0) {
      return eval(y, vly_.data.make_view(l), vly_.section_edges);
    } else {
      return eval(-y, vly_.data.make_view(l), vly_.section_edges) * even_odd_sign(l);
    }
  }

  double d_vly(int l, double y, std::size_t order) const throw(std::runtime_error) {
    using namespace internal;

    if (std::abs(y) > 1) {
        throw std::runtime_error("y must be in [-1,1]!");
    }
    if (y >= 0) {
      return eval_derivative(y, order, vly_.data.make_view(l), vly_.section_edges);
    } else {
      return eval_derivative(-y, order, vly_.data.make_view(l), vly_.section_edges) * even_odd_sign(l + order);
    }
  }

  double get_ref_ulx(std::size_t order) const {
    double ref_data;
    for (int i = 0; i < ref_ulx_.data.extent(0); i++) {
      if (ref_ulx_.data(i, 2) == order) {
        ref_data = ref_ulx_.data(i, 3);
      }
    }
    return ref_data;
  }

  int num_sections_x() const {
    return ulx_.data.extent(1);
  }

  double section_edge_x(std::size_t index) const {
    assert(index >= 0 && index <= num_sections_x());
    return ulx_.section_edges(index).convert_to<double>();
  }

  int num_sections_y() const {
    return vly_.data.extent(1);
  }

  std::vector<std::vector<std::complex<double> > > compute_unl(long long n) const {
      std::vector<long long> n_vec;
      n_vec.push_back(n);
      return compute_unl(n_vec);
  }

  std::vector<std::vector<std::complex<double> > > compute_unl(const std::vector<long long> &n) const {
    using namespace internal;

    typedef std::complex<double> dcomplex;
    typedef std::complex<mpf> mcomplex; // may be illegal to instantiate std::complex with mpf?

    mpf mpi = boost::math::constants::pi<mpf>();

    int num_n = n.size();

    std::vector<mpf> o_vec(n.size());
    if (this->statistics_ == "F") {
      for (int i = 0; i < num_n; i++) {
        o_vec[i] = (mpf(2) * n[i] + 1);
      }
    } else {
      for (int i = 0; i < num_n; i++) {
        o_vec[i] = (mpf(2) * n[i]);
      }
    }

    //w_vec = 0.5 * pi * o_vec
    std::vector<mpf> w_vec(o_vec);
    std::transform(w_vec.begin(), w_vec.end(), w_vec.begin(), std::bind1st(std::multiplies<mpf>(), mpi/2));
    std::vector<double> w_vec_f(num_n);
    for (int i=0; i<num_n; ++i) {
      w_vec_f[i] = w_vec[i].convert_to<double>();
    }

    std::size_t num_deriv = this->ulx_.data.extent(2);

    //Compute tail
    std::vector<std::vector<int> > replaced_with_tail(num_n, std::vector<int>(this->dim_, 0));
    multi_array<double, 2> deriv_x1(this->dim_, num_deriv);
    deriv_x1.fill(0.0);
    std::vector<double> d_ulx_result;
    for (int l = 0; l < dim_; ++l) {
      for (int p = 0; p < num_deriv; ++p) {
        deriv_x1(l, p) = d_ulx(l, 1.0, p);
      }
    }

    multi_array<std::complex<double>, 2> unl_tail = compute_unl_tail(w_vec_f, statistics_, deriv_x1, -1);
    multi_array<std::complex<double>, 2> unl_tail_without_last_two = compute_unl_tail(w_vec_f, statistics_, deriv_x1, 2);

    for (int i = 0; i < num_n; i++) {
      if (statistics_ == "B" && n[i] == 0)
        continue;
      for (int l = 0; l < dim_; ++l) {
        if (std::abs((unl_tail(i, l) - unl_tail_without_last_two(i, l)) / unl_tail(i, l)) < 1e-10) {
          replaced_with_tail[i][l] = 1;
        }
      }
    }

    multi_array<std::complex<mpf>,2> tilde_unl = compute_tilde_unl_fast(w_vec);

    std::vector<std::vector<std::complex<double> > > result_vec(num_n, std::vector<std::complex<double> >(dim_, 0));
    int sign_shift = statistics_ == "F" ? 1 : 0;
    for (int l = 0; l < dim_; ++l) {
      if ((l + sign_shift)%2 == 1) {
        for (int i=0; i<num_n; ++i) {
          result_vec[i][l] = std::complex<double>(
              0, 2*tilde_unl(i, l).imag().convert_to<double>()
              );
        }
      } else {
        for (int i=0; i<num_n; ++i) {
          result_vec[i][l] = 2 * tilde_unl(i, l).real().convert_to<double>();
        }
      }
    }

    //Overwrite by tail
    for (int i = 0; i < num_n; i++) {
      for (int l = 0; l < dim_; l++) {
        if (replaced_with_tail[i][l] == 1) {
          result_vec[i][l] = unl_tail(i, l);
        }
      }
    }

    return result_vec;
  }

  double section_edge_y(std::size_t index) const {
    assert(index >= 0 && index <= num_sections_y());
    return vly_.section_edges(index).convert_to<double>();
  }

protected:
  double Lambda_;
  int dim_;
  std::string statistics_;
  internal::multi_array<double, 1> sl_;
  internal::func ulx_;
  internal::func vly_;
  internal::ref ref_ulx_;
  internal::ref ref_vly_;
  internal::multi_array<double, 2> deriv_mat_;
  std::vector<double> norm_coeff_;

private:
  // Evaluate the value of function at given x
  double eval(double x, const internal::multi_array<double, 2> &data, const internal::multi_array<mpf, 1> &section_edges) const {
    std::size_t section_idx = find_section(section_edges, x);
    return eval_impl(x, section_edges(section_idx), section_edges(section_idx+1), data.make_view(section_idx));
  };

  double eval_impl(double x, mpf x_s, mpf x_sp, const internal::multi_array<double, 1> &coeffs) const {
    mpf dx = x_sp - x_s;
    mpf tilde_x = (2*x - x_sp - x_s)/dx;

    std::vector<double> leg_vals(coeffs.extent(0));
    internal::compute_legendre(tilde_x.convert_to<double>(), leg_vals);
    double eval_result = 0.0;
    for (int p=0; p<leg_vals.size(); ++p) {
      eval_result += leg_vals[p] * norm_coeff_[p] * coeffs(p);
    }
    return eval_result * std::sqrt(2/dx.convert_to<double>());
  }

  inline
  internal::multi_array<double,1>
  differentiate_coeff(const internal::multi_array<double, 1> &coeffs, std::size_t order) const {
    const std::size_t np = coeffs.num_elements();

    internal::multi_array<double,2> tmp2(np, 1);
    internal::multi_array<double,2> tmp(np, 1);
    for (int i=0; i<np; ++i) {
      tmp2(i, 0) = coeffs(i);
    }
    for (int i=0; i<order; ++i) {
      internal::multiply(deriv_mat_, tmp2, tmp);
      std::swap(tmp2, tmp);
    }
    internal::multi_array<double,1> coeffs_deriv(np);
    for (int p=0; p<np; ++p) {
      coeffs_deriv(p) = tmp2(p, 0);
    }
    return coeffs_deriv;
  }

  double eval_derivative(double x,
                                std::size_t order,
                                const internal::multi_array<double, 2> &data,
                                const internal::multi_array<mpf, 1> &section_edges,
                                int section = -1) const {
    using namespace internal;
    std::size_t section_idx = section >= 0 ? section : find_section(section_edges, x);

    multi_array<double, 1> coeffs_deriv = differentiate_coeff(data.make_view(section_idx), order);
    double dx = static_cast<mpf>(section_edges(section_idx+1) - section_edges(section_idx)).convert_to<double>();
    return eval_impl(x, section_edges(section_idx), section_edges(section_idx+1), coeffs_deriv) * std::pow(2/dx, order);
  }

  internal::multi_array<std::complex<mpf>,2> compute_tilde_unl_fast(const std::vector<mpf>& w_vec) const {
    const int num_n = w_vec.size();
    const int np = ulx_.np;

    typedef std::complex<mpf> mcomplex;
    typedef std::complex<double> dcomplex;

    internal::multi_array<mcomplex,2> tilde_unl(num_n, dim_);
    tilde_unl.fill(mcomplex(0,0));

    internal::multi_array<double,2> tmp_lp(dim_, np);
    internal::multi_array<dcomplex,2> tmp_np(num_n, np);
    std::vector<dcomplex> exp_n(num_n);

    for (int s=0; s<num_sections_x(); ++s) {
      const mpf xs = ulx_.section_edges(s);
      const mpf xsp = ulx_.section_edges(s+1);
      const mpf dx = xsp - xs;
      const mpf xmid = (xsp + xs)/2;

      // tmp_lp: lp
      {
        // Normalization factor
        double coeff_tmp = std::sqrt(dx.convert_to<double>())/2;
        for (int l=0; l<dim_; ++l) {
          for (int p=0; p<np; ++p) {
            tmp_lp(l, p) = ulx_.data(l, s, p) * coeff_tmp * norm_coeff_[p];
          }
        }
      }

      // tmp_np: np
      for (int n=0; n<num_n; ++n) {
        mpf phase = w_vec[n] * (xmid+1);
        mpf c = boost::multiprecision::cos(phase);
        mpf s = boost::multiprecision::sin(phase);
        exp_n[n] = std::complex<double>(c.convert_to<double>(), s.convert_to<double>());
      }
      for (int n=0; n<num_n; ++n) {
        double w_tmp = static_cast<mpf>(dx * w_vec[n]/2).convert_to<double>();
        dcomplex phase_p(1, 0);
        for (int p = 0; p < np; ++p) {
          if (w_tmp >= 0) {
              tmp_np(n, p) = 2.0 * phase_p * boost::math::sph_bessel(p, w_tmp) * exp_n[n];
          } else {
              tmp_np(n, p) = std::conj(2.0 * phase_p * boost::math::sph_bessel(p, -w_tmp)) * exp_n[n];
          }
          phase_p *= dcomplex(0, 1);
        }
      }

      for (int n=0; n<num_n; ++n) {
        for (int l=0; l<dim_; ++l) {
          dcomplex tmp_nl(0.0);
          for (int p=0; p<np; ++p) {
             tmp_nl += tmp_np(n, p) * tmp_lp(l, p);
          }
          tilde_unl(n, l) += tmp_nl;
        }
      }

    }
    return tilde_unl;
  }
};

inline
basis load(const std::string &statistics, double Lambda, const std::string &file_name = "./irbasis.h5") {
  std::stringstream ss;
  ss << std::fixed;
  ss << std::setprecision(1);
  ss << Lambda;
  std::string prefix;
  if (statistics == "F") {
    prefix = "basis_f-mp-Lambda" + ss.str() + "_np8";
  } else if (statistics == "B") {
    prefix = "basis_b-mp-Lambda" + ss.str() + "_np8";
  } else {
    throw std::runtime_error("Unsupported statistics " + statistics);
  }

  return basis(file_name, prefix);
}
};