// Copyright (c) 2020 - 2021 Advanced Micro Devices, Inc. All rights reserved.
//
// 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 <complex>
#include <cstddef>
#include <iostream>
#include <numeric>
#include <random>
#include <sstream>

#include "rider.h"
#include <boost/program_options.hpp>
namespace po = boost::program_options;

int input_buffer_size(hipfftType type, int dist, int nbatch)
{
    switch(type)
    {
    case HIPFFT_Z2D:
    case HIPFFT_Z2Z:
        return dist * nbatch * 16;
    case HIPFFT_D2Z:
        return dist * nbatch * 8;
    case HIPFFT_C2R:
    case HIPFFT_C2C:
        return dist * nbatch * 8;
    case HIPFFT_R2C:
        return dist * nbatch * 4;
    }
}

int output_buffer_size(hipfftType type, int dist, int nbatch)
{
    switch(type)
    {
    case HIPFFT_D2Z:
    case HIPFFT_Z2Z:
        return dist * nbatch * 16;
    case HIPFFT_Z2D:
        return dist * nbatch * 8;
    case HIPFFT_R2C:
    case HIPFFT_C2C:
        return dist * nbatch * 8;
    case HIPFFT_C2R:
        return dist * nbatch * 4;
    }
}

std::vector<char> compute_input(hipfftType type, int dist, int nbatch)
{
    auto              nbytes = input_buffer_size(type, dist, nbatch);
    std::vector<char> buffer(nbytes);

    std::random_device rd;
    std::mt19937       gen(rd());

    switch(type)
    {
    case HIPFFT_Z2D:
    case HIPFFT_Z2Z:
    {
        std::uniform_real_distribution<double> dis(0.0, 1.0);

        auto x = (std::complex<double>*)buffer.data();
#pragma omp parallel for
        for(size_t i = 0; i < dist * nbatch; ++i)
        {
            x[i].real(dis(gen));
            x[i].imag(dis(gen));
        }
    }
    break;
    case HIPFFT_D2Z:
    {
        std::uniform_real_distribution<double> dis(0.0, 1.0);

        double* x = (double*)buffer.data();
#pragma omp parallel for
        for(size_t i = 0; i < dist * nbatch; ++i)
        {
            x[i] = dis(gen);
        }
    }
    break;
    case HIPFFT_C2R:
    case HIPFFT_C2C:
    {
        std::uniform_real_distribution<float> dis(0.0, 1.0);

        auto x = (std::complex<float>*)buffer.data();
#pragma omp parallel for
        for(size_t i = 0; i < dist * nbatch; ++i)
        {
            x[i].real(dis(gen));
            x[i].imag(dis(gen));
        }
    }
    break;
    case HIPFFT_R2C:
    {
        std::uniform_real_distribution<float> dis(0.0, 1.0);

        float* x = (float*)buffer.data();
#pragma omp parallel for
        for(size_t i = 0; i < dist * nbatch; ++i)
        {
            x[i] = dis(gen);
        }
    }
    break;
    }

    return buffer;
}

template <typename T1, typename T2>
bool increment_rowmajor(std::vector<T1>& index, const std::vector<T2>& length)
{
    for(int idim = length.size(); idim-- > 0;)
    {
        if(index[idim] < length[idim])
        {
            if(++index[idim] == length[idim])
            {
                index[idim] = 0;
                continue;
            }
            // we know we were able to increment something and didn't hit the end
            return true;
        }
    }
    // End the loop when we get back to the start:
    return !std::all_of(index.begin(), index.end(), [](int i) { return i == 0; });
}

template <typename Toutput, typename Tstream = std::ostream>
inline void printbuffer(const Toutput*          output,
                        const std::vector<int>& length,
                        const std::vector<int>& stride,
                        const int               nbatch,
                        const int               dist,
                        const int               offset,
                        Tstream&                stream = std::cout)
{
    auto i_base = 0;
    for(auto b = 0; b < nbatch; b++, i_base += dist)
    {
        std::vector<int> index(length.size());
        std::fill(index.begin(), index.end(), 0);
        do
        {
            const int i
                = std::inner_product(index.begin(), index.end(), stride.begin(), i_base + offset);
            stream << output[i] << " ";
            for(int i = index.size(); i-- > 0;)
            {
                if(index[i] == (length[i] - 1))
                {
                    stream << "\n";
                }
                else
                {
                    break;
                }
            }
        } while(increment_rowmajor(index, length));
        stream << std::endl;
    }
}

int main(int argc, char* argv[])
{
    // This helps with mixing output of both wide and narrow characters to the screen
    std::ios::sync_with_stdio(false);

    // Control output verbosity:
    int verbose;

    // hip Device number for running tests:
    int deviceId;

    // Transform type parameters:
    int        itransformType;
    hipfftType transformType;

    // Number of performance trial samples
    int ntrial;

    // Number of batches:
    int nbatch = 1;

    // Transform length:
    std::vector<int> length;

    // Transform input and output strides:
    std::vector<int> istride;
    std::vector<int> ostride;

    // Offset to start of buffer:
    std::vector<int> ioffset;
    std::vector<int> ooffset;

    // Input and output distances:
    int idist;
    int odist;

    int itype;
    int otype; // Ignored; for compatibility with rocfft-rider.

    // Declare the supported options.

    // clang-format doesn't handle boost program options very well:
    // clang-format off
    po::options_description opdesc("hipfft rider command line options");
    opdesc.add_options()("help,h", "produces this help message")
        ("version,v", "Print queryable version information from the hipfft library")
        ("device", po::value<int>(&deviceId)->default_value(0), "Select a specific device id")
        ("verbose", po::value<int>(&verbose)->default_value(0), "Control output verbosity")
        ("ntrial,N", po::value<int>(&ntrial)->default_value(1), "Trial size for the problem")
        ("notInPlace,o", "Not in-place FFT transform (default: in-place)")
        ("double", "Double precision transform (default: single)")
        ("transformType,t", po::value<int>(&itransformType)
         ->default_value(0),
         "Type of transform:\n0) complex forward\n1) complex inverse\n2) real "
         "forward\n3) real inverse")
        ( "idist", po::value<int>(&idist)->default_value(0),
          "input distance between successive members when batch size > 1")
        ( "odist", po::value<int>(&odist)->default_value(0),
          "output distance between successive members when batch size > 1")
        ( "batchSize,b", po::value<int>(&nbatch)->default_value(1),
          "If this value is greater than one, arrays will be used ")
        ( "itype", po::value<int>(&itype))
        ( "otype", po::value<int>(&otype))
        ("length",  po::value<std::vector<int>>(&length)->multitoken(), "Lengths.")
        ("istride", po::value<std::vector<int>>(&istride)->multitoken(), "Input strides.")
        ("ostride", po::value<std::vector<int>>(&ostride)->multitoken(), "Output strides.")
        ("ioffset", po::value<std::vector<int>>(&ioffset)->multitoken(), "Input offsets.")
        ("ooffset", po::value<std::vector<int>>(&ooffset)->multitoken(), "Output offsets.");
    // clang-format on

    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, opdesc), vm);
    po::notify(vm);

    if(vm.count("help"))
    {
        std::cout << opdesc << std::endl;
        return 0;
    }

    if(vm.count("version"))
    {
        int v;
        hipfftGetVersion(&v);
        std::cout << "version " << v << std::endl;
        return 0;
    }

    if(!vm.count("length"))
    {
        std::cout << "Please specify transform length!" << std::endl;
        std::cout << opdesc << std::endl;
        return 0;
    }

    const bool inplace          = !bool(vm.count("notInPlace"));
    const bool double_precision = bool(vm.count("double"));
    int        direction;
    switch(itransformType)
    {
    case 0:
        transformType = double_precision ? HIPFFT_Z2Z : HIPFFT_C2C;
        direction     = HIPFFT_FORWARD;
        break;
    case 1:
        transformType = double_precision ? HIPFFT_Z2Z : HIPFFT_C2C;
        direction     = HIPFFT_BACKWARD;
        // backward
        break;
    case 2:
        transformType = double_precision ? HIPFFT_D2Z : HIPFFT_R2C;
        direction     = HIPFFT_FORWARD;
        break;
    case 3:
        transformType = double_precision ? HIPFFT_Z2D : HIPFFT_C2R;
        direction     = HIPFFT_BACKWARD;
        break;
    }

    std::cout << "Placement: ";
    if(!inplace)
    {
        std::cout << "out-of-place\n";
    }
    else
    {
        std::cout << "in-place\n";
    }

    if(vm.count("ntrial"))
    {
        std::cout << "Running profile with " << ntrial << " samples\n";
    }

    if(vm.count("length"))
    {
        std::cout << "Length:";
        for(auto& i : length)
            std::cout << " " << i;
        std::cout << "\n";
    }

    if(vm.count("istride"))
    {
        std::cout << "Input stride:";
        for(auto& i : istride)
            std::cout << " " << i;
        std::cout << "\n";

        // istride in hipfftPlanMany applies to innermost dimension,
        // which is implicitly to each logic dimension.
        for(auto& i : istride)
        {
            if(i != istride[0])
            {
                std::cerr << "Unspported strides: istride must be identical along all dimensions.";
                return -1;
            }
        }
    }
    if(vm.count("ostride"))
    {
        std::cout << "Output stride:";
        for(auto& i : ostride)
            std::cout << " " << i;
        std::cout << "\n";

        for(auto& i : ostride)
        {
            if(i != ostride[0])
            {
                std::cerr << "Unspported strides: ostride must be identical along all dimensions.";
                return -1;
            }
        }
    }

    if(idist > 0)
    {
        std::cout << "Input distance: " << idist << "\n";
    }
    if(odist > 0)
    {
        std::cout << "Output distance: " << odist << "\n";
    }

    if(vm.count("ioffset"))
    {
        std::cout << "Input offset:";
        for(auto& i : ioffset)
            std::cout << " " << i;
        std::cout << "\n";
    }
    if(vm.count("ooffset"))
    {
        std::cout << "Output offset:";
        for(auto& i : ooffset)
            std::cout << " " << i;
        std::cout << "\n";
    }

    std::cout << std::flush;

    // Fixme: set the device id properly after the IDs are synced
    // bewteen hip runtime and rocm-smi.
    //HIP_V_THROW(hipSetDevice(deviceId), "set device failed!");

    // Set default data formats if not yet specified:
    const int dim = length.size();

    auto ilength = length;
    if(transformType == HIPFFT_C2R || transformType == HIPFFT_Z2D)
    {
        ilength[dim - 1] = ilength[dim - 1] / 2 + 1;
    }
    if(istride.empty())
    {
        istride = compute_stride(
            ilength, {1}, inplace && (transformType == HIPFFT_R2C || transformType == HIPFFT_D2Z));
    }

    auto olength = length;
    if(transformType == HIPFFT_R2C || transformType == HIPFFT_D2Z)
    {
        olength[dim - 1] = olength[dim - 1] / 2 + 1;
    }
    if(ostride.empty())
    {
        ostride = compute_stride(
            olength, {1}, inplace && (transformType == HIPFFT_C2R || transformType == HIPFFT_Z2D));
    }

    if(idist == 0)
    {
        idist = std::accumulate(ilength.cbegin(), ilength.cend(), 1, std::multiplies<size_t>());
    }
    if(odist == 0)
    {
        odist = std::accumulate(olength.cbegin(), olength.cend(), 1, std::multiplies<size_t>());
    }

    if(verbose > 0)
    {
        std::cout << "FFT  params:\n";
        std::cout << "\tInput length:";
        for(auto i : ilength)
            std::cout << " " << i;
        std::cout << "\n";
        std::cout << "\tInput stride:";
        for(auto i : istride)
            std::cout << " " << i;
        std::cout << "\n";
        std::cout << "\tInput distance: " << idist << std::endl;

        std::cout << "\tOutput length:";
        for(auto i : olength)
            std::cout << " " << i;
        std::cout << "\n";
        std::cout << "\tOutput stride:";
        for(auto i : ostride)
            std::cout << " " << i;
        std::cout << "\n";
        std::cout << "\tOutput distance: " << odist << std::endl;
    }

    hipfftHandle plan;
    LIB_V_THROW(hipfftCreate(&plan), "hipfftCreate failed");

    int i_stride   = istride[length.size() - 1];
    int o_stride   = ostride[length.size() - 1];
    int inembed[3] = {i_stride * length[0], 0, 0};
    int onembed[3] = {o_stride * length[0], 0, 0};

    if(dim >= 2)
    {
        inembed[1] = i_stride * length[1];
        onembed[1] = o_stride * length[1];
    }
    if(dim >= 3)
    {
        inembed[2] = i_stride * length[2];
        onembed[2] = o_stride * length[2];
    }

    if(transformType == HIPFFT_R2C || transformType == HIPFFT_D2Z)
    {
        switch(dim)
        {
        case 1:
        {
            int n0_complex_elements      = length[0] / 2 + 1;
            int n0_padding_real_elements = n0_complex_elements * 2;
            inembed[0]                   = i_stride * n0_padding_real_elements;
            onembed[0]                   = o_stride * n0_complex_elements;
            break;
        }
        case 2:
        {
            int n1_complex_elements      = length[1] / 2 + 1;
            int n1_padding_real_elements = n1_complex_elements * 2;
            inembed[1]                   = i_stride * n1_padding_real_elements;
            onembed[1]                   = o_stride * n1_complex_elements;
            break;
        }
        case 3:
        {
            int n2_complex_elements      = length[2] / 2 + 1;
            int n2_padding_real_elements = n2_complex_elements * 2;
            inembed[1]                   = i_stride * length[1];
            onembed[1]                   = o_stride * length[1];
            inembed[2]                   = i_stride * n2_padding_real_elements;
            onembed[2]                   = o_stride * n2_complex_elements;
            break;
        }
        }
    }
    else if(transformType == HIPFFT_C2R || transformType == HIPFFT_Z2D)
    {
        switch(dim)
        {
        case 1:
        {
            int n0_complex_elements      = length[0] / 2 + 1;
            int n0_padding_real_elements = n0_complex_elements * 2;
            onembed[0]                   = o_stride * n0_padding_real_elements;
            inembed[0]                   = i_stride * n0_complex_elements;
            break;
        }
        case 2:
        {
            int n1_complex_elements      = length[1] / 2 + 1;
            int n1_padding_real_elements = n1_complex_elements * 2;
            onembed[1]                   = o_stride * n1_padding_real_elements;
            inembed[1]                   = i_stride * n1_complex_elements;
            break;
        }
        case 3:
        {
            int n2_complex_elements      = length[2] / 2 + 1;
            int n2_padding_real_elements = n2_complex_elements * 2;
            onembed[1]                   = o_stride * length[1];
            inembed[1]                   = i_stride * length[1];
            onembed[2]                   = o_stride * n2_padding_real_elements;
            inembed[2]                   = i_stride * n2_complex_elements;
            break;
        }
        }
    }

    LIB_V_THROW(hipfftPlanMany(&plan,
                               dim,
                               length.data(),
                               inembed,
                               i_stride,
                               idist,
                               onembed,
                               o_stride,
                               odist,
                               transformType,
                               nbatch),
                "hipfftPlanMany failed");

    // Get work buffer size and allocated associated work buffer is necessary
    void*  work_buf;
    size_t work_buf_size = 0;
    LIB_V_THROW(hipfftGetSizeMany(plan,
                                  dim,
                                  length.data(),
                                  inembed,
                                  i_stride,
                                  idist,
                                  onembed,
                                  o_stride,
                                  odist,
                                  transformType,
                                  nbatch,
                                  &work_buf_size),
                "hipfftGetSizeMany failed");
    if(work_buf_size)
    {
        HIP_V_THROW(hipMalloc(&work_buf, work_buf_size), "Creating intermediate buffer failed");
        LIB_V_THROW(hipfftSetWorkArea(plan, work_buf), "hipfftSetWorkArea failed");
    }

    hipError_t hip_status = hipSuccess;

    // GPU input and output buffers:
    auto ibuffer_size = input_buffer_size(transformType, idist, nbatch);
    auto obuffer_size = output_buffer_size(transformType, odist, nbatch);

    void* ibuffer;
    HIP_V_THROW(hipMalloc(&ibuffer, ibuffer_size), "hipMalloc failed");

    void* obuffer;
    if(inplace)
    {
        obuffer = ibuffer;
    }
    else
    {
        HIP_V_THROW(hipMalloc(&obuffer, obuffer_size), "hipMalloc failed");
    }

    // Input data:
    const auto input = compute_input(transformType, idist, nbatch);

    if(verbose > 1)
    {
        std::cout << "GPU input:\n";
        switch(transformType)
        {
        case HIPFFT_Z2D:
        case HIPFFT_Z2Z:
            printbuffer((std::complex<double>*)input.data(), ilength, istride, nbatch, idist, 0);
            break;
        case HIPFFT_D2Z:
            printbuffer((double*)input.data(), ilength, istride, nbatch, idist, 0);
            break;
        case HIPFFT_C2R:
        case HIPFFT_C2C:
            printbuffer((std::complex<float>*)input.data(), ilength, istride, nbatch, idist, 0);
            break;
        case HIPFFT_R2C:
            printbuffer((float*)input.data(), ilength, istride, nbatch, idist, 0);
            break;
        }
    }

    // Copy the input data to the GPU:
    HIP_V_THROW(hipMemcpy(ibuffer, input.data(), ibuffer_size, hipMemcpyHostToDevice),
                "hipMemcpy failed");

    // Run the transform several times and record the execution time:
    std::vector<double> gpu_time(ntrial);

    hipEvent_t start, stop;
    HIP_V_THROW(hipEventCreate(&start), "hipEventCreate failed");
    HIP_V_THROW(hipEventCreate(&stop), "hipEventCreate failed");

    // Warm up once (itrial == -1 corresponds to the warm-up trial)
    for(int itrial = -1; itrial < int(gpu_time.size()); ++itrial)
    {
        // Copy the input data to the GPU:
        HIP_V_THROW(hipMemcpy(ibuffer, input.data(), input.size(), hipMemcpyHostToDevice),
                    "hipMemcpy failed");

        HIP_V_THROW(hipEventRecord(start), "hipEventRecord failed");

        switch(transformType)
        {
        case HIPFFT_R2C:
            LIB_V_THROW(hipfftExecR2C(plan, (hipfftReal*)(ibuffer), (hipfftComplex*)(obuffer)),
                        "hipfftExecR2C failed");
            break;
        case HIPFFT_D2Z:
            LIB_V_THROW(
                hipfftExecD2Z(plan, (hipfftDoubleReal*)(ibuffer), (hipfftDoubleComplex*)(obuffer)),
                "hipfftExecD2Z failed");
            break;
        case HIPFFT_C2R:
            LIB_V_THROW(hipfftExecC2R(plan, (hipfftComplex*)(ibuffer), (hipfftReal*)(obuffer)),
                        "hipfftExecC2R failed");
            break;
        case HIPFFT_Z2D:
            LIB_V_THROW(
                hipfftExecZ2D(plan, (hipfftDoubleComplex*)(ibuffer), (hipfftDoubleReal*)(obuffer)),
                "hipfftExecZ2D failed");
            break;
        case HIPFFT_C2C:
            LIB_V_THROW(hipfftExecC2C(
                            plan, (hipfftComplex*)(ibuffer), (hipfftComplex*)(obuffer), direction),
                        "hipfftExecC2C failed");
            break;
        case HIPFFT_Z2Z:
            LIB_V_THROW(hipfftExecZ2Z(plan,
                                      (hipfftDoubleComplex*)(ibuffer),
                                      (hipfftDoubleComplex*)(obuffer),
                                      direction),
                        "hipfftExecZ2Z failed");
            break;
        }

        HIP_V_THROW(hipEventRecord(stop), "hipEventRecord failed");
        HIP_V_THROW(hipEventSynchronize(stop), "hipEventSynchronize failed");

        float time;
        hipEventElapsedTime(&time, start, stop);
        if(itrial > -1)
        {
            gpu_time[itrial] = time;
        }

        if(verbose > 1)
        {
            std::vector<char> output(obuffer_size);
            HIP_V_THROW(hipMemcpy(output.data(), obuffer, output.size(), hipMemcpyDeviceToHost),
                        "hipMemcpy failed");

            std::cout << "GPU output:\n";
            switch(transformType)
            {
            case HIPFFT_D2Z:
            case HIPFFT_Z2Z:
                printbuffer(
                    (std::complex<double>*)output.data(), olength, ostride, nbatch, odist, 0);
                break;
            case HIPFFT_Z2D:
                printbuffer((double*)output.data(), olength, ostride, nbatch, odist, 0);
                break;
            case HIPFFT_R2C:
            case HIPFFT_C2C:
                printbuffer(
                    (std::complex<float>*)output.data(), olength, ostride, nbatch, odist, 0);
                break;
            case HIPFFT_C2R:
                printbuffer((float*)output.data(), olength, ostride, nbatch, odist, 0);
                break;
            }
        }
    }

    std::cout << "\nExecution gpu time:";
    for(const auto& i : gpu_time)
    {
        std::cout << " " << i;
    }
    std::cout << " ms" << std::endl;

    // Clean up:

    hipfftDestroy(plan);
    if(work_buf_size)
        hipFree(work_buf);
    hipFree(ibuffer);
    if(!inplace)
        hipFree(obuffer);

    return 0;
}


#include <iostream>
#include <NTL/ZZ>
#include "sum.h"
int main()
{
	NTL::ZZ x = 1;
	EC ec(x, x);
	return 0;
}


#include <miopen/solver.hpp>

#include <miopen/conv/wrw_invoke_params.hpp>
#include <miopen/errors.hpp>
#include <miopen/gemm_v2.hpp>
#include <miopen/solver/gemm_common.hpp>
#include <miopen/tensor_ops.hpp>
#include <miopen/util.hpp>

#include <boost/range/adaptors.hpp>

MIOPEN_DECLARE_ENV_VAR(MIOPEN_CONV_PRECISE_ROCBLAS_TIMING)

// copy from convolution.cpp
// Workaround for issue 1430.
// Vega20 fails to access GPU memory larger than the return value of GetMaxMemoryAllocSize() of
// Vega10
#define MAX_MEM_ALLOC_SZ (std::min(handle.GetMaxMemoryAllocSize(), size_t(7287183769)))

namespace miopen {
namespace solver {

#if MIOPEN_USE_GEMM
#ifdef CPPCHECK
// Keep the value unknown in cppcheck since this can differ between opencl and hip
static bool IsBF16PathValid;
static bool IsFp16Supported;
#else
static const bool IsBF16PathValid = (MIOPEN_USE_ROCBLAS || MIOPEN_USE_MIOPENTENSILE);
static const bool IsFp16Supported = (MIOPEN_USE_ROCBLAS || MIOPEN_USE_MIOPENTENSILE);
#endif

static inline bool IsAnyBufferBF16(const TensorDescriptor& xDesc,
                                   const TensorDescriptor& yDesc,
                                   const TensorDescriptor& wDesc)
{
    return xDesc.GetType() == miopenBFloat16 || yDesc.GetType() == miopenBFloat16 ||
           wDesc.GetType() == miopenBFloat16;
}

static inline bool IsAnyBufferFp16(const TensorDescriptor& xDesc,
                                   const TensorDescriptor& yDesc,
                                   const TensorDescriptor& wDesc)
{
    return xDesc.GetType() == miopenHalf || yDesc.GetType() == miopenHalf ||
           wDesc.GetType() == miopenHalf;
}
#endif

static double
SlowdownFactor(int n_oper, const double oper_factor, const double multiple_oper_factor)
{
    if(n_oper > 0)
    {
        auto rv = oper_factor;
        if(n_oper > 1)
            rv *= multiple_oper_factor;
        return rv;
    }
    else
        return 1.0;
}

bool GemmWrwBase::IsApplicable(const ExecutionContext& ctx,
                               const conv::ProblemDescription& problem) const
{
#if MIOPEN_USE_GEMM
    if(conv::solver::gemm::IsWorkaroundIssue1315(ctx))
        return false;
    const auto& dyDesc = problem.GetIn();
    const auto& dwDesc = problem.GetWeights();
    const auto& xDesc  = problem.GetOut();
    return problem.GetDirection() == conv::Direction::BackwardWeights &&
           problem.IsLayoutDefault() &&
           !(IsAnyBufferBF16(xDesc, dyDesc, dwDesc) && !IsBF16PathValid) &&
           !(IsAnyBufferFp16(xDesc, dyDesc, dwDesc) && !IsFp16Supported);
#else
    std::ignore = ctx;
    std::ignore = problem;
    return false;
#endif
}

float GemmWrwBase::GetWti(const ExecutionContext&, const conv::ProblemDescription& problem) const
{
#if MIOPEN_USE_GEMM
    const auto& dwDesc = problem.GetWeights();
    const auto& xDesc  = problem.GetOut();
    const auto& conv   = problem.GetConv();

    int n_gemm_strided_batched           = 1; // not strided-batched by default
    int n_gemm_strided_batched_sequental = 1; // not strided-batched-sequental by default
    int n_gemm_runs                      = 1;
    int n_Im2ColGPU                      = 0;

    std::size_t in_n, in_c;
    std::tie(in_n, in_c) = tie_pick<0, 1>()(xDesc.GetLengths());
    const auto wei_spatial =
        boost::adaptors::slice(dwDesc.GetLengths(), 2, 2 + conv.GetSpatialDimension());

    // if not 1x1
    if((miopen::any_of(wei_spatial, [](auto v) { return v != 1; }) ||
        miopen::any_of(conv.GetConvPads(), [](auto v) { return v != 0; }) ||
        miopen::any_of(conv.GetConvStrides(), [](auto v) { return v != 1; })))
    {
        n_Im2ColGPU            = in_n;
        n_gemm_strided_batched = conv.group_count;
        n_gemm_runs            = in_n;
    }
    // 1x1 does not require im2col or workspace
    else if(miopen::any_of(wei_spatial, [](auto v) { return v == 1; }) &&
            miopen::any_of(conv.GetConvPads(), [](auto v) { return v == 0; }) &&
            miopen::any_of(conv.GetConvStrides(), [](auto v) { return v == 1; }))
    {
        n_gemm_strided_batched_sequental = conv.group_count;
        n_gemm_runs                      = in_n;
    }

    auto wti = 0.7; // Memory overhead for WrW is bigger then for Fwd/Bwd.
    wti *= SlowdownFactor(n_gemm_runs, 0.9, 0.9);
    wti *= SlowdownFactor(n_gemm_strided_batched, 1.0, 0.95);
    wti *= SlowdownFactor(n_gemm_strided_batched_sequental, 1.0, 0.9);
    wti *= SlowdownFactor(n_Im2ColGPU, 0.4, 0.8);
    return wti;
#else
    std::ignore = problem;
    return 0;
#endif
}

size_t GemmWrw1x1_stride1::GetWorkspaceSize(const ExecutionContext&,
                                            const conv::ProblemDescription&) const
{
    return 0;
}

bool GemmWrw1x1_stride1::IsApplicable(const ExecutionContext& context,
                                      const conv::ProblemDescription& problem) const
{
#if MIOPEN_USE_GEMM
    if(!GemmWrwBase::IsApplicable(context, problem))
        return false;

    const auto& dwDesc = problem.GetWeights();
    const auto& conv   = problem.GetConv();

    const auto wei_spatial =
        boost::adaptors::slice(dwDesc.GetLengths(), 2, 2 + conv.GetSpatialDimension());

    return miopen::all_of(wei_spatial, [](auto v) { return v == 1; }) &&
           miopen::all_of(conv.GetConvStrides(), [](auto v) { return v == 1; }) &&
           miopen::all_of(conv.GetConvPads(), [](auto v) { return v == 0; });
#else
    std::ignore = context;
    std::ignore = problem;
    return false;
#endif
}

ConvSolution GemmWrw1x1_stride1::GetSolution(const ExecutionContext&,
                                             const conv::ProblemDescription& problem) const
{
#if MIOPEN_USE_GEMM
    const auto& dyDesc     = problem.GetIn();
    const auto& dwDesc     = problem.GetWeights();
    const auto& xDesc      = problem.GetOut();
    const auto& conv       = problem.GetConv();
    const auto group_count = conv.group_count;

    if(group_count > 1)
    {
        MIOPEN_LOG_FUNCTION("groupconv, 1x1");
    }
    else
    {
        MIOPEN_LOG_FUNCTION("convolution, 1x1");
    }

    // dw = sum_over_batch(dy[i] * transpose(x[i])), i is batch id
    const auto gemm_desc =
        group_count > 1 ? CreateGemmDescriptorGroupConvBwdWeight(dyDesc, xDesc, dwDesc, group_count)
                        : CreateGemmStridedBatchedDescriptorConv1x1BwdWeight(dyDesc, xDesc, dwDesc);

    const auto in_spatial =
        boost::adaptors::slice(xDesc.GetLengths(), 2, 2 + conv.GetSpatialDimension());
    const auto out_spatial =
        boost::adaptors::slice(dyDesc.GetLengths(), 2, 2 + conv.GetSpatialDimension());

    const auto out_spatial_size = std::accumulate(
        out_spatial.begin(), out_spatial.end(), std::size_t(1), std::multiplies<std::size_t>());

    const auto in_spatial_size = std::accumulate(
        in_spatial.begin(), in_spatial.end(), std::size_t(1), std::multiplies<std::size_t>());

    std::size_t in_n, in_c;
    std::tie(in_n, in_c) = tie_pick<0, 1>()(xDesc.GetLengths());

    const auto wei_k = dwDesc.GetLengths()[0];

    auto solution = ConvSolution{miopenStatusSuccess};

    solution.invoker_factory = [=](const std::vector<Kernel>&) {
        const bool time_precision = (!IsDisabled(MIOPEN_CONV_PRECISE_ROCBLAS_TIMING{}));

        return [=](const Handle& handle, const AnyInvokeParams& primitive_params) {
            const auto& conv_params = primitive_params.CastTo<conv::WrWInvokeParams>();
            const auto& dy          = conv_params.tensors.dy;
            const auto& dw          = conv_params.tensors.dw;
            const auto& dwDesc_     = conv_params.tensors.dwDesc;
            const auto& x           = conv_params.tensors.x;

            if(group_count > 1)
            {
                MIOPEN_LOG_FUNCTION("groupconv, 1x1");
            }
            else
            {
                MIOPEN_LOG_FUNCTION("conv, 1x1");
            }

            if(conv_params.type != InvokeType::Run)
            {
                const auto status = CallGemmTimeMeasure(
                    handle,
                    gemm_desc,
                    dy,
                    0,
                    x,
                    0,
                    dw,
                    0,
                    nullptr,
                    time_precision,
                    group_count > 1 ? callGemmStridedBatched : callGemmStridedBatchedSequential,
                    group_count > 1 ? GemmBackend_t::miopentensile : GemmBackend_t::miopengemm,
                    conv_params.gfx90aFp16alt);

                if(status != miopenStatusSuccess)
                    MIOPEN_THROW("GemmWrw1x1_stride1 execution failure.");

                auto time = handle.GetKernelTime();

                if(group_count > 1)
                    time *= in_n;

                handle.ResetKernelTime();
                handle.AccumKernelTime(time);
            }
            else
            {
                // Zeroing out the output buffer
                float zero = 0.0f;
                SetTensor(handle, dwDesc_, dw, &zero);

                if(group_count > 1)
                {
                    auto time = 0;

                    for(std::size_t i = 0; i < in_n; i++)
                    {
                        const auto out_offset = i * wei_k * out_spatial_size;
                        const auto in_offset  = i * in_c * in_spatial_size;

                        const auto status = CallGemmStridedBatched(handle,
                                                                   gemm_desc,
                                                                   dy,
                                                                   out_offset,
                                                                   x,
                                                                   in_offset,
                                                                   dw,
                                                                   0,
                                                                   nullptr,
                                                                   GemmBackend_t::miopentensile,
                                                                   conv_params.gfx90aFp16alt);

                        if(status != miopenStatusSuccess)
                            MIOPEN_THROW("GemmWrw1x1_stride1 execution failure.");

                        if(handle.IsProfilingEnabled())
                            time += handle.GetKernelTime();
                    }

                    if(handle.IsProfilingEnabled())
                    {
                        handle.ResetKernelTime();
                        handle.AccumKernelTime(time);
                    }
                }
                else
                {
                    // dw = sum_over_batch(dy[i] * transpose(x[i])), i is batch id
                    const auto status = CallGemmStridedBatchedSequential(handle,
                                                                         gemm_desc,
                                                                         dy,
                                                                         0,
                                                                         x,
                                                                         0,
                                                                         dw,
                                                                         0,
                                                                         nullptr,
                                                                         GemmBackend_t::miopengemm,
                                                                         conv_params.gfx90aFp16alt);

                    if(status != miopenStatusSuccess)
                        MIOPEN_THROW("GemmWrw1x1_stride1 execution failure.");
                }
            }
        };
    };

    return solution;
#else
    std::ignore = problem;
    return {};
#endif
}

size_t GemmWrwUniversal::GetWorkspaceSize(const ExecutionContext& context,
                                          const conv::ProblemDescription& problem) const
{
#if MIOPEN_USE_GEMM
    auto& handle       = context.GetStream();
    const auto& dyDesc = problem.GetIn();
    const auto& dwDesc = problem.GetWeights();
    const auto& conv   = problem.GetConv();

    const auto spatial_dim = conv.GetSpatialDimension();
    const auto out_spatial = boost::adaptors::slice(dyDesc.GetLengths(), 2, 2 + spatial_dim);
    const auto wei_spatial = boost::adaptors::slice(dwDesc.GetLengths(), 2, 2 + spatial_dim);
    const auto wei_c       = dwDesc.GetLengths()[1];

    const auto ws_size = GetTypeSize(dyDesc.GetType()) * wei_c *
                         std::accumulate(out_spatial.begin(),
                                         out_spatial.end(),
                                         std::size_t(1),
                                         std::multiplies<std::size_t>()) *
                         std::accumulate(wei_spatial.begin(),
                                         wei_spatial.end(),
                                         std::size_t(1),
                                         std::multiplies<std::size_t>()) *
                         conv.group_count;

    if(ws_size > MAX_MEM_ALLOC_SZ)
        return 0;

    return ws_size;
#else
    std::ignore = context;
    std::ignore = problem;
    return 0;
#endif
}

bool GemmWrwUniversal::IsApplicable(const ExecutionContext& context,
                                    const conv::ProblemDescription& problem) const
{
#if MIOPEN_USE_GEMM
    if(!GemmWrwBase::IsApplicable(context, problem))
        return false;

    return !GemmWrw1x1_stride1{}.IsApplicable(context, problem) &&
           GetWorkspaceSize(context, problem) != 0;
#else
    std::ignore = context;
    std::ignore = problem;
    return false;
#endif
}

ConvSolution GemmWrwUniversal::GetSolution(const ExecutionContext& context,
                                           const conv::ProblemDescription& problem) const
{
#if MIOPEN_USE_GEMM
    const auto& dyDesc     = problem.GetIn();
    const auto& dwDesc     = problem.GetWeights();
    const auto& xDesc      = problem.GetOut();
    const auto& conv       = problem.GetConv();
    const auto group_count = conv.group_count;

    // dw = dy * transpose(Im2Col(x))
    const auto gemm_desc =
        group_count > 1 ? CreateGemmDescriptorGroupConvBwdWeight(dyDesc, xDesc, dwDesc, group_count)
                        : CreateGemmDescriptorConvBwdWeight(dyDesc, xDesc, dwDesc);

    const auto spatial_dims   = conv.GetSpatialDimension();
    const auto conv_pads      = conv.GetConvPads();
    const auto conv_strides   = conv.GetConvStrides();
    const auto conv_dilations = conv.GetConvDilations();
    const auto workspace_req  = GetWorkspaceSize(context, problem);
    const auto in_n           = xDesc.GetLengths()[0];
    const auto in_c           = xDesc.GetLengths()[1];
    const auto wei_k          = dwDesc.GetLengths()[0];

    const auto in_spatial_ =
        boost::adaptors::slice(xDesc.GetLengths(), 2, 2 + conv.GetSpatialDimension());
    const auto wei_spatial_ =
        boost::adaptors::slice(dwDesc.GetLengths(), 2, 2 + conv.GetSpatialDimension());
    const auto out_spatial_ =
        boost::adaptors::slice(dyDesc.GetLengths(), 2, 2 + conv.GetSpatialDimension());

    const auto in_spatial  = std::vector<std::size_t>(in_spatial_.begin(), in_spatial_.end());
    const auto wei_spatial = std::vector<std::size_t>(wei_spatial_.begin(), wei_spatial_.end());
    const auto out_spatial = std::vector<std::size_t>(out_spatial_.begin(), out_spatial_.end());

    const auto out_spatial_size = std::accumulate(
        out_spatial.begin(), out_spatial.end(), std::size_t(1), std::multiplies<std::size_t>());

    const auto in_spatial_size = std::accumulate(
        in_spatial.begin(), in_spatial.end(), std::size_t(1), std::multiplies<std::size_t>());

    auto solution         = ConvSolution{miopenStatusSuccess};
    solution.workspace_sz = workspace_req;

    solution.invoker_factory = [=](const std::vector<Kernel>&) {
        const bool time_precision = (!IsDisabled(MIOPEN_CONV_PRECISE_ROCBLAS_TIMING{}));

        return [=](const Handle& handle, const AnyInvokeParams& primitive_params) {
            const auto& conv_params    = primitive_params.CastTo<conv::WrWInvokeParams>();
            const auto& dy             = conv_params.tensors.dy;
            const auto& dyDesc_        = conv_params.tensors.dyDesc;
            const auto& dwDesc_        = conv_params.tensors.dwDesc;
            const auto& dw             = conv_params.tensors.dw;
            const auto& x              = conv_params.tensors.x;
            const auto& workspace      = conv_params.workSpace;
            const auto& workspace_size = conv_params.workSpaceSize;

            if(group_count > 1)
            {
                MIOPEN_LOG_FUNCTION("groupconv, non 1x1");
            }
            else
            {
                MIOPEN_LOG_FUNCTION("convolution, non 1x1");
            }

            if(workspace_req > 0 && (workspace == nullptr || workspace_size < workspace_req))
            {
                MIOPEN_THROW("Not enough workspace for GemmWrwUniversal. (" +
                             std::to_string(workspace_size) + " < " +
                             std::to_string(workspace_req) + ")");
            }

            if(conv_params.type == InvokeType::Run)
            {
                // Zeroing out the output buffer
                float zero = 0.0f;
                SetTensor(handle, dwDesc_, dw, &zero);

                float time = 0;

                for(std::size_t i = 0; i < in_n; i++)
                {
                    const auto out_offset = i * wei_k * out_spatial_size;
                    const auto in_offset  = i * in_c * in_spatial_size;

                    time += Im2ColGPU(handle,
                                      spatial_dims,
                                      x,
                                      in_offset,
                                      in_c,
                                      in_spatial,
                                      wei_spatial,
                                      out_spatial,
                                      conv_pads,
                                      conv_strides,
                                      conv_dilations,
                                      workspace,
                                      dyDesc_.GetType());

                    miopenStatus_t status;

                    if(group_count > 1)
                    {
                        status = CallGemmStridedBatched(handle,
                                                        gemm_desc,
                                                        dy,
                                                        out_offset,
                                                        workspace,
                                                        0,
                                                        dw,
                                                        0,
                                                        nullptr,
                                                        GemmBackend_t::miopentensile,
                                                        conv_params.gfx90aFp16alt);
                    }
                    else
                    {
                        // dw = dy * transpose(Im2Col(x))
                        status = CallGemm(handle,
                                          gemm_desc,
                                          dy,
                                          out_offset,
                                          workspace,
                                          0,
                                          dw,
                                          0,
                                          nullptr,
                                          GemmBackend_t::miopengemm,
                                          conv_params.gfx90aFp16alt);
                    }

                    if(status != miopenStatusSuccess)
                        MIOPEN_THROW("GemmWrw1x1_stride1 execution failure.");

                    // Update times for both the kernels
                    if(handle.IsProfilingEnabled())
                        time += handle.GetKernelTime();
                }

                if(handle.IsProfilingEnabled())
                {
                    handle.ResetKernelTime();
                    handle.AccumKernelTime(time);
                }
            }
            else
            {
                float time_im2col = 0;
                int in_offset     = 0;
                time_im2col       = Im2ColGPU(handle,
                                        spatial_dims,
                                        x,
                                        in_offset,
                                        in_c,
                                        in_spatial,
                                        wei_spatial,
                                        out_spatial,
                                        conv_pads,
                                        conv_strides,
                                        conv_dilations,
                                        workspace,
                                        dyDesc_.GetType());

                const auto status = CallGemmTimeMeasure(
                    handle,
                    gemm_desc,
                    dy,
                    0,
                    workspace,
                    0,
                    dw,
                    0,
                    nullptr,
                    time_precision,
                    group_count > 1 ? callGemmStridedBatched : callGemm,
                    group_count > 1 ? GemmBackend_t::miopentensile : GemmBackend_t::miopengemm,
                    conv_params.gfx90aFp16alt);

                if(status != miopenStatusSuccess)
                    MIOPEN_THROW("GemmWrw1x1_stride1 execution failure.");

                const auto gemm_time = handle.GetKernelTime();
                handle.ResetKernelTime();
                handle.AccumKernelTime(in_n * (time_im2col + gemm_time));
            }
        };
    };

    return solution;
#else
    std::ignore = context;
    std::ignore = problem;
    return {};
#endif
}

} // namespace solver
} // namespace miopen


/* +------------------------------------------------------------------------+
|                     Mobile Robot Programming Toolkit (MRPT)            |
|                          https://www.mrpt.org/                         |
|                                                                        |
| Copyright (c) 2005-2019, Individual contributors, see AUTHORS file     |
| See: https://www.mrpt.org/Authors - All rights reserved.               |
| Released under BSD License. See: https://www.mrpt.org/License          |
+------------------------------------------------------------------------+ */

#include "math-precomp.h"  // Precompiled headers

#include <mrpt/math/CMatrixFixed.h>
#include <Eigen/Dense>
#include "MatrixBase_impl.h"

// Template instantiations:
#define DO_MATFIXED_INSTANTIATION_NM(T_, N_, M_) \
	template class mrpt::math::MatrixBase<       \
		T_, mrpt::math::CMatrixFixed<T_, N_, M_>>;

#define DO_MATFIXED_INSTANTIATION(T_) DO_MATFIXED_INSTANTIATION_NM(T_, 6, 6)

DO_MATFIXED_INSTANTIATION(float);