// Copyright (c) 2015-2021 Daniel Cooke
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

#include "coalescent_model.hpp"

#include <memory>
#include <cmath>
#include <complex>
#include <numeric>
#include <stdexcept>

#include <boost/math/special_functions/binomial.hpp>

#include "utils/maths.hpp"
#include "utils/mappable_algorithms.hpp"

namespace octopus {

CoalescentModel::CoalescentModel(Haplotype reference, Parameters params,
                                 std::size_t num_haplotyes_hint, CachingStrategy caching)
: reference_ {std::move(reference)}
, reference_repeats_ {}
, indel_heterozygosity_model_ {make_indel_model(reference_, {params.indel_heterozygosity}, reference_repeats_)}
, params_ {params}
, haplotypes_ {}
, caching_ {caching}
, index_cache_ {}
, index_flag_buffer_ {}
, k_indel_zero_result_cache_ {2 * num_haplotyes_hint, std::vector<boost::optional<LogProbability>> {}}
{
    if (params_.snp_heterozygosity <= 0 || params_.indel_heterozygosity <= 0) {
        throw std::domain_error {"CoalescentModel: snp and indel heterozygosity must be > 0"};
    }
    site_buffer1_.reserve(128);
    site_buffer2_.reserve(128);
    if (caching == CachingStrategy::address) {
        difference_address_cache_.reserve(num_haplotyes_hint);
    } else if (caching_ == CachingStrategy::value) {
        difference_value_cache_.reserve(num_haplotyes_hint);
        difference_value_cache_.emplace(std::piecewise_construct,
                                        std::forward_as_tuple(reference_),
                                        std::forward_as_tuple());
    }
    k_indel_pos_result_cache_.reserve(2 * num_haplotyes_hint);
}

void CoalescentModel::set_reference(Haplotype reference)
{
    reference_ = std::move(reference);
    if (caching_ == CachingStrategy::address) {
        difference_address_cache_.clear();
    } else if (caching_ == CachingStrategy::value) {
        difference_value_cache_.clear();
        difference_value_cache_.emplace(std::piecewise_construct,
                                        std::forward_as_tuple(reference_),
                                        std::forward_as_tuple());
    }
}

void CoalescentModel::prime(MappableBlock<Haplotype> haplotypes)
{
    haplotypes_ = std::move(haplotypes);
    index_cache_.assign(haplotypes_.size(), boost::none);
    index_flag_buffer_.assign(haplotypes_.size(), false);
}

void CoalescentModel::unprime() noexcept
{
    haplotypes_.clear();
    haplotypes_.shrink_to_fit();
    index_cache_.clear();
    index_cache_.shrink_to_fit();
    index_flag_buffer_.clear();
    index_flag_buffer_.shrink_to_fit();
}

bool CoalescentModel::is_primed() const noexcept
{
    return !index_cache_.empty();
}

CoalescentModel::LogProbability CoalescentModel::evaluate(const Haplotype& haplotype) const
{
    return evaluate(count_segregating_sites(haplotype));
}

CoalescentModel::LogProbability CoalescentModel::evaluate(const std::vector<unsigned>& haplotype_indices) const
{
    return evaluate(count_segregating_sites(haplotype_indices));
}

namespace {

auto powm1(const unsigned i) noexcept // std::pow(-1, i)
{
    return (i % 2 == 0) ? 1 : -1;
}

template <typename RealType>
auto coalescent_real_space(const unsigned n, const unsigned k, const RealType theta)
{
    RealType result {0};
    for (unsigned i {2}; i <= n; ++i) {
        result += powm1(i) * maths::binomial_coefficient<RealType>(n - 1, i - 1) * ((i - 1) / (theta + i - 1)) * std::pow(theta / (theta + i - 1), k);
    }
    return std::log(result);
}

template <typename ForwardIt>
auto complex_log_sum_exp(ForwardIt first, ForwardIt last)
{
    using ComplexType = typename std::iterator_traits<ForwardIt>::value_type;
    const auto l = [] (const auto& lhs, const auto& rhs) { return lhs.real() < rhs.real(); };
    const auto max = *std::max_element(first, last, l);
    return max + std::log(std::accumulate(first, last, ComplexType {},
                                          [max] (const auto curr, const auto x) { return curr + std::exp(x - max); }));
}

template <typename Container>
auto complex_log_sum_exp(const Container& logs)
{
    return complex_log_sum_exp(std::cbegin(logs), std::cend(logs));
}

template <typename RealType>
auto coalescent_log_space(const unsigned n, const unsigned k, const RealType theta)
{
    std::vector<std::complex<RealType>> tmp(n - 1, std::log(std::complex<RealType> {-1}));
    for (unsigned i {2}; i <= n; ++i) {
        auto& cur = tmp[i - 2];
        cur *= i;
        cur += maths::log_binomial_coefficient<RealType>(n - 1, i - 1);
        cur += std::log((i - 1) / (theta + i - 1));
        cur += k * std::log(theta / (theta + i - 1));
    }
    return complex_log_sum_exp(tmp).real();
}

template <typename RealType>
auto coalescent(const unsigned n, const unsigned k, const RealType theta)
{
    if (n < 30 && k <= 80) {
        auto result = coalescent_real_space(n, k, theta);
        if (std::isnan(result)) {
            result = coalescent_log_space(n, k, theta);
        }
        return result;
    } else {
        return coalescent_log_space(n, k, theta);
    }
}

template <typename RealType>
auto coalescent(const unsigned n, const unsigned k_snp, const unsigned k_indel,
                const RealType theta_snp, const RealType theta_indel)
{
    const auto theta = theta_snp + theta_indel;
    const auto k_tot = k_snp + k_indel;
    auto result = coalescent(n, k_tot, theta);
    result += k_snp * std::log(theta_snp / theta);
    result += k_indel * std::log(theta_indel / theta);
    result += maths::log_binomial_coefficient<RealType>(k_tot, k_snp);
    return result;
}

template <typename RealType, std::size_t N>
auto coalescent(const unsigned n,
                const std::array<unsigned, N>& k,
                const std::array<RealType, N>& theta)
{
    const auto theta_tot = std::accumulate(std::cbegin(theta), std::cend(theta), RealType {0});
    const auto k_tot = std::accumulate(std::cbegin(k), std::cend(k), 0u);
    auto result = coalescent(n, k_tot, theta_tot);
    for (std::size_t i {0}; i < N; ++i) {
        result += k[i] * std::log(theta[i] / theta_tot);
    }
    result += maths::log_multinomial_coefficient<RealType>(k);
    return result;
}

} // namespace

CoalescentModel::LogProbability CoalescentModel::evaluate(const SegregatingSiteCounts& counts) const
{
    if (counts.repeat_indels + counts.complex_indels == 0) {
        return evaluate_no_indels(counts.snps, counts.haplotypes);
    } else {
        const auto indel_heterozygosities = calculate_buffered_indel_heterozygosities();
        const auto repeat_indel_heterozygosity = maths::round_sf(indel_heterozygosities.second, 6);
        const auto complex_indel_heterozygosity = maths::round_sf(indel_heterozygosities.first, 6);
        SegregatingSiteCountsWithIndelHeterozygosities sites {counts, repeat_indel_heterozygosity, complex_indel_heterozygosity};
        auto itr = k_indel_pos_result_cache_.find(sites);
        if (itr != std::cend(k_indel_pos_result_cache_)) {
            return itr->second;
        }
        const std::array<unsigned, 3> site_counts {counts.snps, counts.repeat_indels, counts.complex_indels};
        const std::array<double, 3> site_heterozygosities {params_.snp_heterozygosity, repeat_indel_heterozygosity, complex_indel_heterozygosity};
        const auto result = coalescent(counts.haplotypes, site_counts, site_heterozygosities);
        k_indel_pos_result_cache_.emplace(sites, result);
        return result;
    }
}

CoalescentModel::LogProbability CoalescentModel::evaluate_no_indels(const unsigned k_snp, const unsigned n) const
{
    if (k_indel_zero_result_cache_.size() > n) {
        if (k_indel_zero_result_cache_[n].size() > k_snp) {
            auto& result = k_indel_zero_result_cache_[n][k_snp];
            if (!result) {
                result = coalescent(n, k_snp, 0, params_.snp_heterozygosity, params_.indel_heterozygosity);
            }
            return *result;
        } else {
            k_indel_zero_result_cache_[n].resize(k_snp + 1, boost::none);
        }
    } else {
        k_indel_zero_result_cache_.resize(n + 1);
        k_indel_zero_result_cache_[n].assign(k_snp + 1, boost::none);
    }
    const auto result = coalescent(n, k_snp, 0, params_.snp_heterozygosity, params_.indel_heterozygosity);
    k_indel_zero_result_cache_[n][k_snp] = result;
    return result;
}

void CoalescentModel::fill_site_buffer(const Haplotype& haplotype) const
{
    assert(site_buffer2_.empty());
    site_buffer1_.clear();
    if (caching_ == CachingStrategy::address) {
        fill_site_buffer_from_address_cache(haplotype);
    } else {
        fill_site_buffer_from_value_cache(haplotype);
    }
    site_buffer1_ = std::move(site_buffer2_);
    site_buffer2_.clear();
}

void CoalescentModel::fill_site_buffer_uncached(const Haplotype& haplotype) const
{
    // Although we won't retrieve from the cache, we need to make sure all the variants
    // stay in existence as we populate the buffers by reference.
    auto itr = difference_value_cache_.find(reference_);
    if (itr == std::cend(difference_value_cache_)) {
        itr = difference_value_cache_.emplace(reference_, haplotype.difference(reference_)).first;
    } else {
        itr->second = haplotype.difference(reference_);
    }
    std::set_union(std::begin(site_buffer1_), std::end(site_buffer1_),
                   std::cbegin(itr->second), std::cend(itr->second),
                   std::back_inserter(site_buffer2_));
}

void CoalescentModel::fill_site_buffer_from_value_cache(const Haplotype& haplotype) const
{
    auto itr = difference_value_cache_.find(haplotype);
    if (itr == std::cend(difference_value_cache_)) {
        itr = difference_value_cache_.emplace(haplotype, haplotype.difference(reference_)).first;
    }
    std::set_union(std::begin(site_buffer1_), std::end(site_buffer1_),
                   std::cbegin(itr->second), std::cend(itr->second),
                   std::back_inserter(site_buffer2_));
}

void CoalescentModel::fill_site_buffer_from_address_cache(const Haplotype& haplotype) const
{
    auto itr = difference_address_cache_.find(std::addressof(haplotype));
    if (itr == std::cend(difference_address_cache_)) {
        itr = difference_address_cache_.emplace(std::addressof(haplotype), haplotype.difference(reference_)).first;
    }
    std::set_union(std::begin(site_buffer1_), std::end(site_buffer1_),
                   std::cbegin(itr->second), std::cend(itr->second),
                   std::back_inserter(site_buffer2_));
}

CoalescentModel::SegregatingSiteCounts CoalescentModel::count_segregating_sites(const Haplotype& haplotype) const
{
    fill_site_buffer(haplotype);
    return count_segregating_sites_in_buffer(1);
}

CoalescentModel::SegregatingSiteCounts CoalescentModel::count_segregating_sites_in_buffer(const unsigned num_haplotypes) const
{
    unsigned repeat_indels {0}, complex_indels {0};
    for (const auto& site : site_buffer1_) {
        if (is_indel(site)) {
            if (has_overlapped(reference_repeats_, site.get())) {
                ++repeat_indels;
            } else {
                ++complex_indels;
            }
        }
    }
    const auto num_indels = repeat_indels + complex_indels;
    const auto num_snps = static_cast<unsigned>(site_buffer1_.size()) - num_indels;
    return {num_haplotypes + 1, num_snps, repeat_indels, complex_indels};
}

std::pair<double, double> CoalescentModel::calculate_buffered_indel_heterozygosities() const
{
    boost::optional<double> min_heterozygosity {}, max_heterozygosity {};
    for (const auto& site : site_buffer1_) {
        if (is_indel(site)) {
            auto site_heterozygosity = calculate_heterozygosity(site);
            if (min_heterozygosity) {
                min_heterozygosity = std::min(*min_heterozygosity, site_heterozygosity);
            } else {
                min_heterozygosity = site_heterozygosity;
            }
            if (max_heterozygosity) {
                max_heterozygosity = std::max(*max_heterozygosity, site_heterozygosity);
            } else {
                max_heterozygosity = site_heterozygosity;
            }
        }
    }
    if (!min_heterozygosity) min_heterozygosity = params_.indel_heterozygosity;
    if (!max_heterozygosity) max_heterozygosity = params_.indel_heterozygosity;
    return std::make_pair(*min_heterozygosity, *max_heterozygosity);
}

double CoalescentModel::calculate_heterozygosity(const Variant& indel) const
{
    assert(is_indel(indel));
    const auto offset = static_cast<std::size_t>(begin_distance(reference_, indel));
    return calculate_indel_probability(indel_heterozygosity_model_, offset, indel_size(indel));
}

CoalescentProbabilityGreater::CoalescentProbabilityGreater(CoalescentModel model)
: model_ {std::move(model)}
, buffer_ {}
, cache_ {}
{
    buffer_.reserve(1);
    cache_.reserve(100);
}

bool CoalescentProbabilityGreater::operator()(const Haplotype& lhs, const Haplotype& rhs) const
{
    if (have_same_alleles(lhs, rhs)) return true;
    auto cache_itr = cache_.find(lhs);
    if (cache_itr == std::cend(cache_)) {
        buffer_.assign({lhs});
        cache_itr = cache_.emplace(lhs, model_.evaluate(buffer_)).first;
    }
    const auto lhs_probability = cache_itr->second;
    cache_itr = cache_.find(rhs);
    if (cache_itr == std::cend(cache_)) {
        buffer_.assign({rhs});
        cache_itr = cache_.emplace(rhs, model_.evaluate(buffer_)).first;
    }
    const auto rhs_probability = cache_itr->second;
    return lhs_probability > rhs_probability;
}

bool operator==(const CoalescentModel::SegregatingSiteCounts& lhs, const CoalescentModel::SegregatingSiteCounts& rhs) noexcept
{
    return lhs.haplotypes == rhs.haplotypes && lhs.snps == rhs.snps && lhs.repeat_indels == rhs.repeat_indels && lhs.complex_indels == rhs.complex_indels;
}

std::size_t CoalescentModel::SegregatingSiteCountsHash::operator()(const SegregatingSiteCounts& counts) const noexcept
{
    return boost::hash_value(std::make_tuple(counts.haplotypes, counts.snps, counts.repeat_indels, counts.complex_indels));
}

std::size_t CoalescentModel::SegregatingSiteCountsWithIndelHeterozygositiesHash::operator()(const SegregatingSiteCountsWithIndelHeterozygosities& sites) const noexcept
{
    using boost::hash_combine;
    std::size_t result {};
    hash_combine(result, SegregatingSiteCountsHash{}(sites.counts));
    hash_combine(result, std::hash<decltype(sites.repeat_heterozygosity)>()(sites.repeat_heterozygosity));
    hash_combine(result, std::hash<decltype(sites.complex_heterozygosity)>()(sites.complex_heterozygosity));
    return result;
}

bool CoalescentModel::SegregatingSiteCountsWithIndelHeterozygositiesEqual::operator()(const SegregatingSiteCountsWithIndelHeterozygosities& lhs, const SegregatingSiteCountsWithIndelHeterozygosities& rhs) const noexcept
{
    return lhs.counts == rhs.counts && lhs.repeat_heterozygosity == rhs.repeat_heterozygosity && lhs.complex_heterozygosity == rhs.complex_heterozygosity;
}

} // namespace octopus


/*!@file
 * @copyright This code is licensed under the 3-clause BSD license.
 *   Copyright ETH Zurich, Laboratory of Physical Chemistry, Reiher Group.
 *   See LICENSE.txt for details.
 */

#include "Molassembler/Interpret.h"

#include "Utils/Geometry/AtomCollection.h"
#include "Utils/Bonds/BondOrderCollection.h"

#include "Molassembler/BondOrders.h"
#include "Molassembler/Detail/Cartesian.h"
#include "Molassembler/Graph.h"
#include "Molassembler/Graph/GraphAlgorithms.h"
#include "Molassembler/Graph/PrivateGraph.h"
#include "Molassembler/Molecule.h"
#include "Molassembler/Shapes/ContinuousMeasures.h"
#include "Molassembler/Temple/Adaptors/AllPairs.h"
#include "Molassembler/Temple/Functional.h"
#include "Molassembler/Temple/Optionals.h"

#include <Eigen/Geometry>

namespace Scine {
namespace Molassembler {
namespace Interpret {
namespace {

Utils::PositionCollection paste(const std::vector<Utils::Position>& positions) {
  const unsigned N = positions.size();
  auto matrix = Utils::PositionCollection(N, 3);
  for(unsigned i = 0; i < N; ++i) {
    matrix.row(i) = positions.at(i);
  }
  return matrix;
}

double vectorAngle(const Eigen::Vector3d& a, const Eigen::Vector3d& b) {
  return std::acos(
    a.dot(b) / (
      a.norm() * b.norm()
    )
  );
}

struct HapticPlaneGeometry {
  double angle;
  double rmsd;
};

HapticPlaneGeometry hapticPlaneGeometry(
  const std::vector<Utils::Position>& positions,
  const AtomIndex v,
  const std::vector<AtomIndex>& site
) {
  const unsigned siteSize = site.size();

  assert(siteSize >= 1 && "Passed hapticPlaneGeometry a non-haptic site!");

  Eigen::Vector3d siteCentroid = Eigen::Vector3d::Zero();
  for(AtomIndex siteAtom : site) {
    siteCentroid += positions.at(siteAtom);
  }
  siteCentroid /= siteSize;

  if(siteSize == 2) {
    const double frontAngle = Cartesian::angle(
      positions.at(v),
      siteCentroid,
      positions.at(site.front())
    );
    const double backAngle = Cartesian::angle(
      positions.at(v),
      siteCentroid,
      positions.at(site.back())
    );

    return HapticPlaneGeometry {
      M_PI / 2.0 - std::min(frontAngle, backAngle),
      0.0
    };
  }

  const Eigen::Vector3d centroidAxis = siteCentroid - positions.at(v).transpose();

  Utils::PositionCollection hapticAtoms(siteSize, 3);
  for(unsigned i = 0; i < siteSize; ++i) {
    hapticAtoms.row(i) = positions.at(site.at(i));
  }
  const auto plane = Cartesian::planeOfBestFit(hapticAtoms);
  const double firstAngle = vectorAngle(plane.normal(), centroidAxis);
  const double secondAngle = vectorAngle(plane.normal(), -centroidAxis);
  return HapticPlaneGeometry {
    std::min(firstAngle, secondAngle),
    Cartesian::planeRmsd(plane, hapticAtoms, Temple::iota<AtomIndex>(siteSize))
  };
}

boost::optional<double> minimumClassificationProbability(
  const PrivateGraph& graph,
  const std::vector<Utils::Position>& positions,
  const AtomIndex v
) {
  auto sites = GraphAlgorithms::sites(graph, v);
  if(sites.size() <= 1) {
    return boost::none;
  }

  const unsigned S = sites.size();
  Eigen::Matrix<double, 3, Eigen::Dynamic> sitePositions(3, S + 1);
  for(unsigned i = 0; i < S; ++i) {
    Eigen::Vector3d averagePosition = Eigen::Vector3d::Zero();
    for(unsigned j : sites[i]) {
      averagePosition += positions.at(j);
    }
    sitePositions.col(i) = averagePosition / sites[i].size();
  }
  sitePositions.col(S) = positions.at(v);

  auto normalizedPositions = Shapes::Continuous::normalize(sitePositions);

  // Classify all suitable shapes
  std::vector<Shapes::Shape> viableShapes;
  for(const Shapes::Shape shape : Shapes::allShapes) {
    if(Shapes::size(shape) == S) {
      viableShapes.push_back(shape);
    }
  }

  const auto classifications = Temple::map(viableShapes,
    [&](const Shapes::Shape shape) -> boost::optional<double> {
      const double measure = Shapes::Continuous::shapeCentroidLast(normalizedPositions, shape).measure;
      return Shapes::Continuous::probabilityRandomCloud(measure, shape);
    }
  );

  if(Temple::all_of(classifications)) {
    const double minimumProbability = std::min_element(
      std::begin(classifications),
      std::end(classifications),
      [](const auto& a, const auto& b) -> bool {
        return a.value() < b.value();
      }
    )->value();

    return minimumProbability;
  }

  return boost::none;
}

struct BestRemovals {
  std::vector<AtomIndex> removals;
  double certainty;
};

boost::optional<BestRemovals> bestRemovalFromHapticSite(
  const PrivateGraph& graph,
  const std::vector<Utils::Position>& positions,
  const AtomIndex v,
  const std::vector<std::vector<AtomIndex>>& sites,
  const unsigned hapticSiteIndex
) {
  const unsigned S = sites.size();
  const std::vector<AtomIndex>& hapticSite = sites.at(hapticSiteIndex);
  const unsigned hapticSiteSize = hapticSite.size();
  return Temple::accumulate(
    hapticSite,
    boost::optional<BestRemovals>(boost::none),
    [&](const auto& carry, const AtomIndex siteVertexToRemove) -> boost::optional<BestRemovals> {
      auto graphCopy = graph;
      graphCopy.removeEdge(graphCopy.edge(v, siteVertexToRemove));
      GraphAlgorithms::updateEtaBonds(graphCopy);
      const auto newSites = GraphAlgorithms::sites(graphCopy, v);

      /* Possible effects of bond removal
       * - Separating a haptic ligand into two single-atom ligands,
       *   changing shapes
       *   - recognizable by size change
       *   - accept if new certainty is low or significantly
       *     lower than old (factor 0.5)
       * - Improves the haptic plane angle by removing a bad bond
       *   - recognized by matching sites and comparing angles
       *   - accept if angle halves
       *
       * How to compare cases?
       */
      if(newSites.size() > S) {
        const auto priorCertainty = minimumClassificationProbability(graph, positions, v);
        const auto posteriorCertainty = minimumClassificationProbability(graphCopy, positions, v);

        if(priorCertainty && posteriorCertainty) {
          if(
            posteriorCertainty.value() <= 0.01
            || posteriorCertainty.value() <= 0.5 * priorCertainty.value()
          ) {
            return BestRemovals {{siteVertexToRemove}, 1 - *posteriorCertainty};
          }
        }
      } else {
        const auto siteFindIter = Temple::find_if(
          newSites,
          [&](const auto& newSite) -> bool {
            if(newSite.size() != hapticSiteSize - 1) {
              return false;
            }

            // Match all vertices except the one to remove
            for(const AtomIndex oldSiteAtomIndex : hapticSite) {
              if(oldSiteAtomIndex == siteVertexToRemove) {
                continue;
              }

              if(Temple::find(newSite, oldSiteAtomIndex) == std::end(newSite)) {
                return false;
              }
            }

            return true;
          }
        );

        if(siteFindIter != std::end(newSites)) {
          const double newHapticAngle = hapticPlaneGeometry(positions, v, *siteFindIter).angle;
          const double newHapticAngleCertainty = 1 - newHapticAngle * 2 / M_PI;
          const double carryCertainty = Temple::Optionals::map(carry,
            [](const auto& removal) { return removal.certainty; }
          ).value_or(0.0);
          if(newHapticAngleCertainty > carryCertainty) {
            return BestRemovals {{siteVertexToRemove}, newHapticAngleCertainty};
          }
        }
      }

      return carry;
    }
  );
}

} // namespace

std::vector<std::vector<unsigned>> ComponentMap::invert() const {
  const unsigned nComponents = *std::max_element(
    std::begin(map),
    std::end(map)
  ) + 1;

  std::vector<
    std::vector<unsigned>
  > inverseMaps (nComponents);

  const unsigned N = map.size();
  for(unsigned i = 0; i < N; ++i) {
    inverseMaps.at(map.at(i)).push_back(i);
  }

  return inverseMaps;
}

std::vector<Utils::AtomCollection> ComponentMap::apply(
  const Utils::AtomCollection& atomCollection
) const {
  const unsigned nComponents = *std::max_element(
    std::begin(map),
    std::end(map)
  ) + 1;
  std::vector<unsigned> componentSizes(nComponents, 0);
  for(unsigned i : map) {
    componentSizes.at(i) += 1;
  }

  /* Allocate the collections */
  std::vector<Utils::AtomCollection> collections = Temple::map(
    componentSizes,
    [](const unsigned size) { return Utils::AtomCollection(size); }
  );
  std::vector<unsigned> collectionSizeCount(nComponents, 0);

  for(unsigned i = 0; i < map.size(); ++i) {
    unsigned moleculeIndex = map.at(i);
    Utils::AtomCollection& collection = collections.at(moleculeIndex);
    unsigned& collectionSize = collectionSizeCount.at(moleculeIndex);
    collection.setElement(
      collectionSize,
      atomCollection.getElement(i)
    );
    collection.setPosition(
      collectionSize,
      atomCollection.getPosition(i)
    );
    ++collectionSize;
  }

  return collections;
}

ComponentMap::ComponentIndexPair ComponentMap::apply(const unsigned index) const {
  ComponentIndexPair pair;
  pair.component = map.at(index);
  pair.atomIndex = std::count(
    std::begin(map),
    std::begin(map) + index,
    pair.component
  );
  return pair;
}

unsigned ComponentMap::invert(const ComponentIndexPair& pair) const {
  unsigned count = 0;
  const unsigned N = map.size();
  for(unsigned i = 0; i < N; ++i) {
    if(map[i] == pair.component) {
      ++count;
    }

    if(count == pair.atomIndex + 1) {
      return i;
    }
  }

  throw std::out_of_range("No match found in component map!");
}

struct MoleculeParts {
  PrivateGraph graph;
  std::vector<Utils::Position> angstromPositions;
  boost::optional<
    std::vector<BondIndex>
  > bondStereopermutatorCandidatesOptional;
};

struct Parts {
  std::vector<MoleculeParts> precursors;
  ComponentMap componentMap;
  unsigned nZeroLengthPositions = 0;
};

// Yields a graph structure without element type annotations
PrivateGraph discretize(
  const Utils::BondOrderCollection& bondOrders,
  const BondDiscretizationOption discretization
) {
  const PrivateGraph::Vertex N = bondOrders.getSystemSize();
  PrivateGraph graph {N};

  if(discretization == BondDiscretizationOption::Binary) {
    for(unsigned i = 0; i < N; ++i) {
      for(unsigned j = i + 1; j < N; ++j) {
        double bondOrder = bondOrders.getOrder(i, j);

        if(bondOrder > 0.5) {
          graph.addEdge(i, j, BondType::Single);
        }
      }
    }
  } else if(discretization == BondDiscretizationOption::RoundToNearest) {
    for(unsigned i = 0; i < N; ++i) {
      for(unsigned j = i + 1; j < N; ++j) {
        double bondOrder = bondOrders.getOrder(i, j);

        if(bondOrder > 0.5) {
          auto bond = static_cast<BondType>(
            std::round(bondOrder) - 1
          );

          if(bondOrder > 6.5) {
            bond = BondType::Sextuple;
          }

          graph.addEdge(i, j, bond);
        }
      }
    }
  }

  return graph;
}

Parts construeParts(
  const Utils::ElementTypeCollection& elements,
  const AngstromPositions& angstromWrapper,
  const Utils::BondOrderCollection& bondOrders,
  const BondDiscretizationOption discretization,
  const boost::optional<double>& stereopermutatorThreshold
) {
  const unsigned N = elements.size();

  // Check preconditions
  if(angstromWrapper.positions.rows() != N) {
    throw std::invalid_argument(
      "Number of positions in angstrom wrapper do not match number of elements"
    );
  }

  if(bondOrders.getSystemSize<unsigned>() != N) {
    throw std::invalid_argument(
      "Bond order argument system size does not match number of elements"
    );
  }

  PrivateGraph atomCollectionGraph = discretize(bondOrders, discretization);

  Parts parts;
  const unsigned numComponents = atomCollectionGraph.connectedComponents(parts.componentMap.map);
  parts.precursors.resize(numComponents);

  if(stereopermutatorThreshold) {
    for(auto& precursor : parts.precursors) {
      // Empty-vector-initialize the candidate optionals
      precursor.bondStereopermutatorCandidatesOptional = std::vector<BondIndex> {};
    }
  }

  // Map from original index to component index
  std::vector<PrivateGraph::Vertex> indexInComponentMap (N);

  /* Maybe
   * - filtered_graph using predicate of componentMap number
   * - copy_graph to new Graph keeping element types and bond orders
   *
   * - alternately, must keep a map of atomcollection index to precursor index
   *   and new precursor atom index in order to transfer edges too
   */

  for(unsigned i = 0; i < N; ++i) {
    auto& precursor = parts.precursors.at(
      parts.componentMap.apply(i).component
    );

    // Add a new vertex with element information
    PrivateGraph::Vertex newIndex = precursor.graph.addVertex(elements.at(i));

    // Save new index in precursor graph
    indexInComponentMap.at(i) = newIndex;

    if(angstromWrapper.positions.row(i).norm() <= 1e-14) {
      parts.nZeroLengthPositions += 1;
    }

    // Copy over position information
    precursor.angstromPositions.emplace_back(
      angstromWrapper.positions.row(i)
    );
  }

  // Copy over edges and bond orders
  for(const PrivateGraph::Edge& edge : atomCollectionGraph.edges()) {
    const PrivateGraph::Vertex source = atomCollectionGraph.source(edge);
    const PrivateGraph::Vertex target = atomCollectionGraph.target(edge);

    // Both source and target are part of the same component (since they are bonded)
    auto& precursor = parts.precursors.at(
      parts.componentMap.apply(source).component
    );

    // Copy over the edge
    precursor.graph.addEdge(
      indexInComponentMap.at(source),
      indexInComponentMap.at(target),
      atomCollectionGraph.bondType(edge)
    );

    // If the edge's bond order exceeds the threshold optional
    if(
      stereopermutatorThreshold
      && bondOrders.getOrder(source, target) >= *stereopermutatorThreshold
    ) {
      precursor.bondStereopermutatorCandidatesOptional->emplace_back(
        indexInComponentMap.at(source),
        indexInComponentMap.at(target)
      );
    }
  }

  return parts;
}

MoleculesResult molecules(
  const Utils::ElementTypeCollection& elements,
  const AngstromPositions& angstromWrapper,
  const Utils::BondOrderCollection& bondOrders,
  const BondDiscretizationOption discretization,
  const boost::optional<double>& stereopermutatorThreshold
) {
  Parts parts = construeParts(
    elements,
    angstromWrapper,
    bondOrders,
    discretization,
    stereopermutatorThreshold
  );

  // Collect results
  MoleculesResult result;

  /* Transform precursors into Molecules. Positions may only be used if there
   * is at most one position very close to (0, 0, 0). Otherwise, we assume that
   * the given positions are faulty or no positional information is present,
   * and only the graph is used to create the Molecules.
   */
  if(parts.nZeroLengthPositions < 2) {
    result.molecules.reserve(parts.precursors.size());
    for(auto& precursor : parts.precursors) {
      result.molecules.emplace_back(
        Graph {std::move(precursor.graph)},
        AngstromPositions(paste(precursor.angstromPositions), LengthUnit::Angstrom),
        precursor.bondStereopermutatorCandidatesOptional
      );
    }
  } else {
    result.molecules.reserve(parts.precursors.size());
    for(auto& precursor : parts.precursors) {
      result.molecules.emplace_back(
        Graph {std::move(precursor.graph)}
      );
    }
  }

  result.componentMap = std::move(parts.componentMap);

  return result;
}

MoleculesResult molecules(
  const Utils::ElementTypeCollection& elements,
  const AngstromPositions& angstromWrapper,
  const BondDiscretizationOption discretization,
  const boost::optional<double>& stereopermutatorThreshold
) {
  return molecules(
    elements,
    angstromWrapper,
    uffBondOrders(elements, angstromWrapper),
    discretization,
    stereopermutatorThreshold
  );
}

MoleculesResult molecules(
  const Utils::AtomCollection& atomCollection,
  const Utils::BondOrderCollection& bondOrders,
  const BondDiscretizationOption discretization,
  const boost::optional<double>& stereopermutatorThreshold
) {
  return molecules(
    atomCollection.getElements(),
    AngstromPositions {atomCollection.getPositions(), LengthUnit::Bohr},
    bondOrders,
    discretization,
    stereopermutatorThreshold
  );
}

MoleculesResult molecules(
  const Utils::AtomCollection& atomCollection,
  const BondDiscretizationOption discretization,
  const boost::optional<double>& stereopermutatorThreshold
) {
  AngstromPositions angstromWrapper {atomCollection.getPositions(), LengthUnit::Bohr};

  return molecules(
    atomCollection.getElements(),
    angstromWrapper,
    uffBondOrders(atomCollection.getElements(), angstromWrapper),
    discretization,
    stereopermutatorThreshold
  );
}

GraphsResult graphs(
  const Utils::ElementTypeCollection& elements,
  const AngstromPositions& angstromWrapper,
  const Utils::BondOrderCollection& bondOrders,
  BondDiscretizationOption discretization
) {
  Parts parts = construeParts(
    elements,
    angstromWrapper,
    bondOrders,
    discretization,
    boost::none
  );

  GraphsResult result;
  result.graphs.reserve(parts.precursors.size());
  for(auto& precursor : parts.precursors) {
    result.graphs.emplace_back(std::move(precursor.graph));
  }
  result.componentMap = std::move(parts.componentMap);
  return result;
}

GraphsResult graphs(
  const Utils::AtomCollection& atomCollection,
  const Utils::BondOrderCollection& bondOrders,
  BondDiscretizationOption discretization
) {
  return graphs(
    atomCollection.getElements(),
    AngstromPositions {atomCollection.getPositions(), LengthUnit::Bohr},
    bondOrders,
    discretization
  );
}

std::vector<FalsePositive> uncertainBonds(
  const Utils::AtomCollection& atomCollection,
  const Utils::BondOrderCollection& bondOrders
) {
  std::vector<FalsePositive> falsePositives;

  Parts parts = construeParts(
    atomCollection.getElements(),
    AngstromPositions {atomCollection.getPositions(), LengthUnit::Bohr},
    bondOrders,
    BondDiscretizationOption::Binary,
    boost::none
  );

  for(unsigned component = 0; component < parts.precursors.size(); ++component) {
    MoleculeParts& part = parts.precursors[component];
    GraphAlgorithms::updateEtaBonds(part.graph);

    std::vector<std::pair<PrivateGraph::Vertex, double>> sketchyClassifications;

    for(const PrivateGraph::Vertex v : part.graph.vertices()) {
      /* Detect high uncertainty shape classifications. Adjacent pairs with high
       * uncertainties are candidates for false positives.
       */
      auto classificationUncertainty = minimumClassificationProbability(part.graph, part.angstromPositions, v);
      if(classificationUncertainty && *classificationUncertainty >= 0.5) {
        sketchyClassifications.emplace_back(v, *classificationUncertainty);
      }
    }

    // NOTE: Cannot remove bonds with overlapping constituent atoms!
    for(const auto& sketchyPair : Temple::Adaptors::allPairs(sketchyClassifications)) {
      const unsigned i = sketchyPair.first.first;
      const unsigned j = sketchyPair.second.first;
      const double i_random = sketchyPair.first.second;
      const double j_random = sketchyPair.second.second;

      if(part.graph.edgeOption(i, j)) {
        falsePositives.push_back(
          FalsePositive {
            parts.componentMap.invert(ComponentMap::ComponentIndexPair {component, i}),
            parts.componentMap.invert(ComponentMap::ComponentIndexPair {component, j}),
            i_random * j_random
          }
        );
      }
    }
  }

  return falsePositives;
}

std::vector<FalsePositive> badHapticLigandBonds(
  const Utils::AtomCollection& atomCollection,
  const Utils::BondOrderCollection& bondOrders
) {
  std::vector<FalsePositive> falsePositives;

  // Avoid duplicate bonds in list with reverse i-j
  auto addFalsePositive = [&](unsigned i, unsigned j, double p) {
    if(i > j) {
      std::swap(i, j);
    }
    auto findIter = std::find_if(
      std::begin(falsePositives),
      std::end(falsePositives),
      [=](const FalsePositive& fp) -> bool {
        return fp.i == i && fp.j == j;
      }
    );
    if(findIter == std::end(falsePositives)) {
      falsePositives.push_back(FalsePositive {i, j, p});
    }
  };

  Parts parts = construeParts(
    atomCollection.getElements(),
    AngstromPositions {atomCollection.getPositions(), LengthUnit::Bohr},
    bondOrders,
    BondDiscretizationOption::Binary,
    boost::none
  );

  for(unsigned component = 0; component < parts.precursors.size(); ++component) {
    MoleculeParts& part = parts.precursors[component];
    GraphAlgorithms::updateEtaBonds(part.graph);

    for(const PrivateGraph::Vertex v : part.graph.vertices()) {
      /* Detect haptic shape planes with large angles to the axis defined by the
       * site position and the central atom or with high rms deviations on
       * their plane fit
       */
      const auto sites = GraphAlgorithms::sites(part.graph, v);
      const unsigned S = sites.size();
      for(unsigned siteIndex = 0; siteIndex < S; ++siteIndex) {
        const auto& site = sites.at(siteIndex);
        const unsigned siteSize = site.size();
        if(siteSize == 1) {
          continue;
        }

        const auto geometry = hapticPlaneGeometry(part.angstromPositions, v, site);

        // Less than 30° and a good plane fit indicate the haptic site is fine
        if(geometry.angle < M_PI / 6 && geometry.rmsd < 0.2) {
          continue;
        }

        if(siteSize == 2) {
          // Suggest the vertex further from the center
          const double frontDistance = (
            part.angstromPositions.at(v)
            - part.angstromPositions.at(site.front())
          ).norm();
          const double backDistance = (
            part.angstromPositions.at(v)
            - part.angstromPositions.at(site.back())
          ).norm();
          const AtomIndex toRemove = frontDistance < backDistance ? site.back() : site.front();
          addFalsePositive(
            parts.componentMap.invert(
              ComponentMap::ComponentIndexPair {component, v}
            ),
            parts.componentMap.invert(
              ComponentMap::ComponentIndexPair {component, toRemove}
            ),
            geometry.angle * 2 / M_PI
          );
        } else {
          const auto suggestedRemovalOption = bestRemovalFromHapticSite(
            part.graph,
            part.angstromPositions,
            v,
            sites,
            siteIndex
          );
          if(suggestedRemovalOption) {
            for(const AtomIndex w : suggestedRemovalOption->removals) {
              addFalsePositive(
                parts.componentMap.invert(
                  ComponentMap::ComponentIndexPair {component, v}
                ),
                parts.componentMap.invert(
                  ComponentMap::ComponentIndexPair {component, w}
                ),
                suggestedRemovalOption->certainty
              );
            }
          }
        }
      }
    }
  }
  return falsePositives;
}

Utils::BondOrderCollection removeFalsePositives(
  const Utils::AtomCollection& atoms,
  Utils::BondOrderCollection bonds
) {
  // First do bad haptic bond orders
  auto haptics = badHapticLigandBonds(atoms, bonds);
  while(!haptics.empty()) {
    Temple::sort(haptics);
    FalsePositive& mostLikely = haptics.back();
    bonds.setOrder(mostLikely.i, mostLikely.j, 0.0);
    haptics = badHapticLigandBonds(atoms, bonds);
  }

  // Then do uncertain bonds
  auto uncertains = uncertainBonds(atoms, bonds);
  while(!uncertains.empty()) {
    Temple::sort(uncertains);
    FalsePositive& mostLikely = uncertains.back();
    bonds.setOrder(mostLikely.i, mostLikely.j, 0.0);
    uncertains = uncertainBonds(atoms, bonds);
  }

  return bonds;
}

} // namespace Interpret
} // namespace Molassembler
} // namespace Scine


/*
 Copyright (C) 2020 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include <orea/aggregation/staticcreditxvacalculator.hpp>

#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/error_of_mean.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/stats.hpp>

using namespace std;
using namespace QuantLib;

using namespace boost::accumulators;

namespace ore {
namespace analytics {

StaticCreditXvaCalculator::StaticCreditXvaCalculator(
    const boost::shared_ptr<Portfolio> portfolio, const boost::shared_ptr<Market> market,
    const string& configuration, const string& baseCurrency, const string& dvaName,
    const string& fvaBorrowingCurve, const string& fvaLendingCurve,
    const bool applyDynamicInitialMargin,
    const boost::shared_ptr<DynamicInitialMarginCalculator> dimCalculator,
    const boost::shared_ptr<NPVCube> tradeExposureCube,
    const boost::shared_ptr<NPVCube> nettingSetExposureCube,
    const Size tradeEpeIndex, const Size tradeEneIndex,
    const Size nettingSetEpeIndex, const Size nettingSetEneIndex,
    const bool flipViewXVA, const string& flipViewBorrowingCurvePostfix, const string& flipViewLendingCurvePostfix)
    : ValueAdjustmentCalculator(portfolio, market, configuration, baseCurrency, dvaName,
                                fvaBorrowingCurve, fvaLendingCurve, applyDynamicInitialMargin,
                                dimCalculator, tradeExposureCube, nettingSetExposureCube, tradeEpeIndex, tradeEneIndex,
                                nettingSetEpeIndex, nettingSetEneIndex,
                                flipViewXVA, flipViewBorrowingCurvePostfix, flipViewLendingCurvePostfix) {
    for (Size i = 0; i < dates().size(); i++) {
        dateIndexMap_.emplace(dates()[i], i);
    }
}

const Real StaticCreditXvaCalculator::calculateCvaIncrement(
    const string& tid, const string& cid, const Date& d0, const Date& d1, const Real& rr) {
    Handle<DefaultProbabilityTermStructure> dts = market_->defaultCurve(cid, configuration_);
    QL_REQUIRE(!dts.empty(), "Default curve missing for counterparty " << cid);
    Real increment = 0.0;
    Real s0 = dts->survivalProbability(d0);
    Real s1 = dts->survivalProbability(d1);
    Real epe = tradeExposureCube_->get(tid, d1, 0, tradeEpeIndex_);
    increment = (1.0 - rr) * (s0 - s1) * epe;
    return increment;
}

const Real StaticCreditXvaCalculator::calculateDvaIncrement(
    const string& tid, const Date& d0, const Date& d1, const Real& rr) {
    Handle<DefaultProbabilityTermStructure> dts = market_->defaultCurve(dvaName_, configuration_);
    QL_REQUIRE(!dts.empty(), "Default curve missing for counterparty " << dvaName_);
    Real increment = 0.0;
    Real s0 = dts->survivalProbability(d0);
    Real s1 = dts->survivalProbability(d1);
    Real ene = tradeExposureCube_->get(tid, d1, 0, tradeEneIndex_);
    increment = (1.0 - rr) * (s0 - s1) * ene;
    return increment;
}

const Real StaticCreditXvaCalculator::calculateNettingSetCvaIncrement(
    const string& nid, const string& cid, const Date& d0, const Date& d1, const Real& rr) {
    Handle<DefaultProbabilityTermStructure> dts = market_->defaultCurve(cid, configuration_);
    QL_REQUIRE(!dts.empty(), "Default curve missing for counterparty " << cid);
    Real increment = 0.0;
    Real s0 = dts->survivalProbability(d0);
    Real s1 = dts->survivalProbability(d1);
    Real epe = nettingSetExposureCube_->get(nid, d1, 0, nettingSetEpeIndex_);
    increment = (1.0 - rr) * (s0 - s1) * epe;
    return increment;
}

const Real StaticCreditXvaCalculator::calculateNettingSetDvaIncrement(
    const string& nid, const Date& d0, const Date& d1, const Real& rr) {
    Handle<DefaultProbabilityTermStructure> dts = market_->defaultCurve(dvaName_, configuration_);
    QL_REQUIRE(!dts.empty(), "Default curve missing for counterparty " << dvaName_);
    Real increment = 0.0;
    Real s0 = dts->survivalProbability(d0);
    Real s1 = dts->survivalProbability(d1);
    Real ene = nettingSetExposureCube_->get(nid, d1, 0, nettingSetEneIndex_);
    increment = (1.0 - rr) * (s0 - s1) * ene;
    return increment;
}

const Real StaticCreditXvaCalculator::calculateFbaIncrement(
    const string& tid, const string& cid, const string& dvaName,
    const Date& d0, const Date& d1, const Real& dcf) {
    Handle<DefaultProbabilityTermStructure> dts_cid;
    Handle<DefaultProbabilityTermStructure> dts_dvaName;
    if (cid != "") {
        dts_cid = market_->defaultCurve(cid, configuration_);
        QL_REQUIRE(!dts_cid.empty(), "Default curve missing for counterparty " << cid);
    }
    if (dvaName != "") {
        dts_dvaName = market_->defaultCurve(dvaName, configuration_);
        QL_REQUIRE(!dts_dvaName.empty(), "Default curve missing for counterparty " << cid);
    }

    Real increment = 0.0;
    Real s0 = cid == "" ? 1.0 : dts_cid->survivalProbability(d0);
    Real s1 = dvaName == "" ? 1.0 : dts_dvaName->survivalProbability(d0);
    Real ene = tradeExposureCube_->get(tid, d1, 0, tradeEneIndex_);
    increment = s0 * s1 * ene * dcf;
    return increment;
}

const Real StaticCreditXvaCalculator::calculateFcaIncrement(
    const string& tid, const string& cid, const string& dvaName,
    const Date& d0, const Date& d1, const Real& dcf) {
    Handle<DefaultProbabilityTermStructure> dts_cid;
    Handle<DefaultProbabilityTermStructure> dts_dvaName;
    if (cid != "") {
        dts_cid = market_->defaultCurve(cid, configuration_);
        QL_REQUIRE(!dts_cid.empty(), "Default curve missing for counterparty " << cid);
    }
    if (dvaName != "") {
        dts_dvaName = market_->defaultCurve(dvaName, configuration_);
        QL_REQUIRE(!dts_dvaName.empty(), "Default curve missing for counterparty " << cid);
    }

    Real increment = 0.0;
    Real s0 = cid == "" ? 1.0 : dts_cid->survivalProbability(d0);
    Real s1 = dvaName == "" ? 1.0 : dts_dvaName->survivalProbability(d0);
    Real epe = tradeExposureCube_->get(tid, d1, 0, tradeEpeIndex_);
    increment = s0 * s1 * epe * dcf;
    return increment;
}

const Real StaticCreditXvaCalculator::calculateNettingSetFbaIncrement(
    const string& nid, const string& cid, const string& dvaName,
    const Date& d0, const Date& d1, const Real& dcf) {
    Handle<DefaultProbabilityTermStructure> dts_cid;
    Handle<DefaultProbabilityTermStructure> dts_dvaName;
    if (cid != "") {
        dts_cid = market_->defaultCurve(cid, configuration_);
        QL_REQUIRE(!dts_cid.empty(), "Default curve missing for counterparty " << cid);
    }
    if (dvaName != "") {
        dts_dvaName = market_->defaultCurve(dvaName, configuration_);
        QL_REQUIRE(!dts_dvaName.empty(), "Default curve missing for counterparty " << cid);
    }

    Real increment = 0.0;
    Real s0 = cid == "" ? 1.0 : dts_cid->survivalProbability(d0);
    Real s1 = dvaName == "" ? 1.0 : dts_dvaName->survivalProbability(d0);
    Real ene = nettingSetExposureCube_->get(nid, d1, 0, nettingSetEneIndex_);
    increment = s0 * s1 * ene * dcf;
    return increment;
}

const Real StaticCreditXvaCalculator::calculateNettingSetFcaIncrement(
    const string& nid, const string& cid, const string& dvaName,
    const Date& d0, const Date& d1, const Real& dcf) {
    Handle<DefaultProbabilityTermStructure> dts_cid;
    Handle<DefaultProbabilityTermStructure> dts_dvaName;
    if (cid != "") {
        dts_cid = market_->defaultCurve(cid, configuration_);
        QL_REQUIRE(!dts_cid.empty(), "Default curve missing for counterparty " << cid);
    }
    if (dvaName != "") {
        dts_dvaName = market_->defaultCurve(dvaName, configuration_);
        QL_REQUIRE(!dts_dvaName.empty(), "Default curve missing for counterparty " << cid);
    }

    Real increment = 0.0;
    Real s0 = cid == "" ? 1.0 : dts_cid->survivalProbability(d0);
    Real s1 = dvaName == "" ? 1.0 : dts_dvaName->survivalProbability(d0);
    Real epe = nettingSetExposureCube_->get(nid, d1, 0, nettingSetEpeIndex_);
    increment = s0 * s1 * epe * dcf;
    return increment;
}

const Real StaticCreditXvaCalculator::calculateNettingSetMvaIncrement(
    const string& nid, const string& cid, const Date& d0, const Date& d1, const Real& dcf) {
    Handle<DefaultProbabilityTermStructure> dts_cid = market_->defaultCurve(cid, configuration_);
    QL_REQUIRE(cid == "" || !dts_cid.empty(), "Default curve missing for counterparty " << cid);
    Handle<DefaultProbabilityTermStructure> dts_dvaName = market_->defaultCurve(dvaName_, configuration_);
    QL_REQUIRE(dvaName_ == "" || !dts_dvaName.empty(), "Default curve missing for counterparty " << dvaName_);

    Real increment = 0.0;
    Real s0 = cid == "" ? 1.0 : dts_cid->survivalProbability(d0);
    Real s1 = dvaName_ == "" ? 1.0 : dts_dvaName->survivalProbability(d0);
    increment = s0 * s1 * dimCalculator_->expectedIM(nid)[dateIndexMap_[d1]] * dcf;
    return increment;
}

} // namespace analytics
} // namespace ore


/*
 * Copyright (c) 2013, Christian Gehring, Hannes Sommer, Paul Furgale, Remo Diethelm
 * 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 Autonomous Systems Lab, ETH Zurich 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 Christian Gehring, Hannes Sommer, Paul Furgale,
 * Remo Diethelm 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 KINDR_ROTATIONS_EIGEN_EULERANGLESZYX_HPP_
#define KINDR_ROTATIONS_EIGEN_EULERANGLESZYX_HPP_

#include <cmath>

#include <Eigen/Geometry>

#include "kindr/common/common.hpp"
#include "kindr/common/assert_macros_eigen.hpp"
#include "kindr/rotations/RotationBase.hpp"
#include "kindr/rotations/eigen/RotationEigenFunctions.hpp"

namespace kindr {
namespace rotations {
namespace eigen_impl {

/*! \class EulerAnglesZyx
 *  \brief Implementation of Euler angles (Z-Y'-X'' / yaw-pitch-roll) rotation based on Eigen::Matrix<Scalar, 3, 1>
 *
 *  The following typedefs are provided for convenience:
 *   - \ref eigen_impl::EulerAnglesZyxAD "EulerAnglesZyxAD" for active rotation and double primitive type
 *   - \ref eigen_impl::EulerAnglesZyxAF "EulerAnglesZyxAF" for active rotation and float primitive type
 *   - \ref eigen_impl::EulerAnglesZyxPD "EulerAnglesZyxPD" for passive rotation and double primitive type
 *   - \ref eigen_impl::EulerAnglesZyxPF "EulerAnglesZyxPF" for passive rotation and float primitive type
 *   - EulerAnglesYprAD = EulerAnglesZyxAD
 *   - EulerAnglesYprAF = EulerAnglesZyxAF
 *   - EulerAnglesYprPD = EulerAnglesZyxPD
 *   - EulerAnglesYprPF = EulerAnglesZyxPF
 *
 *  \tparam PrimType_ the primitive type of the data (double or float)
 *  \tparam Usage_ the rotation usage which is either active or passive
 *  \ingroup rotations
 */
template<typename PrimType_, enum RotationUsage Usage_>
class EulerAnglesZyx : public EulerAnglesZyxBase<EulerAnglesZyx<PrimType_, Usage_>, Usage_> {
 private:
  /*! \brief The base type.
   */
  typedef Eigen::Matrix<PrimType_, 3, 1> Base;

  /*! \brief vector of Euler angles [yaw; pitch; roll]
   */
  Base zyx_;
 public:
  /*! \brief The implementation type.
   *  The implementation type is always an Eigen object.
   */
  typedef Base Implementation;
  /*! \brief The primitive type.
   *  Float/Double
   */
  typedef PrimType_ Scalar;

  /*! \brief Euler angles as 3x1-matrix
   */
  typedef Base Vector;

  /*! \brief Default constructor using identity rotation.
   */
  EulerAnglesZyx()
    : zyx_(Base::Zero()) {
  }

  /*! \brief Constructor using three scalars.
   *  \param yaw      first rotation angle around Z axis
   *  \param pitch    second rotation angle around Y' axis
   *  \param roll     third rotation angle around X'' axis
   */
  EulerAnglesZyx(Scalar yaw, Scalar pitch, Scalar roll)
    : zyx_(yaw,pitch,roll) {
  }

  /*! \brief Constructor using Eigen::Matrix.
   *  \param other   Eigen::Matrix<PrimType_,3,1> [roll; pitch; yaw]
   */
  explicit EulerAnglesZyx(const Base& other)
    : zyx_(other) {
  }

  /*! \brief Constructor using another rotation.
   *  \param other   other rotation
   */
  template<typename OtherDerived_>
  inline explicit EulerAnglesZyx(const RotationBase<OtherDerived_, Usage_>& other)
    : zyx_(internal::ConversionTraits<EulerAnglesZyx, OtherDerived_>::convert(other.derived()).toImplementation()) {
  }

  /*! \brief Assignment operator using another rotation.
   *  \param other   other rotation
   *  \returns referece
   */
  template<typename OtherDerived_>
  EulerAnglesZyx& operator =(const RotationBase<OtherDerived_, Usage_>& other) {
    this->toImplementation() = internal::ConversionTraits<EulerAnglesZyx, OtherDerived_>::convert(other.derived()).toImplementation();
    return *this;
  }

  /*! \brief Parenthesis operator to convert from another rotation.
   *  \param other   other rotation
   *  \returns reference
   */
  template<typename OtherDerived_>
  EulerAnglesZyx& operator ()(const RotationBase<OtherDerived_, Usage_>& other) {
    this->toImplementation() = internal::ConversionTraits<EulerAnglesZyx, OtherDerived_>::convert(other.derived()).toImplementation();
    return *this;
  }

  /*! \brief Returns the inverse of the rotation.
   *  \returns the inverse of the rotation
   */
  EulerAnglesZyx inverted() const {
    return EulerAnglesZyx(eigen_internal::getInverseYpr<PrimType_, PrimType_>(this->toImplementation()));
  }

  /*! \brief Inverts the rotation.
   *  \returns reference
   */
  EulerAnglesZyx& invert() {
    *this = this->inverted();
    return *this;
  }

  /*! \brief Returns the Euler angles in a vector.
   *  \returns  vector Eigen::Matrix<Scalar,3, 1>
   */
  inline const Vector vector() const {
    return this->toImplementation();
  }

  /*! \brief Cast to the implementation type.
   *  \returns the implementation for direct manipulation (recommended only for advanced users)
   */
  inline Base& toImplementation() {
    return static_cast<Base&>(zyx_);
  }

  /*! \brief Cast to the implementation type.
   *  \returns the implementation for direct manipulation (recommended only for advanced users)
   */
  inline const Base& toImplementation() const {
    return static_cast<const Base&>(zyx_);
  }

  /*! \brief Gets yaw (Z) angle.
   *  \returns yaw angle (scalar)
   */
  inline Scalar yaw() const {
    return zyx_(0);
  }

  /*! \brief Gets pitch (Y') angle.
   *  \returns pitch angle (scalar)
   */
  inline Scalar pitch() const {
    return zyx_(1);
  }

  /*! \brief Gets roll (X'') angle.
   *  \returns roll angle (scalar)
   */
  inline Scalar roll() const {
    return zyx_(2);
  }

  /*! \brief Sets yaw (Z) angle.
   */
  inline void setYaw(Scalar yaw) {
    zyx_(0) = yaw;
  }

  /*! \brief Sets pitch (Y') angle.
   */
  inline void setPitch(Scalar pitch) {
    zyx_(1) = pitch;
  }

  /*! \brief Sets roll (X'') angle.
   */
  inline void setRoll(Scalar roll) {
    zyx_(2) = roll;
  }

  /*! \brief Reading access to yaw (Z) angle.
   *  \returns yaw angle (scalar) with reading access
   */
  inline Scalar z() const {
    return zyx_(0);
  }

  /*! \brief Reading access to pitch (Y') angle.
   *  \returns pitch angle (scalar) with reading access
   */
  inline Scalar y() const {
    return zyx_(1);
  }

  /*! \brief Reading access to roll (X'') angle.
   *  \returns roll angle (scalar) with reading access
   */
  inline Scalar x() const {
    return zyx_(2);
  }

  /*! \brief Writing access to yaw (Z) angle.
   *  \returns yaw angle (scalar) with writing access
   */
  inline void setZ(Scalar z) {
    zyx_(0) = z;
  }

  /*! \brief Writing access to pitch (Y') angle.
   *  \returns pitch angle (scalar) with writing access
   */
  inline void setY(Scalar y) {
    zyx_(1) = y;
  }

  /*! \brief Writing access to roll (X'') angle.
   *  \returns roll angle (scalar) with writing access
   */
  inline void setX(Scalar x) {
    zyx_(2) = x;
  }

  /*! \brief Sets the rotation to identity.
   *  \returns reference
   */
  EulerAnglesZyx& setIdentity() {
    zyx_.setZero();
    return *this;
  }

  /*! \brief Returns a unique Euler angles rotation with angles in [-pi,pi),[-pi/2,pi/2),[-pi,pi).
   *  This function is used to compare different rotations.
   *  \returns copy of the Euler angles rotation which is unique
   */
  EulerAnglesZyx getUnique() const {
    Base zyx(kindr::common::floatingPointModulo(z()+M_PI,2*M_PI)-M_PI,
             kindr::common::floatingPointModulo(y()+M_PI,2*M_PI)-M_PI,
             kindr::common::floatingPointModulo(x()+M_PI,2*M_PI)-M_PI); // wrap all angles into [-pi,pi)

    const double tol = 1e-3;

    // wrap angles into [-pi,pi),[-pi/2,pi/2),[-pi,pi)
    if(zyx.y() < -M_PI/2 - tol)
    {
      if(zyx.z() < 0) {
        zyx.z() = zyx.z() + M_PI;
      } else {
        zyx.z() = zyx.z() - M_PI;
      }

      zyx.y() = -(zyx.y() + M_PI);

      if(zyx.x() < 0) {
        zyx.x() = zyx.x() + M_PI;
      } else {
        zyx.x() = zyx.x() - M_PI;
      }
    }
    else if(-M_PI/2 - tol <= zyx.y() && zyx.y() <= -M_PI/2 + tol)
    {
      zyx.z() += zyx.x();
      zyx.x() = 0;
    }
    else if(-M_PI/2 + tol < zyx.y() && zyx.y() < M_PI/2 - tol)
    {
      // ok
    }
    else if(M_PI/2 - tol <= zyx.y() && zyx.y() <= M_PI/2 + tol)
    {
      // todo: M_PI/2 should not be in range, other formula?
      zyx.z() -= zyx.x();
      zyx.x() = 0;
    }
    else // M_PI/2 + tol < zyx.y()
    {
      if(zyx.z() < 0) {
        zyx.z() = zyx.z() + M_PI;
      } else {
        zyx.z() = zyx.z() - M_PI;
      }

      zyx.y() = -(zyx.y() - M_PI);

      if(zyx.x() < 0) {
        zyx.x() = zyx.x() + M_PI;
      } else {
        zyx.x() = zyx.x() - M_PI;
      }
    }

    return EulerAnglesZyx(zyx);
  }

  /*! \brief Modifies the Euler angles rotation such that the angles lie in [-pi,pi),[-pi/2,pi/2),[-pi,pi).
   *  \returns reference
   */
  EulerAnglesZyx& setUnique() {  // wraps angles into [-pi,pi),[-pi/2,pi/2),[-pi,pi)
    *this = getUnique();
    return *this;
  }

  /*! \brief Concenation operator.
   *  This is explicitly specified, because Eigen::Matrix provides also an operator*.
   *  \returns the concenation of two rotations
   */
  using EulerAnglesZyxBase<EulerAnglesZyx<PrimType_, Usage_>, Usage_>::operator*;

  /*! \brief Equivalence operator.
   *  This is explicitly specified, because Eigen::Matrix provides also an operator==.
   *  \returns true if two rotations are similar.
   */
  using EulerAnglesZyxBase<EulerAnglesZyx<PrimType_, Usage_>, Usage_>::operator==;

  /*! \brief Used for printing the object with std::cout.
   *  \returns std::stream object
   */
  friend std::ostream& operator << (std::ostream& out, const EulerAnglesZyx& zyx) {
    out << zyx.toImplementation().transpose();
    return out;
  }
};

//! \brief Active Euler angles rotation (Z,Y',X'' / yaw,pitch,roll) with double primitive type
typedef EulerAnglesZyx<double, RotationUsage::ACTIVE>  EulerAnglesZyxAD;
//! \brief Active Euler angles rotation (Z,Y',X'' / yaw,pitch,roll) with float primitive type
typedef EulerAnglesZyx<float,  RotationUsage::ACTIVE>  EulerAnglesZyxAF;
//! \brief Passive Euler angles rotation (Z,Y',X'' / yaw,pitch,roll) with double primitive type
typedef EulerAnglesZyx<double, RotationUsage::PASSIVE> EulerAnglesZyxPD;
//! \brief Passive Euler angles rotation (Z,Y',X'' / yaw,pitch,roll) with float primitive type
typedef EulerAnglesZyx<float,  RotationUsage::PASSIVE> EulerAnglesZyxPF;

//! \brief Equivalent Euler angles rotation (Z,Y',X'' / yaw,pitch,roll) class
template <typename PrimType_, enum RotationUsage Usage_>
using EulerAnglesYpr = EulerAnglesZyx<PrimType_, Usage_>;

//! \brief Active Euler angles rotation (Z,Y',X'' / yaw,pitch,roll) with double primitive type
typedef EulerAnglesYpr<double, RotationUsage::ACTIVE>  EulerAnglesYprAD;
//! \brief Active Euler angles rotation (Z,Y',X'' / yaw,pitch,roll) with float primitive type
typedef EulerAnglesYpr<float,  RotationUsage::ACTIVE>  EulerAnglesYprAF;
//! \brief Passive Euler angles rotation (Z,Y',X'' / yaw,pitch,roll) with double primitive type
typedef EulerAnglesYpr<double, RotationUsage::PASSIVE> EulerAnglesYprPD;
//! \brief Passive Euler angles rotation (Z,Y',X'' / yaw,pitch,roll) with float primitive type
typedef EulerAnglesYpr<float,  RotationUsage::PASSIVE> EulerAnglesYprPF;

} // namespace eigen_impl

namespace internal {

template<typename PrimType_, enum RotationUsage Usage_>
class get_scalar<eigen_impl::EulerAnglesZyx<PrimType_, Usage_>> {
 public:
  typedef PrimType_ Scalar;
};

template<typename PrimType_, enum RotationUsage Usage_>
class get_matrix3X<eigen_impl::EulerAnglesZyx<PrimType_, Usage_>>{
 public:
  typedef int  IndexType;

  template <IndexType Cols>
  using Matrix3X = Eigen::Matrix<PrimType_, 3, Cols>;
};

template<typename PrimType_>
class get_other_usage<eigen_impl::EulerAnglesZyx<PrimType_, RotationUsage::ACTIVE>> {
 public:
  typedef eigen_impl::EulerAnglesZyx<PrimType_, RotationUsage::PASSIVE> OtherUsage;
};

template<typename PrimType_>
class get_other_usage<eigen_impl::EulerAnglesZyx<PrimType_, RotationUsage::PASSIVE>> {
 public:
  typedef eigen_impl::EulerAnglesZyx<PrimType_, RotationUsage::ACTIVE> OtherUsage;
};

/* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 * Conversion Traits
 * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
template<typename DestPrimType_, typename SourcePrimType_, enum RotationUsage Usage_>
class ConversionTraits<eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_>, eigen_impl::AngleAxis<SourcePrimType_, Usage_>> {
 public:
  inline static eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_> convert(const eigen_impl::AngleAxis<SourcePrimType_, Usage_>& aa) {
//    return eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_>(eigen_impl::getYprFromAngleAxis<SourcePrimType_, DestPrimType_>(aa.toImplementation()));
    return eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_>(eigen_impl::RotationQuaternion<DestPrimType_, Usage_>(aa));
  }
};

template<typename DestPrimType_, typename SourcePrimType_, enum RotationUsage Usage_>
class ConversionTraits<eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_>, eigen_impl::RotationVector<SourcePrimType_, Usage_>> {
 public:
  inline static eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_> convert(const eigen_impl::RotationVector<SourcePrimType_, Usage_>& rotationVector) {
    return eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_>(eigen_impl::AngleAxis<SourcePrimType_, Usage_>(rotationVector));
  }
};

template<typename DestPrimType_, typename SourcePrimType_, enum RotationUsage Usage_>
class ConversionTraits<eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_>, eigen_impl::RotationQuaternion<SourcePrimType_, Usage_>> {
 public:
  inline static eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_> convert(const eigen_impl::RotationQuaternion<SourcePrimType_, Usage_>& q) {
    return eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_>(eigen_impl::eigen_internal::getYprFromQuaternion<SourcePrimType_, DestPrimType_>(q.toImplementation()));
  }
};

template<typename DestPrimType_, typename SourcePrimType_, enum RotationUsage Usage_>
class ConversionTraits<eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_>, eigen_impl::RotationMatrix<SourcePrimType_, Usage_>> {
 public:
  inline static eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_> convert(const eigen_impl::RotationMatrix<SourcePrimType_, Usage_>& R) {
    return eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_>(eigen_impl::eigen_internal::getYprFromRotationMatrix<SourcePrimType_, DestPrimType_>(R.toImplementation().transpose()));
  }
};

template<typename DestPrimType_, typename SourcePrimType_, enum RotationUsage Usage_>
class ConversionTraits<eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_>, eigen_impl::EulerAnglesXyz<SourcePrimType_, Usage_>> {
 public:
  inline static eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_> convert(const eigen_impl::EulerAnglesXyz<SourcePrimType_, Usage_>& xyz) {
    return eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_>(eigen_impl::eigen_internal::getYprFromRpy<SourcePrimType_, DestPrimType_>(xyz.toImplementation()));
  }
};

template<typename DestPrimType_, typename SourcePrimType_, enum RotationUsage Usage_>
class ConversionTraits<eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_>, eigen_impl::EulerAnglesZyx<SourcePrimType_, Usage_>> {
 public:
  inline static eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_> convert(const eigen_impl::EulerAnglesZyx<SourcePrimType_, Usage_>& zyx) {
    return eigen_impl::EulerAnglesZyx<DestPrimType_, Usage_>(zyx.toImplementation().template cast<DestPrimType_>());
  }
};

/* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 * Multiplication Traits
 * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
//template<typename PrimType_>
//class MultiplicationTraits<RotationBase<eigen_impl::EulerAnglesZyx<PrimType_, RotationUsage::ACTIVE>, RotationUsage::ACTIVE>, RotationBase<eigen_impl::EulerAnglesZyx<PrimType_, RotationUsage::ACTIVE>, RotationUsage::ACTIVE>> {
// public:
//  inline static eigen_impl::EulerAnglesZyx<PrimType_, RotationUsage::ACTIVE> mult(const eigen_impl::EulerAnglesZyx<PrimType_, RotationUsage::ACTIVE>& a, const eigen_impl::EulerAnglesZyx<PrimType_, RotationUsage::ACTIVE>& b) {
//    return eigen_impl::EulerAnglesZyx<PrimType_, RotationUsage::ACTIVE>(eigen_impl::RotationQuaternion<PrimType_, RotationUsage::ACTIVE>(
//                                                                 eigen_impl::RotationQuaternion<PrimType_, RotationUsage::ACTIVE>(a).toImplementation()*
//                                                                 eigen_impl::RotationQuaternion<PrimType_, RotationUsage::ACTIVE>(b).toImplementation()));
//  }
//};
//
//template<typename PrimType_>
//class MultiplicationTraits<RotationBase<eigen_impl::EulerAnglesZyx<PrimType_, RotationUsage::PASSIVE>, RotationUsage::PASSIVE>, RotationBase<eigen_impl::EulerAnglesZyx<PrimType_, RotationUsage::PASSIVE>, RotationUsage::PASSIVE>> {
// public:
//  inline static eigen_impl::EulerAnglesZyx<PrimType_, RotationUsage::PASSIVE> mult(const eigen_impl::EulerAnglesZyx<PrimType_, RotationUsage::PASSIVE>& a, const eigen_impl::EulerAnglesZyx<PrimType_, RotationUsage::PASSIVE>& b) {
//    return eigen_impl::EulerAnglesZyx<PrimType_, RotationUsage::PASSIVE>(eigen_impl::RotationQuaternion<PrimType_, RotationUsage::PASSIVE>(
//                                                                 eigen_impl::RotationQuaternion<PrimType_, RotationUsage::PASSIVE>(a).toImplementation()*
//                                                                 eigen_impl::RotationQuaternion<PrimType_, RotationUsage::PASSIVE>(b).toImplementation()));
//  }
//};

//template<typename PrimType_, enum RotationUsage Usage_>
//class MultiplicationTraits<RotationBase<eigen_impl::EulerAnglesZyx<PrimType_, Usage_>, Usage_>, RotationBase<eigen_impl::EulerAnglesZyx<PrimType_, Usage_>, Usage_>> {
// public:
//  inline static eigen_impl::EulerAnglesZyx<PrimType_, Usage_> mult(const eigen_impl::EulerAnglesZyx<PrimType_, Usage_>& a, const eigen_impl::EulerAnglesZyx<PrimType_, Usage_>& b) {
//    return eigen_impl::EulerAnglesZyx<PrimType_, Usage_>(eigen_impl::RotationQuaternion<PrimType_, Usage_>(
//                                                                 eigen_impl::RotationQuaternion<PrimType_, Usage_>(a).toImplementation()*
//                                                                 eigen_impl::RotationQuaternion<PrimType_, Usage_>(b).toImplementation()));
//  }
//};

/* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 * Rotation Traits
 * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */

/* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 * Comparison Traits
 * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */

} // namespace internal
} // namespace rotations
} // namespace kindr

#endif /* KINDR_ROTATIONS_EIGEN_EULERANGLESZYX_HPP_ */