Wednesday, September 28, 2016

MKL and C# for dummies (Part 3)




Fast Fourier Transform

This time I want to talk about Fast Fourier Transform functions and how to use these in C# properly.

Intel MKL team work mainly with C++ and Fortran. They didn’t dedicate a lot of effort toward C# so sometimes if you work in C# and you want to use MKL you face some difficulties.

As I said in the previous article about MKL, the first difficulty is how to declare the functions in C# code. For this I suggest a document from Intel: 33774-Using_Intel_MKL_IPP_NET_Framework.pdf. You can find it on line. It is not only a problem of declaration, but also a question of how to interpret the error messages, sometimes you don’t know what’s going on.

For example if you wrong something, you receive messages like these:
  •          "Inconsistent configuration parameter"
  •          "Functionality is not implemented".

Where is a tutorial that explain what I have wronged? You have to contact the Intel Support and be patient.

With a little help you can understand that the first error is because you are assigning a wrong value to a variable (DftiSetValue). The second one is because you had configured a “Descriptor” with the wrong parameters (DftiCreateDescriptor).

Then after completed a declarative part you can test something, for example I used this web site (http://www.sccon.ca/sccon/fft/fft3.htm) with the FFT input and output certified, as reference.

For some example of FFT usage this is the page: https://software.intel.com/en-us/node/471390.

The problem is that, those example are written in Fortran or C++ and contains some little imprecisions that can confuse you.

Let consider the paragraph: “One-dimensional Out-of-place FFT”. Where they perform a real to complex conjugate-even transform.

They use a real vector for the input and a real vector for the output. But the output is a complex vector! Also the vector size was wrong. These things and other little imprecisions make me lost a lot of time. But not desperate because I will provide you what you need to use FFT in C#.

There are some possible versions of DftiComputeForward() function that compute the FF Transform so at a beginning I adopted this one:

status = DftiComputeForward ( desc_handle, xre_in, xim_in , yre_out, yim_out )

It allows 2 real vectors in input and two real vectors for the output. These vectors represents real and imaginary part.

By the way, although I didn’t receive any error from the method execution the result was always zero! Again problems!

But as I always say: “it ain't over til it's over” I have tried another version of the function, this time with a input a complex vector, and output a complex vector. And like a magic this time worked.

status = DftiComputeForward( desc_handle, x_in, y_out )


So for your joy here the code:


using System;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security;

namespace mkl
{
    [SuppressUnmanagedCodeSecurity]
    public unsafe class MKLWrapper
    {

    
        //==================================================
        #region FFT CONSTANTS
        enum DFTI_CONFIG_PARAM
        {
            /* Domain for forward transform. No default value */
            DFTI_FORWARD_DOMAIN = 0,

            /* Dimensionality, or rank. No default value */
            DFTI_DIMENSION = 1,

            /* Length(s) of transform. No default value */
            DFTI_LENGTHS = 2,

            /* Floating point precision. No default value */
            DFTI_PRECISION = 3,

            /* Scale factor for forward transform [1.0] */
            DFTI_FORWARD_SCALE = 4,

            /* Scale factor for backward transform [1.0] */
            DFTI_BACKWARD_SCALE = 5,

            /* Exponent sign for forward transform [DFTI_NEGATIVE]  */
            /* DFTI_FORWARD_SIGN = 6, ## NOT IMPLEMENTED */

            /* Number of data sets to be transformed [1] */
            DFTI_NUMBER_OF_TRANSFORMS = 7,

            /* Storage of finite complex-valued sequences in complex domain
               [DFTI_COMPLEX_COMPLEX] */
            DFTI_COMPLEX_STORAGE = 8,

            /* Storage of finite real-valued sequences in real domain
               [DFTI_REAL_REAL] */
            DFTI_REAL_STORAGE = 9,

            /* Storage of finite complex-valued sequences in conjugate-even
               domain [DFTI_COMPLEX_REAL] */
            DFTI_CONJUGATE_EVEN_STORAGE = 10,

            /* Placement of result [DFTI_INPLACE] */
            DFTI_PLACEMENT = 11,

            /* Generalized strides for input data layout [tigth, row-major for
               C] */
            DFTI_INPUT_STRIDES = 12,

            /* Generalized strides for output data layout [tight, row-major
               for C] */
            DFTI_OUTPUT_STRIDES = 13,

            /* Distance between first input elements for multiple transforms
               [0] */
            DFTI_INPUT_DISTANCE = 14,

            /* Distance between first output elements for multiple transforms
               [0] */
            DFTI_OUTPUT_DISTANCE = 15,

            /* Effort spent in initialization [DFTI_MEDIUM] */
            /* DFTI_INITIALIZATION_EFFORT = 16, ## NOT IMPLEMENTED */

            /* Use of workspace during computation [DFTI_ALLOW] */
            DFTI_WORKSPACE = 17,

            /* Ordering of the result [DFTI_ORDERED] */
            DFTI_ORDERING = 18,

            /* Possible transposition of result [DFTI_NONE] */
            DFTI_TRANSPOSE = 19,

            /* User-settable descriptor name [""] */
            DFTI_DESCRIPTOR_NAME = 20, /* DEPRECATED */

            /* Packing format for DFTI_COMPLEX_REAL storage of finite
               conjugate-even sequences [DFTI_CCS_FORMAT] */
            DFTI_PACKED_FORMAT = 21,

            /* Commit status of the descriptor - R/O parameter */
            DFTI_COMMIT_STATUS = 22,

            /* Version string for this DFTI implementation - R/O parameter */
            DFTI_VERSION = 23,

            /* Ordering of the forward transform - R/O parameter */
            /* DFTI_FORWARD_ORDERING  = 24, ## NOT IMPLEMENTED */

            /* Ordering of the backward transform - R/O parameter */
            /* DFTI_BACKWARD_ORDERING = 25, ## NOT IMPLEMENTED */

            /* Number of user threads that share the descriptor [1] */
            DFTI_NUMBER_OF_USER_THREADS = 26,

            /* Limit the number of threads used by this descriptor [0 = don't care] */
            DFTI_THREAD_LIMIT = 27,

            /* Possible input data destruction [DFTI_AVOID = prevent input data]*/
            DFTI_DESTROY_INPUT = 28
        };

        enum DFTI_CONFIG_VALUE
        {
            /* DFTI_COMMIT_STATUS */
            DFTI_COMMITTED = 30,
            DFTI_UNCOMMITTED = 31,

            /* DFTI_FORWARD_DOMAIN */
            DFTI_COMPLEX = 32,
            DFTI_REAL = 33,
            /* DFTI_CONJUGATE_EVEN = 34,   ## NOT IMPLEMENTED */

            /* DFTI_PRECISION */
            DFTI_SINGLE = 35,
            DFTI_DOUBLE = 36,

            /* DFTI_FORWARD_SIGN */
            /* DFTI_NEGATIVE = 37,         ## NOT IMPLEMENTED */
            /* DFTI_POSITIVE = 38,         ## NOT IMPLEMENTED */

            /* DFTI_COMPLEX_STORAGE and DFTI_CONJUGATE_EVEN_STORAGE */
            DFTI_COMPLEX_COMPLEX = 39,
            DFTI_COMPLEX_REAL = 40,

            /* DFTI_REAL_STORAGE */
            DFTI_REAL_COMPLEX = 41,
            DFTI_REAL_REAL = 42,

            /* DFTI_PLACEMENT */
            DFTI_INPLACE = 43,          /* Result overwrites input */
            DFTI_NOT_INPLACE = 44,      /* Have another place for result */

            /* DFTI_INITIALIZATION_EFFORT */
            /* DFTI_LOW = 45,              ## NOT IMPLEMENTED */
            /* DFTI_MEDIUM = 46,           ## NOT IMPLEMENTED */
            /* DFTI_HIGH = 47,             ## NOT IMPLEMENTED */

            /* DFTI_ORDERING */
            DFTI_ORDERED = 48,
            DFTI_BACKWARD_SCRAMBLED = 49,
            /* DFTI_FORWARD_SCRAMBLED = 50, ## NOT IMPLEMENTED */

            /* Allow/avoid certain usages */
            DFTI_ALLOW = 51,            /* Allow transposition or workspace */
            DFTI_AVOID = 52,
            DFTI_NONE = 53,

            /* DFTI_PACKED_FORMAT (for storing congugate-even finite sequence
               in real array) */
            DFTI_CCS_FORMAT = 54,       /* Complex conjugate-symmetric */
            DFTI_PACK_FORMAT = 55,      /* Pack format for real DFT */
            DFTI_PERM_FORMAT = 56,      /* Perm format for real DFT */
            DFTI_CCE_FORMAT = 57        /* Complex conjugate-even */
        };

        #endregion

        private MKLWrapper()
        {
        }
    
        //==================================================
        #region FFT (DECLARATIONS)

        //DFTI_EXTERN char* DftiErrorMessage(MKL_LONG);
        [DllImport("mkl_rt.dll", ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)]
        internal static extern IntPtr DftiErrorMessage(
            long error_code
        );

        //DFTI_EXTERN MKL_LONG DftiCreateDescriptor(DFTI_DESCRIPTOR_HANDLE*,
        //                      enum DFTI_CONFIG_VALUE, /* precision */
        //                      enum DFTI_CONFIG_VALUE, /* domain */
        //                      MKL_LONG, ...);
        [DllImport("mkl_rt.dll", ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)]
        internal static extern int DftiCreateDescriptor(
            ref IntPtr handle_descriptor,
            int precision,
            int domain,
            int dime,
            int size
        );
        
        //DFTI_EXTERN MKL_LONG DftiCommitDescriptor(DFTI_DESCRIPTOR_HANDLE);
        [DllImport("mkl_rt.dll", ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)]
        internal static extern int DftiCommitDescriptor(
            IntPtr handle_descriptor
        );

        //DFTI_EXTERN MKL_LONG DftiSetValue(DFTI_DESCRIPTOR_HANDLE, enum DFTI_CONFIG_PARAM, ...);
        [DllImport("mkl_rt.dll", ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)]
        internal static extern int DftiSetValue(IntPtr handle_descriptor, int config_param, __arglist);

        //DFTI_EXTERN MKL_LONG DftiComputeBackward(DFTI_DESCRIPTOR_HANDLE, void*, ...);
        [DllImport("mkl_rt.dll", ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)]
        internal static extern int DftiComputeBackward(
            IntPtr handle_descriptor,
            [In] Complex[] x_in,
            [Out] Complex[] x_out
        );

        [DllImport("mkl_rt.dll", ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)]
        internal static extern int DftiComputeForward(
            IntPtr handle_descriptor,
            [In] Complex[] x_in,            
            [Out] Complex[] x_out
        );

        //DFTI_EXTERN MKL_LONG DftiFreeDescriptor(DFTI_DESCRIPTOR_HANDLE*);
        [DllImport("mkl_rt.dll", ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)]
        internal static extern int DftiFreeDescriptor(
            ref IntPtr handle_descriptor
        );
        #endregion


        //==================================================
        #region FFT (METHODS)
        public static string MKL_FFT_Error(int error, string sText = "")
        {
            if (error == 0)
                return "";

            IntPtr s = DftiErrorMessage(error);
            string sMsg = Marshal.PtrToStringAnsi(s);            
            throw new Exception(sText + sMsg);
        }


        public static Complex[] MKL_FFT(Complex[] in_matrix, double fScalingFactor = 1)
        {
            int status;
            IntPtr des_handle = IntPtr.Zero;

            Complex[] out_matrix = new Complex[in_matrix.Length];

            try
            {

                status = DftiCreateDescriptor(ref des_handle, (int)DFTI_CONFIG_VALUE.DFTI_DOUBLE, (int)DFTI_CONFIG_VALUE.DFTI_COMPLEX, 1, in_matrix.Length);
                MKL_FFT_Error(status, "DftiCreateDescriptor() ");

                status = DftiSetValue(des_handle, (int)DFTI_CONFIG_PARAM.DFTI_FORWARD_SCALE, __arglist(fScalingFactor));
                MKL_FFT_Error(status, "DftiSetValue() ");

                status = DftiSetValue(des_handle, (int)DFTI_CONFIG_PARAM.DFTI_PLACEMENT, __arglist((int)DFTI_CONFIG_VALUE.DFTI_NOT_INPLACE));
                MKL_FFT_Error(status, "DftiSetValue() ");

                status = DftiCommitDescriptor(des_handle); //Finalize the descriptor
                MKL_FFT_Error(status, "DftiCommitDescriptor() ");

                status = DftiComputeForward(des_handle, in_matrix, out_matrix); //Compute the Backward FFT
                MKL_FFT_Error(status, "DftiComputeForward() ");

                status = DftiFreeDescriptor(ref des_handle); //Free the descriptor
                MKL_FFT_Error(status, "DftiFreeDescriptor() ");
                

                return out_matrix;
            }
            catch (Exception ex)
            {
                if (des_handle != IntPtr.Zero)
                    DftiFreeDescriptor(ref des_handle); //Free the descriptor
                throw ex;
            }

        }

        public static Complex[] MKL_FFT_Inv(Complex[] in_matrix, double fScalingFactor = 1)
        {
            int status;
            IntPtr des_handle = IntPtr.Zero;

            Complex[] out_matrix = new Complex[in_matrix.Length];
            
            
            try
            {

                status = DftiCreateDescriptor(ref des_handle, (int)DFTI_CONFIG_VALUE.DFTI_DOUBLE, (int)DFTI_CONFIG_VALUE.DFTI_COMPLEX, 1, in_matrix.Length); 
                MKL_FFT_Error(status, "DftiCreateDescriptor() ");

                status = DftiSetValue(des_handle, (int)DFTI_CONFIG_PARAM.DFTI_BACKWARD_SCALE, __arglist(fScalingFactor));
                MKL_FFT_Error(status, "DftiSetValue() ");
                
                status = DftiSetValue(des_handle, (int)DFTI_CONFIG_PARAM.DFTI_PLACEMENT, __arglist((int)DFTI_CONFIG_VALUE.DFTI_NOT_INPLACE));
                MKL_FFT_Error(status, "DftiSetValue() ");

                status = DftiCommitDescriptor(des_handle); //Finalize the descriptor
                MKL_FFT_Error(status, "DftiCommitDescriptor() ");

                status = DftiComputeBackward(des_handle, in_matrix, out_matrix); //Compute the Backward FFT
                MKL_FFT_Error(status, "DftiComputeBackward() ");

                status = DftiFreeDescriptor(ref des_handle); //Free the descriptor


                return out_matrix;
            }
            catch (Exception ex)
            {
                if (des_handle != IntPtr.Zero)
                    DftiFreeDescriptor(ref des_handle); //Free the descriptor
                throw ex;
            }
        }
        #endregion

    }
}

Here the example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using mkl;
using System.Numerics;

namespace FFTTest
{
    class Program
    {

        #region  TEST INPUT MATRIX
        //http://www.sccon.ca/sccon/fft/fft3.htm
        //INPUT
        //REAL[0] = 1.000,   IMAG[0] = 0.000
        //REAL[1] = 0.000,   IMAG[1] = 0.000
        //REAL[2] = 0.000,   IMAG[2] = 0.000
        //REAL[3] = 0.000,   IMAG[3] = 0.000
        //REAL[4] = 0.000,   IMAG[4] = 0.000
        //REAL[5] = 0.000,   IMAG[5] = 0.000
        //REAL[6] = 0.000,   IMAG[6] = 0.000
        //REAL[7] = 0.000,   IMAG[7] = 0.000

        //RESULTS
        //REAL[0] = 0.125,   IMAG[0] = 0.000
        //REAL[1] = 0.125,   IMAG[1] = 0.000
        //REAL[2] = 0.125,   IMAG[2] = 0.000
        //REAL[3] = 0.125,   IMAG[3] = 0.000
        //REAL[4] = 0.125,   IMAG[4] = 0.000
        //REAL[5] = 0.125,   IMAG[5] = 0.000
        //REAL[6] = 0.125,   IMAG[6] = 0.000
        //REAL[7] = 0.125,   IMAG[7] = 0.000
        #endregion


        static void Main(string[] args)
        {

            Complex[] A = new Complex[8];
            Complex[] B = null;
            Complex[] C = null;


            A[0] = new Complex(0, 0);
            A[1] = new Complex(1, 0);
            A[2] = new Complex(0, 0);
            A[3] = new Complex(0, 0);
            A[4] = new Complex(0, 0);
            A[5] = new Complex(0, 0);
            A[6] = new Complex(0, 0);
            A[7] = new Complex(0, 0);


            try
            {
                DebugMatrix(A, "A:");

                B = MKLWrapper.MKL_FFT(A, 1.0f/ A.Length);
                DebugMatrix(B, "FFT() B:");

                C = MKLWrapper.MKL_FFT_Inv(B); //FFT Backward
                DebugMatrix(C, "iFFT() C:");

                //C = MKLWrapper.MKL_FFT(B);
                //DebugMatrix(lstResults, C, "A:");


                //MessageBox.Show("Operation completed succesfully!");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error: " + ex.Message);
            }

            Console.ReadLine();
        }



        public static void DebugMatrix(Complex[] a, string sCaption, int iRowLimit = 10)
        {
            if (a == null) return;

            string sLine = "";
            Console.WriteLine("");
            Console.WriteLine(sCaption);
            int iDime1 = a.GetUpperBound(0);
            int iStart1 = a.GetLowerBound(0);

            Console.WriteLine("Size: " + a.GetLength(0).ToString());
            for (int i = iStart1; i <= iDime1; i++)
            {
                sLine = i.ToString() + "> ";
                sLine += a[i].ToString("0.###E+000") + "\t";
                //sLine += a[i, j].ToString("") + "\t";
                Console.WriteLine(sLine);
                if (i > iRowLimit)
                    break;
            }
            Console.WriteLine("");
            Console.WriteLine("");
        }
    }
}


Part 1 | Part 2 | Part 3 | Part 4

No comments :

Post a Comment