diff --git a/Core/Utils/MathUtil.cs b/Core/Utils/MathUtil.cs new file mode 100644 index 00000000..eaa53b49 --- /dev/null +++ b/Core/Utils/MathUtil.cs @@ -0,0 +1,116 @@ +using System.Numerics; +using Core.Annotations; + +namespace Core.Utils; + +[Injectable(InjectionType.Singleton)] +public class MathUtil +{ + /// + /// Helper to create the sum of all list elements + /// + /// List of floats to sum + /// sum of all values + public float ListSum(List values) + { + // Sum the list starting with an initial value of 0 + return values.Aggregate(0f, (sum, x) => sum + x); + } + + /// + /// Helper to create the cumulative sum of all list elements + /// ListCumSum([1, 2, 3, 4]) = [1, 3, 6, 10] + /// + /// The list with numbers of which to calculate the cumulative sum + /// cumulative sum of values + public List ListCumSum(List values) + { + var cumSumList = new List(values.Count); + float sum = 0; + + foreach (var value in values) + { + sum += value; + cumSumList.Add(sum); + } + + return cumSumList; + } + + /// + /// Helper to create the product of each element times factor + /// + /// The list of numbers which shall be multiplied by the factor + /// Number to multiply each element by + /// A list of elements all multiplied by the factor + public List ListProduct(List values, float factor) + { + return values.Select(v => v * factor).ToList(); + } + + /// + /// Helper to add a constant to all list elements + /// + /// The list of numbers to which the summand should be added + /// + /// A list of elements with the additive added to all elements + public List ListAdd(List values, float additive) + { + return values.Select(v => v + additive).ToList(); + } + + /// + /// Maps a value from an input range to an output range linearly. + /// + /// Example: + /// a_min = 0; a_max=1; + /// b_min = 1; b_max=3; + /// MapToRange(0.5, a_min, a_max, b_min, b_max) // returns 2 + /// + /// + /// The value from the input range to be mapped to the output range. + /// Minimum of the input range. + /// Maximum of the input range. + /// Minimum of the output range. + /// Maximum of the output range. + /// The result of the mapping. + public double MapToRange(double x, double minIn, double maxIn, double minOut, double maxOut) + { + var deltaIn = maxIn - minIn; + var deltaOut = maxOut - minOut; + + var xScale = (x - minIn) / deltaIn; + return Math.Max(minOut, Math.Min(maxOut, minOut + xScale * deltaOut)); + } + + /// + /// Linear interpolation + /// e.g. used to do a continuous integration for quest rewards which are defined for specific support centers of pmcLevel + /// + /// The point of x at which to interpolate + /// Support points in x (of same length as y) + /// Support points in y (of same length as x) + /// Interpolated value at xp, or null if xp is out of bounds + public static double? Interp1(double xp, double[] x, double[] y) + { + if (xp > x[^1]) // ^1 is the last index in C# + { + return y[^1]; + } + + if (xp < x[0]) + { + return y[0]; + } + + for (var i = 0; i < x.Length - 1; i++) + { + if (xp >= x[i] && xp <= x[i + 1]) + { + return y[i] + ((xp - x[i]) * (y[i + 1] - y[i])) / (x[i + 1] - x[i]); + } + } + + return null; + } +} \ No newline at end of file diff --git a/UnitTests/Tests/Utils/MathUtilTests.cs b/UnitTests/Tests/Utils/MathUtilTests.cs new file mode 100644 index 00000000..79400d7f --- /dev/null +++ b/UnitTests/Tests/Utils/MathUtilTests.cs @@ -0,0 +1,83 @@ +using Core.Utils; + +namespace UnitTests.Tests.Utils; + +[TestClass] +public class MathUtilTests +{ + private readonly MathUtil _mathUtil = new(); + + [TestMethod] + public void ListSumTest() + { + var test = new List { 1.1f, 2.1f, 3.3f }; + const float expected = 6.5f; + + var actual = _mathUtil.ListSum(test); + + Assert.AreEqual(expected, actual, + $"ListSum() Expected: {expected}, Actual: {actual}"); + } + + [TestMethod] + public void ListCumSumTest() + { + var test = new List { 1f, 2f, 3f, 4f }; + var expected = new List { 1f, 3f, 6f, 10f }; + + var actual = _mathUtil.ListCumSum(test); + + for (var i = 0; i < actual.Count; i++) + { + if (Math.Abs(expected[i] - actual[i]) > 0.00001f) + { + Assert.Fail($"ListCumSum() Expected: {string.Join(", ", expected)}, Actual: {string.Join(", ", actual)}"); + } + } + } + + [TestMethod] + public void ListProductTest() + { + var test = new List { 1f, 2f, 3f, 4f }; + var expected = new List { 2f, 4f, 6f, 8f }; + + var actual = _mathUtil.ListProduct(test, 2); + + for (var i = 0; i < actual.Count; i++) + { + if (Math.Abs(expected[i] - actual[i]) > 0.00001f) + { + Assert.Fail($"ListProduct() Expected: {string.Join(", ", expected)}, Actual: {string.Join(", ", actual)}"); + } + } + } + + [TestMethod] + public void ListAddTest() + { + var test = new List { 1f, 2f, 3f, 4f }; + var expected = new List { 3f, 4f, 5f, 6f }; + + var actual = _mathUtil.ListAdd(test, 2); + + for (var i = 0; i < actual.Count; i++) + { + if (Math.Abs(expected[i] - actual[i]) > 0.00001f) + { + Assert.Fail($"ListProduct() Expected: {string.Join(", ", expected)}, Actual: {string.Join(", ", actual)}"); + } + } + } + + [TestMethod] + public void MapToRangeTest() + { + const double expected = 2; + + var actual = _mathUtil.MapToRange(0.5, 0, 1, 1, 3); + + Assert.AreEqual(expected, actual, + $"MapToRange() Expected: {expected}, Actual: {actual}"); + } +} \ No newline at end of file