/*
    SPDX-FileCopyrightText: 2014-2017 Max Planck Society.
    All rights reserved.

    SPDX-License-Identifier: BSD-3-Clause
*/

/**
 * @file
 * @date      2014-2017
 * @copyright Max Planck Society
 *
 * @author    Edgar D. Klenske <edgar.klenske@tuebingen.mpg.de>
 * @author    Stephan Wenninger <stephan.wenninger@tuebingen.mpg.de>
 * @author    Raffi Enficiaud <raffi.enficiaud@tuebingen.mpg.de>
 *
 * @brief     The file holds the covariance functions that can be used with the GP class.
 */

#include "covariance_functions.h"
#include "math_tools.h"

namespace covariance_functions
{

    /* PeriodicSquareExponential */
    PeriodicSquareExponential::PeriodicSquareExponential() :
    hyperParameters(Eigen::VectorXd::Zero(4)), extraParameters(Eigen::VectorXd::Ones(1)*std::numeric_limits<double>::max()) { }

    PeriodicSquareExponential::PeriodicSquareExponential(const Eigen::VectorXd& hyperParameters_) :
    hyperParameters(hyperParameters_), extraParameters(Eigen::VectorXd::Ones(1)*std::numeric_limits<double>::max()) { }

    Eigen::MatrixXd PeriodicSquareExponential::evaluate(const Eigen::VectorXd& x, const Eigen::VectorXd& y)
    {

        double lsSE0 = exp(hyperParameters(0));
        double svSE0 = exp(2 * hyperParameters(1));
        double lsP  = exp(hyperParameters(2));
        double svP  = exp(2 * hyperParameters(3));

        double plP  = exp(extraParameters(0));

        // Work with arrays internally, convert to matrix for return value.
        // This is because all the operations act elementwise, and Eigen::Arrays
        // do so, too.

        // Compute Distances
        Eigen::ArrayXXd squareDistanceXY = math_tools::squareDistance( x.transpose(), y.transpose());

        // fast version
        return svSE0 * ((-0.5 / std::pow(lsSE0, 2)) * squareDistanceXY).exp()
            + svP * (-2 * (((M_PI / plP) * squareDistanceXY.sqrt()).sin() / lsP).square()).exp();

        /* // verbose version
        // Square Exponential Kernel
        Eigen::ArrayXXd K0 = squareDistanceXY / std::pow(lsSE0, 2);
        K0 = svSE0 * (-0.5 * K0).exp();

        // Periodic Kernel
        Eigen::ArrayXXd K1 = (M_PI * squareDistanceXY.sqrt() / plP);
        K1 = K1.sin() / lsP;
        K1 = K1.square();
        K1 = svP * (-2 * K1).exp();

        // Combined Kernel
        return K0 + K1;
        */
    }

    void PeriodicSquareExponential::setParameters(const Eigen::VectorXd& params)
    {
        this->hyperParameters = params;
    }

    void PeriodicSquareExponential::setExtraParameters(const Eigen::VectorXd& params)
    {
        this->extraParameters = params;
    }

    const Eigen::VectorXd& PeriodicSquareExponential::getParameters() const
    {
        return this->hyperParameters;
    }

    const Eigen::VectorXd& PeriodicSquareExponential::getExtraParameters() const
    {
        return this->extraParameters;
    }

    int PeriodicSquareExponential::getParameterCount() const
    {
        return 4;
    }

    int PeriodicSquareExponential::getExtraParameterCount() const
    {
        return 1;
    }


    /* PeriodicSquareExponential2 */
    PeriodicSquareExponential2::PeriodicSquareExponential2() :
        hyperParameters(Eigen::VectorXd::Zero(6)), extraParameters(Eigen::VectorXd::Ones(1)*std::numeric_limits<double>::max()) { }

    PeriodicSquareExponential2::PeriodicSquareExponential2(const Eigen::VectorXd& hyperParameters_) :
        hyperParameters(hyperParameters_), extraParameters(Eigen::VectorXd::Ones(1)*std::numeric_limits<double>::max()) { }

    Eigen::MatrixXd PeriodicSquareExponential2::evaluate(const Eigen::VectorXd& x, const Eigen::VectorXd& y)
    {

        double lsSE0 = exp(hyperParameters(0));
        double svSE0 = exp(2 * hyperParameters(1));
        double lsP  = exp(hyperParameters(2));
        double svP  = exp(2 * hyperParameters(3));
        double lsSE1 = exp(hyperParameters(4));
        double svSE1 = exp(2 * hyperParameters(5));

        double plP  = exp(extraParameters(0));

        // Work with arrays internally, convert to matrix for return value.
        // This is because all the operations act elementwise, and Eigen::Arrays
        // do so, too.

        // Compute Distances
        Eigen::ArrayXXd squareDistanceXY = math_tools::squareDistance( x.transpose(), y.transpose());

        // fast version
        return svSE0 * ((-0.5 / std::pow(lsSE0, 2)) * squareDistanceXY).exp()
            + svP * (-2 * (((M_PI / plP) * squareDistanceXY.sqrt()).sin() / lsP).square()).exp()
            + svSE1 * ((-0.5 / std::pow(lsSE1, 2)) * squareDistanceXY).exp();

        /* // verbose version
        // Square Exponential Kernel
        Eigen::ArrayXXd K0 = squareDistanceXY / pow(lsSE0, 2);
        K0 = svSE0 * (-0.5 * K0).exp();

        // Periodic Kernel
        Eigen::ArrayXXd K1 = (M_PI * squareDistanceXY.sqrt() / plP);
        K1 = K1.sin() / lsP;
        K1 = K1.square();
        K1 = svP * (-2 * K1).exp();

        // Square Exponential Kernel
        Eigen::ArrayXXd K2 = squareDistanceXY / pow(lsSE1, 2);
        K2 = svSE1 * (-0.5 * K2).exp();

        // Combined Kernel
        return K0 + K1 + K2;
        */
    }

    void PeriodicSquareExponential2::setParameters(const Eigen::VectorXd& params)
    {
        this->hyperParameters = params;
    }

    void PeriodicSquareExponential2::setExtraParameters(const Eigen::VectorXd& params)
    {
        this->extraParameters = params;
    }

    const Eigen::VectorXd& PeriodicSquareExponential2::getParameters() const
    {
        return this->hyperParameters;
    }

    const Eigen::VectorXd& PeriodicSquareExponential2::getExtraParameters() const
    {
        return this->extraParameters;
    }

    int PeriodicSquareExponential2::getParameterCount() const
    {
        return 6;
    }

    int PeriodicSquareExponential2::getExtraParameterCount() const
    {
        return 1;
    }

}  // namespace covariance_functions
