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

#include "Base/Spin/SpinMatrix.h"
#include "Base/Spin/Spinor.h"
#include "Base/Util/Assert.h"

SpinMatrix::SpinMatrix(complex_t a_, complex_t b_, complex_t c_, complex_t d_)
    : a(a_)
    , b(b_)
    , c(c_)
    , d(d_)
{
}

SpinMatrix::SpinMatrix()
    : SpinMatrix(0, 0, 0, 0)
{
}

SpinMatrix SpinMatrix::Diag(complex_t a_, complex_t d_)
{
    return {a_, 0, 0, d_};
}

SpinMatrix SpinMatrix::One()
{
    return Diag(1, 1);
}

SpinMatrix SpinMatrix::Null()
{
    return Diag(0, 0);
}

SpinMatrix SpinMatrix::FromBlochVector(const R3& v)
{
    return {(1.0 + v.z()) / 2.0, complex_t(v.x(), -v.y()) / 2.0, complex_t(v.x(), v.y()) / 2.0,
            (1.0 - v.z()) / 2.0};
}

template <DoubleOrComplex T> SpinMatrix SpinMatrix::PauliMapping(const Vec3<T>& v)
{
    static const SpinMatrix Pauli_X(0, 1, 1, 0);
    static const SpinMatrix Pauli_Y(0, -I, I, 0);
    static const SpinMatrix Pauli_Z(1, 0, 0, -1);
    return Pauli_X * v.x() + Pauli_Y * v.y() + Pauli_Z * v.z();
}

// prompt instantiation:

template SpinMatrix SpinMatrix::PauliMapping(const R3&);
template SpinMatrix SpinMatrix::PauliMapping(const C3&);

SpinMatrix SpinMatrix::operator-() const
{
    return {-a, -b, -c, -d};
}

SpinMatrix SpinMatrix::operator+(const SpinMatrix& o) const
{
    return {a + o.a, b + o.b, c + o.c, d + o.d};
}

SpinMatrix SpinMatrix::operator-(const SpinMatrix& o) const
{
    return {a - o.a, b - o.b, c - o.c, d - o.d};
}

SpinMatrix SpinMatrix::operator+=(const SpinMatrix& o)
{
    a += o.a;
    b += o.b;
    c += o.c;
    d += o.d;

    return *this;
}

SpinMatrix SpinMatrix::operator*(const SpinMatrix& o) const
{
    return {a * o.a + b * o.c, a * o.b + b * o.d, c * o.a + d * o.c, c * o.b + d * o.d};
}

SpinMatrix SpinMatrix::operator*=(const SpinMatrix& o)
{
    const SpinMatrix tmp(*this * o);
    *this = tmp;
    return *this;
}

Spinor SpinMatrix::operator*(const Spinor& s) const
{
    return {a * s.u + b * s.v, c * s.u + d * s.v};
}

SpinMatrix SpinMatrix::operator*(complex_t f) const
{
    return {a * f, b * f, c * f, d * f};
}

SpinMatrix SpinMatrix::operator*(double f) const
{
    return {a * f, b * f, c * f, d * f};
}

SpinMatrix SpinMatrix::operator/(complex_t f) const
{
    return {a / f, b / f, c / f, d / f};
}

SpinMatrix SpinMatrix::operator/(double f) const
{
    return {a / f, b / f, c / f, d / f};
}

SpinMatrix SpinMatrix::operator*=(complex_t f)
{
    a *= f;
    b *= f;
    c *= f;
    d *= f;
    return *this;
}

SpinMatrix SpinMatrix::operator*=(double f)
{
    a *= f;
    b *= f;
    c *= f;
    d *= f;
    return *this;
}

SpinMatrix SpinMatrix::operator/=(complex_t f)
{
    a /= f;
    b /= f;
    c /= f;
    d /= f;
    return *this;
}

SpinMatrix SpinMatrix::operator/=(double f)
{
    a /= f;
    b /= f;
    c /= f;
    d /= f;
    return *this;
}

SpinMatrix operator*(complex_t f, const SpinMatrix& m)
{
    return m * f;
}

SpinMatrix operator*(double f, const SpinMatrix& m)
{
    return m * f;
}

Spinor SpinMatrix::col0() const
{
    return {a, c};
}

Spinor SpinMatrix::col1() const
{
    return {b, d};
}

complex_t SpinMatrix::trace() const
{
    return a + d;
}

complex_t SpinMatrix::determinant() const
{
    return a * d - b * c;
}

bool SpinMatrix::allFinite() const
{
    return isfinite(a) && isfinite(b) && isfinite(c) && isfinite(d);
}

SpinMatrix SpinMatrix::adjoint() const
{
    return {conj(a), conj(c), conj(b), conj(d)};
}

double SpinMatrix::intensityTrace(const SpinMatrix& analyzer, const SpinMatrix& intensity)
{
    // tr(analyzer * intensity) is a measured intensity and should therefore be a real, non-negative
    // scalar. We keep explicit checks here instead of silently taking abs(), so that a genuine
    // physical or algebraic inconsistency is not hidden as "just a number".
    const complex_t tr = (analyzer * intensity).trace();
    constexpr double tol = 1e-12;
    const double scale = std::abs(tr) > 1.0 ? std::abs(tr) : 1.0;
    const double real_trace = tr.real();

    ASSERT(std::abs(tr.imag()) <= tol * scale);
    ASSERT(real_trace >= -tol * scale);

    return real_trace > 0.0 ? real_trace : 0.0;
}
