diff --git a/VectoCore/Models/Connector/Ports/Impl/Response.cs b/VectoCore/Models/Connector/Ports/Impl/Response.cs index 32d8dfd7227e5df03a8cbf8e2212924cf25e8a46..7f229998f19c8cf830e65042bba4bf2c6e699b68 100644 --- a/VectoCore/Models/Connector/Ports/Impl/Response.cs +++ b/VectoCore/Models/Connector/Ports/Impl/Response.cs @@ -10,6 +10,11 @@ namespace TUGraz.VectoCore.Models.Connector.Ports.Impl public class ResponseFailTimeInterval : IResponse { + public ResponseFailTimeInterval(TimeSpan dt) + { + DeltaT = dt; + } public TimeSpan DeltaT { get; set; } } + } \ No newline at end of file diff --git a/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs b/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs index 46396270db95553cba9756b17a6441335985390e..ded4a71bb56782c84f5515f831969c5439c902d1 100644 --- a/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs +++ b/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs @@ -142,13 +142,13 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data /// [m] Travelled distance used for distance-based cycles. If "t" /// is also defined this column will be ignored. /// </summary> - public double Distance { get; set; } + public Meter Distance { get; set; } /// <summary> /// [s] Used for time-based cycles. If neither this nor the distance /// "s" is defined the data will be interpreted as 1Hz. /// </summary> - public double Time { get; set; } + public Second Time { get; set; } /// <summary> /// [m/s] Required except for Engine Only Mode calculations. @@ -164,7 +164,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data /// [s] Required for distance-based cycles. Not used in time based /// cycles. "stop" defines the time the vehicle spends in stop phases. /// </summary> - public double StoppingTime { get; set; } + public Second StoppingTime { get; set; } /// <summary> /// [W] Supply Power input for each auxiliary defined in the @@ -243,7 +243,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data ValidateHeader(table.Columns.Cast<DataColumn>().Select(col => col.ColumnName).ToArray()); return table.Rows.Cast<DataRow>().Select(row => new DrivingCycleEntry { - Distance = row.ParseDouble(Fields.Distance), + Distance = row.ParseDouble(Fields.Distance).SI<Meter>(), VehicleSpeed = row.ParseDouble(Fields.VehicleSpeed).SI().Kilo.Meter.Per.Hour.To<MeterPerSecond>(), RoadGradient = row.ParseDoubleOrGetDefault(Fields.RoadGradient), AdditionalAuxPowerDemand = row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.To<Watt>(), @@ -297,7 +297,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data ValidateHeader(table.Columns.Cast<DataColumn>().Select(col => col.ColumnName).ToArray()); var entries = table.Rows.Cast<DataRow>().Select((row, index) => new DrivingCycleEntry { - Time = row.ParseDoubleOrGetDefault(Fields.Time, index), + Time = row.ParseDoubleOrGetDefault(Fields.Time, index).SI<Second>(), VehicleSpeed = row.ParseDouble(Fields.VehicleSpeed).SI().Kilo.Meter.Per.Hour.To<MeterPerSecond>(), RoadGradient = row.ParseDoubleOrGetDefault(Fields.RoadGradient), AdditionalAuxPowerDemand = row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.To<Watt>(), @@ -368,9 +368,12 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data entry.EngineSpeed); } } - entry.Time = absTime.TotalSeconds; - absTime += new TimeSpan(0, 0, 1); - + if (row.Table.Columns.Contains(Fields.Time)) { + entry.Time = row.ParseDouble(Fields.Time).SI<Second>(); + } else { + entry.Time = absTime.TotalSeconds.SI<Second>(); + absTime += new TimeSpan(0, 0, 1); + } yield return entry; } } @@ -378,6 +381,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data private static void ValidateHeader(string[] header) { var allowedCols = new[] { + Fields.Time, Fields.EngineTorque, Fields.EnginePower, Fields.EngineSpeed, diff --git a/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs b/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs index f6db1ff365ef2703175aabe906cfe35cde6eedc1..72939712dca2c88700fd41a61c18a7b2d0818678 100644 --- a/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs +++ b/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs @@ -35,7 +35,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl /// </summary> private EngineState _currentState = new EngineState(); - private CombustionEngineData _data = new CombustionEngineData(); + private CombustionEngineData _data; private EngineState _previousState = new EngineState(); public CombustionEngine(IVehicleContainer cockpit, CombustionEngineData data) @@ -213,10 +213,10 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl _currentState.StationaryFullLoadPower = Formulas.TorqueToPower(_currentState.StationaryFullLoadTorque, angularFrequency); - var pt1 = _data.GetFullLoadCurve(gear).PT1(angularFrequency) / dt.TotalSeconds; + var pt1 = _data.GetFullLoadCurve(gear).PT1(angularFrequency) / dt.TotalSeconds; var dynFullPowerCalculated = ((1 / (pt1 + 1)) * (_currentState.StationaryFullLoadPower + pt1 * _previousState.EnginePower)).To<Watt>(); - ((1 / (pt1 + 1)) * (_currentState.StationaryFullLoadPower + pt1 * _previousState.EnginePower)).To<Watt>(); + _currentState.DynamicFullLoadPower = dynFullPowerCalculated < _currentState.StationaryFullLoadPower ? dynFullPowerCalculated : _currentState.StationaryFullLoadPower; diff --git a/VectoCore/Models/SimulationComponent/Impl/EngineOnlyDrivingCycle.cs b/VectoCore/Models/SimulationComponent/Impl/EngineOnlyDrivingCycle.cs index 77a65c5c6f640158ce34b7b39b145bbc1fb45bec..85638ea137ff18074c9441ee8d4f44d46935e3f5 100644 --- a/VectoCore/Models/SimulationComponent/Impl/EngineOnlyDrivingCycle.cs +++ b/VectoCore/Models/SimulationComponent/Impl/EngineOnlyDrivingCycle.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using TUGraz.VectoCore.Exceptions; using TUGraz.VectoCore.Models.Connector.Ports; using TUGraz.VectoCore.Models.Connector.Ports.Impl; using TUGraz.VectoCore.Models.Simulation; @@ -17,11 +19,18 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl public EngineOnlyDrivingCycle(IVehicleContainer container, DrivingCycleData cycle) : base(container) { Data = cycle; + _nextEntry = cycle.Entries.GetEnumerator(); + CurrentEntry = _nextEntry.Current; + _nextEntry.MoveNext(); } private ITnOutPort OutPort { get; set; } private int CurrentStep { get; set; } + protected IEnumerator<DrivingCycleData.DrivingCycleEntry> _nextEntry; + protected DrivingCycleData.DrivingCycleEntry NextEntry { get { return _nextEntry.Current; } } + protected DrivingCycleData.DrivingCycleEntry CurrentEntry { get; set; } + #region ITnInPort public void Connect(ITnOutPort other) @@ -42,13 +51,21 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl public IResponse Request(TimeSpan absTime, TimeSpan dt) { - //todo: change to variable time steps - var index = (int) Math.Floor(absTime.TotalSeconds); - if (index >= Data.Entries.Count) { - return new ResponseCycleFinished(); + if (absTime.TotalSeconds < CurrentEntry.Time.Double()) { + Log.ErrorFormat(("cannot go back in time! current: {0} requested: {1}", CurrentEntry.Time, absTime.TotalSeconds)); + throw new VectoSimulationException(String.Format("cannot go back in time! current: {0} requested: {1}", CurrentEntry.Time, absTime.TotalSeconds)); + } + while (NextEntry.Time <= absTime.TotalSeconds) { + CurrentEntry = NextEntry; + if (!_nextEntry.MoveNext()) { + return new ResponseCycleFinished(); + } + } + if (dt.TotalSeconds > (NextEntry.Time - CurrentEntry.Time).Double()) { + return new ResponseFailTimeInterval(TimeSpan.FromSeconds((NextEntry.Time - CurrentEntry.Time).Double())); } - return OutPort.Request(absTime, dt, Data.Entries[index].EngineTorque, Data.Entries[index].EngineSpeed); + return OutPort.Request(absTime, dt, CurrentEntry.EngineTorque, CurrentEntry.EngineSpeed); } #endregion diff --git a/VectoCore/Utils/SI.cs b/VectoCore/Utils/SI.cs index 1e1617ae7eee9408382b3473a33d6f0b3b8a0bdb..9532ab7cb8c4a18f06c90e67c4792cb46d6fdb57 100644 --- a/VectoCore/Utils/SI.cs +++ b/VectoCore/Utils/SI.cs @@ -8,6 +8,11 @@ using TUGraz.VectoCore.Exceptions; namespace TUGraz.VectoCore.Utils { + public class Meter : SI + { + public Meter(double val = 0) : base(val, new SI().Meter) { } + } + public class MeterPerSecond : SI { public MeterPerSecond(double val = 0) : base(val, new SI().Meter.Per.Second) {} @@ -58,6 +63,11 @@ namespace TUGraz.VectoCore.Utils Exponent = 1; } + public double Double() + { + return Val; + } + protected SI(double val, IEnumerable<string> numerator, IEnumerable<string> denominator, bool reciproc = false, bool reverse = false, int exponent = 1) { diff --git a/VectoCoreTest/Models/Simulation/DrivingCycleTests.cs b/VectoCoreTest/Models/Simulation/DrivingCycleTests.cs index e5c4f3a179a8dbe84adcdb5ed85cf1e44493b69b..1f413a5e9a541942a06c2ec03ed617007d4f5027 100644 --- a/VectoCoreTest/Models/Simulation/DrivingCycleTests.cs +++ b/VectoCoreTest/Models/Simulation/DrivingCycleTests.cs @@ -41,6 +41,65 @@ namespace TUGraz.VectoCore.Tests.Models.Simulation Assert.AreEqual(0.SI<NewtonMeter>(), outPort.Torque); } + [TestMethod] + public void TestEngineOnlyWithTimestamps() + { + var container = new VehicleContainer(); + + var cycleData = DrivingCycleData.ReadFromFileEngineOnly(@"TestData\Cycles\Coach Engine Only Paux_var-dt.vdri"); + var cycle = new EngineOnlyDrivingCycle(container, cycleData); + + var outPort = new MockTnOutPort(); + var inPort = cycle.InShaft(); + + inPort.Connect(outPort); + + var absTime = TimeSpan.FromSeconds(10); + var dt = TimeSpan.FromSeconds(1); + + var response = cycle.Request(absTime, dt); + Assert.IsInstanceOfType(response, typeof(ResponseFailTimeInterval)); + + dt = TimeSpan.FromSeconds(0.25); + response = cycle.Request(absTime, dt); + Assert.IsInstanceOfType(response, typeof(ResponseSuccess)); + + var dataWriter = new TestModalDataWriter(); + container.CommitSimulationStep(dataWriter); + + Assert.AreEqual(absTime, outPort.AbsTime); + Assert.AreEqual(dt, outPort.Dt); + Assert.AreEqual(743.2361.RPMtoRad(), outPort.AngularFrequency); + Assert.AreEqual(Formulas.PowerToTorque(2779.576.SI<Watt>(), 743.2361.RPMtoRad()), outPort.Torque); + + // ======================== + + dt = TimeSpan.FromSeconds(1); + absTime = TimeSpan.FromSeconds(500); + response = cycle.Request(absTime, dt); + Assert.IsInstanceOfType(response, typeof(ResponseFailTimeInterval)); + + dt = TimeSpan.FromSeconds(0.25); + + for (int i = 0; i < 2; i++) { + response = cycle.Request(absTime, dt); + Assert.IsInstanceOfType(response, typeof (ResponseSuccess)); + + dataWriter = new TestModalDataWriter(); + container.CommitSimulationStep(dataWriter); + + Assert.AreEqual(absTime, outPort.AbsTime); + Assert.AreEqual(dt, outPort.Dt); + Assert.AreEqual(1584.731.RPMtoRad(), outPort.AngularFrequency); + Assert.AreEqual(Formulas.PowerToTorque(3380.548.SI<Watt>(), 1584.731.RPMtoRad()), outPort.Torque); + + absTime += dt; + } + + + // todo: test going backward in time, end of cycle + } + [TestMethod] public void Test_TimeBased_FirstCycle() { diff --git a/VectoCoreTest/TestData/Cycles/Coach Engine Only Paux_var-dt.csv b/VectoCoreTest/TestData/Cycles/Coach Engine Only Paux_var-dt.vdri similarity index 100% rename from VectoCoreTest/TestData/Cycles/Coach Engine Only Paux_var-dt.csv rename to VectoCoreTest/TestData/Cycles/Coach Engine Only Paux_var-dt.vdri diff --git a/VectoCoreTest/VectoCoreTest.csproj b/VectoCoreTest/VectoCoreTest.csproj index 1d6412df1d2f694efb63bbd900b0c89461604ef6..d77ac7994e6f6666c7de4396ac4607c6235ba6c5 100644 --- a/VectoCoreTest/VectoCoreTest.csproj +++ b/VectoCoreTest/VectoCoreTest.csproj @@ -116,6 +116,9 @@ <None Include="TestData\Components\FullLoadCurve insufficient entries.vfld"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> + <None Include="TestData\Cycles\Coach Engine Only Paux_var-dt.vdri"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </None> <None Include="TestData\Cycles\Coach time based.vdri"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None>