//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Device/Pol/PolFilter.cpp
//! @brief     Implements class PolFilter.
//!
//! @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 "Device/Pol/PolFilter.h"

PolFilter::PolFilter(const R3& direction, double transmission_parallel,
                     double transmission_antiparallel)
    : m_direction(direction)
    , m_t_plus(transmission_parallel)
    , m_t_minus(transmission_antiparallel)
{
    if (direction.mag2() < std::numeric_limits<double>::epsilon())
        throw std::runtime_error("Analyzer: direction vector is zero");
    if (transmission_parallel < 0 || transmission_parallel > 1)
        throw std::runtime_error("Analyzer: transmission of parallel component is not in (0, 1)");
    if (transmission_antiparallel < 0 || transmission_antiparallel > 1)
        throw std::runtime_error(
            "Analyzer: transmission of antiparallel component is not in (0, 1)");

    m_direction /= direction.mag();

    // Angles

    // theta - spin direction angle from Z axis
    const double mag_xy = m_direction.magxy();
    const double cos_theta = m_direction.z();

    // theta/2
    const double cos_theta2 = std::sqrt((1 + cos_theta) / 2);
    const double sin_theta2 = std::sqrt((1 - cos_theta) / 2);

    // phi - spin direction angle in XY plane (from X to Y)
    const double cos_phi = mag_xy > 0 ? m_direction.x() / mag_xy : 1;
    const double sin_phi = mag_xy > 0 ? m_direction.y() / mag_xy : 0;

    // Matrix elements

    // Eigen vectors for the given direction, expanded in spin-z-basis (|+> & |->):
    // |plus>  = a_p |+> + b_p |->
    // |minus> = a_m |+> + b_m |->
    const complex_t exp = cos_phi + I * sin_phi;
    const complex_t a_p = cos_theta2;
    const complex_t b_p = exp * sin_theta2;
    const complex_t a_m = sin_theta2;
    const complex_t b_m = -exp * cos_theta2;

    // Operator {t_i*|i><i|} = t_plus*|plus><plus| + t_minus*|minus><minus|
    // In "plus-minus" basis for the given direction it is diagonal:
    //    (t_plus  0      )
    //    (0       t_minus)
    //
    // The meaning of t_plus is the probability of getting |plus> state
    // for the particle after passing through analyzer.
    // Correspondingly, t_minus is the probability of getting |minus> state
    // for the particle after passing through analyzer.
    //
    // Examples of some edge cases:
    //
    // If t_plus = t_minus = 0 then the analyzer absorbs everything. Intensity is 0 after it.
    //
    // If t_plus = t_minus = 1 then the analyzer transmits everything and does not affect the beam.
    // Equivalent to the absence of analyzer.
    //
    // If t_plus = 1, t_minus = 0 then the analyzer is perfect: transmits without absorption
    // the desired component and completely blocks the undesired one.
    //
    // If t_plus = 0, t_minus = 1 then the analyzer is "anti-perfect": completely blocks
    // the desired component and transmits without absorption the undesired one. This is equivalent
    // of the perfect analyzer but in the opposite direction.

    // We substitute |plus> and |minus> expressed in spin-z-basis and
    // get matrix of the same operator {t_i*|i><i|} but in spin-z-basis:
    //    (a b)
    //    (c d)
    // This is not a density matrix, its trace can be >1 (for example, if the unit operator)
    m_ana_matrix.a = m_t_plus * std::norm(a_p) + m_t_minus * std::norm(a_m);
    m_ana_matrix.b = m_t_plus * a_p * std::conj(b_p) + m_t_minus * a_m * std::conj(b_m);
    m_ana_matrix.c = m_t_plus * std::conj(a_p) * b_p + m_t_minus * std::conj(a_m) * b_m;
    m_ana_matrix.d = m_t_plus * std::norm(b_p) + m_t_minus * std::norm(b_m);
}

PolFilter* PolFilter::clone() const
{
    return new PolFilter(m_direction, m_t_plus, m_t_minus);
}
