//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file     Resample/Flux/MatrixFlux.cpp
//! @brief    Implements class MatrixFlux.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2020
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "Resample/Flux/MatrixFlux.h"
#include "Base/Util/Assert.h"

namespace {

const auto eps = std::numeric_limits<double>::epsilon() * 10.;

complex_t GetImExponential(complex_t exponent)
{
    if (exponent.imag() > -std::log(std::numeric_limits<double>::min()))
        return 0.0;
    return std::exp(I * exponent);
}

} // namespace

MatrixFlux::MatrixFlux(double kz_sign, const Spinor& k_eigen, const R3& b, double magnetic_SLD)
    : m_k_eigen(k_eigen)
    , m_T(+1, 0, 0, +1)
    , m_R(-1, 0, 0, -1)
    , m_b(b)
    , m_kz_sign(kz_sign)
    , m_magnetic_SLD(magnetic_SLD)
{
    ASSERT(std::abs(m_b.mag() - 1) < eps || (m_b.mag() < eps && magnetic_SLD < eps));
}


//! See PhysRef, chapter "Polarized", section "Wavenumber operator".
SpinMatrix MatrixFlux::computeKappa() const
{
    const complex_t alpha = m_k_eigen.u + m_k_eigen.v;
    const complex_t beta = m_k_eigen.u - m_k_eigen.v;
    return SpinMatrix(alpha + beta * m_b.z(), beta * (m_b.x() - I * m_b.y()),
                      beta * (m_b.x() + I * m_b.y()), alpha - beta * m_b.z())
           / 2.;
}

//! See PhysRef, chapter "Polarized", section "Wavenumber operator".
SpinMatrix MatrixFlux::computeInverseKappa() const
{
    const complex_t alpha = m_k_eigen.u + m_k_eigen.v;
    const complex_t beta = m_k_eigen.u - m_k_eigen.v;
    if (std::abs(alpha * alpha - beta * beta) == 0.)
        throw std::runtime_error("Inverse wavenumber operator is singular for given B field."
                                 " Add some absorption to the nuclear SLD.");

    return 2. / (alpha * alpha - beta * beta)
           * SpinMatrix(alpha - beta * m_b.z(), beta * (-m_b.x() + I * m_b.y()),
                        -beta * (m_b.x() + I * m_b.y()), alpha + beta * m_b.z());
}

//! See PhysRef, chapter "Polarized", section "Eigendecomposition"
SpinMatrix MatrixFlux::eigenToMatrix(const Spinor& diagonal) const
{
    const SpinMatrix M(diagonal.u, 0, 0, diagonal.v);

    if (std::abs(m_b.mag() - 1.) < eps) {
        SpinMatrix Q(1. + m_b.z(), m_b.x() - I * m_b.y(), m_b.x() + I * m_b.y(), -m_b.z() - 1.);
        return Q * M * Q.adjoint() / (2. * (1. + m_b.z()));
    }
    ASSERT(m_b.mag() < eps); // remaining case: no field
    return M;
}

SpinMatrix MatrixFlux::computeDeltaMatrix(double thickness) const
{
    return eigenToMatrix({GetImExponential(m_kz_sign * thickness * m_k_eigen.u),
                          GetImExponential(m_kz_sign * thickness * m_k_eigen.v)});
}

SpinMatrix MatrixFlux::T1Matrix() const
{
    return eigenToMatrix({1., 0.});
}

SpinMatrix MatrixFlux::T2Matrix() const
{
    return eigenToMatrix({0., 1.});
}

Spinor MatrixFlux::T1plus() const
{
    return T1Matrix() * m_T.col0();
}

Spinor MatrixFlux::R1plus() const
{
    return T1Matrix() * m_R.col0();
}

Spinor MatrixFlux::T2plus() const
{
    return T2Matrix() * m_T.col0();
}

Spinor MatrixFlux::R2plus() const
{
    return T2Matrix() * m_R.col0();
}

Spinor MatrixFlux::T1min() const
{
    return T1Matrix() * m_T.col1();
}

Spinor MatrixFlux::R1min() const
{
    return T1Matrix() * m_R.col1();
}

Spinor MatrixFlux::T2min() const
{
    return T2Matrix() * m_T.col1();
}

Spinor MatrixFlux::R2min() const
{
    return T2Matrix() * m_R.col1();
}

Spinor MatrixFlux::getKz() const
{
    return m_kz_sign * m_k_eigen;
}
