/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\
|  Phycas: Python software for phylogenetic analysis						  |
|  Copyright (C) 2006 Mark T. Holder, Paul O. Lewis and David L. Swofford	  |
|																			  |
|  This program is free software; you can redistribute it and/or modify		  |
|  it under the terms of the GNU General Public License as published by		  |
|  the Free Software Foundation; either version 2 of the License, or		  |
|  (at your option) any later version.										  |
|																			  |
|  This program is distributed in the hope that it will be useful,			  |
|  but WITHOUT ANY WARRANTY; without even the implied warranty of			  |
|  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the			  |
|  GNU General Public License for more details.								  |
|																			  |
|  You should have received a copy of the GNU General Public License along	  |
|  with this program; if not, write to the Free Software Foundation, Inc.,	  |
|  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.				  |
\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

#include "square_matrix.hpp"

#include <boost/format.hpp>

#include "ncl/nxsallocatematrix.h"

namespace phycas
{

unsigned SquareMatrix::k = 0;	//temporary!

/*----------------------------------------------------------------------------------------------------------------------
|	SquareMatrix default constructor. Sets `dim' and `m' data members to 0.
*/
SquareMatrix::SquareMatrix()
  :	 id(++k), m(0), dim(0)
	{
	//std::cerr << "|----> constructing default SquareMatrix " << id << " <----" << std::endl;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	SquareMatrix constructor. Creates square matrix `m' with dimension `sz' and sets all elements of `m' to `value'.
*/
SquareMatrix::SquareMatrix(
  unsigned sz,		/**< the row and column dimension of the matrix */
  double value)		/**< the value to which to set all elements */
  : id(++k), m(0), dim(0)
	{
	//std::cerr << "|----> constructing SquareMatrix " << id << " of size " << sz << " with all values set to " << value << " <----" << (this) << std::endl;
	CreateMatrix(sz, value);
	}

/*----------------------------------------------------------------------------------------------------------------------
|	SquareMatrix copy constructor. This is necessary because the standard vector resize function in some implementations
|	of the standard template library creates only one element with the default constructor, then creates the remaining
|	elements using the copy constructor.
*/
SquareMatrix::SquareMatrix(
  const SquareMatrix & other)	/**< is the SquareMatrix to copy */
  : id(++k), m(0), dim(other.dim)
	{
	//std::cerr << "|----> copy constructing SquareMatrix " << id << " from " << other.id << " <----" << (this) << std::endl;
	if (dim > 0)
		{
		CreateMatrix(dim, 0.0);
		double * pother = &other.m[0][0];
		double * p = &m[0][0];
		unsigned last = dim*dim;
		for (unsigned i = 0; i < last; ++i)
			*p++ = *pother++;
		}
	}

/*----------------------------------------------------------------------------------------------------------------------
|	SquareMatrix destructor. If `m' exists, deletes it.
*/
SquareMatrix::~SquareMatrix()
	{
	//std::cerr << "|----> destroying SquareMatrix " << id << " <----" << (this) << std::endl;
    Clear();
	id = UINT_MAX;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns object to state it would be in if just constructed using the default constructor. Leaves `id' at the same
|   value it had when this object was constucted, however.
*/
void SquareMatrix::Clear()
	{
	if (m)
		DeleteTwoDArray<double>(m);
	m = 0;
	dim = 0;
    }

/*----------------------------------------------------------------------------------------------------------------------
|	Allocates memory for `m' data member and calls SquareMatrix::Identity.
*/
void SquareMatrix::CreateMatrix(
  unsigned sz,		/**< is the row and column dimension of the square matrix to be created */
  double value)		/**< is the value to which each element will be set */
	{
	//std::cerr << "----> SquareMatrix::CreateMatrix " << id << ", sz = " << sz << ", value = " << value << " <----" << std::endl;
    dim = sz;
	if (m)
		DeleteTwoDArray<double>(m);
	m = 0;
	if (sz > 0)
		{
		m = NewTwoDArray<double>(sz, sz);
		Fill(value);
		}

	//std::cerr << "----> SquareMatrix::CreateMatrix " << id << ", sz = " << sz << ", value = " << value << " <----\n";
    //for (unsigned i = 0; i < sz; ++i)
    //    {
    //    for (unsigned j = 0; j < sz; ++j)
    //        {
    //        std::cerr << boost::str(boost::format("%12.1f") % m[i][j]);
    //        }
    //    std::cerr << "\n";
    //    }
    //std::cerr << std::endl;
	}

/*----------------------------------------------------------------------------------------------------------------------
|   Fills `p_mat_trans_scratch' with transpose of `p_mat'. Assumes both `p_mat_trans_scratch' and `p_mat' are allocated
|   square matrices of doubles and are of the same dimension. Overwrites any values currently in `p_mat_trans_scratch'.
*/
void fillTranspose(double ** p_mat_trans_scratch, const double * const *p_mat, unsigned dim)
	{
	for (unsigned j = 0; j < dim; ++j)
		{
		for (unsigned k = 0 ; k < dim; ++k)
			p_mat_trans_scratch[j][k] = p_mat[k][j];
		}
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns the dimension of the data member `m' (the row and column dimension are the same).
*/
unsigned SquareMatrix::GetDimension() const
	{
	return dim;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns the sum of the elements on the main diagonal.
*/
double SquareMatrix::Trace() const
	{
    double trace = 0.0;
    for (unsigned i = 0; i < dim; ++i)
        trace += m[i][i];
	return trace;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Accessor function that simply returns `m'. This allows `m' to be passed to functions that expect a two-dimensional
|	array of doubles, but keep in mind that this is somewhat unsafe.
*/
double * * SquareMatrix::GetMatrixAsRawPointer() const
	{
	return m;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Operator that allows access to this SquareMatrix object as if it were a two-dimensional array of doubles.
*/
double * SquareMatrix::operator[](
  unsigned i) const
	{
	PHYCAS_ASSERT(i < GetDimension());
	return m[i];
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns string representation of the matrix.
*/
std::string SquareMatrix::GetStringRepresentation() const
	{
    std::string s;
    std::string fmt = "\t%g";
    MatrixToString(s, fmt);
    return s;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns string representation of the matrix.
*/
std::string SquareMatrix::GetFormattedStringRepresentation(std::string fmt = "%g") const
	{
    std::string s;
    MatrixToString(s, fmt);
    return s;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns element at row `i', column `'j of the matrix.
*/
double SquareMatrix::GetElement(unsigned i, unsigned j) const
	{
	PHYCAS_ASSERT(i < dim);
	PHYCAS_ASSERT(j < dim);
    return m[i][j];
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Sets element at row `i', column `j' of the matrix to the value `v'.
*/
void SquareMatrix::SetElement(unsigned i, unsigned j, double v)
	{
	PHYCAS_ASSERT(i < dim);
	PHYCAS_ASSERT(j < dim);
    m[i][j] = v;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Sets element at row `i', column `j' of the matrix to the current value plus `v'.
*/
void SquareMatrix::AddToElement(unsigned i, unsigned j, double v)
	{
	PHYCAS_ASSERT(i < dim);
	PHYCAS_ASSERT(j < dim);
    m[i][j] += v;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns the entire matrix as a vector by row.
*/
std::vector<double> SquareMatrix::GetMatrix() const
	{
    std::vector<double> v;
    double * ptr = (double *)&m[0];
    for (unsigned i = 0; i < dim*dim; ++i)
        v.push_back(ptr[i]);
    return v;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Copies supplied vector `v' into this matrix. Vector `v' is expected to have length `sz'*`sz', and matrix is read
|   from `v' by row. The data member `dim' is set equal to `sz'.
*/
void SquareMatrix::SetMatrix(unsigned sz, std::vector<double> v)
	{
	PHYCAS_ASSERT(sz == (int)sqrt(v.size()));
    CreateMatrix(sz, 0.0);
    double * ptr = (double *)&m[0][0];
    for (std::vector<double>::iterator it = v.begin(); it != v.end(); ++it)
        {
        double val = *it;
        *ptr++ = val;
        }
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Fills all cells of matrix `m' with `value'.
*/
void SquareMatrix::Fill(
  double value) /**< is the value to which every element in matrix is to be set */
	{
    unsigned dim = GetDimension();
	unsigned last = dim*dim;
	double * p = &m[0][0];
	for (unsigned i = 0; i < last; ++i)
		*p++ = value;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Assumes dimension > 0 and `m' exists. Converts `m' to the identity matrix.
*/
void SquareMatrix::Identity()
	{
	//std::cerr << "----> SquareMatrix::Identity " << id << " <----" << std::endl;
	PHYCAS_ASSERT(dim > 0);
	unsigned i;
	unsigned last = dim*dim;
	double * p = &m[0][0];
	for (i = 0; i < last; ++i)
		*p++ = 0.0;
	for (i = 0; i < dim; ++i)
		m[i][i] = 1.0;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Assumes dimension > 0 and `m' exists. Multiplies each element of `m' by the supplied `scalar'.
*/
void SquareMatrix::ScalarMultiply(
  double scalar)	/**< is the scalar value multiplied by each element */
	{
	//std::cerr << "----> SquareMatrix::ScalarMultiply " << id << ", scalar = " << scalar << " <----" << std::endl;
	PHYCAS_ASSERT(dim > 0);
	unsigned last = dim*dim;
	double * p = &m[0][0];
	for (unsigned i = 0; i < last; ++i)
		*p++ *= scalar;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Assumes dimension > 0 and `m' exists. Subtracts each element of `other' from the corresponding element of this.
*/
void SquareMatrix::Subtract(
  const SquareMatrix & other)	 /**< is the matrix to subtract from this */
	{
	PHYCAS_ASSERT(dim > 0);
	unsigned last = dim*dim;
	double * otherp = &other.m[0][0];
	double * p = &m[0][0];
	for (unsigned i = 0; i < last; ++i)
		*p++ -= *otherp++;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Assumes dimension > 0 and `m' exists. Sets each element of `m' to the supplied `scalar'.
*/
void SquareMatrix::SetToScalar(
  double scalar)	/**< is the scalar value to which each element is set */
	{
	//std::cerr << "----> SquareMatrix::SetToScalar " << id << ", scalar = " << scalar << " <----" << std::endl;
	PHYCAS_ASSERT(dim > 0);
	unsigned last = dim*dim;
	double * p = &m[0][0];
	for (unsigned i = 0; i < last; ++i)
		*p++ = scalar;
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Saves a representation of the matrix to the supplied string for use in debugging. The caller should supply a format
|	string suitable for representing a single value of the matrix: e.g. "%12.5f\t".
*/
void SquareMatrix::MatrixToString(
  std::string & s,		 /**< the string to which a representation will be saved */
  std::string fmt) const /**< the format string */
	{
    unsigned dim = GetDimension();
	//s += boost::str(boost::format("This is SquareMatrix %d\n") % id);
	for (unsigned i = 0; i < dim; ++i)
		{
		for (unsigned j = 0; j < dim; ++j)
			{
			s += str(boost::format(fmt) % m[i][j]);
			}
		s += '\n';
		}
	}

/*----------------------------------------------------------------------------------------------------------------------
|	Returns the Cholesky decomposition of this SquareMatrix as a lower-triangular matrix. Assumes this SquareMatrix is
|   symmetric and positive definite.
*/
SquareMatrix * SquareMatrix::CholeskyDecomposition() const
    {
	PHYCAS_ASSERT(dim > 0);
    SquareMatrix * L = new SquareMatrix(*this);
    double * p = new double[dim];
    double ** a = L->GetMatrixAsRawPointer();

    for (unsigned i = 0; i < dim; ++i)
        {
        for (unsigned j = i; j < dim; ++j)
            {
            double sum = a[i][j];
            for (int k = i-1; k >= 0; --k)
                {
                sum -= a[i][k]*a[j][k];
                }
            if (i == j)
                {
                if (sum <= 0.0)
                    {
                    // matrix is not positive definite
                    return 0;
                    }
                p[i] = sqrt(sum);
                }
            else
                {
                a[j][i] = sum/p[i];
                }
            }
        }

    // zero out upper diagonal and copy diagonal elements
    for (unsigned i = 0; i < dim; ++i)
        {
        a[i][i] = p[i];
        for (unsigned j = i+1; j < dim; ++j)
            {
            a[i][j] = 0.0;
            }
        }

    return L;
    }

/*----------------------------------------------------------------------------------------------------------------------
|	Returns the LU decomposition of this SquareMatrix.
*/
SquareMatrix * SquareMatrix::LUDecomposition() const
    {
	PHYCAS_ASSERT(dim > 0);
    SquareMatrix * L = new SquareMatrix(*this);
    double * scaling = new double[dim];
    int * permutation = new int[dim];
    double ** a = L->GetMatrixAsRawPointer();
	int err_code = LUDecompose(a, dim, scaling, permutation, NULL);
    if (err_code == 0)
        {
        // LUDecompose worked
        delete [] scaling;
        delete [] permutation;
        return L;
        }

    // Should throw an exception here
    delete L;
    delete [] scaling;
    delete [] permutation;
    return 0;
    }

/*----------------------------------------------------------------------------------------------------------------------
|	Returns the natural logarithm of the product of the eigenvalues of this SquareMatrix.
*/
double SquareMatrix::LogDeterminant() const
    {
	PHYCAS_ASSERT(dim > 0);
    double * fv = new double[dim];
    double * w = new double[dim];
    double ** a = this->GetMatrixAsRawPointer();
    SquareMatrix * Z = new SquareMatrix(dim, 0.0);
    double ** z = Z->GetMatrixAsRawPointer();

	// Calculate eigenvalues (w) and eigenvectors (z)
    // int n        input: the order of the matrix a
    // double **a   input: the real symmetric matrix
    // double *fv   input: temporary storage array of at least n elements
    // double **z   output: the eigenvectors
    // double *w    output: the eigenvalues in ascending order
	int err_code = EigenRealSymmetric(dim, a, w, z, fv);
    if (err_code != 0)
        {
        delete Z;
        delete [] fv;
        delete [] w;
        return 0;
        }

    double log_det = 0.0;
    for (unsigned i = 0; i < dim; ++i)
        {
        double v = w[i];
        log_det += log(v);
        }

    delete Z;
    delete [] fv;
    delete [] w;

    return log_det;
    }

/*----------------------------------------------------------------------------------------------------------------------
|	Returns the natural logarithm of the product of the terms on the main diagonal of this SquareMatrix. If this matrix
|   is triangular, this is equal to the log of the determinant.
*/
double SquareMatrix::LogProdMainDiag() const
    {
	PHYCAS_ASSERT(dim > 0);
    double sumLog = 0.0;
    for (unsigned i = 0; i < dim; ++i)
        {
        double tmp = GetElement(i, i);
        PHYCAS_ASSERT(tmp > 0.0);
        sumLog += log(tmp);
        }
    return sumLog;
    }

/*----------------------------------------------------------------------------------------------------------------------
|	Returns a copy of this matrix'.
*/
SquareMatrix * SquareMatrix::Duplicate() const
    {
	if (dim == 0)
        return new SquareMatrix();

    SquareMatrix * Z = new SquareMatrix(dim, 0.0);
    double * pZ = &Z->m[0][0];
    double * p = &m[0][0];
    unsigned last = dim*dim;
    for (unsigned i = 0; i < last; ++i)
        *pZ++ = *p++;
    return Z;
    }

/*----------------------------------------------------------------------------------------------------------------------
|	Creates and returns a matrix that represents this matrix raised to the power `p'.
*/
SquareMatrix * SquareMatrix::Power(
  double p) const   /**< the power to which this matrix should be raised */
    {
	PHYCAS_ASSERT(dim > 0);
	//PHYCAS_ASSERT(p > 0.0);
    double * fv = new double[dim];
    double * w = new double[dim];
    double ** a = this->GetMatrixAsRawPointer();
    SquareMatrix * Z = new SquareMatrix(dim, 0.0);
    double ** z = Z->GetMatrixAsRawPointer();

	// Calculate eigenvalues (w) and eigenvectors (z)
    // int n        input: the order of the matrix a
    // double **a   input: the real symmetric matrix
    // double *fv   input: temporary storage array of at least n elements
    // double **z   output: the eigenvectors
    // double *w    output: the eigenvalues in ascending order
	int err_code = EigenRealSymmetric(dim, a, w, z, fv);
    if (err_code != 0)
        {
        delete Z;
        delete [] fv;
        delete [] w;
        return 0;
        }

    SquareMatrix * Zinv = Z->Inverse();
    SquareMatrix * Lp = new SquareMatrix(dim, 0.0);

    // create diagonal matrix of scaled eigenvalues
    for (unsigned i = 0; i < dim; ++i)
        {
        double v = pow(w[i],p);
        Lp->SetElement(i, i, v);
        }

    // Zinv * A * Z = L
    //            A = Z * L * Zinv
    //          A^p = Z * L^p * Zinv
    SquareMatrix * ZLp = Z->RightMultiplyMatrix(*Lp);
    SquareMatrix * Ap = ZLp->RightMultiplyMatrix(*Zinv);

    delete Z;
    delete Zinv;
    delete Lp;
    delete ZLp;
    delete [] fv;
    delete [] w;

    return Ap;
    }

/*----------------------------------------------------------------------------------------------------------------------
|	Creates and returns a matrix that represents the inverse of this SquareMatrix.
*/
SquareMatrix * SquareMatrix::Inverse() const
    {
    double * col = new double[dim];
    int * permutation = new int[dim];
    SquareMatrix * tmp = new SquareMatrix(*this);
    double ** a = tmp->GetMatrixAsRawPointer();
    SquareMatrix * inv = new SquareMatrix(*this);
    double ** a_inv = inv->GetMatrixAsRawPointer();

    // double **  a           matrix represented as vector of row pointers
    // int        n           order of matrix
    // double *   col         work vector of size n
    // int *      permutation work vector of size n
    // double **  a_inv       inverse of input matrix a (matrix a is destroyed)
    int result = InvertMatrix(a, dim, col, permutation, a_inv);
    delete tmp;
    delete [] col;
    delete [] permutation;
    if (result != 0)
        {
        delete inv;
        return 0;
        }
    return inv;
    }

/*----------------------------------------------------------------------------------------------------------------------
|	Creates and returns a matrix that represents the product of `matrixOnLeft' with this matrix.
*/
SquareMatrix * SquareMatrix::LeftMultiplyMatrix(SquareMatrix & matrixOnLeft) const
    {
    double ** l = matrixOnLeft.GetMatrixAsRawPointer();
    double ** r = GetMatrixAsRawPointer();
    SquareMatrix * tmp = new SquareMatrix(*this);
    double ** t = tmp->GetMatrixAsRawPointer();

    for (unsigned i = 0; i < dim; ++i)
        {
        for (unsigned j = 0; j < dim; ++j)
            {
            double vsum = 0.0;
            for (unsigned k = 0; k < dim; ++k)
                {
                vsum += l[i][k]*r[k][j];
                }
            t[i][j] = vsum;
            }
        }
    return tmp;
    }

/*----------------------------------------------------------------------------------------------------------------------
|	Creates and returns a matrix that represents the product of this matrix with `matrixOnRight'.
*/
SquareMatrix * SquareMatrix::RightMultiplyMatrix(SquareMatrix & matrixOnRight) const
    {
    double ** l = GetMatrixAsRawPointer();
    double ** r = matrixOnRight.GetMatrixAsRawPointer();
    SquareMatrix * tmp = new SquareMatrix(*this);
    double ** t = tmp->GetMatrixAsRawPointer();

    for (unsigned i = 0; i < dim; ++i)
        {
        for (unsigned j = 0; j < dim; ++j)
            {
            double vsum = 0.0;
            for (unsigned k = 0; k < dim; ++k)
                {
                vsum += l[i][k]*r[k][j];
                }
            t[i][j] = vsum;
            }
        }
    return tmp;
    }

/*----------------------------------------------------------------------------------------------------------------------
|	Creates and returns a matrix that represents the product of  (transposed) `vectorOnLeft' (1xdim) and this
|   matrix (dimxdim). Returns transposed vector (1xdim).
*/
std::vector<double> SquareMatrix::LeftMultiplyVector(const std::vector<double> & vectorOnLeft) const
    {
    double ** r = GetMatrixAsRawPointer();
    //work.clear();
    std::vector<double> tmp;

    for (unsigned i = 0; i < dim; ++i)
        {
        double vsum = 0.0;
        for (unsigned k = 0; k < dim; ++k)
            {
            vsum += vectorOnLeft[k]*r[k][i];
            }
        tmp.push_back(vsum);
        }
    return tmp;
    }

/*----------------------------------------------------------------------------------------------------------------------
|	Creates and returns a matrix that represents the product of this matrix (dimxdim)with `vectorOnRight' (dimx1).
|   Returns vector (dimx1).
*/
std::vector<double> SquareMatrix::RightMultiplyVector(const std::vector<double> & vectorOnRight) const
    {
    double ** l = GetMatrixAsRawPointer();
    //work.clear();
    std::vector<double> tmp;

    for (unsigned i = 0; i < dim; ++i)
        {
        double vsum = 0.0;
        for (unsigned k = 0; k < dim; ++k)
            {
            vsum += l[i][k]*vectorOnRight[k];
            }
        tmp.push_back(vsum);
        }
    return tmp;
    }

} // namespace phycas


//
// Created by robin on 2018/9/1.
// Copyright (c) 2018 Robin. All rights reserved.
//

#include "polynomial.h"
#include "bytes.h"

#include <boost/lexical_cast.hpp>
#include <experimental/iterator>

namespace satz::gf2::v2 {

Polynomial Polynomial::from_ulong (unsigned long l) {
  static_assert(sizeof(l) == 8);
  uint8_t* start = reinterpret_cast<uint8_t*>(&l);
  return from_bytes({start, start + sizeof(l)});
}

Polynomial Polynomial::from_bytes (gsl::span<uint8_t> bytes) {
  return Polynomial(satz::bytes::make_indices<int_type>(bytes));
}

Polynomial Polynomial::from_bytes (gsl::span<uint8_t> bytes,
    Polynomial::int_type degree) {

  Expects(bytes.size() * 8 > degree);

  std::vector<int_type> d = satz::bytes::make_indices<int_type>(bytes);
  auto it = std::lower_bound(d.begin(), d.end(), degree);
  if (it == d.end() || *it != degree) {
    d.erase(it, d.end());
    d.push_back(static_cast<int>(degree));
  } else { // `*it == degree`
    std::advance(it, 1);
    d.erase(it, d.end());
  }

  return Polynomial(std::move(d));
}

Polynomial Polynomial::make_random (Polynomial::int_type degree) {
  auto bytes = satz::bytes::make_random_bytes(static_cast<int>(degree / 8 + 1));
  return Polynomial::from_bytes(bytes, degree);
}

Polynomial Polynomial::make_irreducible (Polynomial::int_type degree) {
  Expects(degree > 0);

  // According to
  //    Some applications of Rabin’s fingerprinting method [Broder],
  // the total number of irreducible polynomials of degree $k$ with
  // coefficients in $Z_2$ is greater than $(2^k - 2^{k/2})/k$. Thus
  // the probability of picking an irreducible polynomial with a
  // uniformly random draw is roughly greater than $1/k$. If we try
  // $m$ independent draws, the probability of failing to obtain an
  // irreducible polynomial is roughly less than $(1 - 1/k)^m$, which
  // is roughly $e^{-m/k}$.
  int magic_number = 15;
  auto trials = degree * magic_number;
  while (trials-- > 0) {
    auto p = Polynomial::make_random(degree);
    if (is_irreducible(p)) return p;
  }

  throw std::runtime_error("fail to obtain an irreducible polynomial");
}

std::vector<uint8_t> Polynomial::to_bytes () const {
  const int d = degree();
  const size_t count = (d >= 0) ? (d / 8 + 1) : 0;

  std::vector<uint8_t> ret(count, 0);
  std::for_each(deg_.begin(),
                deg_.end(),
                [&] (int_type n) {
                  const auto i = n / 8;
                  const auto j = n % 8;
                  ret[i] = ret[i] | uint8_t(0x1 << j);
                });
  return ret;
}

Polynomial::operator bool () const {return !empty();}

bool operator == (const Polynomial& lhs, const Polynomial& rhs) {
  return lhs.deg_ == rhs.deg_;
}

bool operator != (const Polynomial& lhs, const Polynomial& rhs) {
  return !(rhs == lhs);
}

Polynomial& Polynomial::operator ^= (const Polynomial& rhs) {
  Polynomial res = *this ^rhs;
  deg_.swap(res.deg_);
  return *this;
}

Polynomial& Polynomial::operator |= (const Polynomial& rhs) {
  Polynomial res = *this | rhs;
  deg_.swap(res.deg_);
  return *this;
}

Polynomial& Polynomial::operator &= (const Polynomial& rhs) {
  Polynomial res = *this & rhs;
  deg_.swap(res.deg_);
  return *this;
}

Polynomial& Polynomial::operator += (const Polynomial& rhs) {
  Polynomial res = *this + rhs;
  deg_.swap(res.deg_);
  return *this;

}

Polynomial& Polynomial::operator -= (const Polynomial& rhs) {
  Polynomial res = *this - rhs;
  deg_.swap(res.deg_);
  return *this;
}

Polynomial& Polynomial::operator *= (const Polynomial& rhs) {
  Polynomial res = *this * rhs;
  deg_.swap(res.deg_);
  return *this;
}

Polynomial& Polynomial::operator %= (const Polynomial& rhs) {
  Polynomial res = *this % rhs;
  deg_.swap(res.deg_);
  return *this;
}

Polynomial& Polynomial::operator <<= (Polynomial::int_type n) {
  Expects(n >= 0);
  Polynomial res = *this << n;
  deg_.swap(res.deg_);
  return *this;
}

Polynomial& Polynomial::operator >>= (Polynomial::int_type n) {
  Expects(n >= 0);
  Polynomial res = *this >> n;
  deg_.swap(res.deg_);
  return *this;
}

Polynomial operator ^ (const Polynomial& lhs, const Polynomial& rhs) {
  Polynomial ret;
  std::set_symmetric_difference(lhs.deg_.begin(),
                                lhs.deg_.end(),
                                rhs.deg_.begin(),
                                rhs.deg_.end(),
                                std::back_inserter(ret.deg_));
  return ret;
}

Polynomial operator | (const Polynomial& lhs, const Polynomial& rhs) {
  Polynomial ret;
  std::set_union(lhs.deg_.begin(),
                 lhs.deg_.end(),
                 rhs.deg_.begin(),
                 rhs.deg_.end(),
                 std::back_inserter(ret.deg_));
  return ret;
}

Polynomial operator & (const Polynomial& lhs, const Polynomial& rhs) {
  Polynomial ret;
  std::set_intersection(lhs.deg_.begin(),
                        lhs.deg_.end(),
                        rhs.deg_.begin(),
                        rhs.deg_.end(),
                        std::back_inserter(ret.deg_));
  return ret;
}

Polynomial operator + (const Polynomial& lhs, const Polynomial& rhs) {
  return lhs ^ rhs;
}

Polynomial operator - (const Polynomial& lhs, const Polynomial& rhs) {
  return lhs ^ rhs;
}

template<typename T, typename N>
static void flip (std::vector<T>& bytes, N n) {
  bytes[n / 8] ^= (1 << (n % 8));
}

Polynomial operator * (const Polynomial& lhs, const Polynomial& rhs) {
  if (lhs.degree() == 0 || rhs.degree() == 0) return Polynomial{};

  auto degree_sum = lhs.degree() + rhs.degree();
  std::vector<uint8_t> bytes(degree_sum / 8 + 1, 0);
  for (const auto& x : lhs.deg_)
    for (const auto& y : rhs.deg_)
      flip(bytes, x + y);
  return Polynomial::from_bytes(bytes);
}

Polynomial Polynomial::operator << (Polynomial::int_type n) const {
  Expects(n >= 0);

  Polynomial ret;
  std::transform(deg_.begin(),
                 deg_.end(),
                 std::back_inserter(ret.deg_),
                 [&n] (const auto& m) {return m + n;});
  return ret;
}

Polynomial Polynomial::operator >> (Polynomial::int_type n) const {
  Expects(n >= 0);

  Polynomial ret;
  auto start = std::find_if(deg_.begin(),
                            deg_.end(),
                            [&n] (const auto& m) {return m > n;});
  std::transform(start,
                 deg_.end(),
                 std::back_inserter(ret.deg_),
                 [&n] (const auto& m) {return m - n;});
  return ret;
}

Polynomial operator % (const Polynomial& lhs, const Polynomial& rhs) {

  Polynomial ret{lhs};

  using N = Polynomial::int_type;
  const N dl = lhs.degree();
  const N dr = rhs.degree();

  if (dl >= dr) {
    N i = dl - dr;
    do {
      if (ret.contains(i + dr)) ret -= (rhs << i);
    } while (N(0) != i--);
  }

  return ret;
}

std::ostream& operator << (std::ostream& os, const Polynomial& obj) {
  // @formatter:off
  auto to_string = [] (const auto& n) {
    return
      n > 1 ? ("x^" + boost::lexical_cast<std::string>(n)) :
      n == 1 ? "x" :
      n == 0 ? "1" :
      throw std::runtime_error("n should be nonnegative");
  };
  // @formatter:on

  const auto& d = obj.deg_;
  if (d.empty()) {
    os << "0";
  } else {
    std::transform(d.rbegin(),
                   d.rend(),
                   std::experimental::make_ostream_joiner(os, "+"),
                   to_string);
  }
  return os;
}

bool Polynomial::empty () const {return deg_.empty();}

Polynomial::int_type Polynomial::degree () const {
  return empty() ? -1 : *deg_.rbegin();
}

Polynomial::int_type Polynomial::nnz () const {
  return static_cast<int_type>(deg_.size());
}

bool Polynomial::contains (Polynomial::int_type n) const {
  return std::binary_search(deg_.begin(), deg_.end(), n);
}

Polynomial::Polynomial (Polynomial::container_type&& v) : deg_(std::move(v)) {
  Expects(std::all_of(deg_.begin(),
                      deg_.end(),
                      [] (const auto& n) {return n >= 0;}));
  Expects(std::is_sorted(deg_.begin(), deg_.end()));
}

bool operator < (const Polynomial& lhs, const Polynomial& rhs) {
  return std::lexicographical_compare(lhs.deg_.rbegin(),
                                      lhs.deg_.rend(),
                                      rhs.deg_.rbegin(),
                                      rhs.deg_.rend());
}

bool operator > (const Polynomial& lhs, const Polynomial& rhs) {
  return rhs < lhs;
}

bool operator <= (const Polynomial& lhs, const Polynomial& rhs) {
  return !(rhs < lhs);
}

bool operator >= (const Polynomial& lhs, const Polynomial& rhs) {
  return !(lhs < rhs);
}

}


///////////////////////////////////////////////////////////////
//  Copyright 2012-20 John Maddock.
//  Copyright 2019-20 Christopher Kormanyos.
//  Copyright 2019-20 Madhur Chauhan.
//  Distributed under the Boost Software License, Version 1.0.
//  (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt
//
// Comparison operators for cpp_int_backend:
//
#ifndef BOOST_MP_CPP_INT_MUL_HPP
#define BOOST_MP_CPP_INT_MUL_HPP

#include <limits>
#include <boost/multiprecision/detail/standalone_config.hpp>
#include <boost/multiprecision/detail/endian.hpp>
#include <boost/multiprecision/detail/assert.hpp>
#include <boost/multiprecision/integer.hpp>

namespace boost { namespace multiprecision { namespace backends {

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4127) // conditional expression is constant
#endif
//
// Multiplication by a single limb:
//
template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1, std::size_t MinBits2, std::size_t MaxBits2, cpp_integer_type SignType2, cpp_int_check_type Checked2, class Allocator2>
inline BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<!is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value && !is_trivial_cpp_int<cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2> >::value>::type
eval_multiply(
    cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&       result,
    const cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>& a,
    const limb_type&                                                            val) noexcept((is_non_throwing_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value))
{
   if (!val)
   {
      result = static_cast<limb_type>(0);
      return;
   }
   if ((void*)&a != (void*)&result)
      result.resize(a.size(), a.size());
   double_limb_type                                                                                carry = 0;
   typename cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>::limb_pointer       p     = result.limbs();
   typename cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>::limb_pointer       pe    = result.limbs() + result.size();
   typename cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>::const_limb_pointer pa    = a.limbs();
   while (p != pe)
   {
      carry += static_cast<double_limb_type>(*pa) * static_cast<double_limb_type>(val);
#ifdef __MSVC_RUNTIME_CHECKS
      *p = static_cast<limb_type>(carry & ~static_cast<limb_type>(0));
#else
      *p = static_cast<limb_type>(carry);
#endif
      carry >>= cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>::limb_bits;
      ++p, ++pa;
   }
   if (carry)
   {
      std::size_t i = result.size();
      result.resize(i + 1, i + 1);
      if (result.size() > i)
         result.limbs()[i] = static_cast<limb_type>(carry);
   }
   result.sign(a.sign());
   if (is_fixed_precision<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value)
      result.normalize();
}

//
// resize_for_carry forces a resize of the underlying buffer only if a previous request
// for "required" elements could possibly have failed, *and* we have checking enabled.
// This will cause an overflow error inside resize():
//
template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1>
inline BOOST_MP_CXX14_CONSTEXPR void resize_for_carry(cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>& /*result*/, std::size_t /*required*/) {}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, class Allocator1>
inline BOOST_MP_CXX14_CONSTEXPR void resize_for_carry(cpp_int_backend<MinBits1, MaxBits1, SignType1, checked, Allocator1>& result, std::size_t required)
{
   if (result.size() < required)
      result.resize(required, required);
}
//
// Minimum number of limbs required for Karatsuba to be worthwhile:
//
#ifdef BOOST_MP_KARATSUBA_CUTOFF
const size_t karatsuba_cutoff = BOOST_MP_KARATSUBA_CUTOFF;
#else
const size_t karatsuba_cutoff = 40;
#endif
//
// Core (recursive) Karatsuba multiplication, all the storage required is allocated upfront and
// passed down the stack in this routine.  Note that all the cpp_int_backend's must be the same type
// and full variable precision.  Karatsuba really doesn't play nice with fixed-size integers.  If necessary
// fixed precision integers will get aliased as variable-precision types before this is called.
//
template <std::size_t MinBits, std::size_t MaxBits, cpp_int_check_type Checked, class Allocator>
inline void multiply_karatsuba(
    cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked, Allocator>&       result,
    const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked, Allocator>& a,
    const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked, Allocator>& b,
    typename cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked, Allocator>::scoped_shared_storage& storage)
{
   using cpp_int_type = cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked, Allocator>;

   std::size_t as = a.size();
   std::size_t bs = b.size();
   //
   // Termination condition: if either argument is smaller than karatsuba_cutoff
   // then schoolboy multiplication will be faster:
   //
   if ((as < karatsuba_cutoff) || (bs < karatsuba_cutoff))
   {
      eval_multiply(result, a, b);
      return;
   }
   //
   // Partitioning size: split the larger of a and b into 2 halves
   //
   std::size_t n  = (as > bs ? as : bs) / 2 + 1;
   //
   // Partition a and b into high and low parts.
   // ie write a, b as a = a_h * 2^n + a_l, b = b_h * 2^n + b_l
   //
   // We could copy the high and low parts into new variables, but we'll
   // use aliasing to reference the internal limbs of a and b.  There is one wart here:
   // if a and b are mismatched in size, then n may be larger than the smaller
   // of a and b.  In that situation the high part is zero, and we have no limbs
   // to alias, so instead alias a local variable.
   // This raises 2 questions:
   // * Is this the best way to partition a and b?
   // * Since we have one high part zero, the arithmetic simplifies considerably,
   //   so should we have a special routine for this?
   //
   std::size_t          sz = (std::min)(as, n);
   const cpp_int_type a_l(a.limbs(), 0, sz);

   sz = (std::min)(bs, n);
   const cpp_int_type b_l(b.limbs(), 0, sz);

   limb_type          zero = 0;
   const cpp_int_type a_h(as > n ? a.limbs() + n : &zero, 0, as > n ? as - n : 1);
   const cpp_int_type b_h(bs > n ? b.limbs() + n : &zero, 0, bs > n ? bs - n : 1);
   //
   // The basis for the Karatsuba algorithm is as follows:
   //
   // let                x = a_h * b_ h
   //                    y = a_l * b_l
   //                    z = (a_h + a_l)*(b_h + b_l) - x - y
   // and therefore  a * b = x * (2 ^ (2 * n))+ z * (2 ^ n) + y
   //
   // Begin by allocating our temporaries, these alias the memory already allocated in the shared storage:
   //
   cpp_int_type t1(storage, 2 * n + 2);
   cpp_int_type t2(storage, n + 1);
   cpp_int_type t3(storage, n + 1);
   //
   // Now we want:
   //
   // result = | a_h*b_h  | a_l*b_l |
   // (bits)              <-- 2*n -->
   //
   // We create aliases for the low and high parts of result, and multiply directly into them:
   //
   cpp_int_type result_low(result.limbs(), 0, 2 * n);
   cpp_int_type result_high(result.limbs(), 2 * n, result.size() - 2 * n);
   //
   // low part of result is a_l * b_l:
   //
   multiply_karatsuba(result_low, a_l, b_l, storage);
   //
   // We haven't zeroed out memory in result, so set to zero any unused limbs,
   // if a_l and b_l have mostly random bits then nothing happens here, but if
   // one is zero or nearly so, then a memset might be faster... it's not clear
   // that it's worth the extra logic though (and is darn hard to measure
   // what the "average" case is).
   //
   for (std::size_t i = result_low.size(); i < 2 * n; ++i)
      result.limbs()[i] = 0;
   //
   // Set the high part of result to a_h * b_h:
   //
   multiply_karatsuba(result_high, a_h, b_h, storage);
   for (std::size_t i = result_high.size() + 2 * n; i < result.size(); ++i)
      result.limbs()[i] = 0;
   //
   // Now calculate (a_h+a_l)*(b_h+b_l):
   //
   add_unsigned(t2, a_l, a_h);
   add_unsigned(t3, b_l, b_h);
   multiply_karatsuba(t1, t2, t3, storage); // t1 = (a_h+a_l)*(b_h+b_l)
   //
   // There is now a slight deviation from Karatsuba, we want to subtract
   // a_l*b_l + a_h*b_h from t1, but rather than use an addition and a subtraction
   // plus one temporary, we'll use 2 subtractions.  On the minus side, a subtraction
   // is on average slightly slower than an addition, but we save a temporary (ie memory)
   // and also hammer the same piece of memory over and over rather than 2 disparate
   // memory regions.  Overall it seems to be a slight win.
   //
   subtract_unsigned(t1, t1, result_high);
   subtract_unsigned(t1, t1, result_low);
   //
   // The final step is to left shift t1 by n bits and add to the result.
   // Rather than do an actual left shift, we can simply alias the result
   // and add to the alias:
   //
   cpp_int_type result_alias(result.limbs(), n, result.size() - n);
   add_unsigned(result_alias, result_alias, t1);
   //
   // Free up storage for use by sister branches to this one:
   //
   storage.deallocate(t1.capacity() + t2.capacity() + t3.capacity());

   result.normalize();
}

inline std::size_t karatsuba_storage_size(std::size_t s)
{
   //
   // This estimates how much memory we will need based on
   // s-limb multiplication.  In an ideal world the number of limbs
   // would halve with each recursion, and our storage requirements
   // would be 4s in the limit, and rather less in practice since
   // we bail out long before we reach one limb.  In the real world
   // we don't quite halve s in each recursion, so this is an heuristic
   // which over-estimates how much we need.  We could compute an exact
   // value, but it would be rather time consuming.
   //
   return 5 * s;
}
//
// There are 2 entry point routines for Karatsuba multiplication:
// one for variable precision types, and one for fixed precision types.
// These are responsible for allocating all the storage required for the recursive
// routines above, and are always at the outermost level.
//
// Normal variable precision case comes first:
//
template <std::size_t MinBits, std::size_t MaxBits, cpp_integer_type SignType, cpp_int_check_type Checked, class Allocator>
inline typename std::enable_if<!is_fixed_precision<cpp_int_backend<MinBits, MaxBits, SignType, Checked, Allocator> >::value>::type
setup_karatsuba(
   cpp_int_backend<MinBits, MaxBits, SignType, Checked, Allocator>& result,
   const cpp_int_backend<MinBits, MaxBits, SignType, Checked, Allocator>& a,
   const cpp_int_backend<MinBits, MaxBits, SignType, Checked, Allocator>& b)
{
   std::size_t as = a.size();
   std::size_t bs = b.size();
   std::size_t s = as > bs ? as : bs;
   std::size_t storage_size = karatsuba_storage_size(s);
   if (storage_size < 300)
   {
      //
      // Special case: if we don't need too much memory, we can use stack based storage
      // and save a call to the allocator, this allows us to use Karatsuba multiply
      // at lower limb counts than would otherwise be possible:
      //
      limb_type limbs[300];
      typename cpp_int_backend<MinBits, MaxBits, SignType, Checked, Allocator>::scoped_shared_storage storage(limbs, storage_size);
      multiply_karatsuba(result, a, b, storage);
   }
   else
   {
      typename cpp_int_backend<MinBits, MaxBits, SignType, Checked, Allocator>::scoped_shared_storage storage(result.allocator(), storage_size);
      multiply_karatsuba(result, a, b, storage);
   }
}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1, std::size_t MinBits2, std::size_t MaxBits2, cpp_integer_type SignType2, cpp_int_check_type Checked2, class Allocator2, std::size_t MinBits3, std::size_t MaxBits3, cpp_integer_type SignType3, cpp_int_check_type Checked3, class Allocator3>
inline typename std::enable_if<is_fixed_precision<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value || is_fixed_precision<cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2> >::value || is_fixed_precision<cpp_int_backend<MinBits3, MaxBits3, SignType3, Checked3, Allocator3> >::value>::type
setup_karatsuba(
    cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&       result,
    const cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>& a,
    const cpp_int_backend<MinBits3, MaxBits3, SignType3, Checked3, Allocator3>& b)
{
   //
   // Now comes the fixed precision case.
   // In fact Karatsuba doesn't really work with fixed precision since the logic
   // requires that we calculate all the bits of the result (especially in the
   // temporaries used internally).  So... we'll convert all the arguments
   // to variable precision types by aliasing them, this also
   // reduce the number of template instantations:
   //
   using variable_precision_type = cpp_int_backend<0, 0, signed_magnitude, unchecked, std::allocator<limb_type> >;
   variable_precision_type a_t(a.limbs(), 0, a.size()), b_t(b.limbs(), 0, b.size());
   std::size_t as = a.size();
   std::size_t bs = b.size();
   std::size_t s = as > bs ? as : bs;
   std::size_t sz = as + bs;
   std::size_t storage_size = karatsuba_storage_size(s);

   if (!is_fixed_precision<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value || (sz * sizeof(limb_type) * CHAR_BIT <= MaxBits1))
   {
      // Result is large enough for all the bits of the result, so we can use aliasing:
      result.resize(sz, sz);
      variable_precision_type t(result.limbs(), 0, result.size());
      typename variable_precision_type::scoped_shared_storage storage(t.allocator(), storage_size);
      multiply_karatsuba(t, a_t, b_t, storage);
      result.resize(t.size(), t.size());
   }
   else
   {
      //
      // Not enough bit in result for the answer, so we must use a temporary
      // and then truncate (ie modular arithmetic):
      //
      typename variable_precision_type::scoped_shared_storage storage(variable_precision_type::allocator_type(), sz + storage_size);
      variable_precision_type t(storage, sz);
      multiply_karatsuba(t, a_t, b_t, storage);
      //
      // If there is truncation, and result is a checked type then this will throw:
      //
      result = t;
   }
}
template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1, std::size_t MinBits2, std::size_t MaxBits2, cpp_integer_type SignType2, cpp_int_check_type Checked2, class Allocator2, std::size_t MinBits3, std::size_t MaxBits3, cpp_integer_type SignType3, cpp_int_check_type Checked3, class Allocator3>
inline typename std::enable_if<!is_fixed_precision<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value && !is_fixed_precision<cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2> >::value && !is_fixed_precision<cpp_int_backend<MinBits3, MaxBits3, SignType3, Checked3, Allocator3> >::value>::type
setup_karatsuba(
   cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>& result,
   const cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>& a,
   const cpp_int_backend<MinBits3, MaxBits3, SignType3, Checked3, Allocator3>& b)
{
   //
   // Variable precision, mixed arguments, just alias and forward:
   //
   using variable_precision_type = cpp_int_backend<0, 0, signed_magnitude, unchecked, std::allocator<limb_type> >;
   variable_precision_type a_t(a.limbs(), 0, a.size()), b_t(b.limbs(), 0, b.size());
   std::size_t as = a.size();
   std::size_t bs = b.size();
   std::size_t s = as > bs ? as : bs;
   std::size_t sz = as + bs;
   std::size_t storage_size = karatsuba_storage_size(s);

   result.resize(sz, sz);
   variable_precision_type t(result.limbs(), 0, result.size());
   typename variable_precision_type::scoped_shared_storage storage(t.allocator(), storage_size);
   multiply_karatsuba(t, a_t, b_t, storage);
}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1, std::size_t MinBits2, std::size_t MaxBits2, cpp_integer_type SignType2, cpp_int_check_type Checked2, class Allocator2, std::size_t MinBits3, std::size_t MaxBits3, cpp_integer_type SignType3, cpp_int_check_type Checked3, class Allocator3>
inline BOOST_MP_CXX14_CONSTEXPR void
eval_multiply_comba(
    cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&       result,
    const cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>& a,
    const cpp_int_backend<MinBits3, MaxBits3, SignType3, Checked3, Allocator3>& b) noexcept((is_non_throwing_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value))
{
   //
   // see PR #182
   // Comba Multiplier - based on Paul Comba's
   // Exponentiation cryptosystems on the IBM PC, 1990
   //
   std::ptrdiff_t as                                                                                = a.size(),
       bs                                                                                = b.size(),
       rs                                                                                = result.size();
   typename cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>::limb_pointer pr = result.limbs();

   double_limb_type carry    = 0,
                    temp     = 0;
   limb_type      overflow   = 0;
   const std::size_t limb_bits  = sizeof(limb_type) * CHAR_BIT;
   const bool     must_throw = rs < as + bs - 1;
   for (std::ptrdiff_t r = 0, lim = (std::min)(rs, as + bs - 1); r < lim; ++r, overflow = 0)
   {
      std::ptrdiff_t i = r >= as ? as - 1 : r,
          j = r - i,
          k = i < bs - j ? i + 1 : bs - j; // min(i+1, bs-j);

      typename cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>::const_limb_pointer pa = a.limbs() + i;
      typename cpp_int_backend<MinBits3, MaxBits3, SignType3, Checked3, Allocator3>::const_limb_pointer pb = b.limbs() + j;

      temp = carry;
      carry += static_cast<double_limb_type>(*(pa)) * (*(pb));
      overflow += carry < temp;
      for (--k; k; k--)
      {
         temp = carry;
         carry += static_cast<double_limb_type>(*(--pa)) * (*(++pb));
         overflow += carry < temp;
      }
      *(pr++) = static_cast<limb_type>(carry);
      carry   = (static_cast<double_limb_type>(overflow) << limb_bits) | (carry >> limb_bits);
   }
   if (carry || must_throw)
   {
      resize_for_carry(result, as + bs);
      if (static_cast<int>(result.size()) >= as + bs)
         *pr = static_cast<limb_type>(carry);
   }
}
template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1, std::size_t MinBits2, std::size_t MaxBits2, cpp_integer_type SignType2, cpp_int_check_type Checked2, class Allocator2, std::size_t MinBits3, std::size_t MaxBits3, cpp_integer_type SignType3, cpp_int_check_type Checked3, class Allocator3>
inline BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<!is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value && !is_trivial_cpp_int<cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2> >::value && !is_trivial_cpp_int<cpp_int_backend<MinBits3, MaxBits3, SignType3, Checked3, Allocator3> >::value>::type
eval_multiply(
    cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&       result,
    const cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>& a,
    const cpp_int_backend<MinBits3, MaxBits3, SignType3, Checked3, Allocator3>& b)
   noexcept((is_non_throwing_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value
      && (karatsuba_cutoff * sizeof(limb_type) * CHAR_BIT > MaxBits1)
      && (karatsuba_cutoff * sizeof(limb_type)* CHAR_BIT > MaxBits2)
      && (karatsuba_cutoff * sizeof(limb_type)* CHAR_BIT > MaxBits3)))
{
   // Uses simple (O(n^2)) multiplication when the limbs are less
   // otherwise switches to karatsuba algorithm based on experimental value (~40 limbs)
   //
   // Trivial cases first:
   //
   std::size_t                                                                                as = a.size();
   std::size_t                                                                                bs = b.size();
   typename cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>::const_limb_pointer pa = a.limbs();
   typename cpp_int_backend<MinBits3, MaxBits3, SignType3, Checked3, Allocator3>::const_limb_pointer pb = b.limbs();
   if (as == 1)
   {
      bool s = b.sign() != a.sign();
      if (bs == 1)
      {
         result = static_cast<double_limb_type>(*pa) * static_cast<double_limb_type>(*pb);
      }
      else
      {
         limb_type l = *pa;
         eval_multiply(result, b, l);
      }
      result.sign(s);
      return;
   }
   if (bs == 1)
   {
      bool      s = b.sign() != a.sign();
      limb_type l = *pb;
      eval_multiply(result, a, l);
      result.sign(s);
      return;
   }

   if ((void*)&result == (void*)&a)
   {
      cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> t(a);
      eval_multiply(result, t, b);
      return;
   }
   if ((void*)&result == (void*)&b)
   {
      cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> t(b);
      eval_multiply(result, a, t);
      return;
   }

   constexpr const double_limb_type limb_max = ~static_cast<limb_type>(0u);
   constexpr const double_limb_type double_limb_max = ~static_cast<double_limb_type>(0u);

   result.resize(as + bs, as + bs - 1);
#ifndef BOOST_MP_NO_CONSTEXPR_DETECTION
   if (!BOOST_MP_IS_CONST_EVALUATED(as) && (as >= karatsuba_cutoff && bs >= karatsuba_cutoff))
#else
   if (as >= karatsuba_cutoff && bs >= karatsuba_cutoff)
#endif
   {
      setup_karatsuba(result, a, b);
      //
      // Set the sign of the result:
      //
      result.sign(a.sign() != b.sign());
      return;
   }
   typename cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>::limb_pointer pr = result.limbs();
   static_assert(double_limb_max - 2 * limb_max >= limb_max * limb_max, "failed limb size sanity check");

#ifndef BOOST_MP_NO_CONSTEXPR_DETECTION
   if (BOOST_MP_IS_CONST_EVALUATED(as))
   {
      for (std::size_t i = 0; i < result.size(); ++i)
         pr[i] = 0;
   }
   else
#endif
   std::memset(pr, 0, result.size() * sizeof(limb_type));

#if defined(BOOST_MP_COMBA)
       //
       // Comba Multiplier might not be efficient because of less efficient assembly
       // by the compiler as of 09/01/2020 (DD/MM/YY). See PR #182
       // Till then this will lay dormant :(
       //
       eval_multiply_comba(result, a, b);
#else

   double_limb_type carry = 0;
   for (std::size_t i = 0; i < as; ++i)
   {
      BOOST_MP_ASSERT(result.size() > i);
      std::size_t inner_limit = !is_fixed_precision<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value ? bs : (std::min)(result.size() - i, bs);
      std::size_t j           = 0;
      for (; j < inner_limit; ++j)
      {
         BOOST_MP_ASSERT(i + j < result.size());
#if (!defined(__GLIBCXX__) && !defined(__GLIBCPP__)) || !BOOST_WORKAROUND(BOOST_GCC_VERSION, <= 50100)
         BOOST_MP_ASSERT(!std::numeric_limits<double_limb_type>::is_specialized || ((std::numeric_limits<double_limb_type>::max)() - carry >
                                                                                static_cast<double_limb_type>(cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>::max_limb_value) * static_cast<double_limb_type>(cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>::max_limb_value)));
#endif
         carry += static_cast<double_limb_type>(pa[i]) * static_cast<double_limb_type>(pb[j]);
         BOOST_MP_ASSERT(!std::numeric_limits<double_limb_type>::is_specialized || ((std::numeric_limits<double_limb_type>::max)() - carry >= pr[i + j]));
         carry += pr[i + j];
#ifdef __MSVC_RUNTIME_CHECKS
         pr[i + j] = static_cast<limb_type>(carry & ~static_cast<limb_type>(0));
#else
         pr[i + j] = static_cast<limb_type>(carry);
#endif
         carry >>= cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>::limb_bits;
         BOOST_MP_ASSERT(carry <= (cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>::max_limb_value));
      }
      if (carry)
      {
         resize_for_carry(result, i + j + 1); // May throw if checking is enabled
         if (i + j < result.size())
#ifdef __MSVC_RUNTIME_CHECKS
            pr[i + j] = static_cast<limb_type>(carry & ~static_cast<limb_type>(0));
#else
            pr[i + j] = static_cast<limb_type>(carry);
#endif
      }
      carry = 0;
   }
#endif // ifdef(BOOST_MP_COMBA) ends

   result.normalize();
   //
   // Set the sign of the result:
   //
   result.sign(a.sign() != b.sign());
}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1, std::size_t MinBits2, std::size_t MaxBits2, cpp_integer_type SignType2, cpp_int_check_type Checked2, class Allocator2>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<!is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value && !is_trivial_cpp_int<cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2> >::value>::type
eval_multiply(
    cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&       result,
    const cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>& a)
   noexcept((noexcept(eval_multiply(std::declval<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>(), std::declval<const cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>(), std::declval<const cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>&>()))))
{
   eval_multiply(result, result, a);
}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<!is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value>::type
eval_multiply(cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>& result, const limb_type& val)
   noexcept((noexcept(eval_multiply(std::declval<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>(), std::declval<const cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>(), std::declval<const limb_type&>()))))
{
   eval_multiply(result, result, val);
}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1, std::size_t MinBits2, std::size_t MaxBits2, cpp_integer_type SignType2, cpp_int_check_type Checked2, class Allocator2>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<!is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value && !is_trivial_cpp_int<cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2> >::value>::type
eval_multiply(
    cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&       result,
    const cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>& a,
    const double_limb_type&                                                     val)
   noexcept(
      (noexcept(eval_multiply(std::declval<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>(), std::declval<const cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>&>(), std::declval<const limb_type&>())))
      && (noexcept(eval_multiply(std::declval<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>(), std::declval<const cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>&>(), std::declval<const cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>())))
   )
{
   if (val <= (std::numeric_limits<limb_type>::max)())
   {
      eval_multiply(result, a, static_cast<limb_type>(val));
   }
   else
   {
#if BOOST_MP_ENDIAN_LITTLE_BYTE && !defined(BOOST_MP_TEST_NO_LE)
      cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> t(val);
#else
      cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> t;
      t = val;
#endif
      eval_multiply(result, a, t);
   }
}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<!is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value>::type
eval_multiply(cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>& result, const double_limb_type& val)
   noexcept((noexcept(eval_multiply(std::declval<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>(), std::declval<const cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>(), std::declval<const double_limb_type&>()))))
{
   eval_multiply(result, result, val);
}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1, std::size_t MinBits2, std::size_t MaxBits2, cpp_integer_type SignType2, cpp_int_check_type Checked2, class Allocator2>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<!is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value && !is_trivial_cpp_int<cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2> >::value>::type
eval_multiply(
    cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&       result,
    const cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>& a,
    const signed_limb_type&                                                     val)
   noexcept((noexcept(eval_multiply(std::declval<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>(), std::declval<const cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>&>(), std::declval<const limb_type&>()))))
{
   if (val > 0)
      eval_multiply(result, a, static_cast<limb_type>(val));
   else
   {
      eval_multiply(result, a, static_cast<limb_type>(boost::multiprecision::detail::unsigned_abs(val)));
      result.negate();
   }
}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<!is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value>::type
eval_multiply(cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>& result, const signed_limb_type& val)
   noexcept((noexcept(eval_multiply(std::declval<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>(), std::declval<const cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>(), std::declval<const limb_type&>()))))
{
   eval_multiply(result, result, val);
}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1, std::size_t MinBits2, std::size_t MaxBits2, cpp_integer_type SignType2, cpp_int_check_type Checked2, class Allocator2>
inline BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<!is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value && !is_trivial_cpp_int<cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2> >::value>::type
eval_multiply(
    cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&       result,
    const cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>& a,
    const signed_double_limb_type&                                              val)
   noexcept(
   (noexcept(eval_multiply(std::declval<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>(), std::declval<const cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>&>(), std::declval<const limb_type&>())))
      && (noexcept(eval_multiply(std::declval<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>(), std::declval<const cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>&>(), std::declval<const cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>())))
   )
{
   if (val > 0)
   {
      if (val <= (std::numeric_limits<limb_type>::max)())
      {
         eval_multiply(result, a, static_cast<limb_type>(val));
         return;
      }
   }
   else if (val >= -static_cast<signed_double_limb_type>((std::numeric_limits<limb_type>::max)()))
   {
      eval_multiply(result, a, static_cast<limb_type>(boost::multiprecision::detail::unsigned_abs(val)));
      result.negate();
      return;
   }
#if BOOST_MP_ENDIAN_LITTLE_BYTE && !defined(BOOST_MP_TEST_NO_LE)
   cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> t(val);
#else
   cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> t;
   t = val;
#endif
   eval_multiply(result, a, t);
}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<!is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value>::type
eval_multiply(cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>& result, const signed_double_limb_type& val)
   noexcept(
   (noexcept(eval_multiply(std::declval<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>(), std::declval<const cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>(), std::declval<const limb_type&>())))
   && (noexcept(eval_multiply(std::declval<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>(), std::declval<const cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>(), std::declval<const cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&>())))
   )
{
   eval_multiply(result, result, val);
}

//
// Now over again for trivial cpp_int's:
//
template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<
    is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value && is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value && (is_signed_number<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value || is_signed_number<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value)>::type
eval_multiply(
    cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&       result,
    const cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>& o) noexcept((is_non_throwing_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value))
{
   *result.limbs() = detail::checked_multiply(*result.limbs(), *o.limbs(), typename cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>::checked_type());
   result.sign(result.sign() != o.sign());
   result.normalize();
}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<
    is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value && is_unsigned_number<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value>::type
eval_multiply(
    cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&       result,
    const cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>& o) noexcept((is_non_throwing_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value))
{
   *result.limbs() = detail::checked_multiply(*result.limbs(), *o.limbs(), typename cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>::checked_type());
   result.normalize();
}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<
    is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value && is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value && (is_signed_number<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value || is_signed_number<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value)>::type
eval_multiply(
    cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&       result,
    const cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>& a,
    const cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>& b) noexcept((is_non_throwing_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value))
{
   *result.limbs() = detail::checked_multiply(*a.limbs(), *b.limbs(), typename cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>::checked_type());
   result.sign(a.sign() != b.sign());
   result.normalize();
}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<
    is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value && is_unsigned_number<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value>::type
eval_multiply(
    cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&       result,
    const cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>& a,
    const cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>& b) noexcept((is_non_throwing_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value))
{
   *result.limbs() = detail::checked_multiply(*a.limbs(), *b.limbs(), typename cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>::checked_type());
   result.normalize();
}

//
// Special routines for multiplying two integers to obtain a multiprecision result:
//
template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<
    !is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value>::type
eval_multiply(
    cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>& result,
    signed_double_limb_type a, signed_double_limb_type b)
{
   constexpr const signed_double_limb_type mask = ~static_cast<limb_type>(0);
   constexpr const std::size_t limb_bits = sizeof(limb_type) * CHAR_BIT;

   bool s = false;
   if (a < 0)
   {
      a = -a;
      s = true;
   }
   if (b < 0)
   {
      b = -b;
      s = !s;
   }
   double_limb_type w = a & mask;
   double_limb_type x = a >> limb_bits;
   double_limb_type y = b & mask;
   double_limb_type z = b >> limb_bits;

   result.resize(4, 4);
   limb_type* pr = result.limbs();

   double_limb_type carry = w * y;
#ifdef __MSVC_RUNTIME_CHECKS
   pr[0] = static_cast<limb_type>(carry & ~static_cast<limb_type>(0));
   carry >>= limb_bits;
   carry += w * z + x * y;
   pr[1] = static_cast<limb_type>(carry & ~static_cast<limb_type>(0));
   carry >>= limb_bits;
   carry += x * z;
   pr[2] = static_cast<limb_type>(carry & ~static_cast<limb_type>(0));
   pr[3] = static_cast<limb_type>(carry >> limb_bits);
#else
   pr[0] = static_cast<limb_type>(carry);
   carry >>= limb_bits;
   carry += w * z + x * y;
   pr[1] = static_cast<limb_type>(carry);
   carry >>= limb_bits;
   carry += x * z;
   pr[2] = static_cast<limb_type>(carry);
   pr[3] = static_cast<limb_type>(carry >> limb_bits);
#endif
   result.sign(s);
   result.normalize();
}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<
    !is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value>::type
eval_multiply(
    cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>& result,
    double_limb_type a, double_limb_type b)
{
   constexpr const signed_double_limb_type mask = ~static_cast<limb_type>(0);
   constexpr const std::size_t limb_bits = sizeof(limb_type) * CHAR_BIT;

   double_limb_type w = a & mask;
   double_limb_type x = a >> limb_bits;
   double_limb_type y = b & mask;
   double_limb_type z = b >> limb_bits;

   result.resize(4, 4);
   limb_type* pr = result.limbs();

   double_limb_type carry = w * y;
#ifdef __MSVC_RUNTIME_CHECKS
   pr[0] = static_cast<limb_type>(carry & ~static_cast<limb_type>(0));
   carry >>= limb_bits;
   carry += w * z;
   pr[1] = static_cast<limb_type>(carry & ~static_cast<limb_type>(0));
   carry >>= limb_bits;
   pr[2] = static_cast<limb_type>(carry & ~static_cast<limb_type>(0));
   carry = x * y + pr[1];
   pr[1] = static_cast<limb_type>(carry & ~static_cast<limb_type>(0));
   carry >>= limb_bits;
   carry += pr[2] + x * z;
   pr[2] = static_cast<limb_type>(carry & ~static_cast<limb_type>(0));
   pr[3] = static_cast<limb_type>(carry >> limb_bits);
#else
   pr[0] = static_cast<limb_type>(carry);
   carry >>= limb_bits;
   carry += w * z;
   pr[1] = static_cast<limb_type>(carry);
   carry >>= limb_bits;
   pr[2] = static_cast<limb_type>(carry);
   carry = x * y + pr[1];
   pr[1] = static_cast<limb_type>(carry);
   carry >>= limb_bits;
   carry += pr[2] + x * z;
   pr[2] = static_cast<limb_type>(carry);
   pr[3] = static_cast<limb_type>(carry >> limb_bits);
#endif
   result.sign(false);
   result.normalize();
}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1,
          std::size_t MinBits2, std::size_t MaxBits2, cpp_integer_type SignType2, cpp_int_check_type Checked2, class Allocator2>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<
    !is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::value && is_trivial_cpp_int<cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2> >::value && is_trivial_cpp_int<cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2> >::value>::type
eval_multiply(
    cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>&       result,
    cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2> const& a,
    cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2> const& b)
{
   using canonical_type = typename boost::multiprecision::detail::canonical<typename cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2, Allocator2>::local_limb_type, cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1> >::type;
   eval_multiply(result, static_cast<canonical_type>(*a.limbs()), static_cast<canonical_type>(*b.limbs()));
   result.sign(a.sign() != b.sign());
}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1, class SI>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<boost::multiprecision::detail::is_signed<SI>::value && boost::multiprecision::detail::is_integral<SI>::value && (sizeof(SI) <= sizeof(signed_double_limb_type) / 2)>::type
eval_multiply(
    cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>& result,
    SI a, SI b)
{
   result = static_cast<signed_double_limb_type>(a) * static_cast<signed_double_limb_type>(b);
}

template <std::size_t MinBits1, std::size_t MaxBits1, cpp_integer_type SignType1, cpp_int_check_type Checked1, class Allocator1, class UI>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename std::enable_if<boost::multiprecision::detail::is_unsigned<UI>::value && (sizeof(UI) <= sizeof(signed_double_limb_type) / 2)>::type
eval_multiply(
    cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1, Allocator1>& result,
    UI a, UI b)
{
   result = static_cast<double_limb_type>(a) * static_cast<double_limb_type>(b);
}

#ifdef _MSC_VER
#pragma warning(pop)
#endif

}}} // namespace boost::multiprecision::backends

#endif


//=======================================================================
// Copyright 1997, 1998, 1999, 2000 University of Notre Dame.
// Authors: Andrew Lumsdaine, Lie-Quan Lee, Jeremy G. Siek
//
// This file is part of the Boost Graph Library
//
// You should have received a copy of the License Agreement for the
// Boost Graph Library along with the software; see the file LICENSE.
// If not, contact Office of Research, University of Notre Dame, Notre
// Dame, IN 46556.
//
// Permission to modify the code and to distribute modified code is
// granted, provided the text of this NOTICE is retained, a notice that
// the code was modified is included with the above COPYRIGHT NOTICE and
// with the COPYRIGHT NOTICE in the LICENSE file, and that the LICENSE
// file is distributed with the modified code.
//
// LICENSOR MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.
// By way of example, but not limitation, Licensor MAKES NO
// REPRESENTATIONS OR WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY
// PARTICULAR PURPOSE OR THAT THE USE OF THE LICENSED SOFTWARE COMPONENTS
// OR DOCUMENTATION WILL NOT INFRINGE ANY PATENTS, COPYRIGHTS, TRADEMARKS
// OR OTHER RIGHTS.
//=======================================================================

/*
  This file implements the function

  template <class VertexAndEdgeListGraph, class DistanceMatrix,
            class P, class T, class R>
  bool
  johnson_all_pairs_shortest_paths
    (VertexAndEdgeListGraph& g,
     DistanceMatrix& D,
     const bgl_named_params<P, T, R>& params)
 */

#ifndef BOOST_GRAPH_JOHNSON_HPP
#define BOOST_GRAPH_JOHNSON_HPP

#include <boost/graph/graph_traits.hpp>
#include <boost/property_map.hpp>
#include <boost/graph/bellman_ford_shortest_paths.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/pending/ct_if.hpp>
#include <boost/type_traits/same_traits.hpp>

namespace boost {

  template <class VertexAndEdgeListGraph, class DistanceMatrix,
            class VertexID, class Weight, class DistanceZero>
  bool
  johnson_all_pairs_shortest_paths(VertexAndEdgeListGraph& g1,
               DistanceMatrix& D,
               VertexID id1, Weight w1, DistanceZero zero)
  {
    typedef graph_traits<VertexAndEdgeListGraph> Traits1;
    typedef typename property_traits<Weight>::value_type DT;
    function_requires< BasicMatrixConcept<DistanceMatrix,
      typename Traits1::vertices_size_type, DT> >();

    typedef typename Traits1::directed_category DirCat;
    bool is_undirected = is_same<DirCat, undirected_tag>::value;

    typedef adjacency_list<vecS, vecS, directedS,
      property< vertex_distance_t, DT>,
      property< edge_weight_t, DT,
      property< edge_weight2_t, DT > > > Graph2;
    typedef graph_traits<Graph2> Traits2;

    Graph2 g2(num_vertices(g1) + 1);
    typename property_map<Graph2, edge_weight_t>::type
      w = get(edge_weight, g2);
    typename property_map<Graph2, edge_weight2_t>::type
      w_hat = get(edge_weight2, g2);
    typename property_map<Graph2, vertex_distance_t>::type
      d = get(vertex_distance, g2);
    typedef typename property_map<Graph2, vertex_index_t>::type VertexID2;
    VertexID2 id2 = get(vertex_index, g2);

    // Construct g2 where V[g2] = V[g1] U {s}
    //   and  E[g2] = E[g1] U {(s,v)| v in V[g1]}
    std::vector<typename Traits1::vertex_descriptor>
      verts1(num_vertices(g1) + 1);
    typename Traits2::vertex_descriptor s = *vertices(g2).first;
    {
      typename Traits1::vertex_iterator v, v_end;
      int i = 1;
      for (tie(v, v_end) = vertices(g1); v != v_end; ++v, ++i) {
        typename Traits2::edge_descriptor e; bool z;
        tie(e, z) = add_edge(s, id1[*v] + 1, g2);
        w[e] = zero;
        verts1[i] = *v;
      }
      typename Traits1::edge_iterator e, e_end;
      for (tie(e, e_end) = edges(g1); e != e_end; ++e) {
        typename Traits2::edge_descriptor e2; bool z;
        tie(e2, z) = add_edge(id1[source(*e, g1)] + 1,
                             id1[target(*e, g1)] + 1, g2);
        w[e2] = get(w1, *e);
        if (is_undirected) {
          tie(e2, z) = add_edge(id1[target(*e, g1)] + 1,
                                id1[source(*e, g1)] + 1, g2);
          w[e2] = get(w1, *e);
        }
      }
    }
    typename Traits2::vertex_iterator v, v_end, u, u_end;
    typename Traits2::edge_iterator e, e_end;
    std::vector<DT> h_vec(num_vertices(g2));
    typedef typename std::vector<DT>::iterator iter_t;
    iterator_property_map<iter_t,VertexID2,DT,DT&> h(h_vec.begin(), id2);

    DT inf = (std::numeric_limits<DT>::max)();
    for (tie(v, v_end) = vertices(g2); v != v_end; ++v)
      d[*v] = inf;

    put(d, s, zero);
    // Using the non-named parameter versions of bellman_ford and
    // dijkstra for portability reasons.
    dummy_property_map pred; closed_plus<DT> combine;
    std::less<DT> compare; bellman_visitor<> bvis;
    if (bellman_ford_shortest_paths
        (g2, num_vertices(g2), w, pred, d, combine, compare, bvis)) {
      for (tie(v, v_end) = vertices(g2); v != v_end; ++v)
        put(h, *v, get(d, *v));
      // Reweight the edges to remove negatives
      for (tie(e, e_end) = edges(g2); e != e_end; ++e) {
        typename Traits2::vertex_descriptor a = source(*e, g2),
          b = target(*e, g2);
        put(w_hat, *e, get(w, *e) + get(h, a) - get(h, b));
      }
      for (tie(u, u_end) = vertices(g2); u != u_end; ++u) {
        dijkstra_visitor<> dvis;
        dijkstra_shortest_paths
          (g2, *u, pred, d, w_hat, id2, compare, combine, inf, zero,dvis);
        for (tie(v, v_end) = vertices(g2); v != v_end; ++v) {
          if (*u != s && *v != s) {
            typename Traits1::vertex_descriptor u1, v1;
            u1 = verts1[id2[*u]]; v1 = verts1[id2[*v]];
            D[id2[*u]-1][id2[*v]-1] = get(d, *v) + get(h, *v) - get(h, *u);
          }
        }
      }
      return true;
    } else
      return false;
  }

  namespace detail {

    template <class VertexAndEdgeListGraph, class DistanceMatrix,
              class P, class T, class R, class Weight,
              class VertexID>
    bool
    johnson_dispatch(VertexAndEdgeListGraph& g,
                     DistanceMatrix& D,
                     const bgl_named_params<P, T, R>& params,
                     Weight w, VertexID id)
    {
      typedef typename property_traits<Weight>::value_type WT;

      return johnson_all_pairs_shortest_paths
        (g, D, id, w,
         choose_param(get_param(params, distance_zero_t()), WT()) );
    }

  } // namespace detail

  template <class VertexAndEdgeListGraph, class DistanceMatrix,
            class P, class T, class R>
  bool
  johnson_all_pairs_shortest_paths
    (VertexAndEdgeListGraph& g,
     DistanceMatrix& D,
     const bgl_named_params<P, T, R>& params)
  {
    return detail::johnson_dispatch
      (g, D, params,
       choose_const_pmap(get_param(params, edge_weight), g, edge_weight),
       choose_const_pmap(get_param(params, vertex_index), g, vertex_index)
       );
  }

  template <class VertexAndEdgeListGraph, class DistanceMatrix>
  bool
  johnson_all_pairs_shortest_paths
    (VertexAndEdgeListGraph& g, DistanceMatrix& D)
  {
    bgl_named_params<int,int> params(1);
    return detail::johnson_dispatch
      (g, D, params, get(edge_weight, g), get(vertex_index, g));
  }

} // namespace boost

#endif // BOOST_GRAPH_JOHNSON_HPP


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

#include <stdlib.h>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <Eigen/StdVector>

namespace maps { namespace tools {

class SurfaceIntersection
{
public:

/**
 * Computes the intersection points between a plane and an axis aligned box.
 * Note: This method doesn't check if the plane intersects with the box.
 * Note: The intersection points are appended to the vector, i.e., the caller must make sure it is empty.
 */
template<class Scalar, int MatrixOptions, class Allocator>
static void computeIntersections(const Eigen::Hyperplane<Scalar, 3>& plane, const Eigen::AlignedBox<Scalar, 3>& box,
        std::vector< Eigen::Matrix<Scalar, 3, 1, MatrixOptions>, Allocator >& intersections)
{
    typedef Eigen::Matrix<Scalar, 3, 1, MatrixOptions> Vec;
    Vec normal = plane.normal();
    Vec box_center = box.center();
    Vec extents = box.max() - box_center;
    const Scalar dist = -plane.signedDistance(box_center);

    // find the max coefficient of the normal:
    int i=0, j=1, k=2;
    if(std::abs(normal[i]) < std::abs(normal[j])) std::swap(i,j);
    if(std::abs(normal[i]) < std::abs(normal[k])) std::swap(i,k);

    const Scalar dotj = extents[j] * normal[j];
    const Scalar dotk = extents[k] * normal[k];

    Vec prev_p;
    enum { NONE, LOW, BOX, HIGH } prev_pos = NONE, pos;
    // calculate intersections in direction i:
    for(int n=0; n<5; ++n)
    {
        Vec p(0,0,0);
        Scalar dotp = Scalar(0.0);
        if((n+1)&2)
            dotp += dotj, p[j] = extents[j];
        else
            dotp -= dotj, p[j] = -extents[j];
        if(n&2)
            dotp += dotk, p[k] = extents[k];
        else
            dotp -= dotk, p[k] = -extents[k];

        p[i] = (dist - dotp) / normal[i];

        if( p[i] < -extents[i])
            pos = LOW;
        else if( p[i] > extents[i])
            pos = HIGH;
        else
            pos = BOX;

        if( (prev_pos == LOW || prev_pos == HIGH) && pos != prev_pos )
        {
            // clipping in
            const Scalar h = prev_pos == LOW ? -extents[i] : extents[i];
            const Scalar s = (h - prev_p[i]) / (p[i] - prev_p[i]);
            intersections.push_back(box_center + prev_p + (p - prev_p) * s);
        }
        if( pos == BOX )
        {
            if( n==4 ) break; // n==4 is only for clipping in
            intersections.push_back(box_center + p);
        }
        else if( pos != prev_pos && prev_pos != NONE )
        {
            // clipping out
            const Scalar h = pos == LOW ? -extents[i] : extents[i];
            const Scalar s = (h - prev_p[i]) / (p[i] - prev_p[i]);
            intersections.push_back(box_center + prev_p + (p - prev_p) * s);
        }

        prev_pos = pos;
        prev_p = p;
    }
}
};

}}


// 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_SMAT_SMAT_MULT_INCLUDE
#define MTL_SMAT_SMAT_MULT_INCLUDE

#include <boost/numeric/mtl/mtl_fwd.hpp>
#include <boost/numeric/mtl/matrix/parameter.hpp>
#include <boost/numeric/mtl/utility/tag.hpp>
#include <boost/numeric/mtl/utility/transposed_matrix_type.hpp>
#include <boost/numeric/mtl/operation/mult_assign_mode.hpp>
#include <boost/numeric/mtl/interface/vpt.hpp>

namespace mtl {

template <typename MatrixA, typename MatrixB, typename MatrixC, typename Assign>
inline void smat_smat_mult(const MatrixA& A, const MatrixB& B, MatrixC& C, Assign,
			   tag::row_major,  // orientation A
			   tag::row_major)  // orientation B
{
    if (Assign::init_to_zero) set_to_zero(C);

    // Average numbers of non-zeros per row
    double ava= num_cols(A) ? double(A.nnz()) / num_cols(A) : 0,
	   avb= num_rows(B) ? double(B.nnz()) / num_rows(B) : 0;

    // Define Updater type corresponding to assign mode
    typedef typename Collection<MatrixC>::value_type                            C_value_type;
    typedef typename operations::update_assign_mode<Assign, C_value_type>::type Updater;

    // Reserve 20% over the average's product for entries in C
    mat::inserter<MatrixC, Updater>     ins(C, int( ava * avb * 1.4 ));

    typename traits::row<MatrixA>::type             row_A(A);
    typename traits::col<MatrixA>::type             col_A(A);
    typename traits::const_value<MatrixA>::type     value_A(A);

    typename traits::col<MatrixB>::type             col_B(B);
    typename traits::const_value<MatrixB>::type     value_B(B);

    typedef typename traits::range_generator<tag::row, MatrixA>::type  cursor_type;
    cursor_type cursor = begin<tag::row>(A), cend = end<tag::row>(A);
    for (unsigned ra= 0; cursor != cend; ++ra, ++cursor) {
	// Iterate over non-zeros of each row of A
	typedef typename traits::range_generator<tag::nz, cursor_type>::type icursor_type;
	for (icursor_type icursor = begin<tag::nz>(cursor), icend = end<tag::nz>(cursor); icursor != icend; ++icursor) {
	    typename Collection<MatrixA>::size_type     ca= col_A(*icursor);   // column of non-zero
	    typename Collection<MatrixA>::value_type    va= value_A(*icursor); // value of non-zero

	    // Get cursor corresponding to row 'ca' in matrix B
	    typedef typename traits::range_generator<tag::row, MatrixB>::type  B_cursor_type;
	    B_cursor_type B_cursor = begin<tag::row>(B);
		B_cursor += int(ca); // not elegant but prevents warning

	    // Iterate over non-zeros of this row
	    typedef typename traits::range_generator<tag::nz, B_cursor_type>::type ib_cursor_type;
	    for (ib_cursor_type ib_cursor = begin<tag::nz>(B_cursor), ib_cend = end<tag::nz>(B_cursor);
		 ib_cursor != ib_cend; ++ib_cursor) {
		typename Collection<MatrixB>::size_type     cb= col_B(*ib_cursor);   // column of non-zero
		typename Collection<MatrixB>::value_type    vb= value_B(*ib_cursor); // value of non-zero
		ins(ra, cb) << va * vb;
	    }
	}
    }
}

template <typename MatrixA, typename MatrixB, typename MatrixC, typename Assign>
inline void smat_smat_mult(const MatrixA& A, const MatrixB& B, MatrixC& C, Assign,
			   tag::col_major,  // orientation A
			   tag::col_major)  // orientation B
{
    if (Assign::init_to_zero) set_to_zero(C);

    // Average numbers of non-zeros per column
    double ava= double(A.nnz()) / num_cols(A), avb= double(B.nnz()) / num_cols(B);

    // Define Updater type corresponding to assign mode
    typedef typename Collection<MatrixC>::value_type                            C_value_type;
    typedef typename operations::update_assign_mode<Assign, C_value_type>::type Updater;

    // Reserve 20% over the average's product for entries in C
    mat::inserter<MatrixC, Updater>     ins(C, int( ava * avb * 1.2 ));

    typename traits::row<MatrixA>::type             row_A(A);
    typename traits::col<MatrixA>::type             col_A(A);
    typename traits::const_value<MatrixA>::type     value_A(A);

    typename traits::row<MatrixB>::type             row_B(B);
    typename traits::col<MatrixB>::type             col_B(B);
    typename traits::const_value<MatrixB>::type     value_B(B);

    typedef typename traits::range_generator<tag::col, MatrixB>::type  cursor_type;
    cursor_type cursor = begin<tag::col>(B), cend = end<tag::col>(B);
    for (unsigned cb= 0; cursor != cend; ++cb, ++cursor) {
	// Iterate over non-zeros of each column of B
	typedef typename traits::range_generator<tag::nz, cursor_type>::type icursor_type;
	for (icursor_type icursor = begin<tag::nz>(cursor), icend = end<tag::nz>(cursor); icursor != icend; ++icursor) {
	    typename Collection<MatrixB>::size_type     rb= row_B(*icursor);   // row of non-zero
	    typename Collection<MatrixB>::value_type    vb= value_B(*icursor); // value of non-zero

	    // Get cursor corresponding to column 'rb' in matrix A
	    typedef typename traits::range_generator<tag::col, MatrixA>::type  A_cursor_type;
	    A_cursor_type A_cursor = begin<tag::col>(A);
	    A_cursor+= rb;

	    // Iterate over non-zeros of this column
	    typedef typename traits::range_generator<tag::nz, A_cursor_type>::type ia_cursor_type;
	    for (ia_cursor_type ia_cursor = begin<tag::nz>(A_cursor), ia_cend = end<tag::nz>(A_cursor);
		 ia_cursor != ia_cend; ++ia_cursor) {
		typename Collection<MatrixA>::size_type     ra= row_A(*ia_cursor);   // row of non-zero
		typename Collection<MatrixA>::value_type    va= value_A(*ia_cursor); // value of non-zero
		ins(ra, cb) << va * vb;
	    }
	}
    }
}

template <typename MatrixA, typename MatrixB, typename MatrixC, typename Assign>
inline void smat_smat_mult(const MatrixA& A, const MatrixB& B, MatrixC& C, Assign,
			   tag::col_major,  // orientation A
			   tag::row_major)  // orientation B
{
    if (Assign::init_to_zero) set_to_zero(C);

    // Average numbers of non-zeros per row
    double ava= double(A.nnz()) / num_rows(A), avb= double(B.nnz()) / num_rows(B);

    // Define Updater type corresponding to assign mode
    typedef typename Collection<MatrixC>::value_type                            C_value_type;
    typedef typename operations::update_assign_mode<Assign, C_value_type>::type Updater;

    // Reserve 20% over the average's product for entries in C
    mat::inserter<MatrixC, Updater>     ins(C, int( ava * avb * 1.2 ));

    typename traits::row<MatrixA>::type             row_A(A);
    typename traits::col<MatrixA>::type             col_A(A);
    typename traits::const_value<MatrixA>::type     value_A(A);

    typename traits::row<MatrixB>::type             row_B(B);
    typename traits::col<MatrixB>::type             col_B(B);
    typename traits::const_value<MatrixB>::type     value_B(B);

    typedef typename traits::range_generator<tag::col, MatrixA>::type  A_cursor_type;
    A_cursor_type A_cursor = begin<tag::col>(A), A_cend = end<tag::col>(A);

    typedef typename traits::range_generator<tag::row, MatrixB>::type  B_cursor_type;
    B_cursor_type B_cursor = begin<tag::row>(B);

    for (unsigned ca= 0; A_cursor != A_cend; ++ca, ++A_cursor, ++B_cursor) {

	// Iterate over non-zeros of A's column
	typedef typename traits::range_generator<tag::nz, A_cursor_type>::type ia_cursor_type;
	for (ia_cursor_type ia_cursor = begin<tag::nz>(A_cursor), ia_cend = end<tag::nz>(A_cursor);
	     ia_cursor != ia_cend; ++ia_cursor)
        {
	    typename Collection<MatrixA>::size_type     ra= row_A(*ia_cursor);   // row of non-zero
	    typename Collection<MatrixA>::value_type    va= value_A(*ia_cursor); // value of non-zero

	    // Iterate over non-zeros of B's row
	    typedef typename traits::range_generator<tag::nz, B_cursor_type>::type ib_cursor_type;
	    for (ib_cursor_type ib_cursor = begin<tag::nz>(B_cursor), ib_cend = end<tag::nz>(B_cursor);
		 ib_cursor != ib_cend; ++ib_cursor)
            {
		typename Collection<MatrixB>::size_type     cb= col_B(*ib_cursor);   // column of non-zero
		typename Collection<MatrixB>::value_type    vb= value_B(*ib_cursor); // value of non-zero
		ins(ra, cb) << va * vb;
	    }
	}
    }
}

template <typename MatrixA, typename MatrixB, typename MatrixC, typename Assign>
inline void smat_smat_mult(const MatrixA& A, const MatrixB& B, MatrixC& C, Assign,
			   tag::row_major,  // orientation A
			   tag::col_major)  // orientation B
{
	vampir_trace<4020> tracer;
    // Copy B into a sparse row-major matrix
    typename mtl::traits::transposed_sparse_matrix_type<MatrixB>::type B_copy(num_rows(B), num_cols(B));
    B_copy= B;
    smat_smat_mult(A, B_copy, C, Assign(), tag::row_major(), tag::row_major());
}

} // namespace mtl

#endif // MTL_SMAT_SMAT_MULT_INCLUDE