diff --git a/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs b/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs index 5b67799e4957f4e1dcca5c5698bc5797954951b3..44fc5aa55ee8199b1fdae174ec9d9db1dada6fce 100644 --- a/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs +++ b/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs @@ -86,7 +86,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine }; if (entry.FuelConsumption < 0) - throw new ArgumentOutOfRangeException("FuelConsumption < 0" + data.Rows.IndexOf(row)); + throw new ArgumentOutOfRangeException("FuelConsumption < 0"); fuelConsumptionMap._entries.Add(entry); fuelConsumptionMap._fuelMap.AddPoint(entry.EngineSpeed, entry.Torque, entry.FuelConsumption); diff --git a/VectoCore/Utils/DelauneyMap.cs b/VectoCore/Utils/DelauneyMap.cs index 589ffaedf07a94f69aa84a74693266a77575d292..4f51ed7d1a5693eb0416bbf7337b923c02442a1c 100644 --- a/VectoCore/Utils/DelauneyMap.cs +++ b/VectoCore/Utils/DelauneyMap.cs @@ -7,7 +7,7 @@ using TUGraz.VectoCore.Exceptions; namespace TUGraz.VectoCore.Utils { [JsonObject(MemberSerialization.Fields)] - class DelauneyMap + public class DelauneyMap { private readonly List<Point> _points = new List<Point>(); private List<Triangle> _triangles = new List<Triangle>(); @@ -19,44 +19,45 @@ namespace TUGraz.VectoCore.Utils public void Triangulate() { - const int superTriangleScalingFactor = 10; - if (_points.Count < 3) throw new ArgumentException(string.Format("Triangulations needs at least 3 Points. Got {0} Points.", _points.Count)); + var superTriangle = CalculateSuperTriangle(); + // The "supertriangle" encompasses all triangulation points. // This triangle initializes the algorithm and will be removed later. - var max = _points.Max(point => Math.Max(Math.Abs(point.X), Math.Abs(point.Y))) * superTriangleScalingFactor; - var superTriangle = new Triangle(new Point(max, 0, 0), new Point(0, max, 0), new Point(-max, -max, 0)); var triangles = new List<Triangle> { superTriangle }; foreach (var point in _points) { - var edges = new List<Edge>(); - - // If the actual vertex lies inside the circumcircle, then the three edges of the - // triangle are added to the edge buffer and the triangle is removed from list. - foreach (var containerTriangle in triangles.Where(triangle => triangle.ContainsInCircumcircle(point)).ToList()) - { - edges.Add(new Edge(containerTriangle.P1, containerTriangle.P2)); - edges.Add(new Edge(containerTriangle.P2, containerTriangle.P3)); - edges.Add(new Edge(containerTriangle.P3, containerTriangle.P1)); - triangles.Remove(containerTriangle); - } + + // If the actual vertex lies inside a triangle, the edges of the triangle are + // added to the edge buffer and the triangle is removed from list. + var containerTriangles = triangles.FindAll(t => t.ContainsInCircumcircle(point)); + triangles.RemoveAll(t => t.ContainsInCircumcircle(point)); // Remove duplicate edges. This leaves the convex hull of the edges. // The edges in this convex hull are oriented counterclockwise! - var convexHullEdges = edges.GroupBy(edge => edge).Where(group => group.Count() == 1).SelectMany(group => group); + var convexHullEdges = containerTriangles. + SelectMany(t => t.GetEdges()). + GroupBy(edge => edge). + Where(group => group.Count() == 1). + SelectMany(group => group); + + var newTriangles = convexHullEdges.Select(edge => new Triangle(edge.P1, edge.P2, point)); - // Generate new counterclockwise oriented triangles filling the "hole" in - // the existing triangulation. These triangles all share the actual vertex. - var counterTriangles = convexHullEdges.Select(edge => new Triangle(edge.P1, edge.P2, point)); - triangles.AddRange(counterTriangles); + triangles.AddRange(newTriangles); } - // Remove all triangles sharing a vertex with the supertriangle. - _triangles = triangles.Where(triangle => !triangle.SharesVertexWith(superTriangle)).ToList(); + _triangles = triangles.FindAll(t => !t.SharesVertexWith(superTriangle)); + } + + private Triangle CalculateSuperTriangle() + { + const int superTriangleScalingFactor = 10; + var max = _points.Max(point => Math.Max(Math.Abs(point.X), Math.Abs(point.Y))) * superTriangleScalingFactor; + return new Triangle(new Point(max, 0, 0), new Point(0, max, 0), new Point(-max, -max, 0)); } public double Interpolate(double x, double y) @@ -84,14 +85,14 @@ namespace TUGraz.VectoCore.Utils if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; - return Equals((DelauneyMap) obj); + return Equals((DelauneyMap)obj); } public override int GetHashCode() { unchecked { - return ((_points != null ? _points.GetHashCode() : 0)*397) ^ + return ((_points != null ? _points.GetHashCode() : 0) * 397) ^ (_triangles != null ? _triangles.GetHashCode() : 0); } } @@ -132,12 +133,12 @@ namespace TUGraz.VectoCore.Utils { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; - return obj.GetType() == GetType() && Equals((Point) obj); + return obj.GetType() == GetType() && Equals((Point)obj); } public override int GetHashCode() { - return unchecked((((X.GetHashCode()*397) ^ Y.GetHashCode())*397) ^ Z.GetHashCode()); + return unchecked((((X.GetHashCode() * 397) ^ Y.GetHashCode()) * 397) ^ Z.GetHashCode()); } #endregion } @@ -255,7 +256,7 @@ namespace TUGraz.VectoCore.Utils if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; - return Equals((Triangle) obj); + return Equals((Triangle)obj); } public override int GetHashCode() @@ -263,14 +264,20 @@ namespace TUGraz.VectoCore.Utils unchecked { var hashCode = (P1 != null ? P1.GetHashCode() : 0); - hashCode = (hashCode*397) ^ (P2 != null ? P2.GetHashCode() : 0); - hashCode = (hashCode*397) ^ (P3 != null ? P3.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (P2 != null ? P2.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (P3 != null ? P3.GetHashCode() : 0); return hashCode; } } #endregion + public IEnumerable<Edge> GetEdges() + { + yield return new Edge(P1, P2); + yield return new Edge(P2, P3); + yield return new Edge(P3, P1); + } } public class Edge @@ -301,7 +308,7 @@ namespace TUGraz.VectoCore.Utils { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; - return obj.GetType() == GetType() && Equals((Edge) obj); + return obj.GetType() == GetType() && Equals((Edge)obj); } public override int GetHashCode() diff --git a/VectoCoreTest/Models/SimulationComponentData/FuelConsumptionMapTest.cs b/VectoCoreTest/Models/SimulationComponentData/FuelConsumptionMapTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..663f09edf99cc5a5e559da7679ef903a11d4614b --- /dev/null +++ b/VectoCoreTest/Models/SimulationComponentData/FuelConsumptionMapTest.cs @@ -0,0 +1,41 @@ +using System.IO; +using System.Linq; +using System.Globalization; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TUGraz.VectoCore.Models.SimulationComponent.Data.Engine; + +namespace TUGraz.VectoCore.Tests.Models.SimulationComponentData +{ + [TestClass] + public class FuelConsumptionMapTest + { + private const double Tolerance = 0.0001; + + [TestMethod] + public void TestFuelConsumption_FixedPoints() + { + var map = FuelConsumptionMap.ReadFromFile(@"TestData\Components\24t Coach.vmap"); + var lines = File.ReadAllLines(@"TestData\Components\24t Coach.vmap").Skip(1).ToArray(); + AssertMapValuesEqual(lines, map); + } + + [TestMethod] + public void TestFuelConsumption_InterpolatedPoints() + { + var map = FuelConsumptionMap.ReadFromFile(@"TestData\Components\24t Coach.vmap"); + var lines = File.ReadAllLines(@"TestData\Components\24t CoachInterpolated.vmap").Skip(1).ToArray(); + AssertMapValuesEqual(lines, map); + } + + private static void AssertMapValuesEqual(string[] lines, FuelConsumptionMap map) + { + for (var i = 1; i < lines.Count(); i++) + { + var entry = lines[i].Split(',').Select(x => double.Parse(x, CultureInfo.InvariantCulture)).ToArray(); + + Assert.AreEqual(entry[2], map.GetFuelConsumption(entry[0], entry[1]), Tolerance, + string.Format("Line: {0}, n={1}, T={2}", (i + 2), entry[0], entry[1])); + } + } + } +} diff --git a/VectoCoreTest/TestData/Components/24t CoachInterpolated.vmap b/VectoCoreTest/TestData/Components/24t CoachInterpolated.vmap new file mode 100644 index 0000000000000000000000000000000000000000..67d5c12e57a4aa5dd6a69adb6ab71f563ffd8d77 --- /dev/null +++ b/VectoCoreTest/TestData/Components/24t CoachInterpolated.vmap @@ -0,0 +1,199 @@ +engine speed [1/min],torque [Nm],fuel consumption [g/h] +560,-74.5,628 +560,100,2226.5 +560,300,4246 +560,500,6455 +560,700,8495 +560,900,10307 +560,1090,12054 +600,-74,729.5 +600,100,2408.5 +600,300,4428 +600,500,6799.5 +600,700,9057.5 +600,900,11042.5 +600,1100,13136 +600,1241,14752.5 +800,-74.5,939.5 +800,100,3082.5 +800,300,5653.5 +800,500,8540 +800,700,11572.5 +800,900,14550.5 +800,1100,17627 +800,1300,20832.5 +800,1500,23954.5 +800,1695.5,27194 +1000,-80,1432.5 +1000,100,4414 +1000,300,7580.5 +1000,500,10776 +1000,700,14159.5 +1000,900,17914.5 +1000,1100,21697 +1000,1300,25366 +1000,1500,29183.5 +1000,1700,33134 +1000,1900,37231.5 +1000,2100,41740 +1000,2250,45478 +1200,-89.5,1653.5 +1200,100,5102 +1200,300,8774 +1200,500,12648 +1200,700,16880 +1200,900,21396 +1200,1100,25928.5 +1200,1300,30305.5 +1200,1500,34564.5 +1200,1700,39194.5 +1200,1900,44303 +1200,2100,49349 +1200,2250,53357.5 +1400,-101.5,2153 +1400,100,6224.5 +1400,300,10433 +1400,500,15123 +1400,700,19905.5 +1400,900,24690.5 +1400,1100,29814.5 +1400,1300,35141 +1400,1500,40470 +1400,1700,46323.5 +1400,1900,52641.5 +1400,2100,58451 +1400,2250,62724.5 +1600,-117.5,2604.5 +1600,100,7439 +1600,300,12253.5 +1600,500,17482.5 +1600,700,23010.5 +1600,900,28762.5 +1600,1100,34439.5 +1600,1300,40037 +1600,1500,46289 +1600,1700,53386 +1600,1900,60467 +1600,2039.5,65217 +1800,-132,3204.5 +1800,100,9093 +1800,300,14548.5 +1800,500,20357 +1800,700,26947.5 +1800,900,33439.5 +1800,1100,39728.5 +1800,1300,46437.5 +1800,1500,53616 +1800,1700,61296.5 +1800,1828.5,66365.5 +2000,-150.5,4563.5 +2000,100,11974.5 +2000,300,17738.5 +2000,500,23865.5 +2000,700,30632 +2000,900,38512.5 +2000,1100,46927.5 +2000,1276,53818 +2100,-160,5235 +2100,100,13401 +2100,300,19364 +2100,500,25655 +2100,700,32315.5 +2100,900,40680 +2100,1050,48148 +1100,2300,50884 +1300,2300,59654.5 +1100,2200,47951.5 +1300,2200,56427.5 +1500,2139.5,63796 +1100,2000,43137.5 +1300,2000,51372.5 +1500,2000,59872 +1700,1928.5,65744 +1100,1800,38397 +1300,1800,45572 +1500,1800,53236.5 +1700,1800,61088.5 +800,1695.5,27194 +900,1600,28324 +1100,1600,33931.5 +1300,1600,39946 +1500,1600,46473 +1700,1600,53594 +900,1400,24814 +1100,1400,29816.5 +1300,1400,35088.5 +1500,1400,40286 +1700,1400,46311 +1900,1376,53207 +600,1241,14752.5 +700,1200,16720 +900,1200,21384.5 +1100,1200,25855 +1300,1200,30358 +1500,1200,34892 +1700,1200,40163.5 +1900,1200,47048.5 +580,1000,11655 +700,1000,14043 +900,1000,17939.5 +1100,1000,21770.5 +1300,1000,25385 +1500,1000,29362 +1700,1000,34004.5 +1900,1000,39607.5 +2050,1000,44240 +580,800,9694.5 +700,800,11550 +900,800,14525.5 +1100,800,17540 +1300,800,20701.5 +1500,800,24091 +1700,800,28197.5 +1900,800,32344.5 +2050,800,34952.5 +580,600,7858 +700,600,9080 +900,600,11206.5 +1100,600,13499.5 +1300,600,16084 +1500,600,18825 +1700,600,21760.5 +1900,600,25235 +2050,600,27995 +580,400,5396.5 +700,400,6259.5 +900,400,8109.5 +1100,400,9924.5 +1300,400,11687 +1500,400,13780.5 +1700,400,16079 +1900,400,18987.5 +2050,400,21525.5 +580,200,3277.5 +700,200,3822 +900,200,5124.5 +1100,200,6430 +1300,200,7520 +1500,200,8906 +1700,200,10723 +1900,200,13299.5 +2050,200,15577 +580,0,1357.5 +700,0,1669 +900,0,2372 +1100,0,3086 +1300,0,3806.5 +1500,0,4757.5 +1700,0,5809 +1900,0,7768 +2050,0,9798.5 +580,-148.5,0 +680,-149,0 +900,-154.5,0 +1100,-169.5,0 +1300,-191,0 +1500,-219,0 +1700,-249.5,0 +1900,-282.5,0 +2050,-310.5,0 \ No newline at end of file diff --git a/VectoCoreTest/Utils/DelauneyMapTest.cs b/VectoCoreTest/Utils/DelauneyMapTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..eeeef485c53c6bef135bd34c82a630e8805beaa8 --- /dev/null +++ b/VectoCoreTest/Utils/DelauneyMapTest.cs @@ -0,0 +1,163 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TUGraz.VectoCore.Exceptions; +using TUGraz.VectoCore.Utils; + +namespace TUGraz.VectoCore.Tests.Utils +{ + [TestClass] + public class DelauneyMapTest + { + private const double tolerance = 0.00001; + + public static void AssertException<T>(Action func, string message) where T : Exception + { + try + { + func(); + Assert.Fail(); + } + catch (T ex) + { + Assert.AreEqual(message, ex.Message); + } + } + + [TestMethod] + public void Test_Simple_DelauneyMap() + { + var map = new DelauneyMap(); + map.AddPoint(0, 0, 0); + map.AddPoint(1, 0, 0); + map.AddPoint(0, 1, 0); + + map.Triangulate(); + + var result = map.Interpolate(0.25, 0.25); + + Assert.AreEqual(0, result, tolerance); + } + + [TestMethod] + public void Test_DelauneyMapTriangle() + { + var map = new DelauneyMap(); + map.AddPoint(0, 0, 0); + map.AddPoint(1, 0, 1); + map.AddPoint(0, 1, 2); + + map.Triangulate(); + + // fixed points + Assert.AreEqual(0, map.Interpolate(0, 0), tolerance); + Assert.AreEqual(1, map.Interpolate(1, 0), tolerance); + Assert.AreEqual(2, map.Interpolate(0, 1), tolerance); + + // interpolations + Assert.AreEqual(0.5, map.Interpolate(0.5, 0), tolerance); + Assert.AreEqual(1, map.Interpolate(0, 0.5), tolerance); + Assert.AreEqual(1.5, map.Interpolate(0.5, 0.5), tolerance); + + Assert.AreEqual(0.25, map.Interpolate(0.25, 0), tolerance); + Assert.AreEqual(0.5, map.Interpolate(0, 0.25), tolerance); + Assert.AreEqual(0.75, map.Interpolate(0.25, 0.25), tolerance); + + Assert.AreEqual(0.75, map.Interpolate(0.75, 0), tolerance); + Assert.AreEqual(1.5, map.Interpolate(0, 0.75), tolerance); + + // extrapolation (should fail) + AssertException<VectoException>(() => map.Interpolate(1, 1), "Interpolation failed."); + AssertException<VectoException>(() => map.Interpolate(-1, -1), "Interpolation failed."); + AssertException<VectoException>(() => map.Interpolate(1, -1), "Interpolation failed."); + AssertException<VectoException>(() => map.Interpolate(-1, 1), "Interpolation failed."); + } + + + + public void Test_DelauneyMapPlane() + { + var map = new DelauneyMap(); + map.AddPoint(0, 0, 0); + map.AddPoint(1, 0, 1); + map.AddPoint(0, 1, 2); + map.AddPoint(1, 1, 3); + + map.Triangulate(); + + // fixed points + Assert.AreEqual(0, map.Interpolate(0, 0), tolerance); + Assert.AreEqual(1, map.Interpolate(1, 0), tolerance); + Assert.AreEqual(2, map.Interpolate(0, 1), tolerance); + Assert.AreEqual(3, map.Interpolate(1, 1), tolerance); + + // interpolations + Assert.AreEqual(0.5, map.Interpolate(0.5, 0), tolerance); + Assert.AreEqual(1, map.Interpolate(0, 0.5), tolerance); + Assert.AreEqual(2, map.Interpolate(1, 0.5), tolerance); + Assert.AreEqual(2.5, map.Interpolate(0.5, 1), tolerance); + + Assert.AreEqual(1.5, map.Interpolate(0.5, 0.5), tolerance); + + Assert.AreEqual(0.75, map.Interpolate(0.25, 0.25), tolerance); + Assert.AreEqual(2.25, map.Interpolate(0.75, 0.75), tolerance); + + Assert.AreEqual(1.75, map.Interpolate(0.25, 0.75), tolerance); + Assert.AreEqual(1.25, map.Interpolate(0.75, 0.25), tolerance); + + // extrapolation (should fail) + AssertException<VectoException>(() => map.Interpolate(1.5, 0.5), "Interpolation failed."); + AssertException<VectoException>(() => map.Interpolate(1.5, 1.5), "Interpolation failed."); + AssertException<VectoException>(() => map.Interpolate(0.5, 1.5), "Interpolation failed."); + AssertException<VectoException>(() => map.Interpolate(-0.5, 1.5), "Interpolation failed."); + AssertException<VectoException>(() => map.Interpolate(-0.5, 0.5), "Interpolation failed."); + AssertException<VectoException>(() => map.Interpolate(-1.5, -1.5), "Interpolation failed."); + AssertException<VectoException>(() => map.Interpolate(0.5, -0.5), "Interpolation failed."); + AssertException<VectoException>(() => map.Interpolate(-1.5, -0.5), "Interpolation failed."); + } + + [TestMethod] + public void Test_Delauney_LessThan3Points() + { + DelauneyMap map; + try + { + map = new DelauneyMap(); + map.Triangulate(); + Assert.Fail(); + } + catch (ArgumentException ex) + { + Assert.AreEqual("Triangulations needs at least 3 Points. Got 0 Points.", ex.Message); + } + try + { + map = new DelauneyMap(); + map.AddPoint(0, 0, 0); + map.Triangulate(); + Assert.Fail(); + } + catch (ArgumentException ex) + { + Assert.AreEqual("Triangulations needs at least 3 Points. Got 1 Points.", ex.Message); + } + try + { + map = new DelauneyMap(); + map.AddPoint(0, 0, 0); + map.AddPoint(0, 0, 0); + map.Triangulate(); + Assert.Fail(); + } + catch (ArgumentException ex) + { + Assert.AreEqual("Triangulations needs at least 3 Points. Got 2 Points.", ex.Message); + } + + map = new DelauneyMap(); + map.AddPoint(0, 0, 0); + map.AddPoint(1, 0, 0); + map.AddPoint(0, 1, 0); + map.Triangulate(); + } + } +} diff --git a/VectoCoreTest/VectoCoreTest.csproj b/VectoCoreTest/VectoCoreTest.csproj index 8e9c0af4d17c34da9d070e014c5cb5c4f26f9aa1..f1f5f7bc7c5cece9f23c9f20d8de0f04b614d91a 100644 --- a/VectoCoreTest/VectoCoreTest.csproj +++ b/VectoCoreTest/VectoCoreTest.csproj @@ -69,6 +69,7 @@ </Choose> <ItemGroup> <Compile Include="Integration\EngineOnlyCycle\EngineOnlyCycleTest.cs" /> + <Compile Include="Models\SimulationComponentData\FuelConsumptionMapTest.cs" /> <Compile Include="Models\SimulationComponentData\FullLoadCurveTest.cs" /> <Compile Include="Models\SimulationComponent\CombustionEngineTest.cs" /> <Compile Include="Models\Simulation\DrivingCycleTests.cs" /> @@ -81,6 +82,7 @@ <DesignTimeSharedInput>True</DesignTimeSharedInput> <DependentUpon>Settings.settings</DependentUpon> </Compile> + <Compile Include="Utils\DelauneyMapTest.cs" /> <Compile Include="Utils\TestModalDataWriter.cs" /> </ItemGroup> <ItemGroup> @@ -97,6 +99,9 @@ <Generator>SettingsSingleFileGenerator</Generator> <LastGenOutput>Settings.Designer.cs</LastGenOutput> </None> + <None Include="TestData\Components\24t CoachInterpolated.vmap"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </None> <None Include="TestData\Components\FullLoadCurve insufficient columns.vfld"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None>