#include <boost/smart_ptr/shared_ptr.hpp>
#include <caffe/blob.hpp>
#include <caffe/common.hpp>
#include <caffe/filler.hpp>
#include <caffe/layers/dau_conv_layer.hpp>
#include <caffe/layers/cudnn_conv_layer.hpp>
#include <caffe/proto/caffe.pb.h>
#include <caffe/test/test_caffe_main.hpp>
#include <glog/logging.h>
#include <gtest/gtest.h>
#include <vector>

namespace caffe {

template <typename Dtype>
void compare_blobs(Blob<Dtype>& a, Blob<Dtype>& b, bool compare_diff, Dtype eps) {
	Dtype* data_a = compare_diff ? a.mutable_cpu_diff() : a.mutable_cpu_data();
	Dtype* data_b = compare_diff ? b.mutable_cpu_diff() : b.mutable_cpu_data();
	for (int i = 0; i < a.count(); ++i) {
		EXPECT_NEAR(data_a[i], data_b[i], eps);
	}
}

// Reference convolution for checking results:
// accumulate through explicit loops over input, output, and filters.
template <typename Dtype>
void caffe_conv(const Blob<Dtype>* in, ConvolutionParameter* conv_param,
    const vector<shared_ptr<Blob<Dtype> > >& weights,
    Blob<Dtype>* out) {
  const bool has_depth = (out->num_axes() == 5);
  if (!has_depth) { CHECK_EQ(4, out->num_axes()); }
  // Kernel size, stride, and pad
  int kernel_h, kernel_w;
  if (conv_param->has_kernel_h() || conv_param->has_kernel_w()) {
    kernel_h = conv_param->kernel_h();
    kernel_w = conv_param->kernel_w();
  } else {
    kernel_h = kernel_w = conv_param->kernel_size(0);
  }
  int pad_h, pad_w;
  if (conv_param->has_pad_h() || conv_param->has_pad_w()) {
    pad_h = conv_param->pad_h();
    pad_w = conv_param->pad_w();
  } else {
    pad_h = pad_w = conv_param->pad_size() ? conv_param->pad(0) : 0;
  }
  int stride_h, stride_w;
  if (conv_param->has_stride_h() || conv_param->has_stride_w()) {
    stride_h = conv_param->stride_h();
    stride_w = conv_param->stride_w();
  } else {
    stride_h = stride_w = conv_param->stride_size() ? conv_param->stride(0) : 1;
  }
  int kernel_d, pad_d, stride_d;
  if (has_depth) {
    kernel_d = kernel_h;
    stride_d = stride_h;
    pad_d = pad_h;
  } else {
    kernel_d = stride_d = 1;
    pad_d = 0;
  }
  // Groups
  int groups = conv_param->group();
  int o_g = out->shape(1) / groups;
  int k_g = in->shape(1) / groups;
  int o_head, k_head;
  // Convolution
  vector<int> weight_offset(4 + has_depth);
  vector<int> in_offset(4 + has_depth);
  vector<int> out_offset(4 + has_depth);
  Dtype* out_data = out->mutable_cpu_data();
  for (int n = 0; n < out->shape(0); n++) {
    for (int g = 0; g < groups; g++) {
      o_head = o_g * g;
      k_head = k_g * g;
      for (int o = 0; o < o_g; o++) {
        for (int k = 0; k < k_g; k++) {
          for (int z = 0; z < (has_depth ? out->shape(2) : 1); z++) {
            for (int y = 0; y < out->shape(2 + has_depth); y++) {
              for (int x = 0; x < out->shape(3 + has_depth); x++) {
                for (int r = 0; r < kernel_d; r++) {
                  for (int p = 0; p < kernel_h; p++) {
                    for (int q = 0; q < kernel_w; q++) {
                      int in_z = z * stride_d - pad_d + r;
                      int in_y = y * stride_h - pad_h + p;
                      int in_x = x * stride_w - pad_w + q;
                      if (in_z >= 0 && in_z < (has_depth ? in->shape(2) : 1)
                          && in_y >= 0 && in_y < in->shape(2 + has_depth)
                          && in_x >= 0 && in_x < in->shape(3 + has_depth)) {
                        weight_offset[0] = o + o_head;
                        weight_offset[1] = k;
                        if (has_depth) { weight_offset[2] = r; }
                        weight_offset[2 + has_depth] = p;
                        weight_offset[3 + has_depth] = q;
                        in_offset[0] = n;
                        in_offset[1] = k + k_head;
                        if (has_depth) { in_offset[2] = in_z; }
                        in_offset[2 + has_depth] = in_y;
                        in_offset[3 + has_depth] = in_x;
                        out_offset[0] = n;
                        out_offset[1] = o + o_head;
                        if (has_depth) { out_offset[2] = z; }
                        out_offset[2 + has_depth] = y;
                        out_offset[3 + has_depth] = x;
                        out_data[out->offset(out_offset)] +=
                            in->data_at(in_offset)
                            * weights[0]->data_at(weight_offset);
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
  // Bias
  if (conv_param->bias_term()) {
    const Dtype* bias_data = weights[1]->cpu_data();
    for (int n = 0; n < out->shape(0); n++) {
      for (int o = 0; o < out->shape(1); o++) {
        for (int z = 0; z < (has_depth ? out->shape(2) : 1); z++) {
          for (int y = 0; y < out->shape(2 + has_depth); y++) {
            for (int x = 0; x < out->shape(3 + has_depth); x++) {
              out_offset[0] = n;
              out_offset[1] = o;
              if (has_depth) { out_offset[2] = z; }
              out_offset[2 + has_depth] = y;
              out_offset[3 + has_depth] = x;
              out_data[out->offset(out_offset)] += bias_data[o];
            }
          }
        }
      }
    }
  }
}

template void caffe_conv(const Blob<float>* in,
    ConvolutionParameter* conv_param,
    const vector<shared_ptr<Blob<float> > >& weights,
    Blob<float>* out);
template void caffe_conv(const Blob<double>* in,
    ConvolutionParameter* conv_param,
    const vector<shared_ptr<Blob<double> > >& weights,
    Blob<double>* out);

template <typename TypeParam>
class DAUConvolutionLayerTest : public MultiDeviceTest<TypeParam> {
  typedef typename TypeParam::Dtype Dtype;

 protected:
  DAUConvolutionLayerTest()
      : blob_bottom_(new Blob<Dtype>(2, 3, 6, 4)),
        blob_bottom_2_(new Blob<Dtype>(2, 3, 6, 4)),
		blob_bottom_3_(new Blob<Dtype>(1, 1, 4, 4)),
		//blob_bottom_3_(new Blob<Dtype>(2, 3, 32, 48)),
        blob_top_(new Blob<Dtype>()),
		blob_top_2_(new Blob<Dtype>()),
		blob_top_3_(new Blob<Dtype>()) {}
  virtual void SetUp() {
    // fill the values
    FillerParameter filler_param;
    filler_param.set_value(1.);
    GaussianFiller<Dtype> filler(filler_param);
    filler.Fill(this->blob_bottom_);
    filler.Fill(this->blob_bottom_2_);
    filler.Fill(this->blob_bottom_3_);
    blob_bottom_vec_.push_back(blob_bottom_);
    blob_top_vec_.push_back(blob_top_);
  }

  virtual ~DAUConvolutionLayerTest() {
    delete blob_bottom_;
    delete blob_bottom_2_;
    delete blob_bottom_3_;
    delete blob_top_;
    delete blob_top_2_;
    delete blob_top_3_;
  }

  virtual Blob<Dtype>* MakeReferenceTop(Blob<Dtype>* top) {
    this->ref_blob_top_.reset(new Blob<Dtype>());
    this->ref_blob_top_->ReshapeLike(*top);
    return this->ref_blob_top_.get();
  }

  Blob<Dtype>* const blob_bottom_;
  Blob<Dtype>* const blob_bottom_2_;
  Blob<Dtype>* const blob_bottom_3_;
  Blob<Dtype>* const blob_top_;
  Blob<Dtype>* const blob_top_2_;
  Blob<Dtype>* const blob_top_3_;
  shared_ptr<Blob<Dtype> > ref_blob_top_;
  vector<Blob<Dtype>*> blob_bottom_vec_;
  vector<Blob<Dtype>*> blob_top_vec_;
};

TYPED_TEST_CASE(DAUConvolutionLayerTest, TestDtypesAndDevices);

#define OFFSET(l,k,j,i, num_l, num_k, num_j, num_i) ((( (l)*(num_k) + (k)) * (num_j) + (j))*(num_i) + (i) )

TYPED_TEST(DAUConvolutionLayerTest, TestFastGaussForward) {

    Caffe::SetDevice(0);

    typedef typename TypeParam::Dtype Dtype;

    if (Caffe::mode() == Caffe::CPU)
        return;

    if (sizeof(Dtype) > 4)
        return;

    // evaluate size settings
    const int N = 128;
    const int F = 64;
    const int S = 32;
    const int G = 2;
    const int W = 64;
    const int H = 32;

    const bool use_interpolation = true;

    const int kernel_size = 17;

    Blob<float> blob_input(N,S,H,W);
    Blob<float> blob_output(N,F,H,W);
    Blob<float> blob_output_cpu(N,F,H,W);

    FillerParameter const_zero_filler_param;
    const_zero_filler_param.set_value(0);
    ConstantFiller<int> const_zero_filer(const_zero_filler_param);
    ConstantFiller<float> const_zero_float_filer(const_zero_filler_param);

    FillerParameter rand_filler_param;
    rand_filler_param.set_std(1);
    GaussianFiller<float> input_filler(rand_filler_param);

    input_filler.Fill(&blob_input);

    const_zero_float_filer.Fill(&blob_output);

    FillerParameter offset_filler_param;
    offset_filler_param.set_min(1);
    offset_filler_param.set_max(kernel_size-2);
    UniformFiller<float> offset_filler(offset_filler_param);

    LayerParameter layer_param;

    DAUConvolutionParameter* dau_convolution_param = layer_param.mutable_dau_conv_param();

    dau_convolution_param->add_kernel_size(kernel_size);
    dau_convolution_param->add_stride(1);
    dau_convolution_param->add_pad(kernel_size/2);

    dau_convolution_param->add_number_units(G);
    dau_convolution_param->add_number_units(1);

    dau_convolution_param->set_num_output(F);

    dau_convolution_param->mutable_weight_filler()->set_type("gaussian");
    dau_convolution_param->mutable_weight_filler()->set_std(0.1);

    dau_convolution_param->mutable_bias_filler()->set_type("constant");
    dau_convolution_param->mutable_bias_filler()->set_value(0);

    dau_convolution_param->mutable_mu_filler()->set_type("constant");
    dau_convolution_param->mutable_mu_filler()->set_value(0);

    dau_convolution_param->mutable_sigma_filler()->set_type("constant");
    dau_convolution_param->mutable_sigma_filler()->set_value(0.8);

    dau_convolution_param->set_component_border_bound(0);
    dau_convolution_param->set_sigma_lower_bound(0.5);

    dau_convolution_param->set_unit_normalization(true);
    dau_convolution_param->set_square_unit_normalization(false);

    DAUConvolutionLayer<float> layer(layer_param);

    std::vector<Blob<float>* > blob_bottom_vec;
    std::vector<Blob<float>* > blob_top_vec;
    std::vector<Blob<float>* > blob_top_vec_cpu;

    blob_bottom_vec.push_back(&blob_input);

    blob_top_vec.push_back(&blob_output);
    blob_top_vec_cpu.push_back(&blob_output_cpu);

    layer.SetUp(blob_bottom_vec, blob_top_vec);

    // override offset data with random values - must be done after SetUp
    offset_filler.Fill(layer.dau_compute.param_buffer_mu1_.get());
    offset_filler.Fill(layer.dau_compute.param_buffer_mu2_.get());

    // perform forward pass with CPU version
    layer.set_processing_on_gpu(false);
    layer.Forward_cpu(blob_bottom_vec, blob_top_vec_cpu);

    // perform forward pass with GPU version
    layer.set_processing_on_gpu(true);
    layer.Forward_gpu(blob_bottom_vec, blob_top_vec);

    const float* output_cpu = blob_top_vec_cpu[0]->cpu_data();
    const float* output_gpu = blob_top_vec[0]->cpu_data();

    // verify data with CPU version
    int found_invalid = 0;

    double diff = 0;
    double max_diff = 0;

    for (int n = 0; n < N; ++n){
        for (int f = 0; f < F; ++f) {
            for (int i = 0; i < H * W; ++i) {
                int index = (n * F + f )* H * W + i;
                float val = output_gpu[index];
                float GT_VALUE = output_cpu[index];

                if (val != val) {
                    printf("error: got NaN at loc (%d=%d,%d,%d,%d) - should be %f\n",  index, n, f, i / W, i % W, GT_VALUE);
                }

                // interpolation at the right edge excludes one pixel so ignore those pixels
                if (std::abs(val - GT_VALUE) / std::abs(GT_VALUE) > 1e-4 && std::abs(val - GT_VALUE) > 1e-7 && i % W < (W -1)) {
                    if (found_invalid < 10)
                        printf("found invalid output (%f) at loc (%d=%d,%d,%d,%d) - should be %f\n", val, index,  n, f, i / W, i % W, GT_VALUE);
                    found_invalid++;

                    double current_diff = std::abs(val - GT_VALUE) / std::abs(GT_VALUE);

                    diff += current_diff;

                    max_diff = std::max(max_diff, current_diff);
                }
            }
        }
    }
    if (found_invalid > 0) {
        diff /= found_invalid;
        printf("found num of invalid output vals: %d/%d with mean diff val %f and max diff val %f\n", found_invalid, blob_output.count(), diff, max_diff);
    }

    // report fail only if enough samples with big enough diff
    EXPECT_NEAR(diff, 0, 1e-3);
    EXPECT_NEAR(found_invalid/(float)blob_output.count(), 0, 1e-2);
}

TYPED_TEST(DAUConvolutionLayerTest, TestFastGaussBackward) {

    typedef typename TypeParam::Dtype Dtype;

    Caffe::SetDevice(0);

    if (Caffe::mode() == Caffe::CPU)
        return;

    if (sizeof(Dtype) > 4)
        return;
    // evaluate size settings

    const int N = 32;
    const int F = 128;
    const int S = 96;
    const int G = 2;
    const int W = 32;
    const int H = 64;

    // number of Guassian learning parameters we have (w,mu1,mu2,sigma)
    // for each parameter we need convolution of input data with specific kernel
    const int K = 4;
    const bool use_interpolation = true;
    const bool ignore_edge_gradients = true; // for cpu/gpu compatability

    const int kernel_size = 11;

    Blob<float> blob_input(N,S,H,W);
    Blob<float> blob_error(N,F,H,W);
    Blob<float> blob_output(K, S, G, F);
    Blob<float> blob_output_cpu(K, S, G, F);

    Blob<float> blob_output_error_cpu(N,S,H,W);

    FillerParameter const_one_filler_param;
    const_one_filler_param.set_value(1);
    ConstantFiller<float> const_one_filer(const_one_filler_param);

    FillerParameter const_zero_filler_param;
    const_zero_filler_param.set_value(0);
    ConstantFiller<int> const_zero_filer(const_zero_filler_param);
    ConstantFiller<float> const_zero_float_filer(const_zero_filler_param);

    FillerParameter rand_filler_param;
    rand_filler_param.set_value(1);
    GaussianFiller<float> input_filler(rand_filler_param);

    input_filler.Fill(&blob_input);
    input_filler.Fill(&blob_error);
    caffe_rng_gaussian<float>(blob_error.count(), float(0), float(0.1), blob_error.mutable_cpu_diff());

    FillerParameter offset_filler_param;
    offset_filler_param.set_min(4);
    offset_filler_param.set_max(kernel_size - 4);

    UniformFiller<float> offset_filler(offset_filler_param);

    const_zero_float_filer.Fill(&blob_output);

    LayerParameter layer_param;
    DAUConvolutionParameter* dau_convolution_param = layer_param.mutable_dau_conv_param();

    dau_convolution_param->add_kernel_size(kernel_size);
    dau_convolution_param->add_stride(1);
    dau_convolution_param->add_pad(kernel_size/2);

    dau_convolution_param->add_number_units(G);
    dau_convolution_param->add_number_units(1);

    dau_convolution_param->set_num_output(F);

    dau_convolution_param->mutable_weight_filler()->set_type("gaussian");
    dau_convolution_param->mutable_weight_filler()->set_std(0.1);

    dau_convolution_param->mutable_bias_filler()->set_type("constant");
    dau_convolution_param->mutable_bias_filler()->set_value(0);

    dau_convolution_param->mutable_mu_filler()->set_type("constant");
    dau_convolution_param->mutable_mu_filler()->set_value(0);

    dau_convolution_param->mutable_sigma_filler()->set_type("constant");
    dau_convolution_param->mutable_sigma_filler()->set_value(0.8);

    dau_convolution_param->set_component_border_bound(0);
    dau_convolution_param->set_sigma_lower_bound(0.5);

    dau_convolution_param->set_unit_normalization(true);
    dau_convolution_param->set_square_unit_normalization(false);

    DAUConvolutionLayer<float> layer(layer_param, ignore_edge_gradients);

    std::vector<Blob<float>* > blob_bottom_vec;
    std::vector<Blob<float>* > blob_top_vec;
    std::vector<Blob<float>* > blob_top_vec_gt;
    std::vector<bool > propagate_down;
    propagate_down.push_back(true);

    blob_bottom_vec.push_back(&blob_input);
    blob_top_vec.push_back(&blob_error);

    layer.SetUp(blob_bottom_vec, blob_top_vec);

    // override offset data with random values within valid range
    offset_filler.Fill(layer.dau_compute.param_buffer_mu1_.get());
    offset_filler.Fill(layer.dau_compute.param_buffer_mu2_.get());

    caffe_gpu_set(layer.dau_compute.param_buffer_w_->count(), (Dtype)0, (Dtype*)layer.dau_compute.param_buffer_w_->mutable_gpu_diff());
    caffe_gpu_set(layer.dau_compute.param_buffer_mu1_->count(), (Dtype)0, (Dtype*)layer.dau_compute.param_buffer_mu1_->mutable_gpu_diff());
    caffe_gpu_set(layer.dau_compute.param_buffer_mu2_->count(), (Dtype)0, (Dtype*)layer.dau_compute.param_buffer_mu2_->mutable_gpu_diff());
    caffe_gpu_set(layer.dau_compute.param_buffer_sigma_->count(), (Dtype)0, (Dtype*)layer.dau_compute.param_buffer_sigma_->mutable_gpu_diff());

    layer.set_processing_on_gpu(false);
    layer.Backward_cpu(blob_top_vec, propagate_down, blob_bottom_vec);

    // save backproped error values
    float *backprop_error_cpu = blob_output_error_cpu.mutable_cpu_data();
    {
        caffe_copy(blob_output_error_cpu.count(), blob_bottom_vec[0]->cpu_diff(), backprop_error_cpu);
    }

    // save accumulated gradient values
    float *gradients_cpu = blob_output_cpu.mutable_cpu_data();
    {
        int num_params = layer.dau_compute.param_buffer_w_->count();
        if (K > 0) caffe_copy(num_params, layer.dau_compute.param_buffer_w_->cpu_diff(), gradients_cpu + 0 * num_params );
        if (K > 1) caffe_copy(num_params, layer.dau_compute.param_buffer_mu1_->cpu_diff(), gradients_cpu + 1 * num_params );
        if (K > 2) caffe_copy(num_params, layer.dau_compute.param_buffer_mu2_->cpu_diff(), gradients_cpu + 2 * num_params );
        if (K > 3) caffe_copy(num_params, layer.dau_compute.param_buffer_sigma_->cpu_diff(), gradients_cpu + 3 * num_params);
    }

    // reset values for GPU run
    caffe_gpu_set(layer.dau_compute.param_buffer_w_->count(), (Dtype)0, (Dtype*)layer.dau_compute.param_buffer_w_->mutable_gpu_diff());
    caffe_gpu_set(layer.dau_compute.param_buffer_mu1_->count(), (Dtype)0, (Dtype*)layer.dau_compute.param_buffer_mu1_->mutable_gpu_diff());
    caffe_gpu_set(layer.dau_compute.param_buffer_mu2_->count(), (Dtype)0, (Dtype*)layer.dau_compute.param_buffer_mu2_->mutable_gpu_diff());
    caffe_gpu_set(layer.dau_compute.param_buffer_sigma_->count(), (Dtype)0, (Dtype*)layer.dau_compute.param_buffer_sigma_->mutable_gpu_diff());

    layer.set_processing_on_gpu(true);
    layer.Backward_gpu(blob_top_vec, propagate_down, blob_bottom_vec);

    // get ptr to backproped error on gpu
    const float *backprop_error_gpu = blob_bottom_vec[0]->cpu_diff();

    // save accumulated gradient values
    float *gradients_gpu_g = blob_output.mutable_gpu_data();

    int num_params = layer.dau_compute.param_buffer_w_->count();
    if (K > 0) caffe_gpu_memcpy(num_params * sizeof(float), layer.dau_compute.param_buffer_w_->gpu_diff(), gradients_gpu_g + 0 * num_params );
    if (K > 1) caffe_gpu_memcpy(num_params * sizeof(float), layer.dau_compute.param_buffer_mu1_->gpu_diff(), gradients_gpu_g + 1 * num_params );
    if (K > 2) caffe_gpu_memcpy(num_params * sizeof(float), layer.dau_compute.param_buffer_mu2_->gpu_diff(), gradients_gpu_g + 2 * num_params );
    if (K > 3) caffe_gpu_memcpy(num_params * sizeof(float), layer.dau_compute.param_buffer_sigma_->gpu_diff(), gradients_gpu_g + 3 * num_params);

    // get gradients from GPU, but read them on cpu
    float *gradients_gpu = blob_output.mutable_cpu_data();

    {
        // verify accumulated gradients
        int found_invalid_backprop = 0;

        double diff_gradient = 0;
        double max_diff = 0;
        for (int k = 0; k < K - 1; ++k) { // do not test last K since it is optional by default (i.e. it will not be computed unless there is no computation penalty)
            for (int s = 0; s < S; ++s) {
                for (int g = 0; g < G; ++g) {
                    for (int f = 0; f < F; ++f) {
                        int idx = OFFSET(k,s,g,f, K, S,  G, F);
                        float val = gradients_gpu[idx];
                        float GT_VALUE = gradients_cpu[idx];

                        if (val != val) {
                            printf("error: got NaN at loc (%d=%d,%d,%d,%d) - should be %f\n", idx, k, s,g,f, GT_VALUE);
                        }

                        if (std::abs(val - GT_VALUE) / std::abs(GT_VALUE) > 1e-4 && std::abs(val - GT_VALUE) > 1e-7) {
                            if (found_invalid_backprop < 10)
                                printf("found invalid output (%f) at loc (%d=%d,%d,%d,%d) - should be %f\n", val, idx, k, s,g,f, GT_VALUE);
                            found_invalid_backprop++;
                            double current_diff = std::abs(val - GT_VALUE) / std::abs(GT_VALUE);

                            diff_gradient += current_diff;

                            max_diff = std::max(max_diff, current_diff);

                        }
                    }
                }
            }
        }

        if (found_invalid_backprop > 0) {
            diff_gradient /= found_invalid_backprop;
            printf("found num of invalid accumulated gradient vals: %d/%d with mean diff val %f and max diff val %f\n", found_invalid_backprop, K * S * G * F, diff_gradient, max_diff);
        }
        // report fail only if enough samples with big enough diff
        EXPECT_NEAR(diff_gradient, 0, 1e-2);
        EXPECT_NEAR(found_invalid_backprop/(float)(K * S * G * F), 0, 1e-2);
    }
    {
        // verify accumulated gradients
        int found_invalid_backprop = 0;

        double diff_backprop = 0;
        double max_diff = 0;
        for (int n = 0; n < N; ++n) {
            for (int s = 0; s < S; ++s) {
                for (int i = 0; i < H * W; ++i) {
                    int index = (n * S + s )* H * W + i;
                    float val = backprop_error_gpu[index];
                    float GT_VALUE = backprop_error_cpu[index];

                    if (val != val) {
                        printf("error: got NaN at loc (%d=%d,%d,%d,%d) - should be %f\n",  index, n, s, i / W, i % W, GT_VALUE);
                    }

                    if (std::abs(val - GT_VALUE) / GT_VALUE > 1e-4 && std::abs(val - GT_VALUE) > 1e-7 && i % W < (W -1)) {
                        if (found_invalid_backprop < 10)
                            printf("found invalid output (%f) at loc (%d=%d,%d,%d,%d) - should be %f\n", val, index, n, s, i / W, i % W, GT_VALUE);
                        found_invalid_backprop++;
                        diff_backprop += std::abs(val - GT_VALUE) / GT_VALUE;

                        double current_diff = std::abs(val - GT_VALUE) / GT_VALUE;

                        diff_backprop += current_diff;

                        max_diff = std::max(max_diff, current_diff);

                    }
                }
            }
        }

        if (found_invalid_backprop > 0) {
            diff_backprop /= found_invalid_backprop;
            printf("found num of invalid backproped-error vals: %d/%d with mean diff val %f and max diff val %f\n", found_invalid_backprop, N * S * H * W, diff_backprop, max_diff);
        }
        // report fail only if enough samples with big enough diff
        EXPECT_NEAR(diff_backprop, 0, 1e-3);
        EXPECT_NEAR(found_invalid_backprop/(float)(N * S * H * W), 0, 1e-2);
    }
}

TYPED_TEST(DAUConvolutionLayerTest, ProfileFastDAUConvolution) {

    Caffe::SetDevice(0);

    typedef typename TypeParam::Dtype Dtype;

    if (Caffe::mode() == Caffe::CPU)
        return;

    if (sizeof(Dtype) > 4)
        return;

    // evaluate size settings
    const int N = 128;
    const int F = 128;
    const int S = 128;
    const int G = 2;
    const int W = 64;
    const int H = 64;

    const bool use_interpolation = true;

    const int kernel_size = 17;

    Blob<float> blob_input(N,S,H,W);
    Blob<float> blob_weights(1, S, G, F);
    Blob<float> blob_output(N,F,H,W);
    Blob<float> blob_output_cpu(N,F,H,W);

    FillerParameter const_zero_filler_param;
    const_zero_filler_param.set_value(0);
    ConstantFiller<float> const_zero_float_filer(const_zero_filler_param);

    FillerParameter rand_filler_param;
    rand_filler_param.set_std(0.1);
    GaussianFiller<float> input_filler(rand_filler_param);

    input_filler.Fill(&blob_input);

    FillerParameter offset_filler_param;
    offset_filler_param.set_min(2);
    offset_filler_param.set_max(kernel_size-2);
    UniformFiller<float> offset_filler(offset_filler_param);

    const_zero_float_filer.Fill(&blob_output);

    LayerParameter layer_param;

    DAUConvolutionParameter* dau_convolution_param = layer_param.mutable_dau_conv_param();

    dau_convolution_param->add_kernel_size(kernel_size);
    dau_convolution_param->add_stride(1);
    dau_convolution_param->add_pad(kernel_size/2);

    dau_convolution_param->add_number_units(G);
    dau_convolution_param->add_number_units(1);

    dau_convolution_param->set_num_output(F);

    dau_convolution_param->mutable_weight_filler()->set_type("gaussian");
    dau_convolution_param->mutable_weight_filler()->set_std(0.1);

    dau_convolution_param->mutable_bias_filler()->set_type("constant");
    dau_convolution_param->mutable_bias_filler()->set_value(0);

    dau_convolution_param->mutable_mu_filler()->set_type("constant");
    dau_convolution_param->mutable_mu_filler()->set_value(0);

    dau_convolution_param->mutable_sigma_filler()->set_type("constant");
    dau_convolution_param->mutable_sigma_filler()->set_value(0.8);

    dau_convolution_param->set_component_border_bound(0);
    dau_convolution_param->set_sigma_lower_bound(0.5);

    dau_convolution_param->set_unit_normalization(true);
    dau_convolution_param->set_square_unit_normalization(false);

    DAUConvolutionLayer<float> layer(layer_param);

    std::vector<Blob<float>* > blob_bottom_vec;
    std::vector<Blob<float>* > blob_top_vec;

    blob_bottom_vec.push_back(&blob_input);
    blob_top_vec.push_back(&blob_output);

    for (int ii = 0; ii < 1; ++ii) {

        layer.SetUp(blob_bottom_vec, blob_top_vec);

        offset_filler.Fill(layer.dau_compute.param_buffer_mu1_.get());
        offset_filler.Fill(layer.dau_compute.param_buffer_mu2_.get());

        for (int i = 0; i < 30; ++i) {
            cudaDeviceSynchronize();

            clock_t start_t = clock();
            layer.Forward_gpu(blob_bottom_vec, blob_top_vec);

            cudaDeviceSynchronize();
            clock_t end_t = clock();

            std::cout << "fast_gauss_forward in " << (((float)(end_t-start_t))/CLOCKS_PER_SEC) << std::endl;
        }
    }
}

TYPED_TEST(DAUConvolutionLayerTest, ProfileFastGaussBackward) {

    typedef typename TypeParam::Dtype Dtype;

    Caffe::SetDevice(0);

    if (Caffe::mode() == Caffe::CPU)
        return;

    if (sizeof(Dtype) > 4)
        return;

    const int N = 128;
    const int F = 128;
    const int S = 128;
    const int G = 2;
    const int W = 64;
    const int H = 64;

    // number of Guassian learning parameters we have (w,mu1,mu2,sigma)
    // for each parameter we need convolution of input data with specific kernel
    const int K = 4;
    const bool use_interpolation = true;
    const bool ignore_edge_gradients = true; // for cpu/gpu compatability

    const int kernel_size = 11;

    Blob<float> blob_input(N,S,H,W);
    Blob<float> blob_error(N,F,H,W);
    Blob<float> blob_weights(1, S, G, F);
    Blob<float> blob_output(K, S, G, F);

    FillerParameter const_zero_filler_param;
    const_zero_filler_param.set_value(0);
    ConstantFiller<float> const_zero_float_filer(const_zero_filler_param);

    FillerParameter rand_filler_param;
    rand_filler_param.set_value(1);
    GaussianFiller<float> input_filler(rand_filler_param);

    input_filler.Fill(&blob_input);
    input_filler.Fill(&blob_weights);
    input_filler.Fill(&blob_error);
    caffe_rng_gaussian<float>(blob_error.count(), float(0), float(0.1), blob_error.mutable_cpu_diff());

    FillerParameter offset_filler_param;
    offset_filler_param.set_min(3);
    offset_filler_param.set_max(kernel_size - 3);

    UniformFiller<float> offset_filler(offset_filler_param);

    const_zero_float_filer.Fill(&blob_output);

    LayerParameter layer_param;
    DAUConvolutionParameter* dau_convolution_param = layer_param.mutable_dau_conv_param();

    dau_convolution_param->add_kernel_size(kernel_size);
    dau_convolution_param->add_stride(1);
    dau_convolution_param->add_pad(kernel_size/2);

    dau_convolution_param->add_number_units(G);
    dau_convolution_param->add_number_units(1);

    dau_convolution_param->set_num_output(F);

    dau_convolution_param->mutable_weight_filler()->set_type("gaussian");
    dau_convolution_param->mutable_weight_filler()->set_std(0.1);

    dau_convolution_param->mutable_bias_filler()->set_type("constant");
    dau_convolution_param->mutable_bias_filler()->set_value(0);

    dau_convolution_param->mutable_mu_filler()->set_type("constant");
    dau_convolution_param->mutable_mu_filler()->set_value(0);

    dau_convolution_param->mutable_sigma_filler()->set_type("constant");
    dau_convolution_param->mutable_sigma_filler()->set_value(0.8);

    dau_convolution_param->set_component_border_bound(0);
    dau_convolution_param->set_sigma_lower_bound(0.5);

    dau_convolution_param->set_unit_normalization(true);
    dau_convolution_param->set_square_unit_normalization(false);

    DAUConvolutionLayer<float> layer(layer_param, ignore_edge_gradients);

    std::vector<Blob<float>* > blob_bottom_vec;
    std::vector<Blob<float>* > blob_top_vec;

    std::vector<bool > propagate_down;
    propagate_down.push_back(true);

    blob_bottom_vec.push_back(&blob_input);
    blob_top_vec.push_back(&blob_error);

    for (int ii = 0; ii < 1; ++ii) {

        layer.SetUp(blob_bottom_vec, blob_top_vec);

        offset_filler.Fill(layer.dau_compute.param_buffer_mu1_.get());
        offset_filler.Fill(layer.dau_compute.param_buffer_mu2_.get());

        // reset values for GPU run
        caffe_gpu_set(layer.dau_compute.param_buffer_w_->count(), (Dtype)0, (Dtype*)layer.dau_compute.param_buffer_w_->mutable_gpu_diff());
        caffe_gpu_set(layer.dau_compute.param_buffer_mu1_->count(), (Dtype)0, (Dtype*)layer.dau_compute.param_buffer_mu1_->mutable_gpu_diff());
        caffe_gpu_set(layer.dau_compute.param_buffer_mu2_->count(), (Dtype)0, (Dtype*)layer.dau_compute.param_buffer_mu2_->mutable_gpu_diff());
        caffe_gpu_set(layer.dau_compute.param_buffer_sigma_->count(), (Dtype)0, (Dtype*)layer.dau_compute.param_buffer_sigma_->mutable_gpu_diff());

        cudaDeviceSynchronize();

        for (int i = 0; i < 30; ++i) {
            clock_t start_t = clock();

            layer.Backward_gpu(blob_top_vec, propagate_down, blob_bottom_vec);

            cudaDeviceSynchronize();
            clock_t end_t = clock();

            std::cout << "fast_gauss_backward in " << (((float)(end_t-start_t))/CLOCKS_PER_SEC) << std::endl;
        }
    }
}

TYPED_TEST(DAUConvolutionLayerTest, DebugFastDAUConvolution1x1) {

    Caffe::SetDevice(1);

    typedef typename TypeParam::Dtype Dtype;

    if (Caffe::mode() == Caffe::CPU)
        return;

    if (sizeof(Dtype) > 4)
        return;

    // evaluate size settings
    const int N = 22;
    const int F = 64;
    const int S = 16;
    const int G = 4;
    const int W_in = 7;
    const int H_in = 7;

    const int kernel_size = 7;

    //const int pad = kernel_size/2;
    const int pad = 0;
    //const int pad = kernel_size/2  * 2;

    const int W = (W_in + 2 * pad - kernel_size) + 1;
    const int H = (H_in + 2 * pad - kernel_size) + 1;

    const bool use_interpolation = true;

    Blob<float> blob_input(N,S,H_in,W_in);
    Blob<int> blob_offsets_x(1, S, G, F);
    Blob<int> blob_offsets_y(1, S, G, F);
    Blob<float> blob_offsets_float_x(1, S, G, F);
    Blob<float> blob_offsets_float_y(1, S, G, F);
    Blob<float> blob_weights(1, S, G, F);
    Blob<float> blob_output(N,F,H,W);
    Blob<float> blob_output_cpu(N,F,H,W);
    Blob<float> blob_output_cudnn(N,F,H,W);

    FillerParameter const_one_filler_param;
    const_one_filler_param.set_value(1);
    ConstantFiller<float> const_one_filer(const_one_filler_param);

    FillerParameter const_zero_filler_param;
    const_zero_filler_param.set_value(0);
    ConstantFiller<int> const_zero_filer(const_zero_filler_param);
    ConstantFiller<float> const_zero_float_filer(const_zero_filler_param);

    FillerParameter rand_filler_param;
    rand_filler_param.set_std(0.1);
    GaussianFiller<float> input_filler(rand_filler_param);

    //input_filler.Fill(&blob_input);
    //input_filler.Fill(&blob_weights);

    const_one_filer.Fill(&blob_input);
    const_one_filer.Fill(&blob_weights);

    input_filler.Fill(&blob_input);

    float* data = blob_input.mutable_cpu_data();
    for (int n = 0; n < N; ++n){
        for (int s = 0; s < S; ++s) {
            for (int i = 0; i < H_in * W_in; ++i) {
                data[(n * S + s )* H_in * W_in + i] = 1;
                //data[(n * S + s )* H * W + i] = 2;
                //data[(n * S + s )* H * W + i] = n + (i % W + 1);
                //data[(n * S + s )* H * W + i] = n + (i / W + 1);
                //data[(n * S + s )* H * W + i] = (i % W);

            }
        }
    }

    FillerParameter offset_filler_param;
    offset_filler_param.set_min(2);
    offset_filler_param.set_max(kernel_size-2);
    UniformFiller<float> offset_filler(offset_filler_param);

    //offset_filler.Fill(&blob_offsets);
    const_zero_filer.Fill(&blob_offsets_x);
    const_zero_filer.Fill(&blob_offsets_y);

    const_zero_float_filer.Fill(&blob_offsets_float_x);
    const_zero_float_filer.Fill(&blob_offsets_float_y);

    const_zero_float_filer.Fill(&blob_output);

    float* output = Caffe::mode() == Caffe::CPU ? blob_output.mutable_cpu_data() : blob_output.mutable_gpu_data();

    LayerParameter layer_param;

    DAUConvolutionParameter* dau_convolution_param = layer_param.mutable_dau_conv_param();

    dau_convolution_param->add_kernel_size(kernel_size);
    dau_convolution_param->add_stride(1);
    dau_convolution_param->add_pad(pad);

    dau_convolution_param->add_number_units(G);
    dau_convolution_param->add_number_units(1);

    dau_convolution_param->set_num_output(F);

    //dau_convolution_param->mutable_weight_filler()->set_type("constant");
    //dau_convolution_param->mutable_weight_filler()->set_value(2);

    dau_convolution_param->mutable_weight_filler()->set_type("gaussian");
    dau_convolution_param->mutable_weight_filler()->set_std(0.1);

    dau_convolution_param->mutable_bias_filler()->set_type("constant");
    dau_convolution_param->mutable_bias_filler()->set_value(0);

    dau_convolution_param->mutable_mu_filler()->set_type("constant");
    dau_convolution_param->mutable_mu_filler()->set_value(0);

    dau_convolution_param->mutable_sigma_filler()->set_type("constant");
    dau_convolution_param->mutable_sigma_filler()->set_value(0.8);

    dau_convolution_param->set_component_border_bound(0);
    dau_convolution_param->set_sigma_lower_bound(0.5);

    dau_convolution_param->set_unit_normalization(true);
    dau_convolution_param->set_square_unit_normalization(false);

    DAUConvolutionLayer<float> layer(layer_param);

    LayerParameter cudnn_layer_param;

    std::vector<Blob<float>* > blob_bottom_vec;
    std::vector<Blob<float>* > blob_top_vec;
    std::vector<Blob<float>* > blob_top_vec_cudnn;
    std::vector<Blob<float>* > blob_top_vec_cpu;

    blob_bottom_vec.push_back(&blob_input);

    blob_top_vec.push_back(&blob_output);
    blob_top_vec_cudnn.push_back(&blob_output_cudnn);
    blob_top_vec_cpu.push_back(&blob_output_cpu);

    for (int ii = 0; ii < 1; ++ii) {

        layer.SetUp(blob_bottom_vec, blob_top_vec);

        // override offset data with zeros
        float* filter_offsets_float_mu1 = layer.dau_compute.param_buffer_mu1_->mutable_gpu_data();
        float* filter_offsets_float_mu2 = layer.dau_compute.param_buffer_mu2_->mutable_gpu_data();

        cudaMemset(filter_offsets_float_mu1, 0, layer.dau_compute.param_buffer_mu1_->count() * sizeof(float));
        cudaMemset(filter_offsets_float_mu2, 0, layer.dau_compute.param_buffer_mu2_->count() * sizeof(float));

        offset_filler.Fill(layer.dau_compute.param_buffer_mu1_.get());
        offset_filler.Fill(layer.dau_compute.param_buffer_mu2_.get());

        float* mu1_data = layer.dau_compute.param_buffer_mu1_->mutable_cpu_data();
        float* mu2_data = layer.dau_compute.param_buffer_mu2_->mutable_cpu_data();
        float* w_data = layer.dau_compute.param_buffer_w_->mutable_cpu_data();

        for (int s = 0; s < S; s++) {
            for (int g = 0; g < G; g++) {
                for (int f = 0; f < F; f++) {
                    //w_data[OFFSET(0,s,g,f, 1, S,G,F)] = floor(w_data[OFFSET(0,s,g,f, 1, S,G,F)] * 100) / 100.0f ;
                    //w_data[OFFSET(0,s,g,f, 1, S,G,F)] = s;
                    w_data[OFFSET(0,s,g,f, 1, S,G,F)] = 1;
                    //mu1_data[OFFSET(0,s,g,f, 1, S,G,F)] = -2.2 + ((f+1)*(1+s)*(g+1)) % 5 ;
                    //mu2_data[OFFSET(0,s,g,f, 1, S,G,F)] = 3.1 - ((f+1)*(1+s)*(g+1)) % 5 ;
                    mu1_data[OFFSET(0,s,g,f, 1, S,G,F)] = kernel_size/2;
                    mu2_data[OFFSET(0,s,g,f, 1, S,G,F)] = kernel_size/2;
                }
            }
        }

        layer.set_processing_on_gpu(false);
        layer.Forward_cpu(blob_bottom_vec, blob_top_vec_cpu);

        layer.set_processing_on_gpu(true);
        layer.Forward_gpu(blob_bottom_vec, blob_top_vec);

        float* output_cpu = blob_top_vec_cpu[0]->mutable_cpu_data();
        float* output_gpu = blob_top_vec[0]->mutable_cpu_data();

        // verify data - since we use 1 for input and wights and 0 for offsets we should get S as output value for all

        const bool compare_by_cpu = true;
        int found_invalid = 0;
        //double valid_value = S *G;
        double valid_value = (S-1) * (S)/2 * G;
        double diff = 0;
        double max_diff = 0;

        for (int n = 0; n < N; ++n){
            for (int f = 0; f < F; ++f) {
                for (int i = 0; i < H * W; ++i) {
                    int index = (n * F + f )* H * W + i;
                    float val = output_gpu[index];
                    float GT_VALUE = (n + i % W +1 )*G*S;  // for data[] = n + (i % W + 1)
                    //float GT_VALUE = (n + i / W +1 )*G*S;  // for data[] = n + (i  / W + 1)
                    //float GT_VALUE = (i % W  ) *G*S;       // for data[] = s
                    //float GT_VALUE = n + (i % W + 1);      // for just copy

                    if (compare_by_cpu) {
                        GT_VALUE = output_cpu[index];
                    }
                    if (val != val) {
                        printf("error: got NaN at loc (%d=%d,%d,%d,%d) - should be %f\n",  index, n, f, i / W, i % W, GT_VALUE);
                    }
                    // interpolation at the right edge excludes one pixel so ignore those pixels

                    if (std::abs(val - GT_VALUE) / GT_VALUE > 1e-4 && std::abs(val - GT_VALUE) > 1e-7 && (i % W < (W -1) || (W == 1 && H == 1))) {
                        if (found_invalid < 10)
                            printf("found invalid output (%f) at loc (%d=%d,%d,%d,%d) - should be %f\n", val, index,  n, f, i / W, i % W, GT_VALUE);
                        found_invalid++;

                        double current_diff = std::abs(val - GT_VALUE) / GT_VALUE;

                        diff += current_diff;

                        max_diff = std::max(max_diff, current_diff);
                    }
                    //if (i % W == 0)
                    //    std::cout << std::endl;
                    //std::cout << val << " ";
                    //std::cout << GT_VALUE << " ";
                }
                //std::cout << std::endl;
            }
        }
        //std::cout << std::endl;
        diff /= found_invalid;

        if (found_invalid > 0)
            printf("found num of invalid output vals: %d/%d with mean diff val %f and max diff val %f\n",found_invalid, blob_output.count(), diff, max_diff);
    }
}

TYPED_TEST(DAUConvolutionLayerTest, DebugFastGaussMemtest) {

    Caffe::SetDevice(1);

    typedef typename TypeParam::Dtype Dtype;

    if (Caffe::mode() == Caffe::CPU)
        return;

    if (sizeof(Dtype) > 4)
        return;

    SolverParameter solver_param;
    NetParameter net_param;

    solver_param.set_test_initialization(false);
    solver_param.set_max_iter(10000);
    solver_param.set_display(100000);
    solver_param.set_stepsize(100000);
    solver_param.set_base_lr(0.01);
    solver_param.set_snapshot(100000);
    solver_param.set_momentum(0.9);
    solver_param.set_solver_mode(solver_param.GPU);
    solver_param.set_allocated_net_param(&net_param);

    // evaluate size settings
    const int N = 16;
    const int F = 64;
    const int S = 64;
    const int G = 2;
    const int W = 32;
    const int H = 32;

    const bool use_interpolation = true;

    const int kernel_size = 9;

    Blob<float> blob_input(N,S,H,W);
    Blob<int> blob_offsets_x(1, S, G, F);
    Blob<int> blob_offsets_y(1, S, G, F);
    Blob<float> blob_offsets_float_x(1, S, G, F);
    Blob<float> blob_offsets_float_y(1, S, G, F);
    Blob<float> blob_weights(1, S, G, F);
    Blob<float> blob_output(N,F,H,W);
    Blob<float> blob_output_cpu(N,F,H,W);
    Blob<float> blob_output_cudnn(N,F,H,W);

    FillerParameter const_one_filler_param;
    const_one_filler_param.set_value(1);
    ConstantFiller<float> const_one_filer(const_one_filler_param);

    FillerParameter const_zero_filler_param;
    const_zero_filler_param.set_value(0);
    ConstantFiller<int> const_zero_filer(const_zero_filler_param);
    ConstantFiller<float> const_zero_float_filer(const_zero_filler_param);

    FillerParameter rand_filler_param;
    rand_filler_param.set_std(0.1);
    GaussianFiller<float> input_filler(rand_filler_param);

    //input_filler.Fill(&blob_input);
    //input_filler.Fill(&blob_weights);

    const_one_filer.Fill(&blob_input);
    const_one_filer.Fill(&blob_weights);

    input_filler.Fill(&blob_input);

    float* data = blob_input.mutable_cpu_data();
    for (int n = 0; n < N; ++n){
        for (int s = 0; s < S; ++s) {
            for (int i = 0; i < H * W; ++i) {
                //data[(n * S + s )* H * W + i] = 1;
                //data[(n * S + s )* H * W + i] = s;
                //data[(n * S + s )* H * W + i] = n + (i % W + 1);
                //data[(n * S + s )* H * W + i] = n + (i / W + 1);
                //data[(n * S + s )* H * W + i] = (i % W);

            }
        }
    }

    FillerParameter offset_filler_param;
    offset_filler_param.set_min(2);
    offset_filler_param.set_max(kernel_size-2);
    UniformFiller<float> offset_filler(offset_filler_param);

    //offset_filler.Fill(&blob_offsets);
    const_zero_filer.Fill(&blob_offsets_x);
    const_zero_filer.Fill(&blob_offsets_y);

    const_zero_float_filer.Fill(&blob_offsets_float_x);
    const_zero_float_filer.Fill(&blob_offsets_float_y);

    const_zero_float_filer.Fill(&blob_output);

    float* output = Caffe::mode() == Caffe::CPU ? blob_output.mutable_cpu_data() : blob_output.mutable_gpu_data();

    LayerParameter layer_param;

    DAUConvolutionParameter* dau_convolution_param = layer_param.mutable_convolution_param();

    dau_convolution_param->add_kernel_size(kernel_size);
    dau_convolution_param->add_stride(1);
    dau_convolution_param->add_pad(kernel_size/2);

    dau_convolution_param->add_number_units(G);
    dau_convolution_param->add_number_units(1);

    dau_convolution_param->set_num_output(F);

    //dau_convolution_param->mutable_weight_filler()->set_type("constant");
    //dau_convolution_param->mutable_weight_filler()->set_value(2);

    dau_convolution_param->mutable_weight_filler()->set_type("gaussian");
    dau_convolution_param->mutable_weight_filler()->set_std(0.1);

    dau_convolution_param->mutable_bias_filler()->set_type("constant");
    dau_convolution_param->mutable_bias_filler()->set_value(0);

    dau_convolution_param->mutable_mu_filler()->set_type("constant");
    dau_convolution_param->mutable_mu_filler()->set_value(0);

    dau_convolution_param->mutable_sigma_filler()->set_type("constant");
    dau_convolution_param->mutable_sigma_filler()->set_value(0.8);

    dau_convolution_param->set_component_border_bound(0);
    dau_convolution_param->set_sigma_lower_bound(0.5);

    dau_convolution_param->set_unit_normalization(true);
    dau_convolution_param->set_square_unit_normalization(false);

    DAUConvolutionLayer<float> layer(layer_param);

    LayerParameter cudnn_layer_param;

    DAUConvolutionParameter* convolution_param =
            cudnn_layer_param.mutable_dau_conv_param();

    convolution_param->add_kernel_size(3);
    convolution_param->add_stride(1);
    convolution_param->add_pad(1);

    convolution_param->set_num_output(F);

    convolution_param->mutable_weight_filler()->set_type("gaussian");
    convolution_param->mutable_weight_filler()->set_std(0.1);

    std::vector<Blob<float>* > blob_bottom_vec;
    std::vector<Blob<float>* > blob_top_vec;
    std::vector<Blob<float>* > blob_top_vec_cudnn;
    std::vector<Blob<float>* > blob_top_vec_cpu;

    blob_bottom_vec.push_back(&blob_input);

    blob_top_vec.push_back(&blob_output);
    blob_top_vec_cudnn.push_back(&blob_output_cudnn);
    blob_top_vec_cpu.push_back(&blob_output_cpu);

    std::cout << std::endl;
    for (int ii = 0; ii < 1; ++ii) {

        layer.SetUp(blob_bottom_vec, blob_top_vec);

        // override offset data with zeros
        float* filter_offsets_float_mu1 = layer.dau_compute.param_buffer_mu1_->mutable_gpu_data();
        float* filter_offsets_float_mu2 = layer.dau_compute.param_buffer_mu2_->mutable_gpu_data();

        cudaMemset(filter_offsets_float_mu1, 0, layer.dau_compute.param_buffer_mu1_->count() * sizeof(float));
        cudaMemset(filter_offsets_float_mu2, 0, layer.dau_compute.param_buffer_mu2_->count() * sizeof(float));

        offset_filler.Fill(layer.dau_compute.param_buffer_mu1_.get());
        offset_filler.Fill(layer.dau_compute.param_buffer_mu2_.get());

        for (int jj = 0; jj < 10000; ++jj) {
            layer.Forward_gpu(blob_bottom_vec, blob_top_vec);
        }

        float* output_cpu = blob_top_vec_cpu[0]->mutable_cpu_data();
        float* output_gpu = blob_top_vec[0]->mutable_cpu_data();

        // verify data - since we use 1 for input and wights and 0 for offsets we should get S as output value for all
    }
}

#ifdef USE_CUDNN

#endif

}  // namespace caffe


//
//  cal_fe_global_const_ke.hpp
//  hybrid_fem_bie
//
//  Created by Max on 2/7/18.
//
//

#ifndef cal_fe_global_const_ke_hpp
#define cal_fe_global_const_ke_hpp

#include <stdio.h>
#include <iostream>
#include <Eigen/Eigen>
#include "maplocal.hpp"
#include "mapglobal.hpp"

using namespace Eigen;
extern double time_fem;
void cal_fe_global_const_ke(int n_nodes, int n_el, MatrixXi &index_store, double q, VectorXd &u_n, VectorXd &v_n, int Ndofn, MatrixXd ke, VectorXd &fe_global);

#endif /* cal_fe_global_const_ke_hpp */


#include "include/PVWToBFVSeal.h"
#include "include/SealUtils.h"
#include "include/retrieval.h"
#include "include/client.h"
#include "include/LoadAndSaveUtils.h"
#include <NTL/BasicThreadPool.h>
#include <NTL/ZZ.h>
#include <thread>

using namespace seal;

vector<vector<uint64_t>> preparinngTransactionsFormal(PVWpk& pk,
                                                    int numOfTransactions, int pertinentMsgNum, const PVWParam& params, bool formultitest = false){
    srand (time(NULL));

    vector<int> msgs(numOfTransactions);
    vector<vector<uint64_t>> ret;
    vector<int> zeros(params.ell, 0);

    for(int i = 0; i < pertinentMsgNum;){
        auto temp = rand() % numOfTransactions;
        while(msgs[temp]){
            temp = rand() % numOfTransactions;
        }
        msgs[temp] = 1;
        i++;
    }

    cout << "Expected Message Indices: ";

    for(int i = 0; i < numOfTransactions; i++){
        PVWCiphertext tempclue;
        if(msgs[i]){
            cout << i << " ";
            PVWEncPK(tempclue, zeros, pk, params);
            ret.push_back(loadDataSingle(i));
            expectedIndices.push_back(uint64_t(i));
        }
        else
        {
            auto sk2 = PVWGenerateSecretKey(params);
            PVWEncSK(tempclue, zeros, sk2, params);
        }

        saveClues(tempclue, i);
    }
    cout << endl;
    return ret;
}

// Phase 1, obtaining PV's
Ciphertext serverOperations1obtainPackedSIC(vector<PVWCiphertext>& SICPVW, vector<Ciphertext> switchingKey, const RelinKeys& relin_keys,
                            const GaloisKeys& gal_keys, const size_t& degree, const SEALContext& context, const PVWParam& params, const int numOfTransactions){
    Evaluator evaluator(context);

    vector<Ciphertext> packedSIC(params.ell);
    computeBplusASPVWOptimized(packedSIC, SICPVW, switchingKey, gal_keys, context, params);

    int rangeToCheck = 850; // range check is from [-rangeToCheck, rangeToCheck-1]
    newRangeCheckPVW(packedSIC, rangeToCheck, relin_keys, degree, context, params);

    return packedSIC[0];
}

// Phase 2, retrieving
void serverOperations2therest(Ciphertext& lhs, vector<vector<int>>& bipartite_map, Ciphertext& rhs,
                        Ciphertext& packedSIC, const vector<vector<uint64_t>>& payload, const RelinKeys& relin_keys, const GaloisKeys& gal_keys,
                        const size_t& degree, const SEALContext& context, const SEALContext& context2, const PVWParam& params, const int numOfTransactions,
                        int& counter, const int payloadSize = 306){

    Evaluator evaluator(context);
    int step = 32; // simply to save memory so process 32 msgs at a time

    for(int i = counter; i < counter+numOfTransactions; i += step){
        vector<Ciphertext> expandedSIC;
        // step 1. expand PV
        expandSIC(expandedSIC, packedSIC, gal_keys, int(degree), context, context2, step, i-counter);

        // transform to ntt form for better efficiency especially for the last two steps
        for(size_t j = 0; j < expandedSIC.size(); j++)
            if(!expandedSIC[j].is_ntt_form())
                evaluator.transform_to_ntt_inplace(expandedSIC[j]);

        // step 2. deterministic retrieval
        deterministicIndexRetrieval(lhs, expandedSIC, context, degree, i);

        // step 3-4. multiply weights and pack them
        // The following two steps are for streaming updates
        vector<vector<Ciphertext>> payloadUnpacked;
        payloadRetrievalOptimizedwithWeights(payloadUnpacked, payload, bipartite_map_glb, weights_glb, expandedSIC, context, degree, i, i - counter);
        // Note that if number of repeatitions is already set, this is the only step needed for streaming updates
        payloadPackingOptimized(rhs, payloadUnpacked, bipartite_map_glb, degree, context, gal_keys, i);
    }
    if(lhs.is_ntt_form())
        evaluator.transform_from_ntt_inplace(lhs);
    if(rhs.is_ntt_form())
        evaluator.transform_from_ntt_inplace(rhs);

    counter += numOfTransactions;
}

// Phase 2, retrieving for OMR3
void serverOperations3therest(vector<vector<Ciphertext>>& lhs, vector<Ciphertext>& lhsCounter, vector<vector<int>>& bipartite_map, Ciphertext& rhs,
                        Ciphertext& packedSIC, const vector<vector<uint64_t>>& payload, const RelinKeys& relin_keys, const GaloisKeys& gal_keys, const PublicKey& public_key,
                        const size_t& degree, const SEALContext& context, const SEALContext& context2, const PVWParam& params, const int numOfTransactions,
                        int& counter, const int payloadSize = 306){

    Evaluator evaluator(context);

    int step = 32;
    for(int i = counter; i < counter+numOfTransactions; i += step){
        // step 1. expand PV
        vector<Ciphertext> expandedSIC;
        expandSIC(expandedSIC, packedSIC, gal_keys, int(degree), context, context2, step, i-counter);
        // transform to ntt form for better efficiency for all of the following steps
        for(size_t j = 0; j < expandedSIC.size(); j++)
            if(!expandedSIC[j].is_ntt_form())
                evaluator.transform_to_ntt_inplace(expandedSIC[j]);

        // step 2. randomized retrieval
        randomizedIndexRetrieval(lhs, lhsCounter, expandedSIC, context2, public_key, i, degree, C_glb);

        // step 3-4. multiply weights and pack them
        // The following two steps are for streaming updates
        vector<vector<Ciphertext>> payloadUnpacked;
        payloadRetrievalOptimizedwithWeights(payloadUnpacked, payload, bipartite_map_glb, weights_glb, expandedSIC, context, degree, i, i-counter);
        // Note that if number of repeatitions is already set, this is the only step needed for streaming updates
        payloadPackingOptimized(rhs, payloadUnpacked, bipartite_map_glb, degree, context, gal_keys, i);
    }
    for(size_t i = 0; i < lhs.size(); i++){
            evaluator.transform_from_ntt_inplace(lhs[i][0]);
            evaluator.transform_from_ntt_inplace(lhs[i][1]);
            evaluator.transform_from_ntt_inplace(lhsCounter[i]);
    }
    if(rhs.is_ntt_form())
        evaluator.transform_from_ntt_inplace(rhs);

    counter += numOfTransactions;
}

vector<vector<long>> receiverDecoding(Ciphertext& lhsEnc, vector<vector<int>>& bipartite_map, Ciphertext& rhsEnc,
                        const size_t& degree, const SecretKey& secret_key, const SEALContext& context, const int numOfTransactions, int seed = 3,
                        const int payloadUpperBound = 306, const int payloadSize = 306){

    // 1. find pertinent indices
    map<int, int> pertinentIndices;
    decodeIndices(pertinentIndices, lhsEnc, numOfTransactions, degree, secret_key, context);
    for (map<int, int>::iterator it = pertinentIndices.begin(); it != pertinentIndices.end(); it++)
    {
        std::cout << it->first << " ";  // print out all the indices found
    }
    cout << std::endl;

    // 2. forming rhs
    vector<vector<int>> rhs;
    formRhs(rhs, rhsEnc, secret_key, degree, context, OMRtwoM);

    // 3. forming lhs
    vector<vector<int>> lhs;
    formLhsWeights(lhs, pertinentIndices, bipartite_map_glb, weights_glb, 0, OMRtwoM);

    // 4. solving equation
    auto newrhs = equationSolving(lhs, rhs, payloadSize);

    return newrhs;
}

vector<vector<long>> receiverDecodingOMR3(vector<vector<Ciphertext>>& lhsEnc, vector<Ciphertext>& lhsCounter, vector<vector<int>>& bipartite_map, Ciphertext& rhsEnc,
                        const size_t& degree, const SecretKey& secret_key, const SEALContext& context, const int numOfTransactions, int seed = 3,
                        const int payloadUpperBound = 306, const int payloadSize = 306){
    // 1. find pertinent indices
    map<int, int> pertinentIndices;
    decodeIndicesRandom(pertinentIndices, lhsEnc, lhsCounter, degree, secret_key, context);
    for (map<int, int>::iterator it = pertinentIndices.begin(); it != pertinentIndices.end(); it++)
    {
        std::cout << it->first << " ";    // print out all the indices found
    }
    cout << std::endl;

    // 2. forming rhs
    vector<vector<int>> rhs;
    formRhs(rhs, rhsEnc, secret_key, degree, context, OMRthreeM);

    // 3. forming lhs
    vector<vector<int>> lhs;
    formLhsWeights(lhs, pertinentIndices, bipartite_map_glb, weights_glb, 0, OMRthreeM);

    // 4. solving equation
    auto newrhs = equationSolving(lhs, rhs, payloadSize);

    return newrhs;
}

// to check whether the result is as expected
bool checkRes(vector<vector<uint64_t>> expected, vector<vector<long>> res){
    for(size_t i = 0; i < expected.size(); i++){
        bool flag = false;
        for(size_t j = 0; j < res.size(); j++){
            if(expected[i][0] == uint64_t(res[j][0])){
                if(expected[i].size() != res[j].size())
                {
                    cerr << "expected and res length not the same" << endl;
                    return false;
                }
                for(size_t k = 1; k < res[j].size(); k++){
                    if(expected[i][k] != uint64_t(res[j][k]))
                        break;
                    if(k == res[j].size() - 1){
                        flag = true;
                    }
                }
            }
        }
        if(!flag)
            return false;
    }
    return true;
}

// check OMD detection key size
// We are:
//      1. packing PVW sk into ell ciphertexts
//      2. using seed mode in SEAL
void OMDlevelspecificDetectKeySize(){
    auto params = PVWParam(450, 65537, 1.3, 16000, 4);
    auto sk = PVWGenerateSecretKey(params);
    cout << "Finishing generating sk for PVW cts\n";
    EncryptionParameters parms(scheme_type::bfv);
    size_t poly_modulus_degree = poly_modulus_degree_glb;
    parms.set_poly_modulus_degree(poly_modulus_degree);
    auto coeff_modulus = CoeffModulus::Create(poly_modulus_degree, { 28,
                                                                            39, 60, 60, 60,
                                                                            60, 60, 60, 60, 60, 60,
                                                                            32, 30, 60 });
    parms.set_coeff_modulus(coeff_modulus);
    parms.set_plain_modulus(65537);

	prng_seed_type seed;
    for (auto &i : seed)
    {
        i = random_uint64();
    }
    auto rng = make_shared<Blake2xbPRNGFactory>(Blake2xbPRNGFactory(seed));
    parms.set_random_generator(rng);

    SEALContext context(parms, true, sec_level_type::none);
    print_parameters(context);
    KeyGenerator keygen(context);
    SecretKey secret_key = keygen.secret_key();
    PublicKey public_key;
    keygen.create_public_key(public_key);
    RelinKeys relin_keys;
    Encryptor encryptor(context, public_key);
    Evaluator evaluator(context);
    Decryptor decryptor(context, secret_key);
    BatchEncoder batch_encoder(context);
    GaloisKeys gal_keys;

    seal::Serializable<PublicKey> pk = keygen.create_public_key();
	seal::Serializable<RelinKeys> rlk = keygen.create_relin_keys();
	stringstream streamPK, streamRLK, streamRTK;
    auto reskeysize = pk.save(streamPK);
	reskeysize += rlk.save(streamRLK);
	reskeysize += keygen.create_galois_keys(vector<int>({1})).save(streamRTK);

    public_key.load(context, streamPK);
    relin_keys.load(context, streamRLK);
    gal_keys.load(context, streamRTK);
	vector<seal::Serializable<Ciphertext>>  switchingKeypacked = genSwitchingKeyPVWPacked(context, poly_modulus_degree, public_key, secret_key, sk, params);
	stringstream data_stream;
    for(size_t i = 0; i < switchingKeypacked.size(); i++){
        reskeysize += switchingKeypacked[i].save(data_stream);
    }
    cout << "Detection Key Size: " << reskeysize << " bytes" << endl;
}

// check OMR detection key size
// We are:
//      1. packing PVW sk into ell ciphertexts
//      2. use level-specific rot keys
//      3. using seed mode in SEAL
void levelspecificDetectKeySize(){
    auto params = PVWParam(450, 65537, 1.3, 16000, 4);
    auto sk = PVWGenerateSecretKey(params);
    cout << "Finishing generating sk for PVW cts\n";

    EncryptionParameters parms(scheme_type::bfv);
    size_t poly_modulus_degree = poly_modulus_degree_glb;
    auto degree = poly_modulus_degree;
    parms.set_poly_modulus_degree(poly_modulus_degree);
    auto coeff_modulus = CoeffModulus::Create(poly_modulus_degree, { 28,
                                                                            39, 60, 60, 60, 60,
                                                                            60, 60, 60, 60, 60, 60,
                                                                            32, 30, 60 });
    parms.set_coeff_modulus(coeff_modulus);
    parms.set_plain_modulus(65537);

	prng_seed_type seed;
    for (auto &i : seed)
    {
        i = random_uint64();
    }
    auto rng = make_shared<Blake2xbPRNGFactory>(Blake2xbPRNGFactory(seed));
    parms.set_random_generator(rng);

    SEALContext context(parms, true, sec_level_type::none);
    print_parameters(context);
    KeyGenerator keygen(context);
    SecretKey secret_key = keygen.secret_key();
    PublicKey public_key;
    keygen.create_public_key(public_key);
    RelinKeys relin_keys;
    Encryptor encryptor(context, public_key);
    Evaluator evaluator(context);
    Decryptor decryptor(context, secret_key);
    BatchEncoder batch_encoder(context);
    GaloisKeys gal_keys;

    vector<int> steps = {0};
    for(int i = 1; i < int(poly_modulus_degree/2); i *= 2){
	    steps.push_back(i);
    }

    stringstream lvlRTK, lvlRTK2;
    /////////////////////////////////////// Level specific keys
    vector<Modulus> coeff_modulus_next = coeff_modulus;
    coeff_modulus_next.erase(coeff_modulus_next.begin() + 3, coeff_modulus_next.end()-1);
    EncryptionParameters parms_next = parms;
    parms_next.set_coeff_modulus(coeff_modulus_next);
    parms_next.set_random_generator(rng);
    SEALContext context_next = SEALContext(parms_next, true, sec_level_type::none);

    SecretKey sk_next;
    sk_next.data().resize(coeff_modulus_next.size() * degree);
    sk_next.parms_id() = context_next.key_parms_id();
    util::set_poly(secret_key.data().data(), degree, coeff_modulus_next.size() - 1, sk_next.data().data());
    util::set_poly(
        secret_key.data().data() + degree * (coeff_modulus.size() - 1), degree, 1,
        sk_next.data().data() + degree * (coeff_modulus_next.size() - 1));
    KeyGenerator keygen_next(context_next, sk_next);
    vector<int> steps_next = {0,1};
    auto reskeysize = keygen_next.create_galois_keys(steps_next).save(lvlRTK);
        //////////////////////////////////////
    vector<Modulus> coeff_modulus_last = coeff_modulus;
    coeff_modulus_last.erase(coeff_modulus_last.begin() + 2, coeff_modulus_last.end()-1);
    EncryptionParameters parms_last = parms;
    parms_last.set_coeff_modulus(coeff_modulus_last);
    parms_last.set_random_generator(rng);
    SEALContext context_last = SEALContext(parms_last, true, sec_level_type::none);

    SecretKey sk_last;
    sk_last.data().resize(coeff_modulus_last.size() * degree);
    sk_last.parms_id() = context_last.key_parms_id();
    util::set_poly(secret_key.data().data(), degree, coeff_modulus_last.size() - 1, sk_last.data().data());
    util::set_poly(
        secret_key.data().data() + degree * (coeff_modulus.size() - 1), degree, 1,
        sk_last.data().data() + degree * (coeff_modulus_last.size() - 1));
    KeyGenerator keygen_last(context_last, sk_last);
    reskeysize += keygen_last.create_galois_keys(steps).save(lvlRTK2);
    //////////////////////////////////////

    seal::Serializable<PublicKey> pk = keygen.create_public_key();
	seal::Serializable<RelinKeys> rlk = keygen.create_relin_keys();
	stringstream streamPK, streamRLK, streamRTK;
    reskeysize += pk.save(streamPK);
	reskeysize += rlk.save(streamRLK);
	reskeysize += keygen.create_galois_keys(vector<int>({1})).save(streamRTK);

    public_key.load(context, streamPK);
    relin_keys.load(context, streamRLK);
    gal_keys.load(context, streamRTK);
	vector<seal::Serializable<Ciphertext>>  switchingKeypacked = genSwitchingKeyPVWPacked(context, poly_modulus_degree, public_key, secret_key, sk, params);
	stringstream data_stream;
    for(size_t i = 0; i < switchingKeypacked.size(); i++){
        reskeysize += switchingKeypacked[i].save(data_stream);
    }
    cout << "Detection Key Size: " << reskeysize << " bytes" << endl;
}

void OMD1p(){

    size_t poly_modulus_degree = poly_modulus_degree_glb;

    int numOfTransactions = numOfTransactions_glb;
    createDatabase(numOfTransactions, 306); // one time; note that this 306 represents 612 bytes because each slot can contain 2 bytes
    cout << "Finishing createDatabase\n";

    // step 1. generate PVW sk
    // recipient side
    auto params = PVWParam(450, 65537, 1.3, 16000, 4);
    auto sk = PVWGenerateSecretKey(params);
    auto pk = PVWGeneratePublicKey(params, sk);
    cout << "Finishing generating sk for PVW cts\n";

    // step 2. prepare transactions
    auto expected = preparinngTransactionsFormal(pk, numOfTransactions, num_of_pertinent_msgs_glb,  params);
    cout << expected.size() << " pertinent msg: Finishing preparing messages\n";

    // step 3. generate detection key
    // recipient side
    EncryptionParameters parms(scheme_type::bfv);
    parms.set_poly_modulus_degree(poly_modulus_degree);
    auto coeff_modulus = CoeffModulus::Create(poly_modulus_degree, { 28,
                                                                            39, 60, 60, 60,
                                                                            60, 60, 60, 60, 60, 60,
                                                                            32, 30, 60 });
    parms.set_coeff_modulus(coeff_modulus);
    parms.set_plain_modulus(65537);

	prng_seed_type seed;
    for (auto &i : seed)
    {
        i = random_uint64();
    }
    auto rng = make_shared<Blake2xbPRNGFactory>(Blake2xbPRNGFactory(seed));
    parms.set_random_generator(rng);

    SEALContext context(parms, true, sec_level_type::none);
    print_parameters(context);
    KeyGenerator keygen(context);
    SecretKey secret_key = keygen.secret_key();
    PublicKey public_key;
    keygen.create_public_key(public_key);
    RelinKeys relin_keys;
    keygen.create_relin_keys(relin_keys);
    Encryptor encryptor(context, public_key);
    Evaluator evaluator(context);
    Decryptor decryptor(context, secret_key);
    BatchEncoder batch_encoder(context);

    vector<Ciphertext> switchingKey;
    Ciphertext packedSIC;
    switchingKey.resize(params.ell);
    // Generated BFV ciphertexts encrypting PVW secret keys
    genSwitchingKeyPVWPacked(switchingKey, context, poly_modulus_degree, public_key, secret_key, sk, params);

    vector<vector<PVWCiphertext>> SICPVW_multicore(numcores);
    vector<vector<vector<uint64_t>>> payload_multicore(numcores);
    vector<int> counter(numcores);

    GaloisKeys gal_keys;
    vector<int> stepsfirst = {1};
    // only one rot key is needed for full level
    keygen.create_galois_keys(stepsfirst, gal_keys);

    cout << "Finishing generating detection keys\n";

    vector<vector<Ciphertext>> packedSICfromPhase1(numcores,vector<Ciphertext>(numOfTransactions/numcores/poly_modulus_degree)); // Assume numOfTransactions/numcores/poly_modulus_degree is integer, pad if needed

    NTL::SetNumThreads(numcores);
    SecretKey secret_key_blank;

    chrono::high_resolution_clock::time_point time_start, time_end;
    chrono::microseconds time_diff;
    time_start = chrono::high_resolution_clock::now();

    MemoryPoolHandle my_pool = MemoryPoolHandle::New();
    auto old_prof = MemoryManager::SwitchProfile(std::make_unique<MMProfFixed>(std::move(my_pool)));
    NTL_EXEC_RANGE(numcores, first, last);
    for(int i = first; i < last; i++){
        counter[i] = numOfTransactions/numcores*i;

        size_t j = 0;
        while(j < numOfTransactions/numcores/poly_modulus_degree){
            cout << "OMD, Batch " << j << endl;
            loadClues(SICPVW_multicore[i], counter[i], counter[i]+poly_modulus_degree, params);
            packedSICfromPhase1[i][j] = serverOperations1obtainPackedSIC(SICPVW_multicore[i], switchingKey, relin_keys, gal_keys,
                                                            poly_modulus_degree, context, params, poly_modulus_degree);
            j++;
            counter[i] += poly_modulus_degree;
            SICPVW_multicore[i].clear();
        }

    }
    NTL_EXEC_RANGE_END;
    MemoryManager::SwitchProfile(std::move(old_prof));

    int determinCounter = 0;
    Ciphertext res;
    for(size_t i = 0; i < packedSICfromPhase1.size(); i++){
        for(size_t j = 0; j < packedSICfromPhase1[i].size(); j++){
            Plaintext plain_matrix;
            vector<uint64_t> pod_matrix(poly_modulus_degree, 1 << determinCounter);
            batch_encoder.encode(pod_matrix, plain_matrix);
            if((i == 0) && (j == 0)){
                evaluator.multiply_plain(packedSICfromPhase1[i][j], plain_matrix, res);
            } else {
                evaluator.multiply_plain_inplace(packedSICfromPhase1[i][j], plain_matrix);
                evaluator.add_inplace(res, packedSICfromPhase1[i][j]);
            }
            determinCounter++;
        }
    }

    while(context.last_parms_id() != res.parms_id()){
            evaluator.mod_switch_to_next_inplace(res);
        }

    time_end = chrono::high_resolution_clock::now();
    time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);
    cout << "\nDetector runnimg time: " << time_diff.count() << "us." << "\n";

    // step 5. receiver decoding
    time_start = chrono::high_resolution_clock::now();
    auto realres = decodeIndicesOMD(res, numOfTransactions, poly_modulus_degree, secret_key, context);
    time_end = chrono::high_resolution_clock::now();
    time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);
    cout << "\nRecipient runnimg time: " << time_diff.count() << "us." << "\n";

    bool allflags = true;
    for(size_t i = 0; i < expectedIndices.size(); i++){
        bool flag = false;
        for(size_t j = 0; j < realres.size(); j++){
            if(expectedIndices[i] == realres[j])
            {
                flag = true;
                break;
            }
        }
        if(!flag){
            cout << expectedIndices[i] <<" not found" << endl;
            allflags = false;
        }
    }

    if(allflags)
        cout << "Result is correct!" << endl;
    else
        cout << "Overflow" << endl;

    for(size_t i = 0; i < res.size(); i++){

    }

}

void OMR2(){

    size_t poly_modulus_degree = poly_modulus_degree_glb;

    int numOfTransactions = numOfTransactions_glb;
    createDatabase(numOfTransactions, 306);
    cout << "Finishing createDatabase\n";

    // step 1. generate PVW sk
    // recipient side
    auto params = PVWParam(450, 65537, 1.3, 16000, 4);
    auto sk = PVWGenerateSecretKey(params);
    auto pk = PVWGeneratePublicKey(params, sk);
    cout << "Finishing generating sk for PVW cts\n";

    // step 2. prepare transactions
    auto expected = preparinngTransactionsFormal(pk, numOfTransactions, num_of_pertinent_msgs_glb,  params);
    cout << expected.size() << " pertinent msg: Finishing preparing messages\n";

    // step 3. generate detection key
    // recipient side
    EncryptionParameters parms(scheme_type::bfv);
    auto degree = poly_modulus_degree;
    parms.set_poly_modulus_degree(poly_modulus_degree);
    auto coeff_modulus = CoeffModulus::Create(poly_modulus_degree, { 28,
                                                                            39, 60, 60, 60, 60,
                                                                            60, 60, 60, 60, 60, 60,
                                                                            32, 30, 60 });
    parms.set_coeff_modulus(coeff_modulus);
    parms.set_plain_modulus(65537);

	prng_seed_type seed;
    for (auto &i : seed)
    {
        i = random_uint64();
    }
    auto rng = make_shared<Blake2xbPRNGFactory>(Blake2xbPRNGFactory(seed));
    parms.set_random_generator(rng);

    SEALContext context(parms, true, sec_level_type::none);
    print_parameters(context);
    KeyGenerator keygen(context);
    SecretKey secret_key = keygen.secret_key();
    PublicKey public_key;
    keygen.create_public_key(public_key);
    RelinKeys relin_keys;
    keygen.create_relin_keys(relin_keys);
    Encryptor encryptor(context, public_key);
    Evaluator evaluator(context);
    Decryptor decryptor(context, secret_key);
    BatchEncoder batch_encoder(context);

    vector<Ciphertext> switchingKey;
    Ciphertext packedSIC;
    switchingKey.resize(params.ell);
    genSwitchingKeyPVWPacked(switchingKey, context, poly_modulus_degree, public_key, secret_key, sk, params);

    vector<vector<PVWCiphertext>> SICPVW_multicore(numcores);
    vector<vector<vector<uint64_t>>> payload_multicore(numcores);
    vector<int> counter(numcores);

    GaloisKeys gal_keys;
    vector<int> stepsfirst = {1};
    // only one rot key is needed for full level
    keygen.create_galois_keys(stepsfirst, gal_keys);

    /////////////////////////////////////////////////////////////// Rot Key gen
    vector<int> steps = {0};
    for(int i = 1; i < int(poly_modulus_degree/2); i *= 2){
	    steps.push_back(i);
    }

    cout << "Finishing generating detection keys\n";

    /////////////////////////////////////// Level specific keys
    vector<Modulus> coeff_modulus_next = coeff_modulus;
    coeff_modulus_next.erase(coeff_modulus_next.begin() + 4, coeff_modulus_next.end()-1);
    EncryptionParameters parms_next = parms;
    parms_next.set_coeff_modulus(coeff_modulus_next);
    SEALContext context_next = SEALContext(parms_next, true, sec_level_type::none);

    SecretKey sk_next;
    sk_next.data().resize(coeff_modulus_next.size() * degree);
    sk_next.parms_id() = context_next.key_parms_id();
    util::set_poly(secret_key.data().data(), degree, coeff_modulus_next.size() - 1, sk_next.data().data());
    util::set_poly(
        secret_key.data().data() + degree * (coeff_modulus.size() - 1), degree, 1,
        sk_next.data().data() + degree * (coeff_modulus_next.size() - 1));
    KeyGenerator keygen_next(context_next, sk_next);
    vector<int> steps_next = {0,1};
    keygen_next.create_galois_keys(steps_next, gal_keys_next);
        //////////////////////////////////////
    vector<Modulus> coeff_modulus_last = coeff_modulus;
    coeff_modulus_last.erase(coeff_modulus_last.begin() + 2, coeff_modulus_last.end()-1);
    EncryptionParameters parms_last = parms;
    parms_last.set_coeff_modulus(coeff_modulus_last);
    SEALContext context_last = SEALContext(parms_last, true, sec_level_type::none);

    SecretKey sk_last;
    sk_last.data().resize(coeff_modulus_last.size() * degree);
    sk_last.parms_id() = context_last.key_parms_id();
    util::set_poly(secret_key.data().data(), degree, coeff_modulus_last.size() - 1, sk_last.data().data());
    util::set_poly(
        secret_key.data().data() + degree * (coeff_modulus.size() - 1), degree, 1,
        sk_last.data().data() + degree * (coeff_modulus_last.size() - 1));
    KeyGenerator keygen_last(context_last, sk_last);
    keygen_last.create_galois_keys(steps, gal_keys_last);
    //////////////////////////////////////

    vector<vector<Ciphertext>> packedSICfromPhase1(numcores,vector<Ciphertext>(numOfTransactions/numcores/poly_modulus_degree)); // Assume numOfTransactions/numcores/poly_modulus_degree is integer, pad if needed

    NTL::SetNumThreads(numcores);
    SecretKey secret_key_blank;

    chrono::high_resolution_clock::time_point time_start, time_end;
    chrono::microseconds time_diff;
    time_start = chrono::high_resolution_clock::now();

    MemoryPoolHandle my_pool = MemoryPoolHandle::New();
    auto old_prof = MemoryManager::SwitchProfile(std::make_unique<MMProfFixed>(std::move(my_pool)));
    NTL_EXEC_RANGE(numcores, first, last);
    for(int i = first; i < last; i++){
        counter[i] = numOfTransactions/numcores*i;

        size_t j = 0;
        while(j < numOfTransactions/numcores/poly_modulus_degree){
            if(!i)
                cout << "Phase 1, Core " << i << ", Batch " << j << endl;
            loadClues(SICPVW_multicore[i], counter[i], counter[i]+poly_modulus_degree, params);
            packedSICfromPhase1[i][j] = serverOperations1obtainPackedSIC(SICPVW_multicore[i], switchingKey, relin_keys, gal_keys,
                                                            poly_modulus_degree, context, params, poly_modulus_degree);
            j++;
            counter[i] += poly_modulus_degree;
            SICPVW_multicore[i].clear();
        }

    }
    NTL_EXEC_RANGE_END;
    MemoryManager::SwitchProfile(std::move(old_prof));

    // step 4. detector operations
    vector<Ciphertext> lhs_multi(numcores), rhs_multi(numcores);
    vector<vector<vector<int>>> bipartite_map(numcores);

    bipartiteGraphWeightsGeneration(bipartite_map_glb, weights_glb, numOfTransactions,OMRtwoM,repeatition_glb,seed_glb);

    NTL_EXEC_RANGE(numcores, first, last);
    for(int i = first; i < last; i++){
        MemoryPoolHandle my_pool = MemoryPoolHandle::New();
        auto old_prof = MemoryManager::SwitchProfile(std::make_unique<MMProfFixed>(std::move(my_pool)));
        size_t j = 0;
        counter[i] = numOfTransactions/numcores*i;

        while(j < numOfTransactions/numcores/poly_modulus_degree){
            if(!i)
                cout << "Phase 2-3, Core " << i << ", Batch " << j << endl;
            loadData(payload_multicore[i], counter[i], counter[i]+poly_modulus_degree);
            Ciphertext templhs, temprhs;
            serverOperations2therest(templhs, bipartite_map[i], temprhs,
                            packedSICfromPhase1[i][j], payload_multicore[i], relin_keys, gal_keys_next,
                            poly_modulus_degree, context_next, context_last, params, poly_modulus_degree, counter[i]);
            if(j == 0){
                lhs_multi[i] = templhs;
                rhs_multi[i] = temprhs;
            } else {
                evaluator.add_inplace(lhs_multi[i], templhs);
                evaluator.add_inplace(rhs_multi[i], temprhs);
            }
            j++;
            payload_multicore[i].clear();
        }

        MemoryManager::SwitchProfile(std::move(old_prof));
    }
    NTL_EXEC_RANGE_END;

    for(int i = 1; i < numcores; i++){
        evaluator.add_inplace(lhs_multi[0], lhs_multi[i]);
        evaluator.add_inplace(rhs_multi[0], rhs_multi[i]);
    }

    while(context.last_parms_id() != lhs_multi[0].parms_id()){
            evaluator.mod_switch_to_next_inplace(rhs_multi[0]);
            evaluator.mod_switch_to_next_inplace(lhs_multi[0]);
        }

    time_end = chrono::high_resolution_clock::now();
    time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);
    cout << "\nDetector runnimg time: " << time_diff.count() << "us." << "\n";

    stringstream data_streamdg, data_streamdg2;
    cout << "Digest size: " << rhs_multi[0].save(data_streamdg) + lhs_multi[0].save(data_streamdg2) << " bytes" << endl;

    // step 5. receiver decoding
    bipartiteGraphWeightsGeneration(bipartite_map_glb, weights_glb, numOfTransactions,OMRtwoM,repeatition_glb,seed_glb);
    time_start = chrono::high_resolution_clock::now();
    auto res = receiverDecoding(lhs_multi[0], bipartite_map[0], rhs_multi[0],
                        poly_modulus_degree, secret_key, context, numOfTransactions);
    time_end = chrono::high_resolution_clock::now();
    time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);
    cout << "\nRecipient runnimg time: " << time_diff.count() << "us." << "\n";

    if(checkRes(expected, res))
        cout << "Result is correct!" << endl;
    else
        cout << "Overflow" << endl;
}

void OMR3(){

    size_t poly_modulus_degree = poly_modulus_degree_glb;

    int numOfTransactions = numOfTransactions_glb;
    createDatabase(numOfTransactions, 306);
    cout << "Finishing createDatabase\n";

    // step 1. generate PVW sk
    // recipient side
    auto params = PVWParam(450, 65537, 1.3, 16000, 4);
    auto sk = PVWGenerateSecretKey(params);
    auto pk = PVWGeneratePublicKey(params, sk);
    cout << "Finishing generating sk for PVW cts\n";

    // step 2. prepare transactions
    auto expected = preparinngTransactionsFormal(pk, numOfTransactions, num_of_pertinent_msgs_glb,  params);
    cout << expected.size() << " pertinent msg: Finishing preparing messages\n";

    // step 3. generate detection key
    // recipient side
    EncryptionParameters parms(scheme_type::bfv);
    auto degree = poly_modulus_degree;
    parms.set_poly_modulus_degree(poly_modulus_degree);
    auto coeff_modulus = CoeffModulus::Create(poly_modulus_degree, { 28,
                                                                            39, 60, 60, 60, 60,
                                                                            60, 60, 60, 60, 60, 60,
                                                                            32, 30, 60 });
    parms.set_coeff_modulus(coeff_modulus);
    parms.set_plain_modulus(65537);

	prng_seed_type seed;
    for (auto &i : seed)
    {
        i = random_uint64();
    }
    auto rng = make_shared<Blake2xbPRNGFactory>(Blake2xbPRNGFactory(seed));
    parms.set_random_generator(rng);

    SEALContext context(parms, true, sec_level_type::none);
    print_parameters(context);
    KeyGenerator keygen(context);
    SecretKey secret_key = keygen.secret_key();
    PublicKey public_key;
    keygen.create_public_key(public_key);
    RelinKeys relin_keys;
    keygen.create_relin_keys(relin_keys);
    Encryptor encryptor(context, public_key);
    Evaluator evaluator(context);
    Decryptor decryptor(context, secret_key);
    BatchEncoder batch_encoder(context);

    vector<Ciphertext> switchingKey;
    Ciphertext packedSIC;
    switchingKey.resize(params.ell);
    genSwitchingKeyPVWPacked(switchingKey, context, poly_modulus_degree, public_key, secret_key, sk, params);

    vector<vector<PVWCiphertext>> SICPVW_multicore(numcores);
    vector<vector<vector<uint64_t>>> payload_multicore(numcores);
    vector<int> counter(numcores);

    GaloisKeys gal_keys;
    vector<int> stepsfirst = {1};
    keygen.create_galois_keys(stepsfirst, gal_keys);

    /////////////////////////////////////////////////////////////// Rot Key gen
    vector<int> steps = {0};
    for(int i = 1; i < int(poly_modulus_degree/2); i *= 2){
	    steps.push_back(i);
    }

    cout << "Finishing generating detection keys\n";

    /////////////////////////////////////// Level specific keys
    vector<Modulus> coeff_modulus_next = coeff_modulus;
    coeff_modulus_next.erase(coeff_modulus_next.begin() + 4, coeff_modulus_next.end()-1);
    EncryptionParameters parms_next = parms;
    parms_next.set_coeff_modulus(coeff_modulus_next);
    SEALContext context_next = SEALContext(parms_next, true, sec_level_type::none);

    SecretKey sk_next;
    sk_next.data().resize(coeff_modulus_next.size() * degree);
    sk_next.parms_id() = context_next.key_parms_id();
    util::set_poly(secret_key.data().data(), degree, coeff_modulus_next.size() - 1, sk_next.data().data());
    util::set_poly(
        secret_key.data().data() + degree * (coeff_modulus.size() - 1), degree, 1,
        sk_next.data().data() + degree * (coeff_modulus_next.size() - 1));
    KeyGenerator keygen_next(context_next, sk_next);
    vector<int> steps_next = {0,1};
    keygen_next.create_galois_keys(steps_next, gal_keys_next);
        //////////////////////////////////////
    vector<Modulus> coeff_modulus_last = coeff_modulus;
    coeff_modulus_last.erase(coeff_modulus_last.begin() + 2, coeff_modulus_last.end()-1);
    EncryptionParameters parms_last = parms;
    parms_last.set_coeff_modulus(coeff_modulus_last);
    SEALContext context_last = SEALContext(parms_last, true, sec_level_type::none);

    SecretKey sk_last;
    sk_last.data().resize(coeff_modulus_last.size() * degree);
    sk_last.parms_id() = context_last.key_parms_id();
    util::set_poly(secret_key.data().data(), degree, coeff_modulus_last.size() - 1, sk_last.data().data());
    util::set_poly(
        secret_key.data().data() + degree * (coeff_modulus.size() - 1), degree, 1,
        sk_last.data().data() + degree * (coeff_modulus_last.size() - 1));
    KeyGenerator keygen_last(context_last, sk_last);
    keygen_last.create_galois_keys(steps, gal_keys_last);
    PublicKey public_key_last;
    keygen_last.create_public_key(public_key_last);

    //////////////////////////////////////

    vector<vector<Ciphertext>> packedSICfromPhase1(numcores,vector<Ciphertext>(numOfTransactions/numcores/poly_modulus_degree)); // Assume numOfTransactions/numcores/poly_modulus_degree is integer, pad if needed

    NTL::SetNumThreads(numcores);
    SecretKey secret_key_blank;

    chrono::high_resolution_clock::time_point time_start, time_end;
    chrono::microseconds time_diff;
    time_start = chrono::high_resolution_clock::now();

    MemoryPoolHandle my_pool = MemoryPoolHandle::New();
    auto old_prof = MemoryManager::SwitchProfile(std::make_unique<MMProfFixed>(std::move(my_pool)));
    NTL_EXEC_RANGE(numcores, first, last);
    for(int i = first; i < last; i++){
        counter[i] = numOfTransactions/numcores*i;

        size_t j = 0;
        while(j < numOfTransactions/numcores/poly_modulus_degree){
            if(!i)
                cout << "Phase 1, Core " << i << ", Batch " << j << endl;
            loadClues(SICPVW_multicore[i], counter[i], counter[i]+poly_modulus_degree, params);
            packedSICfromPhase1[i][j] = serverOperations1obtainPackedSIC(SICPVW_multicore[i], switchingKey, relin_keys, gal_keys,
                                                            poly_modulus_degree, context, params, poly_modulus_degree);
            j++;
            counter[i] += poly_modulus_degree;
            SICPVW_multicore[i].clear();
        }

    }
    NTL_EXEC_RANGE_END;
    MemoryManager::SwitchProfile(std::move(old_prof));

    // step 4. detector operations
    vector<vector<vector<Ciphertext>>> lhs_multi(numcores);
    vector<vector<Ciphertext>> lhs_multi_ctr(numcores);
    vector<Ciphertext> rhs_multi(numcores);
    vector<vector<vector<int>>> bipartite_map(numcores);

    bipartiteGraphWeightsGeneration(bipartite_map_glb, weights_glb, numOfTransactions,OMRtwoM,repeatition_glb,seed_glb);

    NTL_EXEC_RANGE(numcores, first, last);
    for(int i = first; i < last; i++){
        MemoryPoolHandle my_pool = MemoryPoolHandle::New();
        auto old_prof = MemoryManager::SwitchProfile(std::make_unique<MMProfFixed>(std::move(my_pool)));
        size_t j = 0;
        counter[i] = numOfTransactions/numcores*i;

        while(j < numOfTransactions/numcores/poly_modulus_degree){
            if(!i)
                cout << "Phase 2-3, Core " << i << ", Batch " << j << endl;
            loadData(payload_multicore[i], counter[i], counter[i]+poly_modulus_degree);
            vector<vector<Ciphertext>> templhs;
            vector<Ciphertext> templhsctr;
            Ciphertext temprhs;
            serverOperations3therest(templhs, templhsctr, bipartite_map[i], temprhs,
                            packedSICfromPhase1[i][j], payload_multicore[i], relin_keys, gal_keys_next, public_key_last,
                            poly_modulus_degree, context_next, context_last, params, poly_modulus_degree, counter[i]);
            if(j == 0){
                lhs_multi[i] = templhs;
                lhs_multi_ctr[i] = templhsctr;
                rhs_multi[i] = temprhs;
            } else {
                for(size_t q = 0; q < lhs_multi[i].size(); q++){
                    for(size_t w = 0; w < lhs_multi[i][q].size(); w++){
                        evaluator.add_inplace(lhs_multi[i][q][w], templhs[q][w]);
                    }
                }
                for(size_t q = 0; q < lhs_multi_ctr[i].size(); q++){
                    evaluator.add_inplace(lhs_multi_ctr[i][q], templhsctr[q]);
                }
                evaluator.add_inplace(rhs_multi[i], temprhs);
            }
            j++;
            payload_multicore[i].clear();
        }

        MemoryManager::SwitchProfile(std::move(old_prof));
    }
    NTL_EXEC_RANGE_END;

    for(int i = 1; i < numcores; i++){
        for(size_t q = 0; q < lhs_multi[i].size(); q++){
            for(size_t w = 0; w < lhs_multi[i][q].size(); w++){
                evaluator.add_inplace(lhs_multi[0][q][w], lhs_multi[i][q][w]);
            }
        }
        for(size_t q = 0; q < lhs_multi_ctr[i].size(); q++){
            evaluator.add_inplace(lhs_multi_ctr[0][q], lhs_multi_ctr[i][q]);
        }
        evaluator.add_inplace(rhs_multi[0], rhs_multi[i]);
    }

    while(context.last_parms_id() != lhs_multi[0][0][0].parms_id()){
            for(size_t q = 0; q < lhs_multi[0].size(); q++){
                for(size_t w = 0; w < lhs_multi[0][q].size(); w++){
                    evaluator.mod_switch_to_next_inplace(lhs_multi[0][q][w]);
                }
            }
            for(size_t q = 0; q < lhs_multi_ctr[0].size(); q++){
                evaluator.mod_switch_to_next_inplace(lhs_multi_ctr[0][q]);
            }
            evaluator.mod_switch_to_next_inplace(rhs_multi[0]);
        }

    time_end = chrono::high_resolution_clock::now();
    time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);
    cout << "\nDetector runnimg time: " << time_diff.count() << "us." << "\n";

    stringstream data_streamdg, data_streamdg2;
    auto digsize = rhs_multi[0].save(data_streamdg);
    for(size_t q = 0; q < lhs_multi[0].size(); q++){
        for(size_t w = 0; w < lhs_multi[0][q].size(); w++){
            digsize += lhs_multi[0][q][w].save(data_streamdg2);
        }
    }
    for(size_t q = 0; q < lhs_multi_ctr[0].size(); q++){
        digsize += lhs_multi_ctr[0][q].save(data_streamdg2);
    }
    cout << "Digest size: " << digsize << " bytes" << endl;

    // step 5. receiver decoding
    bipartiteGraphWeightsGeneration(bipartite_map_glb, weights_glb, numOfTransactions,OMRtwoM,repeatition_glb,seed_glb);
    time_start = chrono::high_resolution_clock::now();
    auto res = receiverDecodingOMR3(lhs_multi[0], lhs_multi_ctr[0], bipartite_map[0], rhs_multi[0],
                        poly_modulus_degree, secret_key, context, numOfTransactions);
    time_end = chrono::high_resolution_clock::now();
    time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);
    cout << "\nRecipient runnimg time: " << time_diff.count() << "us." << "\n";

    if(checkRes(expected, res))
        cout << "Result is correct!" << endl;
    else
        cout << "Overflow" << endl;

    for(size_t i = 0; i < res.size(); i++){

    }

}

int main(){

    cout << "+------------------------------------+" << endl;
    cout << "| Demos                              |" << endl;
    cout << "+------------------------------------+" << endl;
    cout << "| 1. OMD1p Detection Key Size        |" << endl;
    cout << "| 2. OMR1p/OMR2p Detection Key Size  |" << endl;
    cout << "| 3. OMD1p                           |" << endl;
    cout << "| 4. OMR1p Single Thread             |" << endl;
    cout << "| 5. OMR2p Single Thread             |" << endl;
    cout << "| 6. OMR1p Two Threads               |" << endl;
    cout << "| 7. OMR2p Two Threads               |" << endl;
    cout << "| 8. OMR1p Four Threads              |" << endl;
    cout << "| 9. OMR2p Four Threads              |" << endl;
    cout << "+------------------------------------+" << endl;

    int selection = 0;
    bool valid = true;
    do
    {
        cout << endl << "> Run demos (1 ~ 9) or exit (0): ";
        if (!(cin >> selection))
        {
            valid = false;
        }
        else if (selection < 0 || selection > 9)
        {
            valid = false;
        }
        else
        {
            valid = true;
        }
        if (!valid)
        {
            cout << "  [Beep~~] valid option: type 0 ~ 9" << endl;
            cin.clear();
            cin.ignore(numeric_limits<streamsize>::max(), '\n');
        }
    } while (!valid);

    switch (selection)
        {
        case 1:
            OMDlevelspecificDetectKeySize();
            break;

        case 2:
            levelspecificDetectKeySize();
            break;

        case 3:
            numcores = 1;
            OMD1p();
            break;

        case 4:
            numcores = 1;
            OMR2();
            break;

        case 5:
            numcores = 1;
            OMR3();
            break;

        case 6:
            numcores = 2;
            OMR2();
            break;

        case 7:
            numcores = 2;
            OMR3();
            break;

        case 8:
            numcores = 4;
            OMR2();
            break;

        case 9:
            numcores = 4;
            OMR3();
            break;

        case 0:
            return 0;
        }

}


#include "SolveProportion.h"
#include <locale>
#include <boost/lexical_cast.hpp>

namespace
{
	template <class StrT>
	StrT& Prepare(StrT& s)
	{
		typedef typename StrT::value_type char_t;
		static const std::locale l;
		std::for_each(s.begin(), s.end(),
				[&] (char_t& s) { s = std::tolower(s,l); }
				);
		return s;
	}
}

SolveProportion::SolveProportion(std::wstring a, std::wstring b, std::wstring c, std::wstring d)
: _name(L"Solve proportion: " + a + L'/' +b+ L'=' +c+ L'/' + d)
{
	Init(Prepare(a), Prepare(b), Prepare(c), Prepare(d));
}

SolveProportion::~SolveProportion(void)
{
}

bool SolveProportion::Init(string_t& a, string_t b, string_t c, string_t d)
{
	bool initialized = a == L"x";
	if (initialized)
	{
		_result = boost::lexical_cast<result_t>(b) * boost::lexical_cast<result_t>(c) / boost::lexical_cast<result_t>(d);
	}
	else
	{
		initialized = c==L"x" ? Init(c,d,a,b) : Init(b,a,d,c);
	}

	return initialized;
}

SolveProportion::string_t SolveProportion::SerializedResult()
{
	return boost::lexical_cast<string_t>(_result);
}

void* SolveProportion::GetResult()
{
	return &_result;
}


// The MIT License (MIT)
//
// Copyright (c) 2015 Jonathan McCluskey and William Harding
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

#define BOOST_TEST_DYN_LINK

#include <boost/test/unit_test.hpp>
#include <gmpxx.h>
#include <omp.h>

#include "PrimeGenerator.h"

BOOST_AUTO_TEST_CASE(prime_test_1)
{
    double start = omp_get_wtime();
    mpz_class prime = PrimeGenerator::GetPrimeNumber(512);
    double end = omp_get_wtime();
    std::cout << "Prime Generator (512 bit) Test 1 Timing " << end - start << "s" << std::endl;

    // Documentation says that 25 tests will allow a
    // composite to be returned as prime with proba-
    // bility less than 2^(-50)
    int probably_prime = mpz_probab_prime_p(prime.get_mpz_t(), 25);

    BOOST_CHECK_EQUAL(probably_prime, 1);

    start = omp_get_wtime();
    prime = PrimeGenerator::GetPrimeNumber(1024);
    end = omp_get_wtime();
    std::cout << "Prime Generator (1024 bit) Test 1 Timing " << end - start << "s" << std::endl;

    probably_prime = mpz_probab_prime_p(prime.get_mpz_t(), 25);

    BOOST_CHECK_EQUAL(probably_prime, 1);

    start = omp_get_wtime();
    prime = PrimeGenerator::GetPrimeNumber(1024);
    end = omp_get_wtime();
    std::cout << "Prime Generator (2048 bit) Test 1 Timing " << end - start << "s" << std::endl;

    probably_prime = mpz_probab_prime_p(prime.get_mpz_t(), 25);

    BOOST_CHECK_EQUAL(probably_prime, 1);
}


/*
 * Copyright (c) 2006 Michael Stevens
 * Use, modification and distribution are subject to 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)
 */

#include <iostream>

#include <boost/numeric/ublas/vector_sparse.hpp>

typedef double Real;

template <class V>
void printV(const V& v) {
  std::cout << "size: " << v.size() << " nnz_capacity: " << v.nnz_capacity() << " nnz: " << v.nnz() << std::endl;
  for (typename V::const_iterator i = v.begin(); i != v.end(); i++) {
    std::cout << i.index() << ":" << (*i) << "  ";
  }
  std::cout << std::endl;
}

template <class V>
void run_test()
{
  V v(10);

  v[0] = 1;
  v[5] = 1;
  v[8] = 1;
  v[9] = 1;

  printV(v);

  v.resize(9); printV(v);
  v.resize(12); printV(v);
  v.resize(2); printV(v);
  v.resize(0); printV(v);

  v.resize(5); v[0] = 1; printV(v);
  v.resize(5,false); printV(v);
}

int main(int, char **) {

  std::cout << "---- MAPPED ----\n";
  run_test< boost::numeric::ublas::mapped_vector<Real> >();
  std::cout << "---- COMPRESSED ----\n";
  run_test< boost::numeric::ublas::compressed_vector<Real> >();
  std::cout << "---- COORDINATE ----\n";
  run_test< boost::numeric::ublas::coordinate_vector<Real> >();

  return 0;
}


// Copyright (c) 2019 Graphcore Ltd. All rights reserved.
#define BOOST_TEST_MODULE GraphReplicationTest
#include <boost/test/unit_test.hpp>
#include <math.h>
#include <poplar/Engine.hpp>
#include <poplar/Graph.hpp>
#include <poplibs_support/TestDevice.hpp>
#include <popops/DynamicSlice.hpp>
#include <popops/ElementWise.hpp>
#include <popops/ScaledAdd.hpp>
#include <popops/codelets.hpp>
#include <poputil/TileMapping.hpp>
#include <poputil/Util.hpp>

using namespace poplar;
using namespace poplar::program;
using namespace poplibs_support;

BOOST_AUTO_TEST_CASE(InitialStateReplication) {
  auto device = createTestDevice(TEST_TARGET, 1, 16);
  poplar::Graph graph(device.getTarget());
  popops::addCodelets(graph);

  Sequence sequence;

  auto const1 = graph.addConstant(INT, {1}, 0);
  graph.setTileMapping(const1, 0);

  auto const2 = graph.addConstant(FLOAT, {1}, 0.0);
  graph.setTileMapping(const2, 0);
  auto c2_bcast = const2.broadcast(300, 0).reshape({3, 10, 10});

  auto counter = poputil::duplicate(graph, const1, sequence);
  auto to_slice = graph.clone(c2_bcast, "blah");
  sequence.add(poplar::program::Copy(c2_bcast, to_slice));
  poputil::mapTensorLinearly(graph, to_slice);
  {
    Sequence repeat_sequence;
    auto counter_unsigned = counter.reinterpret(UNSIGNED_INT);
    auto slice = popops::dynamicSlice(graph, to_slice, counter_unsigned, {0},
                                      {1}, repeat_sequence);
    auto val = graph.addConstant(FLOAT, {1}, 1.0);
    graph.setTileMapping(val, 0);
    auto val_bcast = val.broadcast(100, 0).reshape({1, 10, 10});
    popops::scaledAddTo(graph, slice, val_bcast, 1.0f, repeat_sequence);
    popops::dynamicUpdate(graph, to_slice, slice, counter_unsigned, {0}, {1},
                          repeat_sequence);

    // increase counter.
    auto one = graph.addConstant(INT, {1}, 1);
    graph.setTileMapping(one, 0);
    popops::scaledAddTo(graph, counter, one, 1.0f, repeat_sequence);

    sequence.add(poplar::program::Repeat(3, repeat_sequence));
  }
  auto fifo_out =
      graph.addDeviceToHostFIFO("out_0.0", FLOAT, to_slice.numElements());

  sequence.add(poplar::program::Copy(to_slice, fifo_out, false));

  const std::string runtimeVerify =
      (isSimulator(TEST_TARGET)) ? "true" : "false";
  Engine engine(graph, sequence,
                {// T8477: Find why test fails if this is removed:
                 {"debug.runtimeVerify", runtimeVerify}});
  float result[3][10][10];
  device.bind([&](const Device &d) {
    engine.load(d);
    engine.connectStream("out_0.0", &result);
    engine.run(0);
  });

  for (size_t i = 0; i != 3; ++i) {
    for (size_t j = 0; j != 10; ++j) {
      for (size_t k = 0; k != 10; ++k) {
        BOOST_CHECK_EQUAL(result[i][j][k], 1);
      }
    }
  }
}