// Copyright (c) 2008-2017 Emil Dotchevski and Reverge Studios, Inc.

// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_QVM_2923BE84E16CD6AE529049F1F1BBE9EB
#define BOOST_QVM_2923BE84E16CD6AE529049F1F1BBE9EB

// This file was generated by a program. Do not edit manually.

#include <boost/qvm/assert.hpp>
#include <boost/qvm/deduce_mat.hpp>
#include <boost/qvm/deduce_vec.hpp>
#include <boost/qvm/error.hpp>
#include <boost/qvm/gen/mat_assign2.hpp>
#include <boost/qvm/throw_exception.hpp>

namespace boost {
namespace qvm {
template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 2 && mat_traits<B>::rows == 2 &&
        mat_traits<A>::cols == 2 && mat_traits<B>::cols == 2,
    deduce_mat2<A, B, 2, 2>>::type
operator+(A const &a, B const &b) {
  typedef typename deduce_mat2<A, B, 2, 2>::type R;
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::rows == 2);
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::cols == 2);
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      mat_traits<A>::template read_element<0, 0>(a) +
      mat_traits<B>::template read_element<0, 0>(b);
  mat_traits<R>::template write_element<0, 1>(r) =
      mat_traits<A>::template read_element<0, 1>(a) +
      mat_traits<B>::template read_element<0, 1>(b);
  mat_traits<R>::template write_element<1, 0>(r) =
      mat_traits<A>::template read_element<1, 0>(a) +
      mat_traits<B>::template read_element<1, 0>(b);
  mat_traits<R>::template write_element<1, 1>(r) =
      mat_traits<A>::template read_element<1, 1>(a) +
      mat_traits<B>::template read_element<1, 1>(b);
  return r;
}

namespace sfinae {
using ::boost::qvm::operator+;
}

namespace qvm_detail {
template <int R, int C> struct plus_mm_defined;

template <> struct plus_mm_defined<2, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 2 && mat_traits<B>::rows == 2 &&
        mat_traits<A>::cols == 1 && mat_traits<B>::cols == 1,
    deduce_mat2<A, B, 2, 1>>::type
operator+(A const &a, B const &b) {
  typedef typename deduce_mat2<A, B, 2, 1>::type R;
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::rows == 2);
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::cols == 1);
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      mat_traits<A>::template read_element<0, 0>(a) +
      mat_traits<B>::template read_element<0, 0>(b);
  mat_traits<R>::template write_element<1, 0>(r) =
      mat_traits<A>::template read_element<1, 0>(a) +
      mat_traits<B>::template read_element<1, 0>(b);
  return r;
}

namespace sfinae {
using ::boost::qvm::operator+;
}

namespace qvm_detail {
template <int R, int C> struct plus_mm_defined;

template <> struct plus_mm_defined<2, 1> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 1 && mat_traits<B>::rows == 1 &&
        mat_traits<A>::cols == 2 && mat_traits<B>::cols == 2,
    deduce_mat2<A, B, 1, 2>>::type
operator+(A const &a, B const &b) {
  typedef typename deduce_mat2<A, B, 1, 2>::type R;
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::rows == 1);
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::cols == 2);
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      mat_traits<A>::template read_element<0, 0>(a) +
      mat_traits<B>::template read_element<0, 0>(b);
  mat_traits<R>::template write_element<0, 1>(r) =
      mat_traits<A>::template read_element<0, 1>(a) +
      mat_traits<B>::template read_element<0, 1>(b);
  return r;
}

namespace sfinae {
using ::boost::qvm::operator+;
}

namespace qvm_detail {
template <int R, int C> struct plus_mm_defined;

template <> struct plus_mm_defined<1, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 2 && mat_traits<B>::rows == 2 &&
        mat_traits<A>::cols == 2 && mat_traits<B>::cols == 2,
    deduce_mat2<A, B, 2, 2>>::type
operator-(A const &a, B const &b) {
  typedef typename deduce_mat2<A, B, 2, 2>::type R;
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::rows == 2);
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::cols == 2);
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      mat_traits<A>::template read_element<0, 0>(a) -
      mat_traits<B>::template read_element<0, 0>(b);
  mat_traits<R>::template write_element<0, 1>(r) =
      mat_traits<A>::template read_element<0, 1>(a) -
      mat_traits<B>::template read_element<0, 1>(b);
  mat_traits<R>::template write_element<1, 0>(r) =
      mat_traits<A>::template read_element<1, 0>(a) -
      mat_traits<B>::template read_element<1, 0>(b);
  mat_traits<R>::template write_element<1, 1>(r) =
      mat_traits<A>::template read_element<1, 1>(a) -
      mat_traits<B>::template read_element<1, 1>(b);
  return r;
}

namespace sfinae {
using ::boost::qvm::operator-;
}

namespace qvm_detail {
template <int R, int C> struct minus_mm_defined;

template <> struct minus_mm_defined<2, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 2 && mat_traits<B>::rows == 2 &&
        mat_traits<A>::cols == 1 && mat_traits<B>::cols == 1,
    deduce_mat2<A, B, 2, 1>>::type
operator-(A const &a, B const &b) {
  typedef typename deduce_mat2<A, B, 2, 1>::type R;
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::rows == 2);
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::cols == 1);
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      mat_traits<A>::template read_element<0, 0>(a) -
      mat_traits<B>::template read_element<0, 0>(b);
  mat_traits<R>::template write_element<1, 0>(r) =
      mat_traits<A>::template read_element<1, 0>(a) -
      mat_traits<B>::template read_element<1, 0>(b);
  return r;
}

namespace sfinae {
using ::boost::qvm::operator-;
}

namespace qvm_detail {
template <int R, int C> struct minus_mm_defined;

template <> struct minus_mm_defined<2, 1> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 1 && mat_traits<B>::rows == 1 &&
        mat_traits<A>::cols == 2 && mat_traits<B>::cols == 2,
    deduce_mat2<A, B, 1, 2>>::type
operator-(A const &a, B const &b) {
  typedef typename deduce_mat2<A, B, 1, 2>::type R;
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::rows == 1);
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::cols == 2);
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      mat_traits<A>::template read_element<0, 0>(a) -
      mat_traits<B>::template read_element<0, 0>(b);
  mat_traits<R>::template write_element<0, 1>(r) =
      mat_traits<A>::template read_element<0, 1>(a) -
      mat_traits<B>::template read_element<0, 1>(b);
  return r;
}

namespace sfinae {
using ::boost::qvm::operator-;
}

namespace qvm_detail {
template <int R, int C> struct minus_mm_defined;

template <> struct minus_mm_defined<1, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 2 && mat_traits<B>::rows == 2 &&
                             mat_traits<A>::cols == 2 &&
                             mat_traits<B>::cols == 2,
                         A &>::type
    operator+=(A &a, B const &b) {
  mat_traits<A>::template write_element<0, 0>(a) +=
      mat_traits<B>::template read_element<0, 0>(b);
  mat_traits<A>::template write_element<0, 1>(a) +=
      mat_traits<B>::template read_element<0, 1>(b);
  mat_traits<A>::template write_element<1, 0>(a) +=
      mat_traits<B>::template read_element<1, 0>(b);
  mat_traits<A>::template write_element<1, 1>(a) +=
      mat_traits<B>::template read_element<1, 1>(b);
  return a;
}

namespace sfinae {
using ::boost::qvm::operator+=;
}

namespace qvm_detail {
template <int R, int C> struct plus_eq_mm_defined;

template <> struct plus_eq_mm_defined<2, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 2 && mat_traits<B>::rows == 2 &&
                             mat_traits<A>::cols == 1 &&
                             mat_traits<B>::cols == 1,
                         A &>::type
    operator+=(A &a, B const &b) {
  mat_traits<A>::template write_element<0, 0>(a) +=
      mat_traits<B>::template read_element<0, 0>(b);
  mat_traits<A>::template write_element<1, 0>(a) +=
      mat_traits<B>::template read_element<1, 0>(b);
  return a;
}

namespace sfinae {
using ::boost::qvm::operator+=;
}

namespace qvm_detail {
template <int R, int C> struct plus_eq_mm_defined;

template <> struct plus_eq_mm_defined<2, 1> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 1 && mat_traits<B>::rows == 1 &&
                             mat_traits<A>::cols == 2 &&
                             mat_traits<B>::cols == 2,
                         A &>::type
    operator+=(A &a, B const &b) {
  mat_traits<A>::template write_element<0, 0>(a) +=
      mat_traits<B>::template read_element<0, 0>(b);
  mat_traits<A>::template write_element<0, 1>(a) +=
      mat_traits<B>::template read_element<0, 1>(b);
  return a;
}

namespace sfinae {
using ::boost::qvm::operator+=;
}

namespace qvm_detail {
template <int R, int C> struct plus_eq_mm_defined;

template <> struct plus_eq_mm_defined<1, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 2 && mat_traits<B>::rows == 2 &&
                             mat_traits<A>::cols == 2 &&
                             mat_traits<B>::cols == 2,
                         A &>::type
    operator-=(A &a, B const &b) {
  mat_traits<A>::template write_element<0, 0>(a) -=
      mat_traits<B>::template read_element<0, 0>(b);
  mat_traits<A>::template write_element<0, 1>(a) -=
      mat_traits<B>::template read_element<0, 1>(b);
  mat_traits<A>::template write_element<1, 0>(a) -=
      mat_traits<B>::template read_element<1, 0>(b);
  mat_traits<A>::template write_element<1, 1>(a) -=
      mat_traits<B>::template read_element<1, 1>(b);
  return a;
}

namespace sfinae {
using ::boost::qvm::operator-=;
}

namespace qvm_detail {
template <int R, int C> struct minus_eq_mm_defined;

template <> struct minus_eq_mm_defined<2, 2> {
  static bool const value = true;
};
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 2 && mat_traits<B>::rows == 2 &&
                             mat_traits<A>::cols == 1 &&
                             mat_traits<B>::cols == 1,
                         A &>::type
    operator-=(A &a, B const &b) {
  mat_traits<A>::template write_element<0, 0>(a) -=
      mat_traits<B>::template read_element<0, 0>(b);
  mat_traits<A>::template write_element<1, 0>(a) -=
      mat_traits<B>::template read_element<1, 0>(b);
  return a;
}

namespace sfinae {
using ::boost::qvm::operator-=;
}

namespace qvm_detail {
template <int R, int C> struct minus_eq_mm_defined;

template <> struct minus_eq_mm_defined<2, 1> {
  static bool const value = true;
};
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 1 && mat_traits<B>::rows == 1 &&
                             mat_traits<A>::cols == 2 &&
                             mat_traits<B>::cols == 2,
                         A &>::type
    operator-=(A &a, B const &b) {
  mat_traits<A>::template write_element<0, 0>(a) -=
      mat_traits<B>::template read_element<0, 0>(b);
  mat_traits<A>::template write_element<0, 1>(a) -=
      mat_traits<B>::template read_element<0, 1>(b);
  return a;
}

namespace sfinae {
using ::boost::qvm::operator-=;
}

namespace qvm_detail {
template <int R, int C> struct minus_eq_mm_defined;

template <> struct minus_eq_mm_defined<1, 2> {
  static bool const value = true;
};
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 2 && mat_traits<A>::cols == 2 && is_scalar<B>::value,
    deduce_mat<A>>::type
operator*(A const &a, B b) {
  typedef typename deduce_mat<A>::type R;
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      mat_traits<A>::template read_element<0, 0>(a) * b;
  mat_traits<R>::template write_element<0, 1>(r) =
      mat_traits<A>::template read_element<0, 1>(a) * b;
  mat_traits<R>::template write_element<1, 0>(r) =
      mat_traits<A>::template read_element<1, 0>(a) * b;
  mat_traits<R>::template write_element<1, 1>(r) =
      mat_traits<A>::template read_element<1, 1>(a) * b;
  return r;
}

namespace sfinae {
using ::boost::qvm::operator*;
}

namespace qvm_detail {
template <int R, int C> struct mul_ms_defined;

template <> struct mul_ms_defined<2, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename lazy_enable_if_c<is_scalar<A>::value && mat_traits<B>::rows == 2 &&
                                  mat_traits<B>::cols == 2,
                              deduce_mat<B>>::type
    operator*(A a, B const &b) {
  typedef typename deduce_mat<B>::type R;
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      a * mat_traits<B>::template read_element<0, 0>(b);
  mat_traits<R>::template write_element<0, 1>(r) =
      a * mat_traits<B>::template read_element<0, 1>(b);
  mat_traits<R>::template write_element<1, 0>(r) =
      a * mat_traits<B>::template read_element<1, 0>(b);
  mat_traits<R>::template write_element<1, 1>(r) =
      a * mat_traits<B>::template read_element<1, 1>(b);
  return r;
}

namespace sfinae {
using ::boost::qvm::operator*;
}

namespace qvm_detail {
template <int R, int C> struct mul_sm_defined;

template <> struct mul_sm_defined<2, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 2 && mat_traits<A>::cols == 1 && is_scalar<B>::value,
    deduce_mat<A>>::type
operator*(A const &a, B b) {
  typedef typename deduce_mat<A>::type R;
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      mat_traits<A>::template read_element<0, 0>(a) * b;
  mat_traits<R>::template write_element<1, 0>(r) =
      mat_traits<A>::template read_element<1, 0>(a) * b;
  return r;
}

namespace sfinae {
using ::boost::qvm::operator*;
}

namespace qvm_detail {
template <int R, int C> struct mul_ms_defined;

template <> struct mul_ms_defined<2, 1> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename lazy_enable_if_c<is_scalar<A>::value && mat_traits<B>::rows == 2 &&
                                  mat_traits<B>::cols == 1,
                              deduce_mat<B>>::type
    operator*(A a, B const &b) {
  typedef typename deduce_mat<B>::type R;
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      a * mat_traits<B>::template read_element<0, 0>(b);
  mat_traits<R>::template write_element<1, 0>(r) =
      a * mat_traits<B>::template read_element<1, 0>(b);
  return r;
}

namespace sfinae {
using ::boost::qvm::operator*;
}

namespace qvm_detail {
template <int R, int C> struct mul_sm_defined;

template <> struct mul_sm_defined<2, 1> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 1 && mat_traits<A>::cols == 2 && is_scalar<B>::value,
    deduce_mat<A>>::type
operator*(A const &a, B b) {
  typedef typename deduce_mat<A>::type R;
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      mat_traits<A>::template read_element<0, 0>(a) * b;
  mat_traits<R>::template write_element<0, 1>(r) =
      mat_traits<A>::template read_element<0, 1>(a) * b;
  return r;
}

namespace sfinae {
using ::boost::qvm::operator*;
}

namespace qvm_detail {
template <int R, int C> struct mul_ms_defined;

template <> struct mul_ms_defined<1, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename lazy_enable_if_c<is_scalar<A>::value && mat_traits<B>::rows == 1 &&
                                  mat_traits<B>::cols == 2,
                              deduce_mat<B>>::type
    operator*(A a, B const &b) {
  typedef typename deduce_mat<B>::type R;
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      a * mat_traits<B>::template read_element<0, 0>(b);
  mat_traits<R>::template write_element<0, 1>(r) =
      a * mat_traits<B>::template read_element<0, 1>(b);
  return r;
}

namespace sfinae {
using ::boost::qvm::operator*;
}

namespace qvm_detail {
template <int R, int C> struct mul_sm_defined;

template <> struct mul_sm_defined<1, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 2 && mat_traits<A>::cols == 2 &&
                             is_scalar<B>::value,
                         A &>::type
    operator*=(A &a, B b) {
  mat_traits<A>::template write_element<0, 0>(a) *= b;
  mat_traits<A>::template write_element<0, 1>(a) *= b;
  mat_traits<A>::template write_element<1, 0>(a) *= b;
  mat_traits<A>::template write_element<1, 1>(a) *= b;
  return a;
}

namespace sfinae {
using ::boost::qvm::operator*=;
}

namespace qvm_detail {
template <int R, int C> struct mul_eq_ms_defined;

template <> struct mul_eq_ms_defined<2, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 2 && mat_traits<A>::cols == 1 &&
                             is_scalar<B>::value,
                         A &>::type
    operator*=(A &a, B b) {
  mat_traits<A>::template write_element<0, 0>(a) *= b;
  mat_traits<A>::template write_element<1, 0>(a) *= b;
  return a;
}

namespace sfinae {
using ::boost::qvm::operator*=;
}

namespace qvm_detail {
template <int R, int C> struct mul_eq_ms_defined;

template <> struct mul_eq_ms_defined<2, 1> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 1 && mat_traits<A>::cols == 2 &&
                             is_scalar<B>::value,
                         A &>::type
    operator*=(A &a, B b) {
  mat_traits<A>::template write_element<0, 0>(a) *= b;
  mat_traits<A>::template write_element<0, 1>(a) *= b;
  return a;
}

namespace sfinae {
using ::boost::qvm::operator*=;
}

namespace qvm_detail {
template <int R, int C> struct mul_eq_ms_defined;

template <> struct mul_eq_ms_defined<1, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 2 && mat_traits<A>::cols == 2 && is_scalar<B>::value,
    deduce_mat<A>>::type
operator/(A const &a, B b) {
  typedef typename deduce_mat<A>::type R;
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      mat_traits<A>::template read_element<0, 0>(a) / b;
  mat_traits<R>::template write_element<0, 1>(r) =
      mat_traits<A>::template read_element<0, 1>(a) / b;
  mat_traits<R>::template write_element<1, 0>(r) =
      mat_traits<A>::template read_element<1, 0>(a) / b;
  mat_traits<R>::template write_element<1, 1>(r) =
      mat_traits<A>::template read_element<1, 1>(a) / b;
  return r;
}

namespace sfinae {
using ::boost::qvm::operator/;
}

namespace qvm_detail {
template <int R, int C> struct div_ms_defined;

template <> struct div_ms_defined<2, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename lazy_enable_if_c<is_scalar<A>::value && mat_traits<B>::rows == 2 &&
                                  mat_traits<B>::cols == 2,
                              deduce_mat<B>>::type
    operator/(A a, B const &b) {
  typedef typename deduce_mat<B>::type R;
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      a / mat_traits<B>::template read_element<0, 0>(b);
  mat_traits<R>::template write_element<0, 1>(r) =
      a / mat_traits<B>::template read_element<0, 1>(b);
  mat_traits<R>::template write_element<1, 0>(r) =
      a / mat_traits<B>::template read_element<1, 0>(b);
  mat_traits<R>::template write_element<1, 1>(r) =
      a / mat_traits<B>::template read_element<1, 1>(b);
  return r;
}

namespace sfinae {
using ::boost::qvm::operator/;
}

namespace qvm_detail {
template <int R, int C> struct div_sm_defined;

template <> struct div_sm_defined<2, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 2 && mat_traits<A>::cols == 1 && is_scalar<B>::value,
    deduce_mat<A>>::type
operator/(A const &a, B b) {
  typedef typename deduce_mat<A>::type R;
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      mat_traits<A>::template read_element<0, 0>(a) / b;
  mat_traits<R>::template write_element<1, 0>(r) =
      mat_traits<A>::template read_element<1, 0>(a) / b;
  return r;
}

namespace sfinae {
using ::boost::qvm::operator/;
}

namespace qvm_detail {
template <int R, int C> struct div_ms_defined;

template <> struct div_ms_defined<2, 1> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename lazy_enable_if_c<is_scalar<A>::value && mat_traits<B>::rows == 2 &&
                                  mat_traits<B>::cols == 1,
                              deduce_mat<B>>::type
    operator/(A a, B const &b) {
  typedef typename deduce_mat<B>::type R;
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      a / mat_traits<B>::template read_element<0, 0>(b);
  mat_traits<R>::template write_element<1, 0>(r) =
      a / mat_traits<B>::template read_element<1, 0>(b);
  return r;
}

namespace sfinae {
using ::boost::qvm::operator/;
}

namespace qvm_detail {
template <int R, int C> struct div_sm_defined;

template <> struct div_sm_defined<2, 1> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 1 && mat_traits<A>::cols == 2 && is_scalar<B>::value,
    deduce_mat<A>>::type
operator/(A const &a, B b) {
  typedef typename deduce_mat<A>::type R;
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      mat_traits<A>::template read_element<0, 0>(a) / b;
  mat_traits<R>::template write_element<0, 1>(r) =
      mat_traits<A>::template read_element<0, 1>(a) / b;
  return r;
}

namespace sfinae {
using ::boost::qvm::operator/;
}

namespace qvm_detail {
template <int R, int C> struct div_ms_defined;

template <> struct div_ms_defined<1, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 2 && mat_traits<A>::cols == 2 &&
                             is_scalar<B>::value,
                         A &>::type
    operator/=(A &a, B b) {
  mat_traits<A>::template write_element<0, 0>(a) /= b;
  mat_traits<A>::template write_element<0, 1>(a) /= b;
  mat_traits<A>::template write_element<1, 0>(a) /= b;
  mat_traits<A>::template write_element<1, 1>(a) /= b;
  return a;
}

namespace sfinae {
using ::boost::qvm::operator/=;
}

namespace qvm_detail {
template <int R, int C> struct div_eq_ms_defined;

template <> struct div_eq_ms_defined<2, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 2 && mat_traits<A>::cols == 1 &&
                             is_scalar<B>::value,
                         A &>::type
    operator/=(A &a, B b) {
  mat_traits<A>::template write_element<0, 0>(a) /= b;
  mat_traits<A>::template write_element<1, 0>(a) /= b;
  return a;
}

namespace sfinae {
using ::boost::qvm::operator/=;
}

namespace qvm_detail {
template <int R, int C> struct div_eq_ms_defined;

template <> struct div_eq_ms_defined<2, 1> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 1 && mat_traits<A>::cols == 2 &&
                             is_scalar<B>::value,
                         A &>::type
    operator/=(A &a, B b) {
  mat_traits<A>::template write_element<0, 0>(a) /= b;
  mat_traits<A>::template write_element<0, 1>(a) /= b;
  return a;
}

namespace sfinae {
using ::boost::qvm::operator/=;
}

namespace qvm_detail {
template <int R, int C> struct div_eq_ms_defined;

template <> struct div_eq_ms_defined<1, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class R, class A>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<R>::rows == 2 && mat_traits<A>::rows == 2 &&
                             mat_traits<R>::cols == 2 &&
                             mat_traits<A>::cols == 2,
                         R>::type
    convert_to(A const &a) {
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      mat_traits<A>::template read_element<0, 0>(a);
  mat_traits<R>::template write_element<0, 1>(r) =
      mat_traits<A>::template read_element<0, 1>(a);
  mat_traits<R>::template write_element<1, 0>(r) =
      mat_traits<A>::template read_element<1, 0>(a);
  mat_traits<R>::template write_element<1, 1>(r) =
      mat_traits<A>::template read_element<1, 1>(a);
  return r;
}

namespace sfinae {
using ::boost::qvm::convert_to;
}

namespace qvm_detail {
template <int R, int C> struct convert_to_m_defined;

template <> struct convert_to_m_defined<2, 2> {
  static bool const value = true;
};
} // namespace qvm_detail

template <class R, class A>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<R>::rows == 2 && mat_traits<A>::rows == 2 &&
                             mat_traits<R>::cols == 1 &&
                             mat_traits<A>::cols == 1,
                         R>::type
    convert_to(A const &a) {
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      mat_traits<A>::template read_element<0, 0>(a);
  mat_traits<R>::template write_element<1, 0>(r) =
      mat_traits<A>::template read_element<1, 0>(a);
  return r;
}

namespace sfinae {
using ::boost::qvm::convert_to;
}

namespace qvm_detail {
template <int R, int C> struct convert_to_m_defined;

template <> struct convert_to_m_defined<2, 1> {
  static bool const value = true;
};
} // namespace qvm_detail

template <class R, class A>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<R>::rows == 1 && mat_traits<A>::rows == 1 &&
                             mat_traits<R>::cols == 2 &&
                             mat_traits<A>::cols == 2,
                         R>::type
    convert_to(A const &a) {
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      mat_traits<A>::template read_element<0, 0>(a);
  mat_traits<R>::template write_element<0, 1>(r) =
      mat_traits<A>::template read_element<0, 1>(a);
  return r;
}

namespace sfinae {
using ::boost::qvm::convert_to;
}

namespace qvm_detail {
template <int R, int C> struct convert_to_m_defined;

template <> struct convert_to_m_defined<1, 2> {
  static bool const value = true;
};
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 2 && mat_traits<B>::rows == 2 &&
                             mat_traits<A>::cols == 2 &&
                             mat_traits<B>::cols == 2,
                         bool>::type
    operator==(A const &a, B const &b) {
  return mat_traits<A>::template read_element<0, 0>(a) ==
             mat_traits<B>::template read_element<0, 0>(b) &&
         mat_traits<A>::template read_element<0, 1>(a) ==
             mat_traits<B>::template read_element<0, 1>(b) &&
         mat_traits<A>::template read_element<1, 0>(a) ==
             mat_traits<B>::template read_element<1, 0>(b) &&
         mat_traits<A>::template read_element<1, 1>(a) ==
             mat_traits<B>::template read_element<1, 1>(b);
}

namespace sfinae {
using ::boost::qvm::operator==;
}

namespace qvm_detail {
template <int R, int C> struct eq_mm_defined;

template <> struct eq_mm_defined<2, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 2 && mat_traits<B>::rows == 2 &&
                             mat_traits<A>::cols == 1 &&
                             mat_traits<B>::cols == 1,
                         bool>::type
    operator==(A const &a, B const &b) {
  return mat_traits<A>::template read_element<0, 0>(a) ==
             mat_traits<B>::template read_element<0, 0>(b) &&
         mat_traits<A>::template read_element<1, 0>(a) ==
             mat_traits<B>::template read_element<1, 0>(b);
}

namespace sfinae {
using ::boost::qvm::operator==;
}

namespace qvm_detail {
template <int R, int C> struct eq_mm_defined;

template <> struct eq_mm_defined<2, 1> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 1 && mat_traits<B>::rows == 1 &&
                             mat_traits<A>::cols == 2 &&
                             mat_traits<B>::cols == 2,
                         bool>::type
    operator==(A const &a, B const &b) {
  return mat_traits<A>::template read_element<0, 0>(a) ==
             mat_traits<B>::template read_element<0, 0>(b) &&
         mat_traits<A>::template read_element<0, 1>(a) ==
             mat_traits<B>::template read_element<0, 1>(b);
}

namespace sfinae {
using ::boost::qvm::operator==;
}

namespace qvm_detail {
template <int R, int C> struct eq_mm_defined;

template <> struct eq_mm_defined<1, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 2 && mat_traits<B>::rows == 2 &&
                             mat_traits<A>::cols == 2 &&
                             mat_traits<B>::cols == 2,
                         bool>::type
    operator!=(A const &a, B const &b) {
  return !(mat_traits<A>::template read_element<0, 0>(a) ==
           mat_traits<B>::template read_element<0, 0>(b)) ||
         !(mat_traits<A>::template read_element<0, 1>(a) ==
           mat_traits<B>::template read_element<0, 1>(b)) ||
         !(mat_traits<A>::template read_element<1, 0>(a) ==
           mat_traits<B>::template read_element<1, 0>(b)) ||
         !(mat_traits<A>::template read_element<1, 1>(a) ==
           mat_traits<B>::template read_element<1, 1>(b));
}

namespace sfinae {
using ::boost::qvm::operator!=;
}

namespace qvm_detail {
template <int R, int C> struct neq_mm_defined;

template <> struct neq_mm_defined<2, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 2 && mat_traits<B>::rows == 2 &&
                             mat_traits<A>::cols == 1 &&
                             mat_traits<B>::cols == 1,
                         bool>::type
    operator!=(A const &a, B const &b) {
  return !(mat_traits<A>::template read_element<0, 0>(a) ==
           mat_traits<B>::template read_element<0, 0>(b)) ||
         !(mat_traits<A>::template read_element<1, 0>(a) ==
           mat_traits<B>::template read_element<1, 0>(b));
}

namespace sfinae {
using ::boost::qvm::operator!=;
}

namespace qvm_detail {
template <int R, int C> struct neq_mm_defined;

template <> struct neq_mm_defined<2, 1> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 1 && mat_traits<B>::rows == 1 &&
                             mat_traits<A>::cols == 2 &&
                             mat_traits<B>::cols == 2,
                         bool>::type
    operator!=(A const &a, B const &b) {
  return !(mat_traits<A>::template read_element<0, 0>(a) ==
           mat_traits<B>::template read_element<0, 0>(b)) ||
         !(mat_traits<A>::template read_element<0, 1>(a) ==
           mat_traits<B>::template read_element<0, 1>(b));
}

namespace sfinae {
using ::boost::qvm::operator!=;
}

namespace qvm_detail {
template <int R, int C> struct neq_mm_defined;

template <> struct neq_mm_defined<1, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 2 && mat_traits<A>::cols == 2, deduce_mat<A>>::type
operator-(A const &a) {
  typedef typename deduce_mat<A>::type R;
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      -mat_traits<A>::template read_element<0, 0>(a);
  mat_traits<R>::template write_element<0, 1>(r) =
      -mat_traits<A>::template read_element<0, 1>(a);
  mat_traits<R>::template write_element<1, 0>(r) =
      -mat_traits<A>::template read_element<1, 0>(a);
  mat_traits<R>::template write_element<1, 1>(r) =
      -mat_traits<A>::template read_element<1, 1>(a);
  return r;
}

namespace sfinae {
using ::boost::qvm::operator-;
}

namespace qvm_detail {
template <int R, int C> struct minus_m_defined;

template <> struct minus_m_defined<2, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 2 && mat_traits<A>::cols == 1, deduce_mat<A>>::type
operator-(A const &a) {
  typedef typename deduce_mat<A>::type R;
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      -mat_traits<A>::template read_element<0, 0>(a);
  mat_traits<R>::template write_element<1, 0>(r) =
      -mat_traits<A>::template read_element<1, 0>(a);
  return r;
}

namespace sfinae {
using ::boost::qvm::operator-;
}

namespace qvm_detail {
template <int R, int C> struct minus_m_defined;

template <> struct minus_m_defined<2, 1> { static bool const value = true; };
} // namespace qvm_detail

template <class A>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 1 && mat_traits<A>::cols == 2, deduce_mat<A>>::type
operator-(A const &a) {
  typedef typename deduce_mat<A>::type R;
  R r;
  mat_traits<R>::template write_element<0, 0>(r) =
      -mat_traits<A>::template read_element<0, 0>(a);
  mat_traits<R>::template write_element<0, 1>(r) =
      -mat_traits<A>::template read_element<0, 1>(a);
  return r;
}

namespace sfinae {
using ::boost::qvm::operator-;
}

namespace qvm_detail {
template <int R, int C> struct minus_m_defined;

template <> struct minus_m_defined<1, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 2 && mat_traits<A>::cols == 2,
                         typename mat_traits<A>::scalar_type>::type
    determinant(A const &a) {
  typedef typename mat_traits<A>::scalar_type T;
  T const a00 = mat_traits<A>::template read_element<0, 0>(a);
  T const a01 = mat_traits<A>::template read_element<0, 1>(a);
  T const a10 = mat_traits<A>::template read_element<1, 0>(a);
  T const a11 = mat_traits<A>::template read_element<1, 1>(a);
  T det = (a00 * a11 - a01 * a10);
  return det;
}

namespace sfinae {
using ::boost::qvm::determinant;
}

namespace qvm_detail {
template <int D> struct determinant_defined;

template <> struct determinant_defined<2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 2 && mat_traits<A>::cols == 2 && is_scalar<B>::value,
    deduce_mat<A>>::type
inverse(A const &a, B det) {
  typedef typename mat_traits<A>::scalar_type T;
  BOOST_QVM_ASSERT(det != scalar_traits<B>::value(0));
  T const a00 = mat_traits<A>::template read_element<0, 0>(a);
  T const a01 = mat_traits<A>::template read_element<0, 1>(a);
  T const a10 = mat_traits<A>::template read_element<1, 0>(a);
  T const a11 = mat_traits<A>::template read_element<1, 1>(a);
  T const f = scalar_traits<T>::value(1) / det;
  typedef typename deduce_mat<A>::type R;
  R r;
  mat_traits<R>::template write_element<0, 0>(r) = f * a11;
  mat_traits<R>::template write_element<0, 1>(r) = -f * a01;
  mat_traits<R>::template write_element<1, 0>(r) = -f * a10;
  mat_traits<R>::template write_element<1, 1>(r) = f * a00;
  return r;
}

template <class A>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 2 && mat_traits<A>::cols == 2, deduce_mat<A>>::type
inverse(A const &a) {
  typedef typename mat_traits<A>::scalar_type T;
  T det = determinant(a);
  if (det == scalar_traits<T>::value(0))
    BOOST_QVM_THROW_EXCEPTION(zero_determinant_error());
  return inverse(a, det);
}

namespace sfinae {
using ::boost::qvm::inverse;
}

namespace qvm_detail {
template <int D> struct inverse_m_defined;

template <> struct inverse_m_defined<2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 2 && mat_traits<B>::rows == 2 &&
        mat_traits<A>::cols == 2 && mat_traits<B>::cols == 2,
    deduce_mat2<A, B, 2, 2>>::type
operator*(A const &a, B const &b) {
  typedef typename mat_traits<A>::scalar_type Ta;
  typedef typename mat_traits<B>::scalar_type Tb;
  Ta const a00 = mat_traits<A>::template read_element<0, 0>(a);
  Ta const a01 = mat_traits<A>::template read_element<0, 1>(a);
  Ta const a10 = mat_traits<A>::template read_element<1, 0>(a);
  Ta const a11 = mat_traits<A>::template read_element<1, 1>(a);
  Tb const b00 = mat_traits<B>::template read_element<0, 0>(b);
  Tb const b01 = mat_traits<B>::template read_element<0, 1>(b);
  Tb const b10 = mat_traits<B>::template read_element<1, 0>(b);
  Tb const b11 = mat_traits<B>::template read_element<1, 1>(b);
  typedef typename deduce_mat2<A, B, 2, 2>::type R;
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::rows == 2);
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::cols == 2);
  R r;
  mat_traits<R>::template write_element<0, 0>(r) = a00 * b00 + a01 * b10;
  mat_traits<R>::template write_element<0, 1>(r) = a00 * b01 + a01 * b11;
  mat_traits<R>::template write_element<1, 0>(r) = a10 * b00 + a11 * b10;
  mat_traits<R>::template write_element<1, 1>(r) = a10 * b01 + a11 * b11;
  return r;
}

namespace sfinae {
using ::boost::qvm::operator*;
}

namespace qvm_detail {
template <int R, int CR, int C> struct mul_mm_defined;

template <> struct mul_mm_defined<2, 2, 2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS
    typename enable_if_c<mat_traits<A>::rows == 2 && mat_traits<B>::rows == 2 &&
                             mat_traits<A>::cols == 2 &&
                             mat_traits<B>::cols == 2,
                         A &>::type
    operator*=(A &a, B const &b) {
  typedef typename mat_traits<A>::scalar_type Ta;
  typedef typename mat_traits<B>::scalar_type Tb;
  Ta const a00 = mat_traits<A>::template read_element<0, 0>(a);
  Ta const a01 = mat_traits<A>::template read_element<0, 1>(a);
  Ta const a10 = mat_traits<A>::template read_element<1, 0>(a);
  Ta const a11 = mat_traits<A>::template read_element<1, 1>(a);
  Tb const b00 = mat_traits<B>::template read_element<0, 0>(b);
  Tb const b01 = mat_traits<B>::template read_element<0, 1>(b);
  Tb const b10 = mat_traits<B>::template read_element<1, 0>(b);
  Tb const b11 = mat_traits<B>::template read_element<1, 1>(b);
  mat_traits<A>::template write_element<0, 0>(a) = a00 * b00 + a01 * b10;
  mat_traits<A>::template write_element<0, 1>(a) = a00 * b01 + a01 * b11;
  mat_traits<A>::template write_element<1, 0>(a) = a10 * b00 + a11 * b10;
  mat_traits<A>::template write_element<1, 1>(a) = a10 * b01 + a11 * b11;
  return a;
}

namespace sfinae {
using ::boost::qvm::operator*=;
}

namespace qvm_detail {
template <int D> struct mul_eq_mm_defined;

template <> struct mul_eq_mm_defined<2> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 2 && mat_traits<B>::rows == 2 &&
        mat_traits<A>::cols == 2 && mat_traits<B>::cols == 1,
    deduce_mat2<A, B, 2, 1>>::type
operator*(A const &a, B const &b) {
  typedef typename mat_traits<A>::scalar_type Ta;
  typedef typename mat_traits<B>::scalar_type Tb;
  Ta const a00 = mat_traits<A>::template read_element<0, 0>(a);
  Ta const a01 = mat_traits<A>::template read_element<0, 1>(a);
  Ta const a10 = mat_traits<A>::template read_element<1, 0>(a);
  Ta const a11 = mat_traits<A>::template read_element<1, 1>(a);
  Tb const b00 = mat_traits<B>::template read_element<0, 0>(b);
  Tb const b10 = mat_traits<B>::template read_element<1, 0>(b);
  typedef typename deduce_mat2<A, B, 2, 1>::type R;
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::rows == 2);
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::cols == 1);
  R r;
  mat_traits<R>::template write_element<0, 0>(r) = a00 * b00 + a01 * b10;
  mat_traits<R>::template write_element<1, 0>(r) = a10 * b00 + a11 * b10;
  return r;
}

namespace sfinae {
using ::boost::qvm::operator*;
}

namespace qvm_detail {
template <int R, int CR, int C> struct mul_mm_defined;

template <> struct mul_mm_defined<2, 2, 1> { static bool const value = true; };
} // namespace qvm_detail

template <class A, class B>
BOOST_QVM_INLINE_OPERATIONS typename lazy_enable_if_c<
    mat_traits<A>::rows == 1 && mat_traits<B>::rows == 2 &&
        mat_traits<A>::cols == 2 && mat_traits<B>::cols == 2,
    deduce_mat2<A, B, 1, 2>>::type
operator*(A const &a, B const &b) {
  typedef typename mat_traits<A>::scalar_type Ta;
  typedef typename mat_traits<B>::scalar_type Tb;
  Ta const a00 = mat_traits<A>::template read_element<0, 0>(a);
  Ta const a01 = mat_traits<A>::template read_element<0, 1>(a);
  Tb const b00 = mat_traits<B>::template read_element<0, 0>(b);
  Tb const b01 = mat_traits<B>::template read_element<0, 1>(b);
  Tb const b10 = mat_traits<B>::template read_element<1, 0>(b);
  Tb const b11 = mat_traits<B>::template read_element<1, 1>(b);
  typedef typename deduce_mat2<A, B, 1, 2>::type R;
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::rows == 1);
  BOOST_QVM_STATIC_ASSERT(mat_traits<R>::cols == 2);
  R r;
  mat_traits<R>::template write_element<0, 0>(r) = a00 * b00 + a01 * b10;
  mat_traits<R>::template write_element<0, 1>(r) = a00 * b01 + a01 * b11;
  return r;
}

namespace sfinae {
using ::boost::qvm::operator*;
}

namespace qvm_detail {
template <int R, int CR, int C> struct mul_mm_defined;

template <> struct mul_mm_defined<1, 2, 2> { static bool const value = true; };
} // namespace qvm_detail

} // namespace qvm
} // namespace boost

#endif


// 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_MAT_MAT_ELE_TIMES_EXPR_INCLUDE
#define MTL_MAT_MAT_ELE_TIMES_EXPR_INCLUDE

#include <boost/shared_ptr.hpp>
#include <boost/numeric/mtl/mtl_fwd.hpp>
#include <boost/numeric/mtl/matrix/mat_mat_op_expr.hpp>
#include <boost/numeric/mtl/operation/sfunctor.hpp>
#include <boost/numeric/linear_algebra/identity.hpp>

namespace mtl { namespace matrix {

template <typename E1, typename E2>
struct mat_mat_ele_times_expr
    : public mat_mat_op_expr< E1, E2, mtl::sfunctor::times<typename E1::value_type, typename E2::value_type> >,
      public mat_expr< mat_mat_ele_times_expr<E1, E2> >
{
    typedef mat_mat_op_expr< E1, E2, mtl::sfunctor::times<typename E1::value_type, typename E2::value_type> > op_base;
    typedef mat_expr< mat_mat_ele_times_expr<E1, E2> >                                                        crtp_base;
    typedef mat_mat_ele_times_expr               self;
    typedef E1                                   first_argument_type ;
    typedef E2                                   second_argument_type ;
    typedef typename E1::orientation             orientation;
    typedef mtl::non_fixed::dimensions           dim_type;
    typedef typename E1::key_type                key_type;

    mat_mat_ele_times_expr( E1 const& v1, E2 const& v2 )
	: op_base( v1, v2 ), crtp_base(*this), first(v1), second(v2)
    {}

    first_argument_type const&  first ;
    second_argument_type const& second ;
};

template <typename E1, typename E2>
std::size_t inline num_rows(const mat_mat_ele_times_expr<E1, E2>& expr)
{ return num_rows(expr.first); }

template <typename E1, typename E2>
std::size_t inline num_cols(const mat_mat_ele_times_expr<E1, E2>& expr)
{ return num_cols(expr.second); }

template <typename E1, typename E2>
std::size_t inline size(const mat_mat_ele_times_expr<E1, E2>& expr)
{ return num_rows(expr) * num_cols(expr); }

}} // Namespace mtl::matrix

#endif // MTL_MAT_MAT_ELE_TIMES_EXPR_INCLUDE


// Copyright (c) 2016 Jack Grigg
// Copyright (c) 2016 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

// Implementation of the Equihash Proof-of-Work algorithm.
//
// Reference
// =========
// Alex Biryukov and Dmitry Khovratovich
// Equihash: Asymmetric Proof-of-Work Based on the Generalized Birthday Problem
// NDSS ’16, 21-24 February 2016, San Diego, CA, USA
// https://www.internetsociety.org/sites/default/files/blogs-media/equihash-asymmetric-proof-of-work-based-generalized-birthday-problem.pdf

#if defined(HAVE_CONFIG_H)
#include "config/bpq-config.h"
#endif

#include "crypto/equihash.h"

#ifndef NO_UTIL_LOG
#include "util.h"
#else
#define LogPrint(...)
#endif

#include <algorithm>
#include <iostream>
#include <stdexcept>

#include <boost/optional.hpp>

EhSolverCancelledException solver_cancelled;

template<unsigned int N, unsigned int K>
int Equihash<N,K>::InitialiseState(eh_HashState& base_state)
{
    uint32_t le_N = htole32(N);
    uint32_t le_K = htole32(K);
    unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES] = {};
    memcpy(personalization, "ZcashPoW", 8);
    memcpy(personalization+8,  &le_N, 4);
    memcpy(personalization+12, &le_K, 4);
    return crypto_generichash_blake2b_init_salt_personal(&base_state,
                                                         NULL, 0, // No key.
                                                         (512/N)*N/8,
                                                         NULL,    // No salt.
                                                         personalization);
}

void GenerateHash(const eh_HashState& base_state, eh_index g,
                  unsigned char* hash, size_t hLen)
{
    eh_HashState state;
    state = base_state;
    eh_index lei = htole32(g);
    crypto_generichash_blake2b_update(&state, (const unsigned char*) &lei,
                                      sizeof(eh_index));
    crypto_generichash_blake2b_final(&state, hash, hLen);
}

void ExpandArray(const unsigned char* in, size_t in_len,
                 unsigned char* out, size_t out_len,
                 size_t bit_len, size_t byte_pad)
{
    assert(bit_len >= 8);
    assert(8*sizeof(uint32_t) >= 7+bit_len);

    size_t out_width { (bit_len+7)/8 + byte_pad };
    assert(out_len == 8*out_width*in_len/bit_len);

    uint32_t bit_len_mask { ((uint32_t)1 << bit_len) - 1 };

    // The acc_bits least-significant bits of acc_value represent a bit sequence
    // in big-endian order.
    size_t acc_bits = 0;
    uint32_t acc_value = 0;

    size_t j = 0;
    for (size_t i = 0; i < in_len; i++) {
        acc_value = (acc_value << 8) | in[i];
        acc_bits += 8;

        // When we have bit_len or more bits in the accumulator, write the next
        // output element.
        if (acc_bits >= bit_len) {
            acc_bits -= bit_len;
            for (size_t x = 0; x < byte_pad; x++) {
                out[j+x] = 0;
            }
            for (size_t x = byte_pad; x < out_width; x++) {
                out[j+x] = (
                    // Big-endian
                    acc_value >> (acc_bits+(8*(out_width-x-1)))
                ) & (
                    // Apply bit_len_mask across byte boundaries
                    (bit_len_mask >> (8*(out_width-x-1))) & 0xFF
                );
            }
            j += out_width;
        }
    }
}

void CompressArray(const unsigned char* in, size_t in_len,
                   unsigned char* out, size_t out_len,
                   size_t bit_len, size_t byte_pad)
{
    assert(bit_len >= 8);
    assert(8*sizeof(uint32_t) >= 7+bit_len);

    size_t in_width { (bit_len+7)/8 + byte_pad };
    assert(out_len == bit_len*in_len/(8*in_width));

    uint32_t bit_len_mask { ((uint32_t)1 << bit_len) - 1 };

    // The acc_bits least-significant bits of acc_value represent a bit sequence
    // in big-endian order.
    size_t acc_bits = 0;
    uint32_t acc_value = 0;

    size_t j = 0;
    for (size_t i = 0; i < out_len; i++) {
        // When we have fewer than 8 bits left in the accumulator, read the next
        // input element.
        if (acc_bits < 8) {
            acc_value = acc_value << bit_len;
            for (size_t x = byte_pad; x < in_width; x++) {
                acc_value = acc_value | (
                    (
                        // Apply bit_len_mask across byte boundaries
                        in[j+x] & ((bit_len_mask >> (8*(in_width-x-1))) & 0xFF)
                    ) << (8*(in_width-x-1))); // Big-endian
            }
            j += in_width;
            acc_bits += bit_len;
        }

        acc_bits -= 8;
        out[i] = (acc_value >> acc_bits) & 0xFF;
    }
}

// Big-endian so that lexicographic array comparison is equivalent to integer
// comparison
void EhIndexToArray(const eh_index i, unsigned char* array)
{
    BOOST_STATIC_ASSERT(sizeof(eh_index) == 4);
    eh_index bei = htobe32(i);
    memcpy(array, &bei, sizeof(eh_index));
}

// Big-endian so that lexicographic array comparison is equivalent to integer
// comparison
eh_index ArrayToEhIndex(const unsigned char* array)
{
    BOOST_STATIC_ASSERT(sizeof(eh_index) == 4);
    eh_index bei;
    memcpy(&bei, array, sizeof(eh_index));
    return be32toh(bei);
}

eh_trunc TruncateIndex(const eh_index i, const unsigned int ilen)
{
    // Truncate to 8 bits
    BOOST_STATIC_ASSERT(sizeof(eh_trunc) == 1);
    return (i >> (ilen - 8)) & 0xff;
}

eh_index UntruncateIndex(const eh_trunc t, const eh_index r, const unsigned int ilen)
{
    eh_index i{t};
    return (i << (ilen - 8)) | r;
}

std::vector<eh_index> GetIndicesFromMinimal(std::vector<unsigned char> minimal,
                                            size_t cBitLen)
{
    assert(((cBitLen+1)+7)/8 <= sizeof(eh_index));
    size_t lenIndices { 8*sizeof(eh_index)*minimal.size()/(cBitLen+1) };
    size_t bytePad { sizeof(eh_index) - ((cBitLen+1)+7)/8 };
    std::vector<unsigned char> array(lenIndices);
    ExpandArray(minimal.data(), minimal.size(),
                array.data(), lenIndices, cBitLen+1, bytePad);
    std::vector<eh_index> ret;
    for (size_t i = 0; i < lenIndices; i += sizeof(eh_index)) {
        ret.push_back(ArrayToEhIndex(array.data()+i));
    }
    return ret;
}

std::vector<unsigned char> GetMinimalFromIndices(std::vector<eh_index> indices,
                                                 size_t cBitLen)
{
    assert(((cBitLen+1)+7)/8 <= sizeof(eh_index));
    size_t lenIndices { indices.size()*sizeof(eh_index) };
    size_t minLen { (cBitLen+1)*lenIndices/(8*sizeof(eh_index)) };
    size_t bytePad { sizeof(eh_index) - ((cBitLen+1)+7)/8 };
    std::vector<unsigned char> array(lenIndices);
    for (size_t i = 0; i < indices.size(); i++) {
        EhIndexToArray(indices[i], array.data()+(i*sizeof(eh_index)));
    }
    std::vector<unsigned char> ret(minLen);
    CompressArray(array.data(), lenIndices,
                  ret.data(), minLen, cBitLen+1, bytePad);
    return ret;
}

template<size_t WIDTH>
StepRow<WIDTH>::StepRow(const unsigned char* hashIn, size_t hInLen,
                        size_t hLen, size_t cBitLen)
{
    assert(hLen <= WIDTH);
    ExpandArray(hashIn, hInLen, hash, hLen, cBitLen);
}

template<size_t WIDTH> template<size_t W>
StepRow<WIDTH>::StepRow(const StepRow<W>& a)
{
    BOOST_STATIC_ASSERT(W <= WIDTH);
    std::copy(a.hash, a.hash+W, hash);
}

template<size_t WIDTH>
FullStepRow<WIDTH>::FullStepRow(const unsigned char* hashIn, size_t hInLen,
                                size_t hLen, size_t cBitLen, eh_index i) :
        StepRow<WIDTH> {hashIn, hInLen, hLen, cBitLen}
{
    EhIndexToArray(i, hash+hLen);
}

template<size_t WIDTH> template<size_t W>
FullStepRow<WIDTH>::FullStepRow(const FullStepRow<W>& a, const FullStepRow<W>& b, size_t len, size_t lenIndices, size_t trim) :
        StepRow<WIDTH> {a}
{
    assert(len+lenIndices <= W);
    assert(len-trim+(2*lenIndices) <= WIDTH);
    for (size_t i = trim; i < len; i++)
        hash[i-trim] = a.hash[i] ^ b.hash[i];
    if (a.IndicesBefore(b, len, lenIndices)) {
        std::copy(a.hash+len, a.hash+len+lenIndices, hash+len-trim);
        std::copy(b.hash+len, b.hash+len+lenIndices, hash+len-trim+lenIndices);
    } else {
        std::copy(b.hash+len, b.hash+len+lenIndices, hash+len-trim);
        std::copy(a.hash+len, a.hash+len+lenIndices, hash+len-trim+lenIndices);
    }
}

template<size_t WIDTH>
FullStepRow<WIDTH>& FullStepRow<WIDTH>::operator=(const FullStepRow<WIDTH>& a)
{
    std::copy(a.hash, a.hash+WIDTH, hash);
    return *this;
}

template<size_t WIDTH>
bool StepRow<WIDTH>::IsZero(size_t len)
{
    // This doesn't need to be constant time.
    for (size_t i = 0; i < len; i++) {
        if (hash[i] != 0)
            return false;
    }
    return true;
}

template<size_t WIDTH>
std::vector<unsigned char> FullStepRow<WIDTH>::GetIndices(size_t len, size_t lenIndices,
                                                          size_t cBitLen) const
{
    assert(((cBitLen+1)+7)/8 <= sizeof(eh_index));
    size_t minLen { (cBitLen+1)*lenIndices/(8*sizeof(eh_index)) };
    size_t bytePad { sizeof(eh_index) - ((cBitLen+1)+7)/8 };
    std::vector<unsigned char> ret(minLen);
    CompressArray(hash+len, lenIndices, ret.data(), minLen, cBitLen+1, bytePad);
    return ret;
}

template<size_t WIDTH>
bool HasCollision(StepRow<WIDTH>& a, StepRow<WIDTH>& b, size_t l)
{
    // This doesn't need to be constant time.
    for (size_t j = 0; j < l; j++) {
        if (a.hash[j] != b.hash[j])
            return false;
    }
    return true;
}

template<size_t WIDTH>
TruncatedStepRow<WIDTH>::TruncatedStepRow(const unsigned char* hashIn, size_t hInLen,
                                          size_t hLen, size_t cBitLen,
                                          eh_index i, unsigned int ilen) :
        StepRow<WIDTH> {hashIn, hInLen, hLen, cBitLen}
{
    hash[hLen] = TruncateIndex(i, ilen);
}

template<size_t WIDTH> template<size_t W>
TruncatedStepRow<WIDTH>::TruncatedStepRow(const TruncatedStepRow<W>& a, const TruncatedStepRow<W>& b, size_t len, size_t lenIndices, size_t trim) :
        StepRow<WIDTH> {a}
{
    assert(len+lenIndices <= W);
    assert(len-trim+(2*lenIndices) <= WIDTH);
    for (size_t i = trim; i < len; i++)
        hash[i-trim] = a.hash[i] ^ b.hash[i];
    if (a.IndicesBefore(b, len, lenIndices)) {
        std::copy(a.hash+len, a.hash+len+lenIndices, hash+len-trim);
        std::copy(b.hash+len, b.hash+len+lenIndices, hash+len-trim+lenIndices);
    } else {
        std::copy(b.hash+len, b.hash+len+lenIndices, hash+len-trim);
        std::copy(a.hash+len, a.hash+len+lenIndices, hash+len-trim+lenIndices);
    }
}

template<size_t WIDTH>
TruncatedStepRow<WIDTH>& TruncatedStepRow<WIDTH>::operator=(const TruncatedStepRow<WIDTH>& a)
{
    std::copy(a.hash, a.hash+WIDTH, hash);
    return *this;
}

template<size_t WIDTH>
std::shared_ptr<eh_trunc> TruncatedStepRow<WIDTH>::GetTruncatedIndices(size_t len, size_t lenIndices) const
{
    std::shared_ptr<eh_trunc> p (new eh_trunc[lenIndices], std::default_delete<eh_trunc[]>());
    std::copy(hash+len, hash+len+lenIndices, p.get());
    return p;
}

template<unsigned int N, unsigned int K>
bool Equihash<N,K>::BasicSolve(const eh_HashState& base_state,
                               const std::function<bool(std::vector<unsigned char>)> validBlock,
                               const std::function<bool(EhSolverCancelCheck)> cancelled)
{
    eh_index init_size { 1 << (CollisionBitLength + 1) };
    // So, for <200,9>, that’s 2 ^ ( (200 / (9+1) ) + 1), or 2 to the 21st power, or 2,097,152.strings. 2097152 * 1030=2 160 066 560
    //200x9 = 2097152 * 1030 = 2 160 066 560  // 3m4.064s,  2m56.177s, 5m46.537s
    //176x7 = 8388608 * 262  = 2 197 815 296   // 2m12.193s, 2m21.114s, 2m20.580s
    //144x5 = 33554432 * 70  = 2 348 810 240   // 3m39.719s, 3m58.309s, 7m22.785s
    //168x7 = 4194304 * 262  = 1 098 907 648   // 0m56.635s, 1m0.621s, 0m55.824s

    // 1) Generate first list
    LogPrint(BCLog::POW, "Generating first list\n");
    size_t hashLen = HashLength;
    size_t lenIndices = sizeof(eh_index);
    std::vector<FullStepRow<FullWidth>> X;
    X.reserve(init_size);

    //size_t fullWidth = FullWidth;

    unsigned char tmpHash[HashOutput];
    for (eh_index g = 0; X.size() < init_size; g++) {
        GenerateHash(base_state, g, tmpHash, HashOutput);
        for (eh_index i = 0; i < IndicesPerHashOutput && X.size() < init_size; i++) {
            X.emplace_back(tmpHash+(i*N/8), N/8, HashLength,
                           CollisionBitLength, (g*IndicesPerHashOutput)+i);
        }
        if (cancelled(ListGeneration)) throw solver_cancelled;
    }

    // 3) Repeat step 2 until 2n/(k+1) bits remain
    for (unsigned int r = 1; r < K && X.size() > 0; r++) {
        LogPrint(BCLog::POW, "Round %u:\n", r);
        // 2a) Sort the list
        LogPrint(BCLog::POW, "- Sorting list\n");
        std::sort(X.begin(), X.end(), CompareSR(CollisionByteLength));
        if (cancelled(ListSorting)) throw solver_cancelled;

        LogPrint(BCLog::POW, "- Finding collisions\n");
        size_t i = 0;
        size_t posFree = 0;
        std::vector<FullStepRow<FullWidth>> Xc;
        while (i < X.size() - 1) {
            // 2b) Find next set of unordered pairs with collisions on the next n/(k+1) bits
            size_t j = 1;
            while (i+j < X.size() &&
                    HasCollision(X[i], X[i+j], CollisionByteLength)) {
                j++;
            }

            // 2c) Calculate tuples (X_i ^ X_j, (i, j))
            for (size_t l = 0; l < j - 1; l++) {
                for (size_t m = l + 1; m < j; m++) {
                    if (DistinctIndices(X[i+l], X[i+m], hashLen, lenIndices)) {
                        Xc.emplace_back(X[i+l], X[i+m], hashLen, lenIndices, CollisionByteLength);
                    }
                }
            }

            // 2d) Store tuples on the table in-place if possible
            while (posFree < i+j && Xc.size() > 0) {
                X[posFree++] = Xc.back();
                Xc.pop_back();
            }

            i += j;
            if (cancelled(ListColliding)) throw solver_cancelled;
        }

        // 2e) Handle edge case where final table entry has no collision
        while (posFree < X.size() && Xc.size() > 0) {
            X[posFree++] = Xc.back();
            Xc.pop_back();
        }

        if (Xc.size() > 0) {
            // 2f) Add overflow to end of table
            X.insert(X.end(), Xc.begin(), Xc.end());
        } else if (posFree < X.size()) {
            // 2g) Remove empty space at the end
            X.erase(X.begin()+posFree, X.end());
            X.shrink_to_fit();
        }

        hashLen -= CollisionByteLength;
        lenIndices *= 2;
        if (cancelled(RoundEnd)) throw solver_cancelled;
    }

    // k+1) Find a collision on last 2n(k+1) bits
    LogPrint(BCLog::POW, "Final round:\n");
    if (X.size() > 1) {
        LogPrint(BCLog::POW, "- Sorting list\n");
        std::sort(X.begin(), X.end(), CompareSR(hashLen));
        if (cancelled(FinalSorting)) throw solver_cancelled;
        LogPrint(BCLog::POW, "- Finding collisions\n");
        size_t i = 0;
        while (i < X.size() - 1) {
            size_t j = 1;
            while (i+j < X.size() &&
                    HasCollision(X[i], X[i+j], hashLen)) {
                j++;
            }

            for (size_t l = 0; l < j - 1; l++) {
                for (size_t m = l + 1; m < j; m++) {
                    FullStepRow<FinalFullWidth> res(X[i+l], X[i+m], hashLen, lenIndices, 0);
                    if (DistinctIndices(X[i+l], X[i+m], hashLen, lenIndices)) {
                        auto soln = res.GetIndices(hashLen, 2*lenIndices, CollisionBitLength);
                        assert(soln.size() == equihash_solution_size(N, K));
                        if (validBlock(soln)) {
                            return true;
                        }
                    }
                }
            }

            i += j;
            if (cancelled(FinalColliding)) throw solver_cancelled;
        }
    } else
        LogPrint(BCLog::POW, "- List is empty\n");

    return false;
}

template<size_t WIDTH>
void CollideBranches(std::vector<FullStepRow<WIDTH>>& X, const size_t hlen, const size_t lenIndices, const unsigned int clen, const unsigned int ilen, const eh_trunc lt, const eh_trunc rt)
{
    size_t i = 0;
    size_t posFree = 0;
    std::vector<FullStepRow<WIDTH>> Xc;
    while (i < X.size() - 1) {
        // 2b) Find next set of unordered pairs with collisions on the next n/(k+1) bits
        size_t j = 1;
        while (i+j < X.size() &&
                HasCollision(X[i], X[i+j], clen)) {
            j++;
        }

        // 2c) Calculate tuples (X_i ^ X_j, (i, j))
        for (size_t l = 0; l < j - 1; l++) {
            for (size_t m = l + 1; m < j; m++) {
                if (DistinctIndices(X[i+l], X[i+m], hlen, lenIndices)) {
                    if (IsValidBranch(X[i+l], hlen, ilen, lt) && IsValidBranch(X[i+m], hlen, ilen, rt)) {
                        Xc.emplace_back(X[i+l], X[i+m], hlen, lenIndices, clen);
                    } else if (IsValidBranch(X[i+m], hlen, ilen, lt) && IsValidBranch(X[i+l], hlen, ilen, rt)) {
                        Xc.emplace_back(X[i+m], X[i+l], hlen, lenIndices, clen);
                    }
                }
            }
        }

        // 2d) Store tuples on the table in-place if possible
        while (posFree < i+j && Xc.size() > 0) {
            X[posFree++] = Xc.back();
            Xc.pop_back();
        }

        i += j;
    }

    // 2e) Handle edge case where final table entry has no collision
    while (posFree < X.size() && Xc.size() > 0) {
        X[posFree++] = Xc.back();
        Xc.pop_back();
    }

    if (Xc.size() > 0) {
        // 2f) Add overflow to end of table
        X.insert(X.end(), Xc.begin(), Xc.end());
    } else if (posFree < X.size()) {
        // 2g) Remove empty space at the end
        X.erase(X.begin()+posFree, X.end());
        X.shrink_to_fit();
    }
}

template<unsigned int N, unsigned int K>
bool Equihash<N,K>::OptimisedSolve(const eh_HashState& base_state,
                                   const std::function<bool(std::vector<unsigned char>)> validBlock,
                                   const std::function<bool(EhSolverCancelCheck)> cancelled)
{
    eh_index init_size { 1 << (CollisionBitLength + 1) };
    eh_index recreate_size { UntruncateIndex(1, 0, CollisionBitLength + 1) };

    // First run the algorithm with truncated indices

    const eh_index soln_size { 1 << K };
    std::vector<std::shared_ptr<eh_trunc>> partialSolns;
    size_t invalidCount = 0;
    {

        // 1) Generate first list
        LogPrint(BCLog::POW, "Generating first list\n");
        size_t hashLen = HashLength;
        size_t lenIndices = sizeof(eh_trunc);
        std::vector<TruncatedStepRow<TruncatedWidth>> Xt;
        Xt.reserve(init_size);
        unsigned char tmpHash[HashOutput];
        for (eh_index g = 0; Xt.size() < init_size; g++) {
            GenerateHash(base_state, g, tmpHash, HashOutput);
            for (eh_index i = 0; i < IndicesPerHashOutput && Xt.size() < init_size; i++) {
                Xt.emplace_back(tmpHash+(i*N/8), N/8, HashLength, CollisionBitLength,
                                (g*IndicesPerHashOutput)+i, CollisionBitLength + 1);
            }
            if (cancelled(ListGeneration)) throw solver_cancelled;
        }

        // 3) Repeat step 2 until 2n/(k+1) bits remain
        for (size_t r = 1; r < K && Xt.size() > 0; r++) {
            LogPrint(BCLog::POW, "Round %zu:\n", r);
            // 2a) Sort the list
            LogPrint(BCLog::POW, "- Sorting list\n");
            std::sort(Xt.begin(), Xt.end(), CompareSR(CollisionByteLength));
            if (cancelled(ListSorting)) throw solver_cancelled;

            LogPrint(BCLog::POW, "- Finding collisions\n");
            size_t i = 0;
            size_t posFree = 0;
            std::vector<TruncatedStepRow<TruncatedWidth>> Xc;
            while (i < Xt.size() - 1) {
                // 2b) Find next set of unordered pairs with collisions on the next n/(k+1) bits
                size_t j = 1;
                while (i+j < Xt.size() &&
                        HasCollision(Xt[i], Xt[i+j], CollisionByteLength)) {
                    j++;
                }

                // 2c) Calculate tuples (X_i ^ X_j, (i, j))
                //bool checking_for_zero = (i == 0 && Xt[0].IsZero(hashLen));
                for (size_t l = 0; l < j - 1; l++) {
                    for (size_t m = l + 1; m < j; m++) {
                        // We truncated, so don't check for distinct indices here
                        TruncatedStepRow<TruncatedWidth> Xi {Xt[i+l], Xt[i+m],
                                                             hashLen, lenIndices,
                                                             CollisionByteLength};
                        if (!(Xi.IsZero(hashLen-CollisionByteLength) &&
                              IsProbablyDuplicate<soln_size>(Xi.GetTruncatedIndices(hashLen-CollisionByteLength, 2*lenIndices),
                                                             2*lenIndices))) {
                            Xc.emplace_back(Xi);
                        }
                    }
                }

                // 2d) Store tuples on the table in-place if possible
                while (posFree < i+j && Xc.size() > 0) {
                    Xt[posFree++] = Xc.back();
                    Xc.pop_back();
                }

                i += j;
                if (cancelled(ListColliding)) throw solver_cancelled;
            }

            // 2e) Handle edge case where final table entry has no collision
            while (posFree < Xt.size() && Xc.size() > 0) {
                Xt[posFree++] = Xc.back();
                Xc.pop_back();
            }

            if (Xc.size() > 0) {
                // 2f) Add overflow to end of table
                Xt.insert(Xt.end(), Xc.begin(), Xc.end());
            } else if (posFree < Xt.size()) {
                // 2g) Remove empty space at the end
                Xt.erase(Xt.begin()+posFree, Xt.end());
                Xt.shrink_to_fit();
            }

            hashLen -= CollisionByteLength;
            lenIndices *= 2;
            if (cancelled(RoundEnd)) throw solver_cancelled;
        }

        // k+1) Find a collision on last 2n(k+1) bits
        LogPrint(BCLog::POW, "Final round:\n");
        if (Xt.size() > 1) {
            LogPrint(BCLog::POW, "- Sorting list\n");
            std::sort(Xt.begin(), Xt.end(), CompareSR(hashLen));
            if (cancelled(FinalSorting)) throw solver_cancelled;
            LogPrint(BCLog::POW, "- Finding collisions\n");
            size_t i = 0;
            while (i < Xt.size() - 1) {
                size_t j = 1;
                while (i+j < Xt.size() &&
                        HasCollision(Xt[i], Xt[i+j], hashLen)) {
                    j++;
                }

                for (size_t l = 0; l < j - 1; l++) {
                    for (size_t m = l + 1; m < j; m++) {
                        TruncatedStepRow<FinalTruncatedWidth> res(Xt[i+l], Xt[i+m],
                                                                  hashLen, lenIndices, 0);
                        auto soln = res.GetTruncatedIndices(hashLen, 2*lenIndices);
                        if (!IsProbablyDuplicate<soln_size>(soln, 2*lenIndices)) {
                            partialSolns.push_back(soln);
                        }
                    }
                }

                i += j;
                if (cancelled(FinalColliding)) throw solver_cancelled;
            }
        } else
            LogPrint(BCLog::POW, "- List is empty\n");

    } // Ensure Xt goes out of scope and is destroyed

    LogPrint(BCLog::POW, "Found %d partial solutions\n", partialSolns.size());

    // Now for each solution run the algorithm again to recreate the indices
    LogPrint(BCLog::POW, "Culling solutions\n");
    for (std::shared_ptr<eh_trunc> partialSoln : partialSolns) {
        std::set<std::vector<unsigned char>> solns;
        size_t hashLen;
        size_t lenIndices;
        unsigned char tmpHash[HashOutput];
        std::vector<boost::optional<std::vector<FullStepRow<FinalFullWidth>>>> X;
        X.reserve(K+1);

        // 3) Repeat steps 1 and 2 for each partial index
        for (eh_index i = 0; i < soln_size; i++) {
            // 1) Generate first list of possibilities
            std::vector<FullStepRow<FinalFullWidth>> icv;
            icv.reserve(recreate_size);
            for (eh_index j = 0; j < recreate_size; j++) {
                eh_index newIndex { UntruncateIndex(partialSoln.get()[i], j, CollisionBitLength + 1) };
                if (j == 0 || newIndex % IndicesPerHashOutput == 0) {
                    GenerateHash(base_state, newIndex/IndicesPerHashOutput,
                                 tmpHash, HashOutput);
                }
                icv.emplace_back(tmpHash+((newIndex % IndicesPerHashOutput) * N/8),
                                 N/8, HashLength, CollisionBitLength, newIndex);
                if (cancelled(PartialGeneration)) throw solver_cancelled;
            }
            boost::optional<std::vector<FullStepRow<FinalFullWidth>>> ic = icv;

            // 2a) For each pair of lists:
            hashLen = HashLength;
            lenIndices = sizeof(eh_index);
            size_t rti = i;
            for (size_t r = 0; r <= K; r++) {
                // 2b) Until we are at the top of a subtree:
                if (r < X.size()) {
                    if (X[r]) {
                        // 2c) Merge the lists
                        ic->reserve(ic->size() + X[r]->size());
                        ic->insert(ic->end(), X[r]->begin(), X[r]->end());
                        std::sort(ic->begin(), ic->end(), CompareSR(hashLen));
                        if (cancelled(PartialSorting)) throw solver_cancelled;
                        size_t lti = rti-(1<<r);
                        CollideBranches(*ic, hashLen, lenIndices,
                                        CollisionByteLength,
                                        CollisionBitLength + 1,
                                        partialSoln.get()[lti], partialSoln.get()[rti]);

                        // 2d) Check if this has become an invalid solution
                        if (ic->size() == 0)
                            goto invalidsolution;

                        X[r] = boost::none;
                        hashLen -= CollisionByteLength;
                        lenIndices *= 2;
                        rti = lti;
                    } else {
                        X[r] = *ic;
                        break;
                    }
                } else {
                    X.push_back(ic);
                    break;
                }
                if (cancelled(PartialSubtreeEnd)) throw solver_cancelled;
            }
            if (cancelled(PartialIndexEnd)) throw solver_cancelled;
        }

        // We are at the top of the tree
        assert(X.size() == K+1);
        for (FullStepRow<FinalFullWidth> row : *X[K]) {
            auto soln = row.GetIndices(hashLen, lenIndices, CollisionBitLength);
            assert(soln.size() == equihash_solution_size(N, K));
            solns.insert(soln);
        }
        for (auto soln : solns) {
            if (validBlock(soln))
                return true;
        }
        if (cancelled(PartialEnd)) throw solver_cancelled;
        continue;

invalidsolution:
        invalidCount++;
    }
    LogPrint(BCLog::POW, "- Number of invalid solutions found: %zu\n", invalidCount);

    return false;
}

template<unsigned int N, unsigned int K>
bool Equihash<N,K>::IsValidSolution(const eh_HashState& base_state, std::vector<unsigned char> soln)
{
    if (soln.size() != SolutionWidth) {
        LogPrint(BCLog::POW, "Invalid solution length: %d (expected %d)\n",
                 soln.size(), SolutionWidth);
        return false;
    }

    std::vector<FullStepRow<FinalFullWidth>> X;
    X.reserve(1 << K);
    unsigned char tmpHash[HashOutput];
    for (eh_index i : GetIndicesFromMinimal(soln, CollisionBitLength)) {
        GenerateHash(base_state, i/IndicesPerHashOutput, tmpHash, HashOutput);
        X.emplace_back(tmpHash+((i % IndicesPerHashOutput) * N/8),
                       N/8, HashLength, CollisionBitLength, i);
    }

    size_t hashLen = HashLength;
    size_t lenIndices = sizeof(eh_index);
    while (X.size() > 1) {
        std::vector<FullStepRow<FinalFullWidth>> Xc;
        for (size_t i = 0; i < X.size(); i += 2) {
            if (!HasCollision(X[i], X[i+1], CollisionByteLength)) {
                LogPrint(BCLog::POW, "Invalid solution: invalid collision length between StepRows\n");
                LogPrint(BCLog::POW, "X[i]   = %s\n", X[i].GetHex(hashLen));
                LogPrint(BCLog::POW, "X[i+1] = %s\n", X[i+1].GetHex(hashLen));
                return false;
            }
            if (X[i+1].IndicesBefore(X[i], hashLen, lenIndices)) {
                LogPrint(BCLog::POW, "Invalid solution: Index tree incorrectly ordered\n");
                return false;
            }
            if (!DistinctIndices(X[i], X[i+1], hashLen, lenIndices)) {
                LogPrint(BCLog::POW, "Invalid solution: duplicate indices\n");
                return false;
            }
            Xc.emplace_back(X[i], X[i+1], hashLen, lenIndices, CollisionByteLength);
        }
        X = Xc;
        hashLen -= CollisionByteLength;
        lenIndices *= 2;
    }

    assert(X.size() == 1);
    return X[0].IsZero(hashLen);
}

// Explicit instantiations for Equihash<96,3>
template int Equihash<96,3>::InitialiseState(eh_HashState& base_state);
template bool Equihash<96,3>::BasicSolve(const eh_HashState& base_state,
                                         const std::function<bool(std::vector<unsigned char>)> validBlock,
                                         const std::function<bool(EhSolverCancelCheck)> cancelled);
template bool Equihash<96,3>::OptimisedSolve(const eh_HashState& base_state,
                                             const std::function<bool(std::vector<unsigned char>)> validBlock,
                                             const std::function<bool(EhSolverCancelCheck)> cancelled);
template bool Equihash<96,3>::IsValidSolution(const eh_HashState& base_state, std::vector<unsigned char> soln);

// Explicit instantiations for Equihash<200,9>
template int Equihash<200,9>::InitialiseState(eh_HashState& base_state);
template bool Equihash<200,9>::BasicSolve(const eh_HashState& base_state,
                                          const std::function<bool(std::vector<unsigned char>)> validBlock,
                                          const std::function<bool(EhSolverCancelCheck)> cancelled);
template bool Equihash<200,9>::OptimisedSolve(const eh_HashState& base_state,
                                              const std::function<bool(std::vector<unsigned char>)> validBlock,
                                              const std::function<bool(EhSolverCancelCheck)> cancelled);
template bool Equihash<200,9>::IsValidSolution(const eh_HashState& base_state, std::vector<unsigned char> soln);

// Explicit instantiations for Equihash<96,5>
template int Equihash<96,5>::InitialiseState(eh_HashState& base_state);
template bool Equihash<96,5>::BasicSolve(const eh_HashState& base_state,
                                         const std::function<bool(std::vector<unsigned char>)> validBlock,
                                         const std::function<bool(EhSolverCancelCheck)> cancelled);
template bool Equihash<96,5>::OptimisedSolve(const eh_HashState& base_state,
                                             const std::function<bool(std::vector<unsigned char>)> validBlock,
                                             const std::function<bool(EhSolverCancelCheck)> cancelled);
template bool Equihash<96,5>::IsValidSolution(const eh_HashState& base_state, std::vector<unsigned char> soln);

// Explicit instantiations for Equihash<48,5>
template int Equihash<48,5>::InitialiseState(eh_HashState& base_state);
template bool Equihash<48,5>::BasicSolve(const eh_HashState& base_state,
                                         const std::function<bool(std::vector<unsigned char>)> validBlock,
                                         const std::function<bool(EhSolverCancelCheck)> cancelled);
template bool Equihash<48,5>::OptimisedSolve(const eh_HashState& base_state,
                                             const std::function<bool(std::vector<unsigned char>)> validBlock,
                                             const std::function<bool(EhSolverCancelCheck)> cancelled);
template bool Equihash<48,5>::IsValidSolution(const eh_HashState& base_state, std::vector<unsigned char> soln);

// Explicit instantiations for Equihash<176,7>
template int Equihash<176,7>::InitialiseState(eh_HashState& base_state);
template bool Equihash<176,7>::BasicSolve(const eh_HashState& base_state,
                                         const std::function<bool(std::vector<unsigned char>)> validBlock,
                                         const std::function<bool(EhSolverCancelCheck)> cancelled);
template bool Equihash<176,7>::OptimisedSolve(const eh_HashState& base_state,
                                             const std::function<bool(std::vector<unsigned char>)> validBlock,
                                             const std::function<bool(EhSolverCancelCheck)> cancelled);
template bool Equihash<176,7>::IsValidSolution(const eh_HashState& base_state, std::vector<unsigned char> soln);

// Explicit instantiations for Equihash<144,5>
template int Equihash<144,5>::InitialiseState(eh_HashState& base_state);
template bool Equihash<144,5>::BasicSolve(const eh_HashState& base_state,
                                          const std::function<bool(std::vector<unsigned char>)> validBlock,
                                          const std::function<bool(EhSolverCancelCheck)> cancelled);
template bool Equihash<144,5>::OptimisedSolve(const eh_HashState& base_state,
                                              const std::function<bool(std::vector<unsigned char>)> validBlock,
                                              const std::function<bool(EhSolverCancelCheck)> cancelled);
template bool Equihash<144,5>::IsValidSolution(const eh_HashState& base_state, std::vector<unsigned char> soln);

// Explicit instantiations for Equihash<168,7>
template int Equihash<168,7>::InitialiseState(eh_HashState& base_state);
template bool Equihash<168,7>::BasicSolve(const eh_HashState& base_state,
                                          const std::function<bool(std::vector<unsigned char>)> validBlock,
                                          const std::function<bool(EhSolverCancelCheck)> cancelled);
template bool Equihash<168,7>::OptimisedSolve(const eh_HashState& base_state,
                                              const std::function<bool(std::vector<unsigned char>)> validBlock,
                                              const std::function<bool(EhSolverCancelCheck)> cancelled);
template bool Equihash<168,7>::IsValidSolution(const eh_HashState& base_state, std::vector<unsigned char> soln);