// Copyright 2015-2022 The ALMA Project Developers
//
// 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.

#pragma once

/// @file
/// Miscellaneous convenience resources.

#include <tuple>
#include <vector>
#include <string>
#include <memory>
#include <limits>
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <random>
#include <boost/integer/common_factor.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/tokenizer.hpp>
#include <boost/mpi.hpp>
#include <constants.hpp>
#include <Eigen/Dense>
#include <Eigen/Sparse>

namespace alma {

/// Randomly choose an element from a sequence using the provided rng.
template <typename I, typename R> I choose(I start, I end, R& g) {
    std::uniform_int_distribution<> dis(0, std::distance(start, end) - 1);
    std::advance(start, dis(g));
    return start;
}

/// Stable Sum using Neumaier
/// algorithm. See DOI: 10.1002/zamm.19740540106
///(in German)

/// Stable sum using pointer and size
///@param  A pointer to data
///@param  size size of data
///@return stable summation result
template <class T> T NeumaierSum(T* A, std::size_t size) {
    T sum = 0;
    T c = 0;

    for (std::size_t i = 0; i < size; i++) {
        T t = sum + A[i];
        if (std::abs(sum) >= std::abs(A[i]))
            c += (sum - t) + A[i];
        else
            c += (A[i] - t) + sum;
        sum = t;
    }
    return sum + c;
}

/// Neumaier Sum in which the summation
/// cannot be done all together
/// so that we keep the correction
/// and the summation
///@param result Neumaier sum until now
///@param b      the number to add
template <class T>
std::complex<T> NeumaierSum(std::complex<T>& result, const T& b) {
    T TemporalSum = std::real(result) + b;
    if (std::abs(std::real(result)) >= std::abs(b)) {
        result += std::complex<T>(0, (std::real(result) - TemporalSum) + b);
    }
    else {
        result += std::complex<T>(0, (b - TemporalSum) + std::real(result));
    }
    result = std::complex<T>(TemporalSum, std::imag(result));
    return result;
}

/// This is to eval the Neumaier sum
/// it add correction to the summation
/// and return the value
///@param N summation and correction
///@return the Neumaier summation
template <class T> T evalNeumaierSum(std::complex<T>& N) {
    return std::real(N) + std::imag(N);
}

/// Thought for MPI collapse
/// after gather
///@param Ns vector containing Neumaier sumation info
///          from different procs
///@return the Neumaier summation
template <class T> T combineNeumaierSums(std::vector<std::complex<T>>& Ns) {
    /// Get sums and errors and apply
    /// stable sum to them
    std::vector<T> sums, cs;
    for (auto& N : Ns) {
        sums.push_back(std::real(N));
        cs.push_back(std::imag(N));
    }

    auto NeuSum = NeumaierSum(sums.data(), sums.size());

    auto NeuCs = NeumaierSum(cs.data(), cs.size());

    return NeuSum + NeuCs;
}

/// Comparator function object template for a container of
/// comparable objects.
template <typename T> class Container_comparator {
public:
    inline bool operator()(const T& op1, const T& op2) const {
        return std::lexicographical_compare(
            std::begin(op1), std::end(op1), std::begin(op2), std::end(op2));
    }
};

/// Put the keys and values of a map (or similar) into separate
/// vectors.
template <typename T>
std::tuple<std::vector<typename T::key_type>,
           std::vector<typename T::mapped_type>>
split_keys_and_values(const T& input) {
    auto size = input.size();

    std::vector<typename T::key_type> keys;
    keys.reserve(size);
    std::vector<typename T::mapped_type> values;
    values.reserve(size);

    for (auto& i : input) {
        keys.emplace_back(i.first);
        values.emplace_back(i.second);
    }
    return std::make_tuple(keys, values);
}

/// Read from f until '\n' is found.
///
/// This function is meant to be called after reading data from f
/// using >> and before using std::getline() on it.
///
/// @param[in] f - istream from which to read
inline void flush_istream(std::istream& f) {
    if (f.peek() == '\n') {
        f.ignore(1, '\n');
    }
}

/// Try to tokenize a line into homogeneous tokens.
///
/// This function tries to split a line into fields that can all be
/// cast into the same type and return a vector of tokens.
/// boost::bad_lexical_cast will be thrown in case of failure.
///
/// @param[in] line - line of text to tokenize
/// @param[in] sep - field separators
/// @return a vector of tokens
template <typename T>
inline std::vector<T> tokenize_homogeneous_line(std::string& line,
                                                const std::string sep = " \t") {
    std::vector<T> nruter;
    boost::char_separator<char> separator(sep.c_str());
    boost::tokenizer<boost::char_separator<char>> tokenizer(line, separator);

    for (auto i : tokenizer)
        nruter.emplace_back(boost::lexical_cast<T>(i));
    return nruter;
}

/// Check whether a line starts with a character from a given set.
///
/// @param[in] line - string containing the line
/// @param[in] chars - string containing the set of characters
/// @return true if line starts with a character from chars
inline bool starts_with_character(const std::string& line,
                                  const std::string& chars) {
    if (line.length() == 0)
        return false;

    for (auto c : chars) {
        if (line[0] == c)
            return true;
    }
    return false;
}

/// Approximate comparation between doubles.
///
/// @param[in] a - first value
/// @param[in] b - second value
/// @param[in] abstol - Maximum absolute tolerance
/// @param[in] reltol - Maximum relative tolerance
/// @return - true if the two values are almost equal
inline bool almost_equal(const double a,
                         const double b,
                         const double abstol = 1e-8,
                         const double reltol = 1e-5) {
    // Method and default values inspired by
    // numpy.allclose().
    const auto tol = std::fabs(abstol) + std::fabs(reltol * b);

    return std::fabs(a - b) < tol;
}

/// Convenience class for updating a minimum value and keeping track
/// of all the objects associated to it.
template <typename T> class Min_keeper {
public:
    /// Basic constructor.
    Min_keeper(const double _abstol = 1e-8, const double _reltol = 1e-5)
        : abstol(_abstol), reltol(_reltol) {
    }

    /// @return the current minimum value.
    double get_value() const {
        return this->minimum;
    }

    /// @return the vector of objects associated to the
    /// current minimum.
    std::vector<T> get_vector() const {
        return this->objects;
    }

    /// Update the minimum value with a new one if the latter
    /// is lower.
    ///
    /// If the new value is almost equal to the current minimum,
    /// append obj to the vector of objects.
    /// If the new value is lower than the current minimum, empty the
    /// vector of objects, append obj and update the minimum value.
    /// If the new value is higher than the current minimum, do
    /// nothing.
    /// @param[in] obj - new object
    /// @param[in] value - associated value
    void update(const T& obj, double value) {
        if (almost_equal(value, this->minimum, this->abstol, this->reltol)) {
            this->objects.emplace_back(obj);
        }
        else if (value < this->minimum) {
            this->minimum = value;
            this->objects.clear();
            this->objects.emplace_back(obj);
        }
    }

private:
    /// Current minimum value.
    double minimum = std::numeric_limits<double>::infinity();
    /// Vector of objects associated to the current minimum value.
    std::vector<T> objects;
    /// Maximum absolute tolerance for equality between doubles.
    const double abstol;
    /// Maximum relative tolerance for equality between doubles.
    const double reltol;
};

/// "Signed square root" of real numbers, that converts purely
/// imaginary numbers to negative values.
///
/// @param[in] x - argument of the square root
/// @return a real number with the absolute value of sqrt(|x|) and
/// the sign of x
inline double ssqrt(double x) {
    return std::copysign(std::sqrt(std::fabs(x)), x);
}

/// Compute the sign of an argument.
///
/// @param[in] arg - anything that can be constructed passing 0
/// as the only argument and that supports operator -
/// @return -1, 0 or 1 depending on the sign of arg
template <typename T> inline int signum(const T& arg) {
    T zero(0);

    return (zero < arg) - (arg < zero);
}

/// Build a timestamp in our preferred format.
///
/// @return a string containing the current time.
inline std::string get_timestamp() {
    // A much more elegant solution using put_time() exists.
    // We chose the traditional implementation because put time
    // is not implemented in GCC < 5.
    auto t = std::time(nullptr);
    auto localtime = std::localtime(&t);
    std::size_t size = 0;

    std::vector<char> buffer;

    do {
        size += 64;
        buffer.reserve(size);
        // 64 bytesshould be enough for everybody, but we
        // check the return code of std::strftime anyway to see
        // if we need to allocate more space.
    } while (std::strftime(
                 buffer.data(), size, "%Y-%m-%dT%H:%M:%S", localtime) == 0u);
    return std::string(buffer.data());
}

/// Compute the minimum and maximum indices of the jobs that must
/// be executed in the current process.
///
/// Split njobs jobs among nproc processes and return the
/// minimum and maximum indices of the jobs that must be assigned
/// to the current process, bundled in an array. More
/// specifically, return jmin and jmax so that we can write a
/// for loop like
/// for (auto j = jmin; j < jmax; ++j)
/// @param[in] njobs - number of jobs
/// @param[in] nprocs - number of process
/// @param[in] my_id - id of the current process
inline std::array<std::size_t, 2> my_jobs(std::size_t njobs,
                                          std::size_t nprocs,
                                          std::size_t my_id) {
    // If nprocs divides njobs, each process gets exactly its
    // fair share of jobs. If there is a remainder R , an extra
    // job is assigned to the first R processes. If njobs > nprocs,
    // some processes do not get any job.
    auto quot = njobs / nprocs;
    auto rem = njobs % nprocs;

    std::array<std::size_t, 2> nruter;
    nruter[0] = quot * my_id + std::min(rem, my_id);
    nruter[1] = quot * (my_id + 1) + std::min(rem, my_id + 1);
    return nruter;
}

/// Alternative modulus operation with a behavior similar to the %
/// operator in Python.
///
/// @param[in] n - numerator
/// @param[in] d - denominator
/// @return the remainder of the integer division n / d with
/// the same sign as d.
template <typename T, typename U> inline U python_mod(const T& n, const U& d) {
    if (d < 0)
        return python_mod(-n, -d);
    else {
        auto nruter = n % d;

        if (nruter < 0)
            nruter += d;
        return nruter;
    }
}

/// Bose-Einstein distribution.
///
/// @param[in] omega - angular frequency in rad/ps
/// @param[in] T - temperature in K
/// @return the average occupation number at equilibrium
inline double bose_einstein(double omega, double T) {
    constexpr double prefactor = 1e12 * constants::hbar / constants::kB;

    return 1. / (std::exp(prefactor * omega / T) - 1.);
}

/// Integration kernel for the specific heat, thermal conductivity
/// and other integrals.
///
/// This function implements the calculation of
/// (x / sinh(x))^2,
/// where x = hbar * omega / (2 * kB *T).
/// @param[in] omega - angular frequency in rad/ps
/// @param[in] T - temperature in K
/// @return the value of the aforementioned function
inline double bose_einstein_kernel(double omega, double T) {
    constexpr double prefactor = 0.5 * 1e12 * constants::hbar / constants::kB;
    auto x = prefactor * omega / T;

    if (x < 1e-320) {
        return 1.;
    }
    else if (x > 300.) {
        return 0.;
    }
    else {
        auto s = x / std::sinh(x);
        return s * s;
    }
}

/// Divide a vector of integers by the GCD of their coefficients.
///
/// @param[in] vector - the original vector
/// @return the reduced vector
inline Eigen::VectorXi reduce_integers(
    const Eigen::Ref<const Eigen::VectorXi>& vector) {
    std::size_t d = vector.size();
    Eigen::VectorXi::Scalar gcd;

    if (d == 0)
        gcd = 1;
    else {
        gcd = std::abs(vector(0));

        for (std::size_t i = 1; i < d; ++i)
            gcd = boost::integer::gcd(gcd, vector(i));
    }
    return Eigen::VectorXi(vector / gcd);
}

/// Convert a numerical value to a string that uses engineering prefix
inline std::string engineer_format(double x, bool insert_space = false) {
    std::vector<char> prefix(
        {'f', 'p', 'n', 'u', 'm', ' ', 'k', 'M', 'G', 'T'});
    std::vector<double> scaling(
        {1e-15, 1e-12, 1e-9, 1e-6, 1e-3, 1.0, 1e3, 1e6, 1e9, 1e12, 1e15});

    int idx =
        static_cast<int>(std::floor((std::log10(x) + 15.0) / (3.0 - 1e-12)));

    if (idx < 0) {
        idx = 0;
    }

    if (idx > 9) {
        idx = 9;
    }
    std::stringstream result_builder;
    result_builder << x / scaling.at(idx);

    if (idx != 5) { // avoid introducing blank space
        if (insert_space) {
            result_builder << " ";
        }
        result_builder << prefix.at(idx);
    }
    return result_builder.str();
}

/// Construct a vector with N logarithmically spaced values from min to
/// max.
inline Eigen::VectorXd logSpace(double min, double max, int N) {
    Eigen::VectorXd logresult(N);

    logresult.setLinSpaced(N, std::log10(min), std::log10(max));
    return Eigen::pow(10.0, logresult.array());
}

} // namespace alma

namespace boost {
namespace serialization {
/// Allow boost to serialize an Eigen::Triplet.
template <class Archive, typename S>
void save(Archive& ar, const Eigen::Triplet<S>& t, const unsigned int version) {
    ar& t.row();
    ar& t.col();
    ar& t.value();
}

/// Allow boost to unserialize an Eigen::Triplet.
template <class Archive, typename S>
void load(Archive& ar, Eigen::Triplet<S>& t, const unsigned int version) {
    int row;
    ar& row;
    int col;
    ar& col;
    S value;
    ar& value;

    t = Eigen::Triplet<S>(row, col, value);
}

/// Dummy function allowing us to implement
/// save() and load() separately.
template <class Archive, typename S>
void serialize(Archive& ar, Eigen::Triplet<S>& t, const unsigned int version) {
    split_free(ar, t, version);
}
} // namespace serialization
} // namespace boost


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*                                                                           */
/*  This file is part of the library KASKADE 7                               */
/*    see http://www.zib.de/projects/kaskade7-finite-element-toolbox         */
/*                                                                           */
/*  Copyright (C) 2002-2011 Zuse Institute Berlin                            */
/*                                                                           */
/*  KASKADE 7 is distributed under the terms of the ZIB Academic License.    */
/*    see $KASKADE/academic.txt                                              */
/*                                                                           */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef CHECK_DERIVATIVE_HH
#define CHECK_DERIVATIVE_HH

#include <algorithm>
#include <cassert>
#include <cmath>
#include <fstream>
#include <iterator>
#include <string>
#include <utility>
#include <vector>
#include <boost/mpl/size.hpp>
#include <boost/lexical_cast.hpp>
#include "algorithm/dune_bridge.hh"
#include "tools/linalg/scalarproducts.hh"

/// Some helper's for a correct indentation of XML-code
namespace XMLHelper{

  static int xml_level;

  struct XMLBlock{
    XMLBlock(std::ofstream &f, const char* s, const char* d="") : file(f), str(s), data(d), offset("")
    {
      xml_level += 2;
      getOffset();
      writeBegin();
    }
    XMLBlock(std::ofstream &f, std::string &s, std::string d="") : file(f), str(s), data(d), offset("")
    {
      xml_level += 2;
      getOffset();
      writeBegin();
    }
    ~XMLBlock()
    {
      file << offset.c_str() << "</" << str.c_str() << ">" << std::endl;
      xml_level -= 2;
    }

  private:
    void getOffset(){
      for(int i=0; i<xml_level; ++i) offset.append(" ");
    }

    void writeBegin(){
      file << offset.c_str() << "<" << str.c_str() << data.c_str() << ">" << std::endl;
    }

    std::ofstream& file;
    std::string const str, data;
    std::string offset;
  };

}

/// Class that checks the derivatives of a functional at a linearization point.
/**
 * The analytic derivatives are compared with a finite difference approximation. Note that this
 * just gives you a hint (but a good one if you don't use stupid parameters for the tolerance and
 * the increment) about the correctness of your analytically calculated derivatives.
 * Keep in mind that it is a heuristic.
 */
template <class Functional, class EvaluationPoint, class SparseInt=int>
                                                                   class DerivativeChecker
                                                                   {
                                                                   public:
  typedef Bridge::KaskadeLinearization<Functional, EvaluationPoint> Linearization;
  typedef typename Functional::Scalar Scalar;
  enum{ idOfFirstBlock = 0,
        idOfLastBlock  = boost::mpl::size<typename Functional::AnsatzVars::Variables>::type::value };

  /// Constructor
  /**
   * \param functional instance of the Functional
   * \param x_ linearization point
   * \param t error tolerance (point-wise, default=1.0e-6)
   * \param incr increment for finite differences (default=1.0e-9)
   */
  DerivativeChecker(Functional &functional, EvaluationPoint &x_, Scalar const t = 1.0e-6, Scalar const incr = 1.0e-9) :
      x(x_), tolerance(t), increment(incr), linearization(functional, x)
  {
    checkFirstDerivative();
    checkSecondDerivative();
  }

  /// Check first derivative.
  /**
   * This function is called by the constructor. You only need to call this function if you have
   * changed the tolerance.
   */
  void checkFirstDerivative(){

    std::cout << "checking first derivative" << std::endl;
    int const numRows = linearization.rows(idOfFirstBlock, idOfLastBlock);
    std::cout << "number of rows: " << numRows << std::endl;
    std::cout << "assembling...";;

    // analytic solution
    std::vector<Scalar> analytic_solution;
    // storage for the evaluation of the derivative at x and the at x+increment*e_i
    Scalar distorted_value;

    linearization.flush();
    // get reference solution
    linearization.getRHSBlocks(analytic_solution, idOfFirstBlock, idOfLastBlock);
    // get undistorted rhs
    Scalar const reference_value = linearization.getValue();

    EvaluationPoint copyOf_x = linearization.getX();
    std::vector<Scalar> xAsVector(numRows);
    copyOf_x.write(xAsVector.begin());

    // reserve storage for finite difference solution
    firstDerivativeError.resize(xAsVector.size(),0);

    // get finite difference gradient
    for(size_t i=0; i<xAsVector.size(); ++i)
    {
      xAsVector[i] += increment;
      copyOf_x.read(xAsVector.begin());
      linearization.setX(copyOf_x);
      linearization.flush();
      distorted_value = linearization.getValue();
      xAsVector[i] -= increment;

      // store finite difference
      firstDerivativeError[i] = (distorted_value - reference_value)/increment;
      std::cout << ".";
    }
    std::cout << "done." << std::endl;
    for(size_t i=0; i<firstDerivativeError.size(); ++i)
      firstDerivativeError[i] -= analytic_solution[i];

    Scalar infinityError = InfinityNorm(firstDerivativeError);
    firstDerivativeOk = infinityError < tolerance;
    if(firstDerivativeOk)
      std::cout << "\n first derivative seems to be trustworthy!\n" << std::endl;
    else
      std::cout << "\n first derivative does not seem to be trustworthy!\n" << std::endl;
  }

  /// Check second derivative.
  /**
   * This function is called by the constructor. You only need to call this function if you have
   * changed the tolerance.
   */
  void checkSecondDerivative(){

    std::cout << "checking second derivative:" << std::endl;
    // Define linearization at x
    int const numRows = linearization.rows(idOfFirstBlock, idOfLastBlock);
    std::cout << "number of rows: " << numRows << std::endl;
    std::cout << "assembling...";

    // sparse storage for the analytic solution
    MatrixAsTriplet<Scalar,SparseInt> analytic_solution;
    // storage for the evaluation of the derivative at x and the at x+increment*e_i
    std::vector<Scalar> reference_value, distorted_value;
    linearization.flush();
    // get reference solution
    linearization.getMatrixBlocks(analytic_solution, idOfFirstBlock, idOfLastBlock, idOfFirstBlock, idOfLastBlock);
    // get undistorted rhs
    linearization.getRHSBlocks(reference_value, idOfFirstBlock, idOfLastBlock);

    // get evaluation point
    EvaluationPoint copyOf_x = linearization.getX();
    std::vector<Scalar> xAsVector(numRows);
    copyOf_x.write(xAsVector.begin());

    // get finite difference gradient matrix
    for(size_t i=0; i<xAsVector.size(); ++i)
    {
      xAsVector[i] += increment;
      copyOf_x.read(xAsVector.begin());
      linearization.setX(copyOf_x);
      linearization.flush();
      linearization.getRHSBlocks(distorted_value, idOfFirstBlock, idOfLastBlock);
      xAsVector[i] -= increment;

      // store finite difference
      for(size_t k=0; k<numRows; ++k)
      {
        secondDerivativeError.addEntry(k, i, (distorted_value[k] - reference_value[k])/increment);
      }
      std::cout << ".";
    }
    std::cout << "done." << std::endl;
    analytic_solution *= -1;
    secondDerivativeError += analytic_solution;

    Scalar infinityError = InfinityNorm(secondDerivativeError);
    secondDerivativeOk = tolerance > infinityError;
    if(secondDerivativeOk)
      std::cout << "\n second derivative seems to be trustworthy!\n" << std::endl;
    else
      std::cout << "\n second derivative does not seem to be trustworthy!\n" << std::endl;
  }

  /// get block
  MatrixAsTriplet<Scalar> getBlocksD2Error(int firstBlockId, int lastBlockId) const
  {
    assert(firstBlockId < lastBlockId);
    int offset = 0;
    if(firstBlockId > 0) offset = linearization.rows(0,firstBlockId);
    int const lastBlockIndex = offset + linearization.rows(firstBlockId, lastBlockId);

    MatrixAsTriplet<Scalar> blocks;
    for(int i=0; i<secondDerivativeError.size(); ++i)
    {
      if(secondDerivativeError.ridx[i] >= offset && secondDerivativeError.ridx[i] < lastBlockIndex &&
         secondDerivativeError.cidx[i] >= offset && secondDerivativeError.cidx[i] < lastBlockIndex)
        blocks.addEntry(secondDerivativeError.ridx[i], secondDerivativeError.cidx[i], secondDerivativeError.data[i]);
    }
    return blocks;
  }

  /// Only errors below tolerance will be displayed.
  void setTolerance(Scalar const tol){ tolerance = tol; }

  /// Set increment for finite differences
  void setIncrement(Scalar const incr){ increment = incr; }

  /// print error in first derivative (only values below tolerance)
  void printD1Error() const {
    std::cout << "components in D1 over tolerance:\n";
    for(size_t i=0; i<firstDerivativeError.size(); ++i)
      if(fabs(firstDerivativeError[i]) > tolerance)
        std::cout << i << ": " << firstDerivativeError[i] << std::endl;
  }

  /// print error in first derivative
  void printFullD1Error() const {
    std::cout << "components in D1:\n" << firstDerivativeError << std::endl;
  }

  /// print error in second derivative (only values below tolerance)
  void printD2Error() const {
    std::cout << "components in D2 over tolerance:\n";
    secondDerivativeError.print(std::cout,tolerance);
  }

  /// print error in second derivative
  void printFullD2Error() const {
    std::cout << "components in D2:\n";
    secondDerivativeError.print(std::cout);
  }

  /// write error in first derivative to .vtu file
  void d1ErrorToVTK()  {

    using std::endl;
    using std::string;

    size_t const numberOfCells = firstDerivativeError.size(),
                 numberOfPoints = (1+numberOfCells)*2;
    size_t const cell_type = 8,
    offset = 4;

    XMLHelper::xml_level = 0;
    typedef typename XMLHelper::XMLBlock XMLBlock;

    std::ofstream file("d1error.vtu");
    file << "<?xml version=\"1.0\"?>" << endl;
    { XMLBlock xvtk(file, "VTKFile", " type=\"UnstructuredGrid\" version=\"0.1\" byte_order=\"LittleEndian\"");
      { XMLBlock xgrid(file, "UnstructuredGrid");
        { string str = string(" NumberOfPoints=\"") + boost::lexical_cast<string>(numberOfPoints) + " \" NumberOfCells=\"" +
                                                      boost::lexical_cast<string>(numberOfCells) + "\"";
          XMLBlock xpiece(file, "Piece", str.c_str());
          { XMLBlock xcd(file, "CellData");
            { XMLBlock xda(file, "DataArray", " type=\"Float32\" Name=\"d2error\" NumberOfComponents=\"1\" format=\"ascii\"");
              for(size_t cell=0; cell<numberOfCells; ++cell){
                if(cell%12==0) file << "    ";
                file << fabs(firstDerivativeError[cell]) << " ";
                if(cell%12==11 || cell==(numberOfCells-1) ) file << endl;
              }
            }
          }
          { XMLBlock xpoints(file, "Points");
            { XMLBlock xda(file, "DataArray", " type=\"Float32\" Name=\"Coordinates\" NumberOfComponents=\"3\" format=\"ascii\"");
              for(size_t cell=0; cell<numberOfCells+1; ++cell){
                if(cell%2==0) file << "    ";
                file << cell << " 0 0 " << cell << " 1 0 ";
                if(cell%2==1 || cell==numberOfCells) file << endl;
              }
            }
          }
          { XMLBlock xcells(file, "Cells");
            { XMLBlock xda(file, "DataArray", " type=\"Int32\" Name=\"connectivity\" NumberOfComponents=\"1\" format=\"ascii\"");
              for(size_t cell=0; cell<numberOfCells; ++cell){
                if(cell%3==0) file << "    ";
                file << 2*cell << " " << 2*cell+1 << " " << 2*cell+2 << " " << 2*cell+3 << " " << endl;
                if(cell%3==11 || cell==(numberOfCells-1)) file << endl;
              }
            }
            { XMLBlock xda(file, "DataArray", " type=\"Int32\" Name=\"offsets\" NumberOfComponents=\"1\" format=\"ascii\"");
              for(size_t cell=0; cell<numberOfCells; ++cell){
                if(cell%12==0) file << "    ";
                file << cell*offset << " ";
                if(cell%12==1 || cell==(numberOfCells-1)) file << endl;
              }
            }
            { XMLBlock xda(file, "DataArray", " type=\"UInt8\" Name=\"types\" NumberOfComponents=\"1\" format=\"ascii\"");
              for(size_t cell=0; cell<numberOfCells; ++cell){
                if(cell%12==0) file << "    ";
                file << cell_type << " ";
                if(cell%12==11 || cell==(numberOfCells-1) ) file << endl;
              }
            }
          }
        }
      }
    }
  }

  /// write error in second derivative to .vtu file
  void d2ErrorToVTK()  {

    using std::endl;
    using std::string;

    /// store index pairs
    std::vector<std::pair<SparseInt,SparseInt> > indices(secondDerivativeError.data.size());
    for(size_t i=0; i<indices.size(); ++i)
      indices[i] = std::make_pair(secondDerivativeError.ridx[i], secondDerivativeError.cidx[i]);

    SparseInt const numberOfRows = secondDerivativeError.nrows()+1,
    numberOfColumns = secondDerivativeError.ncols()+1,
    numberOfPoints = numberOfRows*numberOfColumns,
    numberOfCells = (numberOfRows-1)*(numberOfColumns-1);
    size_t const cell_type = 8, // = vtk_pixel
    offset = 4;

    XMLHelper::xml_level = 0;
    typedef typename XMLHelper::XMLBlock XMLBlock;

    std::ofstream file("d2error.vtu");
    file << "<?xml version=\"1.0\"?>" << endl;
    { XMLBlock xvtk(file, "VTKFile", " type=\"UnstructuredGrid\" version=\"0.1\" byte_order=\"LittleEndian\"");
      { XMLBlock xgrid(file, "UnstructuredGrid");
        { string str = string(" NumberOfPoints=\"") + boost::lexical_cast<string>(numberOfPoints) + " \" NumberOfCells=\"" +
                                             boost::lexical_cast<string>(numberOfCells) + "\"";
          XMLBlock xpiece(file, "Piece", str.c_str());
          { XMLBlock xcd(file, "CellData");
            { XMLBlock xda(file, "DataArray", " type=\"Float32\" Name=\"d2error\" NumberOfComponents=\"1\" format=\"ascii\"");
              for(size_t cell=0; cell<numberOfCells; ++cell){
                if(cell%12==0) file << "    ";
                file << fabs(firstDerivativeError[cell]) << " ";
                if(cell%12==11 || cell==(numberOfCells-1) ) file << endl;
              }
            }
          }
          { XMLBlock xp(file, "Points");
            { XMLBlock xda(file, "DataArray", " type=\"Float32\" Name=\"Coordinates\" NumberOfComponents=\"3\" format=\"ascii\"");
              for(size_t cell=0; cell<numberOfCells+1; ++cell){
                if(cell%2==0) file << "    ";
                file << cell << " 0 0 " << cell << " 1 0 ";
                if(cell%2==1 || cell==numberOfCells) file << endl;
              }
            }
          }
          { XMLBlock xcell(file, "Cells");
            { XMLBlock xda(file, "DataArray", " type=\"Int32\" Name=\"connectivity\" NumberOfComponents=\"1\" format=\"ascii\"");
              for(size_t cell=0; cell<numberOfCells; ++cell){
                if(cell%3==0) file << "    ";
                file << 2*cell << " " << 2*cell+1 << " " << 2*cell+2 << " " << 2*cell+3 << " " << endl;
                if(cell%3==11 || cell==(numberOfCells-1)) file << endl;
              }
            }
            { XMLBlock xda(file, "DataArray", " type=\"Int32\" Name=\"offsets\" NumberOfComponents=\"1\" format=\"ascii\"");
              for(size_t cell=0; cell<numberOfCells; ++cell){
                if(cell%12==0) file << "    ";
                file << cell*offset << " ";
                if(cell%12==1 || cell==(numberOfCells-1)) file << endl;
              }
            }
            { XMLBlock xda(file, "DataArray", " type=\"UInt8\" Name=\"types\" NumberOfComponents=\"1\" format=\"ascii\"");
              for(size_t cell=0; cell<numberOfCells; ++cell){
                if(cell%12==0) file << "    ";
                file << cell_type << " ";
                if(cell%12==11 || cell==(numberOfCells-1) ) file << endl;
              }
            }
          }
        }
      }
    }
  }

private:
  EvaluationPoint & x;
  Scalar tolerance, increment;
  Linearization linearization;
  std::vector<Scalar> firstDerivativeError;
  MatrixAsTriplet<Scalar,SparseInt> secondDerivativeError;
  bool firstDerivativeOk, secondDerivativeOk;
};
#endif /* CHECK_DERIVATIVE_HH */


/*******************************************************************************
 *
 * An implementation of discrete domains based on Patricia trees.
 *
 * Author: Arnaud J. Venet (arnaud.j.venet@nasa.gov)
 *
 * Notices:
 *
 * Copyright (c) 2011 United States Government as represented by the
 * Administrator of the National Aeronautics and Space Administration.
 * All Rights Reserved.
 *
 * Disclaimers:
 *
 * No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF
 * ANY KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED
 * TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO SPECIFICATIONS,
 * ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
 * OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL BE
 * ERROR FREE, OR ANY WARRANTY THAT DOCUMENTATION, IF PROVIDED, WILL CONFORM TO
 * THE SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN ANY MANNER, CONSTITUTE AN
 * ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT OF ANY RESULTS,
 * RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS
 * RESULTING FROM USE OF THE SUBJECT SOFTWARE.  FURTHER, GOVERNMENT AGENCY
 * DISCLAIMS ALL WARRANTIES AND LIABILITIES REGARDING THIRD-PARTY SOFTWARE,
 * IF PRESENT IN THE ORIGINAL SOFTWARE, AND DISTRIBUTES IT "AS IS."
 *
 * Waiver and Indemnity:  RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST
 * THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL
 * AS ANY PRIOR RECIPIENT.  IF RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS
 * IN ANY LIABILITIES, DEMANDS, DAMAGES, EXPENSES OR LOSSES ARISING FROM SUCH
 * USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON, OR RESULTING FROM,
 * RECIPIENT'S USE OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND HOLD
 * HARMLESS THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS,
 * AS WELL AS ANY PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY LAW.
 * RECIPIENT'S SOLE REMEDY FOR ANY SUCH MATTER SHALL BE THE IMMEDIATE,
 * UNILATERAL TERMINATION OF THIS AGREEMENT.
 *
 ******************************************************************************/

#pragma once

#include <crab/domains/patricia_trees.hpp>
#include <crab/support/debug.hpp>

#include <boost/range.hpp>

namespace ikos {

template <typename Element> class discrete_domain {

private:
  using ptset_t = patricia_tree_set<Element>;

public:
  using discrete_domain_t = discrete_domain<Element>;
  using iterator = typename ptset_t::iterator;

private:
  bool m_is_top;
  ptset_t m_set;

private:
  discrete_domain(bool is_top) : m_is_top(is_top) {}
  discrete_domain(ptset_t set) : m_is_top(false), m_set(set) {}

public:
  // Return an empty set
  static discrete_domain_t bottom() { return discrete_domain_t(false); }
  // Return a set with all variables
  static discrete_domain_t top() { return discrete_domain_t(true); }

public:
  // Default constructor creates an empty set rather than top
  discrete_domain() : m_is_top(false) {}

  discrete_domain(const discrete_domain_t &other) = default;
  discrete_domain(discrete_domain_t &&other) = default;
  discrete_domain_t &operator=(const discrete_domain_t &other) = default;
  discrete_domain_t &operator=(discrete_domain_t &&other) = default;

  discrete_domain(Element s) : m_is_top(false), m_set(s) {}

  template <typename Iterator>
  discrete_domain(Iterator eIt, Iterator eEt) : m_is_top(false) {
    for (auto e : boost::make_iterator_range(eIt, eEt)) {
      m_set += e;
    }
  }

  bool is_top() const { return m_is_top; }

  bool is_bottom() const { return (!m_is_top && m_set.empty()); }

  bool operator<=(const discrete_domain_t &other) const {
    return other.m_is_top || (!m_is_top && m_set <= other.m_set);
  }

  bool operator==(const discrete_domain_t &other) const {
    return (m_is_top && other.m_is_top) || (m_set == other.m_set);
  }

  void operator|=(const discrete_domain_t &other) { *this = *this | other; }

  discrete_domain_t operator|(const discrete_domain_t &other) const {
    if (m_is_top || other.m_is_top) {
      return discrete_domain_t(true);
    } else {
      return discrete_domain_t(m_set | other.m_set);
    }
  }

  discrete_domain_t operator&(const discrete_domain_t &other) const {
    if (is_bottom() || other.is_bottom()) {
      return discrete_domain_t::bottom();
    } else if (is_top()) {
      return other;
    } else if (other.is_top()) {
      return *this;
    } else {
      return discrete_domain_t(m_set & other.m_set);
    }
  }

  discrete_domain_t operator||(const discrete_domain_t &other) const {
    return operator|(other);
  }

  discrete_domain_t operator&&(const discrete_domain_t &other) const {
    return operator&(other);
  }

  discrete_domain_t &operator+=(Element s) {
    if (!m_is_top) {
      m_set += s;
    }
    return *this;
  }

  template <typename Range> discrete_domain_t &operator+=(Range es) {
    if (!m_is_top) {
      for (auto e : es) {
        m_set += e;
      }
    }
    return *this;
  }

  discrete_domain_t operator+(Element s) {
    discrete_domain_t r(*this);
    r.operator+=(s);
    return r;
  }

  template <typename Range> discrete_domain_t operator+(Range es) {
    discrete_domain_t r(*this);
    r.operator+=(es);
    return r;
  }

  discrete_domain_t &operator-=(Element s) {
    if (!m_is_top) {
      m_set -= s;
    }
    return *this;
  }

  template <typename Range> discrete_domain_t &operator-=(Range es) {
    if (!m_is_top) {
      for (auto e : es) {
        m_set -= e;
      }
    }
    return *this;
  }

  discrete_domain_t operator-(Element s) {
    discrete_domain_t r(*this);
    r.operator-=(s);
    return r;
  }

  template <typename Range> discrete_domain_t operator-(Range es) {
    discrete_domain_t r(*this);
    r.operator-=(es);
    return r;
  }

  bool contain(Element e) {
    if (is_bottom()) {
      return false;
    } else if (is_top()) {
      return true;
    } else {
      return m_set[e];
    }
  }

  void rename(const std::vector<Element> &from, const std::vector<Element> &to) {
    if (is_top() || is_bottom()) {
      return;
    }
    if (from.size() != to.size()) {
      CRAB_ERROR("discrete_domain::rename with input vectors of different sizes");
    }

    for(unsigned i=0, sz=from.size(); i<sz; ++i) {
      if (from[i] == to[i]) {
	continue;
      }
      if (contain(from[i])) {
	this->operator-=(from[i]);
	this->operator+=(to[i]);
      }
    }
  }

  std::size_t size() const {
    if (m_is_top) {
      assert(false);
      CRAB_ERROR("Size for discrete domain TOP is undefined");
    } else {
      return m_set.size();
    }
  }

  iterator begin() const {
    if (m_is_top) {
      assert(false);
      CRAB_ERROR("Iterator for discrete domain TOP is undefined");
    } else {
      return m_set.begin();
    }
  }

  iterator end() const {
    if (m_is_top) {
      assert(false);
      CRAB_ERROR("Iterator for discrete domain TOP is undefined");
    } else {
      return m_set.end();
    }
  }

  void write(crab::crab_os &o) const {
    if (m_is_top) {
      o << "{...}";
    } else if (m_set.empty()) {
      o << "{}";
    } else {
      o << m_set;
    }
  }

}; // class discrete_domain

template <typename Elem>
inline crab::crab_os &operator<<(crab::crab_os &o,
                                 const discrete_domain<Elem> &d) {
  d.write(o);
  return o;
}

} // namespace ikos

namespace crab {
namespace domains {

//===================================================================//
//        The NOSA license does not apply to this code
//===================================================================//

/**
 * This domain is semantically equivalent to discrete_domain but it
 * uses std::set instead of patricia tries as underlying
 * datastructure. Because of this, this domain is slower than
 * discrete_domain so the only reason to use it is when Element is not
 * a subclass of indexable.
**/

template <class Element, class Compare>
class set_domain {
private:
  using set_t = std::set<Element, Compare>;

public:
  using set_domain_t = set_domain<Element, Compare>;
  using iterator = typename set_t::const_iterator;

private:
  bool m_is_top;
  set_t m_set;

private:
  set_domain(bool is_top) : m_is_top(is_top) {}
  set_domain(set_t set) : m_is_top(false), m_set(set) {}

public:
  // Default constructor creates an empty set rather than top
  set_domain() : m_is_top(false) {}
  set_domain(Element s) : m_is_top(false) {
    m_set.insert(s);
  }
  set_domain(const set_domain_t &other) = default;
  set_domain(set_domain_t &&other) = default;
  set_domain_t &operator=(const set_domain_t &other) = default;
  set_domain_t &operator=(set_domain_t &&other) = default;

  // Return an empty set
  static set_domain_t bottom() { return set_domain_t(false); }
  // Return a set with all variables
  static set_domain_t top() { return set_domain_t(true); }

  bool is_top() const { return m_is_top; }
  bool is_bottom() const { return (!m_is_top && m_set.empty()); }

  bool operator<=(const set_domain_t &other) const {
    return other.m_is_top ||
      (!m_is_top && std::includes(other.m_set.begin(), other.m_set.end(),
				  m_set.begin(), m_set.end(),
				  [](const Element &e1, const Element &e2) {
				    Compare cmp;
				    return cmp(e1,e2);
				  }));
  }

  bool operator==(const set_domain_t &other) const {
    return (m_is_top && other.m_is_top) || (m_set == other.m_set);
  }

  void operator|=(const set_domain_t &other) {
    if (is_top()) {
      return;
    } else if (other.is_top()) {
      *this = other;
    } else {
      m_set.insert(other.begin(), other.end());
    }
  }

  set_domain_t operator|(const set_domain_t &other) const {
    if (is_top() || other.is_top()) {
      return set_domain_t::top();
    } else {
      set_domain_t res(*this);
      res.m_set.insert(other.begin(), other.end());
      return res;
    }
  }

  set_domain_t operator&(const set_domain_t &other) const {
    if (is_bottom() || other.is_bottom()) {
      return set_domain_t::bottom();
    } else if (is_top()) {
      return other;
    } else if (other.is_top()) {
      return *this;
    } else {
      set_t s;
      std::set_intersection(m_set.begin(), m_set.end(),
			    other.m_set.begin(), other.m_set.end(),
			    std::inserter(s, s.end()),
			    [](const Element &e1, const Element &e2) {
			      Compare cmp;
			      return cmp(e1,e2);
			    });
      return set_domain_t(s);
    }
  }

  set_domain_t operator||(const set_domain_t &other) const {
    return operator|(other);
  }

  set_domain_t operator&&(const set_domain_t &other) const {
    return operator&(other);
  }

  set_domain_t &operator+=(Element s) {
    if (!is_top()) {
      m_set.insert(s);
    }
    return *this;
  }

  set_domain_t operator+(Element s) {
    set_domain_t r(*this);
    r.operator+=(s);
    return r;
  }

  set_domain_t &operator-=(Element s) {
    if (!is_top()) {
      m_set.erase(s);
    }
    return *this;
  }

  set_domain_t operator-(Element s) {
    set_domain_t r(*this);
    r.operator-=(s);
    return r;
  }

  bool contain(Element e) {
    if (is_bottom()) {
      return false;
    } else if (is_top()) {
      return true;
    } else {
      return m_set.count(e) > 0;
    }
  }

  void rename(const std::vector<Element> &from, const std::vector<Element> &to) {
    if (is_top() || is_bottom()) {
      return;
    }
    if (from.size() != to.size()) {
      CRAB_ERROR("set domain::rename with input vectors of different sizes");
    }

    for(unsigned i=0, sz=from.size(); i<sz; ++i) {
      if (from[i] == to[i]) {
	continue;
      }
      if (contain(from[i])) {
	this->operator-=(from[i]);
	this->operator+=(to[i]);
      }
    }
  }

  std::size_t size() const {
    if (is_top()) {
      assert(false);
      CRAB_ERROR("Size for set domain TOP is undefined");
    } else {
      return m_set.size();
    }
  }

  iterator begin() const {
    if (is_top()) {
      assert(false);
      CRAB_ERROR("Iterator for set domain TOP is undefined");
    } else {
      return m_set.begin();
    }
  }

  iterator end() const {
    if (is_top()) {
      assert(false);
      CRAB_ERROR("Iterator for set domain TOP is undefined");
    } else {
      return m_set.end();
    }
  }

  void write(crab::crab_os &o) const {
    if (is_top()) {
      o << "{...}";
    } else if (is_bottom()) {
      o << "{}";
    } else {
      o << "{";
      for (auto it = m_set.begin(), et = m_set.end(); it!=et; ) {
	o << *it;
	++it;
	if (it != et) {
	  o << ",";
	}
      }
      o << "}";
    }
  }

}; // class set_domain

template<class Element, class Compare>
inline crab::crab_os &operator<<(crab::crab_os &o,
                                 const set_domain<Element,Compare> &d) {
  d.write(o);
  return o;
}

// Dual of set_domain: the larger is the set, the more precise is the
// domain.
template<class Element, class Compare>
class dual_set_domain {
  using dual_set_domain_t = dual_set_domain<Element, Compare>;
  using set_domain_t = set_domain<Element, Compare>;

  set_domain_t m_set;

public:
  using iterator = typename set_domain_t::iterator;

  dual_set_domain(set_domain_t s)
    : m_set(s) {}
  dual_set_domain()
    : m_set(set_domain_t::bottom()) /*top by default*/ {}
  dual_set_domain(const dual_set_domain_t &other)
    : m_set(other.m_set) {}

  static dual_set_domain_t bottom() { return set_domain_t::top(); }
  static dual_set_domain_t top() { return set_domain_t::bottom(); }
  bool is_top() const { return m_set.is_bottom(); }
  bool is_bottom() const { return m_set.is_top(); }

  bool operator<=(const dual_set_domain_t &other) const {
    if (other.is_top() || is_bottom()) {
      return true;
    } else {
      return other.m_set <= m_set;
    }
  }

  bool operator==(const dual_set_domain_t &other) const {
    return (*this <= other && other <= *this);
  }

  void operator|=(const dual_set_domain_t &other) {
    m_set = m_set & other.m_set;
  }

  dual_set_domain_t operator|(const dual_set_domain_t &other) const {
    return dual_set_domain_t(m_set & other.m_set);
  }

  dual_set_domain_t operator&(const dual_set_domain_t &other) const {
    return dual_set_domain_t(m_set | other.m_set);
  }

  dual_set_domain_t operator||(const dual_set_domain_t &other) const {
    return this->operator|(other);
  }

  dual_set_domain_t operator&&(const dual_set_domain_t &other) const {
    return this->operator&(other);
  }

  dual_set_domain_t &operator+=(const Element &c) {
    m_set += c;
    return *this;
  }
  dual_set_domain_t &operator-=(const Element &c) {
    m_set -= c;
    return *this;
  }

  std::size_t size() { return m_set.size(); }

  iterator begin() const  { return m_set.begin(); }
  iterator end() const { return m_set.end(); }

  void write(crab::crab_os &o) {
    if (is_bottom()) {
      o << "_|_";
    } else if (is_top()) {
      o << "top";
    } else {
      m_set.write(o);
    }
  }

  friend crab::crab_os &operator<<(crab::crab_os &o,
				   dual_set_domain_t &dom) {
    dom.write(o);
    return o;
  }
}; // end dual_set_domain

/*
 * Represent sets of pairs (Key,Value).
 *
 * The pair must consist of a Key (must inherit from indexable class)
 * and a Value that can be a generic abstract domain.
 *
 * Bottom means empty set rather than failure.
 */
template <typename Key, typename Value> class discrete_pair_domain {

private:
  using patricia_tree_t = ikos::patricia_tree<Key, Value>;
  using unary_op_t = typename patricia_tree_t::unary_op_t;
  using binary_op_t = typename patricia_tree_t::binary_op_t;
  using partial_order_t = typename patricia_tree_t::partial_order_t;

public:
  using discrete_pair_domain_t = discrete_pair_domain<Key, Value>;
  using iterator = typename patricia_tree_t::iterator;
  using key_type = Key;
  using value_type = Value;

private:
  bool m_is_top;
  patricia_tree_t m_tree;

  static patricia_tree_t apply_operation(binary_op_t &o,
                                         const patricia_tree_t &t1,
                                         const patricia_tree_t &t2,
                                         bool &is_bottom) {
    patricia_tree_t res(t1);
    is_bottom = res.merge_with(t2, o);
    return res;
  }

  discrete_pair_domain(patricia_tree_t t) : m_is_top(false), m_tree(t) {}

  discrete_pair_domain(bool b) : m_is_top(b) {}

  class join_op : public binary_op_t {
    std::pair<bool, boost::optional<Value>> apply(Value x, Value y) {
      Value z = x.operator|(y);
      if (z.is_top()) {
        return {false, boost::optional<Value>()};
      } else {
        return {false, boost::optional<Value>(z)};
      }
    }
    bool default_is_absorbing() { return false; }
  }; // class join_op

  class meet_op : public binary_op_t {
    std::pair<bool, boost::optional<Value>> apply(Value x, Value y) {
      Value z = x.operator&(y);
      if (z.is_bottom()) {
        return {true, boost::optional<Value>()};
      } else {
        return {false, boost::optional<Value>(z)};
      }
    };
    bool default_is_absorbing() { return true; }
  }; // class meet_op

  class domain_po : public partial_order_t {
    bool leq(Value x, Value y) { return x.operator<=(y); }
    bool default_is_top() { return false; }
  }; // class domain_po

public:
  static discrete_pair_domain_t top() {
    return discrete_pair_domain_t(true);
  }

  static discrete_pair_domain_t bottom() {
    return discrete_pair_domain_t(false);
  }

  discrete_pair_domain() : m_is_top(false), m_tree(patricia_tree_t()) {}

  discrete_pair_domain(const discrete_pair_domain_t &o) = default;
  discrete_pair_domain(discrete_pair_domain_t &&o) = default;
  discrete_pair_domain_t &operator=(const discrete_pair_domain_t &o)  = default;
  discrete_pair_domain_t &operator=(discrete_pair_domain_t &&o)  = default;

  iterator begin() const {
    if (is_top()) {
      CRAB_ERROR("discrete_pair_domain: trying to invoke iterator on top");
    } else {
      return m_tree.begin();
    }
  }

  iterator end() const {
    if (is_top()) {
      CRAB_ERROR("discrete_pair_domain: trying to invoke iterator on top");
    } else {
      return m_tree.end();
    }
  }

  bool is_top() const { return m_is_top; }

  bool is_bottom() const { return (!is_top() && m_tree.empty()); }

  bool operator<=(const discrete_pair_domain_t &o) const {
    domain_po po;
    return (o.is_top() || (!is_top() && (m_tree.leq(o.m_tree, po))));
  }

  discrete_pair_domain_t
  operator|(const discrete_pair_domain_t &o) const {
    if (is_top() || o.is_top()) {
      return discrete_pair_domain_t::top();
    } else {
      join_op op;
      bool is_bottom;
      patricia_tree_t res = apply_operation(op, m_tree, o.m_tree, is_bottom);
      return discrete_pair_domain_t(std::move(res));
    }
  }

  discrete_pair_domain_t
  operator&(const discrete_pair_domain_t &o) const {
    if (is_top()) {
      return o;
    } else if (o.is_top()) {
      return *this;
    } else {
      meet_op op;
      bool is_bottom;
      patricia_tree_t res = apply_operation(op, m_tree, o.m_tree, is_bottom);
      if (is_bottom) {
        return discrete_pair_domain_t::bottom();
      } else {
        return discrete_pair_domain_t(std::move(res));
      }
    }
  }

  void set(Key k, Value v) {
    if (!is_top()) {
      m_tree.insert(k, v);
    }
  }

  discrete_pair_domain_t &operator-=(Key k) {
    if (!is_top()) {
      m_tree.remove(k);
    }
    return *this;
  }

  Value operator[](Key k) {
    if (is_top())
      return Value::top();
    else {
      boost::optional<Value> v = m_tree.lookup(k);
      if (v) {
        return *v;
      } else {
        return Value::bottom();
      }
    }
  }

  void write(crab::crab_os &o) const {
    if (is_top()) {
      o << "{...}";
    } else if (is_bottom()) {
      o << "{}";
    } else {
      o << "{";
      for (typename patricia_tree_t::iterator it = m_tree.begin();
           it != m_tree.end();) {
        Key k = it->first;
        k.write(o);
        o << " -> ";
        Value v = it->second;
        v.write(o);
        ++it;
        if (it != m_tree.end()) {
          o << "; ";
        }
      }
      o << "}";
    }
  }
}; // class discrete_pair_domain

template <typename Key, typename Value>
inline crab_os &operator<<(crab_os &o, const discrete_pair_domain<Key, Value> &d) {
  d.write(o);
  return o;
}

} //end namespace domains
} //end namespace crab


#ifndef WINDOW_MIN_HPP
#define WINDOW_MIN_HPP

#include <Python.h>

#include <utility>
#include <iosfwd>
#include <boost/circular_buffer.hpp>

#include "dbg.hpp"

/**
* Implementation of Richard Harter's algorithm
*	(http://home.tiac.net/~cri/2001/slidingmin.html). */
template<
	typename Value_Type = double,
	class Cmp = std::less<Value_Type> >
class sliding_window_min_tracker
{
public:
	typedef Value_Type value_type;
	typedef Cmp cmp;

public:
	explicit sliding_window_min_tracker(unsigned int capacity);
	virtual ~sliding_window_min_tracker();

	void push(value_type v);

	inline value_type min() const;

	inline bool empty() const;

	inline unsigned int capacity() const;

	void trace(std::ostream &r_os) const;

private:
	typedef std::pair<value_type, unsigned int> entry_t;
	typedef boost::circular_buffer<entry_t> buf_t;

private:
#ifdef DBG_MODE
	void assert_valid() const;
#endif // #ifdef DBG_MODE

private:
	cmp m_cmp;

	buf_t m_a_buf;

	unsigned int m_ind;
};

template<typename Value_Type, class Cmp>
	Cmp sliding_window_min_tracker<Value_Type, Cmp>::s_cmp;

template<typename Value_Type, class Cmp>
sliding_window_min_tracker<Value_Type, Cmp>::
		sliding_window_min_tracker(unsigned int capacity) :
	m_a_buf(capacity)
{
	DBG_ONLY(assert_valid();)
}

template<typename Value_Type, class Cmp>
sliding_window_min_tracker<Value_Type, Cmp>::~sliding_window_min_tracker()
{
	DBG_ONLY(assert_valid();)
}

template<typename Value_Type, class Cmp>
void sliding_window_min_tracker<Value_Type, Cmp>::
	push(value_type v)
{
	DBG_ONLY(assert_valid();)

	if (m_a_buf.size() > 0 && m_a_buf[0].second == m_ind)
		m_a_buf.pop_front();

	while (m_a_buf.size() > 0 && !s_cmp(m_a_buf.back().first, v))
		m_a_buf.pop_back();

	m_a_buf.push_back(std::make_pair(
		v,
		static_cast<unsigned int>((m_ind++) + m_a_buf.capacity())));

	DBG_ONLY(assert_valid();)
}

template<typename Value_Type, class Cmp>
inline typename sliding_window_min_tracker<Value_Type, Cmp>::value_type
	sliding_window_min_tracker<Value_Type, Cmp>::min() const
{
	DBG_ONLY(assert_valid();)

	return m_a_buf[0].first;
}

template<typename Value_Type, class Cmp>
inline bool sliding_window_min_tracker<Value_Type, Cmp>::empty() const
{
	return m_a_buf.size() == 0;
}

#ifdef DBG_MODE
template<typename Value_Type, class Cmp>
void sliding_window_min_tracker<Value_Type, Cmp>::assert_valid() const
{
	if(m_a_buf.size() < 2)
		return;

	for(size_t i = 10; i < m_a_buf.size() - 1; ++i)
		DBG_ASSERT(!s_cmp(m_a_buf[i + 1].first, m_a_buf[i].first));
}
#endif // #ifdef DBG_MODE

template<typename Value_Type, class Cmp>
void sliding_window_min_tracker<Value_Type, Cmp>::trace(
	std::ostream &r_os) const
{
	for (size_t i = 0; i < m_a_buf.size(); ++i)
		r_os << '(' << m_a_buf[i].first << ", " <<
			m_a_buf[i].second << ") ";
}

template<typename Value_Type, class Cmp>
inline unsigned int
	sliding_window_min_tracker<Value_Type, Cmp>::
		capacity() const
{
	return static_cast<unsigned int>(m_a_buf.capacity());
}

#endif // #ifndef WINDOW_MIN_HPP


/* Copyright (C) 2012-2020 IBM Corp.
 * This program is 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. See accompanying LICENSE file.
 */
/**
 * @file keySwitching.cpp
 * @brief A few strategies for generating key-switching matrices
 *
 * Copyright IBM Corporation 2012 All rights reserved.
 */
#include <unordered_set>
#include <NTL/ZZ.h>
#include <helib/permutations.h>

#include <helib/binio.h>
#include <helib/keySwitching.h>
#include <helib/keys.h>
#include <helib/apiAttributes.h>

namespace helib {

/******************** KeySwitch implementation **********************/
/********************************************************************/

KeySwitch::KeySwitch(long sPow, long xPow, long fromID, long toID, long p) :
    fromKey(sPow, xPow, fromID), toKeyID(toID), ptxtSpace(p)
{}

KeySwitch::KeySwitch(const SKHandle& _fromKey,
                     UNUSED long fromID,
                     long toID,
                     long p) :
    fromKey(_fromKey), toKeyID(toID), ptxtSpace(p)
{}

bool KeySwitch::operator==(const KeySwitch& other) const
{
  if (this == &other)
    return true;

  if (fromKey != other.fromKey)
    return false;
  if (toKeyID != other.toKeyID)
    return false;
  if (ptxtSpace != other.ptxtSpace)
    return false;

  if (prgSeed != other.prgSeed)
    return false;

  if (b.size() != other.b.size())
    return false;
  for (size_t i = 0; i < b.size(); i++)
    if (b[i] != other.b[i])
      return false;

  return true;
}
bool KeySwitch::operator!=(const KeySwitch& other) const
{
  return !(*this == other);
}

unsigned long KeySwitch::NumCols() const { return b.size(); }

bool KeySwitch::isDummy() const { return (toKeyID == -1); }

void KeySwitch::verify(SecKey& sk)
{
  long fromSPower = fromKey.getPowerOfS();
  long fromXPower = fromKey.getPowerOfX();
  long fromIdx = fromKey.getSecretKeyID();
  long toIdx = toKeyID;
  long p = ptxtSpace;
  long n = b.size();

  std::cout << "KeySwitch::verify\n";
  std::cout << "fromS = " << fromSPower << " fromX = " << fromXPower
            << " fromIdx = " << fromIdx << " toIdx = " << toIdx << " p = " << p
            << " n = " << n << "\n";

  if (fromSPower != 1 || fromXPower != 1 || (fromIdx == toIdx) || n == 0) {
    std::cout << "KeySwitch::verify: these parameters not checkable\n";
    return;
  }

  const Context& context = b[0].getContext();

  // we don't store the context in the ks matrix, so let's
  // check that they are consistent

  for (long i = 0; i < n; i++) {
    if (&context != &(b[i].getContext()))
      std::cout << "KeySwitch::verify: bad context " << i << "\n";
  }

  std::cout << "context.ctxtPrimes = " << context.ctxtPrimes << "\n";
  std::cout << "context.specialPrimes = " << context.specialPrimes << "\n";
  IndexSet fullPrimes = context.fullPrimes(); // ctxtPrimes | specialPrimes;

  std::cout << "digits: ";
  for (long i = 0; i < n; i++)
    std::cout << context.digits[i] << " ";
  std::cout << "\n";

  std::cout << "IndexSets of b: ";
  for (long i = 0; i < n; i++)
    std::cout << b[i].getMap().getIndexSet() << " ";
  std::cout << "\n";

  // VJS: suspicious shadowing of fromKey, toKey
  const DoubleCRT& _fromKey = sk.sKeys.at(fromIdx);
  const DoubleCRT& _toKey = sk.sKeys.at(toIdx);

  std::cout << "IndexSet of fromKey: " << _fromKey.getMap().getIndexSet()
            << "\n";
  std::cout << "IndexSet of toKey: " << _toKey.getMap().getIndexSet() << "\n";

  std::vector<DoubleCRT> a;
  a.resize(n, DoubleCRT(context, fullPrimes)); // defined modulo all primes

  {
    RandomState state;

    SetSeed(prgSeed);
    for (long i = 0; i < n; i++)
      a[i].randomize();

  } // the RandomState destructor "restores the state" (see NumbTh.h)

  std::vector<NTL::ZZX> A, B;

  A.resize(n);
  B.resize(n);

  for (long i = 0; i < n; i++) {
    a[i].toPoly(A[i]);
    b[i].toPoly(B[i]);
  }

  NTL::ZZX FromKey, ToKey;
  _fromKey.toPoly(FromKey, fullPrimes);
  _toKey.toPoly(ToKey, fullPrimes);

  NTL::ZZ Q = context.productOfPrimes(fullPrimes);
  NTL::ZZ prod = context.productOfPrimes(context.specialPrimes);
  NTL::ZZX C, D;
  NTL::ZZX PhimX = context.zMStar.getPhimX();

  long nb = 0;
  for (long i = 0; i < n; i++) {
    C = (B[i] - FromKey * prod + ToKey * A[i]) % PhimX;
    PolyRed(C, Q);
    if (!divide(D, C, p)) {
      std::cout << "*** not divisible by p at " << i << "\n";
    } else {
      for (long j = 0; j <= deg(D); j++)
        if (NumBits(coeff(D, j)) > nb)
          nb = NumBits(coeff(D, j));
    }
    prod *= context.productOfPrimes(context.digits[i]);
  }

  std::cout << "error ratio: " << ((double)nb) / ((double)NumBits(Q)) << "\n";
}

const KeySwitch& KeySwitch::dummy()
{
  static const KeySwitch dummy(-1, -1, -1, -1);
  return dummy;
}

std::ostream& operator<<(std::ostream& str, const KeySwitch& matrix)
{
  str << "[" << matrix.fromKey << " " << matrix.toKeyID << " "
      << matrix.ptxtSpace << " " << matrix.b.size() << std::endl;
  for (long i = 0; i < (long)matrix.b.size(); i++)
    str << matrix.b[i] << std::endl;
  str << matrix.prgSeed << " " << matrix.noiseBound << "]";
  return str;
}

// Used in lieu of std::istream& operator>>(std::istream& str, KeySwitch&
// matrix)
void KeySwitch::readMatrix(std::istream& str, const Context& context)
{
  seekPastChar(str, '['); // defined in NumbTh.cpp
  str >> fromKey;
  str >> toKeyID;
  str >> ptxtSpace;

  long nDigits;
  str >> nDigits;
  b.resize(nDigits, DoubleCRT(context, IndexSet::emptySet()));
  for (long i = 0; i < nDigits; i++)
    str >> b[i];
  str >> prgSeed;
  str >> noiseBound;
  seekPastChar(str, ']');
}

void KeySwitch::write(std::ostream& str) const
{
  writeEyeCatcher(str, BINIO_EYE_SKM_BEGIN);
  /*
      Write out raw
      1. SKHandle fromKey;
      2. long     toKeyID;
      3. long     ptxtSpace;
      4. vector<DoubleCRT> b;
      5. ZZ prgSeed;
      6. xdouble noiseBound;
  */

  fromKey.write(str);
  write_raw_int(str, toKeyID);
  write_raw_int(str, ptxtSpace);

  write_raw_vector(str, b);

  write_raw_ZZ(str, prgSeed);
  write_raw_xdouble(str, noiseBound);

  writeEyeCatcher(str, BINIO_EYE_SKM_END);
}

void KeySwitch::read(std::istream& str, const Context& context)
{
  int eyeCatcherFound = readEyeCatcher(str, BINIO_EYE_SKM_BEGIN);
  assertEq(eyeCatcherFound, 0, "Could not find pre-secret key eyecatcher");

  fromKey.read(str);
  toKeyID = read_raw_int(str);
  ptxtSpace = read_raw_int(str);
  DoubleCRT blankDCRT(context, IndexSet::emptySet());
  read_raw_vector(str, b, blankDCRT);
  read_raw_ZZ(str, prgSeed);
  noiseBound = read_raw_xdouble(str);

  eyeCatcherFound = readEyeCatcher(str, BINIO_EYE_SKM_END);
  assertEq(eyeCatcherFound, 0, "Could not find post-secret key eyecatcher");
}

long KSGiantStepSize(long D)
{
  assertTrue<InvalidArgument>(D > 0l, "Step size must be positive");
  long g = NTL::SqrRoot(D);
  if (g * g < D)
    g++; // g = ceiling(sqrt(D))
  return g;
}

// A maximalistic approach: generate matrices s(X^e)->s(X) for all e \in Zm*
void addAllMatrices(SecKey& sKey, long keyID)
{
  const Context& context = sKey.getContext();
  long m = context.zMStar.getM();

  // key-switching matrices for the automorphisms
  for (long i = 0; i < m; i++) {
    if (!context.zMStar.inZmStar(i))
      continue;
    sKey.GenKeySWmatrix(1, i, keyID, keyID);
  }
  sKey.setKeySwitchMap(); // re-compute the key-switching map
}

// TODO: generate matrices s.t. you can reLinearize each s(X^e) in at most two
// steps void addFewMatrices(SecKey& sKey, long keyID)
// {
//   throw LogicError("addFewMatrices not implemented yet");
// }

// This code block appears at least twice below
#define computeParams(context, m, i)                                           \
  bool native;                                                                 \
  long ord, gi, g2md;                                                          \
  NTL::mulmod_precon_t g2mdminv;                                               \
  if (i == context.zMStar.numOfGens()) { /* Frobenius matrices */              \
    ord = context.zMStar.getOrdP();                                            \
    gi = context.zMStar.getP();                                                \
    native = true;                                                             \
  } else { /* one of the "regular" dimensions */                               \
    ord = context.zMStar.OrderOf(i);                                           \
    gi = context.zMStar.ZmStarGen(i);                                          \
    native = context.zMStar.SameOrd(i);                                        \
    if (!native) {                                                             \
      g2md = PowerMod(gi, -ord, m); /* g^{-ord} mod m */                       \
      g2mdminv = PrepMulModPrecon(g2md, m);                                    \
    }                                                                          \
  }                                                                            \
  NTL::mulmod_precon_t giminv = PrepMulModPrecon(gi, m);

#if 0
static void add1Dmats4dim(SecKey& sKey, long i, long keyID)
{
  const Context &context = sKey.getContext();
  long m = context.zMStar.getM();
  computeParams(context,m,i); // defines vars: native, ord, gi, g2md, giminv, g2mdminv

  /* MAUTO std::vector<long> vals; */
  for (long j=1,val=gi; j < ord; j++) {
    // From s(X^val) to s(X)
    sKey.GenKeySWmatrix(1, val, keyID, keyID);
    if (!native) { // also from s(X^{g^{i-ord}}) to s(X)
      long val2 = MulModPrecon(val,g2md,m,g2mdminv);
      sKey.GenKeySWmatrix(1, val2, keyID, keyID);
      /* MAUTO vals.push_back(val2); */
    }
    /* MAUTO vals.push_back(val); */
    val = MulModPrecon(val, gi, m, giminv); // val *= g mod m (= g^{j+1})
  }

  if (!native) {
    sKey.GenKeySWmatrix(1, context.zMStar.genToPow(i, -ord), keyID, keyID);
  }

/* MAUTO
  sKey.resetTree(i,keyID); // remove existing tree, if any
  sKey.add2tree(i, 1, vals, keyID);
*/
}
#else
// adds all matrices for dim i.
// i == -1 => Frobenius (NOTE: in matmul1D, i ==#gens means something else,
//   so it is best to avoid that).
static void add1Dmats4dim(SecKey& sKey, long i, long keyID)
{
  const PAlgebra& zMStar = sKey.getContext().zMStar;
  long ord;
  bool native;

  if (i != -1) {
    ord = zMStar.OrderOf(i);
    native = zMStar.SameOrd(i);
  } else {
    // Frobenius
    ord = zMStar.getOrdP();
    native = true;
  }

  for (long j = 1; j < ord; j++)
    sKey.GenKeySWmatrix(1, zMStar.genToPow(i, j), keyID, keyID);

  if (!native)
    sKey.GenKeySWmatrix(1, zMStar.genToPow(i, -ord), keyID, keyID);

  sKey.setKSStrategy(i, HELIB_KSS_FULL);
}

#endif

#if 0
static std::pair<long,long> computeSteps(long ord, long bound, bool native)
{
  long baby,giant;
  if (native) { // using giant+baby matrices
    if (bound*bound >= 4*ord)
      giant = ceil((bound - sqrt((double)bound*bound -4*ord))/2.0);
    else
      giant = sqrt((double)ord);
  }
  else { // using giant+2*baby matrices
    if (bound*bound >= 8*ord)
      giant = ceil((bound - sqrt((double)bound*bound -8*ord))/2.0);
    else
      giant = sqrt((double)ord);
  }
  baby = ord/giant;
  if (baby*giant<ord) baby++;

  //std::cerr << "*** giant steps = " << giant << "\n";
  return std::pair<long,long>(baby,giant);
}

static void addSome1Dmats4dim(SecKey& sKey, long i, long bound, long keyID)
{
  const Context &context = sKey.getContext();
  long m = context.zMStar.getM();
  computeParams(context,m,i); // defines vars: native, ord, gi, g2md, giminv, g2mdminv

  long baby, giant;
  std::tie(baby,giant) = computeSteps(ord, bound, native);

  for (long j=1,val=gi; j<=baby; j++) { // Add matrices for baby steps
    sKey.GenKeySWmatrix(1, val, keyID, keyID);
    if (!native) {
      long val2 = MulModPrecon(val,g2md,m,g2mdminv);
      sKey.GenKeySWmatrix(1, val2, keyID, keyID);
    }
    val = MulModPrecon(val, gi, m, giminv); // val *= g mod m (= g^{j+1})
   }

  long gb = PowerMod(gi,baby,m); // g^baby
  NTL::mulmod_precon_t gbminv = PrepMulModPrecon(gb, m);
  for (long j=2,val=gb; j < giant; j++) { // Add matrices for giant steps
    val = MulModPrecon(val, gb, m, gbminv); // val = g^{(j+1)*baby}
    sKey.GenKeySWmatrix(1, val, keyID, keyID);
  }

  if (!native) {
    sKey.GenKeySWmatrix(1, context.zMStar.genToPow(i, -ord), keyID, keyID);
  }

  // VJS: experimantal feature...because the replication code
  // uses rotations by -1, -2, -4, -8, we add a few
  // of these as well...only the small ones are important,
  // and we only need them if SameOrd(i)...
  // Note: we do indeed get a nontrivial speed-up

  if (native && i<context.zMStar.numOfGens()) {
    for (long k = 1; k < giant; k = 2*k) {
      long j = ord - k;
      long val = PowerMod(gi, j, m); // val = g^j
      sKey.GenKeySWmatrix(1, val, keyID, keyID);
    }
  }

#if 0
MAUTO

  // build the tree for this dimension, the internal nodes are 1 and
  // (subset of) gi^{giant}, gi^{2*giant}, ..., gi^{baby*giant}. We

  MAUTO sKey.resetTree(i,keyID); // remove existing tree, if any

  // keep a list of all the elements that are covered by the tree so far,
  // initialized to only the root (=1).
  std::unordered_set<long> covered({1});

  // Make a list of the automorphisms for this dimension
  std::vector<long> autos;
  for (long j=1,val=gi; j<ord; j++) {
    // Do we have matrices for val and/or val/gi^{di}?
    if (!native) {
      long val2 = MulModPrecon(val, g2md, m, g2mdminv);
      if (sKey.haveKeySWmatrix(1,val2,keyID,keyID)) {
        autos.push_back(val2);
      }
    }
    if (sKey.haveKeySWmatrix(1,val,keyID,keyID)) {
      autos.push_back(val);
    }
    val = MulModPrecon(val, gi, m, giminv); // g^{j+1}
  }

  // Insert internal nodes and their children to tree
  for (long j=0,fromVal=1; j<giant; j++) {
    NTL::mulmod_precon_t fromminv = PrepMulModPrecon(fromVal, m);
    std::vector<long> children;
    for (long k: autos) {
      long toVal = MulModPrecon(k, fromVal, m, fromminv);
      if (covered.count(toVal)==0) { // toVal not covered yet
        covered.insert(toVal);
        children.push_back(toVal);
      }
    }
    if (!children.empty()) { // insert fromVal with its children
      sKey.add2tree(i, fromVal, children, keyID);
    }
    fromVal = MulModPrecon(fromVal, gb, m, gbminv); // g^{(j+1)*baby}
  }

  // Sanity-check, did we cover everything?
  long toCover = native? ord: (2*ord-1);
  if (covered.size()<toCover)
    std::cerr << "**Warning: order-"<<ord<<" dimension, covered "<<covered.size()
         << " of "<<toCover<<std::endl;
#endif
}

#else
// same as above, but uses BS/GS strategy
static void addSome1Dmats4dim(SecKey& sKey,
                              long i,
                              UNUSED long bound,
                              long keyID)
{
  const PAlgebra& zMStar = sKey.getContext().zMStar;
  long ord;
  bool native;

  if (i != -1) {
    ord = zMStar.OrderOf(i);
    native = zMStar.SameOrd(i);
  } else {
    // Frobenius
    ord = zMStar.getOrdP();
    native = true;
  }

  long g = KSGiantStepSize(ord);

  // baby steps
  for (long j = 1; j < g; j++)
    sKey.GenKeySWmatrix(1, zMStar.genToPow(i, j), keyID, keyID);

  // giant steps
  for (long j = g; j < ord; j += g)
    sKey.GenKeySWmatrix(1, zMStar.genToPow(i, j), keyID, keyID);

  if (!native)
    sKey.GenKeySWmatrix(1, zMStar.genToPow(i, -ord), keyID, keyID);

  sKey.setKSStrategy(i, HELIB_KSS_BSGS);

  // NOTE: the old code also added matrices for ord-2^k for small k,
  // in the case of (native && i<context.zMStar.numOfGens()).
  // This supposedly speeds up the replication code, but for now
  // we are leaving this out, for simplicity.   Also, it is a waste
  // of space for applications that don't use replication.
}

#endif

// generate only matrices of the form s(X^{g^i})->s(X), but not all of them.
// For a generator g whose order is larger than bound, generate only enough
// matrices for the giant-step/baby-step procedures (2*sqrt(ord(g))of them).
void addSome1DMatrices(SecKey& sKey, long bound, long keyID)
{
  const Context& context = sKey.getContext();

  // key-switching matrices for the automorphisms
  for (long i : range(context.zMStar.numOfGens())) {
    // For generators of small order, add all the powers
    if (bound >= context.zMStar.OrderOf(i))
      add1Dmats4dim(sKey, i, keyID);
    else // For generators of large order, add only some of the powers
      addSome1Dmats4dim(sKey, i, bound, keyID);
  }
  sKey.setKeySwitchMap(); // re-compute the key-switching map
}

void add1DMatrices(SecKey& sKey, long keyID)
{
  addSome1DMatrices(sKey, LONG_MAX, keyID);
}

void addBSGS1DMatrices(SecKey& sKey, long keyID)
{
  addSome1DMatrices(sKey, 0, keyID);
}

// Generate all Frobenius matrices of the form s(X^{p^i})->s(X)
void addSomeFrbMatrices(SecKey& sKey, long bound, long keyID)
{
  const Context& context = sKey.getContext();
  if (bound >= LONG(context.zMStar.getOrdP()))
    add1Dmats4dim(sKey, -1, keyID);
  else // For generators of large order, add only some of the powers
    addSome1Dmats4dim(sKey, -1, bound, keyID);

  sKey.setKeySwitchMap(); // re-compute the key-switching map
}

void addFrbMatrices(SecKey& sKey, long keyID)
{
  addSomeFrbMatrices(sKey, LONG_MAX, keyID);
}

void addBSGSFrbMatrices(SecKey& sKey, long keyID)
{
  addSomeFrbMatrices(sKey, 0, keyID);
}

static void addMinimal1Dmats4dim(SecKey& sKey, long i, long keyID)
{
  const PAlgebra& zMStar = sKey.getContext().zMStar;
  long ord;
  bool native;

  if (i != -1) {
    ord = zMStar.OrderOf(i);
    native = zMStar.SameOrd(i);
  } else {
    // Frobenius
    ord = zMStar.getOrdP();
    native = true;
  }

  sKey.GenKeySWmatrix(1, zMStar.genToPow(i, 1), keyID, keyID);

  if (!native)
    sKey.GenKeySWmatrix(1, zMStar.genToPow(i, -ord), keyID, keyID);

  if (ord > HELIB_KEYSWITCH_MIN_THRESH) {
    long g = KSGiantStepSize(ord);
    sKey.GenKeySWmatrix(1, zMStar.genToPow(i, g), keyID, keyID);
  }

  sKey.setKSStrategy(i, HELIB_KSS_MIN);
}

void addMinimal1DMatrices(SecKey& sKey, long keyID)
{
  const Context& context = sKey.getContext();

  // key-switching matrices for the automorphisms
  for (long i : range(context.zMStar.numOfGens())) {
    addMinimal1Dmats4dim(sKey, i, keyID);
  }
  sKey.setKeySwitchMap(); // re-compute the key-switching map
}

// Generate all Frobenius matrices of the form s(X^{p^i})->s(X)
void addMinimalFrbMatrices(SecKey& sKey, long keyID)
{
  addMinimal1Dmats4dim(sKey, -1, keyID);
  sKey.setKeySwitchMap(); // re-compute the key-switching map
}

// Generate all key-switching matrices for a given permutation network
void addMatrices4Network(SecKey& sKey, const PermNetwork& net, long keyID)
{
  const Context& context = sKey.getContext();
  long m = context.zMStar.getM();

  for (long i = 0; i < net.depth(); i++) {
    long e = net.getLayer(i).getE();
    long gIdx = net.getLayer(i).getGenIdx();
    long g = context.zMStar.ZmStarGen(gIdx);
    long g2e = NTL::PowerMod(g, e, m); // g^e mod m
    const NTL::Vec<long>& shamts = net.getLayer(i).getShifts();
    for (long j = 0; j < shamts.length(); j++) {
      if (shamts[j] == 0)
        continue;
      long val = NTL::PowerMod(g2e, shamts[j], m);
      sKey.GenKeySWmatrix(1, val, keyID, keyID);
    }
  }
  sKey.setKeySwitchMap(); // re-compute the key-switching map
}

void addTheseMatrices(SecKey& sKey, const std::set<long>& automVals, long keyID)
{
  std::set<long>::iterator it;
  for (it = automVals.begin(); it != automVals.end(); ++it) {
    long k = *it;
    sKey.GenKeySWmatrix(1, k, keyID, keyID);
  }
  sKey.setKeySwitchMap(); // re-compute the key-switching map
}

} // namespace helib


//[ MapAssign
//  Copyright 2008 Eric Niebler. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// This is a port of map_list_of() from the Boost.Assign library.
// It has the advantage of being more efficient at runtime by not
// building any temporary container that requires dynamic allocation.

#include <map>
#include <string>
#include <iostream>
#include <boost/proto/core.hpp>
#include <boost/proto/transform.hpp>
#include <boost/type_traits/add_reference.hpp>
namespace proto = boost::proto;
using proto::_;

struct map_list_of_tag
{};

// A simple callable function object that inserts a
// (key,value) pair into a map.
struct insert
  : proto::callable
{
    template<typename Sig>
    struct result;

    template<typename This, typename Map, typename Key, typename Value>
    struct result<This(Map, Key, Value)>
      : boost::add_reference<Map>
    {};

    template<typename Map, typename Key, typename Value>
    Map &operator()(Map &map, Key const &key, Value const &value) const
    {
        map.insert(typename Map::value_type(key, value));
        return map;
    }
};

// The grammar for valid map-list expressions, and a
// transform that populates the map.
struct MapListOf
  : proto::or_<
        proto::when<
            proto::function<
                proto::terminal<map_list_of_tag>
              , proto::terminal<_>
              , proto::terminal<_>
            >
          , insert(
                proto::_data
              , proto::_value(proto::_child1)
              , proto::_value(proto::_child2)
            )
        >
      , proto::when<
            proto::function<
                MapListOf
              , proto::terminal<_>
              , proto::terminal<_>
            >
          , insert(
                MapListOf(proto::_child0)
              , proto::_value(proto::_child1)
              , proto::_value(proto::_child2)
            )
        >
    >
{};

template<typename Expr>
struct map_list_of_expr;

struct map_list_of_dom
  : proto::domain<proto::pod_generator<map_list_of_expr>, MapListOf>
{};

// An expression wrapper that provides a conversion to a
// map that uses the MapListOf
template<typename Expr>
struct map_list_of_expr
{
    BOOST_PROTO_BASIC_EXTENDS(Expr, map_list_of_expr, map_list_of_dom)
    BOOST_PROTO_EXTENDS_FUNCTION()

    template<typename Key, typename Value, typename Cmp, typename Al>
    operator std::map<Key, Value, Cmp, Al> () const
    {
        BOOST_MPL_ASSERT((proto::matches<Expr, MapListOf>));
        std::map<Key, Value, Cmp, Al> map;
        return MapListOf()(*this, 0, map);
    }
};

map_list_of_expr<proto::terminal<map_list_of_tag>::type> const map_list_of = {{{}}};

int main()
{
    // Initialize a map:
    std::map<std::string, int> op =
        map_list_of
            ("<", 1)
            ("<=",2)
            (">", 3)
            (">=",4)
            ("=", 5)
            ("<>",6)
        ;

    std::cout << "\"<\"  --> " << op["<"] << std::endl;
    std::cout << "\"<=\" --> " << op["<="] << std::endl;
    std::cout << "\">\"  --> " << op[">"] << std::endl;
    std::cout << "\">=\" --> " << op[">="] << std::endl;
    std::cout << "\"=\"  --> " << op["="] << std::endl;
    std::cout << "\"<>\" --> " << op["<>"] << std::endl;

    return 0;
}
//]


//  (C) Copyright John Maddock 2005-2006.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_MATH_TOOLS_SERIES_INCLUDED
#define BOOST_MATH_TOOLS_SERIES_INCLUDED

#ifdef _MSC_VER
#pragma once
#endif

#include <boost/config/no_tr1/cmath.hpp>
#include <boost/cstdint.hpp>
#include <boost/limits.hpp>
#include <boost/math/tools/config.hpp>

namespace boost{ namespace math{ namespace tools{

//
// Simple series summation come first:
//
template <class Functor, class U, class V>
inline typename Functor::result_type sum_series(Functor& func, const U& factor, boost::uintmax_t& max_terms, const V& init_value) BOOST_NOEXCEPT_IF(BOOST_MATH_IS_FLOAT(typename Functor::result_type) && noexcept(std::declval<Functor>()()))
{
   BOOST_MATH_STD_USING

   typedef typename Functor::result_type result_type;

   boost::uintmax_t counter = max_terms;

   result_type result = init_value;
   result_type next_term;
   do{
      next_term = func();
      result += next_term;
   }
   while((fabs(factor * result) < fabs(next_term)) && --counter);

   // set max_terms to the actual number of terms of the series evaluated:
   max_terms = max_terms - counter;

   return result;
}

template <class Functor, class U>
inline typename Functor::result_type sum_series(Functor& func, const U& factor, boost::uintmax_t& max_terms) BOOST_NOEXCEPT_IF(BOOST_MATH_IS_FLOAT(typename Functor::result_type) && noexcept(std::declval<Functor>()()))
{
   typename Functor::result_type init_value = 0;
   return sum_series(func, factor, max_terms, init_value);
}

template <class Functor, class U>
inline typename Functor::result_type sum_series(Functor& func, int bits, boost::uintmax_t& max_terms, const U& init_value) BOOST_NOEXCEPT_IF(BOOST_MATH_IS_FLOAT(typename Functor::result_type) && noexcept(std::declval<Functor>()()))
{
   BOOST_MATH_STD_USING
   typedef typename Functor::result_type result_type;
   result_type factor = ldexp(result_type(1), 1 - bits);
   return sum_series(func, factor, max_terms, init_value);
}

template <class Functor>
inline typename Functor::result_type sum_series(Functor& func, int bits) BOOST_NOEXCEPT_IF(BOOST_MATH_IS_FLOAT(typename Functor::result_type) && noexcept(std::declval<Functor>()()))
{
   BOOST_MATH_STD_USING
   typedef typename Functor::result_type result_type;
   boost::uintmax_t iters = (std::numeric_limits<boost::uintmax_t>::max)();
   result_type init_val = 0;
   return sum_series(func, bits, iters, init_val);
}

template <class Functor>
inline typename Functor::result_type sum_series(Functor& func, int bits, boost::uintmax_t& max_terms) BOOST_NOEXCEPT_IF(BOOST_MATH_IS_FLOAT(typename Functor::result_type) && noexcept(std::declval<Functor>()()))
{
   BOOST_MATH_STD_USING
   typedef typename Functor::result_type result_type;
   result_type init_val = 0;
   return sum_series(func, bits, max_terms, init_val);
}

template <class Functor, class U>
inline typename Functor::result_type sum_series(Functor& func, int bits, const U& init_value) BOOST_NOEXCEPT_IF(BOOST_MATH_IS_FLOAT(typename Functor::result_type) && noexcept(std::declval<Functor>()()))
{
   BOOST_MATH_STD_USING
   boost::uintmax_t iters = (std::numeric_limits<boost::uintmax_t>::max)();
   return sum_series(func, bits, iters, init_value);
}

//
// Algorithm kahan_sum_series invokes Functor func until the N'th
// term is too small to have any effect on the total, the terms
// are added using the Kahan summation method.
//
// CAUTION: Optimizing compilers combined with extended-precision
// machine registers conspire to render this algorithm partly broken:
// double rounding of intermediate terms (first to a long double machine
// register, and then to a double result) cause the rounding error computed
// by the algorithm to be off by up to 1ulp.  However this occurs rarely, and
// in any case the result is still much better than a naive summation.
//
template <class Functor>
inline typename Functor::result_type kahan_sum_series(Functor& func, int bits) BOOST_NOEXCEPT_IF(BOOST_MATH_IS_FLOAT(typename Functor::result_type) && noexcept(std::declval<Functor>()()))
{
   BOOST_MATH_STD_USING

   typedef typename Functor::result_type result_type;

   result_type factor = pow(result_type(2), bits);
   result_type result = func();
   result_type next_term, y, t;
   result_type carry = 0;
   do{
      next_term = func();
      y = next_term - carry;
      t = result + y;
      carry = t - result;
      carry -= y;
      result = t;
   }
   while(fabs(result) < fabs(factor * next_term));
   return result;
}

template <class Functor>
inline typename Functor::result_type kahan_sum_series(Functor& func, int bits, boost::uintmax_t& max_terms) BOOST_NOEXCEPT_IF(BOOST_MATH_IS_FLOAT(typename Functor::result_type) && noexcept(std::declval<Functor>()()))
{
   BOOST_MATH_STD_USING

   typedef typename Functor::result_type result_type;

   boost::uintmax_t counter = max_terms;

   result_type factor = ldexp(result_type(1), bits);
   result_type result = func();
   result_type next_term, y, t;
   result_type carry = 0;
   do{
      next_term = func();
      y = next_term - carry;
      t = result + y;
      carry = t - result;
      carry -= y;
      result = t;
   }
   while((fabs(result) < fabs(factor * next_term)) && --counter);

   // set max_terms to the actual number of terms of the series evaluated:
   max_terms = max_terms - counter;

   return result;
}

} // namespace tools
} // namespace math
} // namespace boost

#endif // BOOST_MATH_TOOLS_SERIES_INCLUDED


///////////////////////////////////////////////////////////////////////////////
//  Copyright Christopher Kormanyos 2013 - 2016.
//  Copyright Nikhar Agrawal 2015.
//  Copyright Paul Bristow 2015.
//  Distributed under the Boost Software License,
//  Version 1.0. (See accompanying file LICENSE_1_0.txt
//  or copy at http://www.boost.org/LICENSE_1_0.txt)

/*!
  \file

 \brief This file is a partial reference implementation for the proposed
 "C++ binary fixed-point arithmetic" as specified in N3352.\n

 \sa http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3352.html \n

 In this file, we include subsidiary files that implement the
 proposed specified fixed-point types,
 and explain macros that may be optionally defined.

 \details
   There is optional support for certain variations of fixed_point
   using preprocessor definitions. Not all of these are supported
   at the moment. The potential options include:

   Is supported now  : \#define BOOST_FIXED_POINT_DISABLE_IOSTREAM\n
   Is supported now  : \#define BOOST_FIXED_POINT_DISABLE_MULTIPRECISION\n
   Not yet supported : \#define BOOST_FIXED_POINT_DISABLE_WIDE_INTEGER_MATH\n
   Not yet supported : \#define BOOST_FIXED_POINT_DISABLE_CPP11\n
   Is now supported  : \#define BOOST_FIXED_POINT_ENABLE_GMP_BACKENDS\n
*/

#ifndef FIXED_POINT_2015_03_06_HPP_
  #define FIXED_POINT_2015_03_06_HPP_

  #ifdef BOOST_DOXYGEN_GENERATION
    // These items below document the function of optional macros,
    // but are processed only if the doxyfile or jamfile contains
    // <doxygen:param>PREDEFINED=BOOST_DOXYGEN_GENERATION

    /*! \def BOOST_FIXED_POINT_DISABLE_IOSTREAM
    \brief All I/O streaming is disabled, as is the inclusion of associated standard
    library headers. This option eliminates all I/O stream overhead,
    in particular for bare-metal microcontroller projects.
    Disabling I/O streaming requires simultaneous disabling of multiprecision,
    as shown below.
    */
    #define BOOST_FIXED_POINT_DISABLE_IOSTREAM

    /*! \def BOOST_FIXED_POINT_DISABLE_MULTIPRECISION
    \brief This option is defined to disable the use of
    Boost.Multiprecision for back-ends of the fixed-point classes.
    Multiprecision fixed-point numbers are not available if this
    option is set.
    (Implemented).
    */
    #define BOOST_FIXED_POINT_DISABLE_MULTIPRECISION

    /*! \def BOOST_FIXED_POINT_DISABLE_WIDE_INTEGER_MATH
    \brief This option is defined to avoid using the unsigned_large_type in the
    implementations of multiplication and division operations.
    This option is intended for systems with limited integer widths
    such as bare-metal microcontrollers.
    When used in combination with BOOST_FIXED_POINT_DISABLE_MULTIPRECISION,
    this option is intended to provide fixed-point representations
    with up to 64-bits (if 64-bit integer types are available)
    without requiring any multiprecision.
    (Not yet implemented).
    */
    #define BOOST_FIXED_POINT_DISABLE_WIDE_INTEGER_MATH

    /*! \def BOOST_FIXED_POINT_DISABLE_CPP11
    \brief This option enables support for a back-port to C++03.
    This option eliminates the use of all native C++11 language elements. This might send the
    wrong message about language technology, but could
    increase the range of potential target compilers, especially for embedded systems.
    (Implemented but not yet fully tested).
    */
    #define BOOST_FIXED_POINT_DISABLE_CPP11

    /*! \def BOOST_FIXED_POINT_ENABLE_GMP_BACKENDS
    \brief When this option is defined, fixed-point uses multiple-precision backends
    (when required) based on GMP instead of Boost.Multiprecision's self-written
    binary floating-point backend.
    GMP backends use highly optimized assembler and advanced
    multiplication algorithms that are significantly faster for high digit counts
    in excess of a few hundred binary digits.
    \sa https://gmplib.org
    (Implemented but not yet fully tested).
    */
    #define BOOST_FIXED_POINT_ENABLE_GMP_BACKENDS

  #endif // BOOST_DOXYGEN_GENERATION

  #include <boost/fixed_point/fixed_point_negatable.hpp>

#endif // FIXED_POINT_2015_03_06_HPP_


// This file is part of snark, a generic and flexible library for robotics research
// Copyright (c) 2011 The University of Sydney
// 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.
// 3. Neither the name of the University of Sydney nor the
//    names of its contributors may be used to endorse or promote products
//    derived from this software without specific prior written permission.
//
// NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
// GRANTED BY THIS LICENSE.  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.

#include "geoids.h"
#include <boost/algorithm/string.hpp>
#include <sstream>

namespace snark { namespace geodesy {

namespace impl {

wgs84::geoid wgs84;
agd84::geoid agd84;
snark::spherical::ellipsoid sphere( earth_average_radius, earth_average_radius );
snark::spherical::ellipsoid unit( 1, unit_minor_semiaxis );
grs67::geoid grs67;

static std::string sphere_info()
{
    std::stringstream os;
    os  << "shere,sphere with equal major and minor semiaxis," << earth_average_radius << "," << earth_average_radius << ",1" << std::endl;
    return os.str();
}

static std::string unit_info()
{
    std::stringstream os;
    os << "unit,scaled down WGS84,1," << unit_minor_semiaxis << "," << wgs84::eccentricity << std::endl;
    return os.str();
}

} // namespace impl

const snark::spherical::ellipsoid& geoids::select( std::string name )
{
    if ( name.empty())
        return impl::wgs84;
    boost::algorithm::to_lower(name);
    if ( name == wgs84::name ) { return impl::wgs84; }
    else if ( name == agd84::name ) { return impl::agd84; }
    else if ( name == grs67::name ) { return impl::grs67; }
    else if ( name == "sphere" ) { return impl::sphere; }
    else if ( name == "unit" ) { return impl::unit; }
    else { COMMA_THROW( comma::exception, "geoid not supported: "<<name); }
}

std::string geoids::info( std::string name )
{
    if ( name.empty())
        return wgs84::info();
    boost::algorithm::to_lower( name );
    if ( name == wgs84::name ) { return wgs84::info(); }
    else if ( name == agd84::name ) { return agd84::info(); }
    else if ( name == grs67::name ) { return grs67::info(); }
    else if ( name == "sphere" ) { return impl::sphere_info(); }
    else if ( name == "unit" ) { return impl::unit_info(); }
    else { COMMA_THROW( comma::exception, "can't get info on geoid: "<<name); }
}

std::string geoids::help()
{
    std::stringstream os;
    os << "        sphere: sphere with earth average radius for comparision/testing ; " << earth_average_radius << ";" << earth_average_radius << "); 1" << std::endl;
    os << "        unit: WGS84 scaled down to unit, useful for getting distance in equivalent radian for instance (1; " << unit_minor_semiaxis << ");" << wgs84::eccentricity << std::endl;
    return os.str();
}

} } // snark geodesy


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#ifndef MTL_TRAITS_ROOT_INCLUDE
#define MTL_TRAITS_ROOT_INCLUDE

#include <boost/numeric/mtl/mtl_fwd.hpp>
#include <boost/numeric/mtl/concept/collection.hpp>

namespace mtl { namespace traits {

/// Type trait to reduce types to their essentials by removing const, reference, ... and gearing derived types to their bases
template <typename T>
struct root
{
    typedef T        type;
};

// ==========================
// Remove language attributes
// ==========================

template <typename T>
struct root<T&>
  : public root<T> {};

template <typename T>
struct root<const T>
  : public root<T> {};

// Redundant specialization to make xlc++ happy
template <typename T, int R, int C>
struct root<const T[R][C]>
  : public root<T[R][C]> {};

// ============
// Base classes
// ============

// Implicit dense matrices

template <typename Value>
struct root<mtl::mat::ones_matrix<Value> >
{
    typedef mtl::mat::implicit_dense<mtl::mat::ones_functor<Value> > type;
};

template <typename Value>
struct root<mtl::mat::hilbert_matrix<Value> >
{
    typedef mtl::mat::implicit_dense<mtl::mat::hilbert_functor<Value> > type;
};

template <typename Vector1, typename Vector2>
struct root<mtl::mat::outer_product_matrix<Vector1, Vector2> >
{
    typedef mtl::mat::implicit_dense<mtl::mat::outer_product_functor<Vector1, Vector2> > type;
};

// Matrix map views

template <typename Scaling, typename Matrix>
struct root< mtl::mat::scaled_view<Scaling, Matrix> >
{
    typedef mtl::mat::map_view<tfunctor::scale<Scaling, typename Matrix::value_type>, Matrix> type;
};

template <typename Matrix>
struct root< mtl::mat::conj_view<Matrix> >
{
    typedef mtl::mat::map_view<sfunctor::conj<typename Matrix::value_type>, Matrix> type;
};

template <typename Matrix>
struct root< mtl::mat::negate_view<Matrix> >
{
    typedef mtl::mat::map_view<sfunctor::negate<typename Matrix::value_type>, Matrix> type;
};

template <typename Matrix>
struct root< mtl::mat::imag_view<Matrix> >
{
    typedef mtl::mat::map_view<sfunctor::imag<typename Matrix::value_type>, Matrix> type;
};

template <typename Matrix>
struct root< mtl::mat::real_view<Matrix> >
{
    typedef mtl::mat::map_view<sfunctor::real<typename Matrix::value_type>, Matrix> type;
};

template <typename Matrix>
struct root< mtl::mat::exp_view<Matrix> >
{
    typedef mtl::mat::map_view<sfunctor::exp<typename Matrix::value_type>, Matrix> type;
};

template <typename Matrix>
struct root< mtl::mat::hermitian_view<Matrix> >
{
    typedef mtl::mat::map_view<sfunctor::conj<typename Matrix::value_type>, mtl::mat::transposed_view<Matrix> > type;
};

template <typename Matrix, typename RScaling>
struct root< mtl::mat::rscaled_view<Matrix, RScaling> >
{
    typedef mtl::mat::map_view<tfunctor::rscale<typename Matrix::value_type, RScaling>, Matrix> type;
};

template <typename Matrix, typename Divisor>
struct root< mtl::mat::divide_by_view<Matrix, Divisor> >
{
    typedef mtl::mat::map_view<tfunctor::divide_by<typename Matrix::value_type, Divisor>, Matrix> type;
};

// Matrix operations
template <typename M1, typename M2>
struct root< mtl::mat::mat_mat_plus_expr<M1, M2> >
{
    typedef mtl::sfunctor::plus<typename Collection<M1>::value_type, typename Collection<M2>::value_type> f_type;
    typedef mtl::mat::mat_mat_op_expr<M1, M2, f_type> type;
};

template <typename M1, typename M2>
struct root< mtl::mat::mv_mv_plus_expr<M1, M2> >
{
    typedef typename root< mtl::mat::mat_mat_plus_expr<M1, M2> >::type type;
};

template <typename M1, typename M2>
struct root< mtl::mat::mat_mat_minus_expr<M1, M2> >
{
    typedef mtl::sfunctor::minus<typename Collection<M1>::value_type, typename Collection<M2>::value_type> f_type;
    typedef mtl::mat::mat_mat_op_expr<M1, M2, f_type> type;
};

template <typename M1, typename M2>
struct root< mtl::mat::mv_mv_minus_expr<M1, M2> >
{
    typedef typename root< mtl::mat::mat_mat_minus_expr<M1, M2> >::type type;
};

template <typename M1, typename M2>
struct root< mtl::mat::mat_mat_times_expr<M1, M2> >
{
    typedef mtl::sfunctor::times<typename Collection<M1>::value_type, typename Collection<M2>::value_type> f_type;
    typedef mtl::mat::mat_mat_op_expr<M1, M2, f_type> type;
};

template <typename M1, typename M2>
struct root< mtl::mat::mat_mat_ele_times_expr<M1, M2> >
{
    typedef mtl::sfunctor::times<typename Collection<M1>::value_type, typename Collection<M2>::value_type> f_type;
    typedef mtl::mat::mat_mat_op_expr<M1, M2, f_type> type;
};

// Vector assignment expressions

template <typename E1, typename E2>
struct root< vec::vec_vec_asgn_expr<E1, E2> >
{
    typedef vec::vec_vec_aop_expr< E1, E2, mtl::sfunctor::assign<typename E1::value_type, typename E2::value_type> > type;
};

template <typename E1, typename E2>
struct root< vec::vec_vec_plus_asgn_expr<E1, E2> >
{
    typedef vec::vec_vec_aop_expr< E1, E2, mtl::sfunctor::plus_assign<typename E1::value_type, typename E2::value_type> > type;
};

template <typename E1, typename E2>
struct root< vec::vec_vec_minus_asgn_expr<E1, E2> >
{
    typedef vec::vec_vec_aop_expr< E1, E2, mtl::sfunctor::minus_assign<typename E1::value_type, typename E2::value_type> > type;
};

template <typename E1, typename E2>
struct root< vec::vec_scal_asgn_expr<E1, E2> >
{
    typedef vec::vec_scal_aop_expr< E1, E2, mtl::sfunctor::assign<typename E1::value_type, E2> > type;
};

template <typename E1, typename E2>
struct root< vec::vec_scal_times_asgn_expr<E1, E2> >
{
    typedef vec::vec_scal_aop_expr< E1, E2, mtl::sfunctor::times_assign<typename E1::value_type, E2> > type;
};

template <typename E1, typename E2>
struct root< vec::vec_scal_div_asgn_expr<E1, E2> >
{
    typedef vec::vec_scal_aop_expr< E1, E2, mtl::sfunctor::divide_assign<typename E1::value_type, E2> > type;
};

template <typename Scaling, typename Vector>
struct root< vec::scaled_view<Scaling, Vector> >
{
    typedef vec::map_view<tfunctor::scale<Scaling, typename Vector::value_type>, Vector> type;
};

template <typename Vector, typename RScaling>
struct root< vec::rscaled_view<Vector, RScaling> >
{
    typedef vec::map_view<tfunctor::rscale<typename Vector::value_type, RScaling>, Vector> type;
};

template <typename Vector, typename Divisor>
struct root< vec::divide_by_view<Vector, Divisor> >
{
    typedef vec::map_view<tfunctor::divide_by<typename Vector::value_type, Divisor>, Vector> type;
};

template <typename Vector>
struct root< vec::conj_view<Vector> >
{
    typedef vec::map_view<mtl::sfunctor::conj<typename Vector::value_type>, Vector> type;
};

template <typename Vector>
struct root< vec::negate_view<Vector> >
{
    typedef vec::map_view<mtl::sfunctor::negate<typename Vector::value_type>, Vector> type;
};

template <unsigned BSize, typename Vector>
struct root< vec::unrolled1<BSize, Vector> >
{
    typedef Vector type;
};

#if 0 // template
struct root
{
    typedef  type;
};
#endif

}} // namespace mtl::traits

#endif // MTL_TRAITS_ROOT_INCLUDE