// File: finetune_classifier.cc
// Author: Karl Moritz Hermann (mail@karlmoritz.com)
// Created: 29-01-2013
// Last Update: Thu 03 Oct 2013 11:26:25 AM BST

// STL
#include <iostream>
#include <algorithm>
#include <random>
#include <chrono>

// Boost
#include <boost/program_options/variables_map.hpp>
#include <boost/program_options/parsers.hpp>

// L-BFGS
#include <lbfgs.h>

// Local
#include "finetune_classifier.h"
#include "models.h"
#include "fast_math.h"

using namespace std;
namespace bpo = boost::program_options;

FinetuneClassifier::FinetuneClassifier(RecursiveAutoencoderBase& rae,
    TrainingCorpus& trainC, float lambdaF, float alpha,
    int dynamic_mode, int iterations) : lambda(lambdaF), alpha_rae(alpha),
  mode(dynamic_mode), iterations(iterations), it_count(0), num_batches(100),
  eta(0.1) {

  /***************************************************************************
   *             Define a couple of frequently needed variables              *
   ***************************************************************************/

  train_length = min(rae.config.num_sentences,int(trainC.size()));
  bool use_full_corpus = false;
  if (train_length == 0)
  {
    train_length = trainC.size();
    use_full_corpus = true;
  }

  label_width = rae.config.label_class_size;
  num_label_types = rae.config.num_label_types; // 1

  int multiplier = 1;
  if (mode == 0)
    multiplier = 2;
  if (mode == 3)
    multiplier = 3; // only works for compound test
  if (mode == 4)
    multiplier = 4;
  // Embedding: s1 + s2 + cos_sim(s1,s2) + len(s1) + len(s2) +
  // unigram_overlap(s1,s2) following Blacoe/Lapata 2012
  dynamic_embedding_size = multiplier * rae.config.word_representation_size;

  theta_size_ = dynamic_embedding_size * label_width * num_label_types + label_width * num_label_types;

  trainI_ = new Real[train_length * dynamic_embedding_size]();
  theta_  = new Real[theta_size_];
  WeightVectorType theta(theta_,theta_size_);
  theta.setZero();
  if (true) {
    std::random_device rd;
    std::mt19937 gen(rd());
    //std::mt19937 gen(0);
    float r = sqrt( 6.0 / dynamic_embedding_size);
    std::uniform_real_distribution<> dis(-r,r);
    for (int i=0; i<theta_size_; i++)
      theta(i) = dis(gen);
  }

  Real* ptr = trainI_;
  for (auto i=0; i<train_length; ++i) {
    int j = i;
    if ((not use_full_corpus) and (i%2 == 1))
      j = trainC.size() - i;
    trainData.push_back(VectorLabelPair(WeightVectorType(ptr, dynamic_embedding_size),trainC[j].value));
    mix.push_back(i);
    ptr += dynamic_embedding_size;
  }

  ptr = theta_;
  for (auto i=0; i<num_label_types; ++i) {
    Wcat.push_back(WeightMatrixType(ptr, label_width, dynamic_embedding_size));
    ptr += label_width * dynamic_embedding_size;
  }
  for (auto i=0; i<num_label_types; ++i) {
    Bcat.push_back(WeightVectorType(ptr, label_width));
    Bcat.back().setZero(); // discuss ..
    ptr += label_width;
  }

  /***************************************************************************
   *        Populate train input with forward Propagation and tricks         *
   ***************************************************************************/

#pragma omp parallel for schedule(dynamic)
  for (auto i = 0; i<train_length; ++i)
  {
    int j = i;
    if ((not use_full_corpus) and (i%2 == 1))
      j = trainC.size() - i;

    SinglePropBase* propagator = nullptr;

    Bools bools;
    if(rae.config.tree == TREE_CCG or rae.config.tree == TREE_STANFORD)
      propagator = rae.getSingleProp(trainC[j],0.5,bools);
    assert (propagator != nullptr);

    propagator->forwardPropagate(true);
    propagator->setDynamic(trainData[i].vector,mode);
    //cout << "C: " << trainData[i].vector[0] << endl;

    delete propagator;
  }
}

void FinetuneClassifier::evaluate()
{

  vector<VectorLabelPair>& data = trainData;
  int length = train_length;

  int right = 0;
  int wrong = 0;
  int tp = 0;
  int fp = 0;
  int tn = 0;
  int fn = 0;

#pragma omp parallel for schedule(dynamic)
  for (auto i = 0; i<length; ++i)
  {

    // Encode input
    ArrayReal label_vec = (
        Wcat[0] * data[i].vector + Bcat[0]
        ).unaryExpr(std::ptr_fun(getSigmoid)).array();

    ArrayReal lbl_sm = data[i].label - label_vec;

#pragma omp critical
    {
      if(abs(lbl_sm.sum()) > 0.5)
      {
        wrong += 1;
        if(data[i].label == 0)  ++fp;
        else                    ++fn;
      }
      else
      {
        right += 1;
        if(data[i].label == 0)  ++tn;
        else                    ++tp;
      }
    }
  }
    cout << right << "/" << right + wrong << "  ";
    Real precision = 1.0 * tp / (tp + fp);
    Real recall    = 1.0 * tp / (tp + fn);
    Real accuracy  = 1.0 * (tp + tn) / (tp + tn + fp + fn);
    Real f1score   = 2.0 * (precision * recall) / (precision + recall);
    cout << "Acc/F1: " << accuracy << " " << f1score << endl;
}

void FinetuneClassifier::trainLbfgs(LineSearchType linesearch)
{
  batch_from = 0;
  batch_to = train_length;

  lbfgs_parameter_t param;
  lbfgs_parameter_init(&param);
  param.linesearch = linesearch;
  param.max_iterations = iterations;
  //param.epsilon = 0.00000001;
  param.m = 25;

  const int n = theta_size_;
  auto vars = theta_;
  Real error = 0.0;

  int tries = 0;

  while (tries < 3 and it_count < 250)
  {
    int ret = lbfgs(n, vars, &error, lbfgs_evaluate_, lbfgs_progress_, this, &param);
    cout << "L-BFGS optimization terminated with status code = " << ret << endl;
    cout << "fx=" << error << endl;
    ++tries;
  }
}

void FinetuneClassifier::trainAdaGrad()
{
  auto vars = theta_;
  int number_vars = theta_size_;

  WeightArrayType theta(vars,number_vars);

  Real* Gt_d = new Real[number_vars];
  Real* Ginv_d = new Real[number_vars];
  WeightArrayType Gt(Gt_d,number_vars);
  WeightArrayType Ginv(Ginv_d,number_vars);
  Gt.setZero();

  Real* data1 = new Real[number_vars];

  int batchsize = (train_length / num_batches) + 1;
  cout << "Batch size: " << batchsize << endl;

  for (auto iteration = 0; iteration < iterations; ++iteration)
  {
    //unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
    random_shuffle (mix.begin(), mix.end()); //, std::default_random_engine(seed));

    for (auto batch = 0; batch < num_batches; ++batch)
    {
      batch_from = batch*batchsize;
      batch_to = min((batch+1)*batchsize,train_length);

      if (batch_to - batch_from > 0)
      {
        WeightArrayType grad(data1,number_vars);

        //float err =
        finetuneCostAndGrad_(theta_,data1,number_vars);
        //grad /= (batch_to - batch_from);
        Gt += grad*grad;
        for (int i=0;i<number_vars;i++) {
          if (Gt_d[i] == 0)
            Ginv_d[i] = 0;
          else
            Ginv_d[i] = sqrt(1/Gt_d[i]);
        }

        grad *= Ginv;
        grad *= eta;

        //cout << theta.abs().sum() << " vs " << grad.abs().sum() << endl;
        theta -= grad;
      }
    }

    evaluate();
  }

  delete [] data1;
  delete [] Gt_d;
  delete [] Ginv_d;

}

lbfgsfloatval_t FinetuneClassifier::lbfgs_evaluate_(
    void *instance,
    const lbfgsfloatval_t *x,
    lbfgsfloatval_t *g,
    const int n,
    const lbfgsfloatval_t step
    )
{
  return reinterpret_cast<FinetuneClassifier*>(instance)->finetuneCostAndGrad_(x, g, n);
}

int FinetuneClassifier::lbfgs_progress_(
    void *instance,
    const lbfgsfloatval_t *x,
    const lbfgsfloatval_t *g,
    const lbfgsfloatval_t fx,
    const lbfgsfloatval_t xnorm,
    const lbfgsfloatval_t gnorm,
    const lbfgsfloatval_t step,
    int n,
    int k,
    int ls
    )
{
  cout << "N: " << n << endl;
  printf("Iteration %d:\n", k);
  printf("  fx = %f, x[0] = %f, x[1] = %f %f\n", fx, x[0], x[1], x[2]);
  printf("  fx = %f, g[0] = %f, g[1] = %f %f\n", fx, g[0], g[1], g[2]);
  printf("  xnorm = %f, gnorm = %f, step = %f\n", xnorm, gnorm, step);
  printf("\n");

  reinterpret_cast<FinetuneClassifier*>(instance)->it_count++;
  reinterpret_cast<FinetuneClassifier*>(instance)->evaluate();
  return 0;
}

// ForwardPropagates and returns error and gradient on self
lbfgsfloatval_t FinetuneClassifier::finetuneCostAndGrad_(
    const lbfgsfloatval_t *x,
    lbfgsfloatval_t *gradient_location,
    const int n)
{

  WeightVectorType  grad(gradient_location,theta_size_);
  grad.setZero();

  WeightMatricesType Wcatgrad;
  WeightVectorsType  Bcatgrad;

  Real* ptr = gradient_location;
  for (auto i=0; i<num_label_types; ++i) {
    Wcatgrad.push_back(WeightMatrixType(ptr, label_width, dynamic_embedding_size));
    ptr += label_width * dynamic_embedding_size;
  }
  for (auto i=0; i<num_label_types; ++i) {
    Bcatgrad.push_back(WeightVectorType(ptr, label_width));
    ptr += label_width;
  }

  assert(ptr == theta_size_ + gradient_location);

  /***************************************************************************
   *        Populate train input with forward Propagation and tricks         *
   ***************************************************************************/
  Real cost = 0.0;
  int right = 0;
  int wrong = 0;
  int tp = 0;
  int fp = 0;
  int tn = 0;
  int fn = 0;

#pragma omp parallel for schedule(dynamic)
  for (auto k = batch_from; k<batch_to; ++k)
  {
    auto i = mix[k];

    // Encode input
    ArrayReal label_vec = (
        Wcat[0] * trainData[i].vector + Bcat[0]
        ).unaryExpr(std::ptr_fun(getSigmoid)).array();

    ArrayReal lbl_sm = label_vec - trainData[i].label;
    ArrayReal delta = lbl_sm * (label_vec) * (1 - label_vec);

#pragma omp critical
    {
      //cout << "D/D2" << delta << " " << delta2 << endl;
      cost += 0.5 * (lbl_sm * lbl_sm).sum();
      Wcatgrad[0] += delta.matrix() * trainData[i].vector.transpose();
      Bcatgrad[0] += delta.matrix();

      if(abs(lbl_sm.sum()) > 0.5)
      {
        wrong += 1;
        if(trainData[i].label == 0)
          ++fp;
        else
          ++fn;
      }
      else
      {
        right += 1;
        if(trainData[i].label == 0)
          ++tn;
        else
          ++tp;
      }
    }
  }

  /*
   *cout << "Correct: " << right << "/" << right + wrong << "   ";
   *cout << "Zero: " << tp << "/" << tp+fn << " ";
   *cout << "One:  " << tn << "/" << tn+fp << endl;
   */

  float lambda_partial = lambda * (batch_to - batch_from) / train_length;
  Wcatgrad[0] += lambda_partial*Wcat[0];
  cost += 0.5*lambda_partial*(Wcat[0].cwiseProduct(Wcat[0])).sum();

  Wcatgrad[0] /= (batch_to - batch_from);
  cost /= (batch_to - batch_from);

  return cost;
}

FinetuneClassifier::~FinetuneClassifier()
{
  delete [] trainI_;
  delete [] theta_;

}


#ifndef BOOST_NUMERIC_CHECKED_RESULT_OPERATIONS
#define BOOST_NUMERIC_CHECKED_RESULT_OPERATIONS

//  Copyright (c) 2012 Robert Ramey
//
// 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)

// Implemenation of arithmetic on "extended" integers.
// Extended integers are defined in terms of C++ primitive integers as
//     a) an interger range
//     b) extra elements +inf, -inf, indeterminate
//
// Integer operations are closed on the set of extended integers
// but operations are not necessarily associative when they result in the
// extensions +inf, -inf, and indeterminate
//
// in this code, the type "checked_result<T>" where T is some
// integer type is an "extended" integer.

#include <cassert>

#include <boost/logic/tribool.hpp>

#include "checked_result.hpp"
#include "checked_integer.hpp"

//////////////////////////////////////////////////////////////////////////
// the following idea of "value_type" is used by several of the operations
// defined by checked_result arithmetic.

namespace boost {
namespace safe_numerics {

template<typename T>
constexpr inline void display(const boost::safe_numerics::checked_result<T> & c){
    switch(c.m_e){
    case safe_numerics_error::success:
        std::terminate();
    case safe_numerics_error::positive_overflow_error:    // result is above representational maximum
        std::terminate();
    case safe_numerics_error::negative_overflow_error:    // result is below representational minimum
        std::terminate();
    case safe_numerics_error::domain_error:               // one operand is out of valid range
        std::terminate();
    case safe_numerics_error::range_error:                // result cannot be produced for this operation
        std::terminate();
    case safe_numerics_error::precision_overflow_error:   // result lost precision
        std::terminate();
    case safe_numerics_error::underflow_error:            // result is too small to be represented
        std::terminate();
    case safe_numerics_error::negative_value_shift:       // negative value in shift operator
        std::terminate();
    case safe_numerics_error::negative_shift:             // shift a negative value
        std::terminate();
    case safe_numerics_error::shift_too_large:            // l/r shift exceeds variable size
        std::terminate();
    case safe_numerics_error::uninitialized_value:        // creating of uninitialized value
        std::terminate();
    }
}

//////////////////////////////////////////////////////////////////////////
// implement C++ operators for check_result<T>

struct sum_value_type {
    // characterization of various values
    const enum flag {
        known_value = 0,
        less_than_min,
        greater_than_max,
        indeterminate,
        count
    } m_flag;
    template<class T>
    constexpr flag to_flag(const checked_result<T> & t) const {
        switch(static_cast<safe_numerics_error>(t)){
        case safe_numerics_error::success:
            return known_value;
        case safe_numerics_error::negative_overflow_error:
            // result is below representational minimum
            return less_than_min;
        case safe_numerics_error::positive_overflow_error:
            // result is above representational maximum
            return greater_than_max;
        default:
            return indeterminate;
        }
    }
    template<class T>
    constexpr sum_value_type(const checked_result<T> & t) :
        m_flag(to_flag(t))
    {}
    constexpr operator std::uint8_t () const {
        return static_cast<std::uint8_t>(m_flag);
    }
};

// integers addition
template<class T>
typename std::enable_if<
    std::is_integral<T>::value,
    checked_result<T>
>::type
constexpr inline operator+(
    const checked_result<T> & t,
    const checked_result<T> & u
){
    using value_type = sum_value_type;
    const std::uint8_t order = static_cast<std::uint8_t>(value_type::count);

    // note major pain.  Clang constexpr multi-dimensional array is fine.
    // but gcc doesn't permit a multi-dimensional array to be be constexpr.
    // so we need to some ugly gymnastics to make our system work for all
    // all systems.
    const enum safe_numerics_error result[order * order] = {
        // t == known_value
        //{
            // u == ...
            safe_numerics_error::success,                   // known_value,
            safe_numerics_error::negative_overflow_error,   // less_than_min,
            safe_numerics_error::positive_overflow_error,   // greater_than_max,
            safe_numerics_error::range_error,               // indeterminate,
        //},
        // t == less_than_min,
        //{
            // u == ...
            safe_numerics_error::negative_overflow_error,   // known_value,
            safe_numerics_error::negative_overflow_error,   // less_than_min,
            safe_numerics_error::range_error,               // greater_than_max,
            safe_numerics_error::range_error,               // indeterminate,
        //},
        // t == greater_than_max,
        //{
            // u == ...
            safe_numerics_error::positive_overflow_error,   // known_value,
            safe_numerics_error::range_error,               // less_than_min,
            safe_numerics_error::positive_overflow_error,   // greater_than_max,
            safe_numerics_error::range_error,               // indeterminate,
        //},
        // t == indeterminate,
        //{
            // u == ...
            safe_numerics_error::range_error,      // known_value,
            safe_numerics_error::range_error,      // less_than_min,
            safe_numerics_error::range_error,      // greater_than_max,
            safe_numerics_error::range_error,      // indeterminate,
        //},
    };

    const value_type tx(t);
    const value_type ux(u);

    const safe_numerics_error e = result[tx * order + ux];
    if(safe_numerics_error::success == e)
        return checked::add<T>(t, u);
    return checked_result<T>(e, "addition result");
}

// unary +
template<class T>
typename std::enable_if<
    std::is_integral<T>::value,
    checked_result<T>
>::type
constexpr inline operator+(
    const checked_result<T> & t
){
    return t;
}

// integers subtraction
template<class T>
typename std::enable_if<
    std::is_integral<T>::value,
    checked_result<T>
>::type
constexpr inline operator-(
    const checked_result<T> & t,
    const checked_result<T> & u
){
    using value_type = sum_value_type;
    constexpr const std::uint8_t order = static_cast<std::uint8_t>(value_type::count);

    constexpr const enum safe_numerics_error result[order * order] = {
        // t == known_value
        //{
            // u == ...
            safe_numerics_error::success,                   // known_value,
            safe_numerics_error::positive_overflow_error,   // less_than_min,
            safe_numerics_error::negative_overflow_error,   // greater_than_max,
            safe_numerics_error::range_error,               // indeterminate,
        //},
        // t == less_than_min,
        //{
            // u == ...
            safe_numerics_error::negative_overflow_error,   // known_value,
            safe_numerics_error::range_error,               // less_than_min,
            safe_numerics_error::negative_overflow_error,   // greater_than_max,
            safe_numerics_error::range_error,               // indeterminate,
        //},
        // t == greater_than_max,
        //{
            // u == ...
            safe_numerics_error::positive_overflow_error,   // known_value,
            safe_numerics_error::positive_overflow_error,   // less_than_min,
            safe_numerics_error::range_error,               // greater_than_max,
            safe_numerics_error::range_error,               // indeterminate,
        //},
        // t == indeterminate,
        //{
            // u == ...
            safe_numerics_error::range_error,               // known_value,
            safe_numerics_error::range_error,               // less_than_min,
            safe_numerics_error::range_error,               // greater_than_max,
            safe_numerics_error::range_error,               // indeterminate,
        //},
    };

    const value_type tx(t);
    const value_type ux(u);

    const safe_numerics_error e = result[tx * order + ux];
    if(safe_numerics_error::success == e)
        return checked::subtract<T>(t, u);
    return checked_result<T>(e, "subtraction result");
}

// unary -
template<class T>
typename std::enable_if<
    std::is_integral<T>::value,
    checked_result<T>
>::type
constexpr inline operator-(
    const checked_result<T> & t
){
//    assert(false);
    return checked_result<T>(0) - t;
}

struct product_value_type {
    // characterization of various values
    const enum flag {
        less_than_min = 0,
        less_than_zero,
        zero,
        greater_than_zero,
        greater_than_max,
        indeterminate,
        // count of number of cases for values
        count,
        // temporary values for special cases
        t_value,
        u_value,
        z_value
    } m_flag;
    template<class T>
    constexpr flag to_flag(const checked_result<T> & t) const {
        switch(static_cast<safe_numerics_error>(t)){
        case safe_numerics_error::success:
            return (t < checked_result<T>(0))
                ? less_than_zero
                : (t > checked_result<T>(0))
                ? greater_than_zero
                : zero;
        case safe_numerics_error::negative_overflow_error:
            // result is below representational minimum
            return less_than_min;
        case safe_numerics_error::positive_overflow_error:
            // result is above representational maximum
            return greater_than_max;
        default:
            return indeterminate;
        }
    }
    template<class T>
    constexpr product_value_type(const checked_result<T> & t) :
        m_flag(to_flag(t))
    {}
    constexpr operator std::uint8_t () const {
        return static_cast<std::uint8_t>(m_flag);
    }
};

// integers multiplication
template<class T>
typename std::enable_if<
    std::is_integral<T>::value,
    checked_result<T>
>::type
constexpr inline operator*(
    const checked_result<T> & t,
    const checked_result<T> & u
){
    using value_type = product_value_type;
    const std::uint8_t order = static_cast<std::uint8_t>(value_type::count);

    constexpr const enum value_type::flag result[order * order] = {
        // t == less_than_min
        //{
            // u == ...
            value_type::greater_than_max,   // less_than_min,
            value_type::greater_than_max,   // less_than_zero,
            value_type::zero,               // zero,
            value_type::less_than_min,      // greater_than_zero,
            value_type::less_than_min,      // greater than max,
            value_type::indeterminate,      // indeterminate,
        //},
        // t == less_than_zero,
        //{
            // u == ...
            value_type::greater_than_max,   // less_than_min,
            value_type::greater_than_zero,  // less_than_zero,
            value_type::zero,               // zero,
            value_type::less_than_zero,     // greater_than_zero,
            value_type::less_than_min,      // greater than max,
            value_type::indeterminate,      // indeterminate,
        //},
        // t == zero,
        //{
            // u == ...
            value_type::zero,               // less_than_min,
            value_type::zero,               // less_than_zero,
            value_type::zero,               // zero,
            value_type::zero,               // greater_than_zero,
            value_type::zero,               // greater than max,
            value_type::indeterminate,      // indeterminate,
        //},
        // t == greater_than_zero,
        //{
            // u == ...
            value_type::less_than_min,      // less_than_min,
            value_type::less_than_zero,     // less_than_zero,
            value_type::zero,               // zero,
            value_type::greater_than_zero,  // greater_than_zero,
            value_type::greater_than_max,   // greater than max,
            value_type::indeterminate,      // indeterminate,
        //},
        // t == greater_than_max
        //{
            value_type::less_than_min,      // less_than_min,
            value_type::less_than_min,      // less_than_zero,
            value_type::zero,               // zero,
            value_type::greater_than_max,   // greater_than_zero,
            value_type::greater_than_max,   // greater than max,
            value_type::indeterminate,      // indeterminate,
        //},
        // t == indeterminate
        //{
            value_type::indeterminate,      // less_than_min,
            value_type::indeterminate,      // less_than_zero,
            value_type::indeterminate,      // zero,
            value_type::indeterminate,      // greater_than_zero,
            value_type::indeterminate,      // greater than max,
            value_type::indeterminate,      // indeterminate,
        //}
    };

    const value_type tx(t);
    const value_type ux(u);

    switch(result[tx * order + ux]){
        case value_type::less_than_min:
            return safe_numerics_error::negative_overflow_error;
        case value_type::zero:
            return T(0);
        case value_type::greater_than_max:
            return safe_numerics_error::positive_overflow_error;
        case value_type::less_than_zero:
        case value_type::greater_than_zero:
            return checked::multiply<T>(t, u);
        case value_type::indeterminate:
            return safe_numerics_error::range_error;
        default:
            assert(false);
        }
    return checked_result<T>(0); // to suppress msvc warning
}

// integers division
template<class T>
typename std::enable_if<
    std::is_integral<T>::value,
    checked_result<T>
>::type
constexpr inline operator/(
    const checked_result<T> & t,
    const checked_result<T> & u
){
    using value_type = product_value_type;
    const std::uint8_t order = static_cast<std::uint8_t>(value_type::count);

    constexpr const enum value_type::flag result[order * order] = {
        // t == less_than_min
        //{
            // u == ...
            value_type::indeterminate,   // less_than_min,
            value_type::greater_than_max,   // less_than_zero,
            value_type::less_than_min,      // zero,
            value_type::less_than_min,      // greater_than_zero,
            value_type::less_than_min,      // greater than max,
            value_type::indeterminate,      // indeterminate,
        //},
        // t == less_than_zero,
        //{
            // u == ...
            value_type::zero,               // less_than_min,
            value_type::greater_than_zero,  // less_than_zero,
            value_type::less_than_min,      // zero,
            value_type::less_than_zero,     // greater_than_zero,
            value_type::zero,               // greater than max,
            value_type::indeterminate,      // indeterminate,
        //},
        // t == zero,
        //{
            // u == ...
            value_type::zero,               // less_than_min,
            value_type::zero,               // less_than_zero,
            value_type::indeterminate,      // zero,
            value_type::zero,               // greater_than_zero,
            value_type::zero,               // greater than max,
            value_type::indeterminate,               // indeterminate,
        //},
        // t == greater_than_zero,
        //{
            // u == ...
            value_type::zero,               // less_than_min,
            value_type::less_than_zero,     // less_than_zero,
            value_type::greater_than_max,   // zero,
            value_type::greater_than_zero,  // greater_than_zero,
            value_type::zero,               // greater than max,
            value_type::indeterminate,      // indeterminate,
        //},
        // t == greater_than_max
        //{
            value_type::less_than_min,      // less_than_min,
            value_type::less_than_min,      // less_than_zero,
            value_type::greater_than_max,   // zero,
            value_type::greater_than_max,   // greater_than_zero,
            value_type::indeterminate,   // greater than max,
            value_type::indeterminate,      // indeterminate,
        //},
        // t == indeterminate
        //{
            value_type::indeterminate,      // less_than_min,
            value_type::indeterminate,      // less_than_zero,
            value_type::indeterminate,      // zero,
            value_type::indeterminate,      // greater_than_zero,
            value_type::indeterminate,      // greater than max,
            value_type::indeterminate,      // indeterminate,
        //}
    };

    const value_type tx(t);
    const value_type ux(u);

    switch(result[tx * order + ux]){
        case value_type::less_than_min:
            return safe_numerics_error::negative_overflow_error;
        case value_type::zero:
            return 0;
        case value_type::greater_than_max:
            return safe_numerics_error::positive_overflow_error;
        case value_type::less_than_zero:
        case value_type::greater_than_zero:
            return checked::divide<T>(t, u);
        case value_type::indeterminate:
            return safe_numerics_error::range_error;
        default:
            assert(false);
    }
    return checked_result<T>(0); // to suppress msvc warning
}

// integers modulus
template<class T>
typename std::enable_if<
    std::is_integral<T>::value,
    checked_result<T>
>::type
constexpr inline operator%(
    const checked_result<T> & t,
    const checked_result<T> & u
){
    using value_type = product_value_type;
    const std::uint8_t order = static_cast<std::uint8_t>(value_type::count);

    constexpr const enum value_type::flag result[order * order] = {
        // t == less_than_min
        //{
            // u == ...
            value_type::indeterminate,      // less_than_min,
            value_type::z_value,            // less_than_zero,
            value_type::indeterminate,      // zero,
            value_type::z_value,            // greater_than_zero,
            value_type::indeterminate,      // greater than max,
            value_type::indeterminate,      // indeterminate,
        //},
        // t == less_than_zero,
        //{
            // u == ...
            value_type::t_value,            // less_than_min,
            value_type::greater_than_zero,  // less_than_zero,
            value_type::indeterminate,      // zero,
            value_type::less_than_zero,     // greater_than_zero,
            value_type::t_value,            // greater than max,
            value_type::indeterminate,      // indeterminate,
        //},
        // t == zero,
        //{
            // u == ...
            value_type::zero,               // less_than_min,
            value_type::zero,               // less_than_zero,
            value_type::indeterminate,      // zero,
            value_type::zero,               // greater_than_zero,
            value_type::zero,               // greater than max,
            value_type::indeterminate,      // indeterminate,
        //},
        // t == greater_than_zero,
        //{
            // u == ...
            value_type::t_value,            // less_than_min,
            value_type::less_than_zero,     // less_than_zero,
            value_type::indeterminate,      // zero,
            value_type::greater_than_zero,  // greater_than_zero,
            value_type::t_value,            // greater than max,
            value_type::indeterminate,      // indeterminate,
        //},
        // t == greater_than_max
        //{
            value_type::indeterminate,      // less_than_min,
            value_type::u_value,            // less_than_zero,
            value_type::indeterminate,      // zero,
            value_type::u_value,            // greater_than_zero,
            value_type::indeterminate,      // greater than max,
            value_type::indeterminate,      // indeterminate,
        //},
        // t == indeterminate
        //{
            value_type::indeterminate,      // less_than_min,
            value_type::indeterminate,      // less_than_zero,
            value_type::indeterminate,      // zero,
            value_type::indeterminate,      // greater_than_zero,
            value_type::indeterminate,      // greater than max,
            value_type::indeterminate,      // indeterminate,
        //}
    };

    const value_type tx(t);
    const value_type ux(u);

    switch(result[tx * order + ux]){
        case value_type::zero:
            return 0;
        case value_type::less_than_zero:
        case value_type::greater_than_zero:
            return checked::modulus<T>(t, u);
        case value_type::indeterminate:
            return safe_numerics_error::range_error;
        case value_type::t_value:
            return t;
        case value_type::u_value:
            return checked::subtract<T>(u, 1);
        case value_type::z_value:
            return checked::subtract<T>(1, u);
        case value_type::greater_than_max:
        case value_type::less_than_min:
        default:
            assert(false);
    }
    // suppress msvc warning
    return checked_result<T>(0);
}

// comparison operators

template<class T>
constexpr boost::logic::tribool operator<(
    const checked_result<T> & t,
    const checked_result<T> & u
){
    using value_type = sum_value_type;
    constexpr const std::uint8_t order = static_cast<std::uint8_t>(value_type::count);

    // the question arises about how to order values of type greater_than_min.
    // that is: what should greater_than_min < greater_than_min return.
    //
    // a) return indeterminate because we're talking about the "true" values for
    //    which greater_than_min is a placholder.
    //
    // b) return false because the two values are "equal"
    //
    // for our purposes, a) seems the better interpretation.

    enum class result_type : std::uint8_t {
        runtime,
        false_value,
        true_value,
        indeterminate,
    };
    constexpr const result_type resultx[order * order]{
        // t == known_value
        //{
            // u == ...
            result_type::runtime,       // known_value,
            result_type::false_value,   // less_than_min,
            result_type::true_value,    // greater_than_max,
            result_type::indeterminate, // indeterminate,
        //},
        // t == less_than_min
        //{
            // u == ...
            result_type::true_value,    // known_value,
            result_type::indeterminate, // less_than_min, see above argument
            result_type::true_value,    // greater_than_max,
            result_type::indeterminate, // indeterminate,
        //},
        // t == greater_than_max
        //{
            // u == ...
            result_type::false_value,   // known_value,
            result_type::false_value,   // less_than_min,
            result_type::indeterminate, // greater_than_max, see above argument
            result_type::indeterminate, // indeterminate,
        //},
        // t == indeterminate
        //{
            // u == ...
            result_type::indeterminate, // known_value,
            result_type::indeterminate, // less_than_min,
            result_type::indeterminate, // greater_than_max,
            result_type::indeterminate, // indeterminate,
        //},
    };

    const value_type tx(t);
    const value_type ux(u);

    switch(resultx[tx * order + ux]){
    case result_type::runtime:
        return static_cast<const T &>(t) < static_cast<const T &>(u);
    case result_type::false_value:
        return false;
    case result_type::true_value:
        return true;
    case result_type::indeterminate:
        return boost::logic::indeterminate;
    default:
        assert(false);
    }
    return true;
}

template<class T>
constexpr boost::logic::tribool
operator>=(
    const checked_result<T> & t,
    const checked_result<T> & u
){
    return !(t < u);
}

template<class T>
constexpr boost::logic::tribool
operator>(
    const checked_result<T> & t,
    const checked_result<T> & u
){
    return u < t;
}

template<class T>
constexpr boost::logic::tribool
operator<=(
    const checked_result<T> & t,
    const checked_result<T> & u
){
    return !(u < t);
}

template<class T>
constexpr boost::logic::tribool
operator==(
    const checked_result<T> & t,
    const checked_result<T> & u
){
    using value_type = sum_value_type;
    constexpr const std::uint8_t order = static_cast<std::uint8_t>(value_type::count);

    enum class result_type : std::uint8_t {
        runtime,
        false_value,
        true_value,
        indeterminate,
    };

    constexpr const result_type result[order * order]{
        // t == known_value
        //{
            // u == ...
            result_type::runtime,       // known_value,
            result_type::false_value,   // less_than_min,
            result_type::false_value,   // greater_than_max,
            result_type::indeterminate, // indeterminate,
        //},
        // t == less_than_min
        //{
            // u == ...
            result_type::false_value,   // known_value,
            result_type::indeterminate, // less_than_min,
            result_type::false_value,   // greater_than_max,
            result_type::indeterminate, // indeterminate,
        //},
        // t == greater_than_max
        //{
            // u == ...
            result_type::false_value,   // known_value,
            result_type::false_value,   // less_than_min,
            result_type::indeterminate, // greater_than_max,
            result_type::indeterminate, // indeterminate,
        //},
        // t == indeterminate
        //{
            // u == ...
            result_type::indeterminate, // known_value,
            result_type::indeterminate, // less_than_min,
            result_type::indeterminate, // greater_than_max,
            result_type::indeterminate, // indeterminate,
        //},
    };

    const value_type tx(t);
    const value_type ux(u);

    switch(result[tx * order + ux]){
    case result_type::runtime:
        return static_cast<const T &>(t) == static_cast<const T &>(u);
    case result_type::false_value:
        return false;
    case result_type::true_value:
        return true;
    case result_type::indeterminate:
        return boost::logic::indeterminate;
    default:
        assert(false);
    }
    // suppress msvc warning - not all control paths return a value
    return false;
}

template<class T>
constexpr boost::logic::tribool
operator!=(
    const checked_result<T> & t,
    const checked_result<T> & u
){
    return ! (t == u);
}

template<class T>
typename std::enable_if<
    std::is_integral<T>::value,
    checked_result<T>
>::type
constexpr inline operator>>(
    const checked_result<T> & t,
    const checked_result<T> & u
);

template<class T>
typename std::enable_if<
    std::is_integral<T>::value,
    checked_result<T>
>::type
constexpr inline operator~(
    const checked_result<T> & t
){
//    assert(false);
    return ~t.m_r;
}

template<class T>
typename std::enable_if<
    std::is_integral<T>::value,
    checked_result<T>
>::type
constexpr inline operator<<(
    const checked_result<T> & t,
    const checked_result<T> & u
){
    using value_type = product_value_type;
    const std::uint8_t order = static_cast<std::uint8_t>(value_type::count);

    constexpr const std::uint8_t result[order * order] = {
        // t == less_than_min
        //{
            // u == ...
            1, // -1,                                           // less_than_min,
            2, // safe_numerics_error::negative_overflow_error, // less_than_zero,
            2, // safe_numerics_error::negative_overflow_error, // zero,
            2, // safe_numerics_error::negative_overflow_error, // greater_than_zero,
            2, // safe_numerics_error::negative_overflow_error, // greater than max,
            1, // safe_numerics_error::range_error,             // indeterminate,
        //},
        // t == less_than_zero,
        //{
            // u == ...
            3, // -1,                                           // less_than_min,
            4, // - (-t >> -u),                                 // less_than_zero,
            5, // safe_numerics_error::negative_overflow_error, // zero,
            6, // - (-t << u),                                  // greater_than_zero,
            2, // safe_numerics_error::negative_overflow_error, // greater than max,
            1, // safe_numerics_error::range_error,             // indeterminate,
        //},
        // t == zero,
        //{
            // u == ...
            3, // 0     // less_than_min,
            3, // 0     // less_than_zero,
            3, // 0,    // zero,
            3, // 0,    // greater_than_zero,
            3, // 0,    // greater than max,
            3, // safe_numerics_error::range_error,    // indeterminate,
        //},
        // t == greater_than_zero,
        //{
            // u == ...
            3, // 0,                                            // less_than_min,
            7, // t << -u,                                      // less_than_zero,
            5, // t,                                            // zero,
            8, // t << u                                        // greater_than_zero,
            9, // safe_numerics_error::positive_overflow_error, // greater than max,
            1, // safe_numerics_error::range_error,             // indeterminate,
        //},
        // t == greater_than_max
        //{
            // u == ...
            1, // safe_numerics_error::range_error,               // less_than_min,
            9, // safe_numerics_error::positive_overflow_error),  // less_than_zero,
            9, // safe_numerics_error::positive_overflow_error,   // zero,
            9, // safe_numerics_error::positive_overflow_error),  // greater_than_zero,
            9, // safe_numerics_error::positive_overflow_error,   // greater than max,
            1, // safe_numerics_error::range_error,               // indeterminate,
        //},
        // t == indeterminate
        //{
            1, // safe_numerics_error::range_error,    // indeterminate,
            1, // safe_numerics_error::range_error,    // indeterminate,
            1, // safe_numerics_error::range_error,    // indeterminate,
            1, // safe_numerics_error::range_error,    // indeterminate,
            1, // safe_numerics_error::range_error,    // indeterminate,
            1, // safe_numerics_error::range_error,    // indeterminate,
        //}
    };

    const value_type tx(t);
    const value_type ux(u);
    assert(tx * order + ux < order * order);

    // I had a switch(i) statment here - but it results in an ICE
    // on multiple versions of gcc.  So make the equivalent in
    // nested if statments - should be the same (more or less)
    // performancewise.
    const unsigned int i = result[tx * order + ux];
    assert(i <= 9);
    if(1 == i){
        return safe_numerics_error::range_error;
    }
    else
    if(2 == i){
        return safe_numerics_error::negative_overflow_error;
    }
    else
    if(3 == i){
        return checked_result<T>(0);
    // the following gymnastics are to handle the case where
    // a value is changed from a negative to a positive number.
    // For example, and 8 bit number t == -128.  Then -t also
    // equals -128 since 128 cannot be held in an 8 bit signed
    // integer.
    }
    else
    if(4 == i){ // - (-t >> -u)
        assert(static_cast<bool>(t < checked_result<T>(0)));
        assert(static_cast<bool>(u < checked_result<T>(0)));
        return t >> -u;
    }
    else
    if(5 == i){
        return t;
    }
    else
    if(6 == i){ // - (-t << u)
        assert(static_cast<bool>(t < checked_result<T>(0)));
        assert(static_cast<bool>(u > checked_result<T>(0)));
        const checked_result<T> temp_t = t * checked_result<T>(2);
        const checked_result<T> temp_u = u - checked_result<T>(1);
        return  - (-temp_t << temp_u);
    }
    else
    if(7 == i){  // t >> -u
        assert(static_cast<bool>(t > checked_result<T>(0)));
        assert(static_cast<bool>(u < checked_result<T>(0)));
        return t >> -u;
    }
    else
    if(8 == i){ // t << u
        assert(static_cast<bool>(t > checked_result<T>(0)));
        assert(static_cast<bool>(u > checked_result<T>(0)));
        checked_result<T> r = checked::left_shift<T>(t, u);
        return (r.m_e == safe_numerics_error::shift_too_large)
        ? checked_result<T>(safe_numerics_error::positive_overflow_error)
        : r;
    }
    else
    if(9 == i){
        return safe_numerics_error::positive_overflow_error;
    }
    else{
        assert(false);
    };
    return checked_result<T>(0); // to suppress msvc warning
}

template<class T>
typename std::enable_if<
    std::is_integral<T>::value,
    checked_result<T>
>::type
constexpr inline operator>>(
    const checked_result<T> & t,
    const checked_result<T> & u
){
    using value_type = product_value_type;
    const std::uint8_t order = static_cast<std::uint8_t>(value_type::count);

    const std::uint8_t result[order * order] = {
        // t == less_than_min
        //{
            // u == ...
            2, // safe_numerics_error::negative_overflow_error, // less_than_min,
            2, // safe_numerics_error::negative_overflow_error, // less_than_zero,
            2, // safe_numerics_error::negative_overflow_error, // zero,
            2, // safe_numerics_error::negative_overflow_error, // greater_than_zero,
            1, // safe_numerics_error::range_error,             // greater than max,
            1, // safe_numerics_error::range_error,             // indeterminate,
        //},
        // t == less_than_zero,
        //{
            // u == ...
            2, // safe_numerics_error::negative_overflow_error  // less_than_min,
            4, // - (-t << -u),                                 // less_than_zero,
            5, // safe_numerics_error::negative_overflow_error. // zero,
            6, // - (-t >> u),                                  // greater_than_zero,
            3, // 0, ? or -1                                    // greater than max,
            1, // safe_numerics_error::range_error,             // indeterminate,
        //},
        // t == zero,
        //{
            // u == ...
            3, // 0     // less_than_min,
            3, // 0     // less_than_zero,
            3, // 0,    // zero,
            3, // 0,    // greater_than_zero,
            3, // 0,    // greater than max,
            3, // safe_numerics_error::range_error,    // indeterminate,
        //},
        // t == greater_than_zero,
        //{
            // u == ...
            9, // safe_numerics_error::positive_overflow_error  // less_than_min,
            7, // t << -u,                                      // less_than_zero,
            5, // t,                                            // zero,
            8, // t >> u                                        // greater_than_zero,
            3, // 0,                                            // greater than max,
            1, // safe_numerics_error::range_error,             // indeterminate,
        //},
        // t == greater_than_max
        //{
            // u == ...
            9, // safe_numerics_error::positive_overflow_error, // less_than_min,
            9, // safe_numerics_error::positive_overflow_error, // less_than_zero,
            9, // safe_numerics_error::positive_overflow_error, // zero,
            9, // safe_numerics_error::positive_overflow_error, // greater_than_zero,
            1, // safe_numerics_error::range_error,             // greater than max,
            1, // safe_numerics_error::range_error,             // indeterminate,
        //},
        // t == indeterminate
        //{
            1, // safe_numerics_error::range_error,    // indeterminate,
            1, // safe_numerics_error::range_error,    // indeterminate,
            1, // safe_numerics_error::range_error,    // indeterminate,
            1, // safe_numerics_error::range_error,    // indeterminate,
            1, // safe_numerics_error::range_error,    // indeterminate,
            1, // safe_numerics_error::range_error,    // indeterminate,
        //}
    };

    const value_type tx(t);
    const value_type ux(u);
    assert(tx * order + ux < order * order);

    // I had a switch(i) statment here - but it results in an ICE
    // on multiple versions of gcc.  So make the equivalent in
    // nested if statments - should be the same (more or less)
    // performancewise.
    const unsigned int i = result[tx * order + ux];
    assert(i <= 9);
    if(1 == i){
        return safe_numerics_error::range_error;
    }
    else
    if(2 == i){
        return safe_numerics_error::negative_overflow_error;
    }
    else
    if(3 == i){
        return checked_result<T>(0);
    }
    else
    if(4 == i){ // - (-t << -u)
        assert(static_cast<bool>(t < checked_result<T>(0)));
        assert(static_cast<bool>(u < checked_result<T>(0)));
        return t << -u;
    }
    else
    if(5 == i){
        return t;
    }
    else
    if(6 == i){ //  - (-t >> u)
        assert(static_cast<bool>(t < checked_result<T>(0)));
        assert(static_cast<bool>(u > checked_result<T>(0)));
        const checked_result<T> temp_t = t / checked_result<T>(2);
        const checked_result<T> temp_u = u - checked_result<T>(1);
        return  - (-temp_t >> temp_u);
    }
    else
    if(7 == i){  // t << -u,
        assert(static_cast<bool>(t > checked_result<T>(0)));
        assert(static_cast<bool>(u < checked_result<T>(0)));
        return t << -u;
    }
    else
    if(8 == i){ // t >> u
        assert(static_cast<bool>(t > checked_result<T>(0)));
        assert(static_cast<bool>(u > checked_result<T>(0)));
        checked_result<T> r = checked::right_shift<T>(t, u);
        return (r.m_e == safe_numerics_error::shift_too_large)
        ? checked_result<T>(0)
        : r;
    }
    else
    if(9 == i){
        return safe_numerics_error::positive_overflow_error;
    }
    else{
        assert(false);
    };
    return checked_result<T>(0); // to suppress msvc warning
}

template<class T>
typename std::enable_if<
    std::is_integral<T>::value,
    checked_result<T>
>::type
constexpr inline operator|(
    const checked_result<T> & t,
    const checked_result<T> & u
){
    return
        t.exception() || u.exception()
        ? checked_result<T>(safe_numerics_error::range_error)
        : checked::bitwise_or<T>(
            static_cast<T>(t),
            static_cast<T>(u)
        );
}
template<class T>
typename std::enable_if<
    std::is_integral<T>::value,
    checked_result<T>
>::type
constexpr inline operator^(
    const checked_result<T> & t,
    const checked_result<T> & u
){
    return
        t.exception() || u.exception()
        ? checked_result<T>(safe_numerics_error::range_error)
        : checked::bitwise_xor<T>(
            static_cast<T>(t),
            static_cast<T>(u)
        );
}

template<class T>
typename std::enable_if<
    std::is_integral<T>::value,
    checked_result<T>
>::type
constexpr inline operator&(
    const checked_result<T> & t,
    const checked_result<T> & u
){
    return
        t.exception() || u.exception()
        ? checked_result<T>(safe_numerics_error::range_error)
        : checked::bitwise_and<T>(
            static_cast<T>(t),
            static_cast<T>(u)
        );
}

} // safe_numerics
} // boost

#include <iosfwd>

namespace std {

template<typename CharT, typename Traits, typename R>
inline std::basic_ostream<CharT, Traits> & operator<<(
    std::basic_ostream<CharT, Traits> & os,
    const boost::safe_numerics::checked_result<R> & r
){
    bool e = r.exception();
    os << e;
    if(!e)
        os << static_cast<R>(r);
    else
        os << std::error_code(r.m_e).message() << ':' << static_cast<char const *>(r);
    return os;
}

template<typename CharT, typename Traits>
inline std::basic_ostream<CharT, Traits> & operator<<(
    std::basic_ostream<CharT, Traits> & os,
    const boost::safe_numerics::checked_result<signed char> & r
){
    bool e = r.exception();
    os << e;
    if(! e)
        os << static_cast<std::int16_t>(r);
    else
        os << std::error_code(r.m_e).message() << ':' << static_cast<char const *>(r);
    return os;
}

template<typename CharT, typename Traits, typename R>
inline std::basic_istream<CharT, Traits> & operator>>(
    std::basic_istream<CharT, Traits> & is,
    boost::safe_numerics::checked_result<R> & r
){
    bool e;
    is >> e;
    if(!e)
        is >> static_cast<R>(r);
    else
        is >> std::error_code(r.m_e).message() >> ':' >> static_cast<char const *>(r);
    return is;
}

template<typename CharT, typename Traits>
inline std::basic_istream<CharT, Traits> & operator>>(
    std::basic_istream<CharT, Traits> & is,
    boost::safe_numerics::checked_result<signed char> & r
){
    bool e;
    is >> e;
    if(!e){
        std::int16_t i;
        is >> i;
        r.m_contents.m_r = static_cast<signed char>(i);
    }
    else
        is >> std::error_code(r.m_e).message() >> ':' >> static_cast<char const *>(r);
    return is;
}

} // std

/////////////////////////////////////////////////////////////////
// numeric limits for checked<R>

#include <limits>

namespace std {

template<class R>
class numeric_limits<boost::safe_numerics::checked_result<R> >
    : public std::numeric_limits<R>
{
    using this_type = boost::safe_numerics::checked_result<R>;
public:
    constexpr static this_type min() noexcept {
        return this_type(std::numeric_limits<R>::min());
    }
    constexpr static this_type max() noexcept {
        return this_type(std::numeric_limits<R>::max());
    }
};

} // std

#endif  // BOOST_NUMERIC_CHECKED_RESULT_OPERATIONS


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

#include <limits>

#include "caffe/common.hpp"
#include "caffe/quantizer.hpp"
#include "caffe/util/math_functions.hpp"
#include "caffe/util/rng.hpp"

namespace caffe {

// Integer quantized types
template<typename Dtype>
typename std::enable_if<unsigned_integer_is_same<Dtype>::value, void>::type
caffe_gemv(const CBLAS_TRANSPOSE trans_A,
           const int_tp M, const int_tp N, const Dtype alpha,
           const Dtype* a, const Dtype* x,
           const Dtype beta, Dtype* y,
           const QuantizerValues* const alpha_quant,
           const QuantizerValues* const a_quant,
           const QuantizerValues* const x_quant,
           const QuantizerValues* const beta_quant,
           const QuantizerValues* const y_quant) {

  typedef typename std::conditional<sizeof(Dtype) == 1, int16_t,
          typename std::conditional<sizeof(Dtype) == 2, int32_t,
                                    int64_t>::type>::type Difftype;
  typedef typename std::conditional<sizeof(Dtype) == 1,
                                    int32_t, int64_t>::type Acctype;

  int8_t shift_bits = (32/sizeof(Dtype)) - 1;

  int32_t mult;
  int8_t shift;
  int32_t alpha_mult;
  int8_t alpha_shift;
  int32_t beta_mult;
  int8_t beta_shift;
  Acctype y_max = y_quant->get_max<Acctype>();
  Acctype y_min = y_quant->get_min<Acctype>();
  Dtype lhs_off = a_quant->get_zero<Dtype>();
  Dtype rhs_off = x_quant->get_zero<Dtype>();
  Dtype alpha_off = alpha_quant ? alpha_quant->get_zero<Dtype>() : Dtype(0);
  Dtype beta_off = beta_quant ? beta_quant->get_zero<Dtype>() : Dtype(0);
  const Acctype result_off = y_quant->get_zero<Acctype>();

  QuantizerBase::template MultiplicativeQuantVals<int32_t>(
      a_quant, x_quant, y_quant, &mult, &shift, shift_bits);
  QuantizerBase::template MultiplicativeQuantVals<int32_t>(
      y_quant, alpha_quant, y_quant, &alpha_mult, &alpha_shift, shift_bits);
  QuantizerBase::template MultiplicativeQuantVals<int32_t>(
      y_quant, beta_quant, y_quant, &beta_mult, &beta_shift, shift_bits);

  int_tp a_inc = (trans_A == CblasNoTrans) ? 1 : N;
  int_tp y_cnt = (trans_A == CblasNoTrans) ? M : N;
  int_tp x_cnt = (trans_A == CblasNoTrans) ? N : M;
#pragma omp parallel for
  for (int_tp m = 0; m < y_cnt; m++) {
    int_tp a_index = (trans_A == CblasNoTrans) ? m * N : m;
    Acctype acc = 0;
    for (int_tp n = 0; n < x_cnt; n++) {
      Difftype a_diff = a[a_index] - lhs_off;
      Difftype x_diff = x[n] - rhs_off;
      acc += static_cast<Acctype>(a_diff) * static_cast<Acctype>(x_diff);
      a_index += a_inc;
    }
    Acctype reg = acc * (alpha_quant ? Acctype(1) : alpha);
    reg = static_cast<Acctype>((static_cast<int64_t>(reg) *
                           static_cast<int64_t>(mult)) / (1ll << shift_bits));
    if (shift >= 0) {
      reg = reg >> shift;
    } else {
      reg = reg << -shift;
    }
    if (alpha_quant) {
      Difftype alpha_diff = alpha - alpha_off;
      reg = static_cast<Acctype>(alpha_diff) * static_cast<Acctype>(reg);
      reg = static_cast<Acctype>((static_cast<int64_t>(reg) *
                     static_cast<int64_t>(alpha_mult)) / (1ll << shift_bits));
      if (alpha_shift >= 0) {
        reg = reg >> alpha_shift;
      } else {
        reg = reg << -alpha_shift;
      }
    }
    if (beta_quant) {
      Difftype beta_diff = beta - beta_off;
      Difftype c_diff = y[m] - static_cast<Difftype>(result_off);
      Acctype creg = static_cast<Acctype>(beta_diff)
                   * static_cast<Acctype>(c_diff);
      creg = static_cast<Acctype>((static_cast<int64_t>(creg) *
                      static_cast<int64_t>(beta_mult)) / (1ll << shift_bits));
      if (beta_shift >= 0) {
        creg = creg >> beta_shift;
      } else {
        creg = creg << -beta_shift;
      }
      reg = reg + creg;
    } else if (beta == Dtype(1)) {
      reg = reg + (y[m] - result_off);
    }
    reg = reg + result_off;
    y[m] = static_cast<Dtype>(std::min(std::max(reg, y_min), y_max));
  }
}

// Half precision
template<typename Dtype>
typename std::enable_if<float_is_same<Dtype>::value, void>::type
caffe_gemv(const CBLAS_TRANSPOSE trans_A,
           const int_tp M, const int_tp N, const Dtype alpha,
           const Dtype* a, const Dtype* x,
           const Dtype beta, Dtype* y,
           const QuantizerValues* const alpha_quant,
           const QuantizerValues* const a_quant,
           const QuantizerValues* const x_quant,
           const QuantizerValues* const beta_quant,
           const QuantizerValues* const y_quant) {
  int_tp a_inc = (trans_A == CblasNoTrans) ? 1 : N;
  int_tp y_cnt = (trans_A == CblasNoTrans) ? M : N;
  int_tp x_cnt = (trans_A == CblasNoTrans) ? N : M;
  for (int_tp m = 0; m < y_cnt; m++) {
    int_tp a_index = (trans_A == CblasNoTrans) ? m * N : m;
    Dtype acc = 0;
    for (int_tp n = 0; n < x_cnt; n++) {
      acc += a[a_index] * x[n];
      a_index += a_inc;
    }
    if (beta == 0)
      y[m] = acc * alpha;
    else
      y[m] = acc * alpha + beta * y[m];
  }
}

template
typename std::enable_if<float_is_same<half_fp>::value, void>::type
caffe_gemv<half_fp>(const CBLAS_TRANSPOSE trans_A,
                    const int_tp M, const int_tp N,
                    const half_fp alpha,
                    const half_fp* a, const half_fp* x,
                    const half_fp beta, half_fp* y,
                    const QuantizerValues* const alpha_quant,
                    const QuantizerValues* const a_quant,
                    const QuantizerValues* const x_quant,
                    const QuantizerValues* const beta_quant,
                    const QuantizerValues* const y_quant);
template
typename std::enable_if<unsigned_integer_is_same<uint8_t>::value, void>::type
caffe_gemv<uint8_t>(const CBLAS_TRANSPOSE trans_A,
                    const int_tp M, const int_tp N,
                    const uint8_t alpha,
                    const uint8_t* a, const uint8_t* x,
                    const uint8_t beta, uint8_t* y,
                    const QuantizerValues* const alpha_quant,
                    const QuantizerValues* const a_quant,
                    const QuantizerValues* const x_quant,
                    const QuantizerValues* const beta_quant,
                    const QuantizerValues* const y_quant);
template
typename std::enable_if<unsigned_integer_is_same<uint16_t>::value, void>::type
caffe_gemv<uint16_t>(const CBLAS_TRANSPOSE trans_A,
                     const int_tp M, const int_tp N,
                     const uint16_t alpha,
                     const uint16_t* a, const uint16_t* x,
                     const uint16_t beta, uint16_t* y,
                     const QuantizerValues* const alpha_quant,
                     const QuantizerValues* const a_quant,
                     const QuantizerValues* const x_quant,
                     const QuantizerValues* const beta_quant,
                     const QuantizerValues* const y_quant);
template
typename std::enable_if<unsigned_integer_is_same<uint32_t>::value, void>::type
caffe_gemv<uint32_t>(const CBLAS_TRANSPOSE trans_A,
                     const int_tp M, const int_tp N,
                     const uint32_t alpha,
                     const uint32_t* a, const uint32_t* x,
                     const uint32_t beta, uint32_t* y,
                     const QuantizerValues* const alpha_quant,
                     const QuantizerValues* const a_quant,
                     const QuantizerValues* const x_quant,
                     const QuantizerValues* const beta_quant,
                     const QuantizerValues* const y_quant);

template
typename std::enable_if<unsigned_integer_is_same<uint64_t>::value, void>::type
caffe_gemv<uint64_t>(const CBLAS_TRANSPOSE trans_A,
                     const int_tp M, const int_tp N,
                     const uint64_t alpha,
                     const uint64_t* a, const uint64_t* x,
                     const uint64_t beta, uint64_t* y,
                     const QuantizerValues* const alpha_quant,
                     const QuantizerValues* const a_quant,
                     const QuantizerValues* const x_quant,
                     const QuantizerValues* const beta_quant,
                     const QuantizerValues* const y_quant);

template<>
void caffe_gemv<float>(const CBLAS_TRANSPOSE trans_A, const int_tp M,
                       const int_tp N, const float alpha, const float* A,
                       const float* x, const float beta, float* y,
                       const QuantizerValues* const alpha_quant,
                       const QuantizerValues* const a_quant,
                       const QuantizerValues* const x_quant,
                       const QuantizerValues* const beta_quant,
                       const QuantizerValues* const y_quant) {
  cblas_sgemv(CblasRowMajor, trans_A, M, N, alpha, A, N, x, 1, beta, y, 1);
}

template<>
void caffe_gemv<double>(const CBLAS_TRANSPOSE trans_A, const int_tp M,
                        const int_tp N, const double alpha, const double* A,
                        const double* x, const double beta, double* y,
                        const QuantizerValues* const alpha_quant,
                        const QuantizerValues* const a_quant,
                        const QuantizerValues* const x_quant,
                        const QuantizerValues* const beta_quant,
                        const QuantizerValues* const y_quant) {
  cblas_dgemv(CblasRowMajor, trans_A, M, N, alpha, A, N, x, 1, beta, y, 1);
}

}  // namespace caffe