Skip to content

Commit cc1d04d

Browse files
committed
Added CPUMetaOptimizerTests to test convergence of our optimization algorithm.
1 parent bdb6beb commit cc1d04d

File tree

1 file changed

+336
-0
lines changed

1 file changed

+336
-0
lines changed
Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
// ---------------------------------------------------------------------------------------
2+
// ILGPU Algorithms
3+
// Copyright (c) 2023 ILGPU Project
4+
// www.ilgpu.net
5+
//
6+
// File: CPUMetaOptimizerTests.cs
7+
//
8+
// This file is part of ILGPU and is distributed under the University of Illinois Open
9+
// Source License. See LICENSE.txt for details.
10+
// ---------------------------------------------------------------------------------------
11+
12+
using ILGPU.Algorithms.Optimization.CPU;
13+
using ILGPU.Algorithms.Random;
14+
using System;
15+
using System.Threading.Tasks;
16+
using Xunit;
17+
18+
#if NET7_0_OR_GREATER
19+
20+
#pragma warning disable CA1034 // Do not nest types
21+
#pragma warning disable CA1819 // Properties should not return arrays
22+
23+
namespace ILGPU.Algorithms.Tests.CPU
24+
{
25+
/// <summary>
26+
/// Contains tests to verify the functionality of the CPU-specialized
27+
/// <see cref="MetaOptimizer{T,TEvalType}"/> class.
28+
/// </summary>
29+
public class CPUMetaOptimizerTests
30+
{
31+
#region CPU Functions
32+
33+
public interface IOptimizerTestFunction :
34+
OptimizationTests.IPredefineTestFunction,
35+
ICPUOptimizationFunction<float, float>
36+
{ }
37+
38+
public readonly record struct TestBreakFunction(float Goal) :
39+
ICPUOptimizationBreakFunction<float>
40+
{
41+
public bool Break(float evalType, int iteration) =>
42+
Math.Abs(evalType - Goal) < 1e-3f || iteration > 1000;
43+
}
44+
45+
/// <summary>
46+
/// Represents the Himmelblau function:
47+
/// https://en.wikipedia.org/wiki/Test_functions_for_optimization
48+
/// </summary>
49+
public readonly record struct HimmelblauFunction : IOptimizerTestFunction
50+
{
51+
public float Evaluate(ReadOnlySpan<float> position) =>
52+
OptimizationTests.HimmelblauFunction.Evaluate(
53+
position[0],
54+
position[1]);
55+
56+
public bool CurrentIsBetter(float current, float proposed) =>
57+
current < proposed;
58+
59+
public float Result =>
60+
new OptimizationTests.HimmelblauFunction().Result;
61+
public float[] LowerBounds =>
62+
new OptimizationTests.HimmelblauFunction().LowerBounds;
63+
public float[] UpperBounds =>
64+
new OptimizationTests.HimmelblauFunction().UpperBounds;
65+
}
66+
67+
/// <summary>
68+
/// Represents the Easom function:
69+
/// https://en.wikipedia.org/wiki/Test_functions_for_optimization
70+
/// </summary>
71+
public readonly record struct EasomFunction : IOptimizerTestFunction
72+
{
73+
public float Evaluate(ReadOnlySpan<float> position) =>
74+
OptimizationTests.EasomFunction.Evaluate(
75+
position[0],
76+
position[1]);
77+
78+
public bool CurrentIsBetter(float current, float proposed) =>
79+
current < proposed;
80+
81+
public float Result =>
82+
new OptimizationTests.EasomFunction().Result;
83+
public float[] LowerBounds =>
84+
new OptimizationTests.EasomFunction().LowerBounds;
85+
public float[] UpperBounds =>
86+
new OptimizationTests.EasomFunction().UpperBounds;
87+
}
88+
/// <summary>
89+
/// Represents the Shaffer function N4:
90+
/// https://en.wikipedia.org/wiki/Test_functions_for_optimization
91+
/// </summary>
92+
public readonly record struct ShafferFunction4 : IOptimizerTestFunction
93+
{
94+
public float Evaluate(ReadOnlySpan<float> position) =>
95+
OptimizationTests.ShafferFunction4.Evaluate(
96+
position[0],
97+
position[1]);
98+
99+
public bool CurrentIsBetter(float current, float proposed) =>
100+
current < proposed;
101+
102+
public float Result =>
103+
new OptimizationTests.ShafferFunction4().Result;
104+
public float[] LowerBounds =>
105+
new OptimizationTests.ShafferFunction4().LowerBounds;
106+
public float[] UpperBounds =>
107+
new OptimizationTests.ShafferFunction4().UpperBounds;
108+
}
109+
110+
/// <summary>
111+
/// Represents the Rosenbrock function constrained to a disk
112+
/// https://en.wikipedia.org/wiki/Test_functions_for_optimization
113+
/// </summary>
114+
public readonly record struct RosenbrockDisk : IOptimizerTestFunction
115+
{
116+
public float Evaluate(ReadOnlySpan<float> position) =>
117+
OptimizationTests.RosenbrockDisk.Evaluate(
118+
position[0],
119+
position[1]);
120+
121+
public bool CurrentIsBetter(float current, float proposed) =>
122+
current < proposed;
123+
124+
public float Result =>
125+
new OptimizationTests.RosenbrockDisk().Result;
126+
public float[] LowerBounds =>
127+
new OptimizationTests.RosenbrockDisk().LowerBounds;
128+
public float[] UpperBounds =>
129+
new OptimizationTests.RosenbrockDisk().UpperBounds;
130+
}
131+
132+
/// <summary>
133+
/// Represents the Gomez and Levy function:
134+
/// https://en.wikipedia.org/wiki/Test_functions_for_optimization
135+
/// </summary>
136+
public readonly record struct GomezAndLevyFunction : IOptimizerTestFunction
137+
{
138+
public float Evaluate(ReadOnlySpan<float> position) =>
139+
OptimizationTests.GomezAndLevyFunction.Evaluate(
140+
position[0],
141+
position[1]);
142+
143+
public bool CurrentIsBetter(float current, float proposed) =>
144+
current < proposed;
145+
146+
public float Result =>
147+
new OptimizationTests.GomezAndLevyFunction().Result;
148+
public float[] LowerBounds =>
149+
new OptimizationTests.GomezAndLevyFunction().LowerBounds;
150+
public float[] UpperBounds =>
151+
new OptimizationTests.GomezAndLevyFunction().UpperBounds;
152+
}
153+
154+
#endregion
155+
156+
#region MemberData
157+
158+
public static TheoryData<
159+
object,
160+
object,
161+
object,
162+
object,
163+
object> TestData =>
164+
new TheoryData<
165+
object,
166+
object,
167+
object,
168+
object,
169+
object>
170+
{
171+
{ new HimmelblauFunction(), 8192, 0.5f, 0.5f, 0.5f },
172+
{ new EasomFunction(), 81920, 0.5f, 0.5f, 0.5f },
173+
{ new ShafferFunction4(), 8192, 0.5f, 0.5f, 0.5f },
174+
{ new RosenbrockDisk(), 8192, 0.5f, 0.5f, 0.5f },
175+
{ new GomezAndLevyFunction(), 81920, 0.5f, 0.5f, 0.5f },
176+
};
177+
178+
#endregion
179+
180+
[Theory]
181+
[MemberData(nameof(TestData))]
182+
public void MetaOptimizationScalar<TObjective>(
183+
TObjective objective,
184+
int numParticles,
185+
float stepSizeDefensive,
186+
float stepSizeOffensive,
187+
float stepSizeOffensiveSOG)
188+
where TObjective : struct, IOptimizerTestFunction
189+
{
190+
int numDimensions = objective.LowerBounds.Length;
191+
var random = new System.Random(13377331);
192+
193+
using var optimizer = MetaOptimizer.CreateScalar<
194+
float,
195+
float,
196+
RandomRanges.RandomRangeFloatProvider<XorShift64Star>>(
197+
random,
198+
numParticles,
199+
numDimensions,
200+
maxNumParallelThreads: 1);
201+
202+
optimizer.LowerBounds = objective.LowerBounds;
203+
optimizer.UpperBounds = objective.UpperBounds;
204+
205+
optimizer.DefensiveStepSize = stepSizeDefensive;
206+
optimizer.OffensiveStepSize = stepSizeOffensive;
207+
optimizer.OffensiveSOGStepSize = stepSizeOffensiveSOG;
208+
209+
var breakFunction = new TestBreakFunction(objective.Result);
210+
var result = optimizer.Optimize(
211+
objective,
212+
breakFunction,
213+
float.MaxValue);
214+
215+
// The actually achievable result is 1e-6. However, as the RNG gives us
216+
// non-deterministic results due to parallel processing, we limit ourselves
217+
// to 1e-3 to make sure that the result lies roughly in the same ballpark
218+
// what we were expecting
219+
Assert.True(Math.Abs(result.Result - objective.Result) < 1e-3f);
220+
}
221+
222+
[Theory]
223+
[MemberData(nameof(TestData))]
224+
public void MetaOptimizationVectorized<TObjective>(
225+
TObjective objective,
226+
int numParticles,
227+
float stepSizeDefensive,
228+
float stepSizeOffensive,
229+
float stepSizeOffensiveSOG)
230+
where TObjective : struct, IOptimizerTestFunction
231+
{
232+
int numDimensions = objective.LowerBounds.Length;
233+
var random = new System.Random(13377331);
234+
235+
using var optimizer = MetaOptimizer.CreateVectorized<
236+
float,
237+
float,
238+
RandomRanges.RandomRangeFloatProvider<XorShift64Star>>(
239+
random,
240+
numParticles,
241+
numDimensions,
242+
maxNumParallelThreads: 1);
243+
244+
optimizer.LowerBounds = objective.LowerBounds;
245+
optimizer.UpperBounds = objective.UpperBounds;
246+
247+
optimizer.DefensiveStepSize = stepSizeDefensive;
248+
optimizer.OffensiveStepSize = stepSizeOffensive;
249+
optimizer.OffensiveSOGStepSize = stepSizeOffensiveSOG;
250+
251+
var breakFunction = new TestBreakFunction(objective.Result);
252+
var result = optimizer.Optimize(
253+
objective,
254+
breakFunction,
255+
float.MaxValue);
256+
257+
// The actually achievable result is 1e-6. However, as the RNG gives us
258+
// non-deterministic results due to parallel processing, we limit ourselves
259+
// to 1e-3 to make sure that the result lies roughly in the same ballpark
260+
// what we were expecting
261+
Assert.True(
262+
Math.Abs(result.Result - objective.Result) < 1e-3f,
263+
$"Expected {objective.Result}, but found {result.Result}");
264+
}
265+
266+
[Theory]
267+
[MemberData(nameof(TestData))]
268+
public void MetaOptimizationScalarRaw<TObjective>(
269+
TObjective objective,
270+
int numParticles,
271+
float stepSizeDefensive,
272+
float stepSizeOffensive,
273+
float stepSizeOffensiveSOG)
274+
where TObjective : struct, IOptimizerTestFunction
275+
{
276+
int numDimensions = objective.LowerBounds.Length;
277+
var random = new System.Random(13377331);
278+
279+
using var optimizer = MetaOptimizer.CreateScalar<
280+
float,
281+
float,
282+
RandomRanges.RandomRangeFloatProvider<XorShift64Star>>(
283+
random,
284+
numParticles,
285+
numDimensions,
286+
maxNumParallelThreads: 1);
287+
288+
optimizer.LowerBounds = objective.LowerBounds;
289+
optimizer.UpperBounds = objective.UpperBounds;
290+
291+
optimizer.DefensiveStepSize = stepSizeDefensive;
292+
optimizer.OffensiveStepSize = stepSizeOffensive;
293+
optimizer.OffensiveSOGStepSize = stepSizeOffensiveSOG;
294+
295+
void EvaluatePosition(
296+
Memory<float> allPositions,
297+
Memory<float> evaluations,
298+
int _,
299+
int numPaddedDimensions,
300+
int __,
301+
Stride2D.DenseY positionStride,
302+
ParallelOptions options)
303+
{
304+
for (int i = 0; i < numParticles; ++i)
305+
{
306+
int offset = positionStride.ComputeElementIndex((i, 0));
307+
int endOffset = positionStride.ComputeElementIndex(
308+
(i, numPaddedDimensions));
309+
var position = allPositions.Slice(offset, endOffset - offset);
310+
var result = objective.Evaluate(position.Span);
311+
evaluations.Span[i] = result;
312+
}
313+
}
314+
315+
var breakFunction = new TestBreakFunction(objective.Result);
316+
var result = optimizer.OptimizeRaw(
317+
EvaluatePosition,
318+
breakFunction.Break,
319+
objective.CurrentIsBetter,
320+
float.MaxValue);
321+
322+
// The actually achievable result is 1e-6. However, as the RNG gives us
323+
// non-deterministic results due to parallel processing, we limit ourselves
324+
// to 1e-3 to make sure that the result lies roughly in the same ballpark
325+
// what we were expecting
326+
Assert.True(
327+
Math.Abs(result.Result - objective.Result) < 1e-3f,
328+
$"Expected {objective.Result}, but found {result.Result}");
329+
}
330+
}
331+
}
332+
333+
#pragma warning restore CA1819
334+
#pragma warning restore CA1034
335+
336+
#endif

0 commit comments

Comments
 (0)