/*******************************************************************************
 *
 * MIT License
 *
 * Copyright (c) 2017 Advanced Micro Devices, Inc.
 *
 * 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.
 *
 *******************************************************************************/
#include <cmath>
#include <miopen/kernel_cache.hpp>
#include <miopen/util.hpp>
#include <miopen/logger.hpp>
#include <miopen/datatype.hpp>

#include <boost/range/adaptors.hpp>

#define WG_SIZE 256
#define MAX_ACTIVE_THREADS (64 * 4 * 64)
#define MAX_LOCAL_MEM 65536

namespace miopen {

float Im2d2ColGPU(Handle& handle,
                  ConstData_t im,
                  const int im_offset,
                  const int c,
                  const int in_h,
                  const int in_w,
                  const int wei_h,
                  const int wei_w,
                  const int out_h,
                  const int out_w,
                  const int pad_h,
                  const int pad_w,
                  const int stride_h,
                  const int stride_w,
                  const int dilation_h,
                  const int dilation_w,
                  Data_t col,
                  miopenDataType_t type)
{
    std::string program_name = "MIOpenIm2d2Col.cl";
    std::string kernel_name  = "Im2d2Col";

    // clang-format off
    std::string network_config =
        "c" + std::to_string(c) +
        "i" + std::to_string(in_h) +
        "_" + std::to_string(in_w) +
        "w" + std::to_string(wei_h) +
        "_" + std::to_string(wei_w) +
        "p" + std::to_string(pad_h) +
        "_" + std::to_string(pad_w) +
        "s" + std::to_string(stride_h) +
        "_" + std::to_string(stride_w) +
        "d" + std::to_string(dilation_h) +
        "_" + std::to_string(dilation_w) +
        "t" + std::to_string(type);
    // clang-format on

    auto&& kernels = handle.GetKernels("miopenIm2d2Col", network_config);

    int data_size_bound = c * in_h * in_w;

    int data_size_bound_pack = type == miopenInt8x4 ? data_size_bound * 4 : data_size_bound;
    int im_offset_pack       = type == miopenInt8x4 ? im_offset / 4 : im_offset;

    if(!kernels.empty())
    {
        auto kernel = kernels.front();
        kernel(data_size_bound_pack,
               im,
               im_offset_pack,
               in_h,
               in_w,
               wei_h,
               wei_w,
               out_h,
               out_w,
               pad_h,
               pad_w,
               stride_h,
               stride_w,
               dilation_h,
               dilation_w,
               col);
    }
    else
    {
        const int c_pack = type == miopenInt8x4 ? c / 4 : c;

        std::string params;
        int num_ch_per_wg;
        if((out_h <= 8 && out_w <= 8) && (stride_h == 1 && stride_w == 1) && (c_pack % 4 == 0))
            num_ch_per_wg = 4;
        else
            num_ch_per_wg = 1;

        int tile_sz_x  = 32;
        int tile_sz_y  = 8;
        int num_blks_x = std::ceil(static_cast<float>(out_w) / static_cast<float>(tile_sz_x));
        int num_blks =
            num_blks_x *
            static_cast<int>(std::ceil(static_cast<float>(out_h) / static_cast<float>(tile_sz_y)));
        int local_mem_sz;
        if(num_ch_per_wg == 1)
        {
            local_mem_sz = ((tile_sz_x - 1) * stride_w + (wei_w - 1) * dilation_w + 1) *
                           ((tile_sz_y - 1) * stride_h + (wei_h - 1) * dilation_h + 1);
        }
        else
        {
            auto uprdTileX = static_cast<int>(
                std::ceil(static_cast<float>(tile_sz_x) / static_cast<float>(num_ch_per_wg)) - 1);
            auto uprdTileY = static_cast<int>(
                std::ceil(static_cast<float>(tile_sz_y) / static_cast<float>(num_ch_per_wg)) - 1);
            auto memXsize = (num_ch_per_wg * uprdTileX * stride_w + (wei_w - 1) * dilation_w + 1) *
                            ((tile_sz_y - 1) * stride_h + (wei_h - 1) * dilation_h + 1);
            auto memYsize = num_ch_per_wg *
                            ((tile_sz_x - 1) * stride_w + (wei_w - 1) * dilation_w + 1) *
                            (uprdTileY * stride_h + (wei_h - 1) * dilation_h + 1);
            local_mem_sz = static_cast<int>(std::max(memXsize, memYsize));
        }

        // adjust mapping for large kernel
        int type_size    = 4; // Need to adjust for fp16, int8
        int extreme_case = num_ch_per_wg * ((wei_w - 1) * dilation_w + 1) *
                           ((wei_h - 1) * dilation_h + 1) * type_size;
        if(extreme_case > MAX_LOCAL_MEM)
        {
            params += " -DEXTREME_LARGE";
            params += " -DNUM_CH_TOTAL=" + std::to_string(c_pack);
        }
        else
        {
            while(local_mem_sz * type_size > MAX_LOCAL_MEM)
            {
                tile_sz_x  = tile_sz_x == 1 ? 1 : (tile_sz_y == 1 ? (tile_sz_x / 2) : tile_sz_x);
                tile_sz_y  = tile_sz_y == 1 ? 1 : (tile_sz_y / 2);
                num_blks_x = std::ceil(static_cast<float>(out_w) / static_cast<float>(tile_sz_x));
                num_blks   = num_blks_x * static_cast<int>(std::ceil(static_cast<float>(out_h) /
                                                                   static_cast<float>(tile_sz_y)));
                if(num_ch_per_wg == 1)
                {
                    local_mem_sz = ((tile_sz_x - 1) * stride_w + (wei_w - 1) * dilation_w + 1) *
                                   ((tile_sz_y - 1) * stride_h + (wei_h - 1) * dilation_h + 1);
                }
                else
                {
                    auto uprdTileX = static_cast<int>(std::ceil(static_cast<float>(tile_sz_x) /
                                                                static_cast<float>(num_ch_per_wg)) -
                                                      1);
                    auto uprdTileY = static_cast<int>(std::ceil(static_cast<float>(tile_sz_y) /
                                                                static_cast<float>(num_ch_per_wg)) -
                                                      1);
                    auto memXsize =
                        (num_ch_per_wg * uprdTileX * stride_w + (wei_w - 1) * dilation_w + 1) *
                        ((tile_sz_y - 1) * stride_h + (wei_h - 1) * dilation_h + 1);
                    auto memYsize = num_ch_per_wg *
                                    ((tile_sz_x - 1) * stride_w + (wei_w - 1) * dilation_w + 1) *
                                    (uprdTileY * stride_h + (wei_h - 1) * dilation_h + 1);
                    local_mem_sz = static_cast<int>(std::max(memXsize, memYsize));
                }
            }
        }

        params += " -DNUM_CH_PER_WG=" + std::to_string(num_ch_per_wg);
        params += " -DNUM_IM_BLKS_X=" + std::to_string(num_blks_x);
        params += " -DNUM_IM_BLKS=" + std::to_string(num_blks);
        params += " -DLOCAL_MEM_SIZE=" + std::to_string(local_mem_sz);
        params += " -DSTRIDE_GT_1=" + std::to_string(static_cast<int>(stride_h * stride_w > 1));
        params += " -DTILE_SZ_X=" + std::to_string(tile_sz_x);
        params += " -DTILE_SZ_Y=" + std::to_string(tile_sz_y);
        params += " -DUSE_IM_OFF_GUARD=1";

        params += GetDataTypeKernelParams(type);

        const std::vector<size_t> vld{256, 1, 1};
        size_t global_threads = 256 * std::max(1, (c_pack / num_ch_per_wg)) * num_blks;
        const std::vector<size_t> vgd{global_threads, 1, 1};
        handle.AddKernel(
            "miopenIm2Col", network_config, program_name, kernel_name, vld, vgd, params)(
            data_size_bound_pack,
            im,
            im_offset_pack,
            in_h,
            in_w,
            wei_h,
            wei_w,
            out_h,
            out_w,
            pad_h,
            pad_w,
            stride_h,
            stride_w,
            dilation_h,
            dilation_w,
            col);
    }

    return handle.GetKernelTime();
}

float Im3d2ColGPU(Handle& handle,
                  ConstData_t im,
                  const int im_offset,
                  const int im_c,
                  const int im_d,
                  const int im_h,
                  const int im_w,
                  const int wei_d,
                  const int wei_h,
                  const int wei_w,
                  const int out_d,
                  const int out_h,
                  const int out_w,
                  const int pad_d,
                  const int pad_h,
                  const int pad_w,
                  const int stride_d,
                  const int stride_h,
                  const int stride_w,
                  const int dilation_d,
                  const int dilation_h,
                  const int dilation_w,
                  Data_t col,
                  miopenDataType_t type)
{
    std::string program_name = "MIOpenIm3d2Col.cl";
    std::string kernel_name  = "Im3d2Col";

    // clang-format off
    std::string network_config =
        "c" + std::to_string(im_c) +
        "i" + std::to_string(im_d) +
        "_" + std::to_string(im_h) +
        "_" + std::to_string(im_w) +
        "w" + std::to_string(wei_d) +
        "_" + std::to_string(wei_h) +
        "_" + std::to_string(wei_w) +
        "p" + std::to_string(pad_d) +
        "_" + std::to_string(pad_h) +
        "_" + std::to_string(pad_w) +
        "s" + std::to_string(stride_d) +
        "_" + std::to_string(stride_h) +
        "_" + std::to_string(stride_w) +
        "d" + std::to_string(dilation_d) +
        "_" + std::to_string(dilation_h) +
        "_" + std::to_string(dilation_w) +
        "t" + std::to_string(type);
    // clang-format on

    auto&& kernels = handle.GetKernels("miopenIm3d2Col", network_config);

    // int8x4 vectorize-c format
    int im_offset_pack = type == miopenInt8x4 ? im_offset / 4 : im_offset;
    int im_c_pack      = type == miopenInt8x4 ? im_c / 4 : im_c;

    if(!kernels.empty())
    {
        auto kernel = kernels.front();
        kernel(im,
               im_offset_pack,
               im_c_pack,
               im_d,
               im_h,
               im_w,
               wei_d,
               wei_h,
               wei_w,
               out_d,
               out_h,
               out_w,
               pad_d,
               pad_h,
               pad_w,
               stride_d,
               stride_h,
               stride_w,
               dilation_d,
               dilation_h,
               dilation_w,
               col);
    }
    else
    {
        std::string params = GetDataTypeKernelParams(type);

        const std::vector<size_t> vld{256, 1, 1};
        size_t global_threads = std::min(
            256 * static_cast<std::size_t>(out_d * out_h * out_w * im_c * wei_d * wei_h * wei_w) /
                8,
            static_cast<std::size_t>(256) * 1024);
        const std::vector<size_t> vgd{global_threads, 1, 1};

        handle.AddKernel(
            "miopenIm3d2Col", network_config, program_name, kernel_name, vld, vgd, params)(
            im,
            im_offset_pack,
            im_c_pack,
            im_d,
            im_h,
            im_w,
            wei_d,
            wei_h,
            wei_w,
            out_d,
            out_h,
            out_w,
            pad_d,
            pad_h,
            pad_w,
            stride_d,
            stride_h,
            stride_w,
            dilation_d,
            dilation_h,
            dilation_w,
            col);
    }

    return handle.GetKernelTime();
}

float Col2Im2dGPU(Handle& handle,
                  ConstData_t col,
                  const int out_h,
                  const int out_w,
                  const int wei_h,
                  const int wei_w,
                  const int pad_h,
                  const int pad_w,
                  const int stride_h,
                  const int stride_w,
                  const int dilation_h,
                  const int dilation_w,
                  const int in_c,
                  const int in_h,
                  const int in_w,
                  Data_t im,
                  int im_offset,
                  miopenDataType_t type)
{
    std::string program_name = "MIOpenCol2Im2d.cl";
    std::string kernel_name  = "Col2Im2d";

    // clang-format off
    std::string network_config =
        "c" + std::to_string(in_c) +
        "in_h" + std::to_string(in_h) +
        "in_w" + std::to_string(in_w) +
        "y" + std::to_string(wei_h) +
        "x" + std::to_string(wei_w) +
        "p" + std::to_string(pad_h) +
        "q" + std::to_string(pad_w) +
        "u" + std::to_string(stride_h) +
        "v" + std::to_string(stride_w) +
        "l" + std::to_string(dilation_h) +
        "j" + std::to_string(dilation_w) +
        "t" + std::to_string(type);
    // clang-format on

    auto&& kernels = handle.GetKernels("miopenCol2Im2d", network_config);

    if(!kernels.empty())
    {
        auto kernel = kernels.front();
        kernel(col,
               out_h,
               out_w,
               wei_h,
               wei_w,
               pad_h,
               pad_w,
               stride_h,
               stride_w,
               dilation_h,
               dilation_w,
               in_h,
               in_w,
               im,
               im_offset);
    }
    else
    {
        std::string params = GetDataTypeKernelParams(type);

        const std::vector<size_t> vld{256, 1, 1};
        size_t global_threads = in_c * in_h * in_w;
        const std::vector<size_t> vgd{global_threads, 1, 1};

        handle.AddKernel(
            "miopenCol2Im2d", network_config, program_name, kernel_name, vld, vgd, params)(
            col,
            out_h,
            out_w,
            wei_h,
            wei_w,
            pad_h,
            pad_w,
            stride_h,
            stride_w,
            dilation_h,
            dilation_w,
            in_h,
            in_w,
            im,
            im_offset);
    }
    return handle.GetKernelTime();
}

float Col2Im3dGPU(Handle& handle,
                  ConstData_t col,
                  const int out_d,
                  const int out_h,
                  const int out_w,
                  const int wei_d,
                  const int wei_h,
                  const int wei_w,
                  const int pad_d,
                  const int pad_h,
                  const int pad_w,
                  const int stride_d,
                  const int stride_h,
                  const int stride_w,
                  const int dilation_d,
                  const int dilation_h,
                  const int dilation_w,
                  const int in_c,
                  const int in_d,
                  const int in_h,
                  const int in_w,
                  Data_t im,
                  int im_offset,
                  miopenDataType_t type)
{
    std::string program_name = "MIOpenCol2Im3d.cl";
    std::string kernel_name  = "Col2Im3d";

    // clang-format off
    std::string network_config =
        "c" + std::to_string(in_c) +
        "i" + std::to_string(in_d) +
        "_" + std::to_string(in_h) +
        "_" + std::to_string(in_w) +
        "w" + std::to_string(wei_d) +
        "_" + std::to_string(wei_h) +
        "_" + std::to_string(wei_w) +
        "p" + std::to_string(pad_d) +
        "_" + std::to_string(pad_h) +
        "_" + std::to_string(pad_w) +
        "s" + std::to_string(stride_d) +
        "_" + std::to_string(stride_h) +
        "_" + std::to_string(stride_w) +
        "d" + std::to_string(dilation_d) +
        "_" + std::to_string(dilation_h) +
        "_" + std::to_string(dilation_w) +
        "t" + std::to_string(type);
    // clang-format on

    auto&& kernels = handle.GetKernels("miopenCol2Im3d", network_config);

    if(!kernels.empty())
    {
        auto kernel = kernels.front();
        kernel(col,
               out_d,
               out_h,
               out_w,
               wei_d,
               wei_h,
               wei_w,
               pad_d,
               pad_h,
               pad_w,
               stride_d,
               stride_h,
               stride_w,
               dilation_d,
               dilation_h,
               dilation_w,
               in_d,
               in_h,
               in_w,
               im,
               im_offset);
    }
    else
    {
        std::string params = GetDataTypeKernelParams(type);

        const std::vector<size_t> vld{256, 1, 1};
        size_t global_threads = in_c * in_d * in_h * in_w;
        const std::vector<size_t> vgd{global_threads, 1, 1};

        handle.AddKernel(
            "miopenCol2Im3d", network_config, program_name, kernel_name, vld, vgd, params)(
            col,
            out_d,
            out_h,
            out_w,
            wei_d,
            wei_h,
            wei_w,
            pad_d,
            pad_h,
            pad_w,
            stride_d,
            stride_h,
            stride_w,
            dilation_d,
            dilation_h,
            dilation_w,
            in_d,
            in_h,
            in_w,
            im,
            im_offset);
    }
    return handle.GetKernelTime();
}

float Im2ColGPU(
    Handle& handle,
    std::size_t spatial_dim,
    ConstData_t im,
    std::size_t im_offset,
    std::size_t in_c,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& in_spatial,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& wei_spatial,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& out_spatial,
    const std::vector<int>& pad_spatial,
    const std::vector<int>& stride_spatial,
    const std::vector<int>& dilation_spatial,
    Data_t col,
    miopenDataType_t type)
{
    switch(spatial_dim)
    {
    case 2:
    {
        return Im2d2ColGPU(handle,
                           im,
                           im_offset,
                           in_c,
                           in_spatial[0],
                           in_spatial[1],
                           wei_spatial[0],
                           wei_spatial[1],
                           out_spatial[0],
                           out_spatial[1],
                           pad_spatial[0],
                           pad_spatial[1],
                           stride_spatial[0],
                           stride_spatial[1],
                           dilation_spatial[0],
                           dilation_spatial[1],
                           col,
                           type);
    }
    case 3:
    {
        return Im3d2ColGPU(handle,
                           im,
                           im_offset,
                           in_c,
                           in_spatial[0],
                           in_spatial[1],
                           in_spatial[2],
                           wei_spatial[0],
                           wei_spatial[1],
                           wei_spatial[2],
                           out_spatial[0],
                           out_spatial[1],
                           out_spatial[2],
                           pad_spatial[0],
                           pad_spatial[1],
                           pad_spatial[2],
                           stride_spatial[0],
                           stride_spatial[1],
                           stride_spatial[2],
                           dilation_spatial[0],
                           dilation_spatial[1],
                           dilation_spatial[2],
                           col,
                           type);
    }
    default: { MIOPEN_THROW("unsupported convolution dimension");
    }
    }
}

float Col2ImGPU(
    Handle& handle,
    std::size_t spatial_dim,
    ConstData_t col,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& out_spatial,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& wei_spatial,
    const std::vector<int>& pad_spatial,
    const std::vector<int>& stride_spatial,
    const std::vector<int>& dilation_spatial,
    std::size_t in_c,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& in_spatial,
    Data_t im,
    std::size_t im_offset,
    miopenDataType_t type)
{
    switch(spatial_dim)
    {
    case 2:
    {
        return Col2Im2dGPU(handle,
                           col,
                           out_spatial[0],
                           out_spatial[1],
                           wei_spatial[0],
                           wei_spatial[1],
                           pad_spatial[0],
                           pad_spatial[1],
                           stride_spatial[0],
                           stride_spatial[1],
                           dilation_spatial[0],
                           dilation_spatial[1],
                           in_c,
                           in_spatial[0],
                           in_spatial[1],
                           im,
                           im_offset,
                           type);
    }
    case 3:
    {
        return Col2Im3dGPU(handle,
                           col,
                           out_spatial[0],
                           out_spatial[1],
                           out_spatial[2],
                           wei_spatial[0],
                           wei_spatial[1],
                           wei_spatial[2],
                           pad_spatial[0],
                           pad_spatial[1],
                           pad_spatial[2],
                           stride_spatial[0],
                           stride_spatial[1],
                           stride_spatial[2],
                           dilation_spatial[0],
                           dilation_spatial[1],
                           dilation_spatial[2],
                           in_c,
                           in_spatial[0],
                           in_spatial[1],
                           in_spatial[2],
                           im,
                           im_offset,
                           type);
    }
    default: { MIOPEN_THROW("unsupported convolution dimension");
    }
    }

    MIOPEN_THROW("unsupported convolution dimension");
}

float transpose_NCHW2CNHW(Handle& handle,
                          int n,
                          int c,
                          int h_in,
                          int w_in,
                          int h_out,
                          int w_out,
                          ConstData_t in,
                          Data_t out,
                          int in_offset,
                          int out_offset,
                          int h_stride,
                          int w_stride,
                          miopenDataType_t type)
{

    std::string program_name = "MIOpenUtilKernels4.cl";

    std::string network_config = "n" + std::to_string(n) + "c" + std::to_string(c) + "h" +
                                 std::to_string(h_in) + "w" + std::to_string(w_in) + "inoff" +
                                 std::to_string(in_offset) + "otoff" + std::to_string(out_offset) +
                                 "u" + std::to_string(h_stride) + "v" + std::to_string(w_stride) +
                                 "t" + std::to_string(type);

    std::string kernel_name = "transpose_NCHW2CNHW";

    if(h_stride == 1 && w_stride == 1 && type == miopenFloat)
        kernel_name += "_opt";

    auto&& kernels = handle.GetKernels(kernel_name, network_config);

    if(!kernels.empty())
    {
        auto kernel = kernels.front();
        kernel(in, out);
    }
    else
    {
        std::string params = GetDataTypeKernelParams(type);

        if(type == miopenInt8x4)
        {
            c /= 4;
            in_offset /= 4;
            out_offset /= 4;
        }

        if(h_stride == 1 && w_stride == 1 && type == miopenFloat)
        {
            params +=
                " -DNC_TRANS_NCHW_OPT=1 -DNC_TRANS_CNHW_OPT=0 -DNC_TRANS_NCHW=0 -DNC_TRANS_CNHW=0";

            int RD_BLCK      = ((h_in * w_in) % 4 == 0) ? 4 : ((h_in * w_in) % 2 == 0) ? 2 : 1;
            int HW_RD        = (h_in * w_in) / RD_BLCK;
            size_t MAP_RD    = HW_RD * c;
            size_t lcl_size0 = WG_SIZE; //((MAP_RD + 63)/64 < 4) ? ((MAP_RD + 63)/64)*64 : 256;

            std::string READ_TYPE = (RD_BLCK == 1) ? "float" : "float" + std::to_string(RD_BLCK);

            params += " -DIN_OFF=" + std::to_string(in_offset);
            params += " -DOUT_OFF=" + std::to_string(out_offset);
            params += " -DH=" + std::to_string(h_in);
            params += " -DW=" + std::to_string(w_in);
            params += " -DN=" + std::to_string(n);
            params += " -DC=" + std::to_string(c);
            params += " -DRD_BLCK=" + std::to_string(RD_BLCK);
            params += " -DHW_RD=" + std::to_string(HW_RD);
            params += " -DMAP_RD=" + std::to_string(MAP_RD);
            params += " -DREAD_TYPE=" + READ_TYPE;

            const std::vector<size_t> vld{lcl_size0, 1, 1};
            std::vector<size_t> vgd{MAP_RD, 1, 1};

            if(MAP_RD < MAX_ACTIVE_THREADS)
            {
                vgd = {MAP_RD, static_cast<size_t>(n), 1};
                params += " -DIS_2D_WG=1";
            }
            else
            {
                params += " -DIS_2D_WG=0";
            }

            handle.AddKernel(
                kernel_name, network_config, program_name, kernel_name, vld, vgd, params)(in, out);
        }
        else
        {
            params +=
                " -DNC_TRANS_NCHW_OPT=0 -DNC_TRANS_CNHW_OPT=0 -DNC_TRANS_NCHW=1 -DNC_TRANS_CNHW=0";

            params += " -DN=" + std::to_string(n);
            params += " -DC=" + std::to_string(c);
            params += " -DHW_IN=" + std::to_string(h_in * w_in);
            params += " -DHW_OUT=" + std::to_string(h_out * w_out);
            params += " -DW_IN=" + std::to_string(w_in);
            params += " -DW_OUT=" + std::to_string(w_out);
            params += " -DH_STRIDE=" + std::to_string(h_stride);
            params += " -DW_STRIDE=" + std::to_string(w_stride);
            params += " -DIN_OFF=" + std::to_string(in_offset);
            params += " -DOUT_OFF=" + std::to_string(out_offset);

            size_t ld0 = WG_SIZE;
            size_t gd0 = c * h_out * w_out;
            const std::vector<size_t> vld{ld0, 1, 1};
            std::vector<size_t> vgd{gd0, 1, 1};

            if(gd0 < MAX_ACTIVE_THREADS)
            {
                vgd = {gd0, static_cast<size_t>(n), 1};
                params += " -DIS_2D_WG=1";
            }
            else
            {
                params += " -DIS_2D_WG=0";
            }

            handle.AddKernel(
                kernel_name, network_config, program_name, kernel_name, vld, vgd, params)(in, out);
        }
    }

    return handle.GetKernelTime();
}

float transpose_CNHW2NCHW(Handle& handle,
                          int n,
                          int c,
                          int h_out,
                          int w_out,
                          int h_in,
                          int w_in,
                          ConstData_t in,
                          Data_t out,
                          int in_offset,
                          int out_offset,
                          int h_stride,
                          int w_stride,
                          miopenDataType_t type)
{

    std::string program_name = "MIOpenUtilKernels4.cl";

    std::string network_config = "n" + std::to_string(n) + "c" + std::to_string(c) + "h" +
                                 std::to_string(h_in) + "w" + std::to_string(w_in) + "inoff" +
                                 std::to_string(in_offset) + "otoff" + std::to_string(out_offset) +
                                 "h_stride" + std::to_string(h_stride) + "w_stride" +
                                 std::to_string(w_stride) + "t" + std::to_string(type);

    std::string kernel_name = "transpose_CNHW2NCHW";

    if(h_stride == 1 && w_stride == 1 && type == miopenFloat)
        kernel_name += "_opt";

    auto&& kernels = handle.GetKernels(kernel_name, network_config);

    if(!kernels.empty())
    {
        auto kernel = kernels.front();
        kernel(in, out);
    }
    else
    {
        std::string params = GetDataTypeKernelParams(type);

        if(type == miopenInt8x4)
        {
            c /= 4;
            in_offset /= 4;
            out_offset /= 4;
        }

        if(h_stride == 1 && w_stride == 1 && type == miopenFloat)
        {
            params +=
                " -DNC_TRANS_NCHW_OPT=0 -DNC_TRANS_CNHW_OPT=1 -DNC_TRANS_NCHW=0 -DNC_TRANS_CNHW=0";

            int RD_BLCK      = ((h_out * w_out) % 4 == 0) ? 4 : ((h_out * w_out) % 2 == 0) ? 2 : 1;
            int HW_RD        = (h_out * w_out) / RD_BLCK;
            size_t MAP_RD    = HW_RD * c;
            size_t lcl_size0 = WG_SIZE; //((MAP_RD + 63)/64 < 4) ? ((MAP_RD + 63)/64)*64 : 256;

            std::string READ_TYPE = (RD_BLCK == 1) ? "float" : "float" + std::to_string(RD_BLCK);

            params += " -DIN_OFF=" + std::to_string(in_offset);
            params += " -DOUT_OFF=" + std::to_string(out_offset);
            params += " -DH=" + std::to_string(h_out);
            params += " -DW=" + std::to_string(w_out);
            params += " -DN=" + std::to_string(n);
            params += " -DC=" + std::to_string(c);
            params += " -DRD_BLCK=" + std::to_string(RD_BLCK);
            params += " -DHW_RD=" + std::to_string(HW_RD);
            params += " -DMAP_RD=" + std::to_string(MAP_RD);
            params += " -DREAD_TYPE=" + READ_TYPE;

            const std::vector<size_t> vld{lcl_size0, 1, 1};
            std::vector<size_t> vgd{MAP_RD, 1, 1};

            if(MAP_RD < MAX_ACTIVE_THREADS)
            {
                vgd = {MAP_RD, static_cast<size_t>(n), 1};
                params += " -DIS_2D_WG=1";
            }
            else
            {
                params += " -DIS_2D_WG=0";
            }

            handle.AddKernel(
                kernel_name, network_config, program_name, kernel_name, vld, vgd, params)(in, out);
        }
        else
        {
            params +=
                " -DNC_TRANS_NCHW_OPT=0 -DNC_TRANS_CNHW_OPT=0 -DNC_TRANS_NCHW=0 -DNC_TRANS_CNHW=1";

            params += " -DN=" + std::to_string(n);
            params += " -DC=" + std::to_string(c);
            params += " -DHW_IN=" + std::to_string(h_in * w_in);
            params += " -DHW_OUT=" + std::to_string(h_out * w_out);
            params += " -DW_IN=" + std::to_string(w_in);
            params += " -DW_OUT=" + std::to_string(w_out);
            params += " -DH_STRIDE=" + std::to_string(h_stride);
            params += " -DW_STRIDE=" + std::to_string(w_stride);
            params += " -DIN_OFF=" + std::to_string(in_offset);
            params += " -DOUT_OFF=" + std::to_string(out_offset);

            size_t ld0 = WG_SIZE;
            size_t gd0 = c * h_out * w_out;
            const std::vector<size_t> vld{ld0, 1, 1};
            std::vector<size_t> vgd{gd0, 1, 1};

            if(gd0 < MAX_ACTIVE_THREADS)
            {
                vgd = {gd0, static_cast<size_t>(n), 1};
                params += " -DIS_2D_WG=1";
            }
            else
            {
                params += " -DIS_2D_WG=0";
            }

            handle.AddKernel(
                kernel_name, network_config, program_name, kernel_name, vld, vgd, params)(in, out);
        }
    }

    return handle.GetKernelTime();
}

// NCHW (or NCDHW) to NCHW_C4 (or NCDHW_C4)
float transpose_NCHW2Vec(Handle& handle,
                         const std::vector<std::size_t>& lens,
                         ConstData_t in,
                         Data_t out,
                         std::size_t vec_size,
                         bool trans,
                         bool forward)
{
    std::string program_name = "MIOpenUtilKernels5.cl";

    if(!(vec_size == 2 || vec_size == 4))
    {
        MIOPEN_THROW("Only support type half and int8!");
    }

    const auto n = lens[0];
    const auto c = lens[1];

    // "hw" is for any-D spatial data
    const auto hw = std::accumulate(
        lens.begin() + 2, lens.end(), std::size_t(1), std::multiplies<std::size_t>());

    // clang-format off
    std::string network_config =
        "n" + std::to_string(n) +
        "c" + std::to_string(c) +
        "hw" + std::to_string(hw) +
        "t" + std::to_string(static_cast<int>(trans)) +
        "v" + std::to_string(vec_size) +
        "f" + std::to_string(static_cast<int>(forward));
    // clang-format on

    std::string algo_name = "transpose_NCHWVecForward";

    auto&& kernels = handle.GetKernels(algo_name, network_config);

    if(!kernels.empty())
    {
        auto kernel = kernels.front();
        kernel(in, out);
    }
    else
    {
        auto n_vec = (trans && (n % vec_size != 0)) ? (n + (vec_size - n % vec_size)) : n;
        auto c_vec = (!trans && (c % vec_size != 0)) ? (c + (vec_size - c % vec_size)) : c;

        std::string kernel_name = "transpose_NCHW2Vec";

        const std::vector<size_t> vld{WG_SIZE, 1, 1};
        std::vector<size_t> vgd{1, 1, 1};

        int RD_BLCK = ((hw) % (vec_size * 2) == 0) ? static_cast<int>(vec_size) * 2
                                                   : static_cast<int>(vec_size);
        int HW_RD     = (static_cast<int>(hw) + RD_BLCK - 1) / RD_BLCK;
        size_t MAP_RD = HW_RD * (trans ? c : (c_vec / vec_size));

        std::string READ_TYPE =
            (RD_BLCK == vec_size) ? "uint" : "uint" + std::to_string(RD_BLCK / vec_size);
        int WR_BLCK            = RD_BLCK * static_cast<int>(vec_size);
        std::string WRITE_TYPE = "uint" + std::to_string(WR_BLCK / vec_size);

        std::string params;
        params += " -DFORWARD=" + std::to_string(static_cast<int>(forward));
        params += " -DN=" + std::to_string(n);
        params += " -DC=" + std::to_string(c);
        params += " -DHW=" + std::to_string(hw);
        params += " -DCHW=" + std::to_string(c * hw);
        params += " -DVEC_SIZE=" + std::to_string(vec_size);
        params += vec_size == 4 ? " -DDATA_TYPE=char" : " -DDATA_TYPE=ushort";

        params += " -DTRANS=" + std::to_string(static_cast<int>(trans));
        if(trans)
        {
            params += " -DNHW_OUT=" + std::to_string(n_vec * hw);
            params += " -DN_OUT=" + std::to_string(n_vec);
            params += " -DIS_N_ODD=" + std::to_string(static_cast<int>((n % vec_size) != 0));
        }
        else
        {
            params += " -DCHW_OUT=" + std::to_string(c_vec * hw);
            params += " -DIS_C_ODD=" + std::to_string(static_cast<int>((c % vec_size) != 0));
        }

        params += " -DIS_HW_ODD=" + std::to_string(static_cast<int>(((hw) % vec_size) != 0));
        params += " -DRD_BLCK=" + std::to_string(RD_BLCK);
        params += " -DWR_BLCK=" + std::to_string(WR_BLCK);
        params += " -DHW_RD=" + std::to_string(HW_RD);
        params += " -DMAP_RD=" + std::to_string(MAP_RD);
        params += " -DREAD_TYPE=" + READ_TYPE;
        params += " -DWRITE_TYPE=" + WRITE_TYPE;

        vgd[0] = MAP_RD;

        size_t gd1 = trans ? static_cast<size_t>(n_vec / vec_size) : static_cast<size_t>(n);

        /// disable iteration of n due to perf degrade
        /// \to-do fix the perf issue
        // if(vgd[0] < MAX_ACTIVE_THREADS)
        {
            vgd[1] = gd1;
            params += " -DIS_2D_WG=1";
        }
        // else
        //{
        // params += " -DIS_2D_WG=0";
        // params += " -DGD_1=" + std::to_string(gd1);
        //}

        handle.AddKernel(algo_name, network_config, program_name, kernel_name, vld, vgd, params)(
            in, out);
    }

    return handle.GetKernelTime();
}

float transpose_packed_MN2NM(Handle& handle,
                             int m,
                             int n,
                             int in_offset,
                             int out_offset,
                             ConstData_t in,
                             Data_t out,
                             miopenDataType_t type)
{

    std::string program_name = "MIOpenUtilKernels4.cl";

    std::string network_config = "n" + std::to_string(n) + "m" + std::to_string(m) + "inoff" +
                                 std::to_string(in_offset) + "otoff" + std::to_string(out_offset) +
                                 "t" + std::to_string(type);

    std::string kernel_name = "transpose_packed_MN2NM";

    auto&& kernels = handle.GetKernels(kernel_name, network_config);

    if(!kernels.empty())
    {
        auto kernel = kernels.front();
        kernel(in, out);
    }
    else
    {
        std::string params = GetDataTypeKernelParams(type);
        if(type == miopenInt8x4)
        {
            m /= 4;
            in_offset /= 4;
            out_offset /= 4;
        }

        if(!(type == miopenInt8x4 || type == miopenInt8))
        {
            MIOPEN_THROW("transpose_packed_MN2NM only meant for int8 variants.");
        }

        params += " -DNC_TRANS_MN2NM=1";

        params += " -DN=" + std::to_string(n);
        params += " -DM=" + std::to_string(m);
        params += " -DIN_OFF=" + std::to_string(in_offset);
        params += " -DOUT_OFF=" + std::to_string(out_offset);

        size_t ld0 = WG_SIZE;
        size_t gd0 = m * n;
        const std::vector<size_t> vld{ld0, 1, 1};
        std::vector<size_t> vgd{gd0, 1, 1};

        handle.AddKernel(kernel_name, network_config, program_name, kernel_name, vld, vgd, params)(
            in, out);
    }

    return handle.GetKernelTime();
}
} // namespace miopen


#pragma once
#ifndef ALGORITHM_CALC_FIXPOINT_HPP
#define ALGORITHM_CALC_FIXPOINT_HPP

#include "devavxprng.h"
#include "dSFMTAVX2search.hpp"
//#include "Annihilate.hpp"
#include <errno.h>
#include <stdlib.h>
#include <getopt.h>
#include <errno.h>
#include <NTL/GF2X.h>
#include <NTL/GF2XFactoring.h>
#include <MTToolBox/period.hpp>
#include <MTToolBox/AlgorithmReducibleRecursionSearch.hpp>

namespace MTToolBox {
    template<typename G, typename U>
    U calc_fixpoint(const G& dsfmt, const NTL::GF2X& irreducible,
                         const NTL::GF2X& quotient)
    {
        using namespace std;
        GF2X a, b, d;
        G dsfmt_const(dsfmt);
        /* a*irreducible + b*quotient = d */
        XGCD(d, a, b, irreducible, quotient);
        if (deg(d) != 0) {
            cout << "failure d != 1" << endl;
            throw new logic_error("failure d != 1");
        }
        b *= quotient;
        a *= irreducible;
        dsfmt_const.setConst();
        //Annihilate<G> annihilate;
        annihilate<U>(&dsfmt_const, b);

        GF2X t1(1, 1);
        SetCoeff(t1, 0);
        /* a*irreducible + b*t1 = d */
        XGCD(d, a, b, irreducible, t1);
        if (deg(d) != 0) {
            cout << "failure d != 1" << endl;
            cout << "deg(d) = " << dec << deg(d) << endl;
            cout << "deg(irreducible) = " << dec << deg(irreducible) << endl;
            cout << "deg(t1) = " << dec << deg(t1) << endl;
            throw new logic_error("failure d != 1");
        }
        annihilate<U>(&dsfmt_const, b);
        return dsfmt_const.getParityValue();
    }
}
#endif // ALGORITHM_CALC_FIXPOINT_HPP


// Copyright (c) 2017-2018 Steven Watanabe
//
// Distributed under the Boost Software License Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_UNITS2_ABSOLUTE_HPP_INCLUDED
#define BOOST_UNITS2_ABSOlUTE_HPP_INCLUDED

#include <boost/units2/unit.hpp>
#include <type_traits>

namespace boost {
namespace units2 {

// celsius - celsius = kelvin
// celsius + kelvin = celsius
// fahrenheit
template<class Unit, class Offset=std::ratio<0>>
struct absolute_unit {
    absolute_unit() = default;
    constexpr absolute_unit(Unit, Offset) {}
    /// INTERNAL ONLY
    template<class F, class T>
    using _boost_units2_apply = typename F::template apply_absolute<Unit, Offset>;
};

template<class Unit, class Offset, class Offset2>
operator+(absolute_unit<U, Offset>, Offset2) -> absolute_unit<U, Offset + Offset2>;

template<class Unit, class Offset, class Scale, class=requires_scale<Scale>>
auto operator*(absolute_unit<U, Offset>, Scale) -> absolute_unit<scaled_unit<absolute_unit<U, Offset>, Factor> >
{ return {}; }
template<class Unit, std::intmax_t N1, std::intmax_t D1, std::intmax_t N2, std::intmax_t D2>
auto operator*(absolute_unit<Unit, std::ratio<N1,D1>>, std::ratio<N2,D2>) -> absolute_unit<decltype(Unit{} * std::ratio<N2, D2>), std::ratio_divides<std::ratio<N1,D1>, std::ratio<N2,D2>>>
{ return {}; }

namespace detail {

struct requires_absolute_impl
{
    template<class Unit, class Offset>
    using apply_absolute = void;
};
template<class T>
using requires_absolute = visit<requires_absolute_impl, T>;

struct unitdiff_visitor;

template<class Unit>
using unitdiff_visit = visit<unitdiff_visitor, T>;

template<class T>
using unitdiff_t = detected_or<T, unitdiff_visit, T>;

struct unitdiff_visitor
{
    template<class Unit, class Scale>
    using apply_scaled = scaled_unit<unitdiff_visit<Unit>, Scale>;
    template<class Unit, class Offset>
    using apply_absolute = unitdiff_t<Unit>;
};

}

template<class Unit, class Offset>
constexpr auto operator-(absolute_unit<Unit, Offset>, abosolute_unit<Unit, Offset>) -> detail::unitdiff_t<Unit>
{ return {}; }

template<class Unit, class Offset>
constexpr auto operator+(absolute_unit<Unit, Offset>, detail::unitdiff_t<Unit>) -> absolute_unit<Unit, Offset>
{ return {}; }

template<class Unit, class Offset>
constexpr auto operator+(detail::unitdiff_t<Unit>, absolute_unit<Unit, Offset>) -> absolute_unit<Unit, Offset>
{ return {}; }

template<class Unit, class Offset>
constexpr auto operator-(absolute_unit<Unit, Offset>, detail::unitdiff_t<Unit>) -> absolute_unit<Unit, Offset>
{ return {}; }

template<class Unit, class Offset>
constexpr auto operator+(absolute_unit<Unit, Offset>) -> absolute_unit<Unit, Offset>
{ return {}; }

namespace detail {

struct offset_base_case
{
    template<class Unit>
    using apply_base = void;
    template<class... T>
    using apply_composite = void;
};

template<class Unit, class=visit<offset_base_case, Unit>>
constexpr auto base_offset(Unit) -> double { return 0; }
template<class Unit, class Offset>
constexpr auto base_offset(absolute_unit<Unit, Offset>) -> double
{ return detail::base_offset(Unit{}) + detail::get_value(Offset{}); }
template<class Unit, class Scale>
constexpr auto base_offset(scaled_unit<Unit, Scale>) -> double
{ return detail::base_offset(Unit{}) * detail::get_value(Scale{}); }

}

template<class From, class To>
auto conversion_offset(From, To)
{
    if constexpr(is_absolute)
}

template<class From, class To, class T, class=requires_absolute<From>, class=requires_absolute<To>>
constexpr auto convert(From, To, T&& value)
{
    return (value - detail::base_offset(From)) * conversion_factor(detail::unitdiff_t<From>{}, detail::unitdiff_t<To>{}) + detail::base_offset(To{});
}

}
}


#include <jni.h>
#include <android/log.h>

#include <cpu-features.h>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include <Eigen/Core>

using namespace std;

#define  LOG_TAG	"MatrixOp"
#define  LOGI(...)	__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define  LOGE(...)	__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

template <typename T1, typename T2> void c_mulVector(T1 matrix[], T2 vector[], int rows, int cols, T1 result[]);
//void arm_neon_mulVector3F(float matrix[], float vector[], float result[]);
template <typename T1, typename T2> void c_mulMatrix(T1 matrix1[], T2 matrix2[], int rows1, int cols1, int cols2, T1 result[]);
//void arm_neon_mulMatrix3F(float matrix1[], float matrix2[], float result[]);

//bool neon = (android_getCpuFamily() == ANDROID_CPU_FAMILY_ARM) && ((android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON) != 0);

extern "C"
{
	JNIEXPORT jfloatArray JNICALL Java_it_unibo_slam_datatypes_eigen_typefloat_EigenMatrixF_matrixMulVectorF(JNIEnv *env, jobject obj,
			jfloatArray m, jfloatArray v, jint numRowsMatrix, jint numColsMatrix);
	JNIEXPORT jfloatArray JNICALL Java_it_unibo_slam_datatypes_eigen_typefloat_EigenMatrixF_matrixMulMatrixF(JNIEnv *env, jobject obj,
			jfloatArray m1, jfloatArray m2, jint numRowsMatrix1, jint numColsMatrix1, jint numColsMatrix2);
	JNIEXPORT jfloatArray JNICALL Java_it_unibo_slam_datatypes_eigen_typefloat_EigenMatrixF_matrixMulVectorFD(JNIEnv *env, jobject obj,
			jfloatArray m, jdoubleArray v, jint numRowsMatrix, jint numColsMatrix);
	JNIEXPORT jfloatArray JNICALL Java_it_unibo_slam_datatypes_eigen_typefloat_EigenMatrixF_matrixMulMatrixFD(JNIEnv *env, jobject obj,
			jfloatArray m1, jdoubleArray m2, jint numRowsMatrix1, jint numColsMatrix1, jint numColsMatrix2);
	JNIEXPORT jdoubleArray JNICALL Java_it_unibo_slam_datatypes_eigen_typedouble_EigenMatrixD_matrixMulVectorD(JNIEnv *env, jobject obj,
			jdoubleArray m, jdoubleArray v, jint numRowsMatrix, jint numColsMatrix);
	JNIEXPORT jdoubleArray JNICALL Java_it_unibo_slam_datatypes_eigen_typedouble_EigenMatrixD_matrixMulMatrixD(JNIEnv *env, jobject obj,
			jdoubleArray m1, jdoubleArray m2, jint numRowsMatrix1, jint numColsMatrix1, jint numColsMatrix2);
	JNIEXPORT jdoubleArray JNICALL Java_it_unibo_slam_datatypes_eigen_typedouble_EigenMatrixD_matrixMulVectorDF(JNIEnv *env, jobject obj,
			jdoubleArray m, jfloatArray v, jint numRowsMatrix, jint numColsMatrix);
	JNIEXPORT jdoubleArray JNICALL Java_it_unibo_slam_datatypes_eigen_typedouble_EigenMatrixD_matrixMulMatrixDF(JNIEnv *env, jobject obj,
			jdoubleArray m1, jfloatArray m2, jint numRowsMatrix1, jint numColsMatrix1, jint numColsMatrix2);
}

JNIEXPORT jfloatArray JNICALL Java_it_unibo_slam_datatypes_eigen_typefloat_EigenMatrixF_matrixMulVectorF(JNIEnv *env, jobject obj,
		jfloatArray m, jfloatArray v, jint numRowsMatrix, jint numColsMatrix)
{
	jfloatArray result;
	int rows = numRowsMatrix;
	int cols = numColsMatrix;
	int sizeMatrix = rows * cols;
	result = env->NewFloatArray(rows);

	float *tempResult = new float[rows];
	float *tempM = new float[sizeMatrix];
	float *tempV = new float[cols];

	env->GetFloatArrayRegion(m, 0, sizeMatrix, tempM);
	env->GetFloatArrayRegion(v, 0, cols, tempV);

	c_mulVector<float, float>(tempM, tempV, rows, cols, tempResult);

	env->SetFloatArrayRegion(result, 0, rows, tempResult);

	delete [] tempResult;
	delete [] tempM;
	delete [] tempV;

	return result;
}

JNIEXPORT jfloatArray JNICALL Java_it_unibo_slam_datatypes_eigen_typefloat_EigenMatrixF_matrixMulMatrixF(JNIEnv *env, jobject obj,
		jfloatArray m1, jfloatArray m2, jint numRowsMatrix1, jint numColsMatrix1, jint numColsMatrix2)
{
	jfloatArray result;
	int rows1 = numRowsMatrix1;
	int cols1 = numColsMatrix1;
	int cols2 = numColsMatrix2;
	int sizeMatrix1 = rows1 * cols1;
	int sizeMatrix2 = cols1 * cols2;
	int sizeResult = rows1 * cols2;
	result = env->NewFloatArray(sizeResult);

	float *tempResult = new float[sizeResult];
	float *tempM1 = new float[sizeMatrix1];
	float *tempM2 = new float[sizeMatrix2];

	env->GetFloatArrayRegion(m1, 0, sizeMatrix1, tempM1);
	env->GetFloatArrayRegion(m2, 0, sizeMatrix2, tempM2);

	c_mulMatrix<float, float>(tempM1, tempM2, rows1, cols1, cols2, tempResult);

	env->SetFloatArrayRegion(result, 0, sizeResult, tempResult);

	delete [] tempResult;
	delete [] tempM1;
	delete [] tempM2;

	return result;
}

JNIEXPORT jfloatArray JNICALL Java_it_unibo_slam_datatypes_eigen_typefloat_EigenMatrixF_matrixMulVectorFD(JNIEnv *env, jobject obj,
		jfloatArray m, jdoubleArray v, jint numRowsMatrix, jint numColsMatrix)
{
	jfloatArray result;
	int rows = numRowsMatrix;
	int cols = numColsMatrix;
	int sizeMatrix = rows * cols;
	result = env->NewFloatArray(rows);

	float *tempResult = new float[rows];
	float *tempM = new float[sizeMatrix];
	double *tempV = new double[cols];

	env->GetFloatArrayRegion(m, 0, sizeMatrix, tempM);
	env->GetDoubleArrayRegion(v, 0, cols, tempV);

	c_mulVector<float, double>(tempM, tempV, rows, cols, tempResult);

	env->SetFloatArrayRegion(result, 0, rows, tempResult);

	delete [] tempResult;
	delete [] tempM;
	delete [] tempV;

	return result;
}

JNIEXPORT jfloatArray JNICALL Java_it_unibo_slam_datatypes_eigen_typefloat_EigenMatrixF_matrixMulMatrixFD(JNIEnv *env, jobject obj,
		jfloatArray m1, jdoubleArray m2, jint numRowsMatrix1, jint numColsMatrix1, jint numColsMatrix2)
{
	jfloatArray result;
	int rows1 = numRowsMatrix1;
	int cols1 = numColsMatrix1;
	int cols2 = numColsMatrix2;
	int sizeMatrix1 = rows1 * cols1;
	int sizeMatrix2 = cols1 * cols2;
	int sizeResult = rows1 * cols2;
	result = env->NewFloatArray(sizeResult);

	float *tempResult = new float[sizeResult];
	float *tempM1 = new float[sizeMatrix1];
	double *tempM2 = new double[sizeMatrix2];

	env->GetFloatArrayRegion(m1, 0, sizeMatrix1, tempM1);
	env->GetDoubleArrayRegion(m2, 0, sizeMatrix2, tempM2);

	c_mulMatrix<float, double>(tempM1, tempM2, rows1, cols1, cols2, tempResult);

	env->SetFloatArrayRegion(result, 0, sizeResult, tempResult);

	delete [] tempResult;
	delete [] tempM1;
	delete [] tempM2;

	return result;
}

JNIEXPORT jdoubleArray JNICALL Java_it_unibo_slam_datatypes_eigen_typedouble_EigenMatrixD_matrixMulVectorD(JNIEnv *env, jobject obj,
		jdoubleArray m, jdoubleArray v, jint numRowsMatrix, jint numColsMatrix)
{
	jdoubleArray result;
	int rows = numRowsMatrix;
	int cols = numColsMatrix;
	int sizeMatrix = rows * cols;
	result = env->NewDoubleArray(rows);

	double *tempResult = new double[rows];
	double *tempM = new double[sizeMatrix];
	double *tempV = new double[cols];

	env->GetDoubleArrayRegion(m, 0, sizeMatrix, tempM);
	env->GetDoubleArrayRegion(v, 0, cols, tempV);

	c_mulVector<double, double>(tempM, tempV, rows, cols, tempResult);

	env->SetDoubleArrayRegion(result, 0, rows, tempResult);

	delete [] tempResult;
	delete [] tempM;
	delete [] tempV;

	return result;
}

JNIEXPORT jdoubleArray JNICALL Java_it_unibo_slam_datatypes_eigen_typedouble_EigenMatrixD_matrixMulMatrixD(JNIEnv *env, jobject obj,
		jdoubleArray m1, jdoubleArray m2, jint numRowsMatrix1, jint numColsMatrix1, jint numColsMatrix2)
{
	jdoubleArray result;
	int rows1 = numRowsMatrix1;
	int cols1 = numColsMatrix1;
	int cols2 = numColsMatrix2;
	int sizeMatrix1 = rows1 * cols1;
	int sizeMatrix2 = cols1 * cols2;
	int sizeResult = rows1 * cols2;
	result = env->NewDoubleArray(sizeResult);

	double *tempResult = new double[sizeResult];
	double *tempM1 = new double[sizeMatrix1];
	double *tempM2 = new double[sizeMatrix2];

	env->GetDoubleArrayRegion(m1, 0, sizeMatrix1, tempM1);
	env->GetDoubleArrayRegion(m2, 0, sizeMatrix2, tempM2);

	c_mulMatrix<double, double>(tempM1, tempM2, rows1, cols1, cols2, tempResult);

	env->SetDoubleArrayRegion(result, 0, sizeResult, tempResult);

	delete [] tempResult;
	delete [] tempM1;
	delete [] tempM2;

	return result;
}

JNIEXPORT jdoubleArray JNICALL Java_it_unibo_slam_datatypes_eigen_typedouble_EigenMatrixD_matrixMulVectorDF(JNIEnv *env, jobject obj,
		jdoubleArray m, jfloatArray v, jint numRowsMatrix, jint numColsMatrix)
{
	jdoubleArray result;
	int rows = numRowsMatrix;
	int cols = numColsMatrix;
	int sizeMatrix = rows * cols;
	result = env->NewDoubleArray(rows);

	double *tempResult = new double[rows];
	double *tempM = new double[sizeMatrix];
	float *tempV = new float[cols];

	env->GetDoubleArrayRegion(m, 0, sizeMatrix, tempM);
	env->GetFloatArrayRegion(v, 0, cols, tempV);

	c_mulVector<double, float>(tempM, tempV, rows, cols, tempResult);

	env->SetDoubleArrayRegion(result, 0, rows, tempResult);

	delete [] tempResult;
	delete [] tempM;
	delete [] tempV;

	return result;
}

JNIEXPORT jdoubleArray JNICALL Java_it_unibo_slam_datatypes_eigen_typedouble_EigenMatrixD_matrixMulMatrixDF(JNIEnv *env, jobject obj,
		jdoubleArray m1, jfloatArray m2, jint numRowsMatrix1, jint numColsMatrix1, jint numColsMatrix2)
{
	jdoubleArray result;
	int rows1 = numRowsMatrix1;
	int cols1 = numColsMatrix1;
	int cols2 = numColsMatrix2;
	int sizeMatrix1 = rows1 * cols1;
	int sizeMatrix2 = cols1 * cols2;
	int sizeResult = rows1 * cols2;
	result = env->NewDoubleArray(sizeResult);

	double *tempResult = new double[sizeResult];
	double *tempM1 = new double[sizeMatrix1];
	float *tempM2 = new float[sizeMatrix2];

	env->GetDoubleArrayRegion(m1, 0, sizeMatrix1, tempM1);
	env->GetFloatArrayRegion(m2, 0, sizeMatrix2, tempM2);

	c_mulMatrix<double, float>(tempM1, tempM2, rows1, cols1, cols2, tempResult);

	env->SetDoubleArrayRegion(result, 0, sizeResult, tempResult);

	delete [] tempResult;
	delete [] tempM1;
	delete [] tempM2;

	return result;
}

template <typename T1, typename T2>
void c_mulMatrix(T1 matrix1[], T2 matrix2[], int rows1, int cols1, int cols2, T1 result[])
{
	T1 tempVal;

	for (int x = 0, y = 0; x < cols2; x++, y += cols1)
	{
		for (int i = 0; i < rows1; i++, result++)
		{
			tempVal = 0;
			for (int j = 0, k = 0; j < cols1; j++, k += rows1)
				tempVal += matrix1[i + k] * matrix2[j + y];
			*result = tempVal;
		}
	}
}

template <typename T1, typename T2>
void c_mulVector(T1 matrix[], T2 vector[], int rows, int cols, T1 result[])
{
	T1 tempVal;

	for (int i = 0; i < rows; i++)
	{
		tempVal = 0;
		for (int j = 0, k = 0; j < cols; j++, k += rows)
			tempVal += matrix[i + k] * (T1)vector[j];
		result[i] = tempVal;
	}
}

/*void arm_neon_mulVector3F(float matrix[], float vector[], float result[])
{
	int temp;
	asm volatile
	(
		"mov                    %3, #12						\n\t"   //r3 = 12
		"vld1.32                {d0, d1}, [%1]             	\n\t"   //Q0 = v
		"vld1.32                {d2, d3}, [%0], %3     		\n\t"   //Q1 = m
		"vld1.32                {d4, d5}, [%0], %3        	\n\t"   //Q2 = m+12
		"vld1.32                {d6, d7}, [%0], %3         	\n\t"   //Q3 = m+24

		"vmul.f32               q9, q1, d0[0]             	\n\t"   //Q9 = Q1*Q0[0]
		"vmla.f32               q9, q2, d0[1]              	\n\t"   //Q9 += Q2*Q0[1]
		"vmla.f32               q9, q3, d1[0]              	\n\t"   //Q9 += Q3*Q0[2]
		"vmov.f32               q0, q9           			\n\t"   //Q0 = q9

		"vst1.32                d0, [%2]!                	\n\t"   //r2 = D24
		"fsts                   s2, [%2]                 	\n\t"   //r2 = D25[0]

		:
		: "r" (matrix), "r" (vector), "r" (result), "r" (temp)
		: "q0", "q9", "q10", "q11", "q12", "q13", "memory"
	);
}

void arm_neon_mulMatrix3F(float matrix1[], float matrix2[], float result[])
{
	asm volatile
	(
		"vld1.32                {d0, d1}, [%1]!			\n\t"   //q0 = m1
		"vld1.32                {d2, d3}, [%1]!			\n\t"   //q1 = m1+4
		"flds                   s8, [%1]				\n\t"   //q2 = m1+8

		"vld1.32                {d6, d7}, [%0]			\n\t"   //q3[0] = m0
		"add                    %0, %0, #12				\n\t"   //q3[0] = m0
		"vld1.32                {d8, d9}, [%0]			\n\t"   //q4[0] = m0+12
		"add                    %0, %0, #12				\n\t"   //q3[0] = m0
		"vld1.32                {d10}, [%0]				\n\t"   //q5[0] = m0+24
		"add                    %0, %0, #8				\n\t"   //q3[0] = m0
		"flds                   s22, [%0]				\n\t"   //q2 = m1+8

		"vmul.f32               q6, q3, d0[0]			\n\t"   //q12 = q3 * d0[0]
		"vmul.f32               q7, q3, d1[1]			\n\t"   //q13 = q3 * d2[0]
		"vmul.f32               q8, q3, d3[0]			\n\t"   //q14 = q3 * d4[0]
		"vmla.f32               q6, q4, d0[1] 			\n\t"   //q12 = q9 * d0[1]
		"vmla.f32               q7, q4, d2[0]    		\n\t"   //q13 = q9 * d2[1]
		"vmla.f32               q8, q4, d3[1] 			\n\t"   //q14 = q9 * d4[1]
		"vmla.f32               q6, q5, d1[0]			\n\t"   //q12 = q10 * d0[0]
		"vmla.f32               q7, q5, d2[1]			\n\t"   //q13 = q10 * d2[0]
		"vmla.f32               q8, q5, d4[0]			\n\t"   //q14 = q10 * d4[0]

		"vmov.f32               q0, q8					\n\t"   //q14 = q10 * d4[0]
		"vst1.32                {d12, d13}, [%2]		\n\t"   //d = q12
		"add                    %2, %2, #12				\n\t"   //q3[0] = m0
		"vst1.32                {d14, d15}, [%2]		\n\t"   //d+4 = q13
		"add                    %2, %2, #12				\n\t"   //q3[0] = m0
		"vst1.32                {d0}, [%2]				\n\t"   //d+8 = q14
		"add                    %2, %2, #8				\n\t"   //q3[0] = m0
		"fsts                   s2, [%2]				\n\t"   //d = q12

		:
		: "r"(matrix1), "r"(matrix2), "r"(result)
		: "d8", "d9", "d10", "d11", "d12", "d13", "d14", "d15", "memory"
	);
}*/


// This file is part of LatNet Builder.
//
// Copyright (C) 2012-2021  The LatNet Builder author's, supervised by Pierre L'Ecuyer, Universite de Montreal.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "netbuilder/NetConstructionTraits.h"
#include "netbuilder/Helpers/JoeKuo.h"

#include <sstream>
#include <boost/algorithm/string/erase.hpp>

namespace NetBuilder {

    const std::string NetConstructionTraits<NetConstruction::LMS>::name = "LMS";

    typedef typename NetConstructionTraits<NetConstruction::LMS>::GenValue GenValue;

    typedef std::vector<uInteger> DirectionNumber;
    typedef std::pair<unsigned int, unsigned int> PrimitivePolynomial;
    typedef std::vector<std::vector<bool>> BinaryMatrix;

    typedef NetConstructionTraits<NetConstruction::LMS>::SizeParameter SizeParameter;

    bool NetConstructionTraits<NetConstruction::LMS>::checkGenValue(const GenValue& genValue, const SizeParameter& sizeParameter)
    {
        return genValue.nRows() == nRows(sizeParameter) && genValue.nCols() == nCols(sizeParameter);
    }

    unsigned int NetConstructionTraits<NetConstruction::LMS>::nRows(const SizeParameter& sizeParameter) {return (unsigned int) sizeParameter.first.first; }

    unsigned int NetConstructionTraits<NetConstruction::LMS>::nCols(const SizeParameter& sizeParameter) {return (unsigned int) sizeParameter.first.second; }

    GeneratingMatrix*  NetConstructionTraits<NetConstruction::LMS>::createGeneratingMatrix(const GenValue& genValue, const SizeParameter& sizeParameter, const Dimension& dimension_j, const unsigned int nRows)
    {
        GeneratingMatrix* result = new GeneratingMatrix(genValue * *sizeParameter.second[dimension_j]);
        unsigned int finalnRows = (nRows == 0)? NetConstructionTraits<NetConstruction::LMS>::nRows(sizeParameter) : nRows;
        result->resize(finalnRows, nCols(sizeParameter));
        return result;
    }

    std::vector<GenValue> NetConstructionTraits<NetConstruction::LMS>::genValueSpaceCoord(Dimension coord, const SizeParameter& sizeParameter)
    {
        throw std::logic_error("The space of all matrices is far too big to be exhautively explored.");
        return std::vector<GenValue>{};
    }

    std::vector<std::vector<GenValue>> NetConstructionTraits<NetConstruction::LMS>::genValueSpace(Dimension dimension, const SizeParameter& sizeParameter)
    {
        throw std::logic_error("The space of all matrices is far too big to be exhautively explored.");
        return std::vector<std::vector<GenValue>>{};
    }

    std::string NetConstructionTraits<NetConstruction::LMS>::format(const std::vector<std::shared_ptr<GeneratingMatrix>>& genMatrices, const std::vector<std::shared_ptr<GenValue>>& genVals, const SizeParameter& sizeParameter, OutputStyle outputStyle, unsigned int interlacingFactor)
    {
        std::ostringstream stream;

        if (outputStyle == OutputStyle::TERMINAL){
            stream << "Left-Matrix-Scrambled Digital Net - Matrix size = " << sizeParameter.first.first << "x" << sizeParameter.first.second << std::endl;
            stream << "Printing only the scrambled generating matrices - see output.txt file for original matrices and scrambling matrices.\n";
            for (Dimension dim = 0; dim < genMatrices.size(); ++dim)
            {
                stream << "Coordinate " << dim << std::endl;
                stream << *genMatrices[dim] << std::endl;
            }
        }

        else if (outputStyle == OutputStyle::RANDOMIZED_NET){
            unsigned long dimension = genMatrices.size();
            unsigned int nb_col = sizeParameter.first.second;
            stream << "# Parameters for a digital net in base 2\n";
            stream << dimension / interlacingFactor << "    # s = " << dimension / interlacingFactor << " dimensions\n";
            if (interlacingFactor > 1){
                stream << interlacingFactor << "    # Interlacing factor" << "\n";
                stream << dimension << "    # Number of components = interlacing factor x dimension" << "\n";
            }
            stream << nb_col << "    # k = " << nb_col << ",  n = 2^" << nb_col << " = ";
            stream << (int)pow(2,nb_col) << " points"<< std::endl;
            stream << "31    # r = 31 binary output digits\n";

            if (interlacingFactor == 1){
                stream << "# Columns of original gen. matrices C_1,...,C_s, one matrix per line\n";
            }
            else {
                stream << "# Columns of original gen. matrices C_1,...,C_{ds}, one matrix per line\n";
            }
            for(unsigned int coord = 0; coord < dimension; coord++)
            {
                stream << sizeParameter.second[coord]->formatToColumnsReverse();
                stream << "\n";
            }

            if (interlacingFactor == 1){
                stream << "# Columns of scrambling matrices C_1,...,C_s, one matrix per line\n";
            }
            else {
                stream << "# Columns of scrambling matrices C_1,...,C_{ds}, one matrix per line\n";
            }
            for(unsigned int coord = 0; coord < dimension; coord++)
            {
                stream << genVals[coord]->formatToColumnsReverse();
                stream << "\n";
            }

            if (interlacingFactor == 1){
                stream << "# Columns of scrambled gen. matrices C_1,...,C_s, one matrix per line\n";
            }
            else {
                stream << "# Columns of scrambled gen. matrices C_1,...,C_{ds}, one matrix per line\n";
            }
            for(unsigned int coord = 0; coord < dimension; coord++)
            {
                stream << genMatrices[coord]->formatToColumnsReverse();
                if (coord < dimension - 1){
                    stream << "\n";
                }
            }
        }
        return stream.str();
    }
}


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2019 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2020 Alexander Sokolov <asokolov@nil.foundation>
//
// Distributed under the Boost Software License, Version 1.0
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_HASH_SPONGE_CONSTRUCTION_HPP
#define CRYPTO3_HASH_SPONGE_CONSTRUCTION_HPP

#include <boost/crypto3/detail/static_digest.hpp>
#include <boost/crypto3/detail/pack.hpp>

#include <boost/crypto3/hash/detail/nop_finalizer.hpp>

namespace boost {
    namespace crypto3 {
        namespace hashes {
            /*!
             * @brief
             * @tparam DigestEndian
             * @tparam DigestBits
             * @tparam IV
             * @tparam Compressor
             * @tparam Finalizer
             *
             * The Sponge construction builds a block hashes from a
             * one-way compressor.  As this version operated on the block
             * level, it doesn't contain any padding or other strengthening.
             * For a Wide Pipe construction, use a digest that will
             * truncate the internal state.
             */
            template<typename Params,
                     typename IV,
                     typename Compressor,
                     typename Padding,
                     typename Finalizer = detail::nop_finalizer>
            class sponge_construction {
            public:
                typedef IV iv_generator;
                typedef Compressor compressor_functor;
                typedef Padding padding_functor;
                typedef Finalizer finalizer_functor;

                typedef typename Params::digest_endian endian_type;

                constexpr static const std::size_t word_bits = compressor_functor::word_bits;
                typedef typename compressor_functor::word_type word_type;

                constexpr static const std::size_t state_bits = compressor_functor::state_bits;
                constexpr static const std::size_t state_words = compressor_functor::state_words;
                typedef typename compressor_functor::state_type state_type;

                constexpr static const std::size_t block_bits = compressor_functor::block_bits;
                constexpr static const std::size_t block_words = compressor_functor::block_words;
                typedef typename compressor_functor::block_type block_type;

                constexpr static const std::size_t digest_bits = Params::digest_bits;
                constexpr static const std::size_t digest_bytes = digest_bits / octet_bits;
                constexpr static const std::size_t digest_words = digest_bits / word_bits;
                typedef static_digest<digest_bits> digest_type;

                template<typename Integer = std::size_t>
                inline sponge_construction &process_block(const block_type &block, Integer seen = Integer()) {
                    compressor_functor::process_block(state_, block);
                    return *this;
                }

                inline digest_type digest(const block_type &block = block_type(),
                                          std::size_t total_seen = std::size_t()) {
                    using namespace boost::crypto3::detail;

                    block_type b = block;
                    std::size_t block_seen = total_seen % block_bits;
                    // Process block if it is full
                    if (total_seen && !block_seen)
                        process_block(b);

                    std::size_t copy_seen = block_seen;
                    // Pad last message block
                    padding_functor padding;
                    padding(b, block_seen);
                    process_block(b);

                    // Process additional block if not all bits were padded
                    if (!padding.is_last_block()) {
                        std::fill(b.begin(), b.end(), 0);
                        padding.process_last(b, copy_seen);
                        process_block(b);
                    }

                    // Apply finalizer
                    finalizer_functor()(state_);

                    // Convert digest to byte representation
                    std::array<octet_type, state_bits / octet_bits> d_full;
                    pack_from<endian_type, word_bits, octet_bits>(state_.begin(), state_.end(), d_full.begin());

                    digest_type d;
                    std::copy(d_full.begin(), d_full.begin() + digest_bytes, d.begin());

                    return d;
                }

                sponge_construction() {
                    reset();
                }

                void reset(state_type const &s) {
                    state_ = s;
                }

                void reset() {
                    iv_generator iv;
                    reset(iv());
                }

                state_type const &state() const {
                    return state_;
                }

            private:
                state_type state_;
            };

        }    // namespace hashes
    }        // namespace crypto3
}    // namespace boost

#endif    // CRYPTO3_HASH_SPONGE_CONSTRUCTION_HPP


#ifndef OFFPOLSETACFITTED_HPP
#define OFFPOLSETACFITTED_HPP

#include <vector>
#include <string>
#include <boost/serialization/list.hpp>
#include <boost/serialization/set.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/filesystem.hpp>
#include <tbb/parallel_for.h>
#include <tbb/blocked_range.h>

#include "arch/AACAgent.hpp"
#include "bib/Seed.hpp"
#include "bib/Utils.hpp"
#include <bib/MetropolisHasting.hpp>
#include <bib/XMLEngine.hpp>
#include "MLP.hpp"
#include "LinMLP.hpp"
#include "kde.hpp"
#include "kdtree++/kdtree.hpp"

#define DOUBLE_COMPARE_PRECISION 1e-9

typedef struct _sample {
  std::vector<double> s;
  std::vector<double> pure_a;
  std::vector<double> a;
  std::vector<double> next_s;
  double r;
  bool goal_reached;
  double p0;

  friend class boost::serialization::access;
  template <typename Archive>
  void serialize(Archive& ar, const unsigned int) {
    ar& BOOST_SERIALIZATION_NVP(s);
    ar& BOOST_SERIALIZATION_NVP(pure_a);
    ar& BOOST_SERIALIZATION_NVP(a);
    ar& BOOST_SERIALIZATION_NVP(next_s);
    ar& BOOST_SERIALIZATION_NVP(r);
    ar& BOOST_SERIALIZATION_NVP(goal_reached);
  }

  //Used to store all sample into a tree, might be stochastic
  //only pure_a is negligate
  bool operator< (const _sample& b) const {
    for (uint i = 0; i < s.size(); i++) {
      if(fabs(s[i] - b.s[i])>=DOUBLE_COMPARE_PRECISION)
        return s[i] < b.s[i];
    }

    for (uint i = 0; i < a.size(); i++) {
      if(fabs(a[i] - b.a[i])>=DOUBLE_COMPARE_PRECISION)
        return a[i] < b.a[i];
    }

    for (uint i = 0; i < next_s.size(); i++) {
      if(fabs(next_s[i] - b.next_s[i])>=DOUBLE_COMPARE_PRECISION)
        return next_s[i] < b.next_s[i];
    }

    if(fabs(r - b.r)>=DOUBLE_COMPARE_PRECISION)
        return r < b.r;

    return goal_reached < b.goal_reached;
  }

  typedef double value_type;

  inline double operator[](size_t const N) const{
        return s[N];
  }

  bool same_state(const _sample& b) const{
    for (uint i = 0; i < s.size(); i++) {
      if(fabs(s[i] - b.s[i])>=DOUBLE_COMPARE_PRECISION)
        return false;
    }

    return true;
  }

} sample;

class OffPolSetACFitted : public arch::AACAgent<MLP, arch::AgentProgOptions> {
 public:
  typedef MLP PolicyImpl;

  OffPolSetACFitted(unsigned int _nb_motors, unsigned int _nb_sensors)
    : arch::AACAgent<MLP, arch::AgentProgOptions>(_nb_motors), nb_sensors(_nb_sensors) {

  }

  virtual ~OffPolSetACFitted() {
    delete kdtree_s;

    delete vnn;
    delete ann;
  }

  const std::vector<double>& _run(double reward, const std::vector<double>& sensors,
                                 bool learning, bool goal_reached, bool) {

    vector<double>* next_action = ann->computeOut(sensors);

    if (last_action.get() != nullptr && learning){
      double p0 = 1.f;
      for(uint i=0;i < nb_motors;i++)
        p0 *= exp(-(last_pure_action->at(i)-last_action->at(i))*(last_pure_action->at(i)-last_action->at(i))/(2.f*noise*noise));

      trajectory.insert({last_state, *last_pure_action, *last_action, sensors, reward, goal_reached, p0});
      last_trajectory.insert( {last_state, *last_pure_action, *last_action, sensors, reward, goal_reached, p0});
      proba_s.add_data(last_state);

      sample sa = {last_state, *last_pure_action, *last_action, sensors, reward, goal_reached, p0};

      kdtree_s->insert(sa);

      std::vector<double> psa;
      std::vector<double> psas;
      mergeSA(psa, last_state, *last_action);
      mergeSAS(psas, last_state, *last_action, sensors);

      proba_sa.add_data(psa);
      proba_sas.add_data(psas);
    }

    last_pure_action.reset(new vector<double>(*next_action));
    if(learning) {
      if(gaussian_policy){
        vector<double>* randomized_action = bib::Proba<double>::multidimentionnalGaussianWReject(*next_action, noise);
        delete next_action;
        next_action = randomized_action;
      } else if(bib::Utils::rand01() < noise){ //e-greedy
        for (uint i = 0; i < next_action->size(); i++)
          next_action->at(i) = bib::Utils::randin(-1.f, 1.f);
      }
    }
    last_action.reset(next_action);

    last_state.clear();
    for (uint i = 0; i < sensors.size(); i++)
      last_state.push_back(sensors[i]);

    return *next_action;
  }

  void _unique_invoke(boost::property_tree::ptree* pt, boost::program_options::variables_map*) override {
    hidden_unit_v           = pt->get<int>("agent.hidden_unit_v");
    hidden_unit_a           = pt->get<int>("agent.hidden_unit_a");
    noise                   = pt->get<double>("agent.noise");
    gaussian_policy         = pt->get<bool>("agent.gaussian_policy");
    lecun_activation        = pt->get<bool>("agent.lecun_activation");
    determinist_vnn_update  = pt->get<bool>("agent.determinist_vnn_update");
    vnn_from_scratch        = pt->get<bool>("agent.vnn_from_scratch");
    change_policy_each      = pt->get<uint>("agent.change_policy_each");
    strategy_w              = pt->get<uint>("agent.strategy_w");
    current_loaded_policy   = pt->get<uint>("agent.index_starting_loaded_policy");
    converge_precision      = pt->get<double>("agent.converge_precision");
    number_fitted_iteration = pt->get<uint>("agent.number_fitted_iteration");

    if(hidden_unit_v == 0)
      vnn = new LinMLP(nb_sensors + nb_motors , 1, 0.0, lecun_activation);
    else
      vnn = new MLP(nb_sensors + nb_motors, hidden_unit_v, nb_sensors, 0.0, lecun_activation);

    if(hidden_unit_a == 0)
      ann = new LinMLP(nb_sensors , nb_motors, 0.0, lecun_activation);
    else
      ann = new MLP(nb_sensors, hidden_unit_a, nb_motors, lecun_activation);

    kdtree_s = new kdtree_sample(nb_sensors);
  }

  bool learning;
  void _start_episode(const std::vector<double>& sensors, bool _learning) override {
    learning = _learning;
    last_state.clear();
    for (uint i = 0; i < sensors.size(); i++)
      last_state.push_back(sensors[i]);

    last_action = nullptr;
    last_pure_action = nullptr;

    //trajectory.clear();
    last_trajectory.clear();

    fann_reset_MSE(vnn->getNeuralNet());

    if(episode == 0 || episode % change_policy_each == 0){
      if (! boost::filesystem::exists( "polset."+std::to_string(current_loaded_policy) ) ){
        LOG_ERROR("file doesn't exists "<< "polset."+std::to_string(current_loaded_policy));
        exit(1);
      }

      ann->load("polset."+std::to_string(current_loaded_policy));
      current_loaded_policy++;
      end_episode();
    }
  }

  void computePTheta(vector< sample >& vtraj, double *ptheta){
    uint i=0;
    for(auto it = vtraj.begin(); it != vtraj.end() ; ++it) {
      sample sm = *it;
      vector<double>* next_action = ann->computeOut(sm.s);

      double p0 = 1.f;
      for(uint i=0;i < nb_motors;i++)
        p0 *= exp(-(next_action->at(i)-sm.a[i])*(next_action->at(i)-sm.a[i])/(2.f*noise*noise));

      ptheta[i] = p0;
      i++;
      delete next_action;
    }
  }

  struct ParraVtoVNext {
    ParraVtoVNext(const std::vector<sample>& _vtraj, const OffPolSetACFitted* _ptr) : vtraj(_vtraj), ptr(_ptr) {
      data = fann_create_train(vtraj.size(), ptr->nb_sensors + ptr->nb_motors, 1);
      for (uint n = 0; n < vtraj.size(); n++) {
        sample sm = vtraj[n];
        for (uint i = 0; i < ptr->nb_sensors ; i++)
          data->input[n][i] = sm.s[i];
        for (uint i= ptr->nb_sensors ; i < ptr->nb_sensors + ptr->nb_motors; i++)
          data->input[n][i] = sm.a[i - ptr->nb_sensors];
      }
    }

    ~ParraVtoVNext() { //must be empty cause of tbb

    }

    void free() {
      fann_destroy_train(data);
    }

    void operator()(const tbb::blocked_range<size_t>& range) const {

      struct fann* local_nn = fann_copy(ptr->vnn->getNeuralNet());
      struct fann* local_pol = fann_copy(ptr->ann->getNeuralNet());

      for (size_t n = range.begin(); n < range.end(); n++) {
        sample sm = vtraj[n];

        double delta = sm.r;
        if (!sm.goal_reached) {
          std::vector<double> * next_action = MLP::computeOut(local_pol, sm.next_s);
          double nextQA = MLP::computeOutVF(local_nn, sm.next_s, *next_action);
          delta += ptr->gamma * nextQA;
          delete next_action;
        }

        data->output[n][0] = delta;
      }

      fann_destroy(local_nn);
      fann_destroy(local_pol);
    }

    struct fann_train_data* data;
    const std::vector<sample>& vtraj;
    const OffPolSetACFitted* ptr;
  };

    void update_critic(){
      if (trajectory.size() > 2) {

        std::vector<sample> *vtraj;
        double *importance_sample = nullptr;
        if(strategy_w == 0){//only on the last data
          vtraj = new std::vector<sample>(last_trajectory.size());
          std::copy(last_trajectory.begin(), last_trajectory.end(), vtraj->begin());
        } else if(strategy_w >= 1 && strategy_w <= 5){
          vtraj = new std::vector<sample>(trajectory.size());
          std::copy(trajectory.begin(), trajectory.end(), vtraj->begin());

          if(strategy_w > 1)//on every data
            importance_sample = new double [trajectory.size()];

          if(strategy_w == 2) {//on every data 1/p(s)
            uint i=0;
            for(auto it = vtraj->begin(); it != vtraj->end() ; ++it) {
              importance_sample[i] = 1.f / proba_s.pdf(it->s);
              i++;
            }
          } else if(strategy_w == 3) {//on every data 1/p(s) normalized
            uint i=0;
            double sum_ps = 0.00f;
            for(auto it = vtraj->begin(); it != vtraj->end() ; ++it) {
              sum_ps += 1.f / proba_s.pdf(it->s);
              i++;
            }

            i=0;
            for(auto it = vtraj->begin(); it != vtraj->end() ; ++it) {
              importance_sample[i] =  (1.f / proba_s.pdf(it->s)) / sum_ps;
              i++;
            }
          } else if(strategy_w >= 4 && strategy_w <= 5) {

            double * ptheta = new double [trajectory.size()];
            computePTheta(*vtraj, ptheta);

            if(strategy_w == 4){
              uint i=0;
              for(auto it = vtraj->begin(); it != vtraj->end() ; ++it) {
                importance_sample[i] = ptheta[i];
                i++;
              }
            } else {
              uint i=0;
              for(auto it = vtraj->begin(); it != vtraj->end() ; ++it) {
                importance_sample[i] = ptheta[i] / it->p0;
                i++;
              }
            }

            delete[] ptheta;

          } else if(strategy_w != 1) {
              LOG_ERROR("to be implemented");
              exit(1);
          }

        } else if(strategy_w != 0) {
          LOG_ERROR("to be implemented");
          exit(1);
        }

        ParraVtoVNext dq(*vtraj, this);

        auto iter = [&]() {
          tbb::parallel_for(tbb::blocked_range<size_t>(0, vtraj->size()), dq);

          if(vnn_from_scratch)
            fann_randomize_weights(vnn->getNeuralNet(), -0.025, 0.025);

          if(strategy_w <= 1)
            vnn->learn_stoch(dq.data, 30000, 0, converge_precision);
          else
            vnn->learn_stoch_lw(dq.data, importance_sample, 30000, 0, converge_precision);
        };

        auto eval = [&]() {
          return fann_get_MSE(vnn->getNeuralNet());
        };

        if(determinist_vnn_update)
              bib::Converger::determinist<>(iter, eval, number_fitted_iteration, converge_precision, 0);
        else {
          NN best_nn = nullptr;
          auto save_best = [&]() {
            if(best_nn != nullptr)
              fann_destroy(best_nn);
            best_nn = fann_copy(vnn->getNeuralNet());
          };

          bib::Converger::min_stochastic<>(iter, eval, save_best, 30, converge_precision, 0, 10);
          vnn->copy(best_nn);
          fann_destroy(best_nn);
        }

        dq.free();
        if(strategy_w != 0)
          delete[] importance_sample;
        delete vtraj;
      }
  }

  inline void mergeSA(std::vector<double>& AB, const std::vector<double>& A, const std::vector<double>& B) {
    AB.reserve( A.size() + B.size() ); // preallocate memory
    AB.insert( AB.end(), A.begin(), A.end() );
    AB.insert( AB.end(), B.begin(), B.end() );
  }

  inline void mergeSAS(std::vector<double>& AB, const std::vector<double>& A, const std::vector<double>& B, const std::vector<double>& C) {
    AB.reserve( A.size() + B.size() + C.size() ); // preallocate memory
    AB.insert( AB.end(), A.begin(), A.end() );
    AB.insert( AB.end(), B.begin(), B.end() );
    AB.insert( AB.end(), C.begin(), C.end() );
  }

  void end_episode() override {
    if(learning)
      update_critic();
  }

  void learn_V(std::map<std::vector<double>, double>& bvf) override {
    if((episode+1) % change_policy_each == 0){
      trajectory.clear();
      last_trajectory.clear();

      struct fann_train_data* data = fann_create_train(bvf.size(), nb_sensors + nb_motors, 1);

      auto it = bvf.cbegin();
      for (uint n = 0; n < bvf.size(); n++) {

        for (uint i = 0; i < nb_sensors + nb_motors ; i++){
            data->input[n][i] = it->first[i];
            LOG_FILE_NNL("vset.data."+std::to_string(current_loaded_policy-1), it->first[i] << " ");
        }

        data->output[n][0] = it->second;
        LOG_FILE_NNL("vset.data."+std::to_string(current_loaded_policy-1), it->second << "\n");

        ++it;
      }
      //clear all; close all ;X=load('vset.data.904');[xx,yy] = meshgrid (linspace (-pi,pi,300));griddata(X(:,1),X(:,3),X(:,end),xx,yy);
      bib::Logger::getInstance()->closeFile("vset.data."+std::to_string(current_loaded_policy-1));

      fann_randomize_weights(vnn->getNeuralNet(), -0.025, 0.025);
      vnn->learn_stoch(data, 10000, 200, 0.0000001, 200);

      fann_destroy_train(data);

      vnn->save("vset."+std::to_string(current_loaded_policy-1));
      LOG_DEBUG("vset."+std::to_string(current_loaded_policy-1) << " saved " << bvf.size() );
      bvf.clear();
    }
  }

  void end_instance(bool) override {
    episode++;
  }

  double criticEval(const std::vector<double>& perceptions, const std::vector<double>& actions) override {
    return vnn->computeOutVF(perceptions, actions);
  }

  arch::Policy<MLP>* getCopyCurrentPolicy() override {
    return new arch::Policy<MLP>(new MLP(*ann) , gaussian_policy ? arch::policy_type::GAUSSIAN : arch::policy_type::GREEDY, noise, decision_each);
  }

  void save(const std::string& path) override {
    ann->save(path+".actor");
    vnn->save(path+".critic");
    bib::XMLEngine::save<>(trajectory, "trajectory", "trajectory.data");
  }

  void load(const std::string& path) override {
    ann->load(path+".actor");
    vnn->load(path+".critic");
  }

 protected:
  void _display(std::ostream& out) const override {
    out << std::setw(12) << std::fixed << std::setprecision(10) << sum_weighted_reward << " " << std::setw(
          8) << std::fixed << std::setprecision(8) << vnn->error() << " " << noise << " " << trajectory.size() ;
  }

  void _dump(std::ostream& out) const override {
    out <<" " << std::setw(25) << std::fixed << std::setprecision(22) <<
        sum_weighted_reward << " " << std::setw(8) << std::fixed <<
        std::setprecision(8) << vnn->error() << " " << trajectory.size() ;
  }

 private:
  uint nb_sensors;

  uint episode = 0;
  uint current_loaded_policy = 0;
  uint change_policy_each;
  uint strategy_w;

  double noise, converge_precision;
  bool gaussian_policy, vnn_from_scratch, lecun_activation,
        determinist_vnn_update;
  uint hidden_unit_v;
  uint hidden_unit_a;
  uint number_fitted_iteration;

  std::shared_ptr<std::vector<double>> last_action;
  std::shared_ptr<std::vector<double>> last_pure_action;
  std::vector<double> last_state;

  std::set<sample> trajectory;
  std::set<sample> last_trajectory;
//     std::list<sample> trajectory;
  KDE proba_s;
  KDE proba_sa, proba_sas;

  struct L1_distance
  {
    typedef double distance_type;

    double operator() (const double& __a, const double& __b, const size_t) const
    {
      double d = fabs(__a - __b);
      return d;
    }
  };
  typedef KDTree::KDTree<sample, KDTree::_Bracket_accessor<sample>, L1_distance> kdtree_sample;
  kdtree_sample* kdtree_s;

  MLP* ann;
  MLP* vnn;
};

#endif


#include <iostream>
#include <vector>
#include <cassert>
#include <cstring>
#include <limits>
#include <mpi.h>
//#include <Eigen/Eigenvalues>

enum KernelType {CubicSpline = 0, WendlandC2 = 1, WendlandC4 = 2};

#include "particle_simulator.hpp"
#include "hdr_time.hpp"
#include "hdr_run.hpp"
#include "vector_x86.hpp"
#include "hdr_dimension.hpp"
#include "hdr_kernel.hpp"
#include "hdr_sph.hpp"
#include "hdr_hgas.hpp"
#include "hdr_bhns.hpp"

class SPHAnalysis : public HelmholtzGas {
public:
    SPHAnalysis() {
        this->id    = 0;
        this->istar = 0;
        this->mass  = 0.;
        this->pos   = 0.;
        this->vel   = 0.;
        this->uene  = 0.;
        this->alph  = 0.;
        this->alphu = 0.;
        this->ksr   = 0.;
        for(PS::S32 k = 0; k < NR::NumberOfNucleon; k++) {
            this->cmps[k] = 0.;
        }
    }

    void readAscii(FILE * fp) {
        fscanf(fp, "%lld%lld%lf", &this->id, &this->istar, &this->mass);      //  3
        fscanf(fp, "%lf%lf%lf", &this->pos[0], &this->pos[1], &this->pos[2]); //  6
        fscanf(fp, "%lf%lf%lf", &this->vel[0], &this->vel[1], &this->vel[2]); //  9
        fscanf(fp, "%lf%lf%lf", &this->acc[0], &this->acc[1], &this->acc[2]); // 12
        fscanf(fp, "%lf%lf%lf", &this->uene, &this->alph, &this->alphu);      // 15
        fscanf(fp, "%lf%lf%6d", &this->dens, &this->ksr,  &this->np);         // 18
        fscanf(fp, "%lf%lf%lf", &this->vsnd, &this->pres, &this->temp);       // 21
        fscanf(fp, "%lf%lf%lf", &this->divv, &this->rotv, &this->bswt);       // 24
        fscanf(fp, "%lf%lf%lf", &this->pot,  &this->abar, &this->zbar);       // 27
        fscanf(fp, "%lf",       &this->enuc);                                 // 28
        fscanf(fp, "%lf%lf%lf", &this->vsmx, &this->udot, &this->dnuc);       // 31
        for(PS::S32 k = 0; k < NuclearReaction::NumberOfNucleon; k++) {       // 32 -- 44
            fscanf(fp, "%lf", &this->cmps[k]);
        }
        fscanf(fp, "%lf", &this->pot3);
        fscanf(fp, "%lf%lf%lf", &this->tempmax[0], &this->tempmax[1], &this->tempmax[2]);
        fscanf(fp, "%lf", &this->entr);
    }

    /*
    void write(FILE * fp = stdout) {
        fprintf(fp, "%8d %2d %+e",  this->id, this->istar, this->mass);
        fprintf(fp, " %+e %+e %+e", this->pos[0], this->pos[1], this->pos[2]);
        fprintf(fp, " %+e %+e %+e", this->vel[0], this->vel[1], this->vel[2]);
        fprintf(fp, " %+e %+e %+e", this->uene, this->alph, this->alphu);
        fprintf(fp, " %+e", this->ksr);
        for(PS::S32 k = 0; k < NR::NumberOfNucleon; k++) {
            fprintf(fp, " %+.3e", this->cmps[k]);
        }
        fprintf(fp, "\n");
    }
    */

    inline PS::F64mat calcMomentOfInertia(PS::F64vec xc) {
        PS::F64mat qq = 0.;
        PS::F64vec dx = this->pos - xc;
        PS::F64 r2 = dx * dx;
        qq.xx = this->mass * (r2 - dx[0] * dx[0]);
        qq.xy = this->mass * (   - dx[0] * dx[1]);
        qq.xz = this->mass * (   - dx[0] * dx[2]);
        qq.yy = this->mass * (r2 - dx[1] * dx[1]);
        qq.yz = this->mass * (   - dx[1] * dx[2]);
        qq.zz = this->mass * (r2 - dx[2] * dx[2]);
        return qq;
    }
};

class BlackHoleNeutronStarAnalysis : public BlackHoleNeutronStar {
public:
    BlackHoleNeutronStarAnalysis() {
        this->id    = 0;
        this->istar = 0;
        this->mass  = 0.;
        this->pos   = 0.;
        this->vel   = 0.;
        this->acc   = 0.;
        this->eps   = 0.;
        this->pot   = 0.;
    }

    void readAscii(FILE * fp) {
        fscanf(fp, "%lld%lld%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf",
               &this->id, &this->istar, &this->mass,
               &this->pos[0], &this->pos[1], &this->pos[2],
               &this->vel[0], &this->vel[1], &this->vel[2],
               &this->acc[0], &this->acc[1], &this->acc[2],
               &this->eps, &this->pot);
    }

    inline PS::F64mat calcMomentOfInertia(PS::F64vec xc) {
        PS::F64mat qq = 0.;
        PS::F64vec dx = this->pos - xc;
        PS::F64 r2 = dx * dx;
        qq.xx = this->mass * (r2 - dx[0] * dx[0]);
        qq.xy = this->mass * (   - dx[0] * dx[1]);
        qq.xz = this->mass * (   - dx[0] * dx[2]);
        qq.yy = this->mass * (r2 - dx[1] * dx[1]);
        qq.yz = this->mass * (   - dx[1] * dx[2]);
        qq.zz = this->mass * (r2 - dx[2] * dx[2]);
        return qq;
    }
};

template <class Tsph,
          class Tbhns>
void calcDiskParameter(char * ofile,
                       Tsph  & sph,
                       Tbhns & bhns) {

    for(PS::S64 i = 0; i < sph.getNumberOfParticleLocal(); i++) {
        sph[i].pos -= bhns[0].pos;
    }

    PS::F64 dz      = 1e6;
    PS::F64 rbinmin = 1e7;
    PS::F64 rbinmax = 1e10;
    PS::S64 npd     = 10;
    PS::F64 drbin   = pow(10., 1 / (PS::F64)npd);

    FILE * fp = fopen(ofile, "w");
    for(PS::F64 rbin = rbinmin; rbin < rbinmax; rbin *= drbin) {
        PS::F64 rbin02 = rbin * rbin;
        PS::F64 rbin12 = (rbin * drbin) * (rbin * drbin);
        PS::F64 mdisk  = 0.;
        PS::F64 mcol   = 0.;
        PS::F64 udisk  = 0.;
        PS::S64 ncol   = 0;
        PS::S64 ndisk  = 0;
        for(PS::S64 i  = 0; i < sph.getNumberOfParticleLocal(); i++) {
            PS::F64 r2 = sph[i].pos[0] * sph[i].pos[0] + sph[i].pos[1] * sph[i].pos[1];
            if(r2 < rbin02 || rbin12 <= r2) {
                continue;
            }
            mcol  += sph[i].mass;
            ncol  += 1;
            if(fabs(sph[i].pos[2]) > dz) {
                continue;
            }
            mdisk += sph[i].mass;
            udisk += sph[i].mass * sph[i].uene;
            ndisk += 1;
        }
        PS::F64 surface = M_PI * (rbin12 - rbin02);
        PS::F64 volume  = surface * (2. * dz);
        PS::F64 sigma   = mcol / surface;
        PS::F64 dens    = mdisk / volume;
        PS::F64 uene    = udisk / mdisk;
        PS::F64 tout    = 0.;
        if(dens != 0.) {
            NR::Nucleon cmps;
            cmps[1] = cmps[2] = 0.5;
            PS::F64 temp = 1e9;
            PS::F64 pout, cout, sout;
            flash_helmholtz_(&dens, &uene, &temp, cmps.getPointer(),
                             &pout, &cout, &tout, &sout);
        }
        fprintf(fp, "%+e %+e %+e %+e %6d %6d\n", rbin, sigma, dens, tout, ncol, ndisk);
    }
    fclose(fp);

}

int main(int argc, char ** argv) {
    MPI_Init(&argc, &argv);

    init_flash_helmholtz_(&CodeUnit::FractionOfCoulombCorrection);

    PS::ParticleSystem<SPHAnalysis> sph;
    sph.initialize();
    sph.createParticle(0);
    sph.setNumberOfParticleLocal(0);
    PS::ParticleSystem<BlackHoleNeutronStarAnalysis> bhns;
    bhns.initialize();
    bhns.createParticle(0);
    bhns.setNumberOfParticleLocal(0);

    char idir[1024], otype[1024];
    PS::S64 ibgn, iend;
    FILE * fp = fopen(argv[1], "r");
    fscanf(fp, "%s", idir);
    fscanf(fp, "%s", otype);
    fscanf(fp, "%lld%lld", &ibgn, &iend);
    fclose(fp);

    for(PS::S64 itime = ibgn; itime <= iend; itime++) {
        char sfile[1024], bfile[1024];
        sprintf(sfile, "%s/sph_t%04d.dat",  idir, itime);
        sprintf(bfile, "%s/bhns_t%04d.dat", idir, itime);
        fp = fopen(sfile, "r");
        if(fp == NULL) {
            continue;
        }
        sph.readParticleAscii(sfile);
        fclose(fp);
        fp = fopen(bfile, "r");
        if(fp == NULL) {
            continue;
        }
        bhns.readParticleAscii(bfile);
        fclose(fp);

        char ofile[1024];
        sprintf(ofile, "%s_t%04d.dat", otype, itime);
        printf("Output: %s\n", ofile);
        calcDiskParameter(ofile, sph, bhns);

    }

    MPI_Finalize();

    return 0;
}