File: UnitTests.cs
Web Access
Project: src\test\Microsoft.ML.CpuMath.UnitTests\Microsoft.ML.CpuMath.UnitTests.csproj (Microsoft.ML.CpuMath.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.ML.Internal.CpuMath;
using Microsoft.ML.TestFramework;
using Xunit;
using Xunit.Abstractions;
 
#if NETFRAMEWORK
using RemoteOptions = Microsoft.ML.TestFramework.RemoteInvokeOptions;
#else
using RemoteOptions = Microsoft.DotNet.RemoteExecutor.RemoteInvokeOptions;
#endif
 
namespace Microsoft.ML.CpuMath.UnitTests
{
    public class CpuMathUtilsUnitTests : BaseTestClass
    {
        private static readonly float[][] _testArrays;
        private static readonly int[] _testIndexArray;
        private static readonly AlignedArray[] _testMatrices;
        private static readonly AlignedArray[] _testSrcVectors;
        private static readonly AlignedArray[] _testDstVectors;
        private static readonly int _vectorAlignment = CpuMathUtils.GetVectorAlignment();
        private static readonly FloatEqualityComparer _comparer;
        private static readonly FloatEqualityComparerForMatMul _matMulComparer;
        private static readonly string _defaultMode = "defaultMode";
        private static Dictionary<string, string> _disableAvxEnvironmentVariables;
        private static Dictionary<string, string> _disableAvxAndSseEnvironmentVariables;
        private static readonly string _disableAvx = "COMPlus_EnableAVX";
        private static readonly string _disableSse = "COMPlus_EnableSSE";
        private static readonly string _disableAvxAndSse = "COMPlus_EnableHWIntrinsic";
        public static bool IsNetCore => Environment.Version.Major >= 5 || RuntimeInformation.FrameworkDescription.StartsWith(".NET Core", StringComparison.OrdinalIgnoreCase);
        public static bool IsNetCore2OrOlder => Environment.Version.Major == 4 && Environment.Version.Minor == 0;
        public static bool SkipAvxSse => RuntimeInformation.ProcessArchitecture == Architecture.Arm || RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
 
        static CpuMathUtilsUnitTests()
        {
            // Padded array whose length is a multiple of 4
            float[] testArray1 = new float[32] { 1.96f, -2.38f, -9.76f, 13.84f, -106.37f, -26.93f, 32.45f, 3.29f, 1.96f, -2.38f, -9.76f, 13.84f, -106.37f, -26.93f, 32.45f, 3.29f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 10f, 11f, 12f, 13f, 14f, 15f, 16f };
            // Unpadded array whose length is not a multiple of 4.
            float[] testArray2 = new float[30] { 1.96f, -2.38f, -9.76f, 13.84f, -106.37f, -26.93f, 32.45f, 3.29f, 1.96f, -2.38f, -9.76f, 13.84f, -106.37f, -26.93f, 32.45f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 10f, 11f, 12f, 13f, 14f, 15f };
            // Small Input Size Array
            float[] testArray3 = new float[15] { 1.96f, -2.38f, -9.76f, 13.84f, -106.37f, -26.93f, 32.45f, 3.29f, 1.96f, -2.38f, -9.76f, 13.84f, -106.37f, -26.93f, 32.45f };
            _testArrays = new float[][] { testArray1, testArray2, testArray3 };
            _testIndexArray = new int[18] { 0, 2, 5, 6, 8, 11, 12, 13, 14, 16, 18, 21, 22, 24, 26, 27, 28, 29 };
            _comparer = new FloatEqualityComparer();
            _matMulComparer = new FloatEqualityComparerForMatMul();
 
            // Padded matrices whose dimensions are multiples of 8
            float[] testMatrix1 = new float[8 * 8] { 1.96f, -2.38f, -9.76f, 13.84f, -106.37f, -26.93f, 32.45f, 3.29f,
                                                        1.96f, -2.38f, -9.76f, 13.84f, -106.37f, -26.93f, 32.45f, 3.29f,
                                                        1.96f, -2.38f, -9.76f, 13.84f, -106.37f, -26.93f, 32.45f, 3.29f,
                                                        1.96f, -2.38f, -9.76f, 13.84f, -106.37f, -26.93f, 32.45f, 3.29f,
                                                        1.96f, -2.38f, -9.76f, 13.84f, -106.37f, -26.93f, 32.45f, 3.29f,
                                                        1.96f, -2.38f, -9.76f, 13.84f, -106.37f, -26.93f, 32.45f, 3.29f,
                                                        1.96f, -2.38f, -9.76f, 13.84f, -106.37f, -26.93f, 32.45f, 3.29f,
                                                        1.96f, -2.38f, -9.76f, 13.84f, -106.37f, -26.93f, 32.45f, 3.29f };
            float[] testMatrix2 = new float[8 * 16];
 
            for (int i = 0; i < testMatrix2.Length; i++)
            {
                testMatrix2[i] = i + 1;
            }
 
            AlignedArray testMatrixAligned1 = new AlignedArray(8 * 8, _vectorAlignment);
            AlignedArray testMatrixAligned2 = new AlignedArray(8 * 16, _vectorAlignment);
            testMatrixAligned1.CopyFrom(testMatrix1);
            testMatrixAligned2.CopyFrom(testMatrix2);
 
            _testMatrices = new AlignedArray[] { testMatrixAligned1, testMatrixAligned2 };
 
            // Padded source vectors whose dimensions are multiples of 8
            float[] testSrcVector1 = new float[8] { 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f };
            float[] testSrcVector2 = new float[16] { 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 10f, 11f, 12f, 13f, 14f, 15f, 16f };
 
            AlignedArray testSrcVectorAligned1 = new AlignedArray(8, _vectorAlignment);
            AlignedArray testSrcVectorAligned2 = new AlignedArray(16, _vectorAlignment);
            testSrcVectorAligned1.CopyFrom(testSrcVector1);
            testSrcVectorAligned2.CopyFrom(testSrcVector2);
 
            _testSrcVectors = new AlignedArray[] { testSrcVectorAligned1, testSrcVectorAligned2 };
 
            // Padded destination vectors whose dimensions are multiples of 8
            float[] testDstVector1 = new float[8] { 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f };
            float[] testDstVector2 = new float[16] { 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 10f, 11f, 12f, 13f, 14f, 15f };
 
            AlignedArray testDstVectorAligned1 = new AlignedArray(8, _vectorAlignment);
            AlignedArray testDstVectorAligned2 = new AlignedArray(16, _vectorAlignment);
            testDstVectorAligned1.CopyFrom(testDstVector1);
            testDstVectorAligned2.CopyFrom(testDstVector2);
 
            _testDstVectors = new AlignedArray[] { testDstVectorAligned1, testDstVectorAligned2 };
 
            if ((SkipAvxSse || IsNetCore) && !IsNetCore2OrOlder)
            {
                _disableAvxEnvironmentVariables = new Dictionary<string, string>()
                {
                    { _disableAvx , "0" }
                };
 
                _disableAvxAndSseEnvironmentVariables = new Dictionary<string, string>()
                {
                    { _disableAvx , "0" },
                    { _disableSse , "0" }
                };
            }
        }
 
        public CpuMathUtilsUnitTests(ITestOutputHelper output) : base(output)
        {
        }
 
        private static void CheckProperFlag(string mode)
        {
#if NETCOREAPP3_1_OR_GREATER
            if (mode == _defaultMode)
            {
                Assert.True(System.Runtime.Intrinsics.X86.Avx.IsSupported);
                Assert.True(System.Runtime.Intrinsics.X86.Sse.IsSupported);
            }
            else if (mode == _disableAvx)
            {
                Assert.False(System.Runtime.Intrinsics.X86.Avx.IsSupported);
                Assert.True(System.Runtime.Intrinsics.X86.Sse.IsSupported);
            }
            else if (mode == _disableAvxAndSse)
            {
                Assert.False(System.Runtime.Intrinsics.X86.Avx.IsSupported);
                Assert.False(System.Runtime.Intrinsics.X86.Sse.IsSupported);
            }
#endif
        }
 
        public static TheoryData<string, string, Dictionary<string, string>> AddData()
        {
            if (SkipAvxSse)
            {
                return new TheoryData<string, string, Dictionary<string, string>>()
                {
                    { _disableAvxAndSse, "0", _disableAvxAndSseEnvironmentVariables },
                    { _disableAvxAndSse, "1", _disableAvxAndSseEnvironmentVariables },
                };
            }
            else
            {
                return new TheoryData<string, string, Dictionary<string, string>>()
                {
                    {  _defaultMode, "0", null },
                    {  _defaultMode, "1", null },
                    {  _defaultMode, "2", null },
#if NETCOREAPP3_1_OR_GREATER
                    { _disableAvx, "0", _disableAvxEnvironmentVariables },
                    { _disableAvx, "1", _disableAvxEnvironmentVariables },
 
                    { _disableAvxAndSse, "0", _disableAvxAndSseEnvironmentVariables },
                    { _disableAvxAndSse, "1", _disableAvxAndSseEnvironmentVariables },
#endif
                };
            }
        }
 
        public static TheoryData<string, string, string, Dictionary<string, string>> AddScaleData()
        {
            if (SkipAvxSse)
            {
                return new TheoryData<string, string, string, Dictionary<string, string>>()
                {
                    { _disableAvxAndSse, "0", "1.7", _disableAvxAndSseEnvironmentVariables },
                    { _disableAvxAndSse, "1", "1.7", _disableAvxAndSseEnvironmentVariables },
                    { _disableAvxAndSse, "0", "-1.7", _disableAvxAndSseEnvironmentVariables },
                    { _disableAvxAndSse, "1", "-1.7", _disableAvxAndSseEnvironmentVariables },
                };
            }
            else
            {
                return new TheoryData<string, string, string, Dictionary<string, string>>()
                {
                    { _defaultMode, "0", "1.7", null },
                    { _defaultMode, "1", "1.7", null },
                    { _defaultMode, "2", "1.7", null },
                    { _defaultMode, "0", "-1.7", null },
                    { _defaultMode, "1", "-1.7", null },
                    { _defaultMode, "2", "-1.7", null },
#if NETCOREAPP3_1_OR_GREATER
                    {  _disableAvx, "0", "1.7", _disableAvxEnvironmentVariables },
                    {  _disableAvx, "1", "1.7", _disableAvxEnvironmentVariables },
                    {  _disableAvx, "0", "-1.7", _disableAvxEnvironmentVariables },
                    {  _disableAvx, "1", "-1.7", _disableAvxEnvironmentVariables },
 
                    { _disableAvxAndSse, "0", "1.7", _disableAvxAndSseEnvironmentVariables },
                    { _disableAvxAndSse, "1", "1.7", _disableAvxAndSseEnvironmentVariables },
                    { _disableAvxAndSse, "0", "-1.7", _disableAvxAndSseEnvironmentVariables },
                    { _disableAvxAndSse, "1", "-1.7", _disableAvxAndSseEnvironmentVariables },
#endif
                };
            }
        }
 
        public static TheoryData<string, string, string, string, Dictionary<string, string>> MatMulData()
        {
            if (SkipAvxSse)
            {
                return new TheoryData<string, string, string, string, Dictionary<string, string>>()
                {
                    { _disableAvxAndSse , "0", "0", "0", _disableAvxAndSseEnvironmentVariables },
                    { _disableAvxAndSse , "1", "1", "0", _disableAvxAndSseEnvironmentVariables },
                    { _disableAvxAndSse , "1", "0", "1", _disableAvxAndSseEnvironmentVariables },
                };
            }
            else
            {
                return new TheoryData<string, string, string, string, Dictionary<string, string>>()
                {
                    { _defaultMode, "0", "0", "0", null },
                    { _defaultMode, "1", "1", "0", null },
                    { _defaultMode, "1", "0", "1", null },
#if NETCOREAPP3_1_OR_GREATER
                    { _disableAvx, "0", "0", "0", _disableAvxEnvironmentVariables },
                    { _disableAvx, "1", "1", "0", _disableAvxEnvironmentVariables },
                    { _disableAvx, "1", "0", "1", _disableAvxEnvironmentVariables },
 
                    { _disableAvxAndSse , "0", "0", "0", _disableAvxAndSseEnvironmentVariables },
                    { _disableAvxAndSse , "1", "1", "0", _disableAvxAndSseEnvironmentVariables },
                    { _disableAvxAndSse , "1", "0", "1", _disableAvxAndSseEnvironmentVariables },
#endif
                };
            }
        }
 
        [Theory]
        [MemberData(nameof(MatMulData))]
        public void MatMulTest(string mode, string matTest, string srcTest, string dstTest, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1, arg2, arg3) =>
 
            {
                CheckProperFlag(arg0);
                AlignedArray mat = _testMatrices[int.Parse(arg1)];
                AlignedArray src = _testSrcVectors[int.Parse(arg2)];
                AlignedArray dst = _testDstVectors[int.Parse(arg3)];
 
                float[] expected = new float[dst.Size];
                for (int i = 0; i < dst.Size; i++)
                {
                    float dotProduct = 0;
                    for (int j = 0; j < src.Size; j++)
                    {
                        dotProduct += mat[i * src.Size + j] * src[j];
                    }
                    expected[i] = dotProduct;
                }
 
                CpuMathUtils.MatrixTimesSource(false, mat, src, dst, dst.Size);
                float[] actual = new float[dst.Size];
                dst.CopyTo(actual, 0, dst.Size);
                Assert.Equal(expected, actual, _matMulComparer);
                return RemoteExecutor.SuccessExitCode;
            }, mode, matTest, srcTest, dstTest, options);
 
        }
 
        [Theory]
        [MemberData(nameof(MatMulData))]
        public void MatMulTranTest(string mode, string matTest, string srcTest, string dstTest, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1, arg2, arg3) =>
            {
                CheckProperFlag(arg0);
                AlignedArray mat = _testMatrices[int.Parse(arg1)];
                AlignedArray src = _testSrcVectors[int.Parse(arg2)];
                AlignedArray dst = _testDstVectors[int.Parse(arg3)];
 
                float[] expected = new float[dst.Size];
                for (int i = 0; i < dst.Size; i++)
                {
                    float dotProduct = 0;
                    for (int j = 0; j < src.Size; j++)
                    {
                        dotProduct += mat[j * dst.Size + i] * src[j];
                    }
                    expected[i] = dotProduct;
                }
 
                CpuMathUtils.MatrixTimesSource(true, mat, src, dst, src.Size);
                float[] actual = new float[dst.Size];
                dst.CopyTo(actual, 0, dst.Size);
                Assert.Equal(expected, actual, _matMulComparer);
                return RemoteExecutor.SuccessExitCode;
            }, mode, matTest, srcTest, dstTest, options);
        }
 
        [Theory]
        [MemberData(nameof(MatMulData))]
        public void MatTimesSrcSparseTest(string mode, string matTest, string srcTest, string dstTest, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1, arg2, arg3) =>
            {
                CheckProperFlag(arg0);
                AlignedArray mat = _testMatrices[int.Parse(arg1)];
                AlignedArray src = _testSrcVectors[int.Parse(arg2)];
                AlignedArray dst = _testDstVectors[int.Parse(arg3)];
                int[] idx = _testIndexArray;
 
                float[] expected = new float[dst.Size];
                int limit = (int.Parse(arg2) == 0) ? 4 : 9;
                for (int i = 0; i < dst.Size; i++)
                {
                    float dotProduct = 0;
                    for (int j = 0; j < limit; j++)
                    {
                        int col = idx[j];
                        dotProduct += mat[i * src.Size + col] * src[col];
                    }
                    expected[i] = dotProduct;
                }
 
                CpuMathUtils.MatrixTimesSource(mat, idx, src, 0, 0, limit, dst, dst.Size);
                float[] actual = new float[dst.Size];
                dst.CopyTo(actual, 0, dst.Size);
                Assert.Equal(expected, actual, _matMulComparer);
                return RemoteExecutor.SuccessExitCode;
 
            }, mode, matTest, srcTest, dstTest, options);
        }
 
        [Theory]
        [MemberData(nameof(AddScaleData))]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters", Justification = "<Pending>")]
        public void AddScalarUTest(string mode, string test, string scale, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1, arg2) =>
            {
                CheckProperFlag(arg0);
                float defaultScale = float.Parse(arg2, CultureInfo.InvariantCulture);
                float[] dst = (float[])_testArrays[int.Parse(arg1)].Clone();
                float[] expected = (float[])dst.Clone();
 
                for (int i = 0; i < expected.Length; i++)
                {
                    expected[i] += defaultScale;
                }
 
                CpuMathUtils.Add(defaultScale, dst);
                var actual = dst;
                Assert.Equal(expected, actual, _comparer);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, scale, options);
        }
 
        [Theory]
        [MemberData(nameof(AddScaleData))]
        public void ScaleTest(string mode, string test, string scale, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1, arg2) =>
            {
                CheckProperFlag(arg0);
                float defaultScale = float.Parse(arg2, CultureInfo.InvariantCulture);
                float[] dst = (float[])_testArrays[int.Parse(arg1)].Clone();
                float[] expected = (float[])dst.Clone();
 
                for (int i = 0; i < expected.Length; i++)
                {
                    expected[i] *= defaultScale;
                }
 
                CpuMathUtils.Scale(defaultScale, dst);
                var actual = dst;
                Assert.Equal(expected, actual, _comparer);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, scale, options);
        }
 
        [Theory]
        [MemberData(nameof(AddScaleData))]
        public void ScaleSrcUTest(string mode, string test, string scale, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1, arg2) =>
            {
                CheckProperFlag(arg0);
                float defaultScale = float.Parse(arg2, CultureInfo.InvariantCulture);
                float[] src = (float[])_testArrays[int.Parse(arg1)].Clone();
                float[] dst = (float[])src.Clone();
                float[] expected = (float[])dst.Clone();
 
                for (int i = 0; i < expected.Length; i++)
                {
                    expected[i] *= defaultScale;
                }
 
                CpuMathUtils.Scale(defaultScale, src, dst, dst.Length);
                var actual = dst;
                Assert.Equal(expected, actual, _comparer);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, scale, options);
        }
 
        [Theory]
        [MemberData(nameof(AddScaleData))]
        public void ScaleAddUTest(string mode, string test, string scale, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1, arg2) =>
            {
                CheckProperFlag(arg0);
                float defaultScale = float.Parse(arg2, CultureInfo.InvariantCulture);
                float[] dst = (float[])_testArrays[int.Parse(arg1)].Clone();
                float[] expected = (float[])dst.Clone();
 
                for (int i = 0; i < expected.Length; i++)
                {
                    expected[i] = defaultScale * (dst[i] + defaultScale);
                }
 
                CpuMathUtils.ScaleAdd(defaultScale, defaultScale, dst);
                var actual = dst;
                Assert.Equal(expected, actual, _comparer);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, scale, options);
 
        }
 
        [Theory]
        [MemberData(nameof(AddScaleData))]
        public void AddScaleUTest(string mode, string test, string scale, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1, arg2) =>
            {
                CheckProperFlag(arg0);
                float defaultScale = float.Parse(arg2, CultureInfo.InvariantCulture);
                float[] src = (float[])_testArrays[int.Parse(arg1)].Clone();
                float[] dst = (float[])src.Clone();
                float[] expected = (float[])dst.Clone();
 
                for (int i = 0; i < expected.Length; i++)
                {
                    expected[i] *= (1 + defaultScale);
                }
 
                CpuMathUtils.AddScale(defaultScale, src, dst, dst.Length);
                var actual = dst;
                Assert.Equal(expected, actual, _comparer);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, scale, options);
        }
 
        [Theory]
        [MemberData(nameof(AddScaleData))]
        public void AddScaleSUTest(string mode, string test, string scale, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1, arg2) =>
            {
                CheckProperFlag(arg0);
                float defaultScale = float.Parse(arg2, CultureInfo.InvariantCulture);
                float[] src = (float[])_testArrays[int.Parse(arg1)].Clone();
                float[] dst = (float[])src.Clone();
                int[] idx = _testIndexArray;
                int limit = int.Parse(arg1) == 2 ? 9 : 18;
                float[] expected = (float[])dst.Clone();
 
                CpuMathUtils.AddScale(defaultScale, src, idx, dst, limit);
                for (int i = 0; i < limit; i++)
                {
                    int index = idx[i];
                    expected[index] += defaultScale * src[i];
                }
 
                Assert.Equal(expected, dst, _comparer);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, scale, options);
        }
 
        [Theory]
        [MemberData(nameof(AddScaleData))]
        public void AddScaleCopyUTest(string mode, string test, string scale, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1, arg2) =>
            {
                CheckProperFlag(arg0);
                float defaultScale = float.Parse("1.7", CultureInfo.InvariantCulture);
                float[] src = (float[])_testArrays[int.Parse("0")].Clone();
                float[] dst = (float[])src.Clone();
                float[] result = (float[])dst.Clone();
                float[] expected = (float[])dst.Clone();
 
                for (int i = 0; i < expected.Length; i++)
                {
                    expected[i] *= (1 + defaultScale);
                }
 
                CpuMathUtils.AddScaleCopy(defaultScale, src, dst, result, dst.Length);
                var actual = result;
                Assert.Equal(expected, actual, _comparer);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, scale, options);
        }
 
        [Theory]
        [MemberData(nameof(AddData))]
        public void AddUTest(string mode, string test, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1) =>
            {
                CheckProperFlag(arg0);
                float[] src = (float[])_testArrays[int.Parse(arg1)].Clone();
                float[] dst = (float[])src.Clone();
                float[] expected = (float[])src.Clone();
 
                // Ensures src and dst are different arrays
                for (int i = 0; i < dst.Length; i++)
                {
                    dst[i] += 1;
                }
 
                for (int i = 0; i < expected.Length; i++)
                {
                    expected[i] = 2 * expected[i] + 1;
                }
 
                CpuMathUtils.Add(src, dst, dst.Length);
                var actual = dst;
                Assert.Equal(expected, actual, _comparer);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, options);
 
        }
 
        [Theory]
        [MemberData(nameof(AddData))]
        public void AddSUTest(string mode, string test, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1) =>
            {
                CheckProperFlag(arg0);
                float[] src = (float[])_testArrays[int.Parse(arg1)].Clone();
                float[] dst = (float[])src.Clone();
                int[] idx = _testIndexArray;
                int limit = int.Parse(arg1) == 2 ? 9 : 18;
                float[] expected = (float[])dst.Clone();
 
                for (int i = 0; i < limit; i++)
                {
                    int index = idx[i];
                    expected[index] += src[i];
                }
 
                CpuMathUtils.Add(src, idx, dst, limit);
                var actual = dst;
                Assert.Equal(expected, actual, _comparer);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, options);
        }
 
        [Theory]
        [MemberData(nameof(AddData))]
        public void MulElementWiseUTest(string mode, string test, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1) =>
            {
                CheckProperFlag(arg1);
                float[] src1 = (float[])_testArrays[int.Parse(arg1)].Clone();
                float[] src2 = (float[])src1.Clone();
                float[] dst = (float[])src1.Clone();
 
                // Ensures src1 and src2 are different arrays
                for (int i = 0; i < src2.Length; i++)
                {
                    src2[i] += 1;
                }
 
                float[] expected = (float[])src1.Clone();
 
                for (int i = 0; i < expected.Length; i++)
                {
                    expected[i] *= (1 + expected[i]);
                }
 
                CpuMathUtils.MulElementWise(src1, src2, dst, dst.Length);
                var actual = dst;
                Assert.Equal(expected, actual, _comparer);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, options);
        }
 
        [Theory]
        [MemberData(nameof(AddData))]
        public void SumTest(string mode, string test, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1) =>
            {
                CheckProperFlag(arg0);
                float[] src = (float[])_testArrays[int.Parse(arg1)].Clone();
                float expected = 0;
                for (int i = 0; i < src.Length; i++)
                {
                    expected += src[i];
                }
 
                var actual = CpuMathUtils.Sum(src);
                Assert.Equal((double)expected, (double)actual, 0.01);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, options);
        }
 
        [Theory]
        [MemberData(nameof(AddData))]
        public void SumSqUTest(string mode, string test, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1) =>
            {
                CheckProperFlag(arg0);
                float[] src = (float[])_testArrays[int.Parse(arg1)].Clone();
                float expected = 0;
                for (int i = 0; i < src.Length; i++)
                {
                    expected += src[i] * src[i];
                }
 
                var actual = CpuMathUtils.SumSq(src);
                Assert.Equal((double)expected, (double)actual, 0.01);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, options);
        }
 
        [Theory]
        [MemberData(nameof(AddScaleData))]
        public void SumSqDiffUTest(string mode, string test, string scale, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1, arg2) =>
            {
                CheckProperFlag(arg0);
                float defaultScale = float.Parse(arg2, CultureInfo.InvariantCulture);
                float[] src = (float[])_testArrays[int.Parse(arg1)].Clone();
                var actual = CpuMathUtils.SumSq(defaultScale, src);
 
                float expected = 0;
                for (int i = 0; i < src.Length; i++)
                {
                    expected += (src[i] - defaultScale) * (src[i] - defaultScale);
                }
 
                Assert.Equal((double)expected, (double)actual, 0.1);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, scale, options);
        }
 
        [Theory]
        [MemberData(nameof(AddData))]
        public void SumAbsUTest(string mode, string test, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1) =>
            {
                CheckProperFlag(arg0);
                float[] src = (float[])_testArrays[int.Parse(arg1)].Clone();
                float expected = 0;
                for (int i = 0; i < src.Length; i++)
                {
                    expected += Math.Abs(src[i]);
                }
 
                var actual = CpuMathUtils.SumAbs(src);
                Assert.Equal((double)expected, (double)actual, 0.01);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, options);
        }
 
        [Theory]
        [MemberData(nameof(AddScaleData))]
        public void SumAbsDiffUTest(string mode, string test, string scale, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1, arg2) =>
            {
                CheckProperFlag(arg0);
                float defaultScale = float.Parse(arg2, CultureInfo.InvariantCulture);
                float[] src = (float[])_testArrays[int.Parse(arg1)].Clone();
                var actual = CpuMathUtils.SumAbs(defaultScale, src);
 
                float expected = 0;
                for (int i = 0; i < src.Length; i++)
                {
                    expected += Math.Abs(src[i] - defaultScale);
                }
 
                Assert.Equal((double)expected, (double)actual, 0.01);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, scale, options);
        }
 
        [Theory]
        [MemberData(nameof(AddData))]
        public void MaxAbsUTest(string mode, string test, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1) =>
            {
                CheckProperFlag(arg0);
                float[] src = (float[])_testArrays[int.Parse(arg1)].Clone();
                var actual = CpuMathUtils.MaxAbs(src);
 
                float expected = 0;
                for (int i = 0; i < src.Length; i++)
                {
                    float abs = Math.Abs(src[i]);
                    if (abs > expected)
                    {
                        expected = abs;
                    }
                }
 
                Assert.Equal((double)expected, (double)actual, 0.01);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, options);
        }
 
        [Theory]
        [MemberData(nameof(AddScaleData))]
        public void MaxAbsDiffUTest(string mode, string test, string scale, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1, arg2) =>
            {
                CheckProperFlag(arg0);
                float defaultScale = float.Parse(arg2, CultureInfo.InvariantCulture);
                float[] src = (float[])_testArrays[int.Parse(arg1)].Clone();
                var actual = CpuMathUtils.MaxAbsDiff(defaultScale, src);
 
                float expected = 0;
                for (int i = 0; i < src.Length; i++)
                {
                    float abs = Math.Abs(src[i] - defaultScale);
                    if (abs > expected)
                    {
                        expected = abs;
                    }
                }
                Assert.Equal((double)expected, (double)actual, 0.01);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, scale, options);
        }
 
        [Theory]
        [MemberData(nameof(AddData))]
        public void DotUTest(string mode, string test, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1) =>
            {
                CheckProperFlag(arg0);
                float[] src = (float[])_testArrays[int.Parse(arg1)].Clone();
                float[] dst = (float[])src.Clone();
 
                for (int i = 0; i < dst.Length; i++)
                {
                    dst[i] += 1;
                }
 
                float expected = 0;
                for (int i = 0; i < dst.Length; i++)
                {
                    expected += src[i] * dst[i];
                }
 
                var actual = CpuMathUtils.DotProductDense(src, dst, dst.Length);
                Assert.Equal((double)expected, (double)actual, 0.1);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, options);
        }
 
        [Theory]
        [MemberData(nameof(AddData))]
        public void DotSUTest(string mode, string test, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1) =>
            {
                CheckProperFlag(arg0);
                float[] src = (float[])_testArrays[int.Parse(arg1)].Clone();
                float[] dst = (float[])src.Clone();
                int[] idx = _testIndexArray;
                int limit = int.Parse(arg1) == 2 ? 9 : 18;
 
                // Ensures src and dst are different arrays
                for (int i = 0; i < dst.Length; i++)
                {
                    dst[i] += 1;
                }
 
                float expected = 0;
                for (int i = 0; i < limit; i++)
                {
                    int index = idx[i];
                    expected += src[index] * dst[i];
                }
 
                var actual = CpuMathUtils.DotProductSparse(src, dst, idx, limit);
                Assert.Equal((double)expected, (double)actual, 0.01);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, options);
        }
 
        [Theory]
        [MemberData(nameof(AddData))]
        public void Dist2Test(string mode, string test, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1) =>
            {
                CheckProperFlag(arg0);
                float[] src = (float[])_testArrays[int.Parse(arg1)].Clone();
                float[] dst = (float[])src.Clone();
 
                // Ensures src and dst are different arrays
                for (int i = 0; i < dst.Length; i++)
                {
                    dst[i] += 1;
                }
 
                float expected = 0;
                for (int i = 0; i < dst.Length; i++)
                {
                    float distance = src[i] - dst[i];
                    expected += distance * distance;
                }
 
                var actual = CpuMathUtils.L2DistSquared(src, dst, dst.Length);
                Assert.Equal((double)expected, (double)actual, 0);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, options);
        }
 
        [Theory]
        [InlineData(0, new int[] { 0, 2, 5, 6 }, new float[] { 0f, 2f, 0f, 4f, 5f, 0f, 0f, 8f })]
        [InlineData(1, new int[] { 0, 2, 5, 6, 8, 11, 12, 13, 14 }, new float[] { 0f, 2f, 0f, 4f, 5f, 0f, 0f, 8f, 0f, 10f, 11f, 0f, 0f, 0f, 0f, 16f })]
        public void ZeroItemsUTest(int test, int[] idx, float[] expected)
        {
            AlignedArray src = new AlignedArray(8 + 8 * test, _vectorAlignment);
            src.CopyFrom(_testSrcVectors[test]);
 
            CpuMathUtils.ZeroMatrixItems(src, src.Size, src.Size, idx);
            float[] actual = new float[src.Size];
            src.CopyTo(actual, 0, src.Size);
            Assert.Equal(expected, actual, _comparer);
        }
 
        [Theory]
        [InlineData(0, new int[] { 0, 2, 5 }, new float[] { 0f, 2f, 0f, 4f, 5f, 6f, 0f, 8f })]
        [InlineData(1, new int[] { 0, 2, 5, 6, 8, 11, 12, 13 }, new float[] { 0f, 2f, 0f, 4f, 5f, 0f, 0f, 8f, 9f, 0f, 11f, 12f, 0f, 0f, 0f, 16f })]
        public void ZeroMatrixItemsCoreTest(int test, int[] idx, float[] expected)
        {
            AlignedArray src = new AlignedArray(8 + 8 * test, _vectorAlignment);
            src.CopyFrom(_testSrcVectors[test]);
 
            CpuMathUtils.ZeroMatrixItems(src, src.Size / 2 - 1, src.Size / 2, idx);
            float[] actual = new float[src.Size];
            src.CopyTo(actual, 0, src.Size);
            Assert.Equal(expected, actual, _comparer);
        }
 
        [Theory]
        [MemberData(nameof(AddScaleData))]
        public void SdcaL1UpdateUTest(string mode, string test, string scale, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1, arg2) =>
            {
                CheckProperFlag(arg0);
                float defaultScale = float.Parse(arg2, CultureInfo.InvariantCulture);
                float[] src = (float[])_testArrays[int.Parse(arg1)].Clone();
                float[] v = (float[])src.Clone();
                float[] w = (float[])src.Clone();
                float[] expected = (float[])w.Clone();
 
                for (int i = 0; i < expected.Length; i++)
                {
                    float value = src[i] * (1 + defaultScale);
                    expected[i] = Math.Abs(value) > defaultScale ? (value > 0 ? value - defaultScale : value + defaultScale) : 0;
                }
 
                CpuMathUtils.SdcaL1UpdateDense(defaultScale, src.Length, src, defaultScale, v, w);
                var actual = w;
                Assert.Equal(expected, actual, _comparer);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, scale, options);
        }
 
 
        [Theory]
        [MemberData(nameof(AddScaleData))]
        public void SdcaL1UpdateSUTest(string mode, string test, string scale, Dictionary<string, string> environmentVariables)
        {
            var options = new RemoteOptions();
            UpdateEnvVars(options, environmentVariables);
 
            RemoteExecutor.RemoteInvoke((arg0, arg1, arg2) =>
            {
                CheckProperFlag(arg0);
                float defaultScale = float.Parse(arg2, CultureInfo.InvariantCulture);
                float[] src = (float[])_testArrays[int.Parse(arg1)].Clone();
                float[] v = (float[])src.Clone();
                float[] w = (float[])src.Clone();
                int[] idx = _testIndexArray;
                int limit = int.Parse(arg1) == 2 ? 9 : 18;
                float[] expected = (float[])w.Clone();
 
                for (int i = 0; i < limit; i++)
                {
                    int index = idx[i];
                    float value = v[index] + src[i] * defaultScale;
                    expected[index] = Math.Abs(value) > defaultScale ? (value > 0 ? value - defaultScale : value + defaultScale) : 0;
                }
 
                CpuMathUtils.SdcaL1UpdateSparse(defaultScale, limit, src, idx, defaultScale, v, w);
                var actual = w;
                Assert.Equal(expected, actual, _comparer);
                return RemoteExecutor.SuccessExitCode;
            }, mode, test, scale, options);
        }
 
        private void UpdateEnvVars(RemoteOptions options, Dictionary<string, string> environmentVariables)
        {
            if (environmentVariables == null)
                return;
 
            foreach (var item in environmentVariables)
            {
                options.StartInfo.Environment.Add(item.Key, item.Value);
            }
 
            options.StartInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        }
 
    }
 
    internal class FloatEqualityComparer : IEqualityComparer<float>
    {
        public bool Equals(float a, float b)
        {
            return Math.Abs(a - b) < 1e-5f;
        }
 
        public int GetHashCode(float a)
        {
            throw new NotImplementedException();
        }
    }
 
    internal class FloatEqualityComparerForMatMul : IEqualityComparer<float>
    {
        public bool Equals(float a, float b)
        {
            return Math.Abs(a - b) < 1e-3f;
        }
 
        public int GetHashCode(float a)
        {
            throw new NotImplementedException();
        }
    }
}