diff --git a/Src/ILGPU.Algorithms.Tests/OptimizationTests.cs b/Src/ILGPU.Algorithms.Tests/OptimizationTests.cs index 7ab8acbbe3..1d70ad9292 100644 --- a/Src/ILGPU.Algorithms.Tests/OptimizationTests.cs +++ b/Src/ILGPU.Algorithms.Tests/OptimizationTests.cs @@ -20,6 +20,7 @@ using ILGPU.Tests; using System.Linq; using System.Numerics; +using System.Runtime.CompilerServices; using Xunit; using Xunit.Abstractions; @@ -34,11 +35,18 @@ public abstract partial class OptimizationTests : TestBase protected OptimizationTests(ITestOutputHelper output, TestContext testContext) : base(output, testContext) { } - + #if NET7_0_OR_GREATER - + #region Objectives - + + public interface IPredefineTestFunction + { + float Result { get; } + float[] LowerBounds { get; } + float[] UpperBounds { get; } + } + public readonly record struct DistanceF32x2(float Constant) : IOptimizationFunction { @@ -60,7 +68,271 @@ public float Evaluate( public bool CurrentIsBetter(float current, float proposed) => current <= proposed; } - + + /// + /// Represents the Himmelblau function: + /// https://en.wikipedia.org/wiki/Test_functions_for_optimization + /// + public readonly record struct HimmelblauFunction : + IOptimizationFunction, + IPredefineTestFunction + { + private static readonly float[] GlobalLowerBounds = new float[] + { + -5.0f, -5.0f + }; + + private static readonly float[] GlobalUpperBounds = new float[] + { + 5.0f, 5.0f + }; + + /// + /// The optimal result. + /// + public const float GlobalResult = 0.0f; + + /// + /// Evaluates the Himmelblau function. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Evaluate(float x, float y) + { + float first = (x * x + y - 11); + float second = (x + y * y - 7); + return first * first + second * second; + } + + public float Result => GlobalResult; + public float[] LowerBounds => GlobalLowerBounds; + public float[] UpperBounds => GlobalUpperBounds; + + public float Evaluate( + LongIndex1D index, + Index1D dimension, + SingleVectorView positionView) + { + var first = positionView[0]; + return Evaluate(first.X, first.Y); + } + + public bool CurrentIsBetter(float current, float proposed) => + current < proposed; + } + + /// + /// Represents the Easom function: + /// https://en.wikipedia.org/wiki/Test_functions_for_optimization + /// + public readonly record struct EasomFunction : + IOptimizationFunction, + IPredefineTestFunction + { + private static readonly float[] GlobalLowerBounds = new float[] + { + -100.0f, -100.0f + }; + + private static readonly float[] GlobalUpperBounds = new float[] + { + 100.0f, 100.0f + }; + + /// + /// The optimal result. + /// + public const float GlobalResult = -1.0f; + + public float Result => GlobalResult; + public float[] LowerBounds => GlobalLowerBounds; + public float[] UpperBounds => GlobalUpperBounds; + + /// + /// Evaluates the Easom function. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Evaluate(float x, float y) + { + float xPart = x - XMath.PI; + float yPart = y - XMath.PI; + return -XMath.Cos(x) * XMath.Cos(y) * + XMath.Exp(-(xPart * xPart + yPart * yPart)); + } + public float Evaluate( + LongIndex1D index, + Index1D dimension, + SingleVectorView positionView) + { + var first = positionView[0]; + return Evaluate(first.X, first.Y); + } + + public bool CurrentIsBetter(float current, float proposed) => + current < proposed; + } + + /// + /// Represents the Shaffer function N4: + /// https://en.wikipedia.org/wiki/Test_functions_for_optimization + /// + public readonly record struct ShafferFunction4 : + IOptimizationFunction, + IPredefineTestFunction + { + private static readonly float[] GlobalLowerBounds = new float[] + { + -100.0f, -100.0f + }; + + private static readonly float[] GlobalUpperBounds = new float[] + { + 100.0f, 100.0f + }; + + /// + /// The optimal result. + /// + public const float GlobalResult = 0.292579f; + + public float Result => GlobalResult; + public float[] LowerBounds => GlobalLowerBounds; + public float[] UpperBounds => GlobalUpperBounds; + + /// + /// Evaluates the Shaffer function. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Evaluate(float x, float y) + { + float cos = XMath.Cos(XMath.Sin(XMath.Abs(x * x - y * y))); + float nominator = cos * cos - 0.5f; + float denominator = 1 + 0.001f * (x * x + y * y); + return 0.5f + nominator / (denominator * denominator); + } + public float Evaluate( + LongIndex1D index, + Index1D dimension, + SingleVectorView positionView) + { + var first = positionView[0]; + return Evaluate(first.X, first.Y); + } + + public bool CurrentIsBetter(float current, float proposed) => + current < proposed; + } + + /// + /// Represents the Rosenbrock function constrained to a disk + /// https://en.wikipedia.org/wiki/Test_functions_for_optimization + /// + public readonly record struct RosenbrockDisk : + IOptimizationFunction, + IPredefineTestFunction + { + private static readonly float[] GlobalLowerBounds = new float[] + { + -1.5f, -1.5f + }; + + private static readonly float[] GlobalUpperBounds = new float[] + { + 1.5f, 1.5f + }; + + /// + /// The optimal result. + /// + public const float GlobalResult = 0.0f; + + public float Result => GlobalResult; + public float[] LowerBounds => GlobalLowerBounds; + public float[] UpperBounds => GlobalUpperBounds; + + /// + /// Evaluates the constrained Rosenbrock function. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Evaluate(float x, float y) + { + float xMin = 1.0f - x; + float x2 = x * x; + float result = xMin * xMin + 100.0f * (y - x2) * (y - x2); + if (x * x + y * y <= 2.0f) + return result; + return float.MaxValue; + } + + public float Evaluate( + LongIndex1D index, + Index1D dimension, + SingleVectorView positionView) + { + var first = positionView[0]; + return Evaluate(first.X, first.Y); + } + + public bool CurrentIsBetter(float current, float proposed) => + current < proposed; + } + + /// + /// Represents the Gomez and Levy function: + /// https://en.wikipedia.org/wiki/Test_functions_for_optimization + /// + public readonly record struct GomezAndLevyFunction : + IOptimizationFunction, + IPredefineTestFunction + { + private static readonly float[] GlobalLowerBounds = new float[] + { + -1.0f, -1.0f + }; + + private static readonly float[] GlobalUpperBounds = new float[] + { + 0.75f, 1.0f + }; + + /// + /// The optimal result. + /// + public const float GlobalResult = -1.031628453f; + + public float Result => GlobalResult; + public float[] LowerBounds => GlobalLowerBounds; + public float[] UpperBounds => GlobalUpperBounds; + + /// + /// Evaluates the constrained Gomez and Levy function. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Evaluate(float x, float y) + { + float x2 = x * x; + float x4 = x2 * x2; + float y2 = y * y; + float y4 = y2 * y2; + float result = 4.0f * x2 + 2.1f * x4 + 1.0f / 3.0f * x4 * x2 + + x * y - 4.0f * y2 + 4.0f * y4; + float sin = XMath.Sin(2.0f * XMath.PI * y); + float conditionValue = -XMath.Sin(4.0f * XMath.PI * x) + 2.0f * sin * sin; + return conditionValue < 1.5f ? result : float.MaxValue; + } + + public float Evaluate( + LongIndex1D index, + Index1D dimension, + SingleVectorView positionView) + { + var first = positionView[0]; + return Evaluate(first.X, first.Y); + } + + public bool CurrentIsBetter(float current, float proposed) => + current < proposed; + } + #endregion #region MemberData @@ -129,7 +401,7 @@ public static TheoryData< #endregion - [SkippableTheory()] + [SkippableTheory] [MemberData(nameof(TestData))] public void ParticleSwarmOptimization< TFunc, @@ -157,7 +429,7 @@ public void ParticleSwarmOptimization< Skip.If( Accelerator.AcceleratorType == AcceleratorType.CPU && optimizerConfig.NumIterations * optimizerConfig.NumParticles > 2048); - + const int Seed = 24404699; using var pso = new PSO< TNumericType, @@ -185,7 +457,7 @@ public void ParticleSwarmOptimization< best, optimizerConfig.NumIterations); stream.Synchronize(); - + // Check result Assert.True( result.Result - delta <= expected,