diff --git a/VectoCore/VectoCore/Models/Declaration/DeclarationData.cs b/VectoCore/VectoCore/Models/Declaration/DeclarationData.cs index 16a12d58dd5afafb00a00331e3c9d936cd71b751..2efb9f45380f1eb28697a6bd70f8e158f7899b62 100644 --- a/VectoCore/VectoCore/Models/Declaration/DeclarationData.cs +++ b/VectoCore/VectoCore/Models/Declaration/DeclarationData.cs @@ -249,34 +249,41 @@ namespace TUGraz.VectoCore.Models.Declaration { var engineSpeed85kmhLastGear = ComputeEngineSpeed85kmh(gears[gears.Count - 1], axlegearRatio, dynamicTyreRadius, engine); - var engineSpeed85kmhSecondToLastGear = ComputeEngineSpeed85kmh(gears[gears.Count - 2], axlegearRatio, - dynamicTyreRadius, engine); + //var engineSpeed85kmhSecondToLastGear = ComputeEngineSpeed85kmh(gears[gears.Count - 2], axlegearRatio, + // dynamicTyreRadius, engine); + + var nVHigh = VectoMath.Min(engineSpeed85kmhLastGear, engine.FullLoadCurve.RatedSpeed); + + var diffRatio = gears[gears.Count - 2].Ratio / gears[gears.Count - 1].Ratio - 1; var maxDragTorque = engine.FullLoadCurve.MaxDragTorque * 1.1; var p1 = new Point(engine.IdleSpeed.Value() / 2, 0); var p2 = new Point(engine.IdleSpeed.Value() * 1.1, 0); - var p3 = new Point(engineSpeed85kmhLastGear.Value() * 0.9, - fullLoadCurve.FullLoadStationaryTorque(engineSpeed85kmhLastGear * 0.9).Value()); + var p3 = new Point(nVHigh.Value() * 0.9, + engine.FullLoadCurve.FullLoadStationaryTorque(nVHigh * 0.9).Value()); var p4 = - new Point((engineSpeed85kmhLastGear + (engineSpeed85kmhSecondToLastGear - engineSpeed85kmhLastGear) / 3).Value(), 0); - var p5 = new Point(fullLoadCurve.RatedSpeed.Value() * 0.95, fullLoadCurve.MaxTorque.Value()); + new Point((nVHigh * (1 + diffRatio / 3)).Value(), 0); + var p5 = new Point(engine.FullLoadCurve.N95hSpeed.Value(), engine.FullLoadCurve.MaxTorque.Value()); var p6 = new Point(p2.X, VectoMath.Interpolate(p1, p3, p2.X)); var p7 = new Point(p4.X, VectoMath.Interpolate(p2, p5, p4.X)); - var fldMargin = ShiftPolygonFldMargin(fullLoadCurve.FullLoadEntries, engineSpeed85kmhLastGear * 0.9); + var fldMargin = ShiftPolygonFldMargin(fullLoadCurve.FullLoadEntries, nVHigh * 0.95); var downshiftCorr = MoveDownshiftBelowFld(Edge.Create(p6, p3), fldMargin, 1.1 * fullLoadCurve.MaxTorque); - var downShift = - new[] { p2, downshiftCorr.P1, downshiftCorr.P2 }.Select( - point => new ShiftPolygon.ShiftPolygonEntry() { - AngularSpeed = point.X.SI<PerSecond>(), - Torque = point.Y.SI<NewtonMeter>() - }).ToList(); - downShift[0].Torque = maxDragTorque; + var downShift = new List<ShiftPolygon.ShiftPolygonEntry>(); + if (gear > 0) { + downShift = + new[] { p2, downshiftCorr.P1, downshiftCorr.P2 }.Select( + point => new ShiftPolygon.ShiftPolygonEntry() { + AngularSpeed = point.X.SI<PerSecond>(), + Torque = point.Y.SI<NewtonMeter>() + }).ToList(); + downShift[0].Torque = maxDragTorque; + } var upShift = new List<ShiftPolygon.ShiftPolygonEntry>(); if (gear >= gears.Count - 1) { return new ShiftPolygon(downShift, upShift); @@ -340,14 +347,14 @@ namespace TUGraz.VectoCore.Models.Declaration Meter dynamicTyreRadius, CombustionEngineData engine) { var engineSpeed = TruckMaxAllowedSpeed / dynamicTyreRadius * axleRatio * gear.Ratio; - if (engineSpeed < engine.IdleSpeed) { - throw new VectoException("engine speed at velocity {0} in gear {1} is below engine's idle speed! {2}", - DeclarationData.Gearbox.TruckMaxAllowedSpeed, gear.Gear, engineSpeed); - } - if (engineSpeed > engine.FullLoadCurve.FullLoadEntries.Last().EngineSpeed) { - throw new VectoException("engine speed at velocity {0} in gear {1} is above engine's max speed! {2}", - DeclarationData.Gearbox.TruckMaxAllowedSpeed, gear.Gear, engineSpeed); - } + //if (engineSpeed < engine.IdleSpeed) { + // throw new VectoException("engine speed at velocity {0} in gear {1} is below engine's idle speed! {2}", + // DeclarationData.Gearbox.TruckMaxAllowedSpeed, gear.Gear, engineSpeed); + //} + //if (engineSpeed > engine.FullLoadCurve.FullLoadEntries.Last().EngineSpeed) { + // throw new VectoException("engine speed at velocity {0} in gear {1} is above engine's max speed! {2}", + // DeclarationData.Gearbox.TruckMaxAllowedSpeed, gear.Gear, engineSpeed); + //} return engineSpeed; } diff --git a/VectoCore/VectoCore/Models/SimulationComponent/Data/Gearbox/ShiftPolygon.cs b/VectoCore/VectoCore/Models/SimulationComponent/Data/Gearbox/ShiftPolygon.cs index 208bd8d08d7486cfb483e4391377b41fc7a334d1..6f5c4a992eb0880344ae41e846bd4c63a4f06a9f 100644 --- a/VectoCore/VectoCore/Models/SimulationComponent/Data/Gearbox/ShiftPolygon.cs +++ b/VectoCore/VectoCore/Models/SimulationComponent/Data/Gearbox/ShiftPolygon.cs @@ -32,6 +32,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.ComponentModel.DataAnnotations; using System.Data; using System.Diagnostics; using System.Linq; @@ -41,6 +42,7 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Gearbox { + [CustomValidation(typeof(ShiftPolygon), "ValidateShiftPolygon")] public class ShiftPolygon : SimulationComponentData { private readonly List<ShiftPolygonEntry> _upShiftPolygon; @@ -99,6 +101,46 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Gearbox get { return _downShiftPolygon.AsReadOnly(); } } + /// <summary> + /// Tests if current power request is on the left side of the shiftpolygon segment + /// </summary> + /// <param name="angularSpeed">The angular speed.</param> + /// <param name="torque">The torque.</param> + /// <param name="segment">Edge of the shift polygon</param> + /// <returns><c>true</c> if current power request is on the left side of the shiftpolygon segment; otherwise, <c>false</c>.</returns> + /// <remarks>Computes a simplified cross product for the vectors: from--X, from--to and checks + /// if the z-component is positive (which means that X was on the right side of from--to).</remarks> + public static bool IsLeftOf(PerSecond angularSpeed, NewtonMeter torque, + Tuple<ShiftPolygonEntry, ShiftPolygonEntry> segment) + { + var abX = segment.Item2.AngularSpeed - segment.Item1.AngularSpeed; + var abY = segment.Item2.Torque - segment.Item1.Torque; + var acX = angularSpeed - segment.Item1.AngularSpeed; + var acY = torque - segment.Item1.Torque; + var z = abX * acY - abY * acX; + return z.IsGreater(0); + } + + /// <summary> + /// Tests if current power request is on the left side of the shiftpolygon segment + /// </summary> + /// <param name="angularSpeed">The angular speed.</param> + /// <param name="torque">The torque.</param> + /// <param name="segment">Edge of the shift polygon</param> + /// <returns><c>true</c> if current power request is on the left side of the shiftpolygon segment; otherwise, <c>false</c>.</returns> + /// <remarks>Computes a simplified cross product for the vectors: from--X, from--to and checks + /// if the z-component is negative (which means that X was on the left side of from--to).</remarks> + public static bool IsRightOf(PerSecond angularSpeed, NewtonMeter torque, + Tuple<ShiftPolygonEntry, ShiftPolygonEntry> segment) + { + var abX = segment.Item2.AngularSpeed - segment.Item1.AngularSpeed; + var abY = segment.Item2.Torque - segment.Item1.Torque; + var acX = angularSpeed - segment.Item1.AngularSpeed; + var acY = torque - segment.Item1.Torque; + var z = abX * acY - abY * acX; + return z.IsSmaller(0); + } + private static bool HeaderIsValid(DataColumnCollection columns) { return columns.Contains(Fields.Torque) && columns.Contains(Fields.AngularSpeedUp) && @@ -124,6 +166,16 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Gearbox }).ToList(); } + public static ValidationResult ValidateShiftPolygon(ShiftPolygon shiftPolygon, ValidationContext validationContext) + { + return shiftPolygon.Downshift.Pairwise(Tuple.Create) + .Any( + downshiftLine => + shiftPolygon.Upshift.Any(upshiftEntry => IsLeftOf(upshiftEntry.AngularSpeed, upshiftEntry.Torque, downshiftLine))) + ? new ValidationResult("upshift line has to be right of the downshift line!") + : ValidationResult.Success; + } + private static class Fields { /// <summary> diff --git a/VectoCore/VectoCore/Models/SimulationComponent/Impl/ShiftStrategy.cs b/VectoCore/VectoCore/Models/SimulationComponent/Impl/ShiftStrategy.cs index f2b5c560ea989f896caaf838112a945f1162b7dd..38b620258c82fee359428a5daf1c1e52fd4b1432 100644 --- a/VectoCore/VectoCore/Models/SimulationComponent/Impl/ShiftStrategy.cs +++ b/VectoCore/VectoCore/Models/SimulationComponent/Impl/ShiftStrategy.cs @@ -83,7 +83,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl return false; } - return IsLeftOf(inEngineSpeed, inTorque, downSection.Item1, downSection.Item2); + return ShiftPolygon.IsLeftOf(inEngineSpeed, inTorque, downSection); } /// <summary> @@ -105,49 +105,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl return true; } - return IsRightOf(inEngineSpeed, inTorque, upSection.Item1, upSection.Item2); - } - - /// <summary> - /// Tests if current power request is on the left side of the shiftpolygon segment - /// </summary> - /// <param name="angularSpeed">The angular speed.</param> - /// <param name="torque">The torque.</param> - /// <param name="from">From.</param> - /// <param name="to">To.</param> - /// <returns><c>true</c> if current power request is on the left side of the shiftpolygon segment; otherwise, <c>false</c>.</returns> - /// <remarks>Computes a simplified cross product for the vectors: from--X, from--to and checks - /// if the z-component is positive (which means that X was on the right side of from--to).</remarks> - private static bool IsLeftOf(PerSecond angularSpeed, NewtonMeter torque, ShiftPolygon.ShiftPolygonEntry from, - ShiftPolygon.ShiftPolygonEntry to) - { - var abX = to.AngularSpeed - from.AngularSpeed; - var abY = to.Torque - from.Torque; - var acX = angularSpeed - from.AngularSpeed; - var acY = torque - from.Torque; - var z = abX * acY - abY * acX; - return z.IsGreater(0); - } - - /// <summary> - /// Tests if current power request is on the left side of the shiftpolygon segment - /// </summary> - /// <param name="angularSpeed">The angular speed.</param> - /// <param name="torque">The torque.</param> - /// <param name="from">From.</param> - /// <param name="to">To.</param> - /// <returns><c>true</c> if current power request is on the left side of the shiftpolygon segment; otherwise, <c>false</c>.</returns> - /// <remarks>Computes a simplified cross product for the vectors: from--X, from--to and checks - /// if the z-component is negative (which means that X was on the left side of from--to).</remarks> - private static bool IsRightOf(PerSecond angularSpeed, NewtonMeter torque, ShiftPolygon.ShiftPolygonEntry from, - ShiftPolygon.ShiftPolygonEntry to) - { - var abX = to.AngularSpeed - from.AngularSpeed; - var abY = to.Torque - from.Torque; - var acX = angularSpeed - from.AngularSpeed; - var acY = torque - from.Torque; - var z = abX * acY - abY * acX; - return z.IsSmaller(0); + return ShiftPolygon.IsRightOf(inEngineSpeed, inTorque, upSection); } } diff --git a/VectoCore/VectoCoreTest/FileIO/SimulationDataReaderTest.cs b/VectoCore/VectoCoreTest/FileIO/SimulationDataReaderTest.cs index cc1bfcc0171f556fa0b5aafd7cee394528331820..db54ae5c58a48362a81a28db121a5b6f1b2118ec 100644 --- a/VectoCore/VectoCoreTest/FileIO/SimulationDataReaderTest.cs +++ b/VectoCore/VectoCoreTest/FileIO/SimulationDataReaderTest.cs @@ -97,20 +97,20 @@ namespace TUGraz.VectoCore.Tests.FileIO Assert.AreEqual(3.7890, runData.EngineData.Inertia.Value()); - var downshiftSpeeds = new[] { 660, 660, 1681.4261 }; - var downshiftTorque = new[] { -163.9, 257.7076, 988.9 }; + var downshiftSpeeds = new[] { 660, 660, 1750.70139 }; + var downshiftTorque = new[] { -163.9, 208.116856, 988.9 }; - Assert.AreEqual(downshiftSpeeds.Length, runData.GearboxData.Gears[1].ShiftPolygon.Downshift.Count); + Assert.AreEqual(downshiftSpeeds.Length, runData.GearboxData.Gears[2].ShiftPolygon.Downshift.Count); for (var i = 0; i < downshiftSpeeds.Length; i++) { Assert.AreEqual(downshiftSpeeds[i].RPMtoRad().Value(), - runData.GearboxData.Gears[1].ShiftPolygon.Downshift[i].AngularSpeed.Value(), Tolerance); - Assert.AreEqual(downshiftTorque[i], runData.GearboxData.Gears[1].ShiftPolygon.Downshift[i].Torque.Value(), Tolerance); + runData.GearboxData.Gears[2].ShiftPolygon.Downshift[i].AngularSpeed.Value(), Tolerance); + Assert.AreEqual(downshiftTorque[i], runData.GearboxData.Gears[2].ShiftPolygon.Downshift[i].Torque.Value(), Tolerance); } var upshiftSpeed = new[] { 1891.2419, 1891.2419, 5798.4116 }; var upshiftTorque = new[] { -163.9, 245.3663, 988.9 }; - Assert.AreEqual(upshiftSpeed.Length, runData.GearboxData.Gears[1].ShiftPolygon.Downshift.Count); + Assert.AreEqual(upshiftSpeed.Length, runData.GearboxData.Gears[2].ShiftPolygon.Downshift.Count); for (var i = 0; i < downshiftSpeeds.Length; i++) { Assert.AreEqual(upshiftSpeed[i].RPMtoRad().Value(), runData.GearboxData.Gears[1].ShiftPolygon.Upshift[i].AngularSpeed.Value(), Tolerance); diff --git a/VectoCore/VectoCoreTest/Models/Declaration/ShiftPolygonTest.cs b/VectoCore/VectoCoreTest/Models/Declaration/ShiftPolygonTest.cs index b2b11f045089920b6b1b7e837b2f42a7160d6cfe..8665e626c28e1a996df5b816f18811e4068c759b 100644 --- a/VectoCore/VectoCoreTest/Models/Declaration/ShiftPolygonTest.cs +++ b/VectoCore/VectoCoreTest/Models/Declaration/ShiftPolygonTest.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.Drawing; +using System.IO; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using NUnit.Framework; +using TUGraz.VectoCommon.InputData; using TUGraz.VectoCommon.Utils; using TUGraz.VectoCore.InputData.FileIO.JSON; using TUGraz.VectoCore.InputData.Reader.DataObjectAdaper; @@ -12,6 +14,7 @@ using TUGraz.VectoCore.Models.Declaration; using TUGraz.VectoCore.Models.SimulationComponent.Data; using TUGraz.VectoCore.Models.SimulationComponent.Data.Engine; using TUGraz.VectoCore.Models.SimulationComponent.Data.Gearbox; +using TUGraz.VectoCore.Tests.Utils; using TUGraz.VectoCore.Utils; using Assert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert; using Point = TUGraz.VectoCore.Utils.Point; @@ -293,21 +296,21 @@ namespace TUGraz.VectoCore.Tests.Models.Declaration // Gear 1 new[] { new Point(136.9338946, -352), - new Point(136.9338946, 1492.7289), - new Point(203.9530637, 2530), + new Point(136.9338946, 1281.30911), + new Point(201.7326, 2427.6748), }, // Gear 2 new[] { new Point(136.9338946, -352), - new Point(136.9338946, 1518.81917), - new Point(201.3375424, 2530), + new Point(136.9338946, 1281.30911), + new Point(203.9530, 2466.9558), }, // Gear 3 new[] { new Point(136.9338946, -352), - new Point(136.9338946, 1538.9278), - new Point(153.606666, 1893.19273), - new Point(192.102071, 2530), + new Point(136.9338946, 1281.30911), + //new Point(153.606666, 1893.19273), + new Point(201.3375, 2420.6842), }, }; @@ -317,7 +320,7 @@ namespace TUGraz.VectoCore.Tests.Models.Declaration var gearboxData = new JSONGearboxDataV5(JSONInputDataFactory.ReadFile(gearboxFile), gearboxFile); var shiftPolygons = new List<ShiftPolygon>(); - for (var i = 1; i <= gearboxData.Gears.Count; i++) { + for (var i = 0; i < gearboxData.Gears.Count; i++) { shiftPolygons.Add(DeclarationData.Gearbox.ComputeShiftPolygon(i, engineData.FullLoadCurve, gearboxData.Gears, engineData, axlegearRatio, rdyn)); } @@ -338,27 +341,151 @@ namespace TUGraz.VectoCore.Tests.Models.Declaration } - //[TestMethod] - //public void CompueShiftPolygonDeclarationTestConfidentialEngine() - //{ - // var engineFldFile = @"E:\QUAM\Downloads\EngineFLD\Map_375c_BB1390_modTUG_R49_375c_BB1386.vfld"; - // var gearboxFile = @"TestData\Components\40t_Long_Haul_Truck.vgbx"; + [TestMethod] + public void CompueShiftPolygonDeclarationTestConfidentialEngine() + { + var engineFldFile = @"E:\QUAM\Downloads\EngineFLD\Map_375c_BB1390_modTUG_R49_375c_BB1386.vfld"; + var gearboxFile = @"TestData\Components\40t_Long_Haul_Truck.vgbx"; + + var rdyn = 0.4882675.SI<Meter>(); + var axlegearRatio = 2.59; + + var engineData = new CombustionEngineData() { + IdleSpeed = 509.RPMtoRad(), + FullLoadCurve = EngineFullLoadCurve.ReadFromFile(engineFldFile, true) + }; + engineData.FullLoadCurve.EngineData = engineData; + + var gearboxData = new JSONGearboxDataV5(JSONInputDataFactory.ReadFile(gearboxFile), gearboxFile); - // var rdyn = 0.4882675.SI<Meter>(); - // var axlegearRatio = 2.59; + var shiftPolygons = new List<ShiftPolygon>(); + var downshiftTransformed = new List<List<Point>>(); + var upshiftOrig = new List<List<Point>>(); + for (var i = 0; i < gearboxData.Gears.Count; i++) { + shiftPolygons.Add(DeclarationData.Gearbox.ComputeShiftPolygon(i, engineData.FullLoadCurve, gearboxData.Gears, + engineData, axlegearRatio, rdyn)); + List<Point> tmp1, tmp2; + ShiftPolygonComparison.ComputShiftPolygonPoints(i, engineData.FullLoadCurve, gearboxData.Gears, + engineData, axlegearRatio, rdyn, out tmp1, out tmp2); + upshiftOrig.Add(tmp1); + downshiftTransformed.Add(tmp2); + } + + ShiftPolygonDrawer.DrawShiftPolygons(Path.GetDirectoryName(gearboxFile), engineData.FullLoadCurve, shiftPolygons, + "R49_375c_BB1386.png", + DeclarationData.Gearbox.TruckMaxAllowedSpeed / rdyn * axlegearRatio * gearboxData.Gears.Last().Ratio, upshiftOrig, + downshiftTransformed); + } + } - // var engineData = new CombustionEngineData() { - // IdleSpeed = 509.RPMtoRad(), - // FullLoadCurve = EngineFullLoadCurve.ReadFromFile(engineFldFile, true) - // }; + [TestFixture] + public class ShiftPolygonComparison + { + const string BasePath = @"E:\QUAM\Workspace\Daten_INTERN\Testfahrzeuge\"; + + [ + TestCase(@"class2_12t_baseline\175kW_Diesel_example.vfld", @"class2_12t_baseline\delivery_12t_example.vgbx", 0.421, + 4.18, 600), + TestCase(@"class2_12t_iaxle_long\175kW_Diesel_example.vfld", @"class2_12t_iaxle_long\delivery_12t_example.vgbx", + 0.421, 2.85, 600), + TestCase(@"class2_12t_iaxle_short\175kW_Diesel_example.vfld", @"class2_12t_iaxle_short\delivery_12t_example.vgbx", + 0.421, 5.33, 600), + TestCase(@"class2_12t_Pmax_high\220kW_Diesel_example.vfld", @"class2_12t_Pmax_high\delivery_12t_example_220kW.vgbx", + 0.421, 4.18, 600), + TestCase(@"class2_12t_Pmax_low\130kW_Diesel_example.vfld", @"class2_12t_Pmax_low\delivery_12t_example.vgbx", 0.421, + 4.18, 600), + TestCase(@"class5_40t_baseline\12L-324kW.vfld", @"class5_40t_baseline\tractor_12gear_example.vgbx", 0.421, 2.64, 600), + TestCase(@"class5_40t_iaxle_long\12L-324kW.vfld", @"class5_40t_iaxle_long\tractor_12gear_example.vgbx", 0.421, 2.31, + 600), + TestCase(@"class5_40t_iaxle_short\12L-324kW.vfld", @"class5_40t_iaxle_short\tractor_12gear_example.vgbx", 0.421, 3.71, + 600), + TestCase(@"class5_40t_Pmax_high\13-9-L-375kW.vfld", @"class5_40t_Pmax_high\tractor_12gear_example.vgbx", 0.421, + 2.64, 600), + TestCase(@"class5_40t_Pmax_low\9-6-L_260kW.vfld", @"class5_40t_Pmax_low\tractor_12gear_example.vgbx", 0.421, 2.64, + 600), + ] + public void ComputeShiftPolygon(string engineFldFile, string gearboxFile, double rdyn, double axlegearRatio, + double idlingSpeed) + { + var engineData = new CombustionEngineData() { + IdleSpeed = idlingSpeed.RPMtoRad(), + FullLoadCurve = EngineFullLoadCurve.ReadFromFile(Path.Combine(BasePath, engineFldFile), true) + }; + engineData.FullLoadCurve.EngineData = engineData; - // var gearboxData = new JSONGearboxDataV5(JSONInputDataFactory.ReadFile(gearboxFile), gearboxFile); + var gearboxData = new JSONGearboxDataV5(JSONInputDataFactory.ReadFile(Path.Combine(BasePath, gearboxFile)), + Path.Combine(BasePath, gearboxFile)); - // var shiftPolygons = new List<ShiftPolygon>(); - // for (var i = 1; i <= gearboxData.Gears.Count; i++) { - // shiftPolygons.Add(DeclarationData.Gearbox.ComputeShiftPolygon(i, engineData.FullLoadCurve, gearboxData.Gears, - // engineData, axlegearRatio, rdyn)); - // } - //} + var shiftPolygons = new List<ShiftPolygon>(); + var downshiftTransformed = new List<List<Point>>(); + var upshiftOrig = new List<List<Point>>(); + for (var i = 0; i < gearboxData.Gears.Count; i++) { + shiftPolygons.Add( + DeclarationData.Gearbox.ComputeShiftPolygon(i, engineData.FullLoadCurve, gearboxData.Gears, + engineData, axlegearRatio, rdyn.SI<Meter>()) + ); + List<Point> tmp1, tmp2; + ComputShiftPolygonPoints(i, engineData.FullLoadCurve, gearboxData.Gears, + engineData, axlegearRatio, rdyn.SI<Meter>(), out tmp1, out tmp2); + upshiftOrig.Add(tmp1); + downshiftTransformed.Add(tmp2); + } + + var imageFile = Path.GetDirectoryName(gearboxFile) + "_" + Path.GetFileNameWithoutExtension(gearboxFile) + "_" + + Path.GetFileNameWithoutExtension(engineFldFile) + + ".png"; + ShiftPolygonDrawer.DrawShiftPolygons(Path.GetDirectoryName(gearboxFile), engineData.FullLoadCurve, shiftPolygons, + imageFile, + DeclarationData.Gearbox.TruckMaxAllowedSpeed / rdyn.SI<Meter>() * axlegearRatio * gearboxData.Gears.Last().Ratio, + upshiftOrig, downshiftTransformed); + } + + + public static void ComputShiftPolygonPoints(int gear, FullLoadCurve fullLoadCurve, + IList<ITransmissionInputData> gears, CombustionEngineData engine, double axlegearRatio, Meter dynamicTyreRadius, + out List<Point> upshiftOrig, out List<Point> downshiftTransformed) + { + var engineSpeed85kmhLastGear = DeclarationData.Gearbox.TruckMaxAllowedSpeed / dynamicTyreRadius * axlegearRatio * + gears[gears.Count - 1].Ratio; + //var engineSpeed85kmhSecondToLastGear = ComputeEngineSpeed85kmh(gears[gears.Count - 2], axlegearRatio, + // dynamicTyreRadius, engine); + + var nVHigh = VectoMath.Min(engineSpeed85kmhLastGear, engine.FullLoadCurve.RatedSpeed); + + var diffRatio = gears[gears.Count - 2].Ratio / gears[gears.Count - 1].Ratio - 1; + + var maxDragTorque = engine.FullLoadCurve.MaxDragTorque * 1.1; + + var p1 = new Point(engine.IdleSpeed.Value() / 2, 0); + var p2 = new Point(engine.IdleSpeed.Value() * 1.1, 0); + var p3 = new Point(nVHigh.Value() * 0.9, + engine.FullLoadCurve.FullLoadStationaryTorque(nVHigh * 0.9).Value()); + + var p4 = + new Point((nVHigh * (1 + diffRatio / 3)).Value(), 0); + var p5 = new Point(engine.FullLoadCurve.N95hSpeed.Value(), engine.FullLoadCurve.MaxTorque.Value()); + + var p6 = new Point(p2.X, VectoMath.Interpolate(p1, p3, p2.X)); + var p7 = new Point(p4.X, VectoMath.Interpolate(p2, p5, p4.X)); + + //var fldMargin = ShiftPolygonFldMargin(fullLoadCurve.FullLoadEntries, nVHigh * 0.95); + //var downshiftCorr = MoveDownshiftBelowFld(Edge.Create(p6, p3), fldMargin, 1.1 * fullLoadCurve.MaxTorque); + upshiftOrig = new[] { p4, p7, p5 }.ToList(); + downshiftTransformed = new List<Point>(); + + if (gear >= gears.Count - 1) { + return; + } + var gearRatio = gears[gear].Ratio / gears[gear + 1].Ratio; + var rpmMarginFactor = 1 + DeclarationData.Gearbox.ShiftPolygonRPMMargin / 100.0; + + var p2p = new Point(p2.X * gearRatio * rpmMarginFactor, p2.Y / gearRatio); + var p3p = new Point(p3.X * gearRatio * rpmMarginFactor, p3.Y / gearRatio); + var p6p = new Point(p6.X * gearRatio * rpmMarginFactor, p6.Y / gearRatio); + var edgeP6pP3p = new Edge(p6p, p3p); + var p3pExt = new Point((1.1 * p5.Y - edgeP6pP3p.OffsetXY) / edgeP6pP3p.SlopeXY, 1.1 * p5.Y); + + downshiftTransformed = new[] { p2p, p6p, p3pExt }.ToList(); + } } } \ No newline at end of file diff --git a/VectoCore/VectoCoreTest/Models/Simulation/FactoryTest.cs b/VectoCore/VectoCoreTest/Models/Simulation/FactoryTest.cs index 42fe90b4a5a97a2994fd4f82897549055b802e11..8eaad5afbeb6962133942a6e14a92bf70b9090d7 100644 --- a/VectoCore/VectoCoreTest/Models/Simulation/FactoryTest.cs +++ b/VectoCore/VectoCoreTest/Models/Simulation/FactoryTest.cs @@ -72,18 +72,19 @@ namespace TUGraz.VectoCore.Tests.Models.Simulation // -- shiftpolygon downshift - Assert.AreEqual(660.RPMtoRad().Value(), gearbox.ModelData.Gears[1].ShiftPolygon.Downshift[0].AngularSpeed.Value(), + // no downshift curve in first gear! + Assert.AreEqual(660.RPMtoRad().Value(), gearbox.ModelData.Gears[2].ShiftPolygon.Downshift[0].AngularSpeed.Value(), 0.0001); - Assert.AreEqual(-163.9, gearbox.ModelData.Gears[1].ShiftPolygon.Downshift[0].Torque.Value(), 0.0001); + Assert.AreEqual(-163.9, gearbox.ModelData.Gears[2].ShiftPolygon.Downshift[0].Torque.Value(), 0.0001); - Assert.AreEqual(660.RPMtoRad().Value(), gearbox.ModelData.Gears[1].ShiftPolygon.Downshift[1].AngularSpeed.Value(), + Assert.AreEqual(660.RPMtoRad().Value(), gearbox.ModelData.Gears[2].ShiftPolygon.Downshift[1].AngularSpeed.Value(), 0.0001); - Assert.AreEqual(257.7076, gearbox.ModelData.Gears[1].ShiftPolygon.Downshift[1].Torque.Value(), 0.1); + Assert.AreEqual(208.116856, gearbox.ModelData.Gears[2].ShiftPolygon.Downshift[1].Torque.Value(), 0.1); - Assert.AreEqual(1681.4261.RPMtoRad().Value(), - gearbox.ModelData.Gears[1].ShiftPolygon.Downshift[2].AngularSpeed.Value(), + Assert.AreEqual(1750.70139.RPMtoRad().Value(), + gearbox.ModelData.Gears[2].ShiftPolygon.Downshift[2].AngularSpeed.Value(), 0.1); - Assert.AreEqual(988.9, gearbox.ModelData.Gears[1].ShiftPolygon.Downshift[2].Torque.Value(), 0.0001); + Assert.AreEqual(988.9, gearbox.ModelData.Gears[2].ShiftPolygon.Downshift[2].Torque.Value(), 0.0001); // -- shiftpolygon upshift diff --git a/VectoCore/VectoCoreTest/Models/SimulationComponentData/ValidationTest.cs b/VectoCore/VectoCoreTest/Models/SimulationComponentData/ValidationTest.cs index e275eb04da1ca358fc914d3506dadba55c7b4b11..37bc421fb4bc5ad3d033f6b14c14871f0f1fb96b 100644 --- a/VectoCore/VectoCoreTest/Models/SimulationComponentData/ValidationTest.cs +++ b/VectoCore/VectoCoreTest/Models/SimulationComponentData/ValidationTest.cs @@ -129,6 +129,7 @@ namespace TUGraz.VectoCore.Tests.Models.SimulationComponentData Assert.IsTrue(engineData.IsValid()); } + [TestMethod] public void Validation_CombustionEngineData_Declaration() { @@ -173,7 +174,7 @@ namespace TUGraz.VectoCore.Tests.Models.SimulationComponentData [TestMethod] public void Validation_VectoRun() { - var container = new VehicleContainer(ExecutionMode.Declaration); + var container = new VehicleContainer(ExecutionMode.Engineering); var data = new DistanceRun(container); var engineData = new CombustionEngineData { FullLoadCurve = EngineFullLoadCurve.ReadFromFile(@"TestData\Components\12t Delivery Truck.vfld"), @@ -217,15 +218,49 @@ namespace TUGraz.VectoCore.Tests.Models.SimulationComponentData "Validation Error: " + string.Join("\n_eng_avg", results.Select(r => r.ErrorMessage))); } + /// <summary> + /// VECTO-249: check upshift is above downshift + /// </summary> + [TestMethod] + public void ShiftPolygonValidationTest() + { + var vgbs = new[] { + "-116,600,1508 ", + "0,600,1508 ", + "293,600,1508 ", + "494,806,1508 ", + "956,1278,2355 ", + }; + + var shiftPolygon = + ShiftPolygon.Create( + VectoCSVFile.ReadStream( + InputDataHelper.InputDataAsStream("engine torque,downshift rpm [rpm],upshift rpm [rpm] ", vgbs))); + + var results = shiftPolygon.Validate(); + Assert.IsFalse(results.Any()); + + shiftPolygon = + ShiftPolygon.Create( + VectoCSVFile.ReadStream( + InputDataHelper.InputDataAsStream("engine torque,upshift rpm [rpm], downshift rpm [rpm] ", vgbs))); + + results = shiftPolygon.Validate(); + Assert.IsTrue(results.Any()); + } + + public class DeepDataObject { [Required, Range(41, 42)] protected int public_field = 5; } + public abstract class ParentDataObject { #region 4 parent instance fields + // ReSharper disable once NotAccessedField.Local [Required, Range(1, 2)] private int private_parent_field = 7; [Required, Range(3, 4)] protected int protected_parent_field = 7; [Required, Range(5, 6)] internal int internal_parent_field = 7; @@ -314,6 +349,7 @@ namespace TUGraz.VectoCore.Tests.Models.SimulationComponentData { #region 4 instance fields + // ReSharper disable once NotAccessedField.Local [Required, Range(1, 2)] private int private_field = 7; [Required, Range(3, 4)] protected int protected_field = 7; [Required, Range(5, 6)] internal int internal_field = 7; diff --git a/VectoCore/VectoCoreTest/Utils/ShiftPolygonDrawer.cs b/VectoCore/VectoCoreTest/Utils/ShiftPolygonDrawer.cs new file mode 100644 index 0000000000000000000000000000000000000000..8b16d06df1468e1624403377522d444d1bff9065 --- /dev/null +++ b/VectoCore/VectoCoreTest/Utils/ShiftPolygonDrawer.cs @@ -0,0 +1,367 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices; +using System.Windows.Forms.DataVisualization.Charting; +using TUGraz.VectoCommon.Utils; +using TUGraz.VectoCore.Configuration; +using TUGraz.VectoCore.Models.SimulationComponent.Data.Engine; +using TUGraz.VectoCore.Models.SimulationComponent.Data.Gearbox; +using Point = TUGraz.VectoCore.Utils.Point; + +namespace TUGraz.VectoCore.Tests.Utils +{ + public class ShiftPolygonDrawer + { + private static readonly Font AxisLabelFont = new Font("Consolas", 10); + private static readonly Font AxisTitleFont = new Font("Verdana", 12); + private static readonly Font LegendFont = new Font("Verdana", 14); + + private static Size _diagramSize = new Size(1000, 800); + + public static void DrawShiftPolygons(string title, EngineFullLoadCurve engineFld, List<ShiftPolygon> polygons, + string imageFileName, PerSecond speed85kmh, List<List<Point>> upshiftOrig = null, + List<List<Point>> downshiftTransformed = null) + { + var numRows = Math.Ceiling(polygons.Count / 4.0); + var numCols = Math.Ceiling(polygons.Count / numRows); + + var chart = new Chart() { + Size = new Size((int)(_diagramSize.Width * numCols), (int)(_diagramSize.Height * numRows)) + }; + var maxX = engineFld.FullLoadEntries.Last().EngineSpeed.Value() / Constants.RPMToRad * 1.1; + + AddLegend(chart); + + var i = 0; + foreach (var shiftPolygon in polygons) { + var chartArea = AddChartArea(chart, "Gear " + (i + 1), "Engine Speed", "Torque", 0, maxX); + + PlotPower(engineFld, chartArea, chart, "engine power " + i); + PlotFLD(engineFld, speed85kmh, chartArea, chart, "Engine Full Load " + i); + + if (upshiftOrig != null && i < upshiftOrig.Count) { + PlotShiftLine("UpshiftOrig " + i, chartArea, chart, Color.Gray, + upshiftOrig[i].Select(pt => pt.X / Constants.RPMToRad).ToList(), upshiftOrig[i].Select(pt => pt.Y).ToList(), true); + } + if (downshiftTransformed != null && i < downshiftTransformed.Count) { + PlotShiftLine("DownTransformed " + i, chartArea, chart, Color.BlueViolet, + downshiftTransformed[i].Select(pt => pt.X / Constants.RPMToRad).ToList(), + downshiftTransformed[i].Select(pt => pt.Y).ToList(), true); + } + + PlotShiftPolygon(i, shiftPolygon, chartArea, chart); + + PositionChartArea(chartArea, 5, i, polygons.Count, 7); + + + i++; + } + + AddTitle(chart, title); + chart.Invalidate(); + chart.SaveImage(imageFileName, ChartImageFormat.Png); + } + + private static void PlotPower(EngineFullLoadCurve engineFld, ChartArea chartArea, Chart chart, string name) + { + var series = new Series { + Name = name, + ChartType = SeriesChartType.Line, + Color = Color.DarkGoldenrod, + BorderWidth = 2, + //Legend = legend.Name, + IsVisibleInLegend = false, + ChartArea = chartArea.Name, + //YAxisType = AxisType.Secondary + }; + series.BorderDashStyle = ChartDashStyle.Dash; + series.BorderWidth = 1; + + var x = new List<double>(); + var y = new List<double>(); + for (var i = engineFld.FullLoadEntries.First().EngineSpeed; + i < engineFld.FullLoadEntries.Last().EngineSpeed; + i += 5.RPMtoRad()) { + x.Add(i.Value() / Constants.RPMToRad); + y.Add(engineFld.FullLoadStationaryPower(i).Value() / 1000); + } + + chart.Series.Add(series); + chart.Series[series.Name].Points.DataBindXY(x, y); + } + + private static void AddTitle(Chart chart, string titleText) + { + var title = new Title { + Text = titleText, + IsDockedInsideChartArea = false, + Font = new Font("Verdana", 18, FontStyle.Bold) + }; + //title.DockedToChartArea = dockToChartArea; + chart.Titles.Add(title); + } + + private static void PositionChartArea(ChartArea chartArea, float titleHeight, int i, int count, float legendHeight) + { + var numRows = Math.Ceiling(count / 4.0); + var numCols = Math.Ceiling(count / numRows); + chartArea.Position.Auto = false; + chartArea.Position.Width = (float)((100.0f) / numCols); + chartArea.Position.Height = (float)((100.0f - titleHeight) / numRows); + chartArea.Position.X = (float)(((i) % numCols) * 100.0f / numCols); + chartArea.Position.Y = (float)(titleHeight + (int)((i) / numCols) * (100 - titleHeight - legendHeight) / numRows); + } + + private static void PlotShiftPolygon(int gear, ShiftPolygon shiftPolygon, ChartArea chartArea, Chart chart) + { + var x = new List<double>(); + var y = new List<double>(); + foreach (var entry in shiftPolygon.Downshift) { + x.Add(entry.AngularSpeed.Value() / Constants.RPMToRad); + y.Add(entry.Torque.Value()); + } + PlotShiftLine("DownShift " + gear, chartArea, chart, Color.DarkRed, x, y); + + + var xUp = new List<double>(); + var yUp = new List<double>(); + foreach (var entry in shiftPolygon.Upshift) { + xUp.Add(entry.AngularSpeed.Value() / Constants.RPMToRad); + yUp.Add(entry.Torque.Value()); + } + PlotShiftLine("UpShift " + gear, chartArea, chart, Color.DarkRed, xUp, yUp); + + + //return series; + } + + private static void PlotShiftLine(string name, ChartArea chartArea, Chart chart, Color color, List<double> x, + List<double> y, bool dashed = false) + { + var series = new Series { + Name = name, + ChartType = SeriesChartType.Line, + Color = color, + BorderWidth = 2, + //Legend = legend.Name, + IsVisibleInLegend = false, + ChartArea = chartArea.Name, + }; + if (dashed) { + series.BorderDashStyle = ChartDashStyle.Dash; + series.BorderWidth = 1; + } + + chart.Series.Add(series); + chart.Series[series.Name].Points.DataBindXY(x, y); + } + + private static void PlotFLD(EngineFullLoadCurve engineFld, PerSecond speed85kmh, ChartArea chartArea, Chart chart, + string name) + { + PlotFullLoad(engineFld, chartArea, chart, name); + + PlotDragLoad(engineFld, chartArea, chart, name); + + PlotEngineSpeedLine(engineFld, chartArea, chart, "nPref " + name, Color.DeepSkyBlue, + engineFld.PreferredSpeed.Value() / Constants.RPMToRad); + + PlotEngineSpeedLine(engineFld, chartArea, chart, "n95h " + name, Color.Red, + engineFld.N95hSpeed.Value() / Constants.RPMToRad); + + PlotEngineSpeedLine(engineFld, chartArea, chart, "n85kmh " + name, Color.LimeGreen, + speed85kmh.Value() / Constants.RPMToRad); + + PlotEngineSpeedLine(engineFld, chartArea, chart, "nPmax " + name, Color.Coral, + engineFld.RatedSpeed.Value() / Constants.RPMToRad); + } + + + private static void PlotEngineSpeedLine(EngineFullLoadCurve engineFld, ChartArea chartArea, Chart chart, string name, + Color color, double n) + { + var seriesNpref = new Series { + Name = name, + ChartType = SeriesChartType.Line, + Color = color, + BorderWidth = 2, + //Legend = legend.Name, + IsVisibleInLegend = false, + ChartArea = chartArea.Name, + }; + chart.Series.Add(seriesNpref); + var xpref = + new[] + { n, n } + .ToList(); + var ypref = new[] { engineFld.MaxDragTorque.Value(), engineFld.MaxLoadTorque.Value() }.ToList(); + chart.Series[seriesNpref.Name].Points.DataBindXY(xpref, ypref); + } + + private static void PlotDragLoad(EngineFullLoadCurve engineFld, ChartArea chartArea, Chart chart, string name) + { + var seriesDrag = new Series { + Name = "Drag-" + name, + ChartType = SeriesChartType.Line, + Color = Color.DarkBlue, + BorderWidth = 2, + //Legend = legend.Name, + IsVisibleInLegend = false, + ChartArea = chartArea.Name, + }; + chart.Series.Add(seriesDrag); + var xDrag = new List<double>(); + var yDrag = new List<double>(); + engineFld.FullLoadEntries.ForEach(entry => { + xDrag.Add(entry.EngineSpeed.Value() / Constants.RPMToRad); + yDrag.Add(entry.TorqueDrag.Value()); + }); + chart.Series[seriesDrag.Name].Points.DataBindXY(xDrag, yDrag); + } + + private static void PlotFullLoad(EngineFullLoadCurve engineFld, ChartArea chartArea, Chart chart, string name) + { + var series = new Series { + Name = name, + ChartType = SeriesChartType.Line, + Color = Color.DarkBlue, + BorderWidth = 2, + //Legend = legend.Name, + IsVisibleInLegend = false, + ChartArea = chartArea.Name, + }; + chart.Series.Add(series); + var x = new List<double>(); + var y = new List<double>(); + engineFld.FullLoadEntries.ForEach(entry => { + x.Add(entry.EngineSpeed.Value() / Constants.RPMToRad); + y.Add(entry.TorqueFullLoad.Value()); + }); + chart.Series[series.Name].Points.DataBindXY(x, y); + } + + private static ChartArea AddChartArea(Chart chart, string name, string axisXTitle, string axisYTitle, double xMin, + double xMax, bool discreteValues = false) + { + var chartArea = new ChartArea { Name = name }; + chartArea.AxisX.MajorGrid.LineColor = Color.DarkGray; + chartArea.AxisY.MajorGrid.LineColor = Color.DarkGray; + chartArea.AxisX.LabelStyle.Font = AxisLabelFont; + chartArea.AxisY.LabelStyle.Font = AxisLabelFont; + + chartArea.AxisX.Interval = xMax / 20.0; + chartArea.AxisX.Maximum = xMax; + chartArea.AxisX.Minimum = xMin; + chartArea.AxisX.MinorGrid.Enabled = true; + chartArea.AxisX.MinorGrid.Interval = (xMax - xMin) / 100.0; + chartArea.AxisX.MinorGrid.LineColor = Color.LightGray; + chartArea.AxisX.Title = axisXTitle; + chartArea.AxisX.TitleFont = AxisTitleFont; + chartArea.AxisX.RoundAxisValues(); + chartArea.AxisX.MajorTickMark.Size = 2 * 100.0f / _diagramSize.Height; + + chartArea.AxisY.Title = axisYTitle; + chartArea.AxisY.TitleFont = AxisTitleFont; + chartArea.AxisY.RoundAxisValues(); + if (discreteValues) { + chartArea.AxisY.MajorGrid.Interval = 1; + chartArea.AxisY.MinorGrid.Enabled = false; + } else { + chartArea.AxisY.MinorGrid.Enabled = true; + } + chartArea.AxisY.MinorGrid.LineColor = Color.LightGray; + chartArea.AxisY.MajorTickMark.Size = 5 * 100.0f / _diagramSize.Width; + + chart.ChartAreas.Add(chartArea); + return chartArea; + } + + private static void AddLegend(Chart chart) + { + var legend = new Legend() + { + Docking = Docking.Bottom, + Alignment = StringAlignment.Center, + IsDockedInsideChartArea = false, + Font = LegendFont, + //Title = "Legend", + }; + legend.CustomItems.Add(new LegendItem() + { + Color = Color.DarkBlue, + Name = "Engine Full Load Curve", + MarkerStyle = MarkerStyle.None, + ImageStyle = LegendImageStyle.Line, + BorderWidth = 3, + }); + legend.CustomItems.Add(new LegendItem() + { + Color = Color.DarkRed, + Name = "Upshift / Downshift", + MarkerStyle = MarkerStyle.None, + ImageStyle = LegendImageStyle.Line, + BorderWidth = 3, + }); + legend.CustomItems.Add(new LegendItem() + { + Color = Color.DeepSkyBlue, + Name = "n_pref", + MarkerStyle = MarkerStyle.None, + ImageStyle = LegendImageStyle.Line, + BorderWidth = 3, + }); + legend.CustomItems.Add(new LegendItem() + { + Color = Color.Coral, + Name = "n_Pmax", + MarkerStyle = MarkerStyle.None, + ImageStyle = LegendImageStyle.Line, + BorderWidth = 3, + }); + legend.CustomItems.Add(new LegendItem() + { + Color = Color.Red, + Name = "n_95h", + MarkerStyle = MarkerStyle.None, + ImageStyle = LegendImageStyle.Line, + BorderWidth = 3, + }); + legend.CustomItems.Add(new LegendItem() + { + Color = Color.LimeGreen, + Name = "n_85km/h", + MarkerStyle = MarkerStyle.None, + ImageStyle = LegendImageStyle.Line, + BorderWidth = 3, + }); + legend.CustomItems.Add(new LegendItem() + { + Color = Color.BlueViolet, + Name = "Downshift next gear", + MarkerStyle = MarkerStyle.None, + ImageStyle = LegendImageStyle.Line, + BorderWidth = 3, + }); + legend.CustomItems.Add(new LegendItem() + { + Color = Color.Gray, + Name = "Upshift orig.", + MarkerStyle = MarkerStyle.None, + ImageStyle = LegendImageStyle.Line, + BorderWidth = 3, + }); + legend.CustomItems.Add(new LegendItem() + { + Color = Color.DarkGoldenrod, + Name = "P", + MarkerStyle = MarkerStyle.None, + ImageStyle = LegendImageStyle.Line, + BorderWidth = 3, + }); + chart.Legends.Add(legend); + } + } +} \ No newline at end of file diff --git a/VectoCore/VectoCoreTest/VectoCoreTest.csproj b/VectoCore/VectoCoreTest/VectoCoreTest.csproj index f9fa26838d2bbc5553d63818712cfe9a96d24797..3147d04a8853c60dbb4abf157f8b6f0dc336ce1d 100644 --- a/VectoCore/VectoCoreTest/VectoCoreTest.csproj +++ b/VectoCore/VectoCoreTest/VectoCoreTest.csproj @@ -145,6 +145,7 @@ <DependentUpon>Settings.settings</DependentUpon> </Compile> <Compile Include="Utils\MockGearbox.cs" /> + <Compile Include="Utils\ShiftPolygonDrawer.cs" /> <Compile Include="Utils\SITest.cs" /> <Compile Include="Utils\DelauneyMapTest.cs" /> <Compile Include="Utils\MockModalDataContainer.cs" />