#ifndef SUNDIALS_CVODE_INTERFACE_HPP
#define SUNDIALS_CVODE_INTERFACE_HPP

#include <iostream>
#include <algorithm>
#include <limits>
#include <type_traits>
#include <vector>
#include <functional>
#include <memory>

#include <cassert>

#include <cvode/cvode.h>                  // prototypes for CVODE fcts., consts.
#include <nvector/nvector_serial.h>       // access to serial N_Vector
#include <sunmatrix/sunmatrix_dense.h>    // access to dense SUNMatrix
#include <sunlinsol/sunlinsol_dense.h>    // access to dense SUNLinearSolver
#include <sunmatrix/sunmatrix_band.h>     // access to band SUNMatrix
#include <sunlinsol/sunlinsol_band.h>     // access to band SUNLinearSolver
#include <sunlinsol/sunlinsol_spgmr.h>    // access to SPGMR SUNLinearSolver
#include <sunlinsol/sunlinsol_spfgmr.h>   // access to SPGMR SUNLinearSolver
#include <sunlinsol/sunlinsol_spbcgs.h>   // access to SPBCGS SUNLinearSolver
#include <sunlinsol/sunlinsol_sptfqmr.h>  // access to SPTFQMR SUNLinearSolver
#include <sundials/sundials_types.h>      // defs. of realtype, sunindextype

#include <fmt/format.h>

#include <boost/optional.hpp>

#include "sundials/cvode/types.hpp"
#include "sundials/cvode/client.hpp"

namespace SUNDIALS {

  namespace CVODE {

    /*
       ---------------------------------------------------------------------------
       CVODE inetrafce
       Implementation: strategy pattern
       Strategies:
       - Direct methods:
       * Dense matrix
       * Band matrix
       - Iterative methods:
       * SPGMR   = Scaled Preconditioned Generalized Minimal Residual
       * SPFGMR  = Scaled Preconditioned Flexible Generalized Minimal
       * SPBCGS  = Scaled Preconditioned Bi-Conjugate Gradient Stable
       * SPTFQMR = Scaled Preconditioned Transpose-Free Quasi-Minimal Residual
       ---------------------------------------------------------------------------
    */

    // ------------ CVODE strategy interface
    class IStrategy {

    protected:

      void* memory;
      Client* client;
      SUNLinearSolver linear_solver;

    private:

      Types::LinearMultisptepMethod linear_multi_step_meth;
      Types::TimeStepcontrol time_step_ctrl;
      Types::SolverControl solver_ctrl;
      bool is_initialised;
      bool must_be_reinitialised;

      virtual int SetLinearSolver() = 0;
      virtual int BindUserFunctions() = 0;

    public:

      IStrategy();
      virtual ~IStrategy();
      void SetLinearMultiStepMethod( Types::LinearMultisptepMethod const linear_multi_step_meth_ );
      void SetClientData( void* const user_data_ );
      boost::optional<int> SetInitStepSize( realtype const init_step );
      boost::optional<int> SetMinStepSize( realtype const min_step );
      boost::optional<int> SetMaxStepSize( realtype const max_step );
      boost::optional<int> SetMaxNumSteps( long int const n_steps );
      void SetMaxOrder( int const max_order );
      boost::optional<int> SetMaxWarnMessages( int const max_warn_msgs );
      boost::optional<int> SetStabilityLimitDetection( bool const stab_lib_det_active );
      boost::optional<int> SetMaxErrorTestFailures( int const max_err_test_fails );
      boost::optional<int> SetMaxNonlinearIterations( int const max_nonlin_iters );
      boost::optional<int> SetMaxConvergenceFailures( int const max_conv_fails );
      boost::optional<int> SetNonlinConvergenceCoefficient( realtype const nonlin_conv_coef );
      int Initialise();
      int ReInitialise( realtype const time_init,
                        std::vector<realtype> const& state );
      int ReInitialise( realtype const time_init,
                        realtype const * const state );
      int Integrate( realtype const time_target );
      realtype* Solution() const;
      realtype Solution( sunindextype const i ) const;
      int MainSolverStatistics( Types::MainSolverStatistics& stats ) const;
      int LinearSolverStatistics( Types::LinearSolverStatistics& stats ) const;
      std::string PrintSolverStatistics() const;
    };

    // ------------ Direct solvers interface
    class Direct: public IStrategy {
    public:
      Direct();
      ~Direct();

    protected:
      SUNMatrix matrix;

    private:
      int BindUserFunctions() override;

    };

    // ------------ Dense matrix direct strategy
    class Dense : public Direct {
    public:
      Dense();
      ~Dense();

    private:
      int SetLinearSolver() override;
    };

    // ------------ Band matrix direct strategy
    class Band : public Direct {
    public:
      Band();
      ~Band();
      void SetMatrixBandwidth( Types::Bandwidth const bandwidth_ );
      void SetMatrixBandwidth( sunindextype const upper
                               , sunindextype const lower );

    private:
      Types::Bandwidth bandwidth;
      int SetLinearSolver() override;
    };

    // ------------ Iterative solvers interface
    class Iterative: public IStrategy {
    public:
      Iterative();
      ~Iterative();
      void SetLinearSolverOptions( Types::Preconditioner const preconditioner
                                   , Types::GramSchmidt const gram_schmidt
                                   , int const n_krylov_basis_vectors
                                   , int const max_restarts );

    private:
      int BindUserFunctions() override;

    protected:
      Types::IterativeLinarSolverOptions options;
    };

    // ------------ SPGMR iterative strategy
    class SPGMR : public Iterative {
    public:
      SPGMR();
      ~SPGMR();

    private:
      int SetLinearSolver() override;
    };

    // ------------ SPFGMR iterative strategy
    class SPFGMR : public Iterative {
    public:
      SPFGMR();
      ~SPFGMR();

    private:
      int SetLinearSolver() override;
    };

    // ------------ SPBCGS iterative strategy
    class SPBCGS : public Iterative {
    public:
      SPBCGS();
      ~SPBCGS();

    private:
      int SetLinearSolver() override;
    };

    // ------------ SPTFQMR iterative strategy
    class SPTFQMR : public Iterative {
    public:
      SPTFQMR();
      ~SPTFQMR();

    private:
      int SetLinearSolver() override;
    };

    // CVODE client - context
    class Solver {

    private:

      IStrategy* strategy;

    public:

      explicit Solver()
        : strategy( nullptr )
      {}

      explicit Solver( IStrategy* strategy_ )
        : strategy( strategy_ )
      {}

      void SetStrategy( IStrategy* strategy_ ) {
        strategy = strategy_;
      }

      void SetLinearMultiStepMethod( Types::LinearMultisptepMethod
                                     const linear_multi_step_meth ) {
        strategy->SetLinearMultiStepMethod( linear_multi_step_meth );
      }

      void SetClientData( void* const user_data ) {
        strategy->SetClientData( user_data );
      }

      boost::optional<int> SetInitStepSize( realtype const init_step ) const {
        return strategy->SetInitStepSize( init_step );
      }

      boost::optional<int> SetMinStepSize( realtype const min_step ) const {
        return strategy->SetMinStepSize( min_step );
      }

      boost::optional<int> SetMaxStepSize( realtype const max_step ) const {
        return strategy->SetMaxStepSize( max_step );
      }

      boost::optional<int> SetMaxNumSteps( long int const n_steps ) const {
        return strategy->SetMaxNumSteps( n_steps );
      }

      void SetMaxOrder( int const max_order ) const {
        return strategy->SetMaxOrder( max_order );
      }

      boost::optional<int> SetMaxWarnMessages( int const max_warn_msgs ) const {
        return strategy->SetMaxWarnMessages( max_warn_msgs );
      }

      boost::optional<int> SetStabilityLimitDetection( bool const stab_lib_det_active ) const {
        return strategy->SetStabilityLimitDetection( stab_lib_det_active );
      }

      boost::optional<int> SetMaxErrorTestFailures( int const max_err_test_fails ) const {
        return strategy->SetMaxErrorTestFailures( max_err_test_fails );
      }

      boost::optional<int> SetMaxNonlinearIterations( int const max_nonlin_iters ) const {
        return strategy->SetMaxNonlinearIterations( max_nonlin_iters );
      }

      boost::optional<int> SetMaxConvergenceFailures( int const max_conv_fails ) const {
        return strategy->SetMaxConvergenceFailures( max_conv_fails );
      }

      boost::optional<int> SetNonlinConvergenceCoefficient( realtype const
                                                            nonlin_conv_coef ) const {
        return strategy->SetNonlinConvergenceCoefficient( nonlin_conv_coef );
      }

      int Initialise() const {
        return strategy->Initialise();
      }

      int Integrate( realtype const time_target ) const {
        return strategy->Integrate( time_target );
      }

      realtype* Solution() const {
        return strategy->Solution( );
      }

      int MainSolverStatistics( Types::MainSolverStatistics& stats ) const {
        return strategy->MainSolverStatistics( stats );
      }

      int LinearSolverStatistics( Types::LinearSolverStatistics& stats ) const {
        return strategy->LinearSolverStatistics( stats );
      }

      std::string PrintSolverStatistics() const {
        return strategy->PrintSolverStatistics();
      }
    };

  } // namespace CVODE

} // namespace SUNDIALS

#endif // SUNDIALS_CVODE_INTERFACE_HPP


/*
 * greentea_math_functions.cpp
 *
 *  Created on: Apr 6, 2015
 *      Author: Fabian Tschopp
 */

#include "caffe/common.hpp"
#include "caffe/device.hpp"

#ifdef USE_GREENTEA
#include "caffe/greentea/greentea.hpp"
#include "caffe/greentea/greentea_math_functions.hpp"

#include <boost/math/special_functions/next.hpp>
#include <boost/random.hpp>

#include <cmath>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <limits>
#include <random>
#include <vector>

#include "viennacl/backend/opencl.hpp"
#include "viennacl/ocl/backend.hpp"
#include "viennacl/ocl/context.hpp"
#include "viennacl/ocl/device.hpp"
#include "viennacl/ocl/platform.hpp"

#include "caffe/util/math_functions.hpp"

#if defined(USE_CLBLAS)
  #include <clBLAS.h>       // NOLINT
#elif defined(USE_CLBLAST)
  #include <clblast.h>      // NOLINT
#else
  #include "viennacl/linalg/inner_prod.hpp"
  #include "viennacl/linalg/norm_1.hpp"
  #include "viennacl/linalg/norm_2.hpp"
  #include "viennacl/linalg/norm_inf.hpp"
  #include "viennacl/linalg/prod.hpp"
  #include "viennacl/matrix.hpp"
  #include "viennacl/scalar.hpp"
  #include "viennacl/vector.hpp"
#endif

// ViennaCL 1.5.1 compability fix
#ifndef VIENNACL_MINOR_VERSION
#define VIENNACL_MINOR_VERSION 5
#endif

#if VIENNACL_MINOR_VERSION > 5
#define VCL_ROW_MAJOR , true
#define VCL_COL_MAJOR , false
#else
#define VCL_ROW_MAJOR
#define VCL_COL_MAJOR
#endif

namespace caffe {

void greentea_memset(const int_tp ctx_id, const uint_tp N, const int_tp alpha,
                     cl_mem X, const int_tp offX) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);
  viennacl::ocl::program &program = (Caffe::Get().GetDevice(ctx_id, false))
      ->program();

  // OpenCL Version >= 1.2 approach
  // clEnqueueFillBuffer(ctx.get_queue().handle().get(),
  //  X, &alpha, sizeof(int_tp),
  //                     offX, N, 0, NULL, NULL);
  // OpenCL Version < 1.2 fallback
  typedef float Dtype;
  viennacl::ocl::kernel &oclk_fill = program.get_kernel(
      CL_KERNEL_SELECT("fillbuffer"));
  viennacl::ocl::enqueue(
      oclk_fill(static_cast<int_tp>(N), static_cast<unsigned char>(alpha),
                WrapHandle(X, &ctx), offX),
      ctx.get_queue());
}

// Copy from OpenCL buffer to main memory
void greentea_gpu_memcpy(const uint_tp N, const cl_mem X, const int_tp offX,
                         void *Y, viennacl::ocl::context *ctx) {
  if (Y != NULL) {
    clEnqueueReadBuffer(ctx->get_queue().handle().get(), X, CL_TRUE, offX, N, Y,
                        0,
                        NULL,
                        NULL);
  }
}

// Copy from main memory to OpenCL buffer
void greentea_gpu_memcpy(const uint_tp N, const void* X, cl_mem Y,
                         const int_tp offY, viennacl::ocl::context *ctx) {
  if (X != NULL) {
    clEnqueueWriteBuffer(ctx->get_queue().handle().get(), Y,
    CL_TRUE,
                         offY, N, X, 0, NULL, NULL);
  }
}

// Copy from OpenCL to OpenCL buffer
void greentea_gpu_memcpy(const uint_tp N, const cl_mem X, const int_tp offX,
                         cl_mem Y, const int_tp offY,
                         viennacl::ocl::context *ctx) {
  clEnqueueCopyBuffer(ctx->get_queue().handle().get(), X, Y, offX, offY, N, 0,
  NULL,
                      NULL);
}

template<typename Dtype>
void greentea_copy(const int_tp N, const cl_mem X, const int_tp offX, Dtype* Y,
                   viennacl::ocl::context *ctx) {
  greentea_gpu_memcpy(sizeof(Dtype) * N, X, offX * sizeof(Dtype), Y, ctx);
}

template<typename Dtype>
void greentea_copy(const int_tp N, const Dtype* X, cl_mem Y, const int_tp offY,
                   viennacl::ocl::context *ctx) {
  greentea_gpu_memcpy(sizeof(Dtype) * N, X, Y, offY * sizeof(Dtype), ctx);
}

// Copy from OpenCL buffer to OpenCL buffer
template<typename Dtype>
void greentea_copy(const int_tp N, const cl_mem X, const int_tp offX, cl_mem Y,
                   const int_tp offY, viennacl::ocl::context *ctx) {
  greentea_gpu_memcpy(sizeof(Dtype) * N, X, offX * sizeof(Dtype), Y,
                      offY * sizeof(Dtype), ctx);
}

// Explicit instantiations
template void greentea_copy<int_tp>(const int_tp N, const cl_mem X,
                                    const int_tp offX,
                                    int_tp* Y,
                                    viennacl::ocl::context *ctx);
template void greentea_copy<uint_tp>(const int_tp N, const cl_mem X,
                                     const int_tp offX, uint_tp* Y,
                                     viennacl::ocl::context *ctx);
template void greentea_copy<float>(const int_tp N, const cl_mem X,
                                   const int_tp offX, float* Y,
                                   viennacl::ocl::context *ctx);
template void greentea_copy<double>(const int_tp N, const cl_mem X,
                                    const int_tp offX, double* Y,
                                    viennacl::ocl::context *ctx);
template void greentea_copy<int_tp>(const int_tp N, const int_tp* X, cl_mem Y,
                                    const int_tp offY,
                                    viennacl::ocl::context *ctx);
template void greentea_copy<uint_tp>(const int_tp N, const uint_tp* X, cl_mem Y,
                                     const int_tp offY,
                                     viennacl::ocl::context *ctx);
template void greentea_copy<float>(const int_tp N, const float* X, cl_mem Y,
                                   const int_tp offY,
                                   viennacl::ocl::context *ctx);
template void greentea_copy<double>(const int_tp N, const double* X, cl_mem Y,
                                    const int_tp offY,
                                    viennacl::ocl::context *ctx);
template void greentea_copy<int_tp>(const int_tp N, const cl_mem X,
                                    const int_tp offX, cl_mem Y,
                                    const int_tp offY,
                                    viennacl::ocl::context *ctx);
template void greentea_copy<uint_tp>(const int_tp N, const cl_mem X,
                                     const int_tp offX, cl_mem Y,
                                     const int_tp offY,
                                     viennacl::ocl::context *ctx);
template void greentea_copy<float>(const int_tp N, const cl_mem X,
                                   const int_tp offX, cl_mem Y,
                                   const int_tp offY,
                                   viennacl::ocl::context *ctx);
template void greentea_copy<double>(const int_tp N, const cl_mem X,
                                    const int_tp offX, cl_mem Y,
                                    const int_tp offY,
                                    viennacl::ocl::context *ctx);

template<typename Dtype>
void greentea_gpu_gemm(const int_tp ctx_id, const CBLAS_TRANSPOSE TransA,
                       const CBLAS_TRANSPOSE TransB, const int_tp M,
                       const int_tp N, const int_tp K, const Dtype alpha,
                       const cl_mem A, const int_tp offA, const cl_mem B,
                       const int_tp offB, const Dtype beta, cl_mem C,
                       const int_tp offC) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);

  if (ctx.devices()[0].type() == CL_DEVICE_TYPE_CPU) {
    Dtype* Aptr = reinterpret_cast<Dtype*>(clEnqueueMapBuffer(
        ctx.get_queue().handle().get(), A, true, CL_MAP_READ,
        sizeof(Dtype) * offA, sizeof(Dtype) * M * K, 0, NULL, NULL, NULL));
    Dtype* Bptr = reinterpret_cast<Dtype*>(clEnqueueMapBuffer(
        ctx.get_queue().handle().get(), B, true, CL_MAP_READ,
        sizeof(Dtype) * offB, sizeof(Dtype) * N * K, 0, NULL, NULL, NULL));
    Dtype* Cptr = reinterpret_cast<Dtype*>(clEnqueueMapBuffer(
        ctx.get_queue().handle().get(), C, true, CL_MAP_READ | CL_MAP_WRITE,
        sizeof(Dtype) * offC, sizeof(Dtype) * M * N, 0, NULL, NULL, NULL));

    caffe_cpu_gemm<Dtype>(TransA, TransB, M, N, K, alpha, Aptr, Bptr, beta,
                          Cptr);

    clEnqueueUnmapMemObject(ctx.get_queue().handle().get(), A, Aptr, 0, NULL,
    NULL);
    clEnqueueUnmapMemObject(ctx.get_queue().handle().get(), B, Bptr, 0, NULL,
    NULL);
    clEnqueueUnmapMemObject(ctx.get_queue().handle().get(), C, Cptr, 0, NULL,
    NULL);
  } else {
    int_tp lda = (TransA == CblasNoTrans) ? K : M;
    int_tp ldb = (TransB == CblasNoTrans) ? N : K;
    int_tp ldc = N;

#if defined(USE_CLBLAS)

    clblasOrder clOrder = clblasRowMajor;
    clblasTranspose clTransA =
    (TransA == CblasNoTrans) ? clblasNoTrans : clblasTrans;
    clblasTranspose clTransB =
    (TransB == CblasNoTrans) ? clblasNoTrans : clblasTrans;

    cl_command_queue queue = ctx.get_queue().handle().get();

    if (std::is_same<Dtype, float>::value) {
      GREENTEA_CL_BLAS_CHECK(
          clblasSgemm(clOrder, clTransA, clTransB,
              M, N, K, alpha, A, offA, lda, B, offB, ldb, beta,
              C, offC, ldc, 1, &queue, 0, NULL, NULL));
    } else {
      GREENTEA_CL_BLAS_CHECK(
          clblasDgemm(clOrder, clTransA, clTransB,
              M, N, K, alpha, A, offA, lda, B, offB, ldb, beta,
              C, offC, ldc, 1, &queue, 0, NULL, NULL));
    }

#elif defined(USE_CLBLAST)

    cl_command_queue queue = ctx.get_queue().handle().get();

    clblast::Layout layout = clblast::Layout::kRowMajor;
    clblast::Transpose a_transpose = (TransA == CblasNoTrans) ?
      clblast::Transpose::kNo : clblast::Transpose::kYes;
    clblast::Transpose b_transpose = (TransB == CblasNoTrans) ?
      clblast::Transpose::kNo : clblast::Transpose::kYes;

    if (std::is_same<Dtype, float>::value) {
      GREENTEA_CLBLAST_CHECK(
        clblast::Gemm<float>(
          layout, a_transpose, b_transpose,
          M, N, K,
          alpha,
          A, offA, lda,
          B, offB, ldb,
          beta,
          C, offC, ldc,
          &queue));
    } else {
      GREENTEA_CLBLAST_CHECK(
        clblast::Gemm<double>(
          layout, a_transpose, b_transpose,
          M, N, K,
          alpha,
          A, offA, lda,
          B, offB, ldb,
          beta,
          C, offC, ldc,
          &queue));
    }

#else  // default (ViennaCL)

    typedef typename viennacl::matrix_base<Dtype,
        uint_tp, int_tp>::size_type size_type;
    typedef typename viennacl::matrix_base<Dtype,
        uint_tp, int_tp>::size_type difference_type;

    size_type A_size1 = static_cast<size_type>((TransA == CblasTrans) ? K : M);
    size_type A_size2 = static_cast<size_type>((TransA == CblasTrans) ? M : K);

    size_type B_size1 = static_cast<size_type>((TransB == CblasTrans) ? N : K);
    size_type B_size2 = static_cast<size_type>((TransB == CblasTrans) ? K : N);

    viennacl::matrix_base<Dtype, size_t, ptrdiff_t> matA(A, ctx, A_size1,
                                                       size_type(0),
                                                       difference_type(1),
                                                       size_type(M), A_size2,
                                                       size_type(offA),
                                                       difference_type(1),
                                                       size_type(lda)
                                                       VCL_ROW_MAJOR);

    viennacl::matrix_base<Dtype, size_t, ptrdiff_t> matB(B, ctx, B_size1,
                                                       size_type(0),
                                                       difference_type(1),
                                                       size_type(K), B_size2,
                                                       size_type(offB),
                                                       difference_type(1),
                                                       size_type(ldb)
                                                       VCL_ROW_MAJOR);

    viennacl::matrix_base<Dtype, size_t, ptrdiff_t> matC(C, ctx, size_type(M),
                                                       size_type(0),
                                                       difference_type(1),
                                                       size_type(M),
                                                       size_type(N),
                                                       size_type(offC),
                                                       difference_type(1),
                                                       size_type(ldc)
                                                       VCL_ROW_MAJOR);

    if (TransA == CblasTrans && TransB == CblasTrans)
      viennacl::linalg::prod_impl(viennacl::trans(matA), viennacl::trans(matB),
                                  matC, alpha, beta);
    else if (TransA == CblasTrans && TransB == CblasNoTrans)
      viennacl::linalg::prod_impl(viennacl::trans(matA), matB, matC, alpha,
                                  beta);
    else if (TransA == CblasNoTrans && TransB == CblasTrans)
      viennacl::linalg::prod_impl(matA, viennacl::trans(matB), matC, alpha,
                                  beta);
    else if (TransA == CblasNoTrans && TransB == CblasNoTrans)
      viennacl::linalg::prod_impl(matA, matB, matC, alpha, beta);

#endif  // clBLAS, CLBlast, or default (ViennaCL)
  }
}

template void greentea_gpu_gemm<float>(const int_tp ctx_id,
                                       const CBLAS_TRANSPOSE TransA,
                                       const CBLAS_TRANSPOSE TransB,
                                       const int_tp M, const int_tp N,
                                       const int_tp K, const float alpha,
                                       const cl_mem A, const int_tp offA,
                                       const cl_mem B, const int_tp offB,
                                       const float beta, cl_mem C,
                                       const int_tp offC);
template void greentea_gpu_gemm<double>(const int_tp ctx_id,
                                        const CBLAS_TRANSPOSE TransA,
                                        const CBLAS_TRANSPOSE TransB,
                                        const int_tp M, const int_tp N,
                                        const int_tp K, const double alpha,
                                        const cl_mem A, const int_tp offA,
                                        const cl_mem B, const int_tp offB,
                                        const double beta, cl_mem C,
                                        const int_tp offC);

template<typename Dtype>
void greentea_gpu_gemv(const int_tp ctx_id, const CBLAS_TRANSPOSE TransA,
                       const int_tp M, const int_tp N, const Dtype alpha,
                       const cl_mem A, const int_tp offA, const cl_mem x,
                       const int_tp offx, const Dtype beta, cl_mem y,
                       const int_tp offy) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);

  if (ctx.devices()[0].type() == CL_DEVICE_TYPE_CPU) {
    Dtype* Aptr = reinterpret_cast<Dtype*>(clEnqueueMapBuffer(
        ctx.get_queue().handle().get(), A, true, CL_MAP_READ,
        sizeof(Dtype) * offA, sizeof(Dtype) * M * N, 0, NULL, NULL, NULL));
    Dtype* xptr = reinterpret_cast<Dtype*>(clEnqueueMapBuffer(
        ctx.get_queue().handle().get(), x, true, CL_MAP_READ,
        sizeof(Dtype) * offx, sizeof(Dtype) * (TransA == CblasTrans) ? M : N, 0,
        NULL,
        NULL, NULL));
    Dtype* yptr = reinterpret_cast<Dtype*>(clEnqueueMapBuffer(
        ctx.get_queue().handle().get(), y, true, CL_MAP_READ | CL_MAP_WRITE,
        sizeof(Dtype) * offy, sizeof(Dtype) * (TransA == CblasTrans) ? N : M, 0,
        NULL,
        NULL, NULL));

    caffe_cpu_gemv<Dtype>(TransA, M, N, alpha, Aptr, xptr, beta, yptr);

    clEnqueueUnmapMemObject(ctx.get_queue().handle().get(), A, Aptr, 0, NULL,
    NULL);
    clEnqueueUnmapMemObject(ctx.get_queue().handle().get(), x, xptr, 0, NULL,
    NULL);
    clEnqueueUnmapMemObject(ctx.get_queue().handle().get(), y, yptr, 0, NULL,
    NULL);
  } else {
#if defined(USE_CLBLAS)

    clblasTranspose clTransA =
    (TransA == CblasNoTrans) ? clblasNoTrans : clblasTrans;

    cl_command_queue queue = ctx.get_queue().handle().get();

    if (std::is_same<Dtype, float>::value) {
      GREENTEA_CL_BLAS_CHECK(
          clblasSgemv(clblasRowMajor,
              clTransA, M, N, alpha, A, offA, N, x, offx, 1,
              beta, y, offy, 1, 1, &queue, 0, NULL, NULL));
    } else {
      GREENTEA_CL_BLAS_CHECK(
          clblasDgemv(clblasRowMajor,
              clTransA, M, N, alpha, A, offA, N, x, offx, 1,
              beta, y, offy, 1, 1, &queue, 0, NULL, NULL));
    }

#elif defined(USE_CLBLAST)

    cl_command_queue queue = ctx.get_queue().handle().get();

    clblast::Layout layout = clblast::Layout::kRowMajor;
    clblast::Transpose a_transpose = (TransA == CblasNoTrans) ?
      clblast::Transpose::kNo : clblast::Transpose::kYes;

    const size_t ldA = N;
    const size_t incx = 1;
    const size_t incy = 1;

    if (std::is_same<Dtype, float>::value) {
      GREENTEA_CLBLAST_CHECK(
        clblast::Gemv<float>(
          layout, a_transpose,
          M, N,
          alpha,
          A, offA, ldA,
          x, offx, incx,
          beta,
          y, offy, incy,
          &queue));
    } else {
      GREENTEA_CLBLAST_CHECK(
        clblast::Gemv<double>(
          layout, a_transpose,
          M, N,
          alpha,
          A, offA, ldA,
          x, offx, incx,
          beta,
          y, offy, incy,
          &queue));
    }

#else  // default (ViennaCL)

    typedef typename viennacl::vector_base<Dtype,
        uint_tp, int_tp>::size_type size_type;
    typedef typename viennacl::vector_base<Dtype,
        uint_tp, int_tp>::size_type difference_type;

    viennacl::vector_base<Dtype, size_t, ptrdiff_t> v1(
        x, size_type((TransA == CblasTrans) ? M : N), size_type(offx),
        difference_type(1), ctx);
    viennacl::vector_base<Dtype, size_t, ptrdiff_t> v2(
        y, size_type((TransA == CblasTrans) ? N : M), size_type(offy),
        difference_type(1), ctx);
    viennacl::matrix_base<Dtype, size_t, ptrdiff_t> mat(A, ctx, size_type(M),
                                                      size_type(0),
                                                      difference_type(1),
                                                      size_type(M),
                                                      size_type(N),
                                                      size_type(offA),
                                                      difference_type(1),
                                                      size_type(N)
                                                      VCL_ROW_MAJOR);
    v2 *= beta;
    if (TransA == CblasTrans) {
      v2 += alpha * viennacl::linalg::prod(viennacl::trans(mat), v1);
    } else {
      v2 += alpha * viennacl::linalg::prod(mat, v1);
    }

#endif  // clBLAS, CLBlast, or default (ViennaCL)
  }
}

template void greentea_gpu_gemv<float>(const int_tp ctx_id,
                                       const CBLAS_TRANSPOSE TransA,
                                       const int_tp M, const int_tp N,
                                       const float alpha, const cl_mem A,
                                       const int_tp offA, const cl_mem x,
                                       const int_tp offx, const float beta,
                                       cl_mem y, const int_tp offy);
template void greentea_gpu_gemv<double>(const int_tp ctx_id,
                                        const CBLAS_TRANSPOSE TransA,
                                        const int_tp M, const int_tp N,
                                        const double alpha, const cl_mem A,
                                        const int_tp offA, const cl_mem x,
                                        const int_tp offx, const double beta,
                                        cl_mem y, const int_tp offy);

template<typename Dtype>
void greentea_gpu_axpy(const int_tp ctx_id, const int_tp N, const Dtype alpha,
                       const cl_mem X, const int_tp offX, cl_mem Y,
                       const int_tp offY) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);

  if (ctx.devices()[0].type() == CL_DEVICE_TYPE_CPU) {
    Dtype* Xptr = reinterpret_cast<Dtype*>(clEnqueueMapBuffer(
        ctx.get_queue().handle().get(), X, true, CL_MAP_READ,
        sizeof(Dtype) * offX, sizeof(Dtype) * N, 0, NULL, NULL, NULL));
    Dtype* Yptr = reinterpret_cast<Dtype*>(clEnqueueMapBuffer(
        ctx.get_queue().handle().get(), Y, true, CL_MAP_WRITE,
        sizeof(Dtype) * offY, sizeof(Dtype) * N, 0, NULL, NULL, NULL));

    caffe_axpy<Dtype>(N, alpha, Xptr, Yptr);

    clEnqueueUnmapMemObject(ctx.get_queue().handle().get(), X, Xptr, 0, NULL,
    NULL);
    clEnqueueUnmapMemObject(ctx.get_queue().handle().get(), Y, Yptr, 0, NULL,
    NULL);
  } else {
#if defined(USE_CLBLAS)

    cl_command_queue queue = ctx.get_queue().handle().get();

    if (std::is_same<Dtype, float>::value) {
      GREENTEA_CL_BLAS_CHECK(
          clblasSaxpy(N, alpha, X, offX,
              1, Y, offY, 1, 1, &queue, 0, NULL, NULL));
    } else {
      GREENTEA_CL_BLAS_CHECK(
          clblasDaxpy(N, alpha, X, offX,
              1, Y, offY, 1, 1, &queue, 0, NULL, NULL));
    }

#elif defined(USE_CLBLAST)

    cl_command_queue queue = ctx.get_queue().handle().get();

    const size_t incX = 1;
    const size_t incY = 1;

    if (std::is_same<Dtype, float>::value) {
      GREENTEA_CLBLAST_CHECK(
        clblast::Axpy<float>(
          N,
          alpha,
          X, offX, incX,
          Y, offY, incY,
          &queue));
    } else {
      GREENTEA_CLBLAST_CHECK(
        clblast::Axpy<double>(
          N,
          alpha,
          X, offX, incX,
          Y, offY, incY,
          &queue));
    }

#else  // default (ViennaCL)

    typedef typename viennacl::vector_base<Dtype,
        uint_tp, int_tp>::size_type size_type;
    typedef typename viennacl::vector_base<Dtype,
        uint_tp, int_tp>::size_type difference_type;

    viennacl::vector_base<Dtype, size_t, ptrdiff_t> v1(X, size_type(N),
                                                     size_type(offX),
                                                     difference_type(1), ctx);
    viennacl::vector_base<Dtype, size_t, ptrdiff_t> v2(Y, size_type(N),
                                                     size_type(offY),
                                                     difference_type(1), ctx);
    v2 += alpha * v1;

#endif  // clBLAS, CLBlast, or default (ViennaCL)
  }
}

template void greentea_gpu_axpy<float>(const int_tp ctx_id, const int_tp N,
                                       const float alpha, const cl_mem X,
                                       const int_tp offX, cl_mem Y,
                                       const int_tp offY);
template void greentea_gpu_axpy<double>(const int_tp ctx_id, const int_tp N,
                                        const double alpha, const cl_mem X,
                                        const int_tp offX, cl_mem Y,
                                        const int_tp offY);

template<typename Dtype>
void greentea_gpu_mul(const int_tp ctx_id, const int_tp N, const cl_mem a,
                      const int_tp offa, const cl_mem b, const int_tp offb,
                      cl_mem y, const int_tp offy) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);
  viennacl::ocl::program &program = (Caffe::Get().GetDevice(ctx_id, false))
      ->program();

  viennacl::ocl::kernel &oclk_mul = program.get_kernel(CL_KERNEL_SELECT("mul"));
  viennacl::ocl::enqueue(
      oclk_mul(N, WrapHandle(a, &ctx), offa, WrapHandle(b, &ctx), offb,
               WrapHandle(y, &ctx), offy),
      ctx.get_queue());
}

template void greentea_gpu_mul<float>(const int_tp ctx_id, const int_tp N,
                                      const cl_mem a, const int_tp offa,
                                      const cl_mem b, const int_tp offb,
                                      cl_mem y, const int_tp offy);
template void greentea_gpu_mul<double>(const int_tp ctx_id, const int_tp N,
                                       const cl_mem a, const int_tp offa,
                                       const cl_mem b, const int_tp offb,
                                       cl_mem y, const int_tp offy);

template<typename Dtype>
void greentea_gpu_div(const int_tp ctx_id, const int_tp N, const cl_mem a,
                      const int_tp offa, const cl_mem b, const int_tp offb,
                      cl_mem y, const int_tp offy) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);
  viennacl::ocl::program &program = (Caffe::Get().GetDevice(ctx_id, false))
      ->program();

  viennacl::ocl::kernel &oclk_div = program.get_kernel(CL_KERNEL_SELECT("div"));
  viennacl::ocl::enqueue(
      oclk_div(N, WrapHandle(a, &ctx), offa, WrapHandle(b, &ctx), offb,
               WrapHandle(y, &ctx), offy),
      ctx.get_queue());
}

template void greentea_gpu_div<float>(const int_tp ctx_id, const int_tp N,
                                      const cl_mem a, const int_tp offa,
                                      const cl_mem b, const int_tp offb,
                                      cl_mem y, const int_tp offy);
template void greentea_gpu_div<double>(const int_tp ctx_id, const int_tp N,
                                       const cl_mem a, const int_tp offa,
                                       const cl_mem b, const int_tp offb,
                                       cl_mem y, const int_tp offy);

template<typename Dtype>
void greentea_gpu_scal(const int_tp ctx_id, const int_tp N, const Dtype alpha,
                       cl_mem x, int_tp offx) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);

  if (ctx.devices()[0].type() == CL_DEVICE_TYPE_CPU) {
    Dtype* xptr = reinterpret_cast<Dtype*>(clEnqueueMapBuffer(
        ctx.get_queue().handle().get(), x, true, CL_MAP_READ | CL_MAP_WRITE,
        sizeof(Dtype) * offx, sizeof(Dtype) * N, 0, NULL, NULL, NULL));

    caffe_scal<Dtype>(N, alpha, xptr);

    clEnqueueUnmapMemObject(ctx.get_queue().handle().get(), x, xptr, 0, NULL,
    NULL);
  } else {
#if defined(USE_CLBLAS)

    cl_command_queue queue = ctx.get_queue().handle().get();

    if (std::is_same<Dtype, float>::value) {
      GREENTEA_CL_BLAS_CHECK(clblasSscal(N, alpha, x, offx,
              1, 1, &queue, 0, NULL, NULL));
    } else {
      GREENTEA_CL_BLAS_CHECK(clblasDscal(N, alpha, x, offx,
              1, 1, &queue, 0, NULL, NULL));
    }

#elif defined(USE_CLBLAST)

    cl_command_queue queue = ctx.get_queue().handle().get();

    const size_t incx = 1;

    if (std::is_same<Dtype, float>::value) {
      GREENTEA_CLBLAST_CHECK(
        clblast::Scal<float>(
          N,
          alpha,
          x, offx, incx,
          &queue));
    } else {
      GREENTEA_CLBLAST_CHECK(
        clblast::Scal<double>(
          N,
          alpha,
          x, offx, incx,
          &queue));
    }

#else  // default (ViennaCL)

    typedef typename viennacl::vector_base<Dtype,
        uint_tp, int_tp>::size_type size_type;
    typedef typename viennacl::vector_base<Dtype,
        uint_tp, int_tp>::size_type difference_type;

    viennacl::vector_base<Dtype, size_t, ptrdiff_t> v1(x, size_type(N),
                                                     size_type(offx),
                                                     difference_type(1), ctx);
    v1 *= alpha;

#endif  // clBLAS, CLBlast, or default (ViennaCL)
  }
}

template void greentea_gpu_scal<float>(const int_tp ctx_id, const int_tp N,
                                       const float alpha, cl_mem x,
                                       const int_tp offx);
template void greentea_gpu_scal<double>(const int_tp ctx_id, const int_tp N,
                                        const double alpha, cl_mem x,
                                        const int_tp offx);

template<typename Dtype>
void greentea_gpu_axpby(const int_tp ctx_id, const int_tp N, const Dtype alpha,
                        const cl_mem X, const int_tp offX, const Dtype beta,
                        cl_mem Y, const int_tp offY) {
  greentea_gpu_scal<Dtype>(ctx_id, N, beta, Y, offY);
  greentea_gpu_axpy<Dtype>(ctx_id, N, alpha, X, offX, Y, offY);
}

template void greentea_gpu_axpby<float>(const int_tp ctx_id, const int_tp N,
                                        const float alpha, const cl_mem X,
                                        const int_tp offX, const float beta,
                                        cl_mem Y, const int_tp offY);

template void greentea_gpu_axpby<double>(const int_tp ctx_id, const int_tp N,
                                         const double alpha, const cl_mem X,
                                         const int_tp offX, const double beta,
                                         cl_mem Y, const int_tp offY);

template<typename Dtype>
void greentea_gpu_dot(const int_tp ctx_id, const int_tp n, const cl_mem X,
                      const int_tp offX, const cl_mem Y, const int_tp offY,
                      Dtype* out) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);

  if (ctx.devices()[0].type() == CL_DEVICE_TYPE_CPU) {
    Dtype* Xptr = reinterpret_cast<Dtype*>(clEnqueueMapBuffer(
        ctx.get_queue().handle().get(), X, true, CL_MAP_READ,
        sizeof(Dtype) * offX, sizeof(Dtype) * n, 0, NULL, NULL, NULL));
    Dtype* Yptr = reinterpret_cast<Dtype*>(clEnqueueMapBuffer(
        ctx.get_queue().handle().get(), Y, true, CL_MAP_READ,
        sizeof(Dtype) * offY, sizeof(Dtype) * n, 0, NULL, NULL, NULL));

    *out = caffe_cpu_dot<Dtype>(n, Xptr, Yptr);

    clEnqueueUnmapMemObject(ctx.get_queue().handle().get(), X, Xptr, 0, NULL,
    NULL);
    clEnqueueUnmapMemObject(ctx.get_queue().handle().get(), Y, Yptr, 0, NULL,
    NULL);

  } else {
#if defined(USE_CLBLAS)

    cl_command_queue queue = ctx.get_queue().handle().get();

    cl_int err;
    cl_mem gpuout = clCreateBuffer(ctx.handle().get(), CL_MEM_READ_WRITE,
        sizeof(Dtype), NULL, &err);
    cl_mem scratch = clCreateBuffer(ctx.handle().get(), CL_MEM_READ_WRITE,
        n * sizeof(Dtype), NULL, &err);

    if (std::is_same<Dtype, float>::value) {
      GREENTEA_CL_BLAS_CHECK(
          clblasSdot(n, gpuout, 0, X, offX, 1, Y,
              offY, 1, scratch, 1, &queue, 0, NULL, NULL));
    } else {
      GREENTEA_CL_BLAS_CHECK(
          clblasDdot(n, gpuout, 0, X, offX, 1, Y,
              offY, 1, scratch, 1, &queue, 0, NULL, NULL));
    }

    greentea_gpu_memcpy(sizeof(Dtype), gpuout, 0, out, &ctx);

    clReleaseMemObject(gpuout);
    clReleaseMemObject(scratch);

#elif defined(USE_CLBLAST)

    cl_command_queue queue = ctx.get_queue().handle().get();

    cl_int err = CL_SUCCESS;
    cl_mem Z = clCreateBuffer(ctx.handle().get(), CL_MEM_READ_WRITE,
      sizeof(Dtype), NULL, &err);
    // TODO: error handling.

    const size_t offZ = 0;
    const size_t incX = 1;
    const size_t incY = 1;

    if (std::is_same<Dtype, float>::value) {
      GREENTEA_CLBLAST_CHECK(
        clblast::Dot<float>(
          n,
          Z, offZ,
          X, offX, incX,
          Y, offY, incY,
          &queue));
    } else {
      GREENTEA_CLBLAST_CHECK(
        clblast::Dot<double>(
          n,
          Z, offZ,
          X, offX, incX,
          Y, offY, incY,
          &queue));
    }

    greentea_gpu_memcpy(sizeof(Dtype), Z, offZ, out, &ctx);
    clReleaseMemObject(Z);

#else  // default (ViennaCL)

    typedef typename viennacl::vector_base<Dtype,
        uint_tp, int_tp>::size_type size_type;
    typedef typename viennacl::vector_base<Dtype,
        uint_tp, int_tp>::size_type difference_type;

    viennacl::vector_base<Dtype, size_t, ptrdiff_t> v1(X, size_type(n),
                                                     size_type(offX),
                                                     difference_type(1), ctx);
    viennacl::vector_base<Dtype, size_t, ptrdiff_t> v2(Y, size_type(n),
                                                     size_type(offY),
                                                     difference_type(1), ctx);

    *out = viennacl::linalg::inner_prod(v1, v2);

#endif  // clBLAS, CLBlast, or default (ViennaCL)
  }
}

template void greentea_gpu_dot<float>(const int_tp ctx_id, const int_tp n,
                                      const cl_mem X, const int_tp offX,
                                      const cl_mem Y, const int_tp offY,
                                      float* out);
template void greentea_gpu_dot<double>(const int_tp ctx_id, const int_tp n,
                                       const cl_mem X, const int_tp offX,
                                       const cl_mem Y, const int_tp offY,
                                       double* out);

template<typename Dtype>
void greentea_gpu_asum(const int_tp ctx_id, const int_tp n, const cl_mem X,
                       const int_tp offX, Dtype* Y) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);

  if (ctx.devices()[0].type() == CL_DEVICE_TYPE_CPU) {
    Dtype* Xptr = reinterpret_cast<Dtype*>(clEnqueueMapBuffer(
        ctx.get_queue().handle().get(), X, true, CL_MAP_READ,
        sizeof(Dtype) * offX, sizeof(Dtype) * n, 0, NULL, NULL, NULL));

    *Y = caffe_cpu_asum<Dtype>(n, Xptr);

    clEnqueueUnmapMemObject(ctx.get_queue().handle().get(), X, Xptr, 0, NULL,
    NULL);
  } else {
#if defined(USE_CLBLAS)

    cl_command_queue queue = ctx.get_queue().handle().get();

    cl_int err;
    cl_mem gpuout = clCreateBuffer(ctx.handle().get(), CL_MEM_READ_WRITE,
        sizeof(Dtype), NULL, &err);
    cl_mem scratch = clCreateBuffer(ctx.handle().get(), CL_MEM_READ_WRITE,
        n * sizeof(Dtype), NULL, &err);

    if (std::is_same<Dtype, float>::value) {
      GREENTEA_CL_BLAS_CHECK(
          clblasSasum(n, gpuout, 0, X, offX, 1,
              scratch, 1, &queue, 0, NULL, NULL));
    } else {
      GREENTEA_CL_BLAS_CHECK(
          clblasDasum(n, gpuout, 0, X, offX, 1,
              scratch, 1, &queue, 0, NULL, NULL));
    }

    greentea_gpu_memcpy(sizeof(Dtype), gpuout, 0, Y, &ctx);

    clReleaseMemObject(gpuout);
    clReleaseMemObject(scratch);

#elif defined(USE_CLBLAST)

    cl_command_queue queue = ctx.get_queue().handle().get();

    cl_int err = CL_SUCCESS;
    cl_mem Z = clCreateBuffer(ctx.handle().get(), CL_MEM_READ_WRITE,
      sizeof(Dtype), NULL, &err);
    // TODO: error handling.

    const size_t offZ = 0;
    const size_t incX = 1;

    if (std::is_same<Dtype, float>::value) {
      GREENTEA_CLBLAST_CHECK(
        clblast::Asum<float>(
          n,
          Z, offZ,
          X, offX, incX,
          &queue));
    } else {
      GREENTEA_CLBLAST_CHECK(
        clblast::Asum<double>(
          n,
          Z, offZ,
          X, offX, incX,
          &queue));
    }

    greentea_gpu_memcpy(sizeof(Dtype), Z, offZ, Y, &ctx);

    clReleaseMemObject(Z);

#else  // default (ViennaCL)

    typedef typename viennacl::vector_base<Dtype,
        uint_tp, int_tp>::size_type size_type;
    typedef typename viennacl::vector_base<Dtype,
        uint_tp, int_tp>::size_type difference_type;

    viennacl::vector_base<Dtype, size_t, ptrdiff_t> v1(X, size_type(n),
                                                     size_type(offX),
                                                     difference_type(1), ctx);

    *Y = viennacl::linalg::norm_1(v1);

#endif  // clBLAS, CLBlast, or default (ViennaCL)
  }
}

template void greentea_gpu_asum<float>(const int_tp ctx_id, const int_tp n,
                                       const cl_mem X, const int_tp offX,
                                       float* Y);
template void greentea_gpu_asum<double>(const int_tp ctx_id, const int_tp n,
                                        const cl_mem X, const int_tp offX,
                                        double* Y);

template<typename Dtype>
void greentea_gpu_scale(const int_tp ctx_id, const int_tp n, const Dtype alpha,
                        const cl_mem X, const int_tp offX, cl_mem Y,
                        const int_tp offY) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);

  if (ctx.devices()[0].type() == CL_DEVICE_TYPE_CPU) {
    Dtype* Xptr = reinterpret_cast<Dtype*>(clEnqueueMapBuffer(
        ctx.get_queue().handle().get(), X, true, CL_MAP_READ,
        sizeof(Dtype) * offX, sizeof(Dtype) * n, 0, NULL, NULL, NULL));
    Dtype* Yptr = reinterpret_cast<Dtype*>(clEnqueueMapBuffer(
        ctx.get_queue().handle().get(), Y, true, CL_MAP_WRITE,
        sizeof(Dtype) * offY, sizeof(Dtype) * n, 0, NULL, NULL, NULL));

    caffe_cpu_scale<Dtype>(n, alpha, Xptr, Yptr);

    clEnqueueUnmapMemObject(ctx.get_queue().handle().get(), X, Xptr, 0, NULL,
    NULL);
    clEnqueueUnmapMemObject(ctx.get_queue().handle().get(), Y, Yptr, 0, NULL,
    NULL);
  } else {
#if defined(USE_CLBLAS)

    // FIXME: Remove, as can reuse ctx obtained above?
    viennacl::ocl::context ctx = viennacl::ocl::get_context(ctx_id);
    cl_command_queue queue = ctx.get_queue().handle().get();

    // FIXME: Use xAXPY with beta = 0?
    if (std::is_same<Dtype, float>::value) {
      GREENTEA_CL_BLAS_CHECK(
          clblasScopy(n, X, offX, 1, Y, offY, 1, 1, &queue, 0, NULL, NULL));
      GREENTEA_CL_BLAS_CHECK(
          clblasSscal(n, alpha, Y, offY, 1, 1, &queue, 0, NULL, NULL));
    } else {
      GREENTEA_CL_BLAS_CHECK(
          clblasDcopy(n, X, offX, 1, Y, offY, 1, 1, &queue, 0, NULL, NULL));
      GREENTEA_CL_BLAS_CHECK(
          clblasDscal(n, alpha, Y, offY, 1, 1, &queue, 0, NULL, NULL));
    }

#elif defined(USE_CLBLAST)

    cl_command_queue queue = ctx.get_queue().handle().get();

    const size_t incX = 1;
    const size_t incY = 1;

    if (std::is_same<Dtype, float>::value) {
      GREENTEA_CLBLAST_CHECK(
        clblast::Copy<float>(
          n,
          X, offX, incX,
          Y, offY, incY,
          &queue));
      GREENTEA_CLBLAST_CHECK(
        clblast::Scal<float>(
          n,
          alpha,
          Y, offY, incY,
          &queue));
    } else {
      GREENTEA_CLBLAST_CHECK(
        clblast::Copy<double>(
          n,
          X, offX, incX,
          Y, offY, incY,
          &queue));
      GREENTEA_CLBLAST_CHECK(
        clblast::Scal<double>(
          n,
          alpha,
          Y, offY, incY,
          &queue));
    }

#else  // default (ViennaCL)

    typedef typename viennacl::vector_base<Dtype,
        uint_tp, int_tp>::size_type size_type;
    typedef typename viennacl::vector_base<Dtype,
        uint_tp, int_tp>::size_type difference_type;

    viennacl::vector_base<Dtype, size_t, ptrdiff_t> v1(X, size_type(n),
                                                     size_type(offX),
                                                     difference_type(1), ctx);
    viennacl::vector_base<Dtype, size_t, ptrdiff_t> v2(Y, size_type(n),
                                                     size_type(offY),
                                                     difference_type(1), ctx);

    v2 = v1 * alpha;

#endif  // clBLAS, CLBlast, or default (ViennaCL)
  }
}

template void greentea_gpu_scale<float>(const int_tp ctx_id, const int_tp n,
                                        const float alpha, const cl_mem X,
                                        const int_tp offX, cl_mem Y,
                                        const int_tp offY);

template void greentea_gpu_scale<double>(const int_tp ctx_id, const int_tp n,
                                         const double alpha, const cl_mem X,
                                         const int_tp offX, cl_mem Y,
                                         const int_tp offY);

template<typename Dtype>
void greentea_gpu_set(const int_tp ctx_id, const int_tp N, const Dtype alpha,
                      cl_mem Y, const int_tp offY) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);
  viennacl::ocl::program &program = (Caffe::Get().GetDevice(ctx_id, false))
      ->program();
  // OpenCL Version >= 1.2 approach
  // clEnqueueFillBuffer(ctx.get_queue().handle().get(),
  //                  Y, &alpha, sizeof(Dtype),
  //                  offY, N, 0, NULL, NULL);

  // OpenCL Version < 1.2 fallback
  viennacl::ocl::kernel &oclk_fill = program.get_kernel(
      CL_KERNEL_SELECT("fill"));
  viennacl::ocl::enqueue(oclk_fill(N, alpha, WrapHandle(Y, &ctx), offY),
                         ctx.get_queue());
}

template void greentea_gpu_set<int_tp>(const int_tp ctx_id, const int_tp N,
                                       const int_tp alpha, cl_mem Y,
                                       const int_tp offY);
template void greentea_gpu_set<float>(const int_tp ctx_id, const int_tp N,
                                      const float alpha, cl_mem Y,
                                      const int_tp offY);
template void greentea_gpu_set<double>(const int_tp ctx_id, const int_tp N,
                                       const double alpha, cl_mem Y,
                                       const int_tp offY);

template<typename Dtype>
void greentea_gpu_add_scalar(const int_tp ctx_id, const int_tp N,
                             const Dtype alpha, cl_mem Y, const int_tp offY) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);
  viennacl::ocl::program &program = (Caffe::Get().GetDevice(ctx_id, false))
      ->program();

  viennacl::ocl::kernel &oclk_add_scalar = program.get_kernel(
      CL_KERNEL_SELECT("add_scalar"));
  viennacl::ocl::enqueue(oclk_add_scalar(N, alpha, WrapHandle(Y, &ctx), offY),
                         ctx.get_queue());
}

template void greentea_gpu_add_scalar<float>(const int_tp ctx_id,
                                             const int_tp N, const float alpha,
                                             cl_mem Y, const int_tp offY);
template void greentea_gpu_add_scalar<double>(const int_tp ctx_id,
                                              const int_tp N,
                                              const double alpha, cl_mem Y,
                                              const int_tp offY);

template<typename Dtype>
void greentea_gpu_add(const int_tp ctx_id, const int_tp n, const cl_mem a,
                      const int_tp offa, const cl_mem b, const int_tp offb,
                      cl_mem y, const int_tp offy) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);
  viennacl::ocl::program &program = (Caffe::Get().GetDevice(ctx_id, false))
      ->program();

  viennacl::ocl::kernel &oclk_add = program.get_kernel(CL_KERNEL_SELECT("add"));
  viennacl::ocl::enqueue(
      oclk_add(n, WrapHandle(a, &ctx), offa, WrapHandle(b, &ctx), offb,
               WrapHandle(y, &ctx), offy),
      ctx.get_queue());
}

template void greentea_gpu_add<float>(const int_tp ctx_id, const int_tp n,
                                      const cl_mem a, const int_tp offa,
                                      const cl_mem b, const int_tp offb,
                                      cl_mem y, const int_tp offy);
template void greentea_gpu_add<double>(const int_tp ctx_id, const int_tp n,
                                       const cl_mem a, const int_tp offa,
                                       const cl_mem b, const int_tp offb,
                                       cl_mem y, const int_tp offy);

template<typename Dtype>
void greentea_gpu_sub(const int_tp ctx_id, const int_tp n, const cl_mem a,
                      const int_tp offa, const cl_mem b, const int_tp offb,
                      cl_mem y, const int_tp offy) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);
  viennacl::ocl::program &program = (Caffe::Get().GetDevice(ctx_id, false))
      ->program();

  viennacl::ocl::kernel &oclk_sub = program.get_kernel(CL_KERNEL_SELECT("sub"));
  viennacl::ocl::enqueue(
      oclk_sub(n, WrapHandle(a, &ctx), offa, WrapHandle(b, &ctx), offb,
               WrapHandle(y, &ctx), offy),
      ctx.get_queue());
}

template void greentea_gpu_sub<float>(const int_tp ctx_id, const int_tp n,
                                      const cl_mem a, const int_tp offa,
                                      const cl_mem b, const int_tp offb,
                                      cl_mem y, const int_tp offy);
template void greentea_gpu_sub<double>(const int_tp ctx_id, const int_tp n,
                                       const cl_mem a, const int_tp offa,
                                       const cl_mem b, const int_tp offb,
                                       cl_mem y, const int_tp offy);

template<typename Dtype>
void greentea_gpu_abs(const int_tp ctx_id, const int_tp N, const cl_mem a,
                      const int_tp offa, cl_mem y, const int_tp offy) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);
  viennacl::ocl::program &program = (Caffe::Get().GetDevice(ctx_id, false))
      ->program();

  viennacl::ocl::kernel &oclk_abs = program.get_kernel(CL_KERNEL_SELECT("abs"));
  viennacl::ocl::enqueue(
      oclk_abs(N, WrapHandle(a, &ctx), offa, WrapHandle(y, &ctx), offy),
      ctx.get_queue());
}

template void greentea_gpu_abs<float>(const int_tp ctx_id, const int_tp N,
                                      const cl_mem a, const int_tp offa,
                                      cl_mem y, const int_tp offy);
template void greentea_gpu_abs<double>(const int_tp ctx_id, const int_tp N,
                                       const cl_mem a, const int_tp offa,
                                       cl_mem y, const int_tp offy);

template<typename Dtype>
void greentea_gpu_exp(const int_tp ctx_id, const int_tp N, const cl_mem a,
                      const int_tp offa, cl_mem y, const int_tp offy) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);
  viennacl::ocl::program &program = (Caffe::Get().GetDevice(ctx_id, false))
      ->program();

  viennacl::ocl::kernel &oclk_exp = program.get_kernel(CL_KERNEL_SELECT("exp"));
  viennacl::ocl::enqueue(
      oclk_exp(N, WrapHandle(a, &ctx), offa, WrapHandle(y, &ctx), offy),
      ctx.get_queue());
}

template void greentea_gpu_exp<float>(const int_tp ctx_id, const int_tp N,
                                      const cl_mem a, const int_tp offa,
                                      cl_mem y, const int_tp offy);
template void greentea_gpu_exp<double>(const int_tp ctx_id, const int_tp N,
                                       const cl_mem a, const int_tp offa,
                                       cl_mem y, const int_tp offy);

template<typename Dtype>
void greentea_gpu_sqrt(const int_tp ctx_id, const int_tp n,
                       const cl_mem a, const int_tp offa,
                       cl_mem y, const int_tp offy) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);
  viennacl::ocl::program &program = (Caffe::Get().GetDevice(ctx_id, false))
      ->program();

  viennacl::ocl::kernel &oclk_sqrt = program.get_kernel(
      CL_KERNEL_SELECT("sqrt"));
  viennacl::ocl::enqueue(
      oclk_sqrt(n, WrapHandle(a, &ctx), offa, WrapHandle(y, &ctx), offy),
      ctx.get_queue());
}

template void greentea_gpu_sqrt<float>(const int_tp ctx_id, const int_tp n,
                                       const cl_mem a, const int_tp offa,
                                       cl_mem y, const int_tp offy);
template void greentea_gpu_sqrt<double>(const int_tp ctx_id, const int_tp n,
                                        const cl_mem a, const int_tp offa,
                                        cl_mem y, const int_tp offy);

template<typename Dtype>
void greentea_gpu_powx(const int_tp ctx_id, const int_tp N, const cl_mem a,
                       const int_tp offa, const Dtype alpha, cl_mem y,
                       const int_tp offy) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);
  viennacl::ocl::program &program = (Caffe::Get().GetDevice(ctx_id, false))
      ->program();

  viennacl::ocl::kernel &oclk_powx = program.get_kernel(
      CL_KERNEL_SELECT("powx"));
  viennacl::ocl::enqueue(
      oclk_powx(N, WrapHandle(a, &ctx), offa, alpha, WrapHandle(y, &ctx), offy),
      ctx.get_queue());
}

template void greentea_gpu_powx<float>(const int_tp ctx_id, const int_tp N,
                                       const cl_mem a, const int_tp offa,
                                       const float alpha, cl_mem y,
                                       const int_tp offy);
template void greentea_gpu_powx<double>(const int_tp ctx_id, const int_tp N,
                                        const cl_mem a, const int_tp offa,
                                        const double alpha, cl_mem y,
                                        const int_tp offy);

template<typename Dtype>
void greentea_gpu_log(const int_tp ctx_id, const int_tp N, const cl_mem a,
                      const int_tp offa, cl_mem y, const int_tp offy) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);
  viennacl::ocl::program &program = (Caffe::Get().GetDevice(ctx_id, false))
      ->program();

  viennacl::ocl::kernel &oclk_log = program.get_kernel(CL_KERNEL_SELECT("log"));
  viennacl::ocl::enqueue(
      oclk_log(N, WrapHandle(a, &ctx), offa, WrapHandle(y, &ctx), offy),
      ctx.get_queue());
}

template void greentea_gpu_log<float>(const int_tp ctx_id, const int_tp N,
                                      const cl_mem a, const int_tp offa,
                                      cl_mem y, const int_tp offy);
template void greentea_gpu_log<double>(const int_tp ctx_id, const int_tp N,
                                       const cl_mem a, const int_tp offa,
                                       cl_mem y, const int_tp offy);

template<typename Dtype>
void greentea_gpu_sign(const int_tp ctx_id, const int_tp n, const cl_mem x,
int_tp offx,
                       cl_mem y, const int_tp offy) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);
  viennacl::ocl::program &program = (Caffe::Get().GetDevice(ctx_id, false))
      ->program();

  viennacl::ocl::kernel &oclk_sign = program.get_kernel(
      CL_KERNEL_SELECT("sign"));
  viennacl::ocl::enqueue(
      oclk_sign(n, WrapHandle(x, &ctx), offx, WrapHandle(y, &ctx), offy),
      ctx.get_queue());
}

template void greentea_gpu_sign<float>(const int_tp ctx_id, const int_tp n,
                                       const cl_mem x, int_tp offx, cl_mem y,
                                       const int_tp offy);
template void greentea_gpu_sign<double>(const int_tp ctx_id, const int_tp n,
                                        const cl_mem x, int_tp offx, cl_mem y,
                                        const int_tp offy);

template<typename Dtype>
void greentea_gpu_sgnbit(const int_tp ctx_id, const int_tp n, const cl_mem x,
int_tp offx,
                         cl_mem y, const int_tp offy) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);
  viennacl::ocl::program &program = (Caffe::Get().GetDevice(ctx_id, false))
      ->program();

  viennacl::ocl::kernel &oclk_sgnbit = program.get_kernel(
      CL_KERNEL_SELECT("sgnbit"));
  viennacl::ocl::enqueue(
      oclk_sgnbit(n, WrapHandle(x, &ctx), offx, WrapHandle(y, &ctx), offy),
      ctx.get_queue());
}

template void greentea_gpu_sgnbit<float>(const int_tp ctx_id, const int_tp n,
                                         const cl_mem x, int_tp offx, cl_mem y,
                                         const int_tp offy);
template void greentea_gpu_sgnbit<double>(const int_tp ctx_id, const int_tp n,
                                          const cl_mem x, int_tp offx, cl_mem y,
                                          const int_tp offy);

void greentea_gpu_rng_uniform(const int_tp ctx_id, const int_tp n, cl_mem r,
int_tp offr) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);
  std::vector<uint_tp> random(n);  //NOLINT
  caffe_rng_uniform(n, &random[0]);
  greentea_gpu_memcpy(sizeof(uint_tp) * n, &random[0], r, offr, &ctx);
}

template<typename Dtype>
void greentea_gpu_rng_uniform(const int_tp ctx_id, const int_tp n,
                              const Dtype a, const Dtype b, cl_mem r,
                              const int_tp offr) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);
  std::vector<Dtype> random(n);  // NOLINT
  caffe_rng_uniform(n, a, b, &random[0]);
  greentea_gpu_memcpy(sizeof(Dtype) * n, &random[0], r, offr, &ctx);
}

template void greentea_gpu_rng_uniform<float>(const int_tp ctx_id,
                                              const int_tp n, const float a,
                                              const float b, cl_mem r,
                                              const int_tp offr);
template void greentea_gpu_rng_uniform<double>(const int_tp ctx_id,
                                               const int_tp n, const double a,
                                               const double b, cl_mem r,
                                               const int_tp offr);

template<typename Dtype>
void greentea_gpu_rng_gaussian(const int_tp ctx_id, const int_tp n,
                               const Dtype mu, const Dtype sigma, cl_mem r,
                               const int_tp offr) {
  viennacl::ocl::context &ctx = viennacl::ocl::get_context(ctx_id);
  std::vector<Dtype> random(n);  // NOLINT
  caffe_rng_gaussian(n, mu, sigma, &random[0]);
  greentea_gpu_memcpy(sizeof(Dtype) * n, &random[0], r, offr, &ctx);
}

template void greentea_gpu_rng_gaussian<float>(const int_tp ctx_id,
                                               const int_tp n, const float mu,
                                               const float sigma, cl_mem r,
                                               const int_tp offr);

template void greentea_gpu_rng_gaussian<double>(const int_tp ctx_id,
                                                const int_tp n, const double mu,
                                                const double sigma, cl_mem r,
                                                const int_tp offr);

}  // namespace caffe
#endif  // USE_GREENTEA


/*
 * Copyright (c) 2009 Carnegie Mellon University.
 *     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.
 *
 * For more about this software visit:
 *
 *      http://www.graphlab.ml.cmu.edu
 *
 */

/**
 * \file cgs_lda.cpp
 *
 * \brief This file contains a GraphLab based implementation of the
 * Collapsed Gibbs Sampler (CGS) for the Latent Dirichlet Allocation
 * (LDA) model.
 *
 *
 *
 * \author Joseph Gonzalez, Diana Hu
 */

#include <vector>
#include <set>
#include <algorithm>

#include "util/atomic.hpp"

#include <boost/math/special_functions/gamma.hpp>
#include <vector>
#include <algorithm>
#include <boost/algorithm/string.hpp>
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/input_sequence.hpp>

// Global Types
// ============================================================================
typedef int count_type;

/**
 * \brief The factor type is used to store the counts of tokens in
 * each topic for words, documents, and assignments.
 *
 * Atomic counts are used because we violate the abstraction by
 * modifying adjacent vertex data on scatter.  As a consequence
 * multiple threads on the same machine may try to update the same
 * vertex data at the same time.  The graphlab::atomic type ensures
 * that multiple increments are serially consistent.
 */
typedef std::vector< graphchi::atomic<count_type> > factor_type;

/**
 * \brief We use the factor type in accumulators and so we define an
 * operator+=
 */
inline factor_type& operator+=(factor_type& lvalue,
                               const factor_type& rvalue) {
  if(!rvalue.empty()) {
    if(lvalue.empty()) lvalue = rvalue;
    else {
      for(size_t t = 0; t < lvalue.size(); ++t) lvalue[t] += rvalue[t];
    }
  }
  return lvalue;
} // end of operator +=

/**
 * \brief The latent topic id of a token is the smallest reasonable
 * type.
 */
typedef uint16_t topic_id_type;

// We require a null topic to represent the topic assignment for
// tokens that have not yet been assigned.
#define NULL_TOPIC (topic_id_type(-1))

#define NTOPICS 20

/**
 * \brief The assignment type is used on each edge to store the
 * assignments of each token.  There can be several occurrences of the
 * same word in a given document and so a vector is used to store the
 * assignments of each occurrence.
 */
typedef uint16_t assignment_type[NTOPICS];

// Global Variables
// ============================================================================

/**
 * \brief The alpha parameter determines the sparsity of topics for
 * each document.
 */
double ALPHA = 1;

/**
 * \brief the Beta parameter determines the sparsity of words in each
 * document.
 */
double BETA = 0.1;

/**
 * \brief the total number of topics to uses
 */

/**
 * \brief The total number of words in the dataset.
 */
size_t NWORDS = 0;

/**
 * \brief The total number of docs in the dataset.
 */
size_t NDOCS = 0;

/**
 * \brief The total number of tokens in the corpus
 */
size_t NTOKENS = 0;

/**
 * \brief The number of top words to display during execution (from
 * each topic).
 */
size_t TOPK = 5;

/**
 * \brief The interval to display topics during execution.
 */
size_t INTERVAL = 10;

/**
 * \brief The global variable storing the global topic count across
 * all machines.  This is maintained periodically using aggregation.
 */
factor_type GLOBAL_TOPIC_COUNT;

/**
 * \brief A dictionary of words used to print the top words during
 * execution.
 */
std::vector<std::string> DICTIONARY;

/**
 * \brief The maximum occurences allowed for an individual term-doc
 * pair. (edge data)
 */
size_t MAX_COUNT = 100;

/**
 * \brief The time to run until the first sample is taken.  If less
 * than zero then the sampler will run indefinitely.
 */
float BURNIN = -1;

// Graph Types
// ============================================================================

/**
 * \brief The vertex data represents each term and document in the
 * corpus and contains the counts of tokens in each topic.
 */
struct vertex_data {
  ///! The total number of updates
  uint32_t nupdates;
  ///! The total number of changes to adjacent tokens
  uint32_t nchanges;
  ///! The count of tokens in each topic
  factor_type factor;
  vertex_data() : nupdates(0), nchanges(0), factor(NTOPICS) { }
}; // end of vertex_data

/**
 * \brief The edge data represents the individual tokens (word,doc)
 * pairs and their assignment to topics.
 */
struct edge_data {
  ///! The number of changes on the last update
  uint16_t nchanges;
  ///! The assignment of all tokens
  assignment_type assignment;
  edge_data(size_t ntokens = 0) : nchanges(0) {
      for(int i=0; i<NTOPICS; i++) assignment[i] = 0;
  }
}; // end of edge_data

typedef graphlab::distributed_graph<vertex_data, edge_data> graph_type;

static void parse(edge_data &x, const char * s) {
    size_t count = atol(s);
    count = std::min(count, MAX_COUNT);
    x = (edge_data(count));
}

/**
 * \brief Edge data parser used in graph.load_json
 *
 * Make sure that the edge file list
 * has docids from -2 to -(total #docid) and wordids 0 to (total #words -1)
 */
bool eparser(edge_data& ed, const std::string& line){
  const int BASE = 10;
  char* next_char_ptr = NULL;
  size_t count = strtoul(line.c_str(), &next_char_ptr, BASE);
  if(next_char_ptr ==NULL) return false;

  //threshold count
  count = std::min(count, MAX_COUNT);
  ed = (edge_data(count));
  return true;
}

/**
 * \brief Vertex data parser used in graph.load_json
 */
bool vparser(vertex_data& vd, const std::string& line){
  vd = vertex_data();
  return true;
}

/**
 * \brief Determine if the given vertex is a word vertex or a doc
 * vertex.
 *
 * For simplicity we connect docs --> words and therefore if a vertex
 * has in edges then it is a word.
 */
inline bool is_word(const graph_type::vertex_type& vertex) {
  return vertex.num_in_edges() > 0 ? 1 : 0;
}

/**
 * \brief Determine if the given vertex is a doc vertex
 *
 * For simplicity we connect docs --> words and therefore if a vertex
 * has out edges then it is a doc
 */
inline bool is_doc(const graph_type::vertex_type& vertex) {
  return vertex.num_out_edges() > 0 ? 1 : 0;
}

/**
 * \brief return the number of tokens on a particular edge.
 */
inline size_t count_tokens(const graph_type::edge_type& edge) {
  return edge.data().assignment.size();
}

/**
 * \brief Get the other vertex in the edge.
 */
inline graph_type::vertex_type
get_other_vertex(const graph_type::edge_type& edge,
                 const graph_type::vertex_type& vertex) {
  return vertex.id() == edge.source().id()? edge.target() : edge.source();
}

// ========================================================
// The Collapsed Gibbs Sampler Function

/**
 * \brief The gather type for the collapsed Gibbs sampler is used to
 * collect the topic counts on adjacent edges so that the apply
 * function can compute the correct topic counts for the center
 * vertex.
 *
 */
struct gather_type {
  factor_type factor;
  uint32_t nchanges;
  gather_type() : nchanges(0) { };
  gather_type(uint32_t nchanges) : factor(NTOPICS), nchanges(nchanges) { };
  gather_type& operator+=(const gather_type& other) {
    factor += other.factor;
    nchanges += other.nchanges;
    return *this;
  }
}; // end of gather type

/**
 * \brief The collapsed Gibbs sampler vertex program updates the topic
 * counts for the center vertex and then draws new topic assignments
 * for each edge durring the scatter phase.
 *
 */
class cgs_lda_vertex_program :
  public graphlab::ivertex_program<graph_type, gather_type> {
public:

  /**
   * \brief At termination we want to disable sampling to allow the
   * correct final counts to be computed.
   */
  static bool DISABLE_SAMPLING;

  /** \brief gather on all edges */
  edge_dir_type gather_edges(icontext_type& context,
                             const vertex_type& vertex) const {
    return graphlab::ALL_EDGES;
  } // end of gather_edges

  /**
   * \brief Collect the current topic count on each edge.
   */
  gather_type gather(icontext_type& context, const vertex_type& vertex,
                     edge_type& edge) const {
    gather_type ret(edge.data().nchanges);
    const assignment_type& assignment = edge.data().assignment;
    foreach(topic_id_type asg, assignment) {
      if(asg != NULL_TOPIC) ++ret.factor[asg];
    }
    return ret;
  } // end of gather

  /**
   * \brief Update the topic count for the center vertex.  This
   * ensures that the center vertex has the correct topic count before
   * resampling the topics for each token along each edge.
   */
  void apply(icontext_type& context, vertex_type& vertex,
             const gather_type& sum) {
    const size_t num_neighbors = vertex.num_in_edges() + vertex.num_out_edges();
    ASSERT_GT(num_neighbors, 0);
    // There should be no new edge data since the vertex program has been cleared
    vertex_data& vdata = vertex.data();
    ASSERT_EQ(sum.factor.size(), NTOPICS);
    ASSERT_EQ(vdata.factor.size(), NTOPICS);
    vdata.nupdates++;
    vdata.nchanges = sum.nchanges;
    vdata.factor = sum.factor;
  } // end of apply

  /**
   * \brief Scatter on all edges if the computation is on-going.
   * Computation stops after bunrin or when disable sampling is set to
   * true.
   */
  edge_dir_type scatter_edges(icontext_type& context,
                              const vertex_type& vertex) const {
    return (DISABLE_SAMPLING || (BURNIN > 0 && context.elapsed_seconds() > BURNIN))?
      graphlab::NO_EDGES : graphlab::ALL_EDGES;
  }; // end of scatter edges

  /**
   * \brief Draw new topic assignments for each edge token.
   *
   * Note that we exploit the GraphLab caching model here by DIRECTLY
   * modifying the topic counts of adjacent vertices.  Making the
   * changes immediately visible to any adjacent vertex programs
   * running on the same machine.  However, these changes will be
   * overwritten during the apply step and are only used to accelerate
   * sampling.  This is a potentially dangerous violation of the
   * abstraction and should be taken with caution.  In our case all
   * vertex topic counts are preallocated and atomic operations are
   * used.  In addition during the sampling phase we must be careful
   * to guard against potentially negative temporary counts.
   */
  void scatter(icontext_type& context, const vertex_type& vertex,
               edge_type& edge) const {
    factor_type& doc_topic_count =  is_doc(edge.source()) ?
      edge.source().data().factor : edge.target().data().factor;
    factor_type& word_topic_count = is_word(edge.source()) ?
      edge.source().data().factor : edge.target().data().factor;
    ASSERT_EQ(doc_topic_count.size(), NTOPICS);
    ASSERT_EQ(word_topic_count.size(), NTOPICS);
    // run the actual gibbs sampling
    std::vector<double> prob(NTOPICS);
    assignment_type& assignment = edge.data().assignment;
    edge.data().nchanges = 0;
    foreach(topic_id_type& asg, assignment) {
      const topic_id_type old_asg = asg;
      if(asg != NULL_TOPIC) { // construct the cavity
        --doc_topic_count[asg];
        --word_topic_count[asg];
        --GLOBAL_TOPIC_COUNT[asg];
      }
      for(size_t t = 0; t < NTOPICS; ++t) {
        const double n_dt =
          std::max(count_type(doc_topic_count[t]), count_type(0));
        const double n_wt =
          std::max(count_type(word_topic_count[t]), count_type(0));
        const double n_t  =
          std::max(count_type(GLOBAL_TOPIC_COUNT[t]), count_type(0));
        prob[t] = (ALPHA + n_dt) * (BETA + n_wt) / (BETA * NWORDS + n_t);
      }
      asg = graphlab::random::multinomial(prob);
      // asg = std::max_element(prob.begin(), prob.end()) - prob.begin();
      ++doc_topic_count[asg];
      ++word_topic_count[asg];
      ++GLOBAL_TOPIC_COUNT[asg];
      if(asg != old_asg) {
        ++edge.data().nchanges;
      }
    } // End of loop over each token
    // singla the other vertex
    context.signal(get_other_vertex(edge, vertex));
  } // end of scatter function

}; // end of cgs_lda_vertex_program

bool cgs_lda_vertex_program::DISABLE_SAMPLING = false;

/**
 * \brief The icontext type associated with the cgs_lda_vertex program
 * is needed for all aggregators.
 */
typedef cgs_lda_vertex_program::icontext_type icontext_type;

// ========================================================
// Aggregators

/**
 * \brief The topk aggregator is used to periodically compute and
 * display the topk most common words in each topic.
 *
 * The number of words is determined by the global variable \ref TOPK
 * and the interval is determined by the global variable \ref INTERVAL.
 *
 */
class topk_aggregator {
  typedef std::pair<float, graphlab::vertex_id_type> cw_pair_type;
private:
  std::vector< std::set<cw_pair_type> > top_words;
  size_t nchanges, nupdates;
public:
  topk_aggregator(size_t nchanges = 0, size_t nupdates = 0) :
    nchanges(nchanges), nupdates(nupdates) { }

  topk_aggregator& operator+=(const topk_aggregator& other) {
    nchanges += other.nchanges;
    nupdates += other.nupdates;
    if(other.top_words.empty()) return *this;
    if(top_words.empty()) top_words.resize(NTOPICS);
    for(size_t i = 0; i < top_words.size(); ++i) {
      // Merge the topk
      top_words[i].insert(other.top_words[i].begin(),
                          other.top_words[i].end());
      // Remove excess elements
      while(top_words[i].size() > TOPK)
        top_words[i].erase(top_words[i].begin());
    }
    return *this;
  } // end of operator +=

  static topk_aggregator map(icontext_type& context,
                             const graph_type::vertex_type& vertex) {
    topk_aggregator ret_value;
    const vertex_data& vdata = vertex.data();
    ret_value.nchanges = vdata.nchanges;
    ret_value.nupdates = vdata.nupdates;
    if(is_word(vertex)) {
      const graphlab::vertex_id_type wordid = vertex.id();
      ret_value.top_words.resize(vdata.factor.size());
      for(size_t i = 0; i < vdata.factor.size(); ++i) {
        const cw_pair_type pair(vdata.factor[i], wordid);
        ret_value.top_words[i].insert(pair);
      }
    }
    return ret_value;
  } // end of map function

  static void finalize(icontext_type& context,
                       const topk_aggregator& total) {
    if(context.procid() != 0) return;
     for(size_t i = 0; i < total.top_words.size(); ++i) {
      std::cout << "Topic " << i << ": ";
      rev_foreach(cw_pair_type pair, total.top_words[i])  {

        std::cout << DICTIONARY[pair.second]
                  << "(" << pair.first << ")" << ", ";
      }

      std::cout << std::endl;
    }

    std::cout << "\nNumber of token changes: " << total.nchanges << std::endl;
    std::cout << "\nNumber of updates:       " << total.nupdates << std::endl;
  } // end of finalize
}; // end of topk_aggregator struct

/**
 * \brief The global counts aggregator computes the total number of
 * tokens in each topic across all words and documents and then
 * updates the \ref GLOBAL_TOPIC_COUNT variable.
 *
 */
struct global_counts_aggregator {
  typedef graph_type::vertex_type vertex_type;
  static factor_type map(icontext_type& context, const vertex_type& vertex) {
    return vertex.data().factor;
  } // end of map function

  static void finalize(icontext_type& context, const factor_type& total) {
    size_t sum = 0;
    for(size_t t = 0; t < total.size(); ++t) {
      GLOBAL_TOPIC_COUNT[t] =
        std::max(count_type(total[t]/2), count_type(0));
      sum += GLOBAL_TOPIC_COUNT[t];
    }
    context.cout() << "Total Tokens: " << sum << std::endl;
  } // end of finalize
}; // end of global_counts_aggregator struct

/**
 * \brief The Likelihood aggregators maintains the current estimate of
 * the log-likelihood of the current token assignments.
 *
 *  llik_words_given_topics = ...
 *    ntopics * (gammaln(nwords * beta) - nwords * gammaln(beta)) - ...
 *    sum_t(gammaln( n_t + nwords * beta)) +
 *    sum_w(sum_t(gammaln(n_wt + beta)));
 *
 *  llik_topics = ...
 *    ndocs * (gammaln(ntopics * alpha) - ntopics * gammaln(alpha)) + ...
 *    sum_d(sum_t(gammaln(n_td + alpha)) - gammaln(sum_t(n_td) + ntopics * alpha));
 */
class likelihood_aggregator : public graphlab::IS_POD_TYPE {
  typedef graph_type::vertex_type vertex_type;
  double lik_words_given_topics;
  double lik_topics;
public:
  likelihood_aggregator() : lik_words_given_topics(0), lik_topics(0) { }

  likelihood_aggregator& operator+=(const likelihood_aggregator& other) {
    lik_words_given_topics += other.lik_words_given_topics;
    lik_topics += other.lik_topics;
    return *this;
  } // end of operator +=

  static likelihood_aggregator
  map(icontext_type& context, const vertex_type& vertex) {
    using boost::math::lgamma;
    const factor_type& factor = vertex.data().factor;
    ASSERT_EQ(factor.size(), NTOPICS);
   likelihood_aggregator ret;
    if(is_word(vertex)) {
      for(size_t t = 0; t < NTOPICS; ++t) {
        const double value = std::max(count_type(factor[t]), count_type(0));
        ret.lik_words_given_topics += lgamma(value + BETA);
      }
    } else {  ASSERT_TRUE(is_doc(vertex));
      double ntokens_in_doc = 0;
      for(size_t t = 0; t < NTOPICS; ++t) {
        const double value = std::max(count_type(factor[t]), count_type(0));
        ret.lik_topics += lgamma(value + ALPHA);
        ntokens_in_doc += factor[t];
      }
      ret.lik_topics -= lgamma(ntokens_in_doc + NTOPICS * ALPHA);
    }
    return ret;
  } // end of map function

  static void finalize(icontext_type& context, const likelihood_aggregator& total) {
    using boost::math::lgamma;
    // Address the global sum terms
    double denominator = 0;
    for(size_t t = 0; t < NTOPICS; ++t) {
      denominator += lgamma(GLOBAL_TOPIC_COUNT[t] + NWORDS * BETA);
    } // end of for loop

    const double lik_words_given_topics =
      NTOPICS * (lgamma(NWORDS * BETA) - NWORDS * lgamma(BETA)) -
      denominator + total.lik_words_given_topics;

    const double lik_topics =
      NDOCS * (lgamma(NTOPICS * ALPHA) - NTOPICS * lgamma(ALPHA)) +
      total.lik_topics;

    const double lik = lik_words_given_topics + lik_topics;
    context.cout() << "Likelihood: " << lik << std::endl;
  } // end of finalize
}; // end of likelihood_aggregator struct

/**
 * \brief The selective signal functions are used to signal only the
 * vertices corresponding to words or documents.  This is done by
 * using the iengine::map_reduce_vertices function.
 */
struct signal_only {
  /**
   * \brief Signal only the document vertices and skip the word
   * vertices.
   */
  static graphlab::empty
  docs(icontext_type& context, const graph_type::vertex_type& vertex) {
    if(is_doc(vertex)) context.signal(vertex);
    return graphlab::empty();
  } // end of signal_docs

 /**
  * \brief Signal only the word vertices and skip the document
  * vertices.
  */
  static graphlab::empty
  words(icontext_type& context, const graph_type::vertex_type& vertex) {
    if(is_word(vertex)) context.signal(vertex);
    return graphlab::empty();
  } // end of signal_words
}; // end of selective_only

/**
 * \brief Load the dictionary global variable from the file containing
 * the terms (one term per line).
 *
 * Note that while graphs can be loaded from multiple files the
 * dictionary must be in a single file.  The dictionary is loaded
 * entirely into memory and used to display word clouds and the top
 * terms in each topic.
 *
 * \param [in] fname the file containing the dictionary data.  The
 * data can be located on HDFS and can also be gzipped (must end in
 * ".gz").
 *
 */
bool load_dictionary(const std::string& fname)  {
  // std::cout << "staring load on: "
  //           << graphlab::get_local_ip_as_str() << std::endl;
  const bool gzip = boost::ends_with(fname, ".gz");
  // test to see if the graph_dir is an hadoop path

    std::cout << "opening: " << fname << std::endl;
    std::ifstream in_file(fname.c_str(),
                          std::ios_base::in | std::ios_base::binary);
    boost::iostreams::filtering_stream<boost::iostreams::input> fin;
    fin.push(in_file);
    if(!fin.good() || !fin.good()) {
      logstream(LOG_ERROR) << "Error loading dictionary: "
                           << fname << std::endl;
      return false;
    }
    std::string term;
    std::cout << "Loooping" << std::endl;
    while(std::getline(fin, term).good()) DICTIONARY.push_back(term);
    fin.pop();
    in_file.close();
    // std::cout << "Finished load on: "
  //           << graphlab::get_local_ip_as_str() << std::endl;
  std::cout << "Dictionary Size: " << DICTIONARY.size() << std::endl;
  return true;
} // end of load dictionary

struct count_saver {
  bool save_words;
  count_saver(bool save_words) : save_words(save_words) { }
  typedef graph_type::vertex_type vertex_type;
  typedef graph_type::edge_type   edge_type;
  std::string save_vertex(const vertex_type& vertex) const {
    // Skip saving vertex data if the vertex type is not consistent
    // with the save type
    if((save_words && is_doc(vertex)) ||
       (!save_words && is_word(vertex))) return "";
    // Proceed to save
    std::stringstream strm;
    if(save_words) {
      const graphlab::vertex_id_type vid = vertex.id();
      strm << vid << '\t';
    } else { // save documents
      const graphlab::vertex_id_type vid = (-vertex.id()) - 2;
      strm << vid << '\t';
    }
    const factor_type& factor = vertex.data().factor;
    for(size_t i = 0; i < factor.size(); ++i) {
      strm << factor[i];
      if(i+1 < factor.size()) strm << '\t';
    }
    strm << '\n';
    return strm.str();
  }
  std::string save_edge(const edge_type& edge) const {
    return ""; //nop
  }
}; // end of prediction_saver