/**
    Automatic differentiation data type for C++, depends on the Eigen
    linear algebra library.

    Copyright (c) 2012 by Wenzel Jakob. Based on code by Jon Kaldor
    and Eitan Grinspun.

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#if !defined(__AUTODIFF_H)
#define __AUTODIFF_H
#define EIGEN_DONT_PARALLELIZE
#define EIGEN_NO_DEBUG

#include <Eigen/Core>
#include <Eigen/Geometry>
#include <cmath>
#include <stdexcept>

#if defined(__WINDOWS__)
  #define __tls_decl __declspec(thread)
#else
  #define __tls_decl __thread
#endif

/**
 * \brief Base class of all automatic differentiation types
 *
 * This class records the number of independent variables with respect
 * to which derivatives are computed.
 */
struct DiffScalarBase {
    // ======================================================================
    /// @{ \name Configuration
    // ======================================================================

    /**
     * \brief Set the independent variable count used by the automatic
     * differentiation layer
     *
     * This function must be called before doing any computations with
     * \ref DScalar1 or \ref DScalar2. The value will be recorded in
     * thread-local storage.
     */
    static inline void setVariableCount(size_t value) {
        m_variableCount = value;
    }

    /// Get the variable count used by the automatic differentiation layer
    static inline size_t getVariableCount() {
        return m_variableCount;
    }

    /// @}
    // ======================================================================

    static __tls_decl size_t m_variableCount;
};

#define DECLARE_DIFFSCALAR_BASE() \
    __tls_decl size_t DiffScalarBase::m_variableCount = 0

/**
 * \brief Automatic differentiation scalar with first-order derivatives
 *
 * This class provides an instrumented "scalar" value, which may be dependent on
 * a number of independent variables. The implementation keeps tracks of
 * first -order drivatives with respect to these variables using a set
 * of overloaded operations and implementations of special functions (sin,
 * tan, exp, ..).
 *
 * This is extremely useful for numerical zero-finding, particularly when
 * analytic derivatives from programs like Maple or Mathematica suffer from
 * excessively complicated expressions.
 *
 * The class relies on templates, which makes it possible to fix the
 * number of independent variables at compile-time so that instances can
 * be allocated on the stack. Otherwise, they will be placed on the heap.
 *
 * This is an extended C++ port of Jon Kaldor's implementation, which is
 * based on a C version by Eitan Grinspun at Caltech)
 *
 * \sa DScalar2
 * \author Wenzel Jakob
 */
template <typename _Scalar, typename _Gradient = Eigen::Matrix<_Scalar, Eigen::Dynamic, 1> >
    struct DScalar1 : public DiffScalarBase {
public:
    typedef _Scalar                                         Scalar;
    typedef _Gradient                                       Gradient;
    typedef Eigen::Matrix<DScalar1, 2, 1>                   DVector2;
    typedef Eigen::Matrix<DScalar1, 3, 1>                   DVector3;

    // ======================================================================
    /// @{ \name Constructors and accessors
    // ======================================================================

    /// Create a new constant automatic differentiation scalar
    explicit DScalar1(Scalar value = (Scalar) 0) : value(value) {
        size_t variableCount = getVariableCount();
        grad.resize(Eigen::NoChange_t(), variableCount);
        grad.setZero();
    }

    /// Construct a new scalar with the specified value and one first derivative set to 1
    DScalar1(size_t index, const Scalar &value)
     : value(value) {
        size_t variableCount = getVariableCount();
        grad.resize(Eigen::NoChange_t(), variableCount);
        grad.setZero();
        grad(index) = 1;
    }

    /// Construct a scalar associated with the given gradient
    DScalar1(Scalar value, const Gradient &grad)
     : value(value), grad(grad) { }

    /// Copy constructor
    DScalar1(const DScalar1 &s)
     : value(s.value), grad(s.grad) { }

    inline const Scalar &getValue() const { return value; }
    inline const Gradient &getGradient() const { return grad; }

    // ======================================================================
    /// @{ \name Addition
    // ======================================================================
    friend DScalar1 operator+(const DScalar1 &lhs, const DScalar1 &rhs) {
        return DScalar1(lhs.value+rhs.value, lhs.grad+rhs.grad);
    }

    friend DScalar1 operator+(const DScalar1 &lhs, const Scalar &rhs) {
        return DScalar1(lhs.value+rhs, lhs.grad);
    }

    friend DScalar1 operator+(const Scalar &lhs, const DScalar1 &rhs) {
        return DScalar1(rhs.value+lhs, rhs.grad);
    }

    inline DScalar1& operator+=(const DScalar1 &s) {
        value += s.value;
        grad += s.grad;
        return *this;
    }

    inline DScalar1& operator+=(const Scalar &v) {
        value += v;
        return *this;
    }

    /// @}
    // ======================================================================

    // ======================================================================
    /// @{ \name Subtraction
    // ======================================================================

    friend DScalar1 operator-(const DScalar1 &lhs, const DScalar1 &rhs) {
        return DScalar1(lhs.value-rhs.value, lhs.grad-rhs.grad);
    }

    friend DScalar1 operator-(const DScalar1 &lhs, const Scalar &rhs) {
        return DScalar1(lhs.value-rhs, lhs.grad);
    }

    friend DScalar1 operator-(const Scalar &lhs, const DScalar1 &rhs) {
        return DScalar1(lhs-rhs.value, -rhs.grad);
    }

    friend DScalar1 operator-(const DScalar1 &s) {
        return DScalar1(-s.value, -s.grad);
    }

    inline DScalar1& operator-=(const DScalar1 &s) {
        value -= s.value;
        grad -= s.grad;
        return *this;
    }

    inline DScalar1& operator-=(const Scalar &v) {
        value -= v;
        return *this;
    }
    /// @}
    // ======================================================================

    // ======================================================================
    /// @{ \name Division
    // ======================================================================
    friend DScalar1 operator/(const DScalar1 &lhs, const Scalar &rhs) {
        if (rhs == 0)
            throw std::runtime_error("DScalar1: Division by zero!");
        Scalar inv = 1.0f / rhs;
        return DScalar1(lhs.value*inv, lhs.grad*inv);
    }

    friend DScalar1 operator/(const Scalar &lhs, const DScalar1 &rhs) {
        return lhs * inverse(rhs);
    }

    friend DScalar1 operator/(const DScalar1 &lhs, const DScalar1 &rhs) {
        return lhs * inverse(rhs);
    }

    friend DScalar1 inverse(const DScalar1 &s) {
        Scalar valueSqr = s.value*s.value,
            invValueSqr = (Scalar) 1 / valueSqr;

        // vn = 1/v, Dvn = -1/(v^2) Dv
        return DScalar1((Scalar) 1 / s.value, s.grad * -invValueSqr);
    }

    inline DScalar1& operator/=(const Scalar &v) {
        value /= v;
        grad /= v;
        return *this;
    }

    /// @}
    // ======================================================================

    // ======================================================================
    /// @{ \name Multiplication
    // ======================================================================
    inline friend DScalar1 operator*(const DScalar1 &lhs, const Scalar &rhs) {
        return DScalar1(lhs.value*rhs, lhs.grad*rhs);
    }

    inline friend DScalar1 operator*(const Scalar &lhs, const DScalar1 &rhs) {
        return DScalar1(rhs.value*lhs, rhs.grad*lhs);
    }

    inline friend DScalar1 operator*(const DScalar1 &lhs, const DScalar1 &rhs) {
        // Product rule
        return DScalar1(lhs.value*rhs.value,
            rhs.grad * lhs.value + lhs.grad * rhs.value);
    }

    inline DScalar1& operator*=(const Scalar &v) {
        value *= v;
        grad *= v;
        return *this;
    }

    inline DScalar1& operator*=(const DScalar1 &v) {
        grad = v.grad * value + grad * v.value;
        value *= v.value;
        return *this;
    }

    /// @}
    // ======================================================================

    // ======================================================================
    /// @{ \name Miscellaneous functions
    // ======================================================================

    friend DScalar1 sqrt(const DScalar1 &s) {
        Scalar sqrtVal = std::sqrt(s.value),
               temp    = (Scalar) 1 / ((Scalar) 2 * sqrtVal);

        // vn = sqrt(v)
        // Dvn = 1/(2 sqrt(v)) Dv
        return DScalar1(sqrtVal, s.grad * temp);
    }

    friend DScalar1 pow(const DScalar1 &s, const Scalar &a) {
        Scalar powVal = std::pow(s.value, a),
               temp   = a * std::pow(s.value, a-1);
        // vn = v ^ a, Dvn = a*v^(a-1) * Dv
        return DScalar1(powVal, s.grad * temp);
    }

    friend DScalar1 exp(const DScalar1 &s) {
        Scalar expVal = std::exp(s.value);

        // vn = exp(v), Dvn = exp(v) * Dv
        return DScalar1(expVal, s.grad * expVal);
    }

    friend DScalar1 log(const DScalar1 &s) {
        Scalar logVal = std::log(s.value);

        // vn = log(v), Dvn = Dv / v
        return DScalar1(logVal, s.grad / s.value);
    }

    friend DScalar1 sin(const DScalar1 &s) {
        // vn = sin(v), Dvn = cos(v) * Dv
        return DScalar1(std::sin(s.value), s.grad * std::cos(s.value));
    }

    friend DScalar1 cos(const DScalar1 &s) {
        // vn = cos(v), Dvn = -sin(v) * Dv
        return DScalar1(std::cos(s.value), s.grad * -std::sin(s.value));
    }

    friend DScalar1 acos(const DScalar1 &s) {
        if (std::abs(s.value) >= 1)
            throw std::runtime_error("acos: Expected a value in (-1, 1)");

        Scalar temp = -std::sqrt((Scalar) 1 - s.value*s.value);

        // vn = acos(v), Dvn = -1/sqrt(1-v^2) * Dv
        return DScalar1(std::acos(s.value),
            s.grad * ((Scalar) 1 / temp));
    }

    friend DScalar1 asin(const DScalar1 &s) {
        if (std::abs(s.value) >= 1)
            throw std::runtime_error("asin: Expected a value in (-1, 1)");

        Scalar temp = std::sqrt((Scalar) 1 - s.value*s.value);

        // vn = asin(v), Dvn = 1/sqrt(1-v^2) * Dv
        return DScalar1(std::asin(s.value),
            s.grad * ((Scalar) 1 / temp));
    }

    friend DScalar1 atan2(const DScalar1 &y, const DScalar1 &x) {
        Scalar denom = x.value*x.value + y.value*y.value;

        // vn = atan2(y, x), Dvn = (x*Dy - y*Dx) / (x^2 + y^2)
        return DScalar1(std::atan2(y.value, x.value),
            y.grad * (x.value / denom) - x.grad * (y.value / denom));
    }

    /// @}
    // ======================================================================

    // ======================================================================
    /// @{ \name Comparison and assignment
    // ======================================================================

    inline void operator=(const DScalar1& s) { value = s.value; grad = s.grad; }
    inline void operator=(const Scalar &v) { value = v; grad.setZero(); }
    inline bool operator<(const DScalar1& s) const { return value < s.value; }
    inline bool operator<=(const DScalar1& s) const { return value <= s.value; }
    inline bool operator>(const DScalar1& s) const { return value > s.value; }
    inline bool operator>=(const DScalar1& s) const { return value >= s.value; }
    inline bool operator<(const Scalar& s) const { return value < s; }
    inline bool operator<=(const Scalar& s) const { return value <= s; }
    inline bool operator>(const Scalar& s) const { return value > s; }
    inline bool operator>=(const Scalar& s) const { return value >= s; }
    inline bool operator==(const Scalar& s) const { return value == s; }
    inline bool operator!=(const Scalar& s) const { return value != s; }

    /// @}
    // ======================================================================

    // ======================================================================
    /// @{ \name Comparison and assignment
    // ======================================================================

    #if defined(__MITSUBA_MITSUBA_H_) /* Mitsuba-specific */
        /// Initialize a constant two-dimensional vector
        static inline DVector2 vector(const mitsuba::TVector2<Scalar> &v) {
            return DVector2(DScalar1(v.x), DScalar1(v.y));
        }

        /// Initialize a constant two-dimensional vector
        static inline DVector2 vector(const mitsuba::TPoint2<Scalar> &p) {
            return DVector2(DScalar1(p.x), DScalar1(p.y));
        }

        /// Create a constant three-dimensional vector
        static inline DVector3 vector(const mitsuba::TVector3<Scalar> &v) {
            return DVector3(DScalar1(v.x), DScalar1(v.y), DScalar1(v.z));
        }

        /// Create a constant three-dimensional vector
        static inline DVector3 vector(const mitsuba::TPoint3<Scalar> &p) {
            return DVector3(DScalar1(p.x), DScalar1(p.y), DScalar1(p.z));
        }
    #endif

    /// @}
    // ======================================================================
protected:
    Scalar value;
    Gradient grad;
};

template <typename Scalar, typename VecType>
        std::ostream &operator<<(std::ostream &out, const DScalar1<Scalar, VecType> &s) {
    out << "[" << s.getValue()
        << ", grad=" << s.getGradient().format(Eigen::IOFormat(4, 1, ", ", "; ", "", "", "[", "]"))
        << "]";
    return out;
}

/**
 * \brief Automatic differentiation scalar with first- and second-order derivatives
 *
 * This class provides an instrumented "scalar" value, which may be dependent on
 * a number of independent variables. The implementation keeps tracks of first
 * and second-order drivatives with respect to these variables using a set
 * of overloaded operations and implementations of special functions (sin,
 * tan, exp, ..).
 *
 * This is extremely useful for numerical optimization, particularly when
 * analytic derivatives from programs like Maple or Mathematica suffer from
 * excessively complicated expressions.
 *
 * The class relies on templates, which makes it possible to fix the
 * number of independent variables at compile-time so that instances can
 * be allocated on the stack. Otherwise, they will be placed on the heap.
 *
 * This is an extended C++ port of Jon Kaldor's implementation, which is
 * based on a C version by Eitan Grinspun at Caltech)
 *
 * \sa DScalar1
 * \author Wenzel Jakob
 */
template <typename _Scalar, typename _Gradient = Eigen::Matrix<_Scalar, Eigen::Dynamic, 1>,
          typename _Hessian = Eigen::Matrix<_Scalar, Eigen::Dynamic, Eigen::Dynamic> >
          struct DScalar2 : public DiffScalarBase {
public:
    typedef _Scalar                                         Scalar;
    typedef _Gradient                                       Gradient;
    typedef _Hessian                                        Hessian;
    typedef Eigen::Matrix<DScalar2, 2, 1>                   DVector2;
    typedef Eigen::Matrix<DScalar2, 3, 1>                   DVector3;

    // ======================================================================
    /// @{ \name Constructors and accessors
    // ======================================================================

    /// Create a new constant automatic differentiation scalar
    explicit DScalar2(Scalar value = (Scalar) 0) : value(value) {
        size_t variableCount = getVariableCount();

        grad.resize(Eigen::NoChange_t(), variableCount);
        grad.setZero();
        hess.resize(variableCount, variableCount);
        hess.setZero();
    }

    /// Construct a new scalar with the specified value and one first derivative set to 1
    DScalar2(size_t index, const Scalar &value)
     : value(value) {
        size_t variableCount = getVariableCount();

        grad.resize(Eigen::NoChange_t(), variableCount);
        grad.setZero();
        grad(index) = 1;
        hess.resize(variableCount, variableCount);
        hess.setZero();
    }

    /// Construct a scalar associated with the given gradient and Hessian
    DScalar2(Scalar value, const Gradient &grad, const Hessian &hess)
     : value(value), grad(grad), hess(hess) { }

    /// Copy constructor
    DScalar2(const DScalar2 &s)
     : value(s.value), grad(s.grad), hess(s.hess) { }

    inline const Scalar &getValue() const { return value; }
    inline const Gradient &getGradient() const { return grad; }
    inline const Hessian &getHessian() const { return hess; }

    // ======================================================================
    /// @{ \name Addition
    // ======================================================================
    friend DScalar2 operator+(const DScalar2 &lhs, const DScalar2 &rhs) {
        return DScalar2(lhs.value+rhs.value,
            lhs.grad+rhs.grad, lhs.hess+rhs.hess);
    }

    friend DScalar2 operator+(const DScalar2 &lhs, const Scalar &rhs) {
        return DScalar2(lhs.value+rhs, lhs.grad, lhs.hess);
    }

    friend DScalar2 operator+(const Scalar &lhs, const DScalar2 &rhs) {
        return DScalar2(rhs.value+lhs, rhs.grad, rhs.hess);
    }

    inline DScalar2& operator+=(const DScalar2 &s) {
        value += s.value;
        grad += s.grad;
        hess += s.hess;
        return *this;
    }

    inline DScalar2& operator+=(const Scalar &v) {
        value += v;
        return *this;
    }

    /// @}
    // ======================================================================

    // ======================================================================
    /// @{ \name Subtraction
    // ======================================================================

    friend DScalar2 operator-(const DScalar2 &lhs, const DScalar2 &rhs) {
        return DScalar2(lhs.value-rhs.value, lhs.grad-rhs.grad, lhs.hess-rhs.hess);
    }

    friend DScalar2 operator-(const DScalar2 &lhs, const Scalar &rhs) {
        return DScalar2(lhs.value-rhs, lhs.grad, lhs.hess);
    }

    friend DScalar2 operator-(const Scalar &lhs, const DScalar2 &rhs) {
        return DScalar2(lhs-rhs.value, -rhs.grad, -rhs.hess);
    }

    friend DScalar2 operator-(const DScalar2 &s) {
        return DScalar2(-s.value, -s.grad, -s.hess);
    }

    inline DScalar2& operator-=(const DScalar2 &s) {
        value -= s.value;
        grad -= s.grad;
        hess -= s.hess;
        return *this;
    }

    inline DScalar2& operator-=(const Scalar &v) {
        value -= v;
        return *this;
    }
    /// @}
    // ======================================================================

    // ======================================================================
    /// @{ \name Division
    // ======================================================================
    friend DScalar2 operator/(const DScalar2 &lhs, const Scalar &rhs) {
        if (rhs == 0)
            throw std::runtime_error("DScalar2: Division by zero!");
        Scalar inv = 1.0f / rhs;
        return DScalar2(lhs.value*inv, lhs.grad*inv, lhs.hess*inv);
    }

    friend DScalar2 operator/(const Scalar &lhs, const DScalar2 &rhs) {
        return lhs * inverse(rhs);
    }

    friend DScalar2 operator/(const DScalar2 &lhs, const DScalar2 &rhs) {
        return lhs * inverse(rhs);
    }

    friend DScalar2 inverse(const DScalar2 &s) {
        Scalar valueSqr = s.value*s.value,
            valueCub = valueSqr * s.value,
            invValueSqr = (Scalar) 1 / valueSqr;

        // vn = 1/v
        DScalar2 result((Scalar) 1 / s.value);

        // Dvn = -1/(v^2) Dv
        result.grad = s.grad * -invValueSqr;

        // D^2vn = -1/(v^2) D^2v + 2/(v^3) Dv Dv^T
        result.hess = s.hess * -invValueSqr;
        result.hess += s.grad * s.grad.transpose()
            * ((Scalar) 2 / valueCub);

        return result;
    }

    inline DScalar2& operator/=(const Scalar &v) {
        value /= v;
        grad /= v;
        hess /= v;
        return *this;
    }
    /// @}
    // ======================================================================

    // ======================================================================
    /// @{ \name Multiplication
    // ======================================================================
    friend DScalar2 operator*(const DScalar2 &lhs, const Scalar &rhs) {
        return DScalar2(lhs.value*rhs, lhs.grad*rhs, lhs.hess*rhs);
    }

    friend DScalar2 operator*(const Scalar &lhs, const DScalar2 &rhs) {
        return DScalar2(rhs.value*lhs, rhs.grad*lhs, rhs.hess*lhs);
    }

    friend DScalar2 operator*(const DScalar2 &lhs, const DScalar2 &rhs) {
        DScalar2 result(lhs.value*rhs.value);

        /// Product rule
        result.grad = rhs.grad * lhs.value + lhs.grad * rhs.value;

        // (i,j) = g*F_xixj + g*G_xixj + F_xi*G_xj + F_xj*G_xi
        result.hess = rhs.hess * lhs.value;
        result.hess += lhs.hess * rhs.value;
        result.hess += lhs.grad * rhs.grad.transpose();
        result.hess += rhs.grad * lhs.grad.transpose();

        return result;
    }

    inline DScalar2& operator*=(const Scalar &v) {
        value *= v;
        grad *= v;
        hess *= v;
        return *this;
    }

    /// @}
    // ======================================================================

    // ======================================================================
    /// @{ \name Miscellaneous functions
    // ======================================================================

    friend DScalar2 sqrt(const DScalar2 &s) {
        Scalar sqrtVal = std::sqrt(s.value),
               temp    = (Scalar) 1 / ((Scalar) 2 * sqrtVal);

        // vn = sqrt(v)
        DScalar2 result(sqrtVal);

        // Dvn = 1/(2 sqrt(v)) Dv
        result.grad = s.grad * temp;

        // D^2vn = 1/(2 sqrt(v)) D^2v - 1/(4 v*sqrt(v)) Dv Dv^T
        result.hess = s.hess * temp;
        result.hess += s.grad * s.grad.transpose()
            * (-(Scalar) 1 / ((Scalar) 4 * s.value * sqrtVal));

        return result;
    }

    friend DScalar2 pow(const DScalar2 &s, const Scalar &a) {
        Scalar powVal = std::pow(s.value, a),
               temp   = a * std::pow(s.value, a-1);
        // vn = v ^ a
        DScalar2 result(powVal);

        // Dvn = a*v^(a-1) * Dv
        result.grad = s.grad * temp;

        // D^2vn = a*v^(a-1) D^2v - 1/(4 v*sqrt(v)) Dv Dv^T
        result.hess = s.hess * temp;
        result.hess += s.grad * s.grad.transpose()
            * (a * (a-1) * std::pow(s.value, a-2));

        return result;
    }

    friend DScalar2 exp(const DScalar2 &s) {
        Scalar expVal = std::exp(s.value);

        // vn = exp(v)
        DScalar2 result(expVal);

        // Dvn = exp(v) * Dv
        result.grad = s.grad * expVal;

        // D^2vn = exp(v) * Dv*Dv^T + exp(v) * D^2v
        result.hess = (s.grad * s.grad.transpose()
            + s.hess) * expVal;

        return result;
    }

    friend DScalar2 log(const DScalar2 &s) {
        Scalar logVal = std::log(s.value);

        // vn = log(v)
        DScalar2 result(logVal);

        // Dvn = Dv / v
        result.grad = s.grad / s.value;

        // D^2vn = (v*D^2v - Dv*Dv^T)/(v^2)
        result.hess = s.hess / s.value -
            (s.grad * s.grad.transpose() / (s.value*s.value));

        return result;
    }

    friend DScalar2 sin(const DScalar2 &s) {
        Scalar sinVal = std::sin(s.value),
               cosVal = std::cos(s.value);

        // vn = sin(v)
        DScalar2 result(sinVal);

        // Dvn = cos(v) * Dv
        result.grad = s.grad * cosVal;

        // D^2vn = -sin(v) * Dv*Dv^T + cos(v) * Dv^2
        result.hess = s.hess * cosVal;
        result.hess += s.grad * s.grad.transpose() * -sinVal;

        return result;
    }

    friend DScalar2 cos(const DScalar2 &s) {
        Scalar sinVal = std::sin(s.value),
               cosVal = std::cos(s.value);
        // vn = cos(v)
        DScalar2 result(cosVal);

        // Dvn = -sin(v) * Dv
        result.grad = s.grad * -sinVal;

        // D^2vn = -cos(v) * Dv*Dv^T - sin(v) * Dv^2
        result.hess = s.hess * -sinVal;
        result.hess += s.grad * s.grad.transpose() * -cosVal;

        return result;
    }

    friend DScalar2 acos(const DScalar2 &s) {
        if (std::abs(s.value) >= 1)
            throw std::runtime_error("acos: Expected a value in (-1, 1)");

        Scalar temp = -std::sqrt((Scalar) 1 - s.value*s.value);

        // vn = acos(v)
        DScalar2 result(std::acos(s.value));

        // Dvn = -1/sqrt(1-v^2) * Dv
        result.grad = s.grad * ((Scalar) 1 / temp);

        // D^2vn = -1/sqrt(1-v^2) * D^2v - v/[(1-v^2)^(3/2)] * Dv*Dv^T
        result.hess = s.hess * ((Scalar) 1 / temp);
        result.hess += s.grad * s.grad.transpose()
            * s.value / (temp*temp*temp);

        return result;
    }

    friend DScalar2 asin(const DScalar2 &s) {
        if (std::abs(s.value) >= 1)
            throw std::runtime_error("asin: Expected a value in (-1, 1)");

        Scalar temp = std::sqrt((Scalar) 1 - s.value*s.value);

        // vn = asin(v)
        DScalar2 result(std::asin(s.value));

        // Dvn = 1/sqrt(1-v^2) * Dv
        result.grad = s.grad * ((Scalar) 1 / temp);

        // D^2vn = 1/sqrt(1-v*v) * D^2v + v/[(1-v^2)^(3/2)] * Dv*Dv^T
        result.hess = s.hess * ((Scalar) 1 / temp);
        result.hess += s.grad * s.grad.transpose()
            * s.value / (temp*temp*temp);

        return result;
    }

    friend DScalar2 atan2(const DScalar2 &y, const DScalar2 &x) {
        // vn = atan2(y, x)
        DScalar2 result(std::atan2(y.value, x.value));

        // Dvn = (x*Dy - y*Dx) / (x^2 + y^2)
        Scalar denom = x.value*x.value + y.value*y.value,
            denomSqr = denom*denom;
        result.grad = y.grad * (x.value / denom)
            - x.grad * (y.value / denom);

        // D^2vn = (Dy*Dx^T + xD^2y - Dx*Dy^T - yD^2x) / (x^2+y^2)
        //    - [(x*Dy - y*Dx) * (2*x*Dx + 2*y*Dy)^T] / (x^2+y^2)^2
        result.hess = (y.hess*x.value
            + y.grad * x.grad.transpose()
            - x.hess*y.value
            - x.grad*y.grad.transpose()
        ) / denom;

        result.hess -=
            (y.grad*(x.value/denomSqr) - x.grad*(y.value/denomSqr)) *
            (x.grad*((Scalar) 2 * x.value) + y.grad*((Scalar) 2 * y.value)).transpose();

        return result;
    }

    /// @}
    // ======================================================================

    // ======================================================================
    /// @{ \name Comparison and assignment
    // ======================================================================

    inline void operator=(const DScalar2& s) { value = s.value; grad = s.grad; hess = s.hess; }
    inline void operator=(const Scalar &v) { value = v; grad.setZero(); hess.setZero(); }
    inline bool operator<(const DScalar2& s) const { return value < s.value; }
    inline bool operator<=(const DScalar2& s) const { return value <= s.value; }
    inline bool operator>(const DScalar2& s) const { return value > s.value; }
    inline bool operator>=(const DScalar2& s) const { return value >= s.value; }
    inline bool operator<(const Scalar& s) const { return value < s; }
    inline bool operator<=(const Scalar& s) const { return value <= s; }
    inline bool operator>(const Scalar& s) const { return value > s; }
    inline bool operator>=(const Scalar& s) const { return value >= s; }
    inline bool operator==(const Scalar& s) const { return value == s; }
    inline bool operator!=(const Scalar& s) const { return value != s; }

    /// @}
    // ======================================================================

    // ======================================================================
    /// @{ \name Comparison and assignment
    // ======================================================================

    #if defined(__MITSUBA_MITSUBA_H_) /* Mitsuba-specific */
        /// Initialize a constant two-dimensional vector
        static inline DVector2 vector(const mitsuba::TVector2<Scalar> &v) {
            return DVector2(DScalar2(v.x), DScalar2(v.y));
        }

        /// Initialize a constant two-dimensional vector
        static inline DVector2 vector(const mitsuba::TPoint2<Scalar> &p) {
            return DVector2(DScalar2(p.x), DScalar2(p.y));
        }

        /// Create a constant three-dimensional vector
        static inline DVector3 vector(const mitsuba::TVector3<Scalar> &v) {
            return DVector3(DScalar2(v.x), DScalar2(v.y), DScalar2(v.z));
        }

        /// Create a constant three-dimensional vector
        static inline DVector3 vector(const mitsuba::TPoint3<Scalar> &p) {
            return DVector3(DScalar2(p.x), DScalar2(p.y), DScalar2(p.z));
        }
    #endif

    /// @}
    // ======================================================================
protected:
    Scalar value;
    Gradient grad;
    Hessian hess;
};

template <typename Scalar, typename VecType, typename MatType>
        std::ostream &operator<<(std::ostream &out, const DScalar2<Scalar, VecType, MatType> &s) {
    out << "[" << s.getValue()
        << ", grad=" << s.getGradient().format(Eigen::IOFormat(4, 1, ", ", "; ", "", "", "[", "]"))
        << ", hess=" << s.getHessian().format(Eigen::IOFormat(4, 0, ", ", "; ", "", "", "[", "]"))
        << "]";
    return out;
}

#endif /* __AUTODIFF_H */
