From e4b346d6280e14907ba293e1ef1448a8d15450e5 Mon Sep 17 00:00:00 2001
From: Cj <161484149+CJ-SPT@users.noreply.github.com>
Date: Wed, 8 Jan 2025 04:10:40 -0500
Subject: [PATCH] Math Util and tests
---
Core/Utils/MathUtil.cs | 116 +++++++++++++++++++++++++
UnitTests/Tests/Utils/MathUtilTests.cs | 83 ++++++++++++++++++
2 files changed, 199 insertions(+)
create mode 100644 Core/Utils/MathUtil.cs
create mode 100644 UnitTests/Tests/Utils/MathUtilTests.cs
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