// Copyright 2018 Xiaomi, Inc.  All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <Eigen/Dense>
#include <algorithm>
#include <string>
#include <tuple>
#include <vector>

#include "mace/core/testing/test_benchmark.h"
#include "mace/kernels/gemm.h"
#include "public/gemmlowp.h"

namespace gemmlowp {

template<typename tScalar, MapOrder tOrder>
class Matrix : public MatrixMap<tScalar, tOrder> {
 public:
  typedef MatrixMap<tScalar, tOrder> Map;
  typedef MatrixMap<const tScalar, tOrder> ConstMap;
  typedef typename Map::Scalar Scalar;
  static const MapOrder Order = tOrder;
  using Map::cols_;
  using Map::data_;
  using Map::kOrder;
  using Map::rows_;
  using Map::stride_;

 public:
  Matrix() : Map(nullptr, 0, 0, 0) {}

  Matrix(int rows, int cols) : Map(nullptr, 0, 0, 0) { Resize(rows, cols); }

  Matrix(const Matrix &other) : Map(nullptr, 0, 0, 0) { *this = other; }

  Matrix &operator=(const Matrix &other) {
    Resize(other.rows_, other.cols_);
    std::memcpy(data_, other.data_, size() * sizeof(Scalar));
    return *this;
  }

  friend bool operator==(const Matrix &a, const Matrix &b) {
    return a.rows_ == b.rows_ && a.cols_ == b.cols_ &&
        !std::memcmp(a.data_, b.data_, a.size());
  }

  void Resize(int rows, int cols) {
    rows_ = rows;
    cols_ = cols;
    stride_ = kOrder == gemmlowp::MapOrder::ColMajor ? rows : cols;
    storage.resize(size());
    data_ = storage.data();
  }

  int size() const { return rows_ * cols_; }

  Map &map() { return *static_cast<Map *>(this); }

  ConstMap const_map() const { return ConstMap(data_, rows_, cols_, stride_); }

 protected:
  std::vector<Scalar> storage;
};

template<typename MatrixType>
void MakeZero(MatrixType *m) {
  for (int c = 0; c < m->cols(); c++) {
    for (int r = 0; r < m->rows(); r++) {
      (*m)(r, c) = 128;
    }
  }
}

}  // namespace gemmlowp

namespace mace {
namespace kernels {
namespace test {

// Test the speed of different access order of a NHWC buffer

namespace {

// Matmul with (m, k) x (k, n)
void MatmulBenchmark_Mace(int iters, int m, int k, int n) {
  mace::testing::StopTiming();
  std::vector<float> lhs(m * k);
  std::vector<float> rhs(k * n);
  std::vector<float> result(m * n);
  // warm up
  Gemm(lhs.data(), rhs.data(), 1, m, k, n, result.data());
  mace::testing::StartTiming();
  while (iters--) {
    Gemm(lhs.data(), rhs.data(), 1, m, k, n, result.data());
  }
}

void MatmulBenchmark_Eigen(int iters, int m, int k, int n) {
  mace::testing::StopTiming();
  Eigen::MatrixXd lhs = Eigen::MatrixXd::Random(m, k);
  Eigen::MatrixXd rhs = Eigen::MatrixXd::Random(k, n);
  Eigen::MatrixXd result = Eigen::MatrixXd::Zero(m, n);
  // warm up
  result = lhs * rhs;
  mace::testing::StartTiming();
  while (iters--) {
    result = lhs * rhs;
  }
}

void MatmulBenchmark_gemmlowp_uint8(int iters, int rows, int depth, int cols) {
  mace::testing::StopTiming();

  gemmlowp::Matrix<std::uint8_t, gemmlowp::MapOrder::RowMajor> lhs;
  gemmlowp::Matrix<std::uint8_t, gemmlowp::MapOrder::ColMajor> rhs;
  gemmlowp::Matrix<std::uint8_t, gemmlowp::MapOrder::ColMajor> result;
  lhs.Resize(rows, depth);
  rhs.Resize(depth, cols);
  result.Resize(rows, cols);
  gemmlowp::MakeZero(&lhs);
  gemmlowp::MakeZero(&rhs);
  gemmlowp::MakeZero(&result);

  gemmlowp::OutputStageQuantizeDownInt32ByFixedPoint quantize_down_stage;
  quantize_down_stage.result_offset_after_shift = 128;
  quantize_down_stage.result_fixedpoint_multiplier = 1234567890;
  quantize_down_stage.result_shift = 16;
  gemmlowp::OutputStageSaturatingCastToUint8 saturating_cast_stage;
  const auto output_pipeline =
      std::make_tuple(quantize_down_stage, saturating_cast_stage);

  gemmlowp::GemmContext gemm_context;
  gemm_context.set_max_num_threads(4);
  using BitDepthParams = gemmlowp::L8R8WithLhsNonzeroBitDepthParams;

  gemmlowp::GemmWithOutputPipeline<std::uint8_t, std::uint8_t, BitDepthParams>(
      &gemm_context, lhs.const_map(), rhs.const_map(), &result.map(), -128,
      -128, output_pipeline);

  mace::testing::StartTiming();
  while (iters--) {
    gemmlowp::GemmWithOutputPipeline<std::uint8_t, std::uint8_t,
                                     BitDepthParams>(
        &gemm_context, lhs.const_map(), rhs.const_map(), &result.map(), -128,
        -128, output_pipeline);
  }
}

void MatmulBenchmark_gemmlowp_int32(int iters, int rows, int depth, int cols) {
  mace::testing::StopTiming();

  gemmlowp::Matrix<std::uint8_t, gemmlowp::MapOrder::RowMajor> lhs;
  gemmlowp::Matrix<std::uint8_t, gemmlowp::MapOrder::ColMajor> rhs;
  gemmlowp::Matrix<std::int32_t, gemmlowp::MapOrder::ColMajor> result;
  lhs.Resize(rows, depth);
  rhs.Resize(depth, cols);
  result.Resize(rows, cols);
  gemmlowp::MakeZero(&lhs);
  gemmlowp::MakeZero(&rhs);
  gemmlowp::MakeZero(&result);

  const auto output_pipeline = std::make_tuple();

  gemmlowp::GemmContext gemm_context;
  gemm_context.set_max_num_threads(4);
  using BitDepthParams = gemmlowp::L8R8WithLhsNonzeroBitDepthParams;

  gemmlowp::GemmWithOutputPipeline<std::uint8_t, std::int32_t, BitDepthParams>(
      &gemm_context, lhs.const_map(), rhs.const_map(), &result.map(), -128,
      -128, output_pipeline);

  mace::testing::StartTiming();
  while (iters--) {
    gemmlowp::GemmWithOutputPipeline<std::uint8_t, std::int32_t,
                                     BitDepthParams>(
        &gemm_context, lhs.const_map(), rhs.const_map(), &result.map(), -128,
        -128, output_pipeline);
  }
}

}  // namespace

#define MACE_BM_MATMUL_FUNC(M, K, N, FUNC)                         \
  static void MACE_BM_MATMUL_##M##_##K##_##N##_##FUNC(int iters) { \
    const int64_t macc = static_cast<int64_t>(iters) * M * K * N;  \
    const int64_t tot = static_cast<int64_t>(iters) * (M + N) * K; \
    mace::testing::MaccProcessed(macc);                            \
    mace::testing::BytesProcessed(tot * sizeof(float));            \
    MatmulBenchmark_##FUNC(iters, M, K, N);                        \
  }                                                                \
  MACE_BENCHMARK(MACE_BM_MATMUL_##M##_##K##_##N##_##FUNC)

#define MACE_BM_MATMUL(M, K, N)                 \
  MACE_BM_MATMUL_FUNC(M, K, N, Mace);           \
  MACE_BM_MATMUL_FUNC(M, K, N, Eigen);          \
  MACE_BM_MATMUL_FUNC(M, K, N, gemmlowp_uint8); \
  MACE_BM_MATMUL_FUNC(M, K, N, gemmlowp_int32);

// Embedding size 384
MACE_BM_MATMUL(7, 384, 384);
MACE_BM_MATMUL(7, 384, 1536);
MACE_BM_MATMUL(7, 1536, 384);

MACE_BM_MATMUL(15, 384, 384);
MACE_BM_MATMUL(15, 384, 1536);
MACE_BM_MATMUL(15, 1536, 384);

MACE_BM_MATMUL(1, 384, 384);
MACE_BM_MATMUL(1, 384, 1536);
MACE_BM_MATMUL(1, 1536, 384);
MACE_BM_MATMUL(1, 384, 44678);

// Embedding size 128
MACE_BM_MATMUL(1, 128, 1536);
MACE_BM_MATMUL(1, 128, 44678);

}  // namespace test
}  // namespace kernels
}  // namespace mace


#ifndef PDESOLVERC1
#define PDESOLVERC1

#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>

#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/banded.hpp>

#include <boost/format.hpp>

#include <boost/numeric/ublas/matrix_proxy.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/numeric/ublas/banded.hpp>
#include <boost/numeric/ublas/operation.hpp>
#include <boost/numeric/ublas/lu.hpp>

#include <boost/numeric/bindings/detail/property_map.hpp>
#include <boost/numeric/bindings/views.hpp>
#include <boost/numeric/bindings/ublas/vector_expression.hpp>
#include <boost/numeric/bindings/ublas/matrix.hpp>
#include <boost/numeric/bindings/ublas/vector.hpp>
#include <boost/numeric/bindings/lapack.hpp>

using std::cout;
using std::endl;
using std::flush;
namespace ubs = boost::numeric::ublas;
using boost::format;

typedef std::pair<double,double> dpair;
typedef ubs::vector<double> dvector_type;
typedef ubs::vector<std::complex<double> > cvector_type;

template <typename TParams> class CNSolver {
  private:
    // parameters
    std::ofstream &outFile;
    std::vector<ubs::vector<double> > vars;
    ubs::matrix<double,ubs::column_major> lpM,
                                          unM;
    std::vector<ubs::matrix<double> > auxLPMP;
    std::vector<ubs::vector<double> > auxDL, auxDU, auxD;

    std::vector<ubs::vector<double> > vars_DUMP;
    std::vector<ubs::vector<double> > auxDL_DUMP, auxDU_DUMP, auxD_DUMP;

    std::vector<ubs::vector<double> > RK_k1, RK_k2, RK_k3, RK_k4, RK_k0;

    // do not touch it
    void defaults();
    void performRKstep(std::vector<ubs::vector<double> > &, double = 0);
    void step(std::vector<ubs::vector<double> > &, double &);

  public:
    CNSolver(std::ofstream &);
    void runSolver();
    void initialize();

  protected:
    double stopAfter;
    double timeStep;
    double logEvery;
    double reacLength;
    int    reacN;
    double reacStep;
    int    nVars;

    virtual void setInitials(std::vector<ubs::vector<double> >&) = 0;

    TParams pp;
    virtual void setParams(TParams &) = 0;

    dvector_type spec_var;
    dvector_type TMPR_vec_un,
      TMPR_vec_t1, TMPR_vec_t2, TMPR_vec_t3, TMPR_vec_t4;
    cvector_type TMPR_cpx_un,
      TMPR_cpx_t1, TMPR_cpx_t2, TMPR_cpx_t3, TMPR_cpx_t4,
      TMPR_cpx_t5, TMPR_cpx_t6, TMPR_cpx_t7, TMPR_cpx_t8,
      TMPR_cpx_t9, TMPR_cpx_t10, TMPR_cpx_t11, TMPR_cpx_t12,
      TMPR_cpx_t13, TMPR_cpx_t14, TMPR_cpx_t15, TMPR_cpx_t16;
    virtual void reaction(std::vector<ubs::vector<double> > &, double = 0) = 0;

};

dvector_type vector_realize(const cvector_type&);
cvector_type vector_complexize(const dvector_type&);

#include "solver_impl.hpp"

#endif


#include <blitz/tinymat.h>
#include <blitz/vector.h>
#include <blitz/benchext.h>
#include <blitz/rand-uniform.h>

#ifdef BZ_HAVE_COMPLEX

BZ_USING_NAMESPACE(blitz)

#if defined(BZ_FORTRAN_SYMBOLS_WITH_TRAILING_UNDERSCORES)
  #define qcdf  qcdf_
  #define qcdf2 qcdf2_
#elif defined( BZ_FORTRAN_SYMBOLS_CAPS)
  #define qcdf  QCDF
  #define qcdf2 QCDF2
#endif

extern "C" {
  void qcdf(const void* M, void* res, const void* src, const int& N,
    const int& iters);
  void qcdf2(const void* M, void* res, const void* src, const int& N,
    const int& iters);
}

int QCDBlitzVersion(BenchmarkExt<int>& bench);
int QCDBlitzTunedVersion(BenchmarkExt<int>& bench);
int QCDFortran77Version(BenchmarkExt<int>& bench);
int QCDFortran77TunedVersion(BenchmarkExt<int>& bench);

void initializeRandomDouble(double* data, int numElements);

int main()
{
    cout << "Blitz++ QCD Benchmark" << endl
         << "Working... (this may take a while)  ";
    cout.flush();

    BenchmarkExt<int> bench("Lattice QCD Benchmark", 4);

    bench.setRateDescription("Millions of operations/s");
    bench.beginBenchmarking();

    QCDBlitzVersion(bench);
    QCDBlitzTunedVersion(bench);
    QCDFortran77Version(bench);
    QCDFortran77TunedVersion(bench);

    bench.endBenchmarking();

    bench.saveMatlabGraph("qcd.m");

    cout << "Done." << endl;

    return 0;
}

int QCDBlitzVersion(BenchmarkExt<int>& bench)
{
    typedef TinyMatrix<complex<double>, 3, 2> spinor;
    typedef TinyMatrix<complex<double>, 3, 3> SU3Gauge;

    bench.beginImplementation("Blitz++");

    while (!bench.doneImplementationBenchmark())
    {
        int length = bench.getParameter();
        int iters = (int)bench.getIterations();

cout << "length = " << length << endl;

        Vector<spinor> res(length), src(length);
        Vector<SU3Gauge> M(length);

        initializeRandomDouble((double*)src.data(),
            length * sizeof(spinor) / sizeof(double));
        initializeRandomDouble((double*)M.data(),
            length * sizeof(SU3Gauge) / sizeof(double));

        bench.start();
        for (long i=0; i < iters; ++i)
        {
            for (int i=0; i < length; ++i)
                res[i] = product(M[i], src[i]);
        }
        bench.stop();

        // Time overhead
        bench.startOverhead();
        for (long i=0; i < iters; ++i)
        {
        }
        bench.stopOverhead();
    }

    bench.endImplementation();

    return 0;
}

    typedef TinyMatrix<complex<double>, 3, 2> spinor;
    typedef TinyMatrix<complex<double>, 3, 3> gaugeFieldElement;

    struct latticeUnit {
        spinor one;
        gaugeFieldElement gauge;
        spinor two;
    };

int QCDBlitzTunedVersion(BenchmarkExt<int>& bench)
{
    bench.beginImplementation("Blitz++ (tuned)");

    while (!bench.doneImplementationBenchmark())
    {
        int length = bench.getParameter();
        int iters = (int)bench.getIterations();

        Vector<latticeUnit> lattice(length);

        initializeRandomDouble((double*)lattice.data(),
            length * sizeof(latticeUnit) / sizeof(double));

        bench.start();
        for (long i=0; i < iters; ++i)
        {
            for (int i=0; i < length; ++i)
                lattice[i].two = product(lattice[i].gauge, lattice[i].one);
        }
        bench.stop();

        // Time overhead
        bench.startOverhead();
        for (long i=0; i < iters; ++i)
        {
        }
        bench.stopOverhead();
    }

    bench.endImplementation();

    return 0;
}

int QCDFortran77Version(BenchmarkExt<int>& bench)
{
    // Use Blitz++ library only to allocate space for the
    // arrays.
    typedef TinyMatrix<complex<double>, 3, 2> spinor;
    typedef TinyMatrix<complex<double>, 3, 3> SU3Gauge;

    bench.beginImplementation("Fortran 77");

    while (!bench.doneImplementationBenchmark())
    {
        int length = bench.getParameter();
        int iters = (int)bench.getIterations();

        Vector<spinor> res(length), src(length);
        Vector<SU3Gauge> M(length);

        initializeRandomDouble((double*)src.data(),
            length * sizeof(spinor) / sizeof(double));
        initializeRandomDouble((double*)M.data(),
            length * sizeof(SU3Gauge) / sizeof(double));

        bench.start();
        qcdf(M.data(), res.data(), src.data(), length, iters);
        bench.stop();

        // Time overhead
        bench.startOverhead();
        for (long i=0; i < iters; ++i)
        {
        }
        bench.stopOverhead();
    }

    bench.endImplementation();

    return 0;
}

int QCDFortran77TunedVersion(BenchmarkExt<int>& bench)
{
    // Use Blitz++ library only to allocate space for the
    // arrays.
    typedef TinyMatrix<complex<double>, 3, 2> spinor;
    typedef TinyMatrix<complex<double>, 3, 3> SU3Gauge;

    bench.beginImplementation("Fortran 77 Hand-tuned");

    while (!bench.doneImplementationBenchmark())
    {
        int length = bench.getParameter();
        int iters = (int)bench.getIterations();

        Vector<spinor> res(length), src(length);
        Vector<SU3Gauge> M(length);

        initializeRandomDouble((double*)src.data(),
            length * sizeof(spinor) / sizeof(double));
        initializeRandomDouble((double*)M.data(),
            length * sizeof(SU3Gauge) / sizeof(double));

        bench.start();
        qcdf2(M.data(), res.data(), src.data(), length, iters);
        bench.stop();

        // Time overhead
        bench.startOverhead();
        for (long i=0; i < iters; ++i)
        {
        }
        bench.stopOverhead();
    }

    bench.endImplementation();

    return 0;
}

void initializeRandomDouble(double* data, int numElements)
{
    // This is a temporary kludge until I implement random complex
    // numbers.

    static Random<Uniform> rnd;

    for (int i=0; i < numElements; ++i)
        data[i] = rnd.random();
}

#else // BZ_HAVE_COMPLEX

#include <iostream.h>

int main()
{
    cout << "This benchmark requires <complex> from the ISO/ANSI C++ standard."
         << endl;
    return 0;
}

#endif // BZ_HAVE_COMPLEX


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


/*
Part of the Fluid Corpus Manipulation Project (http://www.flucoma.org/)
Copyright 2017-2019 University of Huddersfield.
Licensed under the BSD-3 License.
See license.md file in the project root for full license information.
This project has received funding from the European Research Council (ERC)
under the European Union’s Horizon 2020 research and innovation programme
(grant agreement No 725899).
*/
#pragma once

#include "FluidTensor.hpp"
#include <Eigen/Core>
#include <complex>

namespace fluid {

using RealMatrix = FluidTensor<double, 2>;
using ComplexMatrix = FluidTensor<std::complex<double>, 2>;
using RealVector = FluidTensor<double, 1>;
using ComplexVector = FluidTensor<std::complex<double>, 1>;

using RealMatrixView = FluidTensorView<double, 2>;
using ComplexMatrixView = FluidTensorView<std::complex<double>, 2>;
using RealVectorView = FluidTensorView<double, 1>;
using ComplexVectorView = FluidTensorView<std::complex<double>, 1>;

using ArrayXXidx = Eigen::Array<fluid::index, Eigen::Dynamic, Eigen::Dynamic>;
using ArrayXidx = Eigen::Array<fluid::index, Eigen::Dynamic, 1>;
} // namespace fluid


// Ben Martin
// October 11, 2005

// This currently works only on undirected graphs...
// Graph must model Adjacency Graph, Incidence Graph, VertexListGraph

#ifndef BOOST_GRAPH_SPECTRAL_EMBEDDING_LAYOUT_HPP
#define BOOST_GRAPH_SPECTRAL_EMBEDDING_LAYOUT_HPP

#include <boost/graph/graph_traits.hpp>
#include <boost/graph/named_function_params.hpp>
#include <boost/graph/properties.hpp>
#include <vecLib/clapack.h>
//#include "f2c.h"
//#include "cblas.h"
//#include "clapack.h"
#include <utility> // for pair
//#include <boost/graph/point2d.hpp>
//#include <boost/graph/point3d.hpp>
#include <iostream.h>

typedef long int integer;
typedef double doublereal;

namespace boost {

  template <typename Graph, typename PositionMap>
  void spectral_embedding_layout(Graph& g, PositionMap pos_map) {

    typedef typename property_map<Graph, vertex_index_t>::const_type IndexMap;
    typedef typename Graph::vertex_iterator VertexIterator;
    typedef typename boost::graph_traits<Graph>::adjacency_iterator AdjacencyIterator;

    IndexMap index_map = get(vertex_index, g);

    VertexIterator v, vs, ve;
    using std::pair;
    std::pair<VertexIterator, VertexIterator> p;
    p = vertices(g);
    vs = p.first;
    ve = p.second;

    integer N = num_vertices(g);

    doublereal *A;
    //    A = (doublereal*)malloc( N*N*sizeof(double) );
    A = new double[N * N];

    int i;

    AdjacencyIterator a, as, ae;
    std::pair<AdjacencyIterator, AdjacencyIterator> ap;

    for (i = 0; i < N * N; i++)
      A[i] = 0;
    i = 0;
    for (v = vs; v != ve; ++v) {
      A[(index_map[*v] * N) + index_map[*v]] = (doublereal)out_degree(*v, g);
      ap = adjacent_vertices(*v, g);
      as = ap.first;
      ae = ap.second;
      for (a = as; a != ae; ++a) {
	A[index_map[*v] + index_map[*a]*N] = (doublereal)(-1);
	A[index_map[*v]*N + index_map[*a]] = (doublereal)(-1);
      }

      pos_map[*v][0] = i;
      pos_map[*v][1] = i;

      i++;
    }

    /*
    cout << "index_map = " << endl;
    for (v = vs; v != ve; ++v) {
      cout << index_map[*v] << " ";
    }
    cout << endl;
    cout << "A = " << endl;
    for (i = 0; i < N; i++)
    {
      for (int j = 0; j < N; j++)
      {
        cout << A[i*N + j] << " ";
      }
        cout << endl;
    }
    */

    // Set up the 8 billion parameters for CLAPACK
    char   JOBZ   = 'v';
    char   RANGE  = 'i';
    char   UPLO   = 'l'; // arbitrary at the moment...
    // N is defined
    // A is defined
    integer    LDA    = N;
    doublereal VL     = 0; // not needed
    doublereal VU     = 0; // not needed
    integer    IL     = 2;// 1; // first NON-ZERO eigenvalue
    integer    IU     = 3;// N; // 4 for the 3d case
    //    char   dlamch_cmach = 's';

    doublereal ABSTOL = 0.01;// dlamch_(&dlamch_cmach); // most likely SEVERE OVERKILL
    integer    M = (IU - IL + 1); // N; // should come back 2 or 3 (for 2d, 3d respectively)
    doublereal *W = new double[N]; // the eigenvalues
    doublereal *Z = new double[N*M]; // the eigenvectors
    integer    LDZ = N;
    integer    *ISUPPZ = new integer[2*M];// (integer*)0; // = new int[2*(IU-IL+1)]; ??? Not needed?
    doublereal *WORK = new double[26*N]; // should this be allocated?
    integer    LWORK  = 26*N; // -1
    integer    *IWORK = new integer[10*N];
    integer    LIWORK = 10*N; // -1
    integer    INFO;

    //    dstegr_()
    dsyevr_(&JOBZ, &RANGE, &UPLO, &N, A, &LDA, &VL, &VU, &IL, &IU, &ABSTOL,
            &M, W, Z, &LDZ, ISUPPZ, WORK, &LWORK, IWORK, &LIWORK, &INFO);

    /*
    cout << "N = " << N << endl;
    cout << "IL = " << INFO << endl;
    cout << "IU = " << INFO << endl;

    cout << "INFO = " << INFO << endl;
    cout << "M = " << M << endl;

    cout << "W = ";
    for (i = 0; i < M; i++)
      cout << W[i] << " ";
    cout << endl;
    for (i = 0; i < M; i++)
      for (int j = 0; j < N; j++)
      {
        cout << Z[i*N + j] << "";
        cout << endl;
      }
    */

    // Set position based on eigenvectors
    i = 0;
    for (v = vs; v != ve; ++v)
    {
      pos_map[*v][0] = Z[0*LDZ + i];
      i++;
    }
    i = 0;
    for (v = vs; v != ve; ++v)
    {
      pos_map[*v][1] = Z[1*LDZ + i];
      i++;
    }

  } // end spectral_embedding_layout()

} // end namespace boost

#endif // BOOST_GRAPH_SPECTRAL_EMBEDDING_LAYOUT_HPP


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

#ifndef TRACKINGCONTROLIPOPT_HH
#define TRACKINGCONTROLIPOPT_HH

#include <memory>
#include <algorithm>
#include <cstdlib>

#include <boost/timer.hpp>

#include "IpTNLP.hpp"
#include "variationalproblemutil.hh"

struct FirstLess
{
  template <class T>
  bool operator()(std::pair<T,T> const& p) const { return p.first < p.second; }
};

/**
 * Ipopt interface implementation for optimal control problems. This
 * adapts Galerkin operator representations from variational
 * functionals to Ipopt.  Since Ipopt requires not only value and
 * derivatives of the Lagrange functional, the primal variables are
 * split into two quasi-identical groups: those that enter the cost
 * functional (u,y) and those that enter the constraints (v,z).
 */
template <class GOP>
class TrackingControlInterface: public Ipopt::TNLP
{
  typedef GOP Gop;
  typedef typename Gop::RT RT;
  typedef typename Gop::VariableSet VarSet;
  typedef typename VarSet::Grid Grid;
  typedef typename Gop::Functional Functional;

  typedef Ipopt::Index Index;
  typedef Ipopt::TNLP::IndexStyleEnum IndexStyleEnum;
  typedef Ipopt::SolverReturn SolverReturn;
  typedef Ipopt::Number Number;

  enum { U=0, Y, V, Z, LAMBDA, END};
  typedef int AssemblyParts;

public:
  TrackingControlInterface(VarSet const& varSet_,
                           Functional& f_):
    varSet(varSet_),
    gop(varSet),
    f(f_),
    nu(varSet.dimension(U,U+1)),
    ny(varSet.dimension(Y,Y+1)),
    nl(varSet.dimension(LAMBDA,LAMBDA+1)),
    n(nu+ny)
  {}

  /** overload this method to return the number of variables
   *  and constraints, and the number of non-zeros in the jacobian and
   *  the hessian. The index_style parameter lets you specify C or Fortran
   *  style indexing for the sparse matrix iRow and jCol parameters.
   *  C_STYLE is 0-based, and FORTRAN_STYLE is 1-based.
   */
  virtual bool get_nlp_info(Index& n, Index& m, Index& nnz_jac_g,
                            Index& nnz_h_lag, IndexStyleEnum& index_style)
  {
    std::cout << "nu=" << nu << " ny=" << ny << " nl=" << nl << '\n';

    n = this->n;
    m = nl;

    nnz_jac_g = gop.nnz(LAMBDA,LAMBDA+1,V,Z+1,false);

    int nnz1 = gop.nnz(U,Y+1,U,Y+1,true);
    int nnz2 = gop.nnz(V,Z+1,V,Z+1,true);
    int nnzMax = std::max(nnz1,nnz2);

    std::vector<int> rows(nnzMax), cols(nnzMax);
    std::vector<RT> data(nnzMax);
    std::vector<std::pair<int,int> > entries(nnz1+nnz2);

    gop.toTriplet(U,Y+1,U,Y+1,rows.begin(),cols.begin(),data.begin(),true);
    for (int i=0; i<nnz1; ++i)
      entries[i] = std::make_pair(rows[i],cols[i]);
    gop.toTriplet(V,Z+1,V,Z+1,rows.begin(),cols.begin(),data.begin(),true);
    for (int i=0; i<nnz2; ++i)
      entries[i+nnz1] = std::make_pair(rows[i],cols[i]);

    // remove upper diagonal entries
    entries.erase(std::remove_if(entries.begin(),entries.end(),FirstLess()),entries.end());
    // remove duplicate entries
    std::sort(entries.begin(),entries.end());
    entries.erase(std::unique(entries.begin(),entries.end()),entries.end());

    nnz_h_lag = entries.size();

    index_style = C_STYLE;

    return true;
  }

  /** overload this method to return the information about the bound
   *  on the variables and constraints. The value that indicates
   *  that a bound does not exist is specified in the parameters
   *  nlp_lower_bound_inf and nlp_upper_bound_inf.  By default,
   *  nlp_lower_bound_inf is -1e19 and nlp_upper_bound_inf is
   *  1e19. (see TNLPAdapter) */
  virtual bool get_bounds_info(Index n, Number* x_l, Number* x_u,
                               Index m, Number* g_l, Number* g_u)
  {
    assert(n == ny+nu);
    assert(m == nl);

    f.boxConstraints(x_l+nu,x_u+nu,x_l,x_u);

    std::fill_n(g_l,nl,0);
    std::fill_n(g_u,nl,0);

    return true;
  }

  /** overload this method to return the starting point. The bools
   *  init_x and init_lambda are both inputs and outputs. As inputs,
   *  they indicate whether or not the algorithm wants you to
   *  initialize x and lambda respectively. If, for some reason, the
   *  algorithm wants you to initialize these and you cannot, set
   *  the respective bool to false.
   */
  virtual bool get_starting_point(Index n, bool init_x, Number* x,
                                  bool init_z, Number* z_L, Number* z_U,
                                  Index m, bool init_lambda,
                                  Number* lambda)
  {
    if (init_x) {
      std::fill_n(x,n,0);
      for (size_t i=0; i<nu; ++i)
        x[i] = static_cast<Number>(std::rand())/RAND_MAX;
    }
    if (init_z) {
      std::fill_n(z_L,n,1);
      std::fill_n(z_U,n,1);
    }
    if (init_lambda) {
      std::fill_n(lambda,m,1);
    }

    return true;
  }

  /** overload this method to return the value of the objective function */
  virtual bool eval_f(Index n, const Number* x, bool new_x,
                      Number& obj_value)
  {
    std::vector<Number> lambda(nl,0);
    assemble(new_x,x,true,&lambda[0],GOP::VALUE);

    obj_value = gop.functional();

    return true;
  }

  /** overload this method to return the vector of the gradient of
   *  the objective w.r.t. x */
  virtual bool eval_grad_f(Index n, const Number* x, bool new_x,
                           Number* grad_f)
  {
    std::vector<Number> lambda(nl,0);
    assemble(new_x,x,true,&lambda[0],GOP::RHS);

    ToSequence<U,LAMBDA>::call(gop,grad_f);

    return true;
  }

  /** overload this method to return the vector of constraint values */
  virtual bool eval_g(Index n, const Number* x, bool new_x,
                      Index m, Number* g)
  {
    assemble(new_x,x,false,0,GOP::RHS);

    ToSequence<LAMBDA,END>::call(gop,g);

    return true;
  }

  /** overload this method to return the jacobian of the
   *  constraints. The vectors iRow and jCol only need to be set
   *  once. The first call is used to set the structure only (iRow
   *  and jCol will be non-NULL, and values will be NULL) For
   *  subsequent calls, iRow and jCol will be NULL. */
  virtual bool eval_jac_g(Index n, const Number* x, bool new_x,
                          Index m, Index nele_jac, Index* iRow,
                          Index *jCol, Number* values)
  {
    if (iRow != 0) {
      // provide sparsity structure
      std::vector<RT> data(nele_jac);
      typename std::vector<RT>::iterator v = data.begin();
      gop.toTriplet(LAMBDA,END,U,LAMBDA,iRow,jCol,v,false);
    } else {
      assert(x);
      assert(values);

      assemble(new_x,x,false,0,GOP::MATRIX);

      std::vector<Index> idx(nele_jac);
      typename std::vector<Index>::iterator i = idx.begin(), j = idx.begin();
      gop.toTriplet(LAMBDA,END,U,LAMBDA,i,j,values,false);
    }

    return true;
  }

  /** overload this method to return the hessian of the
   *  lagrangian. The vectors iRow and jCol only need to be set once
   *  (during the first call). The first call is used to set the
   *  structure only (iRow and jCol will be non-NULL, and values
   *  will be NULL) For subsequent calls, iRow and jCol will be
   *  NULL. This matrix is symmetric - specify the lower diagonal
   *  only.  A default implementation is provided, in case the user
   *  wants to se quasi-Newton approximations to estimate the second
   *  derivatives and doesn't not neet to implement this method. */
  virtual bool eval_h(Index n, const Number* x, bool new_x,
                      Number obj_factor, Index m, const Number* lambda,
                      bool new_lambda, Index nele_hess,
                      Index* iRow, Index* jCol, Number* values)
  {
    int nnz = gop.nnz(U,LAMBDA,U,LAMBDA,true);
    assert(nnz==nele_hess);

    if (values) {
      if (x || lambda) {
        // if (lambda) {
        //   std::cerr << "set lambda: ";
        //   std::copy(lambda,lambda+m,std::ostream_iterator<Number>(std::cerr," "));
        //   std::cerr << '\n';
        // }

        assemble(new_x,x,new_lambda,lambda,GOP::MATRIX,obj_factor);
      } else
        std::cerr << "eval_h called without x or lambda\n";

      std::vector<int> unused(nnz);
      std::vector<int>::iterator ri = unused.begin(), ci = unused.begin();
      gop.toTriplet(U,LAMBDA,U,LAMBDA,ri,ci,values,true);
    } else {
      std::vector<RT> unused(nnz);
      gop.toTriplet(U,LAMBDA,U,LAMBDA,iRow,jCol,unused.begin(),true);
    }

    return true;
  }

  /** This method is called when the algorithm is complete so the TNLP can store/write the solution */
  virtual void finalize_solution(SolverReturn status,
                                 Index n, const Number* x, const Number* z_L, const Number* z_U,
                                 Index m, const Number* g, const Number* lambda,
                                 Number obj_value)
  {
    int nnz = gop.nnz(U,END,U,END,false);
    std::vector<size_t> ri(nnz), ci(nnz);
    std::vector<double> di(nnz);
    gop.toTriplet(U,END,U,END,ri.begin(),ci.begin(),di.begin(),false);

    std::ofstream out("result.m");
    out << "function [A] = result()\n"
        << "ri = [\n";
    std::copy(ri.begin(),ri.end(),std::ostream_iterator<size_t>(out,"\n"));
    out << "];\nci = [\n";
    std::copy(ci.begin(),ci.end(),std::ostream_iterator<size_t>(out,"\n"));
    out << "];\ndi = [\n";
    std::copy(di.begin(),di.end(),std::ostream_iterator<double>(out,"\n"));
    out << "];\n"
        << "A = spconvert([ri+1 ci+1 di]);\n";

    std::vector<RT> data(n+nl,0);
    std::copy(x,x+n,data.begin());
    if (lambda)
      std::copy(lambda,lambda+nl,data.begin()+n);
    f.vars.read(data.begin());
    writeVTKFile(varSet,f.vars,"solution");
  }

  // /** Intermediate Callback method for the user.  Providing dummy
  //  *  default implementation.  For details see IntermediateCallBack
  //  *  in IpNLP.hpp. */
  // virtual bool intermediate_callback(AlgorithmMode mode,
  //                                    Index iter, Number obj_value,
  //                                    Number inf_pr, Number inf_du,
  //                                    Number mu, Number d_norm,
  //                                    Number regularization_size,
  //                                    Number alpha_du, Number alpha_pr,
  //                                    Index ls_trials,
  //                                    const IpoptData* ip_data,
  //                                    IpoptCalculatedQuantities* ip_cq);

private:
  void assemble(bool new_x, Number const* x, bool new_lambda, Number const* lambda, AssemblyParts ap, double sigma = 1.0)
  {
    if (new_x || new_lambda || (sigma!=f.ipoptSigma)) {

      std::vector<RT> data(n+nl);
      f.vars.write(data.begin());

      if (new_x) {
        assert(x);
        std::copy(x,x+n,data.begin());
        // std::cout << "u = " << x[0] << '\n';

      }

      if (new_lambda) {
        assert(lambda);
        std::copy(lambda,lambda+nl,data.begin()+n);
      }

      f.vars.read(data.begin());
      f.ipoptSigma = sigma;

      // it's more efficient to simultaneously assemble functional/rhs/matrix
      boost::timer timer;
      gop.assemble(f);

      // std::cout << "\nAssembled (sigma=" << sigma << "):\nu=";
      // std::copy(data.begin(),data.begin()+nu,std::ostream_iterator<RT>(std::cout," "));
      // std::cout << "\ny="; std::copy(data.begin()+nu,data.begin()+n,std::ostream_iterator<RT>(std::cout," "));
      // std::cout << "\nl="; std::copy(data.begin()+n,data.begin()+n+nl,std::ostream_iterator<RT>(std::cout," "));
      // toSequence<0,3>(gop,data.begin());
      // std::cout << "\nLagrange gradient:\ndu="; std::copy(data.begin(),data.begin()+nu,std::ostream_iterator<RT>(std::cout," "));
      // std::cout << "\ndy="; std::copy(data.begin()+nu,data.begin()+n,std::ostream_iterator<RT>(std::cout," "));
      // std::cout << "\ndl="; std::copy(data.begin()+n,data.begin()+n+nl,std::ostream_iterator<RT>(std::cout," "));
      // std::cout << "\n";

      //std::cout << "assembly time: " << timer.elapsed() << "s\n";
      // std::cout << "As. u=[";
      // std::cout.precision(2);
      // std::copy(x+ny,x+ny+nu,std::ostream_iterator<Number>(std::cout,","));
      // std::cout << "]\n";
    }
  }

  VarSet const& varSet;
  Gop gop;
  Functional& f;
  int nu, ny, nl, n;
};

#endif


/*
 *
 * 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


// -------------------------------------------------------------------------------------------------
//                              Copyright 2016 - NumScale SAS
//
//                   Distributed under the Boost Software License, Version 1.0.
//                        See accompanying file LICENSE.txt or copy at
//                            http://www.boost.org/LICENSE_1_0.txt
// -------------------------------------------------------------------------------------------------

#include <simd_bench.hpp>
#include <boost/simd/function/simd/genmask.hpp>

namespace nsb = ns::bench;
namespace bs =  boost::simd;
struct gen
{
  template<class T> T operator()(const T & a) const
  {
    return bs::genmask(a);
  }
};
DEFINE_SCALAR_BENCH(scalar_genmask, gen());

DEFINE_BENCH_MAIN()
{
  nsb::for_each<scalar_genmask, NS_BENCH_IEEE_TYPES>(-10, 10);
}


#pragma once
#include <vector>
#include <istream>
#include <string>
#include <algorithm>
#include <Eigen/Dense>
#include <set>

using namespace Eigen;

std::vector<std::string> input_csv(std::istream& stream);

MatrixXd readMatrix(std::istream& stream, int rows, int cols);

void stream_network_data(std::vector<MatrixXd>& weights, std::vector<VectorXd>& biases, std::set<std::string>& labels, std::istream& stream);


#pragma once

// Switching to Blitz Based Arrays
#include <blitz/array.h>
#include <fftw3.h>
#include <omp.h>
#include <string.h>
#include <algorithm>
#include <cmath>
#include <complex>
#include <fstream>
#include <iostream>
#include <vector>

// It should be ok at populate namespace with complex
using std::complex;

namespace NDarray {

using namespace blitz;

// FFT Libraries complex Float
void fftshift(Array<complex<float>, 3> &temp);
void fftshift(Array<complex<float>, 4> &temp);
void fft(Array<complex<float>, 3> &temp);
void fft(Array<complex<float>, 4> &temp);
void ifft(Array<complex<float>, 3> &temp);
void ifft(Array<complex<float>, 4> &temp);

void fft(Array<complex<float>, 3> &temp, int);
void ifft(Array<complex<float>, 3> &temp, int);
void fft3(Array<complex<float>, 3> &temp, int, int, bool);

void gaussian_filter(Array<float, 2> &, int);
void gaussian_filter(Array<complex<float>, 2> &, int);
complex<float> conj_sum(Array<complex<float>, 3> P, Array<complex<float>, 3> R);
void gaussian_blur(Array<complex<float>, 3> &In, float sigX, float sigY, float sigZ);
void gaussian_blur(Array<float, 3> &In, float sigX, float sigY, float sigZ);

inline void endian_swap(int &x) {
  x = (x << 24 & 0xFF000000) | (x << 8 & 0x00FF0000) | (x >> 8 & 0x0000FF00) |
      (x >> 24 & 0x000000FF);
}

template <typename T, int N>
void ArrayRead(blitz::Array<T, N> &temp, const char *name) {
  FILE *fid;
  if ((fid = fopen(name, "r")) == NULL) {
    cout << "Array:Can't Open " << name << endl;
    cout << "Exiting" << endl;
    exit(1);
  } else {
    int j;
    if ((j = fread(temp.data(), sizeof(T), temp.numElements(), fid)) !=
        (int)(temp.numElements())) {
      cout << "Array3:Not enough data: only read " << j << "points of"
           << temp.numElements() << endl;
      exit(1);
    }
    fclose(fid);
  }
}

template <typename T, const int N_rank>
void ArrayWriteMagAppend(Array<complex<T>, N_rank> &temp, const char *name) {
  ofstream ofs(name, ios_base::binary | ios_base::app);

  for (typename Array<complex<T>, N_rank>::iterator miter = temp.begin();
       miter != temp.end(); miter++) {
    T val = abs(*miter);
    ofs.write((char *)&val, sizeof(T));
  }
}

template <typename T, const int N_rank>
void ArrayWriteAppend(Array<T, N_rank> &temp, const char *name) {
  ofstream ofs(name, ios_base::binary | ios_base::app);

  for (typename Array<T, N_rank>::iterator miter = temp.begin();
       miter != temp.end(); miter++) {
    T val = *miter;
    ofs.write((char *)&val, sizeof(T));
  }
}

template <typename T, const int N_rank>
void ArrayWriteAppendAsComplexFloat(Array<T, N_rank> &temp, const char *name) {
  ofstream ofs(name, ios_base::binary | ios_base::app);

  for (typename Array<T, N_rank>::iterator miter = temp.begin();
       miter != temp.end(); miter++) {
    complex<float> val = *miter;
    ofs.write((char *)&val, sizeof(complex<float>));
  }
}

template <typename T>
void ArrayWriteAppendZeros(int number, const char *name) {
  ofstream ofs(name, ios_base::binary | ios_base::app);

  for (int i = 0; i < number; i++) {
    T val = (T)0.0;  // Zero is zero for all types!
    ofs.write((char *)&val, sizeof(T));
  }
}

template <typename T, const int N_rank>
void ArrayWrite(Array<T, N_rank> &temp, const char *name) {
  remove(name);  // This speeds things up considerably
  ofstream ofs(name, ios_base::binary);

  for (typename Array<T, N_rank>::iterator miter = temp.begin();
       miter != temp.end(); miter++) {
    T val = *miter;
    ofs.write((char *)&val, sizeof(T));
  }
}

template <typename T, const int N_rank>
void ArrayWriteMag(Array<complex<T>, N_rank> &temp, const char *name) {
  remove(name);  // This speeds things up considerably
  ofstream ofs(name, ios_base::binary);
  for (typename Array<complex<T>, N_rank>::iterator miter = temp.begin();
       miter != temp.end(); miter++) {
    T val = abs(*miter);
    ofs.write((char *)&val, sizeof(T));
  }
}

template <typename T, const int N_rank>
double ArrayEnergy(Array<complex<T>, N_rank> &temp) {
  double EE = 0;
  for (typename Array<complex<T>, N_rank>::iterator miter = temp.begin();
       miter != temp.end(); miter++) {
    EE += (double)(norm(*miter));
  }
  return (EE);
}

template <typename T, const int N_rank, const int M_rank>
double ArrayEnergy(Array<Array<complex<T>, N_rank>, M_rank> &temp) {
  double EE = 0;
  for (typename Array<Array<complex<T>, N_rank>, M_rank>::iterator miter =
           temp.begin();
       miter != temp.end(); miter++) {
    EE += ArrayEnergy(*miter);
  }
  return (EE);
}

template <typename T, const int N_rank>
void ArrayWritePhase(Array<complex<T>, N_rank> &temp, const char *name) {
  ofstream ofs(name, ios_base::binary);

  for (typename Array<complex<T>, N_rank>::iterator miter = temp.begin();
       miter != temp.end(); miter++) {
    T val = arg(*miter);
    ofs.write((char *)&val, sizeof(T));
  }
}

template <typename T, const int N_rank>
void ArrayWritePhaseAppend(Array<complex<T>, N_rank> &temp, const char *name) {
  ofstream ofs(name, ios_base::binary | ios_base::app);

  for (typename Array<complex<T>, N_rank>::iterator miter = temp.begin();
       miter != temp.end(); miter++) {
    T val = arg(*miter);
    ofs.write((char *)&val, sizeof(T));
  }
}

template <typename T, const int N_rank, const int M_rank>
void WriteCFL(Array<Array<T, N_rank>, M_rank> &temp, const char *name,
              int pad_dim1 = 0) {
  // Create names for header and binary
  char name_hdr[2048];
  char name_bin[2048];

  sprintf(name_hdr, "%s.hdr", name);
  sprintf(name_bin, "%s.cfl", name);

  // Now determine the size of the ND array
  TinyVector<int, M_rank> Dim1 = temp.shape();

  // Loop over all sub-arrays to get the max
  typename Array<Array<T, N_rank>, M_rank>::iterator miter = temp.begin();

  TinyVector<int, N_rank> Dim2 = (*miter).shape();
  for (; miter != temp.end(); miter++) {
    TinyVector<int, N_rank> Dim2_temp = (*miter).shape();
    for (int i = 0; i < N_rank; i++) {
      Dim2(i) = max(Dim2(i), Dim2_temp(i));
    }
  }

  // Combine the Size to get the total size
  Array<int, 1> Dim(N_rank + M_rank + pad_dim1);
  int count = 0;
  for (int i = 0; i < pad_dim1; i++) {
    Dim(count) = 1;
    count++;
  }
  for (int i = 0; i < N_rank; i++) {
    Dim(count) = Dim2(i);
    count++;
  }
  for (int i = 0; i < M_rank; i++) {
    Dim(count) = Dim1(i);
    count++;
  }

  // Debug info (temp)
  cout << "File Name = " << name_bin << endl;
  cout << "Header Name = " << name_hdr << endl;
  cout << "Output Size = " << Dim << endl;

  // Write the header
  FILE *fid;
  fid = fopen(name_hdr, "w");
  fprintf(fid, "# Dimensions\n");
  for (int i = 0; i < M_rank + N_rank; i++) {
    fprintf(fid, "%d ", Dim(i));
  }
  if ((N_rank + M_rank) < 5) {
    for (int i = 0; i < (5 - (M_rank + N_rank)); i++) {
      fprintf(fid, "%d ", 1);
    }
  }
  fclose(fid);

  // Write the binary data
  remove(name_bin);
  int inner_size = product(Dim2);
  for (typename Array<Array<T, N_rank>, M_rank>::iterator miter = temp.begin();
       miter != temp.end(); miter++) {
    ArrayWriteAppendAsComplexFloat((*miter), name_bin);

    // Bart doesn't support container sizes. Just pad with zeros
    if ((*miter).numElements() < inner_size) {
      ArrayWriteAppendZeros<complex<float> >(
          inner_size - (*miter).numElements(), name_bin);
    }
  }
}

template <typename T, const int N_rank>
void WriteCFL(Array<T, N_rank> &temp, const char *name) {
  // Create names for header and binary
  char name_hdr[2048];
  char name_bin[2048];

  sprintf(name_hdr, "%s.hdr", name);
  sprintf(name_bin, "%s.cfl", name);

  // Now determine the size of the ND array
  TinyVector<int, N_rank> Dim = temp.shape();

  // Debug info (temp)
  cout << "File Name = " << name_bin << endl;
  cout << "Header Name = " << name_hdr << endl;
  cout << "Output Size = " << Dim << endl;

  // Write the header
  FILE *fid;
  fid = fopen(name_hdr, "w");
  fprintf(fid, "# Dimensions\n");
  for (int i = 0; i < N_rank; i++) {
    fprintf(fid, "%d ", Dim(i));
  }
  if ((N_rank) < 5) {
    for (int i = 0; i < (5 - (N_rank)); i++) {
      fprintf(fid, "%d ", 1);
    }
  }
  fclose(fid);

  // Write the binary data
  remove(name_bin);
  ArrayWriteAppendAsComplexFloat(temp, name_bin);
}

template <typename T, const int N_rank, const int M_rank>
void WriteCFL_triplet(Array<Array<T, N_rank>, M_rank> &temp,
                      Array<Array<T, N_rank>, M_rank> &temp2,
                      Array<Array<T, N_rank>, M_rank> &temp3,
                      const char *name) {
  // Create names for header and binary
  char name_hdr[2048];
  char name_bin[2048];

  sprintf(name_hdr, "%s.hdr", name);
  sprintf(name_bin, "%s.cfl", name);

  // Now determine the size of the ND array
  TinyVector<int, M_rank> Dim1 = temp.shape();

  // Loop over all sub-arrays to get the max
  typename Array<Array<T, N_rank>, M_rank>::iterator miter = temp.begin();

  TinyVector<int, N_rank> Dim2 = (*miter).shape();
  for (; miter != temp.end(); miter++) {
    TinyVector<int, N_rank> Dim2_temp = (*miter).shape();
    for (int i = 0; i < N_rank; i++) {
      Dim2(i) = max(Dim2(i), Dim2_temp(i));
    }
  }

  // Combine the Size to get the total size
  TinyVector<int, N_rank + M_rank> Dim;
  for (int i = 0; i < N_rank; i++) {
    Dim(i) = Dim2(i);
  }
  for (int i = 0; i < M_rank; i++) {
    Dim(i + N_rank) = Dim1(i);
  }

  TinyVector<int, N_rank + M_rank + 1> DimTriple;
  DimTriple(0) = 3;
  for (int i = 0; i < (N_rank + M_rank); i++) {
    DimTriple(i + 1) = Dim(i);
  }

  // Debug info (temp)
  cout << "File Name = " << name_bin << endl;
  cout << "Header Name = " << name_hdr << endl;
  cout << "Output Size = " << Dim << endl;

  // Write the header
  FILE *fid;
  fid = fopen(name_hdr, "w");
  fprintf(fid, "# Dimensions\n");
  for (int i = 0; i < M_rank + N_rank + 1; i++) {
    fprintf(fid, "%d ", DimTriple(i));
  }
  if ((N_rank + M_rank + 1) < 5) {
    for (int i = 0; i < (5 - (M_rank + N_rank + 1)); i++) {
      fprintf(fid, "%d ", 1);
    }
  }
  fclose(fid);

  // Write the binary data
  remove(name_bin);
  int inner_size = product(Dim2);
  ofstream ofs(name_bin, ios_base::binary | ios_base::app);

  // Three input arrays
  typename Array<Array<T, N_rank>, M_rank>::iterator miter1 = temp.begin();
  typename Array<Array<T, N_rank>, M_rank>::iterator miter2 = temp2.begin();
  typename Array<Array<T, N_rank>, M_rank>::iterator miter3 = temp3.begin();
  for (; (miter1 != temp.end()); miter1++, miter2++, miter3++) {
    // Pointers to the the inner array
    typename Array<T, N_rank>::iterator niter1 = (*miter1).begin();
    typename Array<T, N_rank>::iterator niter2 = (*miter2).begin();
    typename Array<T, N_rank>::iterator niter3 = (*miter3).begin();

    for (; (niter1 != (*miter1).end()); niter1++, niter2++, niter3++) {
      {
        complex<float> val = *niter1;
        ofs.write((char *)&val, sizeof(complex<float>));
      }
      {
        complex<float> val = *niter2;
        ofs.write((char *)&val, sizeof(complex<float>));
      }
      {
        complex<float> val = *niter3;
        ofs.write((char *)&val, sizeof(complex<float>));
      }
    }

    // Bart doesn't support container sizes. Just pad with zeros
    if ((*miter1).numElements() < inner_size) {
      ArrayWriteAppendZeros<complex<float> >(
          3 * (inner_size - (*miter1).numElements()), name_bin);
    }
  }
}

template <typename T>
Array<Array<T, 3>, 1> Alloc4DContainer(int x, int y, int z, int t) {
  Array<Array<T, 3>, 1> temp;
  temp.setStorage(ColumnMajorArray<1>());
  temp.resize(t);

  for (typename Array<Array<T, 3>, 1>::iterator miter = temp.begin();
       miter != temp.end(); miter++) {
    (*miter).setStorage(ColumnMajorArray<3>());
    (*miter).resize(x, y, z);
    (*miter) = (T)0;
  }
  return (temp);
}

template <typename T>
Array<Array<T, 3>, 3> Alloc6DContainer(int x, int y, int z, int d1, int d2,
                                       int d3) {
  Array<Array<T, 3>, 3> temp;
  temp.setStorage(ColumnMajorArray<3>());
  temp.resize(d1, d2, d3);

  for (typename Array<Array<T, 3>, 3>::iterator miter = temp.begin();
       miter != temp.end(); miter++) {
    (*miter).setStorage(ColumnMajorArray<3>());
    (*miter).resize(x, y, z);
    (*miter) = (T)0;
  }
  return (temp);
}

template <typename T>
Array<Array<T, 3>, 2> Alloc5DContainer(int x, int y, int z, int d1, int d2) {
  Array<Array<T, 3>, 2> temp;
  temp.setStorage(ColumnMajorArray<2>());
  temp.resize(d1, d2);

  for (int i = 0; i < d1; i++) {
    for (int j = 0; j < d2; j++) {
      temp(i, j).setStorage(ColumnMajorArray<3>());
      temp(i, j).resize(x, y, z);
      temp(i, j) = (T)0;
    }
  }

  /* This leads to errors for no real reason
  for( typename Array< Array<T,3>,2>::iterator miter=temp.begin();   miter
  !=temp.end(); miter++){
          (*miter).setStorage(ColumnMajorArray<3>());
          (*miter).resize(x,y,z);
          (*miter)= (T )0;
  }
  */
  return (temp);
}

double Dmax(const Array<Array<double, 2>, 1> &A);
double Dmin(const Array<Array<double, 2>, 1> &A);

void nested_workaround(long index, int *N, int *idx, int total);

}  // namespace NDarray