// Copyright (c) 2019, Tom Westerhout
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
//   contributors may be used to endorse or promote products derived from
//   this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#pragma once

#include "detail/lattice.hpp"
#include "detail/magnetic_cluster.hpp"
#include "detail/simulated_annealing.hpp"
#include "detail/utility.hpp"
#include <boost/align/aligned_allocator.hpp>
#include <gsl/gsl-lite.hpp>
#include <sleef.h>
#include <sys/user.h>
#include <vector>

#if !defined(__AVX__)
#    error "Thermalisation relies on AVX"
#endif

TCM_NAMESPACE_BEGIN

using size_t = std::size_t;

namespace detail {
/// Wrapping to [0, 2pi).
struct to_0_2pi_fn {
    static constexpr float one_over_two_pi = 0.15915494f;
    static constexpr float two_pi          = 6.2831855f;

    auto operator()(float const x) const noexcept -> float
    {
        return x - two_pi * std::floor(one_over_two_pi * x);
    }

    auto operator()(__m256 const x) const noexcept -> __m256
    {
        return x
               - _mm256_set1_ps(two_pi)
                     * Sleef_floorf8_avx(_mm256_set1_ps(one_over_two_pi) * x);
    }
};

#if 0
/// Performs the actual computation of energy
///
/// NOTE: Uses AVX intrinsics.
inline auto energy_kernel(
    gsl::span<float const> const                     spin,
    gsl::span<std::pair<size_t, size_t> const> const edges) TCM_NOEXCEPT
    -> float
{ // {{{
    auto const load_angles =
        [s = spin.data()](auto const* const d, auto const n)
            TCM_NOEXCEPT -> __m256 {
        switch (n) {
        case 0: return _mm256_set1_ps(0);
        case 1:
            return _mm256_set_ps(0, 0, 0, 0, 0, 0, 0, //
                                 s[d[0].first] - s[d[0].second]);
        case 2:
            return _mm256_set_ps(0, 0, 0, 0, 0, 0,               //
                                 s[d[1].first] - s[d[1].second], //
                                 s[d[0].first] - s[d[0].second]);
        case 3:
            return _mm256_set_ps(0, 0, 0, 0, 0,                  //
                                 s[d[2].first] - s[d[2].second], //
                                 s[d[1].first] - s[d[1].second], //
                                 s[d[0].first] - s[d[0].second]);
        case 4:
            return _mm256_set_ps(0, 0, 0, 0,                     //
                                 s[d[3].first] - s[d[3].second], //
                                 s[d[2].first] - s[d[2].second], //
                                 s[d[1].first] - s[d[1].second], //
                                 s[d[0].first] - s[d[0].second]);
        case 5:
            return _mm256_set_ps(0, 0, 0,                        //
                                 s[d[4].first] - s[d[4].second], //
                                 s[d[3].first] - s[d[3].second], //
                                 s[d[2].first] - s[d[2].second], //
                                 s[d[1].first] - s[d[1].second], //
                                 s[d[0].first] - s[d[0].second]);
        case 6:
            return _mm256_set_ps(0, 0,                           //
                                 s[d[5].first] - s[d[5].second], //
                                 s[d[4].first] - s[d[4].second], //
                                 s[d[3].first] - s[d[3].second], //
                                 s[d[2].first] - s[d[2].second], //
                                 s[d[1].first] - s[d[1].second], //
                                 s[d[0].first] - s[d[0].second]);
        case 7:
            return _mm256_set_ps(0,                              //
                                 s[d[6].first] - s[d[6].second], //
                                 s[d[5].first] - s[d[5].second], //
                                 s[d[4].first] - s[d[4].second], //
                                 s[d[3].first] - s[d[3].second], //
                                 s[d[2].first] - s[d[2].second], //
                                 s[d[1].first] - s[d[1].second], //
                                 s[d[0].first] - s[d[0].second]);

        case 8:
            return _mm256_set_ps(s[d[7].first] - s[d[7].second], //
                                 s[d[6].first] - s[d[6].second], //
                                 s[d[5].first] - s[d[5].second], //
                                 s[d[4].first] - s[d[4].second], //
                                 s[d[3].first] - s[d[3].second], //
                                 s[d[2].first] - s[d[2].second], //
                                 s[d[1].first] - s[d[1].second], //
                                 s[d[0].first] - s[d[0].second]);
        default: TCM_ASSERT(false, "Bug! This should never have happened.");
        } // end switch
    };

    constexpr auto vector_size = 8;
    auto const     chunks      = edges.size() / vector_size;
    auto const     rest        = edges.size() % vector_size;

    auto        energy = _mm256_set1_ps(0);
    auto const* data   = edges.data();

    for (auto i = std::size_t{0}; i < chunks; ++i, data += vector_size) {
        energy += Sleef_cosf8_u35avx(load_angles(data, vector_size));
    }
    if (rest != 0) { energy += Sleef_cosf8_u35avx(load_angles(data, rest)); }

    return energy[0] + energy[1] + energy[2] + energy[3] + energy[4] + energy[5]
           + energy[6] + energy[7] - static_cast<float>(rest);
} // }}}
#endif

inline auto
energy_kernel(gsl::span<float const> const                         spin,
              gsl::span<std::pair<unsigned, unsigned> const> const edges,
              gsl::span<float const> const coeffs) TCM_NOEXCEPT -> float
{ // {{{
    auto const load_chunk = [s = spin, d = edges](auto const n)
                                TCM_NOEXCEPT -> __m256 {
        TCM_ASSERT(8 * (n + 1) <= d.size(), "Index out of bounds");
        auto const i = 8 * n;
        auto const x = _mm256_set_ps(s[d[i + 7].first], s[d[i + 6].first],
                                     s[d[i + 5].first], s[d[i + 4].first],
                                     s[d[i + 3].first], s[d[i + 2].first],
                                     s[d[i + 1].first], s[d[i + 0].first]);
        auto const y = _mm256_set_ps(s[d[i + 7].second], s[d[i + 6].second],
                                     s[d[i + 5].second], s[d[i + 4].second],
                                     s[d[i + 3].second], s[d[i + 2].second],
                                     s[d[i + 1].second], s[d[i + 0].second]);
        return _mm256_sub_ps(x, y);
    };

    auto const load_part = [s = spin, d = edges](auto const n, auto const rest)
                               TCM_NOEXCEPT -> __m256 {
        TCM_ASSERT(0 < rest && rest < 8, "Precondition violated");
        TCM_ASSERT(8 * n + rest <= d.size(), "Index out of bounds");
        auto const i = 8 * n;
        switch (rest) {
        case 1:
            return _mm256_set_ps(0, 0, 0, 0, 0, 0, 0, //
                                 s[d[i + 0].first] - s[d[i + 0].second]);
        case 2:
            return _mm256_set_ps(0, 0, 0, 0, 0, 0,                       //
                                 s[d[i + 1].first] - s[d[i + 1].second], //
                                 s[d[i + 0].first] - s[d[i + 0].second]);
        case 3:
            return _mm256_set_ps(0, 0, 0, 0, 0,                          //
                                 s[d[i + 2].first] - s[d[i + 2].second], //
                                 s[d[i + 1].first] - s[d[i + 1].second], //
                                 s[d[i + 0].first] - s[d[i + 0].second]);
        case 4:
            return _mm256_set_ps(0, 0, 0, 0,                             //
                                 s[d[i + 3].first] - s[d[i + 3].second], //
                                 s[d[i + 2].first] - s[d[i + 2].second], //
                                 s[d[i + 1].first] - s[d[i + 1].second], //
                                 s[d[i + 0].first] - s[d[i + 0].second]);
        case 5:
            return _mm256_set_ps(0, 0, 0,                                //
                                 s[d[i + 4].first] - s[d[i + 4].second], //
                                 s[d[i + 3].first] - s[d[i + 3].second], //
                                 s[d[i + 2].first] - s[d[i + 2].second], //
                                 s[d[i + 1].first] - s[d[i + 1].second], //
                                 s[d[i + 0].first] - s[d[i + 0].second]);
        case 6:
            return _mm256_set_ps(0, 0,                                   //
                                 s[d[i + 5].first] - s[d[i + 5].second], //
                                 s[d[i + 4].first] - s[d[i + 4].second], //
                                 s[d[i + 3].first] - s[d[i + 3].second], //
                                 s[d[i + 2].first] - s[d[i + 2].second], //
                                 s[d[i + 1].first] - s[d[i + 1].second], //
                                 s[d[i + 0].first] - s[d[i + 0].second]);
        case 7:
            return _mm256_set_ps(0,                                      //
                                 s[d[i + 6].first] - s[d[i + 6].second], //
                                 s[d[i + 5].first] - s[d[i + 5].second], //
                                 s[d[i + 4].first] - s[d[i + 4].second], //
                                 s[d[i + 3].first] - s[d[i + 3].second], //
                                 s[d[i + 2].first] - s[d[i + 2].second], //
                                 s[d[i + 1].first] - s[d[i + 1].second], //
                                 s[d[i + 0].first] - s[d[i + 0].second]);
        default: TCM_ASSERT(false, "Bug! This should never have happened.");
        } // end switch
    };

    constexpr auto vector_size = 8;
    auto const     chunks      = edges.size() / vector_size;
    auto const     rest        = edges.size() % vector_size;

    auto energy = _mm256_set1_ps(0.0f);
    for (auto i = size_t{0}; i < chunks; ++i) {
        energy += _mm256_mul_ps(_mm256_load_ps(coeffs.data() + i * vector_size),
                                Sleef_cosf8_u35avx(load_chunk(i)));
    }
    auto energy_rest = 0.0f;
    if (rest != 0) {
        auto const begin = chunks * vector_size;
        auto const end   = begin + rest;
        for (auto i = begin; i < end; ++i) {
            energy_rest +=
                coeffs[i]
                * Sleef_cosf_u35(spin[edges[i].first] - spin[edges[i].second]);
        }
    }
    return energy[0] + energy[1] + energy[2] + energy[3] + energy[4] + energy[5]
           + energy[6] + energy[7] + energy_rest;
} // }}}
} // namespace detail

#if 0
struct energy_buffers_t {

    struct energy_fn_type {
        gsl::span<std::pair<unsigned, unsigned> const> edges;
        gsl::span<float const>                         coeffs;
        unsigned                                       number_sites;

        auto operator()(gsl::span<float const> const spin) TCM_NOEXCEPT -> float
        {
            TCM_ASSERT(spin.size() == number_sites, "");
            return detail::energy_kernel(spin, edges, coeffs);
        }
    };

  private:
    template <class T>
    using buffer_type =
        std::vector<T, boost::alignment::aligned_allocator<T, PAGE_SIZE>>;

    static constexpr auto empty = std::numeric_limits<unsigned>::max();

    buffer_type<unsigned> _g_to_l; ///< Mapping of global to local indices.
    buffer_type<std::pair<unsigned, unsigned>>
                       _edges;  ///< Edges (given in terms of local indices)
    buffer_type<float> _coeffs; ///< Couplings (+1 for antiferromagnetic
                                ///< connections and -1 for ferromagnetic ones)

  public:
    explicit energy_buffers_t(size_t const size)
        : _g_to_l(size)
        , _edges(size)  // NOTE: This is just an initial estimate
        , _coeffs(size) // And this as well
    {}

    energy_buffers_t(const energy_buffers_t&)     = delete;
    energy_buffers_t(energy_buffers_t&&) noexcept = default;
    energy_buffers_t& operator=(energy_buffers_t const&) = delete;
    energy_buffers_t& operator=(energy_buffers_t&&) noexcept = default;

    template <class System>
    auto energy_fn(System const& system) -> energy_fn_type;

  private:
    template <class System>
    auto set_initial_state(sa_buffers_t const& sa_buffers) -> energy_fn_type;

    auto global_to_local(size_t const i) const TCM_NOEXCEPT -> size_t
    {
        TCM_ASSERT(i < _g_to_l.size(), "Index out of bounds");
        TCM_ASSERT(_g_to_l[i] != empty, "Site does not belong to the cluster");
        return _g_to_l[i];
    }
};
#endif

#if 0
// {{{ energy_buffers_t::energy_fn IMPLEMENTATION
template <class Lattice>
TCM_NOINLINE auto energy_buffers_t::energy_fn(gsl::span<size_t const> sites,
                                              Lattice const&          lattice)
    -> energy_fn_type
{
    using std::begin, std::end;
    if (::TCM_NAMESPACE::size(lattice) != _g_to_l.size()) {
        _g_to_l.resize(::TCM_NAMESPACE::size(lattice));
    }

    std::fill(begin(_g_to_l), end(_g_to_l), empty);
    for (auto local_i = size_t{0}; local_i < sites.size(); ++local_i) {
        auto const global_i = sites[local_i];
        _g_to_l[global_i]   = local_i;
    }

    _fm_edges.clear();
    _afm_edges.clear();
    for (auto local_i = size_t{0}; local_i < sites.size(); ++local_i) {
        auto const global_i = sites[local_i];
        for (std::int64_t const _global_j :
             ::TCM_NAMESPACE::neighbours(lattice, global_i)) {
            // We want signed comparison here, because j may denote a
            // boundary and thus be negative. This condition ensures that
            // every edge is counted only once.
            if (static_cast<std::int64_t>(global_i) < _global_j) {
                // Here we know that _global_j > 0
                TCM_ASSERT(_global_j > 0, "");
                auto const global_j = static_cast<size_t>(_global_j);
                auto const local_j  = _g_to_l[global_j];
                if (local_j != empty) {
                    switch (::TCM_NAMESPACE::interaction(lattice, global_i,
                                                         global_j)) {
                    case interaction_t::Ferromagnetic:
                        _fm_edges.emplace_back(local_i, local_j);
                        break;
                    case interaction_t::Antiferromagnetic:
                        _afm_edges.emplace_back(local_i, local_j);
                        break;
                    } // end switch
                }
            }
        }
    }

    return {{_fm_edges}, {_afm_edges}};
}
#endif

#if 0
template <class System>
TCM_NOINLINE auto energy_buffers_t::energy_fn(System const& system)
    -> energy_fn_type
{
    using std::begin, std::end;

    auto const& lattice = system.lattice();
    if (::TCM_NAMESPACE::size(system.lattice()) != _g_to_l.size()) {
        _g_to_l.resize(::TCM_NAMESPACE::size(lattice));
    }
    std::fill(begin(_g_to_l), end(_g_to_l), empty);
    _edges.clear();
    _coeffs.clear();

    auto number_sites = unsigned{0};
    for (auto i = size_t{0}; i < ::TCM_NAMESPACE::size(lattice); ++i) {
        if (!system.is_empty(i)) {
            for (auto const _j : ::TCM_NAMESPACE::neighbours(lattice, i)) {
                if (_j >= 0) {
                    auto const j = static_cast<size_t>(_j);
                    if (!system.is_empty(j)
                        && system.get_magnetic_cluster(i)
                               == system.get_magnetic_cluster(j)) {
                        if (_g_to_l[i] == empty) {
                            _g_to_l[i] = number_sites++;
                        }
                        if (j < i) {
                            TCM_ASSERT(
                                _g_to_l[j] != empty && _g_to_l[i] != empty, "");
                            _coeffs.emplace_back(
                                ::TCM_NAMESPACE::coupling(lattice, j, i));
                            _edges.emplace_back(_g_to_l[j], _g_to_l[i]);
                        }
                    }
                }
            }
        }
    }

    return {{_edges}, {_coeffs}, number_sites};
}
// }}}
#endif

struct thermaliser_t {

    struct energy_fn_type {
        gsl::span<std::pair<unsigned, unsigned> const> edges;
        gsl::span<float const>                         coeffs;
        size_t                                         number_sites;

        auto operator()(gsl::span<float const> const spin) TCM_NOEXCEPT -> float
        {
            TCM_ASSERT(spin.size() == number_sites, "");
            return detail::energy_kernel(spin, edges, coeffs);
        }
    };

  private:
    template <class T>
    using buffer_type =
        std::vector<T, boost::alignment::aligned_allocator<T, PAGE_SIZE>>;

    static constexpr auto empty = std::numeric_limits<unsigned>::max();

    sa_buffers_t          _sa_buffers; ///< Simulated Annealing buffers.
    buffer_type<unsigned> _l_to_g; ///< Mapping from local to global indices.
    buffer_type<unsigned> _g_to_l; ///< Mapping from global to local indices.
    buffer_type<std::pair<unsigned, unsigned>>
                       _edges;  ///< Edges (given in terms of local indices)
    buffer_type<float> _coeffs; ///< Couplings (+1 for antiferromagnetic
                                ///< connections and -1 for ferromagnetic ones)

  public:
    explicit thermaliser_t(size_t const size)
        : _sa_buffers{size}
        , _l_to_g(size)
        , _g_to_l(size)
        , _edges(size)  // NOTE: This is just an initial estimate
        , _coeffs(size) // And this as well
    {}

    thermaliser_t(const thermaliser_t&)     = delete;
    thermaliser_t(thermaliser_t&&) noexcept = default;
    thermaliser_t& operator=(thermaliser_t const&) = delete;
    thermaliser_t& operator=(thermaliser_t&&) noexcept = default;

    template <class System>
    TCM_NOINLINE auto thermalise(magnetic_cluster_t<System>& cluster,
                                 sa_pars_t const& parameters) -> void;

    template <class System>
    TCM_NOINLINE auto thermalise(System& system, sa_pars_t const& parameters)
        -> void;

  private:
    template <class System>
    TCM_NOINLINE auto reset(System const& system) -> void;

    template <class System>
    TCM_NOINLINE auto reset(magnetic_cluster_t<System> const& cluster) -> void;

    template <class System>
    TCM_NOINLINE auto store(gsl::span<float const>      best,
                            magnetic_cluster_t<System>& cluster) -> void;

    template <class System>
    TCM_NOINLINE auto store(gsl::span<float const> best, System& system)
        -> void;
};

template <class System>
auto thermaliser_t::thermalise(magnetic_cluster_t<System>& cluster,
                               sa_pars_t const&            parameters) -> void
{
    reset(cluster);
    auto&      system    = cluster.system();
    auto       energy_fn = energy_fn_type{{_edges}, {_coeffs}, _l_to_g.size()};
    auto       wrap_fn   = detail::to_0_2pi_fn{};
    sa_chain_t chain{_sa_buffers, parameters, energy_fn, wrap_fn,
                     system.rng_stream()};
    for (auto i = 0u; i < parameters.n; ++i) {
        chain();
    }
    store(chain.best(), system);
}

template <class System>
auto thermaliser_t::thermalise(System& system, sa_pars_t const& parameters)
    -> void
{
    reset(system);
    auto       energy_fn = energy_fn_type{{_edges}, {_coeffs}, _l_to_g.size()};
    auto       wrap_fn   = detail::to_0_2pi_fn{};
    sa_chain_t chain{_sa_buffers, parameters, energy_fn, wrap_fn,
                     system.rng_stream()};
    for (auto i = 0u; i < parameters.n; ++i) {
        chain();
    }
    store(chain.best(), system);
}

template <class System>
auto thermaliser_t::reset(magnetic_cluster_t<System> const& cluster) -> void
{
    auto const& system  = cluster.system();
    auto const& lattice = system.lattice();
    if (::TCM_NAMESPACE::size(lattice) != _g_to_l.size()) {
        _g_to_l.resize(::TCM_NAMESPACE::size(lattice));
    }
    std::fill(begin(_g_to_l), end(_g_to_l), empty);
    _l_to_g.clear();
    _edges.clear();
    _coeffs.clear();

    for (auto const i : cluster.sites()) {
        if (!system.is_empty(i)) {
            for (auto const _j : ::TCM_NAMESPACE::neighbours(lattice, i)) {
                if (_j >= 0) {
                    auto const j = static_cast<unsigned>(_j);
                    if (!system.is_empty(j)
                        && (&system.get_magnetic_cluster(j) == &cluster)) {
                        if (_g_to_l[i] == empty) {
                            _g_to_l[i] = static_cast<unsigned>(_l_to_g.size());
                            _l_to_g.push_back(i);
                        }
                        if (j < i) {
                            TCM_ASSERT(
                                _g_to_l[j] != empty && _g_to_l[i] != empty, "");
                            _coeffs.push_back(
                                ::TCM_NAMESPACE::coupling(lattice, j, i));
                            _edges.emplace_back(_g_to_l[j], _g_to_l[i]);
                        }
                    }
                }
            }
        }
    }

    _sa_buffers.resize(_l_to_g.size());
    auto initial = _sa_buffers.current();
    for (auto const i : _l_to_g) {
        initial[i] = system.get_angle(i);
    }
}

template <class System> auto thermaliser_t::reset(System const& system) -> void
{
    auto const& lattice = system.lattice();
    if (::TCM_NAMESPACE::size(lattice) != _g_to_l.size()) {
        _g_to_l.resize(::TCM_NAMESPACE::size(lattice));
    }
    std::fill(begin(_g_to_l), end(_g_to_l), empty);
    _l_to_g.clear();
    _edges.clear();
    _coeffs.clear();

    for (auto i = 0u; i < ::TCM_NAMESPACE::size(lattice); ++i) {
        if (!system.is_empty(i)) {
            for (auto const _j : ::TCM_NAMESPACE::neighbours(lattice, i)) {
                if (_j >= 0) {
                    auto const j = static_cast<unsigned>(_j);
                    if (!system.is_empty(j)
                        && system.get_magnetic_cluster(i)
                               == system.get_magnetic_cluster(j)) {
                        if (_g_to_l[i] == empty) {
                            _g_to_l[i] = static_cast<unsigned>(_l_to_g.size());
                            _l_to_g.push_back(i);
                        }
                        if (j < i) {
                            TCM_ASSERT(
                                _g_to_l[j] != empty && _g_to_l[i] != empty, "");
                            _coeffs.push_back(
                                ::TCM_NAMESPACE::coupling(lattice, j, i));
                            _edges.emplace_back(_g_to_l[j], _g_to_l[i]);
                        }
                    }
                }
            }
        }
    }

    _sa_buffers.resize(_l_to_g.size());
    auto initial = _sa_buffers.current();
    for (auto const i : _l_to_g) {
        initial[i] = system.get_angle(i);
    }
}

template <class System>
auto thermaliser_t::store(gsl::span<float const>      best,
                          magnetic_cluster_t<System>& cluster) -> void
{
    auto const& system = cluster.system();
    for (auto i = 0u; i < _l_to_g.size(); ++i) {
        system.set_angle(_l_to_g[i], angle_t{best[i]});
    }
}

template <class System>
auto thermaliser_t::store(gsl::span<float const> best, System& system) -> void
{
    for (auto i = 0u; i < _l_to_g.size(); ++i) {
        system.set_angle(_l_to_g[i], angle_t{best[i]});
    }
}

#if 0
template <class EnergyFn, class InitFn>
inline auto optimise(EnergyFn&& energy, sa_pars_t const& parameters,
                     sa_buffers_t& buffers, VSLStreamStatePtr stream,
                     InitFn&& init_fn)
{
    std::forward<InitFn>(init_fn)(buffers.current());
    sa_chain_t chain{buffers, parameters, std::forward<EnergyFn>(energy),
                     detail::to_0_2pi_fn{}, stream};
    for (auto i = 0u; i < parameters.n; ++i) {
        chain();
    }
    return chain.best();
}
#endif

TCM_NAMESPACE_END


#include "camera.h"

#include <atomic>
#include <glm/common.hpp>
#include <memory>
#include <boost/asio/executor.hpp>
#include <boost/asio/post.hpp>
#include <glm/glm.hpp>
#include "light.h"
#include "scene.h"
#include "ray.h"
#include "object.h"

namespace {

glm::vec3 shadow(const Scene &scene, const Ray &r,
				 const PointLight *source) noexcept {
	Intersection i;
	if (!scene.intersect(r, i))
		return {1, 1, 1};
	if (i.object == source)
		return {1, 1, 1};
	if (glm::length(i.object->transmissivity) < 1e-5)
		return {0, 0, 0};
	return i.object->transmissivity
			* shadow(scene, Ray{r.at(i.t) + 1e-5f*r.dir, r.dir}, source);
}

glm::vec3 shade(const Object &o, float shininess,
                const Scene &scene,
                glm::vec3 Q, glm::vec3 N, glm::vec3 V) noexcept {
    glm::vec3 I = o.emissive;                            // emissive
    I += scene.ambiant * o.color;                        // ambient
    for (const PointLight *pl : scene.point_lights) {
		glm::vec3 diff = pl->pos() - Q;
		glm::vec3 L = glm::normalize(diff);
		float dis = glm::length(diff);
		if (glm::dot(N, L) >= 0) {
			float atten = 1.0f / (dis * dis);
			glm::vec3 I_l = atten * pl->emissive
					* shadow(scene, Ray(Q + 1e-5f*L, L), pl);
			glm::vec3 H = glm::normalize(V + L);
			I += glm::dot(N, L) * I_l * o.color;          // diffuse
			I += glm::pow(glm::max(0.0f, glm::dot(H, N)),
						  shininess) * I_l * o.specular;  // specular
		}
	}
	for (const auto &dl : scene.dir_lights) {
		glm::vec3 L = -glm::normalize(dl->direction);
		if (glm::dot(N, L) >= 0) {
			glm::vec3 I_l = dl->color * shadow(scene, Ray(Q + 1e-5f*L, L), nullptr);
			glm::vec3 H = glm::normalize(V + L);
			I += glm::dot(N, L) * I_l * o.color;          // diffuse
			I += glm::pow(glm::max(0.0f, glm::dot(H, N)),
						  shininess) * I_l * o.specular;  // specular
		}
	}
    return I;
}

glm::vec3 trace(const Scene &scene,
				const Ray &r,
				int32_t depth) noexcept {
	if (depth == 0)
		return {0, 0, 0};
	Intersection i;
	if (scene.intersect(r, i)) {
		const Object &o = *i.object;
		const float shininess = i.object->shininess;
		const glm::vec3 Q = r.at(i.t);
		const glm::vec3 D = r.dir;
		const bool exit = glm::dot(D, i.normal) > 0.0;
		const glm::vec3 N = exit ? -i.normal : i.normal;

		// direct
		const glm::vec3 I_direct = shade(o, shininess, scene, Q, N, -D);

		const float d_dot_n = glm::dot(D, N);

		// reflection
		glm::vec3 I_reflected = {0, 0, 0};
		if (glm::length(o.specular) > 1e-5) {
			glm::vec3 R = D - 2.0f * d_dot_n * N;
			I_reflected = o.specular * trace(scene, Ray(Q, R), depth - 1);
		}

		// refraction
		glm::vec3 I_refracted = {0, 0, 0};
		if (glm::length(o.transmissivity) > 1e-5) {
			float ni, nt;
			if (exit) {
				// exiting object
				ni = o.ior;
				nt = 1.00029;
			} else {
				// entering object
				ni = 1.00029;
				nt = o.ior;
			}
			const auto sqr = [](float x) { return x * x; };
			float inside_sqrt = 1.0f -
					sqr(ni) / sqr(nt) * (1.0f - sqr(d_dot_n));
			if (inside_sqrt > 0.0f) {
				// not total internal reflection
				glm::vec3 T =
						(ni / nt) * (D - d_dot_n * N)
						- (glm::sqrt(inside_sqrt) * N);
				I_refracted = o.transmissivity
						* trace(scene, Ray(Q, T), depth - 1);
			}
		}

		return glm::clamp(I_direct + I_reflected + I_refracted, 0.0f, 1.0f);
	} else if (scene.envmap != nullptr) {
		return scene.envmap->get_color(r.dir);
	} else {
		return {0, 0, 0};
	}
}

template<typename It, typename F>
void parallelForEach(boost::asio::executor exec, It begin, It end, F f) {
	if (begin + 1 == end) {
		f(begin);
	} else {
		It mid = begin + (end - begin) / 2;
		boost::asio::post(
				exec,
				[=]() { parallelForEach(exec, begin, mid, f); });
		parallelForEach(exec, mid, end, f);
	}
}

}  // namespace

void Camera::trace_rays(
		const Scene &root,
		std::function<void(std::vector<glm::vec3>)> completion) const noexcept {
	auto results = std::make_shared<std::vector<glm::vec3>>(rays.size());
	auto finished = std::make_shared<std::atomic_int32_t>(0);
		auto task = [this, finished, results, &root, completion](int32_t i) {
			(*results)[i] = trace(
					root,
					Ray{inner_to_world({0, 0, 0, 1}), inner_to_world({rays[i], 0})},
					depth);
			if (finished->fetch_add(1, std::memory_order_relaxed) + 1
					== rays.size())
				completion(*results);
		};
	parallelForEach<int32_t>(exec_, 0, rays.size(), task);
}


/*
 * Copyright (c) 2017, Doug Smith, KEBA Corp
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 *  Created on: Aug 25, 2017
 *      Author: Doug Smith
 */

#include "rmi_driver/util.h"

#include <boost/algorithm/string.hpp>
#include <boost/tokenizer.hpp>

#include <boost/spirit/include/qi.hpp>

namespace rmi_driver
{
namespace util
{
double radToDeg(double rad)
{
  return rad * (180.0 / M_PI);
}

double degToRad(double degrees)
{
  return degrees * (M_PI / 180.0);
}

std::string floatToStringNoTrailing(float fval, int precision)
{
  // Convert the value to a string with fixed precision
  std::ostringstream oss;
  oss << std::setprecision(precision) << std::fixed;
  oss << fval;
  auto str = oss.str();
  // Remove extra trailing 0s or .
  boost::trim_right_if(str, boost::is_any_of("0"));
  boost::trim_right_if(str, boost::is_any_of("."));

  return str;
}

using boost::spirit::qi::double_;
using boost::spirit::qi::parse;

namespace qi = boost::spirit::qi;

std::vector<double> stringToDoubleVec(const std::string& s)
{
  std::vector<double> doubleVec;

  // So, boost::split is the least efficient thing ever.  Use qi instead.

  // std::vector<std::string> strVec;

  //
  //  // Split the string at spaces into a vector of strings
  //  // boost::split(strVec, s, boost::is_any_of(" "), boost::token_compress_on);
  //  boost::split(strVec, s, boost::is_space(), boost::token_compress_on);
  //  t.stop();
  //
  //  auto time_total = t.getTime();
  //  std::cout << "duration boost::split " << t.getTime() << " ";
  //
  //  t.reset();
  //  t.start();
  //  // Insert the double value of each entry
  //
  //  doubleVec.reserve(10);
  //  std::transform(strVec.begin(), strVec.end(), std::back_inserter(doubleVec),
  //                 [](const std::string& val) { return boost::lexical_cast<double>(val); });
  //  t.stop();
  //
  //  time_total += t.getTime();
  //  std::cout << "duration transform: " << t.getTime() << " total: " << time_total << " (" << s << ")" << std::endl;
  //
  //  // Clock::time_point t0 = Clock::now();
  //
  //  {
  //    std::vector<double> doubleVec;
  //    t.reset();
  //    t.start();
  //    auto s_trim = boost::trim_copy(s);
  //    doubleVec.reserve(10);
  //    std::stringstream ss(s_trim);
  //
  //    double d;
  //    while (ss >> d)
  //      doubleVec.push_back(d);
  //
  //    t.stop();
  //    std::cout << "duration ss: " << t.getTime() << std::endl;
  //  }

  std::string::const_iterator start = s.begin();
  std::string::const_iterator end = s.end();

  std::string s_trim;

  // If it starts or ends with whitespace, trim it
  if (*start == ' ' || *end == ' ')
  {
    s_trim = boost::trim_copy(s);
    start = s_trim.begin();
    end = s_trim.end();
  }

  bool r;

  r = qi::parse(start, end, (double_ % ' '), doubleVec);

  return doubleVec;
}

bool usedAndNotEqualIdx(int entry_index, const std::vector<float>& sample, const std::vector<float>& msg)
{
  // If there is a message then there must be a valid index
  // @todo This seems safer, but then it's not "ignoring" the entry.
  //  if (msg.size() > 0)
  //  {
  //    if (entry_index < 0 || entry_index >= sample.size())
  //      return true;
  //  }

  // Check if the sample has anything
  if (sample.size() == 0)
    return false;

  // Make sure the index is valid
  if (entry_index < 0 || entry_index >= sample.size())
    return true;

  // Get the value stored in sample[entry_index] and compare it with the size of msg
  auto expected_size = std::lround(sample[entry_index]);

  return msg.size() != expected_size;
}

bool usedAndNotEqual(const std::string& sample, const std::string& msg, int* entry_index)
{
  if (entry_index)
    *entry_index = 0;

  if (sample.length() <= 0)
    return false;

  boost::char_separator<char> sep("|", "", boost::keep_empty_tokens);
  boost::tokenizer<boost::char_separator<char>, std::string::const_iterator, std::string> tok(sample, sep);

  // Eclipse complains about auto&& with a tokenizer?
  for (const std::string& entry : tok)
  {
    // Found an entry that matches.
    if (entry.compare(msg) == 0)
      return false;

    if (entry_index)
      (*entry_index)++;
  }

  // A sample was given but no match was found.
  if (entry_index)
    *entry_index = -1;  // Redundant since commandhandler matching will stop after the 1st fail?

  return true;
}

}  // namespace util

}  // namespace rmi_driver


/*
 * Copyright (c) 2011, Mattia Penati <mattia.penati@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *     * Neither the name of the Politecnico di Milano nor the names of its
 *       contributors may be used to endorse or promote products derived from
 *       this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef AMA_TENSOR_DETAIL_TENSOR_CWISE_BINARY_HPP
#define AMA_TENSOR_DETAIL_TENSOR_CWISE_BINARY_HPP 1

#include <ama/tensor/detail/is_same_tensor.hpp>
#include <ama/tensor/detail/tensor_base.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/if.hpp>

namespace ama
{
  namespace tensor_
  {

    /* this class represent a unary component-wise operation */
    template <typename LEFT, typename RIGHT, typename OPERATOR> class tensor_cwise_binary;

    /* specialization of tensor_traits */
    template <typename LEFT, typename RIGHT, typename OPERATOR>
    struct tensor_traits< tensor_cwise_binary<LEFT, RIGHT, OPERATOR> >
    {
      typedef typename OPERATOR::result_type value_type;

      typedef typename LEFT::dimension_type dimension_type;

      typedef typename LEFT::controvariant_type controvariant_type;
      typedef typename LEFT::covariant_type covariant_type;

      typedef ::boost::mpl::false_ is_assignable;
      typedef ::boost::mpl::true_ is_temporary;
    };

    /* class declaration */
    template <typename LEFT, typename RIGHT, typename OPERATOR>
    class tensor_cwise_binary:
        public tensor_base< tensor_cwise_binary<LEFT, RIGHT, OPERATOR> >
    {
      BOOST_MPL_ASSERT_MSG(
            (tensor_::is_same_tensor<LEFT, RIGHT>::value)
          , COMPONENT_WISE_BINARY_OPERATION_BETWEEN_DIFFERENT_TENSORS_ARE_NOT_ALLOWED
          , (LEFT, RIGHT));

    protected:
      typedef tensor_base< tensor_cwise_binary<LEFT, RIGHT, OPERATOR> > base_type;
      typedef tensor_cwise_binary<LEFT, RIGHT, OPERATOR> derived_type;

    protected:
      typedef LEFT left_operand_type;
      typedef RIGHT right_operand_type;
      typedef OPERATOR operator_type;

    public:
      typedef typename base_type::value_type value_type;

    public:
      /* constructor */
      tensor_cwise_binary(left_operand_type const & left_operand,
                          right_operand_type const & right_operand,
                          operator_type const & op = operator_type())
          : m_left_operand(left_operand),
            m_right_operand(right_operand),
            m_operator(op) { }

    public:
      /* retrieve the value */
      template <typename ILIST>
      value_type at() const
      {
        return m_operator(
                 m_left_operand.template at<ILIST>(),
                 m_right_operand.template at<ILIST>());
      }

    protected:
      /* if the operand is temporary we save a copy, otherwise a reference */
      typedef typename LEFT::is_temporary left_operand_is_temporary;
      typedef typename ::boost::mpl::if_<
            left_operand_is_temporary
          , left_operand_type const
          , left_operand_type const &
          >::type const_left_operand_type;

      /* if the operand is temporary we save a copy, otherwise a reference */
      typedef typename RIGHT::is_temporary right_operand_is_temporary;
      typedef typename ::boost::mpl::if_<
            right_operand_is_temporary
          , right_operand_type const
          , right_operand_type const &
          >::type const_right_operand_type;

      /* members */
      const_left_operand_type m_left_operand;
      const_right_operand_type m_right_operand;
      operator_type const m_operator;
    };

  }
}

#endif /* AMA_TENSOR_DETAIL_TENSOR_CWISE_BINARY_HPP */


/**  \file ublas_elementwise.hpp \brief Elementwise operations for ublas vector/matrix */
/*
-------------------------------------------------------------------------
   This file is part of BayesOpt, an efficient C++ library for
   Bayesian optimization.

   Copyright (C) 2011-2015 Ruben Martinez-Cantin <rmcantin@unizar.es>

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

   BayesOpt 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 Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with BayesOpt.  If not, see <http://www.gnu.org/licenses/>.
------------------------------------------------------------------------
*/

#ifndef  _ELEMENTWISE_UBLAS_HPP_
#define  _ELEMENTWISE_UBLAS_HPP_

#include <algorithm>
#include <boost/numeric/ublas/vector.hpp>

namespace bayesopt
{
  namespace utils
  {

    /**
     * Computes the elementwise product of two vectors or matrices.
     *             c_i = a_i * b_i
     */
    template <class v1, class v2>
    v1 ublas_elementwise_prod(const v1& a, const v2& b)
    {
      typedef typename v1::value_type D;
      v1 c(a.size());
      std::transform(a.begin(),a.end(),b.begin(),c.begin(),std::multiplies<D>());
      return c;
    }

    /**
     * Computes the elementwise division of two vectors or matrices.
     *            c_i = a_i / b_i
     */
    template <class v1, class v2>
    v1 ublas_elementwise_div(const v1& a, const v2& b)
    {
      typedef typename v1::value_type D;
      v1 c(a.size());
      std::transform(a.begin(),a.end(),b.begin(),c.begin(),std::divides<D>());
      return c;
    }

  } //namespace utils
} //namespace bayesopt

#endif


// ====================================================================
// This file is part of FlexibleSUSY.
//
// FlexibleSUSY is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// FlexibleSUSY 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
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with FlexibleSUSY.  If not, see
// <http://www.gnu.org/licenses/>.
// ====================================================================

/**
 * @file standard_model.hpp
 *
 * @brief contains class for Standard model running and self-energies
 *
 */

#ifndef STANDARD_MODEL_H
#define STANDARD_MODEL_H

#include "betafunction.hpp"
#include "standard_model_physical.hpp"
#include "two_loop_corrections.hpp"
#include "problems.hpp"
#include "config.h"
#include "lowe.h"
#include "physical_input.hpp"

#include <iosfwd>
#include <string>

#ifdef ENABLE_THREADS
#include <mutex>
#endif

#include <gsl/gsl_vector.h>
#include <Eigen/Core>

namespace flexiblesusy {

class EWSB_solver;

namespace standard_model_info {

enum Particles : unsigned {VG, Hp, Fv, Ah, hh, VP, VZ, Fd, Fu, Fe, VWp,
   NUMBER_OF_PARTICLES};

enum Parameters : unsigned {g1, g2, g3, Lambdax, Yu0_0, Yu0_1, Yu0_2, Yu1_0,
   Yu1_1, Yu1_2, Yu2_0, Yu2_1, Yu2_2, Yd0_0, Yd0_1, Yd0_2, Yd1_0, Yd1_1, Yd1_2
   , Yd2_0, Yd2_1, Yd2_2, Ye0_0, Ye0_1, Ye0_2, Ye1_0, Ye1_1, Ye1_2, Ye2_0,
   Ye2_1, Ye2_2, mu2, v, NUMBER_OF_PARAMETERS};

extern const char* particle_names[NUMBER_OF_PARTICLES];

extern const char* parameter_names[NUMBER_OF_PARAMETERS];

} // namespace standard_model_info

namespace standard_model {

/**
 * @class Standard_model
 * @brief model class with routines for SM running and self-energies
 */
class Standard_model : public Beta_function {
public:

   Standard_model();
   Standard_model(double scale_, double loops_, double thresholds_
   , double g1_, double g2_, double g3_, double Lambdax_, const Eigen::Matrix<
   double,3,3>& Yu_, const Eigen::Matrix<double,3,3>& Yd_, const Eigen::Matrix<
   double,3,3>& Ye_, double mu2_, double v_);

   virtual ~Standard_model();

   /// number of EWSB equations
   static const std::size_t number_of_ewsb_equations = 1;

   void calculate_DRbar_masses();
   void calculate_DRbar_parameters();
   void calculate_pole_masses();
   void check_pole_masses_for_tachyons();
   void do_force_output(bool);
   bool do_force_output() const;
   void set_ewsb_iteration_precision(double);
   void set_ewsb_loop_order(unsigned);
   void set_two_loop_corrections(const Two_loop_corrections&);
   const Two_loop_corrections& get_two_loop_corrections() const;
   void set_number_of_ewsb_iterations(std::size_t);
   void set_number_of_mass_iterations(std::size_t);
   std::size_t get_number_of_ewsb_iterations() const;
   std::size_t get_number_of_mass_iterations() const;
   void set_pole_mass_loop_order(unsigned);
   unsigned get_pole_mass_loop_order() const;
   void set_physical(const Standard_model_physical&);
   double get_ewsb_iteration_precision() const;
   double get_ewsb_loop_order() const;
   const Standard_model_physical& get_physical() const;
   Standard_model_physical& get_physical();
   const Problems<standard_model_info::NUMBER_OF_PARTICLES>& get_problems() const;
   Problems<standard_model_info::NUMBER_OF_PARTICLES>& get_problems();
   int solve_ewsb_tree_level();
   int solve_ewsb_one_loop();
   int solve_ewsb();            ///< solve EWSB at ewsb_loop_order level

   virtual Eigen::ArrayXd beta() const;
   virtual Eigen::ArrayXd get() const;
   void print(std::ostream&) const;
   virtual void set(const Eigen::ArrayXd&);

   Standard_model calc_beta() const;
   void clear();
   void clear_running_parameters();
   void clear_DRbar_parameters();
   void clear_problems();

   void calculate_spectrum();
   std::string name() const;
   void run_to(double scale, double eps = -1.0);
   void set_precision(double);
   double get_precision() const;

   void set_low_energy_data(const softsusy::QedQcd& qedqcd_) { qedqcd = qedqcd_; }
   const softsusy::QedQcd& get_low_energy_data() const { return qedqcd; }
   softsusy::QedQcd& get_low_energy_data() { return qedqcd; }
   void set_physical_input(const Physical_input& input_) { input = input_; }
   const Physical_input& get_physical_input() const { return input; }
   Physical_input& get_physical_input() { return input; }

   void initialise_from_input();

   void set_g1(double g1_) { g1 = g1_; }
   void set_g2(double g2_) { g2 = g2_; }
   void set_g3(double g3_) { g3 = g3_; }
   void set_Lambdax(double Lambdax_) { Lambdax = Lambdax_; }
   void set_Yu(const Eigen::Matrix<double,3,3>& Yu_) { Yu = Yu_; }
   void set_Yu(int i, int k, double value) { Yu(i,k) = value; }
   void set_Yd(const Eigen::Matrix<double,3,3>& Yd_) { Yd = Yd_; }
   void set_Yd(int i, int k, double value) { Yd(i,k) = value; }
   void set_Ye(const Eigen::Matrix<double,3,3>& Ye_) { Ye = Ye_; }
   void set_Ye(int i, int k, double value) { Ye(i,k) = value; }
   void set_mu2(double mu2_) { mu2 = mu2_; }
   void set_v(double v_) { v = v_; }

   double get_g1() const { return g1; }
   double get_g2() const { return g2; }
   double get_g3() const { return g3; }
   double get_Lambdax() const { return Lambdax; }
   const Eigen::Matrix<double,3,3>& get_Yu() const { return Yu; }
   double get_Yu(int i, int k) const { return Yu(i,k); }
   const Eigen::Matrix<double,3,3>& get_Yd() const { return Yd; }
   double get_Yd(int i, int k) const { return Yd(i,k); }
   const Eigen::Matrix<double,3,3>& get_Ye() const { return Ye; }
   double get_Ye(int i, int k) const { return Ye(i,k); }
   double get_mu2() const { return mu2; }
   double get_v() const { return v; }

   double get_MVG() const { return MVG; }
   double get_MHp() const { return MHp; }
   const Eigen::Array<double,3,1>& get_MFv() const { return MFv; }
   double get_MFv(int i) const { return MFv(i); }
   double get_MAh() const { return MAh; }
   double get_Mhh() const { return Mhh; }
   double get_MVP() const { return MVP; }
   double get_MVZ() const { return MVZ; }
   const Eigen::Array<double,3,1>& get_MFd() const { return MFd; }
   double get_MFd(int i) const { return MFd(i); }
   const Eigen::Array<double,3,1>& get_MFu() const { return MFu; }
   double get_MFu(int i) const { return MFu(i); }
   const Eigen::Array<double,3,1>& get_MFe() const { return MFe; }
   double get_MFe(int i) const { return MFe(i); }
   double get_MVWp() const { return MVWp; }
   const Eigen::Array<double,2,1>& get_MVPVZ() const { return MVPVZ; }
   double get_MVPVZ(int i) const { return MVPVZ(i); }

   const Eigen::Matrix<std::complex<double>,3,3>& get_Vd() const { return Vd; }
   const std::complex<double>& get_Vd(int i, int k) const { return Vd(i,k); }
   const Eigen::Matrix<std::complex<double>,3,3>& get_Ud() const { return Ud; }
   const std::complex<double>& get_Ud(int i, int k) const { return Ud(i,k); }
   const Eigen::Matrix<std::complex<double>,3,3>& get_Vu() const { return Vu; }
   const std::complex<double>& get_Vu(int i, int k) const { return Vu(i,k); }
   const Eigen::Matrix<std::complex<double>,3,3>& get_Uu() const { return Uu; }
   const std::complex<double>& get_Uu(int i, int k) const { return Uu(i,k); }
   const Eigen::Matrix<std::complex<double>,3,3>& get_Ve() const { return Ve; }
   const std::complex<double>& get_Ve(int i, int k) const { return Ve(i,k); }
   const Eigen::Matrix<std::complex<double>,3,3>& get_Ue() const { return Ue; }
   const std::complex<double>& get_Ue(int i, int k) const { return Ue(i,k); }
   const Eigen::Matrix<double,2,2>& get_ZZ() const { return ZZ; }
   double get_ZZ(int i, int k) const { return ZZ(i,k); }

   double get_mass_matrix_VG() const;
   void calculate_MVG();
   double get_mass_matrix_Hp() const;
   void calculate_MHp();
   Eigen::Matrix<double,3,3> get_mass_matrix_Fv() const;
   void calculate_MFv();
   double get_mass_matrix_Ah() const;
   void calculate_MAh();
   double get_mass_matrix_hh() const;
   void calculate_Mhh();
   double get_mass_matrix_VP() const;
   void calculate_MVP();
   double get_mass_matrix_VZ() const;
   void calculate_MVZ();
   Eigen::Matrix<double,3,3> get_mass_matrix_Fd() const;
   void calculate_MFd();
   Eigen::Matrix<double,3,3> get_mass_matrix_Fu() const;
   void calculate_MFu();
   Eigen::Matrix<double,3,3> get_mass_matrix_Fe() const;
   void calculate_MFe();
   double get_mass_matrix_VWp() const;
   void calculate_MVWp();
   Eigen::Matrix<double,2,2> get_mass_matrix_VPVZ() const;
   void calculate_MVPVZ();

   double get_ewsb_eq_hh_1() const;

   double CpconjHpHphh() const;
   double CpconjHpVWpVP() const;
   double CpconjHpVZVWp() const;
   double CpHpgWpCbargZ() const;
   double CpconjHpbargWpCgZ() const;
   double CpHpgZbargWp() const;
   double CpconjHpbargZgWp() const;
   double CpHpconjHpAhAh() const;
   double CpHpconjHphhhh() const;
   double CpHpconjHpconjHpHp() const;
   std::complex<double> CpconjHpVWpAh() const;
   double CpconjHpVWphh() const;
   double CpconjHpVPHp() const;
   double CpconjHpVZHp() const;
   double CpHpconjHpconjVWpVWp() const;
   std::complex<double> CpHpconjHpVZVZ() const;
   std::complex<double> CpconjHpbarFdFuPR(unsigned gI1, unsigned gI2) const;
   std::complex<double> CpconjHpbarFdFuPL(unsigned gI1, unsigned gI2) const;
   double CpconjHpbarFeFvPR(unsigned , unsigned ) const;
   std::complex<double> CpconjHpbarFeFvPL(unsigned gI1, unsigned gI2) const;
   double CpAhhhAh() const;
   std::complex<double> CpAhbargWpgWp() const;
   std::complex<double> CpAhbargWpCgWpC() const;
   double CpAhAhAhAh() const;
   double CpAhAhhhhh() const;
   double CpAhAhconjHpHp() const;
   std::complex<double> CpAhVZhh() const;
   std::complex<double> CpAhconjVWpHp() const;
   double CpAhAhconjVWpVWp() const;
   std::complex<double> CpAhAhVZVZ() const;
   std::complex<double> CpAhbarFdFdPR(unsigned gI1, unsigned gI2) const;
   std::complex<double> CpAhbarFdFdPL(unsigned gI1, unsigned gI2) const;
   std::complex<double> CpAhbarFeFePR(unsigned gI1, unsigned gI2) const;
   std::complex<double> CpAhbarFeFePL(unsigned gI1, unsigned gI2) const;
   std::complex<double> CpAhbarFuFuPR(unsigned gI1, unsigned gI2) const;
   std::complex<double> CpAhbarFuFuPL(unsigned gI1, unsigned gI2) const;
   double CphhAhAh() const;
   double Cphhhhhh() const;
   double CphhVZVZ() const;
   double CphhbargWpgWp() const;
   double CphhbargWpCgWpC() const;
   double CphhbargZgZ() const;
   double CphhconjHpHp() const;
   double CphhconjVWpVWp() const;
   double CphhhhAhAh() const;
   double Cphhhhhhhh() const;
   double CphhhhconjHpHp() const;
   std::complex<double> CphhVZAh() const;
   double CphhconjVWpHp() const;
   double CphhhhconjVWpVWp() const;
   std::complex<double> CphhhhVZVZ() const;
   std::complex<double> CphhbarFdFdPR(unsigned gI1, unsigned gI2) const;
   std::complex<double> CphhbarFdFdPL(unsigned gI1, unsigned gI2) const;
   std::complex<double> CphhbarFeFePR(unsigned gI1, unsigned gI2) const;
   std::complex<double> CphhbarFeFePL(unsigned gI1, unsigned gI2) const;
   std::complex<double> CphhbarFuFuPR(unsigned gI1, unsigned gI2) const;
   std::complex<double> CphhbarFuFuPL(unsigned gI1, unsigned gI2) const;
   std::complex<double> CpVZhhAh() const;
   double CpVZVZhh() const;
   double CpVZbargWpgWp() const;
   double CpVZbargWpCgWpC() const;
   double CpVZconjHpHp() const;
   double CpVZconjVWpHp() const;
   std::complex<double> CpVZVZAhAh() const;
   std::complex<double> CpVZVZhhhh() const;
   std::complex<double> CpVZVZconjHpHp() const;
   double CpVZconjVWpVWp() const;
   double CpVZbarFdFdPL(unsigned gI1, unsigned gI2) const;
   double CpVZbarFdFdPR(unsigned gI1, unsigned gI2) const;
   double CpVZbarFeFePL(unsigned gI1, unsigned gI2) const;
   double CpVZbarFeFePR(unsigned gI1, unsigned gI2) const;
   double CpVZbarFuFuPL(unsigned gI1, unsigned gI2) const;
   double CpVZbarFuFuPR(unsigned gI1, unsigned gI2) const;
   double CpVZbarFvFvPL(unsigned gI1, unsigned gI2) const;
   double CpVZbarFvFvPR(unsigned , unsigned ) const;
   double CpVZVZconjVWpVWp1() const;
   double CpVZVZconjVWpVWp2() const;
   double CpVZVZconjVWpVWp3() const;
   std::complex<double> CpconjVWpHpAh() const;
   double CpconjVWpHphh() const;
   double CpconjVWpVPHp() const;
   double CpconjVWpVWphh() const;
   double CpconjVWpVZHp() const;
   double CpconjVWpbargPgWp() const;
   double CpconjVWpbargWpCgP() const;
   double CpconjVWpbargWpCgZ() const;
   double CpconjVWpbargZgWp() const;
   double CpVWpconjVWpAhAh() const;
   double CpVWpconjVWphhhh() const;
   double CpVWpconjVWpconjHpHp() const;
   double CpconjVWpVWpVP() const;
   double CpconjVWpVZVWp() const;
   std::complex<double> CpconjVWpbarFdFuPL(unsigned gI1, unsigned gI2) const;
   double CpconjVWpbarFdFuPR(unsigned , unsigned ) const;
   std::complex<double> CpconjVWpbarFeFvPL(unsigned gI1, unsigned gI2) const;
   double CpconjVWpbarFeFvPR(unsigned , unsigned ) const;
   double CpVWpconjVWpVPVP1() const;
   double CpVWpconjVWpVPVP2() const;
   double CpVWpconjVWpVPVP3() const;
   double CpVWpconjVWpVZVZ1() const;
   double CpVWpconjVWpVZVZ2() const;
   double CpVWpconjVWpVZVZ3() const;
   double CpVWpconjVWpconjVWpVWp1() const;
   double CpVWpconjVWpconjVWpVWp2() const;
   double CpVWpconjVWpconjVWpVWp3() const;
   std::complex<double> CpbarUFdFdAhPL(unsigned gO2, unsigned gI1) const;
   std::complex<double> CpbarUFdFdAhPR(unsigned gO1, unsigned gI1) const;
   std::complex<double> CpbarUFdhhFdPL(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarUFdhhFdPR(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarUFdVGFdPR(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarUFdVGFdPL(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarUFdVPFdPR(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarUFdVPFdPL(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarUFdVZFdPR(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarUFdVZFdPL(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarUFdconjHpFuPL(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarUFdconjHpFuPR(unsigned gO1, unsigned gI2) const;
   double CpbarUFdconjVWpFuPR(unsigned , unsigned ) const;
   std::complex<double> CpbarUFdconjVWpFuPL(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarUFuFuAhPL(unsigned gO2, unsigned gI1) const;
   std::complex<double> CpbarUFuFuAhPR(unsigned gO1, unsigned gI1) const;
   std::complex<double> CpbarUFuhhFuPL(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarUFuhhFuPR(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarUFuHpFdPL(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarUFuHpFdPR(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarUFuVGFuPR(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarUFuVGFuPL(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarUFuVPFuPR(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarUFuVPFuPL(unsigned gO1, unsigned gI2) const;
   double CpbarUFuVWpFdPR(unsigned , unsigned ) const;
   std::complex<double> CpbarUFuVWpFdPL(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarUFuVZFuPR(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarUFuVZFuPL(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarUFeFeAhPL(unsigned gO2, unsigned gI1) const;
   std::complex<double> CpbarUFeFeAhPR(unsigned gO1, unsigned gI1) const;
   std::complex<double> CpbarUFehhFePL(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarUFehhFePR(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarUFeVPFePR(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarUFeVPFePL(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarUFeVZFePR(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarUFeVZFePL(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarUFeconjHpFvPL(unsigned gO2, unsigned gI2) const;
   double CpbarUFeconjHpFvPR(unsigned , unsigned ) const;
   double CpbarUFeconjVWpFvPR(unsigned , unsigned ) const;
   double CpbarUFeconjVWpFvPL(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarFdFdAhPL(unsigned gO2, unsigned gI1) const;
   std::complex<double> CpbarFdFdAhPR(unsigned gO1, unsigned gI1) const;
   std::complex<double> CpbarFdhhFdPL(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarFdhhFdPR(unsigned gO1, unsigned gI2) const;
   double CpbarFdVZFdPR(unsigned gO2, unsigned gI2) const;
   double CpbarFdVZFdPL(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarFdconjHpFuPL(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarFdconjHpFuPR(unsigned gO1, unsigned gI2) const;
   double CpbarFdconjVWpFuPR(unsigned , unsigned ) const;
   std::complex<double> CpbarFdconjVWpFuPL(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarFeFeAhPL(unsigned gO2, unsigned gI1) const;
   std::complex<double> CpbarFeFeAhPR(unsigned gO1, unsigned gI1) const;
   std::complex<double> CpbarFehhFePL(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarFehhFePR(unsigned gO1, unsigned gI2) const;
   double CpbarFeVZFePR(unsigned gO2, unsigned gI2) const;
   double CpbarFeVZFePL(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarFeconjHpFvPL(unsigned gO2, unsigned gI2) const;
   double CpbarFeconjHpFvPR(unsigned , unsigned ) const;
   double CpbarFeconjVWpFvPR(unsigned , unsigned ) const;
   std::complex<double> CpbarFeconjVWpFvPL(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarFuFuAhPL(unsigned gO2, unsigned gI1) const;
   std::complex<double> CpbarFuFuAhPR(unsigned gO1, unsigned gI1) const;
   std::complex<double> CpbarFuhhFuPL(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarFuhhFuPR(unsigned gO1, unsigned gI2) const;
   std::complex<double> CpbarFuHpFdPL(unsigned gO2, unsigned gI2) const;
   std::complex<double> CpbarFuHpFdPR(unsigned gO1, unsigned gI2) const;
   double CpbarFuVPFuPR(unsigned gO2, unsigned gI2) const;
   double CpbarFuVPFuPL(unsigned gO1, unsigned gI2) const;
   double CpbarFuVWpFdPR(unsigned , unsigned ) const;
   std::complex<double> CpbarFuVWpFdPL(unsigned gO1, unsigned gI2) const;
   double CpbarFuVZFuPR(unsigned gO2, unsigned gI2) const;
   double CpbarFuVZFuPL(unsigned gO1, unsigned gI2) const;
   std::complex<double> self_energy_Hp(double p ) const;
   std::complex<double> self_energy_Ah(double p ) const;
   std::complex<double> self_energy_hh(double p ) const;
   std::complex<double> self_energy_VZ(double p ) const;
   std::complex<double> self_energy_VWp(double p ) const;
   std::complex<double> self_energy_Fd_1(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fd_PR(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fd_PL(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fu_1(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fu_PR(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fu_PL(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fe_1(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fe_PR(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fe_PL(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_VZ_heavy(double p ) const;
   std::complex<double> self_energy_VWp_heavy(double p ) const;
   std::complex<double> self_energy_Fd_1_heavy_rotated(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fd_PR_heavy_rotated(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fd_PL_heavy_rotated(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fe_1_heavy_rotated(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fe_PR_heavy_rotated(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fe_PL_heavy_rotated(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fu_1_heavy_rotated(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fu_PR_heavy_rotated(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fu_PL_heavy_rotated(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fu_1_heavy(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fu_PR_heavy(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> self_energy_Fu_PL_heavy(double p , unsigned gO1, unsigned gO2) const;
   std::complex<double> tadpole_hh() const;

   /// calculates the tadpoles at current loop order
   void tadpole_equations(double[number_of_ewsb_equations]) const;

   void self_energy_hh_2loop(double result[1]) const;

   void calculate_MVG_pole();
   void calculate_MFv_pole();
   void calculate_Mhh_pole();
   void calculate_MVP_pole();
   void calculate_MVZ_pole();
   void calculate_MFd_pole();
   void calculate_MFu_pole();
   void calculate_MFe_pole();
   void calculate_MVWp_pole();
   double calculate_MVWp_pole(double);
   double calculate_MVZ_pole(double);

   double calculate_MFv_DRbar(double, int) const;
   double calculate_MFe_DRbar(double, int) const;
   double calculate_MFu_DRbar(double, int) const;
   double calculate_MFd_DRbar(double, int) const;
   double calculate_MVP_DRbar(double);
   double calculate_MVZ_DRbar(double);
   double calculate_MVWp_DRbar(double);
   double calculate_Mhh_DRbar(double);

   double ThetaW() const;

private:

   static const int numberOfParameters = 33;

   struct Beta_traces {
      double traceYdAdjYd;
      double traceYeAdjYe;
      double traceYuAdjYu;
      double traceYdAdjYdYdAdjYd;
      double traceYeAdjYeYeAdjYe;
      double traceYuAdjYuYuAdjYu;
      double traceYdAdjYuYuAdjYd;
      double traceYdAdjYdYdAdjYdYdAdjYd;
      double traceYdAdjYdYdAdjYuYuAdjYd;
      double traceYdAdjYuYuAdjYdYdAdjYd;
      double traceYdAdjYuYuAdjYuYuAdjYd;
      double traceYeAdjYeYeAdjYeYeAdjYe;
      double traceYuAdjYuYuAdjYuYuAdjYu;
   };
   void calc_beta_traces(Beta_traces&) const;

   double calc_beta_g1_one_loop(const Beta_traces&) const;
   double calc_beta_g1_two_loop(const Beta_traces&) const;
   double calc_beta_g1_three_loop(const Beta_traces&) const;
   double calc_beta_g2_one_loop(const Beta_traces&) const;
   double calc_beta_g2_two_loop(const Beta_traces&) const;
   double calc_beta_g2_three_loop(const Beta_traces&) const;
   double calc_beta_g3_one_loop(const Beta_traces&) const;
   double calc_beta_g3_two_loop(const Beta_traces&) const;
   double calc_beta_g3_three_loop(const Beta_traces&) const;
   double calc_beta_Lambdax_one_loop(const Beta_traces&) const;
   double calc_beta_Lambdax_two_loop(const Beta_traces&) const;
   double calc_beta_Lambdax_three_loop(const Beta_traces&) const;
   Eigen::Matrix<double,3,3> calc_beta_Yu_one_loop(const Beta_traces&) const;
   Eigen::Matrix<double,3,3> calc_beta_Yu_two_loop(const Beta_traces&) const;
   Eigen::Matrix<double,3,3> calc_beta_Yu_three_loop(const Beta_traces&) const;
   Eigen::Matrix<double,3,3> calc_beta_Yd_one_loop(const Beta_traces&) const;
   Eigen::Matrix<double,3,3> calc_beta_Yd_two_loop(const Beta_traces&) const;
   Eigen::Matrix<double,3,3> calc_beta_Yd_three_loop(const Beta_traces&) const;
   Eigen::Matrix<double,3,3> calc_beta_Ye_one_loop(const Beta_traces&) const;
   Eigen::Matrix<double,3,3> calc_beta_Ye_two_loop(const Beta_traces&) const;
   Eigen::Matrix<double,3,3> calc_beta_Ye_three_loop(const Beta_traces&) const;
   double calc_beta_mu2_one_loop(const Beta_traces&) const;
   double calc_beta_mu2_two_loop(const Beta_traces&) const;
   double calc_beta_mu2_three_loop(const Beta_traces&) const;
   double calc_beta_v_one_loop(const Beta_traces&) const;
   double calc_beta_v_two_loop(const Beta_traces&) const;
   double calc_beta_v_three_loop(const Beta_traces&) const;

   struct EWSB_args {
      Standard_model* model;
      unsigned ewsb_loop_order;
   };

#ifdef ENABLE_THREADS
   struct Thread {
      typedef void(Standard_model::*Memfun_t)();
      Standard_model* model;
      Memfun_t fun;

      Thread(Standard_model* model_, Memfun_t fun_)
         : model(model_), fun(fun_) {}
      void operator()() {
         try {
            (model->*fun)();
         } catch (...) {
            model->thread_exception = std::current_exception();
         }
      }
   };
#endif

   std::size_t number_of_ewsb_iterations;
   std::size_t number_of_mass_iterations;
   unsigned ewsb_loop_order;
   unsigned pole_mass_loop_order;
   bool force_output;             ///< switch to force output of pole masses
   double precision;              ///< RG running precision
   double ewsb_iteration_precision;
   Standard_model_physical physical; ///< contains the pole masses and mixings
   Problems<standard_model_info::NUMBER_OF_PARTICLES> problems;
   Two_loop_corrections two_loop_corrections; ///< used 2-loop corrections
   softsusy::QedQcd qedqcd;
   Physical_input input;
#ifdef ENABLE_THREADS
   std::exception_ptr thread_exception;
   static std::mutex mtx_fortran; /// locks fortran functions
#endif

   int solve_ewsb_iteratively();
   int solve_ewsb_iteratively(unsigned);
   int solve_ewsb_iteratively_with(EWSB_solver*, const double[number_of_ewsb_equations]);
   void ewsb_initial_guess(double[number_of_ewsb_equations]);
   int ewsb_step(double[number_of_ewsb_equations]) const;
   static int ewsb_step(const gsl_vector*, void*, gsl_vector*);
   static int tadpole_equations(const gsl_vector*, void*, gsl_vector*);
   void copy_DRbar_masses_to_pole_masses();

   void initial_guess_for_parameters();
   void calculate_Yu_DRbar();
   void calculate_Yd_DRbar();
   void calculate_Ye_DRbar();
   void calculate_Lambdax_DRbar();
   double calculate_delta_alpha_em(double alphaEm) const;
   double calculate_delta_alpha_s(double alphaS) const;
   double calculate_theta_w(double alpha_em_drbar);
   void recalculate_mw_pole();
   bool check_convergence(const Standard_model& old) const;

   // Passarino-Veltman loop functions
   double A0(double) const;
   double B0(double, double, double) const;
   double B1(double, double, double) const;
   double B00(double, double, double) const;
   double B22(double, double, double) const;
   double H0(double, double, double) const;
   double F0(double, double, double) const;
   double G0(double, double, double) const;

   // Running parameters
   double g1;
   double g2;
   double g3;
   double Lambdax;
   Eigen::Matrix<double,3,3> Yu;
   Eigen::Matrix<double,3,3> Yd;
   Eigen::Matrix<double,3,3> Ye;
   double mu2;
   double v;

   // DR-bar masses
   double MVG;
   double MHp;
   Eigen::Array<double,3,1> MFv;
   double MAh;
   double Mhh;
   double MVP;
   double MVZ;
   Eigen::Array<double,3,1> MFd;
   Eigen::Array<double,3,1> MFu;
   Eigen::Array<double,3,1> MFe;
   double MVWp;
   Eigen::Array<double,2,1> MVPVZ;

   // DR-bar mixing matrices
   Eigen::Matrix<std::complex<double>,3,3> Vd;
   Eigen::Matrix<std::complex<double>,3,3> Ud;
   Eigen::Matrix<std::complex<double>,3,3> Vu;
   Eigen::Matrix<std::complex<double>,3,3> Uu;
   Eigen::Matrix<std::complex<double>,3,3> Ve;
   Eigen::Matrix<std::complex<double>,3,3> Ue;
   Eigen::Matrix<double,2,2> ZZ;

};

std::ostream& operator<<(std::ostream&, const Standard_model&);

} // namespace standard_model

} // namespace flexiblesusy

#endif


/*
 *            Copyright 2009-2020 The VOTCA Development Team
 *                       (http://www.votca.org)
 *
 *      Licensed under the Apache License, Version 2.0 (the "License")
 *
 * You may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *              http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

// Third party includes
#include <boost/format.hpp>

// VOTCA includes
#include <votca/tools/elements.h>

// Local VOTCA includes
#include "votca/xtp/atom.h"
#include "votca/xtp/forces.h"
#include "votca/xtp/statetracker.h"

namespace votca {
namespace xtp {

using std::flush;
void Forces::Initialize(tools::Property& options) {

  std::vector<std::string> choices = {"forward", "central"};
  _force_method =
      options.ifExistsAndinListReturnElseThrowRuntimeError<std::string>(
          ".method", choices);

  _displacement = options.ifExistsReturnElseReturnDefault<double>(
      ".displacement", 0.001);  // Angstrom
  _displacement *= tools::conv::ang2bohr;

  _remove_total_force = options.ifExistsReturnElseReturnDefault<bool>(
      ".CoMforce_removal", _remove_total_force);
  return;
}

void Forces::Calculate(const Orbitals& orbitals) {

  Index natoms = orbitals.QMAtoms().size();
  _forces = Eigen::MatrixX3d::Zero(natoms, 3);

  for (Index atom_index = 0; atom_index < natoms; atom_index++) {

    XTP_LOG(Log::debug, *_pLog)
        << "FORCES--DEBUG working on atom " << atom_index << flush;
    Eigen::Vector3d atom_force = Eigen::Vector3d::Zero();
    // Calculate Force on this atom
    if (_force_method == "forward") {
      atom_force = NumForceForward(orbitals, atom_index);
    }
    if (_force_method == "central") {
      atom_force = NumForceCentral(orbitals, atom_index);
    }
    _forces.row(atom_index) = atom_force.transpose();
  }
  if (_remove_total_force) {
    RemoveTotalForce();
  }
  return;
}

void Forces::Report() const {

  XTP_LOG(Log::error, *_pLog)
      << (boost::format(" ---- FORCES (Hartree/Bohr)   ")).str() << flush;
  XTP_LOG(Log::error, *_pLog)
      << (boost::format("      %1$s differences   ") % _force_method).str()
      << flush;
  XTP_LOG(Log::error, *_pLog)
      << (boost::format("      displacement %1$1.4f Angstrom   ") %
          (_displacement * tools::conv::bohr2ang))
             .str()
      << flush;
  XTP_LOG(Log::error, *_pLog)
      << (boost::format(" Atom\t x\t  y\t  z ")).str() << flush;

  for (Index i = 0; i < _forces.rows(); i++) {
    XTP_LOG(Log::error, *_pLog)
        << (boost::format("%1$4d    %2$+1.4f  %3$+1.4f  %4$+1.4f") % i %
            _forces(i, 0) % _forces(i, 1) % _forces(i, 2))
               .str()
        << flush;
  }
  return;
}

Eigen::Vector3d Forces::NumForceForward(Orbitals orbitals, Index atom_index) {
  Eigen::Vector3d force = Eigen::Vector3d::Zero();
  // get this atoms's current coordinates
  double energy_center =
      orbitals.getTotalStateEnergy(_tracker.CalcState(orbitals));
  const Eigen::Vector3d current_pos = orbitals.QMAtoms()[atom_index].getPos();
  for (Index i_cart = 0; i_cart < 3; i_cart++) {
    Eigen::Vector3d displacement_vec = Eigen::Vector3d::Zero();
    displacement_vec[i_cart] = _displacement;
    // update the coordinate
    Eigen::Vector3d pos_displaced = current_pos + displacement_vec;
    orbitals.QMAtoms()[atom_index].setPos(pos_displaced);
    _gwbse_engine.ExcitationEnergies(orbitals);
    double energy_displaced =
        orbitals.getTotalStateEnergy(_tracker.CalcState(orbitals));
    force(i_cart) = (energy_center - energy_displaced) / _displacement;
    orbitals.QMAtoms()[atom_index].setPos(
        current_pos);  // restore original coordinate into segment
  }                    // Cartesian directions
  return force;
}

Eigen::Vector3d Forces::NumForceCentral(Orbitals orbitals, Index atom_index) {
  Eigen::Vector3d force = Eigen::Vector3d::Zero();
  const Eigen::Vector3d current_pos = orbitals.QMAtoms()[atom_index].getPos();
  for (Index i_cart = 0; i_cart < 3; i_cart++) {
    XTP_LOG(Log::debug, *_pLog)
        << "FORCES--DEBUG           Cartesian component " << i_cart << flush;
    Eigen::Vector3d displacement_vec = Eigen::Vector3d::Zero();
    displacement_vec[i_cart] = _displacement;
    // update the coordinate
    Eigen::Vector3d pos_displaced = current_pos + displacement_vec;
    orbitals.QMAtoms()[atom_index].setPos(pos_displaced);
    _gwbse_engine.ExcitationEnergies(orbitals);
    double energy_displaced_plus =
        orbitals.getTotalStateEnergy(_tracker.CalcState(orbitals));
    // update the coordinate
    pos_displaced = current_pos - displacement_vec;
    orbitals.QMAtoms()[atom_index].setPos(pos_displaced);
    _gwbse_engine.ExcitationEnergies(orbitals);
    double energy_displaced_minus =
        orbitals.getTotalStateEnergy(_tracker.CalcState(orbitals));
    force(i_cart) =
        0.5 * (energy_displaced_minus - energy_displaced_plus) / _displacement;
    orbitals.QMAtoms()[atom_index].setPos(
        current_pos);  // restore original coordinate into orbital
  }
  return force;
}

void Forces::RemoveTotalForce() {
  Eigen::Vector3d avgtotal_force =
      _forces.colwise().sum() / double(_forces.rows());
  for (Index i_atom = 0; i_atom < _forces.rows(); i_atom++) {
    _forces.row(i_atom) -= avgtotal_force;
  }
  return;
}

}  // namespace xtp
}  // namespace votca


#ifndef SGM_RP_HANSER96_HH_
#define SGM_RP_HANSER96_HH_

#include <boost/graph/adjacency_list.hpp>

#include "sgm/RingPerception.hh"

#include <limits>
#include <set>

namespace sgm {

	 /*! @brief Ring enumeration ala Hanser et al.
	  *
	  * Implementation of the exhaustive ring perception algorithm by
	  * Hanser et al. (1996)
	  *
	  *   A New Algorithm for Exhaustive Ring Perception in a Molecular Graph
	  *   T. Hanser, P. Jauffret, and G. Kaufmann
	  *   J. Chem. Inf. Comput. Sci., 1996, 36, 1146-1152
	  *
	  * @author Martin Mann - 2010 - http://www.bioinf.uni-freiburg.de/~mmann/
	  *
	  */
	class RP_Hanser96 : public RingPerception {
	public:

		typedef std::set< std::pair<size_t,size_t> > BondSet;

		  //! construction
		RP_Hanser96();

		  //! destruction
		virtual ~RP_Hanser96();

		  /*!
		   * Finds ALL rings within the given graph and reports each found
		   * ring to the assigned reporter. The enumeration can be restricted
		   * to rings up to a given ring size, ie. number of nodes per ring.
		   *
		   * @param graph the graph to be analyzed
		   * @param reporter the RingReporter to report all found rings to
		   * @param maxRingSize the maximal size of rings to report
		   * @return the number of all rings within the gaph
		   */
		size_t
		findRings( const Graph_Interface & graph,
					RingReporter & reporter,
					const size_t maxRingSize = std::numeric_limits<size_t>::max());

		  /*!
		   * Identifies all bonds participating in rings and stores the
		   * according vertex index pairs in the provided container.
		   *
		   * @param graph the graph to search for ring bonds
		   * @param ringBonds OUT : the container to fill with the ring bonds.
		   *   NOTE: the container is cleared at the beginning of the call.
		   * @param maxRingSize the maximal size of rings to consider
		   *
		   * @return the number of ring bonds within the graph
		   */
		size_t
		findRingBonds( const Graph_Interface & graph
					, BondSet & ringBonds
					, const size_t maxRingSize = std::numeric_limits<size_t>::max() );

	protected:

		  //! information on the current ring part
		struct RingInfo {
		public:
			RingReporter::RingNodes nodes;
			RingReporter::RingList path;
		};

		  //! The properties available for the nodes of the P-graph
		typedef	boost::property<	boost::vertex_index_t, size_t >
						Graph_NodeProperties;

		  //! The properties available for the edges of the P-graph
		typedef	boost::property<	boost::edge_name_t, RingInfo >
						Graph_EdgeProperties;

		  //! the P-graph type that is used to detect all rings
		typedef boost::adjacency_list<
							boost::multisetS,      			// store edges
							boost::vecS,       				// store vertices
							boost::undirectedS,				// is an undirected graph
							Graph_NodeProperties,  			// index information
							Graph_EdgeProperties   			// ring information
						>
							P_Graph;

	protected:

		  //! the P-graph to be compressed during the ring perception
		P_Graph pGraph;

		  //! the index access for pGraph
		boost::property_map<P_Graph, boost::vertex_index_t>::type pGraphIndex;
		  //! the edge label access for pGraph, i.e. the according RingList
		boost::property_map<P_Graph, boost::edge_name_t>::type pGraphPath;

		  //! the degree of each node in pGraph
		std::vector<size_t> pGraphDegree;
		  //! the nodes to be removed during the ring perception from pGraph
		std::vector<size_t> toRemove;

	protected:

		  /*!
		   * Initializes the pGraph member with the given graph. If direct loops
		   * are found, these are directly reported and counted.
		   *
		   * @param graph the graph to be encoded in the pGraph member
		   * @param reporter the RingReporter to report all rings to
		   * @return the number of rings found
		   */
		size_t
		initializeP_Graph( const Graph_Interface & graph
							, RingReporter& reporter);

		  /*!
		   * Virtually removes the vertex nextToRemove from the pGraph and
		   * reports all found rings to the given reporter.
		   * The removal is only virtual, since the node remains within the
		   * pGraph but all adjacent edges are removed, i.e. it is not
		   * accessible anymore in later iterations. This is to ease the
		   * maintenance of the temporary data structures.
		   *
		   * @param nextToRemove the index of the vertex to be removed
		   * @param graph the graph searched (needed for ring report)
		   * @param reporter the RingReporter to report all rings to
		   * @param maxRingSize the maximal ring size to consider
		   * @return the number of rings found
		   */
		size_t
		remove_vertex( const size_t nextToRemove
						, const Graph_Interface& graph
						, RingReporter& reporter
						, const size_t maxRingSize);

		struct degree_sort {
			const std::vector<size_t> & degree;
			degree_sort( const std::vector<size_t> & degree_ )
			 : degree(degree_)
			{}
			bool operator()(const size_t& i1, const size_t& i2 ) const {
				return degree[i1] > degree[i2];
			}
		};

		/**
		 * Stores the bond information for loops.
		 */
		class LoopBondReporter : public RingReporter {

		protected:

			BondSet & bondSet;

		public:
			  //! construction
			LoopBondReporter( BondSet & bondSet );
			  //! destruction
			virtual ~LoopBondReporter();

			  /*!
			   * Is called to report a ring.
			   * @param graph the graph that contains the ring
			   * @param ringList the ring to report
			   */
			virtual
			void
			reportRing( const Graph_Interface& graph, const RingList & ringList );

		};

	};

} // namespace sgm

#endif /* SGM_RP_HANSER96_HH_ */