diff --git a/VectoCore/VectoCore/Models/SimulationComponent/Data/RetarderLossMap.cs b/VectoCore/VectoCore/Models/SimulationComponent/Data/RetarderLossMap.cs index fc8b6904e839e0637f723addc14f50918eab5f57..1b50887eb048019a83d84d3fccea2abe1458a61b 100644 --- a/VectoCore/VectoCore/Models/SimulationComponent/Data/RetarderLossMap.cs +++ b/VectoCore/VectoCore/Models/SimulationComponent/Data/RetarderLossMap.cs @@ -40,29 +40,58 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Models.SimulationComponent.Data { + /// <summary> + /// LossMap for retarder. + /// </summary> public class RetarderLossMap : SimulationComponentData { [ValidateObject] private List<RetarderLossEntry> _entries; + private PerSecond _minSpeed; + private PerSecond _maxSpeed; + /// <summary> + /// Gets the minimal defined speed of the retarder loss map. + /// </summary> + public PerSecond MinSpeed + { + get { return _minSpeed ?? (_minSpeed = _entries.Min(e => e.RetarderSpeed)); } + } + + /// <summary> + /// Gets the maximal defined speed of the retarder loss map. + /// </summary> + public PerSecond MaxSpeed + { + get { return _maxSpeed ?? (_maxSpeed = _entries.Max(e => e.RetarderSpeed)); } + } + + /// <summary> + /// Read the retarder loss map from a file. + /// </summary> + /// <param name="fileName"></param> + /// <returns></returns> public static RetarderLossMap ReadFromFile(string fileName) { try { - DataTable data; - data = VectoCSVFile.Read(fileName); - return Create(data); + return Create(VectoCSVFile.Read(fileName)); } catch (Exception ex) { throw new VectoException("ERROR while loading RetarderLossMap: " + ex.Message); } } + /// <summary> + /// Create the retarder loss map from an appropriate datatable. (2 columns: Retarder Speed, Torque Loss) + /// </summary> + /// <param name="data"></param> + /// <returns></returns> public static RetarderLossMap Create(DataTable data) { if (data.Columns.Count != 2) { - throw new VectoException("RetarderLossMap Data File must consist of 2 columns."); + throw new VectoException("RetarderLossMap Data File must consist of 2 columns: Retarder Speed, Torque Loss"); } if (data.Rows.Count < 2) { - throw new VectoException("RetarderLossMap must consist of at least two entries."); + throw new VectoException("RetarderLossMap must contain at least 2 entries."); } List<RetarderLossEntry> entries; @@ -75,55 +104,27 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data ", ".Join(data.Columns.Cast<DataColumn>().Select(c => c.ColumnName).Reverse())); entries = CreateFromColumnIndizes(data); } + entries.Sort((entry1, entry2) => entry1.RetarderSpeed.Value().CompareTo(entry2.RetarderSpeed.Value())); return new RetarderLossMap { _entries = entries }; } - public NewtonMeter RetarderLoss(PerSecond angularVelocity, bool allowExtrapolation) + /// <summary> + /// Calculates the retarder losses. + /// </summary> + /// <param name="angularVelocity"></param> + /// <returns></returns> + public NewtonMeter RetarderLoss(PerSecond angularVelocity) { - if (angularVelocity < _entries.First().RetarderSpeed) { - if (!allowExtrapolation) { - throw new VectoSimulationException("angular velocity {0} below min. entry in retarder loss map ({1})", - angularVelocity, _entries.First().RetarderSpeed); - } - Log.Warn("Extrapolating retarder losses! Angular velocity {0} below min. entry in retarder loss map ({1})", - angularVelocity, _entries.First().RetarderSpeed); - } - if (angularVelocity > _entries.Last().RetarderSpeed) { - if (!allowExtrapolation) { - throw new VectoSimulationException("angular velocity {0} above max. entry in retarder loss map ({1})", - angularVelocity, _entries.Last().RetarderSpeed); - } - Log.Warn("Extrapolating retarder losses! Angular velocity {0} above max. entry in retarder loss map ({1})", - angularVelocity, _entries.Last().RetarderSpeed); - } - - var idx = FindIndex(angularVelocity); - return VectoMath.Interpolate(_entries[idx - 1].RetarderSpeed, _entries[idx].RetarderSpeed, - _entries[idx - 1].TorqueLoss, _entries[idx].TorqueLoss, angularVelocity); - } - - protected int FindIndex(PerSecond angularVelocity) - { - int idx; - if (angularVelocity < _entries[0].RetarderSpeed) { - Log.Info("requested rpm below minimum rpm in retarder loss map - extrapolating. n_eng_avg: {0}, rpm_min: {1}", - angularVelocity.ConvertTo().Rounds.Per.Minute, - _entries[0].RetarderSpeed.ConvertTo().Rounds.Per.Minute); - idx = 1; - } else { - idx = _entries.FindIndex(x => x.RetarderSpeed > angularVelocity); - } - if (idx <= 0) { - idx = angularVelocity > _entries[0].RetarderSpeed ? _entries.Count - 1 : 1; - } - return idx; + var s = _entries.GetSection(e => e.RetarderSpeed < angularVelocity); + return VectoMath.Interpolate(s.Item1.RetarderSpeed, s.Item2.RetarderSpeed, s.Item1.TorqueLoss, s.Item2.TorqueLoss, + angularVelocity); } private static List<RetarderLossEntry> CreateFromColumnNames(DataTable data) { - return (from DataRow row in data.Rows - select new RetarderLossEntry { + return data.Rows.Cast<DataRow>() + .Select(row => new RetarderLossEntry { RetarderSpeed = row.ParseDouble(Fields.RetarderSpeed).RPMtoRad(), TorqueLoss = row.ParseDouble(Fields.TorqueLoss).SI<NewtonMeter>() }).ToList(); @@ -136,12 +137,11 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data private static List<RetarderLossEntry> CreateFromColumnIndizes(DataTable data) { - return (from DataRow row in data.Rows - select - new RetarderLossEntry { - RetarderSpeed = row.ParseDouble(0).RPMtoRad(), - TorqueLoss = row.ParseDouble(1).SI<NewtonMeter>() - }).ToList(); + return data.Rows.Cast<DataRow>() + .Select(row => new RetarderLossEntry { + RetarderSpeed = row.ParseDouble(0).RPMtoRad(), + TorqueLoss = row.ParseDouble(1).SI<NewtonMeter>() + }).ToList(); } private class RetarderLossEntry diff --git a/VectoCore/VectoCore/Models/SimulationComponent/Impl/Retarder.cs b/VectoCore/VectoCore/Models/SimulationComponent/Impl/Retarder.cs index 7ebcb80621158db334ce2945c72291614cbd4691..68db358a8c3446be766920faade9930b8759c2e1 100644 --- a/VectoCore/VectoCore/Models/SimulationComponent/Impl/Retarder.cs +++ b/VectoCore/VectoCore/Models/SimulationComponent/Impl/Retarder.cs @@ -29,6 +29,7 @@ * Martin Rexeis, rexeis@ivt.tugraz.at, IVT, Graz University of Technology */ +using TUGraz.VectoCommon.Exceptions; using TUGraz.VectoCommon.Models; using TUGraz.VectoCommon.Utils; using TUGraz.VectoCore.Models.Connector.Ports; @@ -39,15 +40,23 @@ using TUGraz.VectoCore.OutputData; namespace TUGraz.VectoCore.Models.SimulationComponent.Impl { + /// <summary> + /// Retarder component. + /// </summary> public class Retarder : StatefulVectoSimulationComponent<SimpleComponentState>, IPowerTrainComponent, ITnInPort, ITnOutPort { protected ITnOutPort NextComponent; - private readonly RetarderLossMap _lossMap; - private double _ratio; + private readonly double _ratio; - public Retarder(IVehicleContainer cockpit, RetarderLossMap lossMap, double ratio) : base(cockpit) + /// <summary> + /// Creates a new Retarder. + /// </summary> + /// <param name="container"></param> + /// <param name="lossMap"></param> + /// <param name="ratio"></param> + public Retarder(IVehicleContainer container, RetarderLossMap lossMap, double ratio) : base(container) { _lossMap = lossMap; _ratio = ratio; @@ -56,11 +65,25 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl protected override void DoWriteModalResults(IModalDataContainer container) { var avgAngularSpeed = (PreviousState.InAngularVelocity + CurrentState.InAngularVelocity) / 2.0; - container[ModalResultField.P_ret_loss] = (PreviousState.InTorque - PreviousState.OutTorque) * avgAngularSpeed; + container[ModalResultField.P_ret_loss] = (CurrentState.InTorque - CurrentState.OutTorque) * avgAngularSpeed; container[ModalResultField.P_retarder_in] = CurrentState.InTorque * avgAngularSpeed; } - protected override void DoCommitSimulationStep() {} + protected override void DoCommitSimulationStep() + { + var avgAngularSpeed = (PreviousState.InAngularVelocity + CurrentState.InAngularVelocity) / 2.0; + if (!avgAngularSpeed.IsBetween(_lossMap.MinSpeed, _lossMap.MaxSpeed)) { + Log.Warn( + "Retarder LossMap data was extrapolated: range for loss map is not sufficient: n:{0} (min:{1}, max:{2}), ratio:{3}", + CurrentState.OutAngularVelocity.AsRPM, _lossMap.MinSpeed.AsRPM, _lossMap.MaxSpeed.AsRPM, _ratio); + if (DataBus.ExecutionMode == ExecutionMode.Declaration) { + throw new VectoException( + "Retarder LossMap data was extrapolated in Declaration mode: range for loss map is not sufficient: n:{0} (min:{1}, max:{2}), ratio:{3}", + CurrentState.OutAngularVelocity.AsRPM, _lossMap.MinSpeed.AsRPM, _lossMap.MaxSpeed.AsRPM, _ratio); + } + } + AdvanceState(); + } public ITnInPort InPort() { @@ -83,20 +106,16 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl return NextComponent.Request(absTime, dt, torque, null, dryRun); } var avgAngularSpeed = (PreviousState.InAngularVelocity + angularVelocity) / 2.0; - var retarderTorqueLoss = - _lossMap.RetarderLoss(avgAngularSpeed * _ratio, DataBus.ExecutionMode != ExecutionMode.Declaration) / _ratio; + var retarderTorqueLoss = _lossMap.RetarderLoss(avgAngularSpeed * _ratio) / _ratio; CurrentState.SetState(torque + retarderTorqueLoss, angularVelocity, torque, angularVelocity); - - return NextComponent.Request(absTime, dt, torque + retarderTorqueLoss, angularVelocity, dryRun); + return NextComponent.Request(absTime, dt, CurrentState.InTorque, CurrentState.InAngularVelocity, dryRun); } public IResponse Initialize(NewtonMeter torque, PerSecond angularVelocity) { - var retarderTorqueLoss = - _lossMap.RetarderLoss(angularVelocity * _ratio, DataBus.ExecutionMode != ExecutionMode.Declaration) / _ratio; + var retarderTorqueLoss = _lossMap.RetarderLoss(angularVelocity * _ratio) / _ratio; PreviousState.SetState(torque + retarderTorqueLoss, angularVelocity, torque, angularVelocity); - - return NextComponent.Initialize(torque + retarderTorqueLoss, angularVelocity); + return NextComponent.Initialize(PreviousState.InTorque, PreviousState.InAngularVelocity); } } } \ No newline at end of file diff --git a/VectoCore/VectoCore/Models/SimulationComponent/VectoSimulationComponent.cs b/VectoCore/VectoCore/Models/SimulationComponent/VectoSimulationComponent.cs index 41285d29e99e13df0781ffb93061abafa5706014..f272e38bda9778514b3f93e60d175fd8da7454d0 100644 --- a/VectoCore/VectoCore/Models/SimulationComponent/VectoSimulationComponent.cs +++ b/VectoCore/VectoCore/Models/SimulationComponent/VectoSimulationComponent.cs @@ -90,12 +90,6 @@ namespace TUGraz.VectoCore.Models.SimulationComponent PreviousState = CurrentState; CurrentState = new TStateType(); } - - //protected virtual Watt GetPowerLoss(SimpleComponentState previousState, SimpleComponentState currentState) - //{ - // return (previousState.InAngularVelocity + currentState.InAngularVelocity) / 2.0 * - // (currentState.InTorque - currentState.OutTorque); - //} } public class SimpleComponentState @@ -106,13 +100,6 @@ namespace TUGraz.VectoCore.Models.SimulationComponent public PerSecond OutAngularVelocity = 0.SI<PerSecond>(); public PerSecond InAngularVelocity = 0.SI<PerSecond>(); - //public NewtonMeter TorqueLoss = 0.SI<NewtonMeter>(); - - //public Watt PowerLoss() - //{ - // return InTorque * InAngularVelocity - OutTorque * OutAngularVelocity; - //} - public void SetState(NewtonMeter inTorque, PerSecond inAngularVelocity, NewtonMeter outTorque, PerSecond outAngularVelocity) { @@ -120,8 +107,6 @@ namespace TUGraz.VectoCore.Models.SimulationComponent InAngularVelocity = inAngularVelocity; OutTorque = outTorque; OutAngularVelocity = outAngularVelocity; - - //TorqueLoss = (inTorque * inAngularVelocity - outTorque * outAngularVelocity) / inAngularVelocity; } } } \ No newline at end of file diff --git a/VectoCore/VectoCoreTest/Models/SimulationComponent/RetarderTest.cs b/VectoCore/VectoCoreTest/Models/SimulationComponent/RetarderTest.cs index 66723a062709ad4b755e8259d44b1793617a5ea6..0449b9b8b77422941ca2e476b59590fe8edfb7fb 100644 --- a/VectoCore/VectoCoreTest/Models/SimulationComponent/RetarderTest.cs +++ b/VectoCore/VectoCoreTest/Models/SimulationComponent/RetarderTest.cs @@ -77,17 +77,25 @@ namespace TUGraz.VectoCore.Tests.Models.SimulationComponent Assert.AreEqual(112, nextRequest.Torque.Value(), Delta); // -------- - outPort.Initialize(50.SI<NewtonMeter>(), 1550.RPMtoRad()); + outPort.Initialize(50.SI<NewtonMeter>(), 650.RPMtoRad()); outPort.Request(absTime, dt, 50.SI<NewtonMeter>(), 1550.RPMtoRad()); - + retarder.CommitSimulationStep(new MockModalDataContainer()); Assert.AreEqual(1550.RPMtoRad().Value(), nextRequest.AngularVelocity.Value(), Delta); - Assert.AreEqual(50 + 14.81, nextRequest.Torque.Value(), Delta); + + // (650+1550)/2 = 1100 => 12.42Nm + Assert.AreEqual(50 + 12.42, nextRequest.Torque.Value(), Delta); + + //VECTO-307: added an additional request after a commit + outPort.Request(absTime, dt, 50.SI<NewtonMeter>(), 450.RPMtoRad()); + Assert.AreEqual(450.RPMtoRad().Value(), nextRequest.AngularVelocity.Value(), Delta); + // avg: (1550+450)/2 = 1000 rpm => 12Nm + Assert.AreEqual(50 + 12, nextRequest.Torque.Value(), Delta); } [TestMethod] public void RetarderRatioTest() { - var vehicle = new VehicleContainer(ExecutionMode.Engineering, null, null); + var vehicle = new VehicleContainer(ExecutionMode.Engineering); var retarderData = RetarderLossMap.ReadFromFile(RetarderLossMapFile); var retarder = new Retarder(vehicle, retarderData, 2.0); @@ -132,12 +140,10 @@ namespace TUGraz.VectoCore.Tests.Models.SimulationComponent retarder.InPort().Connect(nextRequest); var outPort = retarder.OutPort(); - var absTime = 0.SI<Second>(); - var dt = 0.SI<Second>(); - - // -------- - AssertHelper.Exception<VectoSimulationException>(() => outPort.Initialize(50.SI<NewtonMeter>(), 1550.RPMtoRad()), - "angular velocity 324.6312 [1/s] above max. entry in retarder loss map (240.8554 [1/s])"); + outPort.Initialize(50.SI<NewtonMeter>(), 2550.RPMtoRad()); + outPort.Request(0.SI<Second>(), 0.SI<Second>(), 50.SI<NewtonMeter>(), 2550.RPMtoRad()); + AssertHelper.Exception<VectoException>(() => retarder.CommitSimulationStep(new MockModalDataContainer()), + "Retarder LossMap data was extrapolated in Declaration mode: range for loss map is not sufficient: n:2550 (min:0, max:2300), ratio:2"); } [TestMethod]