diff --git a/VectoCore/Models/SimulationComponent/Data/AccelerationCurve.cs b/VectoCore/Models/SimulationComponent/Data/AccelerationCurve.cs index 17417da1f038c362ce373c40ba435053f4440a75..c11c9a4bd62d31b72c73dc30e531584b7917f45e 100644 --- a/VectoCore/Models/SimulationComponent/Data/AccelerationCurve.cs +++ b/VectoCore/Models/SimulationComponent/Data/AccelerationCurve.cs @@ -9,158 +9,203 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Models.SimulationComponent.Data { - public class AccelerationCurveData : SimulationComponentData - { - private List<KeyValuePair<MeterPerSecond, AccelerationEntry>> _entries; - - public static AccelerationCurveData ReadFromStream(Stream stream) - { - var data = VectoCSVFile.ReadStream(stream); - return ParseData(data); - } - - public static AccelerationCurveData ReadFromFile(string fileName) - { - var data = VectoCSVFile.Read(fileName); - return ParseData(data); - } - - private static AccelerationCurveData ParseData(DataTable data) - { - if (data.Columns.Count != 3) { - throw new VectoException("Acceleration Limiting File must consist of 3 columns."); - } - - if (data.Rows.Count < 2) { - throw new VectoException("Acceleration Limiting File must consist of at least two entries."); - } - - return new AccelerationCurveData { - _entries = data.Rows.Cast<DataRow>() - .Select(r => new KeyValuePair<MeterPerSecond, AccelerationEntry>( - r.ParseDouble("v").KMPHtoMeterPerSecond(), - new AccelerationEntry { - Acceleration = r.ParseDouble("acc").SI<MeterPerSquareSecond>(), - Deceleration = r.ParseDouble("dec").SI<MeterPerSquareSecond>() - })) - .OrderBy(x => x.Key) - .ToList() - }; - } - - public AccelerationEntry Lookup(MeterPerSecond key) - { - var index = FindIndex(key); - - return new AccelerationEntry { - Acceleration = - VectoMath.Interpolate(_entries[index - 1].Key, _entries[index].Key, _entries[index - 1].Value.Acceleration, - _entries[index].Value.Acceleration, key), - Deceleration = - VectoMath.Interpolate(_entries[index - 1].Key, _entries[index].Key, _entries[index - 1].Value.Deceleration, - _entries[index].Value.Deceleration, key) - }; - } - - protected int FindIndex(MeterPerSecond key) - { - var index = 1; - if (key < _entries[0].Key) { - Log.Error("requested velocity below minimum - extrapolating. velocity: {0}, min: {1}", - key.ConvertTo().Kilo.Meter.Per.Hour, _entries[0].Key.ConvertTo().Kilo.Meter.Per.Hour); - } else { - index = _entries.FindIndex(x => x.Key > key); - if (index <= 0) { - index = (key > _entries[0].Key) ? _entries.Count - 1 : 1; - } - } - return index; - } - - public MeterPerSquareSecond MinDeceleration() - { - return _entries.Max(x => x.Value.Deceleration); - } - - public MeterPerSquareSecond MaxDeceleration() - { - return _entries.Min(x => x.Value.Deceleration); - } - - public MeterPerSquareSecond MinAcceleration() - { - return _entries.Min(x => x.Value.Acceleration); - } - - public MeterPerSquareSecond MaxAcceleration() - { - return _entries.Max(x => x.Value.Acceleration); - } - - [DebuggerDisplay("Acceleration: {Acceleration}, Deceleration: {Deceleration}")] - public class AccelerationEntry - { - public MeterPerSquareSecond Acceleration { get; set; } - public MeterPerSquareSecond Deceleration { get; set; } - } - - /// <summary> - /// - /// </summary> - /// <param name="v1">current speed of the vehicle</param> - /// <param name="v2">desired speed of the vehicle at the end of acceleration/deceleration phase</param> - /// <returns>distance required to accelerate/decelerate the vehicle from v1 to v2 according to the acceleration curve</returns> - public Meter ComputeAccelerationDistance(MeterPerSecond v1, MeterPerSecond v2) - { - var index1 = FindIndex(v1); - var index2 = FindIndex(v2); - - var distance = 0.SI<Meter>(); - for (var i = index2; i <= index1; i++) { - distance += ComputeAccelerationSegmentDistance(i, v1, v2); - } - return distance; - } - - /// <summary> - /// - /// </summary> - /// <param name="i">segment of the acceleration curve to use [(i-1) ... i]</param> - /// <param name="v1">current speed of the vehicle</param> - /// <param name="v2">desired speed of the vehicle at the end of acceleration/deceleration phase</param> - /// <returns>distance required to accelerate/decelerate the vehicle from v1 to v2 according to the acceleration curve</returns> - private Meter ComputeAccelerationSegmentDistance(int i, MeterPerSecond v1, MeterPerSecond v2) - { - var leftEntry = _entries[i - 1]; // entry with lower velocity - var rightEntry = _entries[i]; // entry with higher velocity - - v2 = VectoMath.Max(v2, leftEntry.Key); // min. velocity within current segment - v1 = VectoMath.Min(v1, rightEntry.Key); // max. velocity within current segment - - if (leftEntry.Value.Deceleration.IsEqual(rightEntry.Value.Deceleration)) { - // v(t) = a * t + v1 => t = (v2 - v1) / a - // s(t) = a/2 * t^2 + v1 * t + s0 {s0 == 0} => s(t) - var acceleration = v2 > v1 ? leftEntry.Value.Acceleration : leftEntry.Value.Deceleration; - return ((v2 - v1) * (v2 - v1) / 2.0 / acceleration + v1 * (v2 - v1) / acceleration).Cast<Meter>(); - } - - // a(v) = k * v + d - // dv/dt = a(v) = d * v + d ==> v(t) = sgn(k * v1 + d) * exp(-k * c) / k * exp(t * k) - d / k - // v(0) = v1 => c = - ln(|v1 * k + d|) / k - // v(t) = (v1 + d / k) * exp(t * k) - d / k => t = 1 / k * ln((v2 * k + d) / (v1 * k + d)) - // s(t) = m / k* exp(t * k) + b * t + c' {m = v1 + d / k, b = -d / k} - - var k = (leftEntry.Value.Deceleration - rightEntry.Value.Deceleration) / (leftEntry.Key - rightEntry.Key); - var d = leftEntry.Value.Deceleration - k * leftEntry.Key; - if (v2 > v1) { - k = (leftEntry.Value.Acceleration - rightEntry.Value.Acceleration) / (leftEntry.Key - rightEntry.Key); - d = leftEntry.Value.Acceleration - k * leftEntry.Key; - } - var m = v1 + d / k; - var b = -d / k; - var c = 0.SI<Meter>() - m / k; - var t = Math.Log(((v2 * k + d) / (v1 * k + d)).Cast<Scalar>()) / k; - return m / k * Math.Exp((k * t).Value()) + b * t + c; - } - } + public class AccelerationCurveData : SimulationComponentData + { + private List<KeyValuePair<MeterPerSecond, AccelerationEntry>> _entries; + + public static AccelerationCurveData ReadFromStream(Stream stream) + { + var data = VectoCSVFile.ReadStream(stream); + return ParseData(data); + } + + public static AccelerationCurveData ReadFromFile(string fileName) + { + var data = VectoCSVFile.Read(fileName); + return ParseData(data); + } + + private static AccelerationCurveData ParseData(DataTable data) + { + if (data.Columns.Count != 3) { + throw new VectoException("Acceleration Limiting File must consist of 3 columns."); + } + + if (data.Rows.Count < 2) { + throw new VectoException("Acceleration Limiting File must consist of at least two entries."); + } + + if (HeaderIsValid(data.Columns)) { + return CreateFromColumnNames(data); + } + Logger<AccelerationCurveData>().Warn( + "Acceleration Curve: Header Line is not valid. Expected: '{0}, {1}, {2}', Got: {3}", + Fields.Velocity, Fields.Acceleration, Fields.Deceleration, + string.Join(", ", data.Columns.Cast<DataColumn>().Select(c => c.ColumnName))); + return CreateFromColumnIndizes(data); + } + + private static AccelerationCurveData CreateFromColumnIndizes(DataTable data) + { + return new AccelerationCurveData { + _entries = data.Rows.Cast<DataRow>() + .Select(r => new KeyValuePair<MeterPerSecond, AccelerationEntry>( + r.ParseDouble(0).KMPHtoMeterPerSecond(), + new AccelerationEntry { + Acceleration = r.ParseDouble(1).SI<MeterPerSquareSecond>(), + Deceleration = r.ParseDouble(2).SI<MeterPerSquareSecond>() + })) + .OrderBy(x => x.Key) + .ToList() + }; + } + + private static AccelerationCurveData CreateFromColumnNames(DataTable data) + { + return new AccelerationCurveData { + _entries = data.Rows.Cast<DataRow>() + .Select(r => new KeyValuePair<MeterPerSecond, AccelerationEntry>( + r.ParseDouble(Fields.Velocity).KMPHtoMeterPerSecond(), + new AccelerationEntry { + Acceleration = r.ParseDouble(Fields.Acceleration).SI<MeterPerSquareSecond>(), + Deceleration = r.ParseDouble(Fields.Deceleration).SI<MeterPerSquareSecond>() + })) + .OrderBy(x => x.Key) + .ToList() + }; + } + + private static bool HeaderIsValid(DataColumnCollection columns) + { + return columns.Contains(Fields.Velocity) && + columns.Contains(Fields.Acceleration) && + columns.Contains(Fields.Deceleration); + } + + public AccelerationEntry Lookup(MeterPerSecond key) + { + var index = FindIndex(key); + + return new AccelerationEntry { + Acceleration = + VectoMath.Interpolate(_entries[index - 1].Key, _entries[index].Key, + _entries[index - 1].Value.Acceleration, + _entries[index].Value.Acceleration, key), + Deceleration = + VectoMath.Interpolate(_entries[index - 1].Key, _entries[index].Key, + _entries[index - 1].Value.Deceleration, + _entries[index].Value.Deceleration, key) + }; + } + + protected int FindIndex(MeterPerSecond key) + { + var index = 1; + if (key < _entries[0].Key) { + Log.Error("requested velocity below minimum - extrapolating. velocity: {0}, min: {1}", + key.ConvertTo().Kilo.Meter.Per.Hour, _entries[0].Key.ConvertTo().Kilo.Meter.Per.Hour); + } else { + index = _entries.FindIndex(x => x.Key > key); + if (index <= 0) { + index = (key > _entries[0].Key) ? _entries.Count - 1 : 1; + } + } + return index; + } + + public MeterPerSquareSecond MinDeceleration() + { + return _entries.Max(x => x.Value.Deceleration); + } + + public MeterPerSquareSecond MaxDeceleration() + { + return _entries.Min(x => x.Value.Deceleration); + } + + public MeterPerSquareSecond MinAcceleration() + { + return _entries.Min(x => x.Value.Acceleration); + } + + public MeterPerSquareSecond MaxAcceleration() + { + return _entries.Max(x => x.Value.Acceleration); + } + + [DebuggerDisplay("Acceleration: {Acceleration}, Deceleration: {Deceleration}")] + public class AccelerationEntry + { + public MeterPerSquareSecond Acceleration { get; set; } + public MeterPerSquareSecond Deceleration { get; set; } + } + + /// <summary> + /// + /// </summary> + /// <param name="v1">current speed of the vehicle</param> + /// <param name="v2">desired speed of the vehicle at the end of acceleration/deceleration phase</param> + /// <returns>distance required to accelerate/decelerate the vehicle from v1 to v2 according to the acceleration curve</returns> + public Meter ComputeAccelerationDistance(MeterPerSecond v1, MeterPerSecond v2) + { + var index1 = FindIndex(v1); + var index2 = FindIndex(v2); + + var distance = 0.SI<Meter>(); + for (var i = index2; i <= index1; i++) { + distance += ComputeAccelerationSegmentDistance(i, v1, v2); + } + return distance; + } + + /// <summary> + /// + /// </summary> + /// <param name="i">segment of the acceleration curve to use [(i-1) ... i]</param> + /// <param name="v1">current speed of the vehicle</param> + /// <param name="v2">desired speed of the vehicle at the end of acceleration/deceleration phase</param> + /// <returns>distance required to accelerate/decelerate the vehicle from v1 to v2 according to the acceleration curve</returns> + private Meter ComputeAccelerationSegmentDistance(int i, MeterPerSecond v1, MeterPerSecond v2) + { + var leftEntry = _entries[i - 1]; // entry with lower velocity + var rightEntry = _entries[i]; // entry with higher velocity + + v2 = VectoMath.Max(v2, leftEntry.Key); // min. velocity within current segment + v1 = VectoMath.Min(v1, rightEntry.Key); // max. velocity within current segment + + if (leftEntry.Value.Deceleration.IsEqual(rightEntry.Value.Deceleration)) { + // v(t) = a * t + v1 => t = (v2 - v1) / a + // s(t) = a/2 * t^2 + v1 * t + s0 {s0 == 0} => s(t) + var acceleration = v2 > v1 ? leftEntry.Value.Acceleration : leftEntry.Value.Deceleration; + return ((v2 - v1) * (v2 - v1) / 2.0 / acceleration + v1 * (v2 - v1) / acceleration).Cast<Meter>(); + } + + // a(v) = k * v + d + // dv/dt = a(v) = d * v + d ==> v(t) = sgn(k * v1 + d) * exp(-k * c) / k * exp(t * k) - d / k + // v(0) = v1 => c = - ln(|v1 * k + d|) / k + // v(t) = (v1 + d / k) * exp(t * k) - d / k => t = 1 / k * ln((v2 * k + d) / (v1 * k + d)) + // s(t) = m / k* exp(t * k) + b * t + c' {m = v1 + d / k, b = -d / k} + + var k = (leftEntry.Value.Deceleration - rightEntry.Value.Deceleration) / (leftEntry.Key - rightEntry.Key); + var d = leftEntry.Value.Deceleration - k * leftEntry.Key; + if (v2 > v1) { + k = (leftEntry.Value.Acceleration - rightEntry.Value.Acceleration) / (leftEntry.Key - rightEntry.Key); + d = leftEntry.Value.Acceleration - k * leftEntry.Key; + } + var m = v1 + d / k; + var b = -d / k; + var c = 0.SI<Meter>() - m / k; + var t = Math.Log(((v2 * k + d) / (v1 * k + d)).Cast<Scalar>()) / k; + return m / k * Math.Exp((k * t).Value()) + b * t + c; + } + + private static class Fields + { + public const string Velocity = "v"; + + public const string Acceleration = "acc"; + + public const string Deceleration = "dec"; + } + } } \ No newline at end of file diff --git a/VectoCore/Models/SimulationComponent/Data/AuxiliaryData.cs b/VectoCore/Models/SimulationComponent/Data/AuxiliaryData.cs index 032439971f5e6ddc824e3deb0c4839978d2fd55e..041efbe45c23cea1a791e0b55a75e8fb534d4325 100644 --- a/VectoCore/Models/SimulationComponent/Data/AuxiliaryData.cs +++ b/VectoCore/Models/SimulationComponent/Data/AuxiliaryData.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Data; using System.IO; using System.Linq; @@ -7,50 +8,85 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Models.SimulationComponent.Data { - public class AuxiliaryData - { - public double EfficiencyToSupply { get; set; } - public double TransitionRatio { get; set; } - public double EfficiencyToEngine { get; set; } - - private readonly DelauneyMap _map = new DelauneyMap(); - - public Watt GetPowerDemand(PerSecond nAuxiliary, Watt powerAuxOut) - { - return _map.Interpolate(nAuxiliary.Value(), powerAuxOut.Value()).SI<Watt>(); - } - - public static AuxiliaryData ReadFromFile(string fileName) - { - var auxData = new AuxiliaryData(); - - try { - var stream = new StreamReader(fileName); - stream.ReadLine(); // skip header "Transmission ration to engine rpm [-]" - auxData.TransitionRatio = stream.ReadLine().ToDouble(); - stream.ReadLine(); // skip header "Efficiency to engine [-]" - auxData.EfficiencyToEngine = stream.ReadLine().ToDouble(); - stream.ReadLine(); // skip header "Efficiency auxiliary to supply [-]" - auxData.EfficiencyToSupply = stream.ReadLine().ToDouble(); - - var m = new MemoryStream(Encoding.UTF8.GetBytes(stream.ReadToEnd())); - var table = VectoCSVFile.ReadStream(m); - - var data = table.Rows.Cast<DataRow>().Select(row => new { - AuxiliarySpeed = row.ParseDouble("Auxiliary speed").RPMtoRad(), - MechanicalPower = row.ParseDouble("Mechanical power").SI().Kilo.Watt.Cast<Watt>(), - SupplyPower = row.ParseDouble("Supply power").SI().Kilo.Watt.Cast<Watt>() - }); - - foreach (var d in data) { - auxData._map.AddPoint(d.AuxiliarySpeed.Value(), d.SupplyPower.Value(), d.MechanicalPower.Value()); - } - auxData._map.Triangulate(); - - return auxData; - } catch (FileNotFoundException e) { - throw new VectoException("Auxiliary file not found: " + fileName, e); - } - } - } + public class AuxiliaryData + { + public double EfficiencyToSupply { get; set; } + public double TransitionRatio { get; set; } + public double EfficiencyToEngine { get; set; } + + private readonly DelauneyMap _map = new DelauneyMap(); + + public Watt GetPowerDemand(PerSecond nAuxiliary, Watt powerAuxOut) + { + return _map.Interpolate(nAuxiliary.Value(), powerAuxOut.Value()).SI<Watt>(); + } + + public static AuxiliaryData ReadFromFile(string fileName) + { + var auxData = new AuxiliaryData(); + + try { + var stream = new StreamReader(fileName); + stream.ReadLine(); // skip header "Transmission ration to engine rpm [-]" + auxData.TransitionRatio = stream.ReadLine().IndulgentParse(); + stream.ReadLine(); // skip header "Efficiency to engine [-]" + auxData.EfficiencyToEngine = stream.ReadLine().IndulgentParse(); + stream.ReadLine(); // skip header "Efficiency auxiliary to supply [-]" + auxData.EfficiencyToSupply = stream.ReadLine().IndulgentParse(); + + var m = new MemoryStream(Encoding.UTF8.GetBytes(stream.ReadToEnd())); + var table = VectoCSVFile.ReadStream(m); + + // todo: @@@ check for valid header columns, otherwise use index + if (HeaderIsValid(table.Columns)) { + FillFromColumnNames(table, auxData._map); + } else { + FillFromColumnIndizes(table, auxData._map); + } + + auxData._map.Triangulate(); + + return auxData; + } catch (FileNotFoundException e) { + throw new VectoException("Auxiliary file not found: " + fileName, e); + } + } + + private static void FillFromColumnIndizes(DataTable table, DelauneyMap map) + { + var data = table.Rows.Cast<DataRow>().Select(row => new { + AuxiliarySpeed = row.ParseDouble(0).RPMtoRad(), + MechanicalPower = row.ParseDouble(1).SI().Kilo.Watt.Cast<Watt>(), + SupplyPower = row.ParseDouble(2).SI().Kilo.Watt.Cast<Watt>() + }); + foreach (var d in data) { + map.AddPoint(d.AuxiliarySpeed.Value(), d.SupplyPower.Value(), d.MechanicalPower.Value()); + } + } + + private static void FillFromColumnNames(DataTable table, DelauneyMap map) + { + var data = table.Rows.Cast<DataRow>().Select(row => new { + AuxiliarySpeed = row.ParseDouble(Fields.AuxSpeed).RPMtoRad(), + MechanicalPower = row.ParseDouble(Fields.MechPower).SI().Kilo.Watt.Cast<Watt>(), + SupplyPower = row.ParseDouble(Fields.SupplyPower).SI().Kilo.Watt.Cast<Watt>() + }); + foreach (var d in data) { + map.AddPoint(d.AuxiliarySpeed.Value(), d.SupplyPower.Value(), d.MechanicalPower.Value()); + } + } + + private static bool HeaderIsValid(DataColumnCollection columns) + { + return columns.Contains(Fields.AuxSpeed) && columns.Contains(Fields.MechPower) && + columns.Contains(Fields.SupplyPower); + } + + private static class Fields + { + public const string AuxSpeed = "Auxiliary speed"; + public const string MechPower = "Mechanical power"; + public const string SupplyPower = "Supply power"; + } + } } \ No newline at end of file diff --git a/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs b/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs index 88d514a42a62d4244d62878ec9653ebc00d9fd19..635fd9bec4744db1f20ce00fe871a079f035ef2c 100644 --- a/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs +++ b/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs @@ -9,166 +9,194 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine { - [JsonObject(MemberSerialization.Fields)] - public class FuelConsumptionMap : SimulationComponentData - { - private readonly IList<FuelConsumptionEntry> _entries = new List<FuelConsumptionEntry>(); - private readonly DelauneyMap _fuelMap = new DelauneyMap(); - private FuelConsumptionMap() {} - - public static FuelConsumptionMap ReadFromFile(string fileName) - { - var fuelConsumptionMap = new FuelConsumptionMap(); - var data = VectoCSVFile.Read(fileName); - - try { - foreach (DataRow row in data.Rows) { - try { - var entry = new FuelConsumptionEntry { - EngineSpeed = - row.ParseDouble(Fields.EngineSpeed).SI().Rounds.Per.Minute.Cast<PerSecond>(), - Torque = row.ParseDouble(Fields.Torque).SI<NewtonMeter>(), - FuelConsumption = - row.ParseDouble(Fields.FuelConsumption).SI().Gramm.Per.Hour.ConvertTo().Kilo.Gramm.Per.Second - }; - - if (entry.FuelConsumption < 0) { - throw new ArgumentOutOfRangeException("FuelConsumption", "FuelConsumption < 0 not allowed."); - } - - fuelConsumptionMap._entries.Add(entry); - - // Delauney map works only as expected, when the engineSpeed is in rpm. - fuelConsumptionMap._fuelMap.AddPoint(entry.Torque.Value(), row.ParseDouble(Fields.EngineSpeed), - entry.FuelConsumption.Value()); - } catch (Exception e) { - throw new VectoException(string.Format("Line {0}: {1}", data.Rows.IndexOf(row), e.Message), e); - } - } - } catch (Exception e) { - throw new VectoException(string.Format("File {0}: {1}", fileName, e.Message), e); - } - - fuelConsumptionMap._fuelMap.Triangulate(); - return fuelConsumptionMap; - } - - /// <summary> - /// [kg/s] Calculates the fuel consumption based on the given fuel map, - /// the engineSpeed [rad/s] and the torque [Nm]. - /// </summary> - /// <param name="engineSpeed">[rad/sec]</param> - /// <param name="torque">[Nm]</param> - /// <returns>[kg/s]</returns> - public KilogramPerSecond GetFuelConsumption(NewtonMeter torque, PerSecond engineSpeed) - { - // delauney map needs is initialised with rpm, therefore the engineSpeed has to be converted. - return - _fuelMap.Interpolate(torque.Value(), engineSpeed.ConvertTo().Rounds.Per.Minute.Value()) - .SI() - .Kilo.Gramm.Per.Second.Cast<KilogramPerSecond>(); - } - - private static class Fields - { - /// <summary> - /// [rpm] - /// </summary> - public const string EngineSpeed = "engine speed"; - - /// <summary> - /// [Nm] - /// </summary> - public const string Torque = "torque"; - - /// <summary> - /// [g/h] - /// </summary> - public const string FuelConsumption = "fuel consumption"; - }; - - private class FuelConsumptionEntry - { - /// <summary> - /// engine speed [rad/s] - /// </summary> - public PerSecond EngineSpeed { get; set; } - - /// <summary> - /// Torque [Nm] - /// </summary> - public NewtonMeter Torque { get; set; } - - /// <summary> - /// Fuel consumption [kg/s] - /// </summary> - public SI FuelConsumption { get; set; } - - #region Equality members - - private bool Equals(FuelConsumptionEntry other) - { - Contract.Requires(other != null); - return EngineSpeed.Equals(other.EngineSpeed) && Torque.Equals(other.Torque) && - FuelConsumption.Equals(other.FuelConsumption); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) { - return false; - } - if (ReferenceEquals(this, obj)) { - return true; - } - if (obj.GetType() != GetType()) { - return false; - } - return Equals((FuelConsumptionEntry)obj); - } - - public override int GetHashCode() - { - unchecked { - var hashCode = EngineSpeed.GetHashCode(); - hashCode = (hashCode * 397) ^ Torque.GetHashCode(); - hashCode = (hashCode * 397) ^ FuelConsumption.GetHashCode(); - return hashCode; - } - } - - #endregion - } - - #region Equality members - - protected bool Equals(FuelConsumptionMap other) - { - return _entries.SequenceEqual(other._entries) && Equals(_fuelMap, other._fuelMap); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) { - return false; - } - if (ReferenceEquals(this, obj)) { - return true; - } - if (obj.GetType() != GetType()) { - return false; - } - return Equals((FuelConsumptionMap)obj); - } - - public override int GetHashCode() - { - unchecked { - return ((_entries != null ? _entries.GetHashCode() : 0) * 397) ^ - (_fuelMap != null ? _fuelMap.GetHashCode() : 0); - } - } - - #endregion - } + [JsonObject(MemberSerialization.Fields)] + public class FuelConsumptionMap : SimulationComponentData + { + private readonly IList<FuelConsumptionEntry> _entries = new List<FuelConsumptionEntry>(); + private readonly DelauneyMap _fuelMap = new DelauneyMap(); + private FuelConsumptionMap() {} + + public static FuelConsumptionMap ReadFromFile(string fileName) + { + var fuelConsumptionMap = new FuelConsumptionMap(); + var data = VectoCSVFile.Read(fileName); + + var headerValid = HeaderIsValid(data.Columns); + if (!headerValid) { + Logger<FuelConsumptionMap>().Warn( + "FuelConsumptionMap: Header Line is not valid. Expected: '{0}, {1}, {2}', Got: {3}", + Fields.EngineSpeed, Fields.Torque, Fields.FuelConsumption, + string.Join(", ", data.Columns.Cast<DataColumn>().Select(c => c.ColumnName))); + } + try { + foreach (DataRow row in data.Rows) { + try { + var entry = headerValid ? CreateFromColumNames(row) : CreateFromColumnIndizes(row); + + if (entry.FuelConsumption < 0) { + throw new ArgumentOutOfRangeException("FuelConsumption", "FuelConsumption < 0 not allowed."); + } + + fuelConsumptionMap._entries.Add(entry); + + // Delauney map works only as expected, when the engineSpeed is in rpm. + fuelConsumptionMap._fuelMap.AddPoint(entry.Torque.Value(), + headerValid ? row.ParseDouble(Fields.EngineSpeed) : row.ParseDouble(0), + entry.FuelConsumption.Value()); + } catch (Exception e) { + throw new VectoException(string.Format("Line {0}: {1}", data.Rows.IndexOf(row), e.Message), e); + } + } + } catch (Exception e) { + throw new VectoException(string.Format("File {0}: {1}", fileName, e.Message), e); + } + + fuelConsumptionMap._fuelMap.Triangulate(); + return fuelConsumptionMap; + } + + private static bool HeaderIsValid(DataColumnCollection columns) + { + return columns.Contains(Fields.EngineSpeed) && columns.Contains(Fields.Torque) && + columns.Contains(Fields.FuelConsumption); + } + + private static FuelConsumptionEntry CreateFromColumnIndizes(DataRow row) + { + return new FuelConsumptionEntry { + EngineSpeed = row.ParseDouble(0).RPMtoRad(), + Torque = row.ParseDouble(1).SI<NewtonMeter>(), + FuelConsumption = + row.ParseDouble(2).SI().Gramm.Per.Hour.ConvertTo().Kilo.Gramm.Per.Second + }; + } + + private static FuelConsumptionEntry CreateFromColumNames(DataRow row) + { + return new FuelConsumptionEntry { + EngineSpeed = row.ParseDouble(Fields.EngineSpeed).SI().Rounds.Per.Minute.Cast<PerSecond>(), + Torque = row.ParseDouble(Fields.Torque).SI<NewtonMeter>(), + FuelConsumption = + row.ParseDouble(Fields.FuelConsumption).SI().Gramm.Per.Hour.ConvertTo().Kilo.Gramm.Per.Second + }; + } + + /// <summary> + /// [kg/s] Calculates the fuel consumption based on the given fuel map, + /// the engineSpeed [rad/s] and the torque [Nm]. + /// </summary> + /// <param name="engineSpeed">[rad/sec]</param> + /// <param name="torque">[Nm]</param> + /// <returns>[kg/s]</returns> + public KilogramPerSecond GetFuelConsumption(NewtonMeter torque, PerSecond engineSpeed) + { + // delauney map needs is initialised with rpm, therefore the engineSpeed has to be converted. + return + _fuelMap.Interpolate(torque.Value(), engineSpeed.ConvertTo().Rounds.Per.Minute.Value()) + .SI() + .Kilo.Gramm.Per.Second.Cast<KilogramPerSecond>(); + } + + private static class Fields + { + /// <summary> + /// [rpm] + /// </summary> + public const string EngineSpeed = "engine speed"; + + /// <summary> + /// [Nm] + /// </summary> + public const string Torque = "torque"; + + /// <summary> + /// [g/h] + /// </summary> + public const string FuelConsumption = "fuel consumption"; + }; + + private class FuelConsumptionEntry + { + /// <summary> + /// engine speed [rad/s] + /// </summary> + public PerSecond EngineSpeed { get; set; } + + /// <summary> + /// Torque [Nm] + /// </summary> + public NewtonMeter Torque { get; set; } + + /// <summary> + /// Fuel consumption [kg/s] + /// </summary> + public SI FuelConsumption { get; set; } + + #region Equality members + + private bool Equals(FuelConsumptionEntry other) + { + Contract.Requires(other != null); + return EngineSpeed.Equals(other.EngineSpeed) && Torque.Equals(other.Torque) && + FuelConsumption.Equals(other.FuelConsumption); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) { + return false; + } + if (ReferenceEquals(this, obj)) { + return true; + } + if (obj.GetType() != GetType()) { + return false; + } + return Equals((FuelConsumptionEntry)obj); + } + + public override int GetHashCode() + { + unchecked { + var hashCode = EngineSpeed.GetHashCode(); + hashCode = (hashCode * 397) ^ Torque.GetHashCode(); + hashCode = (hashCode * 397) ^ FuelConsumption.GetHashCode(); + return hashCode; + } + } + + #endregion + } + + #region Equality members + + protected bool Equals(FuelConsumptionMap other) + { + return _entries.SequenceEqual(other._entries) && Equals(_fuelMap, other._fuelMap); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) { + return false; + } + if (ReferenceEquals(this, obj)) { + return true; + } + if (obj.GetType() != GetType()) { + return false; + } + return Equals((FuelConsumptionMap)obj); + } + + public override int GetHashCode() + { + unchecked { + return ((_entries != null ? _entries.GetHashCode() : 0) * 397) ^ + (_fuelMap != null ? _fuelMap.GetHashCode() : 0); + } + } + + #endregion + } } \ No newline at end of file diff --git a/VectoCore/Models/SimulationComponent/Impl/Vehicle.cs b/VectoCore/Models/SimulationComponent/Impl/Vehicle.cs index 365add8e0de8a7891c97656abaa9eddeb0912395..4402ce894926474fb208780460e22c2422e303ae 100644 --- a/VectoCore/Models/SimulationComponent/Impl/Vehicle.cs +++ b/VectoCore/Models/SimulationComponent/Impl/Vehicle.cs @@ -13,310 +13,322 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Models.SimulationComponent.Impl { - public class Vehicle : VectoSimulationComponent, IVehicle, IMileageCounter, IFvInPort, IDriverDemandOutPort - { - protected IFvOutPort NextComponent; - private VehicleState _previousState; - private VehicleState _currentState; - private readonly VehicleData _data; - - private readonly Point[] _airResistanceCurve; - - public MeterPerSecond VehicleSpeed - { - get { return _previousState.Velocity; } - } - - public Kilogram VehicleMass - { - get { return _data.TotalCurbWeight(); } - } - - public Kilogram VehicleLoading - { - get { return _data.Loading; } - } - - public Kilogram TotalMass - { - get { return _data.TotalVehicleWeight(); } - } - - public Meter Distance - { - get { return _previousState.Distance; } - } - - - public Vehicle(IVehicleContainer container, VehicleData data) : base(container) - { - _data = data; - _previousState = new VehicleState { Distance = 0.SI<Meter>(), Velocity = 0.SI<MeterPerSecond>() }; - _currentState = new VehicleState { Distance = 0.SI<Meter>(), Velocity = 0.SI<MeterPerSecond>() }; - - var values = DeclarationData.AirDrag.Lookup(_data.VehicleCategory); - _airResistanceCurve = CalculateAirResistanceCurve(values); - } - - - public IFvInPort InPort() - { - return this; - } - - public IDriverDemandOutPort OutPort() - { - return this; - } - - public void Connect(IFvOutPort other) - { - NextComponent = other; - } - - public IResponse Initialize(MeterPerSecond vehicleSpeed, Radian roadGradient) - { - _previousState = new VehicleState { - Distance = DataBus.CycleStartDistance, - Velocity = vehicleSpeed, - RollingResistance = RollingResistance(roadGradient), - SlopeResistance = SlopeResistance(roadGradient) - }; - _previousState.AirDragResistance = AirDragResistance(0.SI<MeterPerSquareSecond>(), - Constants.SimulationSettings.TargetTimeInterval); - _previousState.VehicleAccelerationForce = _previousState.RollingResistance - + _previousState.AirDragResistance - + _previousState.SlopeResistance; - - _currentState = new VehicleState { - Distance = DataBus.CycleStartDistance, - Velocity = vehicleSpeed, - AirDragResistance = _previousState.AirDragResistance, - RollingResistance = _previousState.RollingResistance, - SlopeResistance = _previousState.SlopeResistance, - VehicleAccelerationForce = _previousState.VehicleAccelerationForce, - }; - - - return NextComponent.Initialize(_currentState.VehicleAccelerationForce, vehicleSpeed); - } - - public IResponse Initialize(MeterPerSecond vehicleSpeed, MeterPerSquareSecond startAcceleration, Radian roadGradient) - { - var tmp = _previousState.Velocity; - // set vehicle speed to get accurate airdrag resistance - _previousState.Velocity = vehicleSpeed; - _currentState.Velocity = vehicleSpeed + startAcceleration * Constants.SimulationSettings.TargetTimeInterval; - var vehicleAccelerationForce = DriverAcceleration(startAcceleration) + RollingResistance(roadGradient) + - AirDragResistance(startAcceleration, Constants.SimulationSettings.TargetTimeInterval) + - SlopeResistance(roadGradient); - - var retVal = NextComponent.Initialize(vehicleAccelerationForce, vehicleSpeed); - - _previousState.Velocity = tmp; - _currentState.Velocity = tmp; - return retVal; - } - - public IResponse Request(Second absTime, Second dt, MeterPerSquareSecond acceleration, Radian gradient, - bool dryRun = false) - { - Log.Debug("from Wheels: acceleration: {0}", acceleration); - _currentState.dt = dt; - _currentState.Acceleration = acceleration; - _currentState.Velocity = _previousState.Velocity + acceleration * dt; - if (_currentState.Velocity.IsEqual(0.SI<MeterPerSecond>(), Constants.SimulationSettings.VehicleSpeedHaltTolerance)) { - _currentState.Velocity = 0.SI<MeterPerSecond>(); - } - _currentState.Distance = _previousState.Distance + _previousState.Velocity * dt + acceleration * dt * dt / 2; - - _currentState.DriverAcceleration = DriverAcceleration(acceleration); - _currentState.RollingResistance = RollingResistance(gradient); - _currentState.AirDragResistance = AirDragResistance(acceleration, dt); - _currentState.SlopeResistance = SlopeResistance(gradient); - - // DriverAcceleration = vehicleAccelerationForce - RollingResistance - AirDragResistance - SlopeResistance - _currentState.VehicleAccelerationForce = _currentState.DriverAcceleration - + _currentState.RollingResistance - + _currentState.AirDragResistance - + _currentState.SlopeResistance; - - var retval = NextComponent.Request(absTime, dt, _currentState.VehicleAccelerationForce, _currentState.Velocity, - dryRun); - return retval; - } - - protected override void DoWriteModalResults(IModalDataWriter writer) - { - var averageVelocity = (_previousState.Velocity + _currentState.Velocity) / 2; - - writer[ModalResultField.v_act] = averageVelocity; - writer[ModalResultField.PaVeh] = ((_previousState.VehicleAccelerationForce * _previousState.Velocity + - _currentState.VehicleAccelerationForce * _currentState.Velocity) / 2.0).Cast<Watt>(); - writer[ModalResultField.Pgrad] = ((_previousState.SlopeResistance * _previousState.Velocity + - _currentState.SlopeResistance * _currentState.Velocity) / 2.0).Cast<Watt>(); - writer[ModalResultField.Proll] = ((_previousState.RollingResistance * _previousState.Velocity + - _currentState.RollingResistance * _currentState.Velocity) / 2.0).Cast<Watt>(); - - writer[ModalResultField.Pair] = ComputeAirDragPowerLoss(_previousState.Velocity, _currentState.Velocity, - _currentState.dt); - - - // sanity check: is the vehicle in step with the cycle? - if (writer[ModalResultField.dist] == DBNull.Value) { - Log.Warn("distance field is not set!"); - } else { - var distance = (SI)writer[ModalResultField.dist]; - if (!distance.IsEqual(_currentState.Distance, 1e-12.SI<Meter>())) { - Log.Warn("distance diverges: {0}, distance: {1}", (distance - _currentState.Distance).Value(), distance); - } - } - } - - - protected override void DoCommitSimulationStep() - { - _previousState = _currentState; - _currentState = new VehicleState(); - } - - protected Newton RollingResistance(Radian gradient) - { - var retVal = (Math.Cos(gradient.Value()) * _data.TotalVehicleWeight() * - Physics.GravityAccelleration * - _data.TotalRollResistanceCoefficient).Cast<Newton>(); - Log.Debug("RollingResistance: {0}", retVal); - return retVal; - } - - - protected Newton DriverAcceleration(MeterPerSquareSecond accelleration) - { - var retVal = ((_data.TotalVehicleWeight() + _data.ReducedMassWheels) * accelleration).Cast<Newton>(); - Log.Debug("DriverAcceleration: {0}", retVal); - return retVal; - } - - - protected internal Newton SlopeResistance(Radian gradient) - { - var retVal = (_data.TotalVehicleWeight() * Physics.GravityAccelleration * Math.Sin(gradient.Value())).Cast<Newton>(); - Log.Debug("SlopeResistance: {0}", retVal); - return retVal; - } - - - protected internal Newton AirDragResistance(MeterPerSquareSecond acceleration, Second dt) - { - var vAverage = _previousState.Velocity + acceleration * dt / 2; - if (vAverage.IsEqual(0)) { - return 0.SI<Newton>(); - } - var result = (ComputeAirDragPowerLoss(_previousState.Velocity, _previousState.Velocity + acceleration * dt, dt) / - vAverage).Cast<Newton>(); - - Log.Debug("AirDragResistance: {0}", result); - return result; - } - - private Watt ComputeAirDragPowerLoss(MeterPerSecond v1, MeterPerSecond v2, Second dt) - { - var vAverage = (v1 + v2) / 2; - var CdA = ComputeEffectiveAirDragArea(vAverage); - Watt averageAirDragPower; - if (v1.IsEqual(v2)) { - averageAirDragPower = (Physics.AirDensity / 2.0 * CdA * vAverage * vAverage * vAverage).Cast<Watt>(); - } else { - // compute the average force within the current simulation interval - // P(t) = k * v(t)^3 , v(t) = v0 + a * t // a != 0 - // => P_avg = (CdA * rho/2)/(4*a * dt) * (v2^4 - v1^4) - var acceleration = (v2 - v1) / dt; - averageAirDragPower = - (Physics.AirDensity / 2.0 * CdA * (v2 * v2 * v2 * v2 - v1 * v1 * v1 * v1) / (4 * acceleration * dt)).Cast<Watt>(); - } - return averageAirDragPower; - } - - protected internal SquareMeter ComputeEffectiveAirDragArea(MeterPerSecond velocity) - { - var CdA = _data.AerodynamicDragAera; - switch (_data.CrossWindCorrectionMode) { - case CrossWindCorrectionMode.NoCorrection: - break; - case CrossWindCorrectionMode.DeclarationModeCorrection: - CdA = AirDragInterpolate(velocity); - break; - default: - throw new NotImplementedException(string.Format(" is not implemented", _data.CrossWindCorrectionMode)); - } - return CdA; - } - - private SquareMeter AirDragInterpolate(MeterPerSecond x) - { - var p = _airResistanceCurve.GetSection(c => c.X < x); - - if (x < p.Item1.X || p.Item2.X < x) { - Log.Error(_data.CrossWindCorrectionMode == CrossWindCorrectionMode.VAirBetaLookupTable - ? string.Format("CdExtrapol β = {0}", x) - : string.Format("CdExtrapol v = {0}", x)); - } - - return VectoMath.Interpolate(p.Item1.X, p.Item2.X, p.Item1.Y, p.Item2.Y, x); - } - - protected Point[] CalculateAirResistanceCurve(AirDrag.AirDragEntry values) - { - var points = new List<Point> { new Point { X = 0.SI<MeterPerSecond>(), Y = 0.SI<SquareMeter>() } }; - - for (var speed = 60; speed <= 100; speed += 5) { - var vVeh = speed.KMPHtoMeterPerSecond(); - var cdASum = 0.0.SI<SquareMeter>(); - for (var alpha = 0; alpha <= 180; alpha += 10) { - var vWindX = Physics.BaseWindSpeed * Math.Cos(alpha.ToRadian()); - var vWindY = Physics.BaseWindSpeed * Math.Sin(alpha.ToRadian()); - var vAirX = vVeh + vWindX; - var vAirY = vWindY; + public class Vehicle : VectoSimulationComponent, IVehicle, IMileageCounter, IFvInPort, IDriverDemandOutPort + { + protected IFvOutPort NextComponent; + private VehicleState _previousState; + private VehicleState _currentState; + private readonly VehicleData _data; + + private readonly Point[] _airResistanceCurve; + + public MeterPerSecond VehicleSpeed + { + get { return _previousState.Velocity; } + } + + public Kilogram VehicleMass + { + get { return _data.TotalCurbWeight(); } + } + + public Kilogram VehicleLoading + { + get { return _data.Loading; } + } + + public Kilogram TotalMass + { + get { return _data.TotalVehicleWeight(); } + } + + public Meter Distance + { + get { return _previousState.Distance; } + } + + + public Vehicle(IVehicleContainer container, VehicleData data) : base(container) + { + _data = data; + _previousState = new VehicleState { Distance = 0.SI<Meter>(), Velocity = 0.SI<MeterPerSecond>() }; + _currentState = new VehicleState { Distance = 0.SI<Meter>(), Velocity = 0.SI<MeterPerSecond>() }; + + var values = DeclarationData.AirDrag.Lookup(_data.VehicleCategory); + _airResistanceCurve = CalculateAirResistanceCurve(values); + } + + + public IFvInPort InPort() + { + return this; + } + + public IDriverDemandOutPort OutPort() + { + return this; + } + + public void Connect(IFvOutPort other) + { + NextComponent = other; + } + + public IResponse Initialize(MeterPerSecond vehicleSpeed, Radian roadGradient) + { + _previousState = new VehicleState { + Distance = DataBus.CycleStartDistance, + Velocity = vehicleSpeed, + RollingResistance = RollingResistance(roadGradient), + SlopeResistance = SlopeResistance(roadGradient) + }; + _previousState.AirDragResistance = AirDragResistance(0.SI<MeterPerSquareSecond>(), + Constants.SimulationSettings.TargetTimeInterval); + _previousState.VehicleAccelerationForce = _previousState.RollingResistance + + _previousState.AirDragResistance + + _previousState.SlopeResistance; + + _currentState = new VehicleState { + Distance = DataBus.CycleStartDistance, + Velocity = vehicleSpeed, + AirDragResistance = _previousState.AirDragResistance, + RollingResistance = _previousState.RollingResistance, + SlopeResistance = _previousState.SlopeResistance, + VehicleAccelerationForce = _previousState.VehicleAccelerationForce, + }; + + + return NextComponent.Initialize(_currentState.VehicleAccelerationForce, vehicleSpeed); + } + + public IResponse Initialize(MeterPerSecond vehicleSpeed, MeterPerSquareSecond startAcceleration, + Radian roadGradient) + { + var tmp = _previousState.Velocity; + // set vehicle speed to get accurate airdrag resistance + _previousState.Velocity = vehicleSpeed; + _currentState.Velocity = vehicleSpeed + startAcceleration * Constants.SimulationSettings.TargetTimeInterval; + var vehicleAccelerationForce = DriverAcceleration(startAcceleration) + RollingResistance(roadGradient) + + AirDragResistance(startAcceleration, + Constants.SimulationSettings.TargetTimeInterval) + + SlopeResistance(roadGradient); + + var retVal = NextComponent.Initialize(vehicleAccelerationForce, vehicleSpeed); + + _previousState.Velocity = tmp; + _currentState.Velocity = tmp; + return retVal; + } + + public IResponse Request(Second absTime, Second dt, MeterPerSquareSecond acceleration, Radian gradient, + bool dryRun = false) + { + Log.Debug("from Wheels: acceleration: {0}", acceleration); + _currentState.dt = dt; + _currentState.Acceleration = acceleration; + _currentState.Velocity = _previousState.Velocity + acceleration * dt; + if (_currentState.Velocity.IsEqual(0.SI<MeterPerSecond>(), + Constants.SimulationSettings.VehicleSpeedHaltTolerance)) { + _currentState.Velocity = 0.SI<MeterPerSecond>(); + } + _currentState.Distance = _previousState.Distance + _previousState.Velocity * dt + acceleration * dt * dt / 2; + + _currentState.DriverAcceleration = DriverAcceleration(acceleration); + _currentState.RollingResistance = RollingResistance(gradient); + _currentState.AirDragResistance = AirDragResistance(acceleration, dt); + _currentState.SlopeResistance = SlopeResistance(gradient); + + // DriverAcceleration = vehicleAccelerationForce - RollingResistance - AirDragResistance - SlopeResistance + _currentState.VehicleAccelerationForce = _currentState.DriverAcceleration + + _currentState.RollingResistance + + _currentState.AirDragResistance + + _currentState.SlopeResistance; + + var retval = NextComponent.Request(absTime, dt, _currentState.VehicleAccelerationForce, + _currentState.Velocity, + dryRun); + return retval; + } + + protected override void DoWriteModalResults(IModalDataWriter writer) + { + var averageVelocity = (_previousState.Velocity + _currentState.Velocity) / 2; + + writer[ModalResultField.v_act] = averageVelocity; + writer[ModalResultField.PaVeh] = ((_previousState.VehicleAccelerationForce * _previousState.Velocity + + _currentState.VehicleAccelerationForce * _currentState.Velocity) / 2.0) + .Cast<Watt>(); + writer[ModalResultField.Pgrad] = ((_previousState.SlopeResistance * _previousState.Velocity + + _currentState.SlopeResistance * _currentState.Velocity) / 2.0).Cast<Watt> + (); + writer[ModalResultField.Proll] = ((_previousState.RollingResistance * _previousState.Velocity + + _currentState.RollingResistance * _currentState.Velocity) / 2.0) + .Cast<Watt>(); + + writer[ModalResultField.Pair] = ComputeAirDragPowerLoss(_previousState.Velocity, _currentState.Velocity, + _currentState.dt); + + + // sanity check: is the vehicle in step with the cycle? + if (writer[ModalResultField.dist] == DBNull.Value) { + Log.Warn("distance field is not set!"); + } else { + var distance = (SI)writer[ModalResultField.dist]; + if (!distance.IsEqual(_currentState.Distance, 1e-12.SI<Meter>())) { + Log.Warn("distance diverges: {0}, distance: {1}", (distance - _currentState.Distance).Value(), + distance); + } + } + } + + + protected override void DoCommitSimulationStep() + { + _previousState = _currentState; + _currentState = new VehicleState(); + } + + protected Newton RollingResistance(Radian gradient) + { + var retVal = (Math.Cos(gradient.Value()) * _data.TotalVehicleWeight() * + Physics.GravityAccelleration * + _data.TotalRollResistanceCoefficient).Cast<Newton>(); + Log.Debug("RollingResistance: {0}", retVal); + return retVal; + } + + + protected Newton DriverAcceleration(MeterPerSquareSecond accelleration) + { + var retVal = ((_data.TotalVehicleWeight() + _data.ReducedMassWheels) * accelleration).Cast<Newton>(); + Log.Debug("DriverAcceleration: {0}", retVal); + return retVal; + } + + + protected internal Newton SlopeResistance(Radian gradient) + { + var retVal = + (_data.TotalVehicleWeight() * Physics.GravityAccelleration * Math.Sin(gradient.Value())).Cast<Newton>(); + Log.Debug("SlopeResistance: {0}", retVal); + return retVal; + } + + + protected internal Newton AirDragResistance(MeterPerSquareSecond acceleration, Second dt) + { + var vAverage = _previousState.Velocity + acceleration * dt / 2; + if (vAverage.IsEqual(0)) { + return 0.SI<Newton>(); + } + var result = + (ComputeAirDragPowerLoss(_previousState.Velocity, _previousState.Velocity + acceleration * dt, dt) / + vAverage).Cast<Newton>(); + + Log.Debug("AirDragResistance: {0}", result); + return result; + } + + private Watt ComputeAirDragPowerLoss(MeterPerSecond v1, MeterPerSecond v2, Second dt) + { + var vAverage = (v1 + v2) / 2; + var CdA = ComputeEffectiveAirDragArea(vAverage); + Watt averageAirDragPower; + if (v1.IsEqual(v2)) { + averageAirDragPower = (Physics.AirDensity / 2.0 * CdA * vAverage * vAverage * vAverage).Cast<Watt>(); + } else { + // compute the average force within the current simulation interval + // P(t) = k * v(t)^3 , v(t) = v0 + a * t // a != 0 + // => P_avg = (CdA * rho/2)/(4*a * dt) * (v2^4 - v1^4) + var acceleration = (v2 - v1) / dt; + averageAirDragPower = + (Physics.AirDensity / 2.0 * CdA * (v2 * v2 * v2 * v2 - v1 * v1 * v1 * v1) / (4 * acceleration * dt)) + .Cast<Watt>(); + } + return averageAirDragPower; + } + + protected internal SquareMeter ComputeEffectiveAirDragArea(MeterPerSecond velocity) + { + var CdA = _data.AerodynamicDragAera; + switch (_data.CrossWindCorrectionMode) { + case CrossWindCorrectionMode.NoCorrection: + break; + case CrossWindCorrectionMode.DeclarationModeCorrection: + CdA = AirDragInterpolate(velocity); + break; + default: + throw new NotImplementedException(string.Format("CrossWindcorrection {0} is not implemented", + _data.CrossWindCorrectionMode)); + } + return CdA; + } + + private SquareMeter AirDragInterpolate(MeterPerSecond x) + { + var p = _airResistanceCurve.GetSection(c => c.X < x); + + if (x < p.Item1.X || p.Item2.X < x) { + Log.Error(_data.CrossWindCorrectionMode == CrossWindCorrectionMode.VAirBetaLookupTable + ? string.Format("CdExtrapol β = {0}", x) + : string.Format("CdExtrapol v = {0}", x)); + } + + return VectoMath.Interpolate(p.Item1.X, p.Item2.X, p.Item1.Y, p.Item2.Y, x); + } + + protected Point[] CalculateAirResistanceCurve(AirDrag.AirDragEntry values) + { + var points = new List<Point> { new Point { X = 0.SI<MeterPerSecond>(), Y = 0.SI<SquareMeter>() } }; + + for (var speed = 60; speed <= 100; speed += 5) { + var vVeh = speed.KMPHtoMeterPerSecond(); + var cdASum = 0.0.SI<SquareMeter>(); + for (var alpha = 0; alpha <= 180; alpha += 10) { + var vWindX = Physics.BaseWindSpeed * Math.Cos(alpha.ToRadian()); + var vWindY = Physics.BaseWindSpeed * Math.Sin(alpha.ToRadian()); + var vAirX = vVeh + vWindX; + var vAirY = vWindY; // var vAir = VectoMath.Sqrt<MeterPerSecond>(vAirX * vAirX + vAirY * vAirY); - var beta = Math.Atan((vAirY / vAirX).Value()).ToDegree(); - var deltaCdA = ComputeDeltaCd(beta, values); - var cdA = _data.AerodynamicDragAera + deltaCdA; + var beta = Math.Atan((vAirY / vAirX).Value()).ToDegree(); + var deltaCdA = ComputeDeltaCd(beta, values); + var cdA = _data.AerodynamicDragAera + deltaCdA; - var degreeShare = ((alpha != 0 && alpha != 180) ? 10.0 / 180.0 : 5.0 / 180.0); + var degreeShare = ((alpha != 0 && alpha != 180) ? 10.0 / 180.0 : 5.0 / 180.0); // cdASum += degreeShare * cdA * (vAir * vAir / (vVeh * vVeh)).Cast<Scalar>(); - cdASum += degreeShare * cdA * ((vAirX * vAirX + vAirY * vAirY) / (vVeh * vVeh)).Cast<Scalar>(); - } - points.Add(new Point { X = vVeh, Y = cdASum }); - } - - points[0].Y = points[1].Y; - return points.ToArray(); - } - - protected SquareMeter ComputeDeltaCd(double beta, AirDrag.AirDragEntry values) - { - return (values.A1 * beta + values.A2 * beta * beta + values.A3 * beta * beta * beta).SI<SquareMeter>(); - } - - public class VehicleState - { - public MeterPerSecond Velocity; - public Second dt; - public Meter Distance; - - public Newton VehicleAccelerationForce; - public Newton DriverAcceleration; - public Newton SlopeResistance; - public Newton AirDragResistance; - public Newton RollingResistance; - public MeterPerSquareSecond Acceleration { get; set; } - } - - public class Point - { - public MeterPerSecond X; - public SquareMeter Y; - } - } + cdASum += degreeShare * cdA * ((vAirX * vAirX + vAirY * vAirY) / (vVeh * vVeh)).Cast<Scalar>(); + } + points.Add(new Point { X = vVeh, Y = cdASum }); + } + + points[0].Y = points[1].Y; + return points.ToArray(); + } + + protected SquareMeter ComputeDeltaCd(double beta, AirDrag.AirDragEntry values) + { + return (values.A1 * beta + values.A2 * beta * beta + values.A3 * beta * beta * beta).SI<SquareMeter>(); + } + + public class VehicleState + { + public MeterPerSecond Velocity; + public Second dt; + public Meter Distance; + + public Newton VehicleAccelerationForce; + public Newton DriverAcceleration; + public Newton SlopeResistance; + public Newton AirDragResistance; + public Newton RollingResistance; + public MeterPerSquareSecond Acceleration { get; set; } + } + + public class Point + { + public MeterPerSecond X; + public SquareMeter Y; + } + } } \ No newline at end of file diff --git a/VectoCore/Utils/StringExtensionMethods.cs b/VectoCore/Utils/StringExtensionMethods.cs index 1ef1bcf3945c3a2be2b7a2cb7f716881a30888c1..2ad25c3d154baf6d14bd54fdfc0e611b15a9b4aa 100644 --- a/VectoCore/Utils/StringExtensionMethods.cs +++ b/VectoCore/Utils/StringExtensionMethods.cs @@ -16,5 +16,10 @@ namespace TUGraz.VectoCore.Utils { return double.Parse(self, CultureInfo.InvariantCulture); } + + public static double IndulgentParse(this string self) + { + return double.Parse(new string(self.Trim().TakeWhile(c => char.IsDigit(c) || c == '.').ToArray()), CultureInfo.InvariantCulture); + } } } \ No newline at end of file