/*
 *
 * Copyright (c) Kresimir Fresl 2002
 *
 * 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)
 *
 * Author acknowledges the support of the Faculty of Civil Engineering,
 * University of Zagreb, Croatia.
 *
 */

#ifndef BOOST_NUMERIC_BINDINGS_CBLAS_LEVEL_3_HPP
#define BOOST_NUMERIC_BINDINGS_CBLAS_LEVEL_3_HPP

#include <cassert>

#include <boost/numeric/bindings/traits/traits.hpp>
#include <boost/numeric/bindings/traits/type_traits.hpp>
#include <boost/numeric/bindings/atlas/cblas3_overloads.hpp>
#include <boost/numeric/bindings/atlas/cblas_enum.hpp>
#include <boost/type_traits/same_traits.hpp>
#include <boost/mpl/if.hpp>

#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
#  include <boost/static_assert.hpp>
#endif

namespace boost { namespace numeric { namespace bindings {

  namespace atlas {

    // C <- alpha * op (A) * op (B) + beta * C
    // op (A) == A || A^T || A^H
    template <typename T, typename MatrA, typename MatrB, typename MatrC>
    inline
    void gemm (CBLAS_TRANSPOSE const TransA, CBLAS_TRANSPOSE const TransB,
               T const& alpha, MatrA const& a, MatrB const& b,
               T const& beta, MatrC& c
               )
    {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<MatrA>::matrix_structure,
        traits::general_t
      >::value));
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<MatrB>::matrix_structure,
        traits::general_t
      >::value));
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<MatrC>::matrix_structure,
        traits::general_t
      >::value));

      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<MatrA>::ordering_type,
        typename traits::matrix_traits<MatrB>::ordering_type
      >::value));
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<MatrA>::ordering_type,
        typename traits::matrix_traits<MatrC>::ordering_type
      >::value));
#endif

      assert (TransA == CblasNoTrans
              || TransA == CblasTrans
              || TransA == CblasConjTrans);
      assert (TransB == CblasNoTrans
              || TransB == CblasTrans
              || TransB == CblasConjTrans);

      int const m = TransA == CblasNoTrans
        ? traits::matrix_size1 (a)
        : traits::matrix_size2 (a);
      int const n = TransB == CblasNoTrans
        ? traits::matrix_size2 (b)
        : traits::matrix_size1 (b);
      int const k = TransA == CblasNoTrans
        ? traits::matrix_size2 (a)
        : traits::matrix_size1 (a);
      assert (m == traits::matrix_size1 (c));
      assert (n == traits::matrix_size2 (c));
#ifndef NDEBUG
      int const k1 = TransB == CblasNoTrans
        ? traits::matrix_size1 (b)
        : traits::matrix_size2 (b);
      assert (k == k1);
#endif
      // .. what about AtlasConj?

      CBLAS_ORDER const stor_ord
        = enum_cast<CBLAS_ORDER const>
        (storage_order<
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
           typename traits::matrix_traits<MatrA>::ordering_type
#else
           typename MatrA::orientation_category
#endif
         >::value);

      detail::gemm (stor_ord, TransA, TransB, m, n, k, alpha,
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
                    traits::matrix_storage (a),
#else
                    traits::matrix_storage_const (a),
#endif
                    traits::leading_dimension (a),
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
                    traits::matrix_storage (b),
#else
                    traits::matrix_storage_const (b),
#endif
                    traits::leading_dimension (b),
                    beta,
                    traits::matrix_storage (c),
                    traits::leading_dimension (c));
    }


    // C <- alpha * A * B + beta * C
    template <typename T, typename MatrA, typename MatrB, typename MatrC>
    inline
    void gemm (T const& alpha, MatrA const& a, MatrB const& b,
               T const& beta, MatrC& c)
    {
      gemm (CblasNoTrans, CblasNoTrans, alpha, a, b, beta, c) ;
    }


    // C <- A * B
    template <typename MatrA, typename MatrB, typename MatrC>
    inline
    void gemm (MatrA const& a, MatrB const& b, MatrC& c) {
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
      typedef typename traits::matrix_traits<MatrC>::value_type val_t;
#else
      typedef typename MatrC::value_type val_t;
#endif
      gemm (CblasNoTrans, CblasNoTrans, (val_t) 1, a, b, (val_t) 0, c);
    }



    // C <- alpha * A * B + beta * C
    // C <- alpha * B * A + beta * C
    // A == A^T

    namespace detail {

      template <typename T, typename SymmA, typename MatrB, typename MatrC>
      inline
      void symm (CBLAS_SIDE const side, CBLAS_UPLO const uplo,
                 T const& alpha, SymmA const& a, MatrB const& b,
                 T const& beta, MatrC& c)
      {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<MatrB>::matrix_structure,
          traits::general_t
        >::value));
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<MatrC>::matrix_structure,
          traits::general_t
        >::value));

        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<SymmA>::ordering_type,
          typename traits::matrix_traits<MatrB>::ordering_type
        >::value));
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<SymmA>::ordering_type,
          typename traits::matrix_traits<MatrC>::ordering_type
        >::value));
#endif

        assert (side == CblasLeft || side == CblasRight);
        assert (uplo == CblasUpper || uplo == CblasLower);

        int const m = traits::matrix_size1 (c);
        int const n = traits::matrix_size2 (c);

        assert (side == CblasLeft
                ? m == traits::matrix_size1 (a)
                  && m == traits::matrix_size2 (a)
                : n == traits::matrix_size1 (a)
                  && n == traits::matrix_size2 (a));
        assert (m == traits::matrix_size1 (b)
                && n == traits::matrix_size2 (b));

        CBLAS_ORDER const stor_ord
          = enum_cast<CBLAS_ORDER const>
          (storage_order<
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
            typename traits::matrix_traits<SymmA>::ordering_type
#else
            typename SymmA::orientation_category
#endif
           >::value);

        symm (stor_ord, side, uplo,
              m, n, alpha,
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
              traits::matrix_storage (a),
#else
              traits::matrix_storage_const (a),
#endif
              traits::leading_dimension (a),
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
              traits::matrix_storage (b),
#else
              traits::matrix_storage_const (b),
#endif
              traits::leading_dimension (b),
              beta,
              traits::matrix_storage (c),
              traits::leading_dimension (c));
      }

    } // detail

    // C <- alpha * A * B + beta * C
    // C <- alpha * B * A + beta * C
    // A == A^T
    template <typename T, typename SymmA, typename MatrB, typename MatrC>
    inline
    void symm (CBLAS_SIDE const side, CBLAS_UPLO const uplo,
               T const& alpha, SymmA const& a, MatrB const& b,
               T const& beta, MatrC& c)
    {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<SymmA>::matrix_structure,
        traits::general_t
      >::value));
#endif

      detail::symm (side, uplo, alpha, a, b, beta, c);
    }

    template <typename T, typename SymmA, typename MatrB, typename MatrC>
    inline
    void symm (CBLAS_SIDE const side,
               T const& alpha, SymmA const& a, MatrB const& b,
               T const& beta, MatrC& c)
    {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<SymmA>::matrix_structure,
        traits::symmetric_t
      >::value));
#endif

      CBLAS_UPLO const uplo
        = enum_cast<CBLAS_UPLO const>
        (uplo_triang<
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
          typename traits::matrix_traits<SymmA>::uplo_type
#else
          typename SymmA::packed_category
#endif
         >::value);

      detail::symm (side, uplo, alpha, a, b, beta, c);
    }


#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS

    namespace detail {

      // C <- alpha * A * B + beta * C ;  A == A^T
      struct symm_left {
        template <typename T, typename SymmA, typename MatrB, typename MatrC>
        static void f (T const& alpha, SymmA const& a, MatrB const& b,
                       T const& beta, MatrC& c)
        {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
          BOOST_STATIC_ASSERT((boost::is_same<
            typename traits::matrix_traits<SymmA>::matrix_structure,
            traits::symmetric_t
          >::value));
          BOOST_STATIC_ASSERT((boost::is_same<
            typename traits::matrix_traits<MatrB>::matrix_structure,
            traits::general_t
          >::value));
#endif

          int const m = traits::matrix_size1 (c);
          int const n = traits::matrix_size2 (c);

          assert (m == traits::matrix_size1 (a)
                  && m == traits::matrix_size2 (a));
          assert (m == traits::matrix_size1 (b)
                  && n == traits::matrix_size2 (b));

          CBLAS_ORDER const stor_ord
            = enum_cast<CBLAS_ORDER const>
            (storage_order<
              typename traits::matrix_traits<SymmA>::ordering_type
             >::value);

          CBLAS_UPLO const uplo
            = enum_cast<CBLAS_UPLO const>
            (uplo_triang<
              typename traits::matrix_traits<SymmA>::uplo_type
             >::value);

          symm (stor_ord, CblasLeft, uplo,
                m, n, alpha,
                traits::matrix_storage (a), traits::leading_dimension (a),
                traits::matrix_storage (b), traits::leading_dimension (b),
                beta,
                traits::matrix_storage (c), traits::leading_dimension (c));
        }
      };

      // C <- alpha * A * B + beta * C ;  B == B^T
      struct symm_right {
        template <typename T, typename MatrA, typename SymmB, typename MatrC>
        static void f (T const& alpha, MatrA const& a, SymmB const& b,
                       T const& beta, MatrC& c)
        {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
          BOOST_STATIC_ASSERT((boost::is_same<
            typename traits::matrix_traits<MatrA>::matrix_structure,
            traits::general_t
          >::value));
          BOOST_STATIC_ASSERT((boost::is_same<
            typename traits::matrix_traits<SymmB>::matrix_structure,
            traits::symmetric_t
          >::value));
#endif

          int const m = traits::matrix_size1 (c);
          int const n = traits::matrix_size2 (c);

          assert (n == traits::matrix_size1 (b)
                  && n == traits::matrix_size2 (b));
          assert (m == traits::matrix_size1 (a)
                  && n == traits::matrix_size2 (a));

          CBLAS_ORDER const stor_ord
            = enum_cast<CBLAS_ORDER const>
            (storage_order<
              typename traits::matrix_traits<SymmB>::ordering_type
             >::value);

          CBLAS_UPLO const uplo
            = enum_cast<CBLAS_UPLO const>
            (uplo_triang<
              typename traits::matrix_traits<SymmB>::uplo_type
             >::value);

          symm (stor_ord, CblasRight, uplo,
                m, n, alpha,
                traits::matrix_storage (b), traits::leading_dimension (b),
                traits::matrix_storage (a), traits::leading_dimension (a),
                beta,
                traits::matrix_storage (c), traits::leading_dimension (c));
        }
      };

    } // detail

    // C <- alpha * A * B + beta * C
    // C <- alpha * B * A + beta * C
    // A == A^T
    template <typename T, typename MatrA, typename MatrB, typename MatrC>
    inline
    void symm (T const& alpha, MatrA const& a, MatrB const& b,
               T const& beta, MatrC& c)
    {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<MatrC>::matrix_structure,
        traits::general_t
      >::value));

      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<MatrA>::ordering_type,
        typename traits::matrix_traits<MatrB>::ordering_type
      >::value));
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<MatrA>::ordering_type,
        typename traits::matrix_traits<MatrC>::ordering_type
      >::value));
#endif

      typedef typename
        boost::mpl::if_c<
          boost::is_same<
            typename traits::matrix_traits<MatrA>::matrix_structure,
            traits::symmetric_t
          >::value,
          detail::symm_left,
          detail::symm_right
        >::type functor;

      functor::f (alpha, a, b, beta, c);
    }

    // C <- A * B
    // C <- B * A
    template <typename MatrA, typename MatrB, typename MatrC>
    inline
    void symm (MatrA const& a, MatrB const& b, MatrC& c) {
      typedef typename traits::matrix_traits<MatrC>::value_type val_t;
      symm ((val_t) 1, a, b, (val_t) 0, c);
    }

#endif // BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS



    // C <- alpha * A * B + beta * C
    // C <- alpha * B * A + beta * C
    // A == A^H

    namespace detail {

      template <typename T, typename HermA, typename MatrB, typename MatrC>
      inline
      void hemm (CBLAS_SIDE const side, CBLAS_UPLO const uplo,
                 T const& alpha, HermA const& a, MatrB const& b,
                 T const& beta, MatrC& c)
      {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<MatrB>::matrix_structure,
          traits::general_t
        >::value));
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<MatrC>::matrix_structure,
          traits::general_t
        >::value));

        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<HermA>::ordering_type,
          typename traits::matrix_traits<MatrB>::ordering_type
        >::value));
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<HermA>::ordering_type,
          typename traits::matrix_traits<MatrC>::ordering_type
        >::value));
#endif

        assert (side == CblasLeft || side == CblasRight);
        assert (uplo == CblasUpper || uplo == CblasLower);

        int const m = traits::matrix_size1 (c);
        int const n = traits::matrix_size2 (c);

        assert (side == CblasLeft
                ? m == traits::matrix_size1 (a)
                  && m == traits::matrix_size2 (a)
                : n == traits::matrix_size1 (a)
                  && n == traits::matrix_size2 (a));
        assert (m == traits::matrix_size1 (b)
                && n == traits::matrix_size2 (b));

        CBLAS_ORDER const stor_ord
          = enum_cast<CBLAS_ORDER const>
          (storage_order<
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
            typename traits::matrix_traits<HermA>::ordering_type
#else
            typename HermA::orientation_category
#endif
           >::value);

        hemm (stor_ord, side, uplo,
              m, n, alpha,
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
              traits::matrix_storage (a),
#else
              traits::matrix_storage_const (a),
#endif
              traits::leading_dimension (a),
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
              traits::matrix_storage (b),
#else
              traits::matrix_storage_const (b),
#endif
              traits::leading_dimension (b),
              beta,
              traits::matrix_storage (c),
              traits::leading_dimension (c));
      }

    } // detail

    // C <- alpha * A * B + beta * C
    // C <- alpha * B * A + beta * C
    // A == A^H
    template <typename T, typename HermA, typename MatrB, typename MatrC>
    inline
    void hemm (CBLAS_SIDE const side, CBLAS_UPLO const uplo,
               T const& alpha, HermA const& a, MatrB const& b,
               T const& beta, MatrC& c)
    {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<HermA>::matrix_structure,
        traits::general_t
      >::value));
#endif

      detail::hemm (side, uplo, alpha, a, b, beta, c);
    }

    template <typename T, typename HermA, typename MatrB, typename MatrC>
    inline
    void hemm (CBLAS_SIDE const side,
               T const& alpha, HermA const& a, MatrB const& b,
               T const& beta, MatrC& c)
    {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<HermA>::matrix_structure,
        traits::hermitian_t
      >::value));
#endif

      CBLAS_UPLO const uplo
        = enum_cast<CBLAS_UPLO const>
        (uplo_triang<
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
          typename traits::matrix_traits<HermA>::uplo_type
#else
          typename HermA::packed_category
#endif
         >::value);

      detail::hemm (side, uplo, alpha, a, b, beta, c);
    }


#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS

    namespace detail {

      // C <- alpha * A * B + beta * C ;  A == A^H
      struct hemm_left {
        template <typename T, typename HermA, typename MatrB, typename MatrC>
        static void f (T const& alpha, HermA const& a, MatrB const& b,
                       T const& beta, MatrC& c)
        {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
          BOOST_STATIC_ASSERT((boost::is_same<
            typename traits::matrix_traits<HermA>::matrix_structure,
            traits::hermitian_t
          >::value));
          BOOST_STATIC_ASSERT((boost::is_same<
            typename traits::matrix_traits<MatrB>::matrix_structure,
            traits::general_t
          >::value));
#endif

          int const m = traits::matrix_size1 (c);
          int const n = traits::matrix_size2 (c);

          assert (m == traits::matrix_size1 (a)
                  && m == traits::matrix_size2 (a));
          assert (m == traits::matrix_size1 (b)
                  && n == traits::matrix_size2 (b));

          CBLAS_ORDER const stor_ord
            = enum_cast<CBLAS_ORDER const>
            (storage_order<
              typename traits::matrix_traits<HermA>::ordering_type
             >::value);

          CBLAS_UPLO const uplo
            = enum_cast<CBLAS_UPLO const>
            (uplo_triang<
              typename traits::matrix_traits<HermA>::uplo_type
             >::value);

          hemm (stor_ord, CblasLeft, uplo,
                m, n, alpha,
                traits::matrix_storage (a), traits::leading_dimension (a),
                traits::matrix_storage (b), traits::leading_dimension (b),
                beta,
                traits::matrix_storage (c), traits::leading_dimension (c));
        }
      };

      // C <- alpha * A * B + beta * C ;  B == B^H
      struct hemm_right {
        template <typename T, typename MatrA, typename HermB, typename MatrC>
        static void f (T const& alpha, MatrA const& a, HermB const& b,
                       T const& beta, MatrC& c)
        {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
          BOOST_STATIC_ASSERT((boost::is_same<
            typename traits::matrix_traits<MatrA>::matrix_structure,
            traits::general_t
          >::value));
          BOOST_STATIC_ASSERT((boost::is_same<
            typename traits::matrix_traits<HermB>::matrix_structure,
            traits::hermitian_t
          >::value));
#endif

          int const m = traits::matrix_size1 (c);
          int const n = traits::matrix_size2 (c);

          assert (n == traits::matrix_size1 (b)
                  && n == traits::matrix_size2 (b));
          assert (m == traits::matrix_size1 (a)
                  && n == traits::matrix_size2 (a));

          CBLAS_ORDER const stor_ord
            = enum_cast<CBLAS_ORDER const>
            (storage_order<
              typename traits::matrix_traits<HermB>::ordering_type
             >::value);

          CBLAS_UPLO const uplo
            = enum_cast<CBLAS_UPLO const>
            (uplo_triang<
              typename traits::matrix_traits<HermB>::uplo_type
             >::value);

          hemm (stor_ord, CblasRight, uplo,
                m, n, alpha,
                traits::matrix_storage (b), traits::leading_dimension (b),
                traits::matrix_storage (a), traits::leading_dimension (a),
                beta,
                traits::matrix_storage (c), traits::leading_dimension (c));
        }
      };

    }

    template <typename T, typename MatrA, typename MatrB, typename MatrC>
    inline
    void hemm (T const& alpha, MatrA const& a, MatrB const& b,
               T const& beta, MatrC& c)
    {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<MatrC>::matrix_structure,
        traits::general_t
      >::value));

      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<MatrA>::ordering_type,
        typename traits::matrix_traits<MatrB>::ordering_type
      >::value));
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<MatrA>::ordering_type,
        typename traits::matrix_traits<MatrC>::ordering_type
      >::value));
#endif

      typedef typename
        boost::mpl::if_c<
          boost::is_same<
            typename traits::matrix_traits<MatrA>::matrix_structure,
            traits::hermitian_t
          >::value,
          detail::hemm_left,
          detail::hemm_right
        >::type functor;

      functor::f (alpha, a, b, beta, c);
    }

    // C <- A * B
    // C <- B * A
    template <typename MatrA, typename MatrB, typename MatrC>
    inline
    void hemm (MatrA const& a, MatrB const& b, MatrC& c) {
      typedef typename traits::matrix_traits<MatrC>::value_type val_t;
      hemm ((val_t) 1, a, b, (val_t) 0, c);
    }

#endif // BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS


    // C <- alpha * A * A^T + beta * C
    // C <- alpha * A^T * A + beta * C
    // C == C^T

    namespace detail {

      template <typename T, typename MatrA, typename SymmC>
      inline
      void syrk (CBLAS_UPLO const uplo, CBLAS_TRANSPOSE trans,
                 T const& alpha, MatrA const& a,
                 T const& beta, SymmC& c)
      {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<MatrA>::matrix_structure,
          traits::general_t
        >::value));
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<MatrA>::ordering_type,
          typename traits::matrix_traits<SymmC>::ordering_type
        >::value));
#endif

        assert (uplo == CblasUpper || uplo == CblasLower);
        assert (trans == CblasNoTrans
                || trans == CblasTrans
                || trans == CblasConjTrans);

        int const n = traits::matrix_size1 (c);
        assert (n == traits::matrix_size2 (c));

        int const k = trans == CblasNoTrans
          ? traits::matrix_size2 (a)
          : traits::matrix_size1 (a);
        assert (n == (trans == CblasNoTrans
                      ? traits::matrix_size1 (a)
                      : traits::matrix_size2 (a)));

        CBLAS_ORDER const stor_ord
          = enum_cast<CBLAS_ORDER const>
          (storage_order<
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
            typename traits::matrix_traits<SymmC>::ordering_type
#else
            typename SymmC::orientation_category
#endif
           >::value);

        syrk (stor_ord, uplo, trans,
              n, k, alpha,
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
              traits::matrix_storage (a),
#else
              traits::matrix_storage_const (a),
#endif
              traits::leading_dimension (a),
              beta,
              traits::matrix_storage (c),
              traits::leading_dimension (c));
      }

    } // detail

    // C <- alpha * A * A^T + beta * C
    // C <- alpha * A^T * A + beta * C
    // C == C^T
    template <typename T, typename MatrA, typename SymmC>
    inline
    void syrk (CBLAS_UPLO const uplo, CBLAS_TRANSPOSE trans,
               T const& alpha, MatrA const& a,
               T const& beta, SymmC& c)
    {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<SymmC>::matrix_structure,
        traits::general_t
      >::value));
#endif

      detail::syrk (uplo, trans, alpha, a, beta, c);
    }

    template <typename T, typename MatrA, typename SymmC>
    inline
    void syrk (CBLAS_TRANSPOSE trans,
               T const& alpha, MatrA const& a,
               T const& beta, SymmC& c)
    {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<SymmC>::matrix_structure,
        traits::symmetric_t
      >::value));
#endif

      CBLAS_UPLO const uplo
        = enum_cast<CBLAS_UPLO const>
        (uplo_triang<
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
          typename traits::matrix_traits<SymmC>::uplo_type
#else
          typename SymmC::packed_category
#endif
         >::value);

      detail::syrk (uplo, trans, alpha, a, beta, c);
    }

    // C <- A * A^T + C
    // C <- A^T * A + C
    template <typename MatrA, typename SymmC>
    inline
    void syrk (CBLAS_TRANSPOSE trans, MatrA const& a, SymmC& c) {
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
      typedef typename traits::matrix_traits<SymmC>::value_type val_t;
#else
      typedef typename SymmC::value_type val_t;
#endif
      syrk (trans, (val_t) 1, a, (val_t) 0, c);
    }


    // C <- alpha * A * B^T + conj(alpha) * B * A^T + beta * C
    // C <- alpha * A^T * B + conj(alpha) * B^T * A + beta * C
    // C == C^T

    namespace detail {

      template <typename T, typename MatrA, typename MatrB, typename SymmC>
      inline
      void syr2k (CBLAS_UPLO const uplo, CBLAS_TRANSPOSE trans,
                  T const& alpha, MatrA const& a, MatrB const& b,
                  T const& beta, SymmC& c)
      {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<MatrA>::matrix_structure,
          traits::general_t
        >::value));
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<MatrB>::matrix_structure,
          traits::general_t
        >::value));
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<MatrA>::ordering_type,
          typename traits::matrix_traits<SymmC>::ordering_type
        >::value));
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<MatrB>::ordering_type,
          typename traits::matrix_traits<SymmC>::ordering_type
        >::value));
#endif

        assert (uplo == CblasUpper || uplo == CblasLower);
        assert (trans == CblasNoTrans
                || trans == CblasTrans
                || trans == CblasConjTrans);

        int const n = traits::matrix_size1 (c);
        assert (n == traits::matrix_size2 (c));

        int const k = trans == CblasNoTrans
          ? traits::matrix_size2 (a)
          : traits::matrix_size1 (a);
        assert (k == (trans == CblasNoTrans
                      ? traits::matrix_size2 (b)
                      : traits::matrix_size1 (b)));
        assert (n == (trans == CblasNoTrans
                      ? traits::matrix_size1 (a)
                      : traits::matrix_size2 (a)));
        assert (n == (trans == CblasNoTrans
                      ? traits::matrix_size1 (b)
                      : traits::matrix_size2 (b)));

        CBLAS_ORDER const stor_ord
          = enum_cast<CBLAS_ORDER const>
          (storage_order<
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
            typename traits::matrix_traits<SymmC>::ordering_type
#else
            typename SymmC::orientation_category
#endif
           >::value);

        syr2k (stor_ord, uplo, trans,
               n, k, alpha,
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
               traits::matrix_storage (a),
#else
               traits::matrix_storage_const (a),
#endif
               traits::leading_dimension (a),
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
               traits::matrix_storage (b),
#else
               traits::matrix_storage_const (b),
#endif
               traits::leading_dimension (b),
               beta,
               traits::matrix_storage (c),
               traits::leading_dimension (c));
      }

    } // detail

    // C <- alpha * A * B^T + conj(alpha) * B * A^T + beta * C
    // C <- alpha * A^T * B + conj(alpha) * B^T * A + beta * C
    // C == C^T
    template <typename T, typename MatrA, typename MatrB, typename SymmC>
    inline
    void syr2k (CBLAS_UPLO const uplo, CBLAS_TRANSPOSE trans,
               T const& alpha, MatrA const& a, MatrB const& b,
               T const& beta, SymmC& c)
    {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<SymmC>::matrix_structure,
        traits::general_t
      >::value));
#endif

      detail::syr2k (uplo, trans, alpha, a, b, beta, c);
    }

    template <typename T, typename MatrA, typename MatrB, typename SymmC>
    inline
    void syr2k (CBLAS_TRANSPOSE trans,
               T const& alpha, MatrA const& a, MatrB const& b,
               T const& beta, SymmC& c)
    {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<SymmC>::matrix_structure,
        traits::symmetric_t
      >::value));
#endif

      CBLAS_UPLO const uplo
        = enum_cast<CBLAS_UPLO const>
        (uplo_triang<
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
          typename traits::matrix_traits<SymmC>::uplo_type
#else
          typename SymmC::packed_category
#endif
         >::value);

      detail::syr2k (uplo, trans, alpha, a, b, beta, c);
    }

    // C <- A * B^T + B * A^T + C
    // C <- A^T * B + B^T * A + C
    template <typename MatrA, typename MatrB, typename SymmC>
    inline
    void syr2k (CBLAS_TRANSPOSE trans,
                MatrA const& a, MatrB const& b, SymmC& c)
    {
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
      typedef typename traits::matrix_traits<SymmC>::value_type val_t;
#else
      typedef typename SymmC::value_type val_t;
#endif
      syr2k (trans, (val_t) 1, a, b, (val_t) 0, c);
    }


    // C <- alpha * A * A^H + beta * C
    // C <- alpha * A^H * A + beta * C
    // C == C^H

    namespace detail {

      template <typename T, typename MatrA, typename HermC>
      inline
      void herk (CBLAS_UPLO const uplo, CBLAS_TRANSPOSE trans,
                 T const& alpha, MatrA const& a,
                 T const& beta, HermC& c)
      {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<MatrA>::matrix_structure,
          traits::general_t
        >::value));
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<MatrA>::ordering_type,
          typename traits::matrix_traits<HermC>::ordering_type
        >::value));
#endif

        assert (uplo == CblasUpper || uplo == CblasLower);
        assert (trans == CblasNoTrans || trans == CblasConjTrans);

        int const n = traits::matrix_size1 (c);
        assert (n == traits::matrix_size2 (c));

        int const k = trans == CblasNoTrans
          ? traits::matrix_size2 (a)
          : traits::matrix_size1 (a);
        assert (n == (trans == CblasNoTrans
                      ? traits::matrix_size1 (a)
                      : traits::matrix_size2 (a)));

        CBLAS_ORDER const stor_ord
          = enum_cast<CBLAS_ORDER const>
          (storage_order<
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
            typename traits::matrix_traits<HermC>::ordering_type
#else
            typename HermC::orientation_category
#endif
           >::value);

        herk (stor_ord, uplo, trans,
              n, k, alpha,
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
              traits::matrix_storage (a),
#else
              traits::matrix_storage_const (a),
#endif
              traits::leading_dimension (a),
              beta,
              traits::matrix_storage (c),
              traits::leading_dimension (c));
      }

    } // detail

    // C <- alpha * A * A^H + beta * C
    // C <- alpha * A^H * A + beta * C
    // C == C^H
    template <typename T, typename MatrA, typename HermC>
    inline
    void herk (CBLAS_UPLO const uplo, CBLAS_TRANSPOSE trans,
               T const& alpha, MatrA const& a,
               T const& beta, HermC& c)
    {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<HermC>::matrix_structure,
        traits::general_t
      >::value));
#endif

      detail::herk (uplo, trans, alpha, a, beta, c);
    }

    template <typename T, typename MatrA, typename HermC>
    inline
    void herk (CBLAS_TRANSPOSE trans,
               T const& alpha, MatrA const& a,
               T const& beta, HermC& c)
    {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<HermC>::matrix_structure,
        traits::hermitian_t
      >::value));
#endif

      CBLAS_UPLO const uplo
        = enum_cast<CBLAS_UPLO const>
        (uplo_triang<
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
          typename traits::matrix_traits<HermC>::uplo_type
#else
          typename HermC::packed_category
#endif
         >::value);

      detail::herk (uplo, trans, alpha, a, beta, c);
    }

    // C <- A * A^H + C
    // C <- A^H * A + C
    template <typename MatrA, typename HermC>
    inline
    void herk (CBLAS_TRANSPOSE trans, MatrA const& a, HermC& c) {
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
      typedef typename traits::matrix_traits<HermC>::value_type val_t;
#else
      typedef typename HermC::value_type val_t;
#endif
      typedef typename traits::type_traits<val_t>::real_type real_t;
      herk (trans, (real_t) 1, a, (real_t) 0, c);
    }


    // C <- alpha * A * B^H + conj(alpha) * B * A^H + beta * C
    // C <- alpha * A^H * B + conj(alpha) * B^H * A + beta * C
    // C == C^H

    namespace detail {

      template <typename T1, typename T2,
                typename MatrA, typename MatrB, typename HermC>
      inline
      void her2k (CBLAS_UPLO const uplo, CBLAS_TRANSPOSE trans,
                  T1 const& alpha, MatrA const& a, MatrB const& b,
                  T2 const& beta, HermC& c)
      {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<MatrA>::matrix_structure,
          traits::general_t
        >::value));
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<MatrB>::matrix_structure,
          traits::general_t
        >::value));
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<MatrA>::ordering_type,
          typename traits::matrix_traits<HermC>::ordering_type
        >::value));
        BOOST_STATIC_ASSERT((boost::is_same<
          typename traits::matrix_traits<MatrB>::ordering_type,
          typename traits::matrix_traits<HermC>::ordering_type
        >::value));
#endif

        assert (uplo == CblasUpper || uplo == CblasLower);
        assert (trans == CblasNoTrans || trans == CblasConjTrans);

        int const n = traits::matrix_size1 (c);
        assert (n == traits::matrix_size2 (c));

        int const k = trans == CblasNoTrans
          ? traits::matrix_size2 (a)
          : traits::matrix_size1 (a);
        assert (k == (trans == CblasNoTrans
                      ? traits::matrix_size2 (b)
                      : traits::matrix_size1 (b)));
        assert (n == (trans == CblasNoTrans
                      ? traits::matrix_size1 (a)
                      : traits::matrix_size2 (a)));
        assert (n == (trans == CblasNoTrans
                      ? traits::matrix_size1 (b)
                      : traits::matrix_size2 (b)));

        CBLAS_ORDER const stor_ord
          = enum_cast<CBLAS_ORDER const>
          (storage_order<
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
            typename traits::matrix_traits<HermC>::ordering_type
#else
            typename HermC::orientation_category
#endif
           >::value);

        her2k (stor_ord, uplo, trans,
               n, k, alpha,
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
               traits::matrix_storage (a),
#else
               traits::matrix_storage_const (a),
#endif
               traits::leading_dimension (a),
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
               traits::matrix_storage (b),
#else
               traits::matrix_storage_const (b),
#endif
               traits::leading_dimension (b),
               beta,
               traits::matrix_storage (c),
               traits::leading_dimension (c));
      }

    } // detail

    // C <- alpha * A * B^H + conj(alpha) * B * A^H + beta * C
    // C <- alpha * A^H * B + conj(alpha) * B^H * A + beta * C
    // C == C^H
    template <typename T1, typename T2,
              typename MatrA, typename MatrB, typename HermC>
    inline
    void her2k (CBLAS_UPLO const uplo, CBLAS_TRANSPOSE trans,
               T1 const& alpha, MatrA const& a, MatrB const& b,
               T2 const& beta, HermC& c)
    {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<HermC>::matrix_structure,
        traits::general_t
      >::value));
#endif

      detail::her2k (uplo, trans, alpha, a, b, beta, c);
    }

    template <typename T1, typename T2,
              typename MatrA, typename MatrB, typename HermC>
    inline
    void her2k (CBLAS_TRANSPOSE trans,
               T1 const& alpha, MatrA const& a, MatrB const& b,
               T2 const& beta, HermC& c)
    {
#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<HermC>::matrix_structure,
        traits::hermitian_t
      >::value));
#endif

      CBLAS_UPLO const uplo
        = enum_cast<CBLAS_UPLO const>
        (uplo_triang<
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
          typename traits::matrix_traits<HermC>::uplo_type
#else
          typename HermC::packed_category
#endif
         >::value);

      detail::her2k (uplo, trans, alpha, a, b, beta, c);
    }

    // C <- A * B^H + B * A^H + C
    // C <- A^H * B + B^H * A + C
    template <typename MatrA, typename MatrB, typename HermC>
    inline
    void her2k (CBLAS_TRANSPOSE trans,
                MatrA const& a, MatrB const& b, HermC& c)
    {
#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
      typedef typename traits::matrix_traits<HermC>::value_type val_t;
#else
      typedef typename HermC::value_type val_t;
#endif
      typedef typename traits::type_traits<val_t>::real_type real_t;
      her2k (trans, (val_t) 1, a, b, (real_t) 0, c);
    }


  } // namespace atlas

}}}

#endif // BOOST_NUMERIC_BINDINGS_CBLAS_LEVEL_3_HPP
