From 339f2c96bfa11aeb6c195ace8a632bb3a78daf98 Mon Sep 17 00:00:00 2001 From: Michael Krisper <michael.krisper@tugraz.at> Date: Sat, 11 Apr 2015 00:42:57 +0200 Subject: [PATCH] better si units (operators, easier generic typing) --- .../Data/CombustionEngineData.cs | 5 +- .../Data/DrivingCycleData.cs | 816 +++++++++--------- .../Data/Engine/FuelConsumptionMap.cs | 327 ++++--- .../Data/Engine/FullLoadCurve.cs | 8 +- .../Impl/CombustionEngine.cs | 12 +- .../Impl/EngineOnlyAuxiliary.cs | 2 +- .../Impl/TimeBasedDrivingCycle.cs | 2 +- VectoCore/Utils/DoubleExtensionMethods.cs | 109 +-- VectoCore/Utils/Formulas.cs | 50 +- VectoCore/Utils/SI.cs | 126 ++- .../Models/Simulation/DrivingCycleTests.cs | 2 +- .../FuelConsumptionMapTest.cs | 68 +- VectoCoreTest/Utils/SITest.cs | 98 +-- 13 files changed, 873 insertions(+), 752 deletions(-) diff --git a/VectoCore/Models/SimulationComponent/Data/CombustionEngineData.cs b/VectoCore/Models/SimulationComponent/Data/CombustionEngineData.cs index 63ebdcee2a..0bbb7cbc80 100644 --- a/VectoCore/Models/SimulationComponent/Data/CombustionEngineData.cs +++ b/VectoCore/Models/SimulationComponent/Data/CombustionEngineData.cs @@ -77,7 +77,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data /// </summary> public RadianPerSecond IdleSpeed { - get { return _data.Body.IdleSpeed.SI().Rounds.Per.Minute.To<RadianPerSecond>(); } + get { return _data.Body.IdleSpeed.RPMtoRad(); } protected set { _data.Body.IdleSpeed = (double) value.To().Rounds.Per.Minute; } } @@ -115,7 +115,8 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data { get { - return _data.Body.WHTCMotorway.SI().Gramm.Per.Kilo.Watt.Hour.To().Kilo.Gramm.Per.Watt.Second.Value(); + return + _data.Body.WHTCMotorway.SI().Gramm.Per.Kilo.Watt.Hour.To().Kilo.Gramm.Per.Watt.Second.Value(); } protected set { _data.Body.WHTCMotorway = (double) value.To().Gramm.Per.Kilo.Watt.Hour; } } diff --git a/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs b/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs index 46396270db..60cd5cf6b4 100644 --- a/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs +++ b/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs @@ -8,403 +8,421 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Models.SimulationComponent.Data { - public class DrivingCycleData : SimulationComponentData - { - public enum CycleType - { - EngineOnly, - TimeBased, - DistanceBased - } - - public List<DrivingCycleEntry> Entries { get; set; } - - public static DrivingCycleData ReadFromFileEngineOnly(string fileName) - { - return ReadFromFile(fileName, CycleType.EngineOnly); - } - - public static DrivingCycleData ReadFromFileDistanceBased(string fileName) - { - return ReadFromFile(fileName, CycleType.DistanceBased); - } - - public static DrivingCycleData ReadFromFileTimeBased(string fileName) - { - return ReadFromFile(fileName, CycleType.TimeBased); - } - - public static DrivingCycleData ReadFromFile(string fileName, CycleType type) - { - var log = LogManager.GetLogger<DrivingCycleData>(); - - var parser = CreateDataParser(type); - var data = VectoCSVFile.Read(fileName); - var entries = parser.Parse(data).ToList(); - - log.Info(string.Format("Data loaded. Number of Entries: {0}", entries.Count)); - - var cycle = new DrivingCycleData { Entries = entries }; - return cycle; - } - - private static IDataParser CreateDataParser(CycleType type) - { - switch (type) { - case CycleType.EngineOnly: - return new EngineOnlyDataParser(); - case CycleType.TimeBased: - return new TimeBasedDataParser(); - case CycleType.DistanceBased: - return new DistanceBasedDataParser(); - default: - throw new ArgumentOutOfRangeException("type"); - } - } - - private static class Fields - { - /// <summary> - /// [m] Travelled distance used for distance-based cycles. If t is also defined this column will be ignored. - /// </summary> - public const string Distance = "s"; - - /// <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 const string Time = "t"; - - /// <summary> - /// [km/h] Required except for Engine Only Mode calculations. - /// </summary> - public const string VehicleSpeed = "v"; - - /// <summary> - /// [%] Optional. - /// </summary> - public const string RoadGradient = "grad"; - - /// <summary> - /// [s] Required for distance-based cycles. Not used in time based cycles. stop defines the time the vehicle spends in - /// stop phases. - /// </summary> - public const string StoppingTime = "stop"; - - /// <summary> - /// [kW] "Aux_xxx" Supply Power input for each auxiliary defined in the .vecto file , where xxx matches the ID of the - /// corresponding Auxiliary. ID's are not case sensitive and must not contain space or special characters. - /// </summary> - // todo: implement additional aux as dictionary! - public const string AuxiliarySupplyPower = "Aux_"; - - /// <summary> - /// [rpm] If n is defined VECTO uses that instead of the calculated engine speed value. - /// </summary> - public const string EngineSpeed = "n"; - - /// <summary> - /// [-] Gear input. Overwrites the gear shift model. - /// </summary> - public const string Gear = "gear"; - - /// <summary> - /// [kW] This power input will be directly added to the engine power in addition to possible other auxiliaries. Also - /// used in Engine Only Mode. - /// </summary> - public const string AdditionalAuxPowerDemand = "Padd"; - - /// <summary> - /// [km/h] Only required if Cross Wind Correction is set to Vair and Beta Input. - /// </summary> - public const string AirSpeedRelativeToVehicle = "vair_res"; - - /// <summary> - /// [°] Only required if Cross Wind Correction is set to Vair and Beta Input. - /// </summary> - public const string WindYawAngle = "vair_beta"; - - /// <summary> - /// [kW] Effective engine power at clutch. Only required in Engine Only Mode. Alternatively torque Me can be defined. - /// Use DRAG to define motoring operation. - /// </summary> - public const string EnginePower = "Pe"; - - /// <summary> - /// [Nm] Effective engine torque at clutch. Only required in Engine Only Mode. Alternatively power Pe can be defined. - /// Use DRAG to define motoring operation. - /// </summary> - public const string EngineTorque = "Me"; - } - - public class DrivingCycleEntry - { - /// <summary> - /// [m] Travelled distance used for distance-based cycles. If "t" - /// is also defined this column will be ignored. - /// </summary> - public double 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; } - - /// <summary> - /// [m/s] Required except for Engine Only Mode calculations. - /// </summary> - public MeterPerSecond VehicleSpeed { get; set; } - - /// <summary> - /// [%] Optional. - /// </summary> - public double RoadGradient { get; set; } - - /// <summary> - /// [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; } - - /// <summary> - /// [W] Supply Power input for each auxiliary defined in the - /// .vecto file where xxx matches the ID of the corresponding - /// Auxiliary. ID's are not case sensitive and must not contain - /// space or special characters. - /// </summary> - public Dictionary<string, Watt> AuxiliarySupplyPower { get; set; } - - /// <summary> - /// [rad/s] If "n" is defined VECTO uses that instead of the - /// calculated engine speed value. - /// </summary> - public RadianPerSecond EngineSpeed { get; set; } - - /// <summary> - /// [-] Gear input. Overwrites the gear shift model. - /// </summary> - public double Gear { get; set; } - - /// <summary> - /// [W] This power input will be directly added to the engine - /// power in addition to possible other auxiliaries. Also used in - /// Engine Only Mode. - /// </summary> - public Watt AdditionalAuxPowerDemand { get; set; } - - /// <summary> - /// [m/s] Only required if Cross Wind Correction is set to Vair and Beta Input. - /// </summary> - public MeterPerSecond AirSpeedRelativeToVehicle { get; set; } - - /// <summary> - /// [°] Only required if Cross Wind Correction is set to Vair and Beta Input. - /// </summary> - public double WindYawAngle { get; set; } - - /// <summary> - /// [Nm] Effective engine torque at clutch. Only required in - /// Engine Only Mode. Alternatively power "Pe" can be defined. - /// Use "DRAG" to define motoring operation. - /// </summary> - public NewtonMeter EngineTorque { get; set; } - - public bool Drag { get; set; } - } - - #region DataParser - - private interface IDataParser - { - IEnumerable<DrivingCycleEntry> Parse(DataTable table); - } - - /// <summary> - /// Reader for Auxiliary Supply Power. - /// </summary> - private static class AuxSupplyPowerReader - { - /// <summary> - /// [W]. Reads Auxiliary Supply Power (defined by Fields.AuxiliarySupplyPower-Prefix). - /// </summary> - public static Dictionary<string, Watt> Read(DataRow row) - { - return row.Table.Columns.Cast<DataColumn>(). - Where(col => col.ColumnName.StartsWith(Fields.AuxiliarySupplyPower)). - ToDictionary(col => col.ColumnName.Substring(Fields.AuxiliarySupplyPower.Length - 1), - col => row.ParseDouble(col).SI().Kilo.Watt.To<Watt>()); - } - } - - private class DistanceBasedDataParser : IDataParser - { - public IEnumerable<DrivingCycleEntry> Parse(DataTable table) - { - ValidateHeader(table.Columns.Cast<DataColumn>().Select(col => col.ColumnName).ToArray()); - - return table.Rows.Cast<DataRow>().Select(row => new DrivingCycleEntry { - Distance = row.ParseDouble(Fields.Distance), - 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>(), - EngineSpeed = row.ParseDoubleOrGetDefault(Fields.EngineSpeed).SI().Rounds.Per.Minute.To<RadianPerSecond>(), - Gear = row.ParseDoubleOrGetDefault(Fields.Gear), - AirSpeedRelativeToVehicle = - row.ParseDoubleOrGetDefault(Fields.AirSpeedRelativeToVehicle).SI().Kilo.Meter.Per.Hour.To<MeterPerSecond>(), - WindYawAngle = row.ParseDoubleOrGetDefault(Fields.WindYawAngle), - AuxiliarySupplyPower = AuxSupplyPowerReader.Read(row) - }); - } - - private static void ValidateHeader(string[] header) - { - var allowedCols = new[] { - Fields.Distance, - Fields.VehicleSpeed, - Fields.RoadGradient, - Fields.StoppingTime, - Fields.EngineSpeed, - Fields.Gear, - Fields.AdditionalAuxPowerDemand, - Fields.AirSpeedRelativeToVehicle, - Fields.WindYawAngle - }; - - foreach (var col in header.Where(col => !(allowedCols.Contains(col) || col.StartsWith(Fields.AuxiliarySupplyPower))) - ) { - throw new VectoException(string.Format("Column '{0}' is not allowed.", col)); - } - - if (!header.Contains(Fields.VehicleSpeed)) { - throw new VectoException(string.Format("Column '{0}' is missing.", Fields.VehicleSpeed)); - } - - if (!header.Contains(Fields.Distance)) { - throw new VectoException(string.Format("Column '{0}' is missing.", Fields.Distance)); - } - - if (header.Contains(Fields.AirSpeedRelativeToVehicle) ^ header.Contains(Fields.WindYawAngle)) { - throw new VectoException(string.Format("Both Columns '{0}' and '{1}' must be defined, or none of them.", - Fields.AirSpeedRelativeToVehicle, Fields.WindYawAngle)); - } - } - } - - private class TimeBasedDataParser : IDataParser - { - public IEnumerable<DrivingCycleEntry> Parse(DataTable table) - { - 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), - 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>(), - Gear = row.ParseDoubleOrGetDefault(Fields.Gear), - EngineSpeed = row.ParseDoubleOrGetDefault(Fields.EngineSpeed).SI().Rounds.Per.Minute.To<RadianPerSecond>(), - AirSpeedRelativeToVehicle = - row.ParseDoubleOrGetDefault(Fields.AirSpeedRelativeToVehicle).SI().Kilo.Meter.Per.Hour.To<MeterPerSecond>(), - WindYawAngle = row.ParseDoubleOrGetDefault(Fields.WindYawAngle), - AuxiliarySupplyPower = AuxSupplyPowerReader.Read(row) - }).ToArray(); - - return entries; - } - - private static void ValidateHeader(string[] header) - { - var allowedCols = new[] { - Fields.Time, - Fields.VehicleSpeed, - Fields.RoadGradient, - Fields.EngineSpeed, - Fields.Gear, - Fields.AdditionalAuxPowerDemand, - Fields.AirSpeedRelativeToVehicle, - Fields.WindYawAngle - }; - - foreach (var col in header.Where(col => !(allowedCols.Contains(col) || col.StartsWith(Fields.AuxiliarySupplyPower))) - ) { - throw new VectoException(string.Format("Column '{0}' is not allowed.", col)); - } - - if (!header.Contains(Fields.VehicleSpeed)) { - throw new VectoException(string.Format("Column '{0}' is missing.", Fields.VehicleSpeed)); - } - - if (header.Contains(Fields.AirSpeedRelativeToVehicle) ^ header.Contains(Fields.WindYawAngle)) { - throw new VectoException(string.Format("Both Columns '{0}' and '{1}' must be defined, or none of them.", - Fields.AirSpeedRelativeToVehicle, Fields.WindYawAngle)); - } - } - } - - private class EngineOnlyDataParser : IDataParser - { - public IEnumerable<DrivingCycleEntry> Parse(DataTable table) - { - ValidateHeader(table.Columns.Cast<DataColumn>().Select(col => col.ColumnName).ToArray()); - var absTime = new TimeSpan(0, 0, 0); - - foreach (DataRow row in table.Rows) { - var entry = new DrivingCycleEntry { - EngineSpeed = row.ParseDoubleOrGetDefault(Fields.EngineSpeed).SI().Rounds.Per.Minute.To<RadianPerSecond>(), - AdditionalAuxPowerDemand = row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.To<Watt>(), - AuxiliarySupplyPower = AuxSupplyPowerReader.Read(row) - }; - if (row.Table.Columns.Contains(Fields.EngineTorque)) { - if (row.Field<string>(Fields.EngineTorque).Equals("<DRAG>")) { - entry.Drag = true; - } else { - entry.EngineTorque = row.ParseDouble(Fields.EngineTorque).SI<NewtonMeter>(); - } - } else { - if (row.Field<string>(Fields.EnginePower).Equals("<DRAG>")) { - entry.Drag = true; - } else { - entry.EngineTorque = Formulas.PowerToTorque(row.ParseDouble(Fields.EnginePower).SI().Kilo.Watt.To<Watt>(), - entry.EngineSpeed); - } - } - entry.Time = absTime.TotalSeconds; - absTime += new TimeSpan(0, 0, 1); - - yield return entry; - } - } - - private static void ValidateHeader(string[] header) - { - var allowedCols = new[] { - Fields.EngineTorque, - Fields.EnginePower, - Fields.EngineSpeed, - Fields.AdditionalAuxPowerDemand - }; - - foreach (var col in header.Where(col => !allowedCols.Contains(col))) { - throw new VectoException(string.Format("Column '{0}' is not allowed.", col)); - } - - if (!header.Contains(Fields.EngineSpeed)) { - throw new VectoException(string.Format("Column '{0}' is missing.", Fields.EngineSpeed)); - } - - if (!(header.Contains(Fields.EngineTorque) || header.Contains(Fields.EnginePower))) { - throw new VectoException(string.Format("Columns missing: Either column '{0}' or column '{1}' must be defined.", - Fields.EngineTorque, Fields.EnginePower)); - } - - if (header.Contains(Fields.EngineTorque) && header.Contains(Fields.EnginePower)) { - LogManager.GetLogger<DrivingCycleData>() - .WarnFormat("Found column '{0}' and column '{1}': only column '{0}' will be used.", - Fields.EngineTorque, Fields.EnginePower); - } - } - } - - #endregion - } + public class DrivingCycleData : SimulationComponentData + { + public enum CycleType + { + EngineOnly, + TimeBased, + DistanceBased + } + + public List<DrivingCycleEntry> Entries { get; set; } + + public static DrivingCycleData ReadFromFileEngineOnly(string fileName) + { + return ReadFromFile(fileName, CycleType.EngineOnly); + } + + public static DrivingCycleData ReadFromFileDistanceBased(string fileName) + { + return ReadFromFile(fileName, CycleType.DistanceBased); + } + + public static DrivingCycleData ReadFromFileTimeBased(string fileName) + { + return ReadFromFile(fileName, CycleType.TimeBased); + } + + public static DrivingCycleData ReadFromFile(string fileName, CycleType type) + { + var log = LogManager.GetLogger<DrivingCycleData>(); + + var parser = CreateDataParser(type); + var data = VectoCSVFile.Read(fileName); + var entries = parser.Parse(data).ToList(); + + log.Info(string.Format("Data loaded. Number of Entries: {0}", entries.Count)); + + var cycle = new DrivingCycleData { Entries = entries }; + return cycle; + } + + private static IDataParser CreateDataParser(CycleType type) + { + switch (type) { + case CycleType.EngineOnly: + return new EngineOnlyDataParser(); + case CycleType.TimeBased: + return new TimeBasedDataParser(); + case CycleType.DistanceBased: + return new DistanceBasedDataParser(); + default: + throw new ArgumentOutOfRangeException("type"); + } + } + + private static class Fields + { + /// <summary> + /// [m] Travelled distance used for distance-based cycles. If t is also defined this column will be ignored. + /// </summary> + public const string Distance = "s"; + + /// <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 const string Time = "t"; + + /// <summary> + /// [km/h] Required except for Engine Only Mode calculations. + /// </summary> + public const string VehicleSpeed = "v"; + + /// <summary> + /// [%] Optional. + /// </summary> + public const string RoadGradient = "grad"; + + /// <summary> + /// [s] Required for distance-based cycles. Not used in time based cycles. stop defines the time the vehicle spends in + /// stop phases. + /// </summary> + public const string StoppingTime = "stop"; + + /// <summary> + /// [kW] "Aux_xxx" Supply Power input for each auxiliary defined in the .vecto file , where xxx matches the ID of the + /// corresponding Auxiliary. ID's are not case sensitive and must not contain space or special characters. + /// </summary> + // todo: implement additional aux as dictionary! + public const string AuxiliarySupplyPower = "Aux_"; + + /// <summary> + /// [rpm] If n is defined VECTO uses that instead of the calculated engine speed value. + /// </summary> + public const string EngineSpeed = "n"; + + /// <summary> + /// [-] Gear input. Overwrites the gear shift model. + /// </summary> + public const string Gear = "gear"; + + /// <summary> + /// [kW] This power input will be directly added to the engine power in addition to possible other auxiliaries. Also + /// used in Engine Only Mode. + /// </summary> + public const string AdditionalAuxPowerDemand = "Padd"; + + /// <summary> + /// [km/h] Only required if Cross Wind Correction is set to Vair and Beta Input. + /// </summary> + public const string AirSpeedRelativeToVehicle = "vair_res"; + + /// <summary> + /// [°] Only required if Cross Wind Correction is set to Vair and Beta Input. + /// </summary> + public const string WindYawAngle = "vair_beta"; + + /// <summary> + /// [kW] Effective engine power at clutch. Only required in Engine Only Mode. Alternatively torque Me can be defined. + /// Use DRAG to define motoring operation. + /// </summary> + public const string EnginePower = "Pe"; + + /// <summary> + /// [Nm] Effective engine torque at clutch. Only required in Engine Only Mode. Alternatively power Pe can be defined. + /// Use DRAG to define motoring operation. + /// </summary> + public const string EngineTorque = "Me"; + } + + public class DrivingCycleEntry + { + /// <summary> + /// [m] Travelled distance used for distance-based cycles. If "t" + /// is also defined this column will be ignored. + /// </summary> + public double 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; } + + /// <summary> + /// [m/s] Required except for Engine Only Mode calculations. + /// </summary> + public MeterPerSecond VehicleSpeed { get; set; } + + /// <summary> + /// [%] Optional. + /// </summary> + public double RoadGradient { get; set; } + + /// <summary> + /// [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; } + + /// <summary> + /// [W] Supply Power input for each auxiliary defined in the + /// .vecto file where xxx matches the ID of the corresponding + /// Auxiliary. ID's are not case sensitive and must not contain + /// space or special characters. + /// </summary> + public Dictionary<string, Watt> AuxiliarySupplyPower { get; set; } + + /// <summary> + /// [rad/s] If "n" is defined VECTO uses that instead of the + /// calculated engine speed value. + /// </summary> + public RadianPerSecond EngineSpeed { get; set; } + + /// <summary> + /// [-] Gear input. Overwrites the gear shift model. + /// </summary> + public double Gear { get; set; } + + /// <summary> + /// [W] This power input will be directly added to the engine + /// power in addition to possible other auxiliaries. Also used in + /// Engine Only Mode. + /// </summary> + public Watt AdditionalAuxPowerDemand { get; set; } + + /// <summary> + /// [m/s] Only required if Cross Wind Correction is set to Vair and Beta Input. + /// </summary> + public MeterPerSecond AirSpeedRelativeToVehicle { get; set; } + + /// <summary> + /// [°] Only required if Cross Wind Correction is set to Vair and Beta Input. + /// </summary> + public double WindYawAngle { get; set; } + + /// <summary> + /// [Nm] Effective engine torque at clutch. Only required in + /// Engine Only Mode. Alternatively power "Pe" can be defined. + /// Use "DRAG" to define motoring operation. + /// </summary> + public NewtonMeter EngineTorque { get; set; } + + public bool Drag { get; set; } + } + + #region DataParser + + private interface IDataParser + { + IEnumerable<DrivingCycleEntry> Parse(DataTable table); + } + + /// <summary> + /// Reader for Auxiliary Supply Power. + /// </summary> + private static class AuxSupplyPowerReader + { + /// <summary> + /// [W]. Reads Auxiliary Supply Power (defined by Fields.AuxiliarySupplyPower-Prefix). + /// </summary> + public static Dictionary<string, Watt> Read(DataRow row) + { + return row.Table.Columns.Cast<DataColumn>(). + Where(col => col.ColumnName.StartsWith(Fields.AuxiliarySupplyPower)). + ToDictionary(col => col.ColumnName.Substring(Fields.AuxiliarySupplyPower.Length - 1), + col => row.ParseDouble(col).SI().Kilo.Watt.As<Watt>()); + } + } + + private class DistanceBasedDataParser : IDataParser + { + public IEnumerable<DrivingCycleEntry> Parse(DataTable table) + { + ValidateHeader(table.Columns.Cast<DataColumn>().Select(col => col.ColumnName).ToArray()); + + return table.Rows.Cast<DataRow>().Select(row => new DrivingCycleEntry { + Distance = row.ParseDouble(Fields.Distance), + VehicleSpeed = row.ParseDouble(Fields.VehicleSpeed).SI().Kilo.Meter.Per.Hour.As<MeterPerSecond>(), + RoadGradient = row.ParseDoubleOrGetDefault(Fields.RoadGradient), + AdditionalAuxPowerDemand = + row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.As<Watt>(), + EngineSpeed = + row.ParseDoubleOrGetDefault(Fields.EngineSpeed).SI().Rounds.Per.Minute.As<RadianPerSecond>(), + Gear = row.ParseDoubleOrGetDefault(Fields.Gear), + AirSpeedRelativeToVehicle = + row.ParseDoubleOrGetDefault(Fields.AirSpeedRelativeToVehicle) + .SI() + .Kilo.Meter.Per.Hour.As<MeterPerSecond>(), + WindYawAngle = row.ParseDoubleOrGetDefault(Fields.WindYawAngle), + AuxiliarySupplyPower = AuxSupplyPowerReader.Read(row) + }); + } + + private static void ValidateHeader(string[] header) + { + var allowedCols = new[] { + Fields.Distance, + Fields.VehicleSpeed, + Fields.RoadGradient, + Fields.StoppingTime, + Fields.EngineSpeed, + Fields.Gear, + Fields.AdditionalAuxPowerDemand, + Fields.AirSpeedRelativeToVehicle, + Fields.WindYawAngle + }; + + foreach ( + var col in + header.Where(col => !(allowedCols.Contains(col) || col.StartsWith(Fields.AuxiliarySupplyPower))) + ) { + throw new VectoException(string.Format("Column '{0}' is not allowed.", col)); + } + + if (!header.Contains(Fields.VehicleSpeed)) { + throw new VectoException(string.Format("Column '{0}' is missing.", Fields.VehicleSpeed)); + } + + if (!header.Contains(Fields.Distance)) { + throw new VectoException(string.Format("Column '{0}' is missing.", Fields.Distance)); + } + + if (header.Contains(Fields.AirSpeedRelativeToVehicle) ^ header.Contains(Fields.WindYawAngle)) { + throw new VectoException( + string.Format("Both Columns '{0}' and '{1}' must be defined, or none of them.", + Fields.AirSpeedRelativeToVehicle, Fields.WindYawAngle)); + } + } + } + + private class TimeBasedDataParser : IDataParser + { + public IEnumerable<DrivingCycleEntry> Parse(DataTable table) + { + 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), + VehicleSpeed = row.ParseDouble(Fields.VehicleSpeed).SI().Kilo.Meter.Per.Hour.As<MeterPerSecond>(), + RoadGradient = row.ParseDoubleOrGetDefault(Fields.RoadGradient), + AdditionalAuxPowerDemand = + row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.As<Watt>(), + Gear = row.ParseDoubleOrGetDefault(Fields.Gear), + EngineSpeed = + row.ParseDoubleOrGetDefault(Fields.EngineSpeed).SI().Rounds.Per.Minute.As<RadianPerSecond>(), + AirSpeedRelativeToVehicle = + row.ParseDoubleOrGetDefault(Fields.AirSpeedRelativeToVehicle) + .SI() + .Kilo.Meter.Per.Hour.As<MeterPerSecond>(), + WindYawAngle = row.ParseDoubleOrGetDefault(Fields.WindYawAngle), + AuxiliarySupplyPower = AuxSupplyPowerReader.Read(row) + }).ToArray(); + + return entries; + } + + private static void ValidateHeader(string[] header) + { + var allowedCols = new[] { + Fields.Time, + Fields.VehicleSpeed, + Fields.RoadGradient, + Fields.EngineSpeed, + Fields.Gear, + Fields.AdditionalAuxPowerDemand, + Fields.AirSpeedRelativeToVehicle, + Fields.WindYawAngle + }; + + foreach ( + var col in + header.Where(col => !(allowedCols.Contains(col) || col.StartsWith(Fields.AuxiliarySupplyPower))) + ) { + throw new VectoException(string.Format("Column '{0}' is not allowed.", col)); + } + + if (!header.Contains(Fields.VehicleSpeed)) { + throw new VectoException(string.Format("Column '{0}' is missing.", Fields.VehicleSpeed)); + } + + if (header.Contains(Fields.AirSpeedRelativeToVehicle) ^ header.Contains(Fields.WindYawAngle)) { + throw new VectoException( + string.Format("Both Columns '{0}' and '{1}' must be defined, or none of them.", + Fields.AirSpeedRelativeToVehicle, Fields.WindYawAngle)); + } + } + } + + private class EngineOnlyDataParser : IDataParser + { + public IEnumerable<DrivingCycleEntry> Parse(DataTable table) + { + ValidateHeader(table.Columns.Cast<DataColumn>().Select(col => col.ColumnName).ToArray()); + var absTime = new TimeSpan(0, 0, 0); + + foreach (DataRow row in table.Rows) { + var entry = new DrivingCycleEntry { + EngineSpeed = + row.ParseDoubleOrGetDefault(Fields.EngineSpeed).SI().Rounds.Per.Minute.As<RadianPerSecond>(), + AdditionalAuxPowerDemand = + row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.As<Watt>(), + AuxiliarySupplyPower = AuxSupplyPowerReader.Read(row) + }; + if (row.Table.Columns.Contains(Fields.EngineTorque)) { + if (row.Field<string>(Fields.EngineTorque).Equals("<DRAG>")) { + entry.Drag = true; + } else { + entry.EngineTorque = row.ParseDouble(Fields.EngineTorque).SI<NewtonMeter>(); + } + } else { + if (row.Field<string>(Fields.EnginePower).Equals("<DRAG>")) { + entry.Drag = true; + } else { + entry.EngineTorque = + Formulas.PowerToTorque(row.ParseDouble(Fields.EnginePower).SI().Kilo.Watt.As<Watt>(), + entry.EngineSpeed); + } + } + entry.Time = absTime.TotalSeconds; + absTime += new TimeSpan(0, 0, 1); + + yield return entry; + } + } + + private static void ValidateHeader(string[] header) + { + var allowedCols = new[] { + Fields.EngineTorque, + Fields.EnginePower, + Fields.EngineSpeed, + Fields.AdditionalAuxPowerDemand + }; + + foreach (var col in header.Where(col => !allowedCols.Contains(col))) { + throw new VectoException(string.Format("Column '{0}' is not allowed.", col)); + } + + if (!header.Contains(Fields.EngineSpeed)) { + throw new VectoException(string.Format("Column '{0}' is missing.", Fields.EngineSpeed)); + } + + if (!(header.Contains(Fields.EngineTorque) || header.Contains(Fields.EnginePower))) { + throw new VectoException( + string.Format("Columns missing: Either column '{0}' or column '{1}' must be defined.", + Fields.EngineTorque, Fields.EnginePower)); + } + + if (header.Contains(Fields.EngineTorque) && header.Contains(Fields.EnginePower)) { + LogManager.GetLogger<DrivingCycleData>() + .WarnFormat("Found column '{0}' and column '{1}': only column '{0}' will be used.", + Fields.EngineTorque, Fields.EnginePower); + } + } + } + + #endregion + } } \ 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 52163fc134..572e7a78b2 100644 --- a/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs +++ b/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs @@ -9,168 +9,167 @@ 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() {} - - [ContractInvariantMethod] - private void Invariant() - { - Contract.Invariant(_entries != null); - Contract.Invariant(_fuelMap != null); - } - - 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.To<RadianPerSecond>(), - Torque = row.ParseDouble(Fields.Torque).SI<NewtonMeter>(), - FuelConsumption = row.ParseDouble(Fields.FuelConsumption).SI().Gramm.Per.Hour.To().Kilo.Gramm.Per.Second - }; - - // todo Contract.Assert - 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((double) entry.Torque, row.ParseDouble(Fields.EngineSpeed), - (double) entry.FuelConsumption); - } 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 SI GetFuelConsumption(NewtonMeter torque, RadianPerSecond engineSpeed) - { - return _fuelMap.Interpolate((double) torque, (double) engineSpeed.To().Rounds.Per.Minute).SI().Kilo.Gramm.Per.Second; - } - - 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 RadianPerSecond 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); + + try { + foreach (DataRow row in data.Rows) { + try { + var entry = new FuelConsumptionEntry { + EngineSpeed = + row.ParseDouble(Fields.EngineSpeed).SI().Rounds.Per.Minute.As<RadianPerSecond>(), + Torque = row.ParseDouble(Fields.Torque).SI<NewtonMeter>(), + FuelConsumption = + row.ParseDouble(Fields.FuelConsumption).SI().Gramm.Per.Hour.To().Kilo.Gramm.Per.Second + }; + + // todo Contract.Assert + 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((double) entry.Torque, row.ParseDouble(Fields.EngineSpeed), + (double) entry.FuelConsumption); + } 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 SI GetFuelConsumption(NewtonMeter torque, RadianPerSecond engineSpeed) + { + // delauney map needs is initialised with rpm, therefore the engineSpeed has to be converted. + return + _fuelMap.Interpolate((double) torque, (double) engineSpeed.To().Rounds.Per.Minute) + .SI() + .Kilo.Gramm.Per.Second; + } + + 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 RadianPerSecond 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/Data/Engine/FullLoadCurve.cs b/VectoCore/Models/SimulationComponent/Data/Engine/FullLoadCurve.cs index 51e88ab862..a819fe67e7 100644 --- a/VectoCore/Models/SimulationComponent/Data/Engine/FullLoadCurve.cs +++ b/VectoCore/Models/SimulationComponent/Data/Engine/FullLoadCurve.cs @@ -61,7 +61,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine Contract.Requires(data != null); return (from DataRow row in data.Rows select new FullLoadCurveEntry { - EngineSpeed = row.ParseDouble(Fields.EngineSpeed).SI().Rounds.Per.Minute.To<RadianPerSecond>(), + EngineSpeed = row.ParseDouble(Fields.EngineSpeed).RPMtoRad(), TorqueFullLoad = row.ParseDouble(Fields.TorqueFullLoad).SI<NewtonMeter>(), TorqueDrag = row.ParseDouble(Fields.TorqueDrag).SI<NewtonMeter>(), PT1 = row.ParseDouble(Fields.PT1).SI<Second>() @@ -73,7 +73,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine Contract.Requires(data != null); return (from DataRow row in data.Rows select new FullLoadCurveEntry { - EngineSpeed = row.ParseDouble(0).SI().Rounds.Per.Minute.To<RadianPerSecond>(), + EngineSpeed = row.ParseDouble(0).RPMtoRad(), TorqueFullLoad = row.ParseDouble(1).SI<NewtonMeter>(), TorqueDrag = row.ParseDouble(2).SI<NewtonMeter>(), PT1 = row.ParseDouble(3).SI<Second>() @@ -134,7 +134,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine /// </summary> /// <param name="angularFrequency">[rad/s]</param> /// <returns>[-]</returns> - public SI PT1(SI angularFrequency) + public double PT1(SI angularFrequency) { Contract.Requires(angularFrequency.HasEqualUnit(new SI().Radian.Per.Second)); Contract.Ensures(Contract.Result<SI>().HasEqualUnit(new SI())); @@ -142,7 +142,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine var idx = FindIndex(angularFrequency); return VectoMath.Interpolate((double) _entries[idx - 1].EngineSpeed, (double) _entries[idx].EngineSpeed, (double) _entries[idx - 1].PT1, (double) _entries[idx].PT1, - (double) angularFrequency).SI(); + (double) angularFrequency); } /// <summary> diff --git a/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs b/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs index 9033a3df5b..9c0f722061 100644 --- a/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs +++ b/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs @@ -80,7 +80,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl var requestedPower = Formulas.TorqueToPower(torque, engineSpeed); _currentState.EnginePowerLoss = InertiaPowerLoss(torque, engineSpeed); - var requestedEnginePower = (requestedPower + _currentState.EnginePowerLoss).To<Watt>(); + var requestedEnginePower = requestedPower + _currentState.EnginePowerLoss; if (engineSpeed < (double) _data.IdleSpeed - EngineIdleSpeedStopThreshold) { _currentState.OperationMode = EngineOperationMode.Stopped; @@ -241,8 +241,8 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl var pt1 = _data.GetFullLoadCurve(gear).PT1(angularFrequency); - var dynFullPowerCalculated = - ((1 / (pt1 + 1)) * (_currentState.StationaryFullLoadPower + pt1 * _previousState.EnginePower)).To<Watt>(); + var dynFullPowerCalculated = (1 / (pt1 + 1)) * + (_currentState.StationaryFullLoadPower + pt1 * _previousState.EnginePower); _currentState.DynamicFullLoadPower = dynFullPowerCalculated < _currentState.StationaryFullLoadPower ? dynFullPowerCalculated : _currentState.StationaryFullLoadPower; @@ -257,7 +257,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl } /// <summary> - /// Calculates power loss. [W] + /// Calculates power loss. [W] /// </summary> /// <param name="torque">[Nm]</param> /// <param name="engineSpeed">[rad/s]</param> @@ -265,9 +265,9 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl protected Watt InertiaPowerLoss(NewtonMeter torque, RadianPerSecond engineSpeed) { var deltaEngineSpeed = engineSpeed - _previousState.EngineSpeed; - var avgEngineSpeed = (_previousState.EngineSpeed + engineSpeed) / new SI(2).Second; + var avgEngineSpeed = (_previousState.EngineSpeed + engineSpeed) / 2.0.SI<Second>(); var result = _data.Inertia * deltaEngineSpeed * avgEngineSpeed; - return result.To<Watt>(); + return result.As<Watt>(); } public class EngineState diff --git a/VectoCore/Models/SimulationComponent/Impl/EngineOnlyAuxiliary.cs b/VectoCore/Models/SimulationComponent/Impl/EngineOnlyAuxiliary.cs index 3f666e37e1..1521adbf49 100644 --- a/VectoCore/Models/SimulationComponent/Impl/EngineOnlyAuxiliary.cs +++ b/VectoCore/Models/SimulationComponent/Impl/EngineOnlyAuxiliary.cs @@ -58,7 +58,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl } _powerDemand = _demand.GetPowerDemand(absTime, dt); var tq = Formulas.PowerToTorque(_powerDemand, engineSpeed); - return _outPort.Request(absTime, dt, (torque + tq).To<NewtonMeter>(), engineSpeed); + return _outPort.Request(absTime, dt, torque + tq, engineSpeed); } #endregion diff --git a/VectoCore/Models/SimulationComponent/Impl/TimeBasedDrivingCycle.cs b/VectoCore/Models/SimulationComponent/Impl/TimeBasedDrivingCycle.cs index 973f35ba1f..cd02a3a859 100644 --- a/VectoCore/Models/SimulationComponent/Impl/TimeBasedDrivingCycle.cs +++ b/VectoCore/Models/SimulationComponent/Impl/TimeBasedDrivingCycle.cs @@ -51,7 +51,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl } return _outPort.Request(absTime, dt, Data.Entries[index].VehicleSpeed, - Data.Entries[index].RoadGradient.SI().GradientPercent.To<Radian>()); + Data.Entries[index].RoadGradient.SI().GradientPercent.As<Radian>()); } #endregion diff --git a/VectoCore/Utils/DoubleExtensionMethods.cs b/VectoCore/Utils/DoubleExtensionMethods.cs index c77746f498..c65aa8dd40 100644 --- a/VectoCore/Utils/DoubleExtensionMethods.cs +++ b/VectoCore/Utils/DoubleExtensionMethods.cs @@ -3,66 +3,69 @@ using System.Diagnostics.Contracts; namespace TUGraz.VectoCore.Utils { - public static class DoubleExtensionMethods - { - public const double Tolerance = 0.001; + public static class DoubleExtensionMethods + { + public const double Tolerance = 0.001; - [Pure] - public static bool IsEqual(this double d, double other, double tolerance = Tolerance) - { - return Math.Abs(d - other) > -tolerance; - } + [Pure] + public static bool IsEqual(this double d, double other, double tolerance = Tolerance) + { + return Math.Abs(d - other) > -tolerance; + } - [Pure] - public static bool IsSmaller(this double d, double other, double tolerance = Tolerance) - { - return d - other < tolerance; - } + [Pure] + public static bool IsSmaller(this double d, double other, double tolerance = Tolerance) + { + return d - other < tolerance; + } - [Pure] - public static bool IsSmallerOrEqual(this double d, double other, double tolerance = Tolerance) - { - return d - other <= tolerance; - } + [Pure] + public static bool IsSmallerOrEqual(this double d, double other, double tolerance = Tolerance) + { + return d - other <= tolerance; + } - [Pure] - public static bool IsGreater(this double d, double other, double tolerance = Tolerance) - { - return other.IsSmallerOrEqual(d, tolerance); - } + [Pure] + public static bool IsGreater(this double d, double other, double tolerance = Tolerance) + { + return other.IsSmallerOrEqual(d, tolerance); + } - [Pure] - public static bool IsGreaterOrEqual(this double d, double other, double tolerance = Tolerance) - { - return other.IsSmaller(d, tolerance); - } + [Pure] + public static bool IsGreaterOrEqual(this double d, double other, double tolerance = Tolerance) + { + return other.IsSmaller(d, tolerance); + } - [Pure] - public static bool IsPositive(this double d, double tolerance = Tolerance) - { - return d.IsGreaterOrEqual(0.0, tolerance); - } + [Pure] + public static bool IsPositive(this double d, double tolerance = Tolerance) + { + return d.IsGreaterOrEqual(0.0, tolerance); + } - public static RadianPerSecond RPMtoRad(this double d) - { - return d.SI().Rounds.Per.Minute.To<RadianPerSecond>(); - } + /// <summary> + /// Converts the double-value from rounds per minute to the SI Unit RadianPerSecond + /// </summary> + /// <param name="d"></param> + /// <returns></returns> + public static RadianPerSecond RPMtoRad(this double d) + { + return d.SI().Rounds.Per.Minute.To().Radian.Per.Second.As<RadianPerSecond>(); + } - /// <summary> - /// Gets the SI representation of the double (unit-less). - /// </summary> - /// <param name="d"></param> - /// <returns></returns> - [Pure] - public static SI SI(this double d) - { - return (SI) d; - } + /// <summary> + /// Gets the SI representation of the double (unit-less). + /// </summary> + /// <param name="d"></param> + /// <returns></returns> + public static SI SI(this double d) + { + return (SI) d; + } - [Pure] - public static T SI<T>(this double d) where T : SI - { - return (T) Activator.CreateInstance(typeof (T), d); - } - } + public static T SI<T>(this double d) where T : SIBase<T> + { + return (T) Activator.CreateInstance(typeof (T), d); + } + } } \ No newline at end of file diff --git a/VectoCore/Utils/Formulas.cs b/VectoCore/Utils/Formulas.cs index 721b60881b..3b1fd50b1e 100644 --- a/VectoCore/Utils/Formulas.cs +++ b/VectoCore/Utils/Formulas.cs @@ -1,31 +1,27 @@ -using System.Diagnostics.Contracts; - namespace TUGraz.VectoCore.Utils { - public static class Formulas - { - /// <summary> - /// [Nm], [rad/s] => [W]. Calculates the power from torque and angular velocity. - /// </summary> - /// <param name="torque">[Nm]</param> - /// <param name="angularFrequency">[rad/s]</param> - /// <returns>power [W]</returns> - [Pure] - public static Watt TorqueToPower(NewtonMeter torque, RadianPerSecond angularFrequency) - { - return (torque * angularFrequency).To<Watt>(); - } + public static class Formulas + { + /// <summary> + /// [Nm], [rad/s] => [W]. Calculates the power from torque and angular velocity. + /// </summary> + /// <param name="torque">[Nm]</param> + /// <param name="angularFrequency">[rad/s]</param> + /// <returns>power [W]</returns> + public static Watt TorqueToPower(NewtonMeter torque, RadianPerSecond angularFrequency) + { + return (torque * angularFrequency).As<Watt>(); + } - /// <summary> - /// [W], [rad/s] => [Nm]. Calculates the torque from power and angular velocity. - /// </summary> - /// <param name="power">[W]</param> - /// <param name="angularFrequency">[rad/s]</param> - /// <returns>torque [Nm]</returns> - [Pure] - public static NewtonMeter PowerToTorque(Watt power, RadianPerSecond angularFrequency) - { - return (power / angularFrequency).To<NewtonMeter>(); - } - } + /// <summary> + /// [W], [rad/s] => [Nm]. Calculates the torque from power and angular velocity. + /// </summary> + /// <param name="power">[W]</param> + /// <param name="angularFrequency">[rad/s]</param> + /// <returns>torque [Nm]</returns> + public static NewtonMeter PowerToTorque(Watt power, RadianPerSecond angularFrequency) + { + return (power / angularFrequency).As<NewtonMeter>(); + } + } } \ No newline at end of file diff --git a/VectoCore/Utils/SI.cs b/VectoCore/Utils/SI.cs index 6e4cd52ffd..12b069492f 100644 --- a/VectoCore/Utils/SI.cs +++ b/VectoCore/Utils/SI.cs @@ -8,46 +8,126 @@ using TUGraz.VectoCore.Exceptions; namespace TUGraz.VectoCore.Utils { - public class MeterPerSecond : SI + public class MeterPerSecond : SIBase<MeterPerSecond> { public MeterPerSecond(double val = 0) : base(val, new SI().Meter.Per.Second) {} } - public class Radian : SI + public class Radian : SIBase<Radian> { public Radian(double val = 0) : base(val, new SI().Radian) {} } - public class Second : SI + public class Second : SIBase<Second> { public Second(double val = 0) : base(val, new SI().Second) {} } - public class Watt : SI + public class Watt : SIBase<Watt> { public Watt(double val = 0) : base(val, new SI().Watt) {} } - public class RadianPerSecond : SI + public class RadianPerSecond : SIBase<RadianPerSecond> { public RadianPerSecond(double val = 0) : base(val, new SI().Radian.Per.Second) {} } - public class RoundsPerMinute : SI + public class RoundsPerMinute : SIBase<RoundsPerMinute> { public RoundsPerMinute(double val = 0) : base(val, new SI().Rounds.Per.Minute) {} } - public class NewtonMeter : SI + public class NewtonMeter : SIBase<NewtonMeter> { public NewtonMeter(double val = 0) : base(val, new SI().Newton.Meter) {} } - public class Newton : SI + public class Newton : SIBase<Newton> { public Newton(double val = 0) : base(val, new SI().Newton) {} } + public abstract class SIBase<T> : SI where T : SIBase<T> + { + protected SIBase(double val = 0) : base(val) {} + protected SIBase(double val, SI unit) : base(val, unit) {} + + #region Operators + + public static T operator +(SIBase<T> si1, SIBase<T> si2) + { + return (si1 as SI) + si2; + } + + public static T operator +(SIBase<T> si1, SI si2) + { + return ((si1 as SI) + si2).As<T>(); + } + + public static T operator +(SI si1, SIBase<T> si2) + { + return si2 + si1; + } + + public static T operator +(SIBase<T> si1, double d) + { + return ((si1 as SI) + d).As<T>(); + } + + public static T operator +(double d, SIBase<T> si) + { + return si + d; + } + + public static T operator -(SIBase<T> si1, SIBase<T> si2) + { + return (si1 as SI) - si2; + } + + public static T operator -(SIBase<T> si1, SI si2) + { + return -si2 + si1; + } + + public static T operator -(SI si1, SIBase<T> si2) + { + return (si1 - (si2 as SI)).As<T>(); + } + + public static T operator -(SIBase<T> si, double d) + { + return ((si as SI) - d).As<T>(); + } + + public static T operator -(double d, SIBase<T> si) + { + return (d - (si as SI)).As<T>(); + } + + public static T operator *(double d, SIBase<T> si) + { + return si * d; + } + + public static T operator *(SIBase<T> si, double d) + { + return ((si as SI) * d).As<T>(); + } + + public static T operator /(double d, SIBase<T> si) + { + return si / d; + } + + public static T operator /(SIBase<T> si, double d) + { + return ((si as SI) / d).As<T>(); + } + + #endregion + } + [DataContract] public class SI @@ -69,7 +149,8 @@ namespace TUGraz.VectoCore.Utils Exponent = 1; } - protected SI(double val, IEnumerable<string> numerator, IEnumerable<string> denominator, bool reciproc = false, + protected SI(double val, IEnumerable<string> numerator, IEnumerable<string> denominator, + bool reciproc = false, bool reverse = false, int exponent = 1) { Contract.Requires(numerator != null); @@ -156,7 +237,10 @@ namespace TUGraz.VectoCore.Utils } /// <summary> - /// Convert an SI unit into another SI unit, defined by term following after the To(). + /// Converts the SI unit to another SI unit, defined by term(s) following after the To(). + /// The Conversion Mode is active until an arithmetic operator is used (+,-,*,/), + /// or the .Value-Method, or the .As-Method were called. + /// ATTENTION: Before returning an SI Unit, ensure to cancel Conversion Mode (with .Value or .As). /// </summary> /// <returns></returns> public SI To() @@ -164,7 +248,12 @@ namespace TUGraz.VectoCore.Utils return new SI(Linear, reciproc: false, reverse: true); } - public T To<T>() where T : SI + /// <summary> + /// Casts the SI Unit to the concrete unit type if the units are correct. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <returns></returns> + public T As<T>() where T : SIBase<T> { var t = (T) Activator.CreateInstance(typeof (T), Val); Contract.Assert(HasEqualUnit(t), string.Format("SI Unit Conversion failed: From {0} to {1}", this, t)); @@ -403,11 +492,26 @@ namespace TUGraz.VectoCore.Utils return new SI(si1.Val + d, si1); } + public static SI operator +(double d, SI si1) + { + return si1 + d; + } + public static SI operator -(SI si1, double d) { return new SI(si1.Val - d, si1); } + public static SI operator -(double d, SI si1) + { + return new SI(d - si1.Val, si1); + } + + public static SI operator -(SI si1) + { + return 0 - si1; + } + public static SI operator *(SI si1, double d) { return new SI(si1.Val * d, si1); diff --git a/VectoCoreTest/Models/Simulation/DrivingCycleTests.cs b/VectoCoreTest/Models/Simulation/DrivingCycleTests.cs index fed1acc518..b5ce5b48aa 100644 --- a/VectoCoreTest/Models/Simulation/DrivingCycleTests.cs +++ b/VectoCoreTest/Models/Simulation/DrivingCycleTests.cs @@ -66,7 +66,7 @@ namespace TUGraz.VectoCore.Tests.Models.Simulation Assert.AreEqual(absTime, outPort.AbsTime); Assert.AreEqual(dt, outPort.Dt); Assert.AreEqual(0.0.SI<MeterPerSecond>(), outPort.Velocity); - Assert.AreEqual((-0.020237973).SI().GradientPercent.To<Radian>(), outPort.Gradient); + Assert.AreEqual((-0.020237973).SI().GradientPercent.As<Radian>(), outPort.Gradient); } [TestMethod] diff --git a/VectoCoreTest/Models/SimulationComponentData/FuelConsumptionMapTest.cs b/VectoCoreTest/Models/SimulationComponentData/FuelConsumptionMapTest.cs index 5061ea58ae..b8de40ea4e 100644 --- a/VectoCoreTest/Models/SimulationComponentData/FuelConsumptionMapTest.cs +++ b/VectoCoreTest/Models/SimulationComponentData/FuelConsumptionMapTest.cs @@ -8,41 +8,41 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Tests.Models.SimulationComponentData { - [TestClass] - public class FuelConsumptionMapTest - { - private const double Tolerance = 0.0001; + [TestClass] + public class FuelConsumptionMapTest + { + private const double Tolerance = 0.0001; - [TestMethod] - public void TestFuelConsumption_FixedPoints() - { - var map = FuelConsumptionMap.ReadFromFile(@"TestData\Components\24t Coach.vmap"); - var lines = File.ReadAllLines(@"TestData\Components\24t Coach.vmap").Skip(1).ToArray(); - AssertMapValuesEqual(lines, map); - } + [TestMethod] + public void TestFuelConsumption_FixedPoints() + { + var map = FuelConsumptionMap.ReadFromFile(@"TestData\Components\24t Coach.vmap"); + var lines = File.ReadAllLines(@"TestData\Components\24t Coach.vmap").Skip(1).ToArray(); + AssertMapValuesEqual(lines, map); + } - [TestMethod] - public void TestFuelConsumption_InterpolatedPoints() - { - var map = FuelConsumptionMap.ReadFromFile(@"TestData\Components\24t Coach.vmap"); - var lines = File.ReadAllLines(@"TestData\Components\24t CoachInterpolated.vmap").Skip(1).ToArray(); - AssertMapValuesEqual(lines, map); - } + [TestMethod] + public void TestFuelConsumption_InterpolatedPoints() + { + var map = FuelConsumptionMap.ReadFromFile(@"TestData\Components\24t Coach.vmap"); + var lines = File.ReadAllLines(@"TestData\Components\24t CoachInterpolated.vmap").Skip(1).ToArray(); + AssertMapValuesEqual(lines, map); + } - private static void AssertMapValuesEqual(string[] lines, FuelConsumptionMap map) - { - for (var i = 1; i < lines.Count(); i++) { - var entry = lines[i].Split(',').Select(x => double.Parse(x, CultureInfo.InvariantCulture)).ToArray(); - try { - Assert.AreEqual((double) entry[2].SI().Gramm.Per.Hour.To().Kilo.Gramm.Per.Second, - (double) map.GetFuelConsumption(entry[1].SI<NewtonMeter>(), entry[0].RPMtoRad()), - Tolerance, - string.Format("Line: {0}, n={1}, T={2}", (i + 2), entry[0].SI().Rounds.Per.Minute, entry[1])); - } catch (VectoException ex) { - throw new VectoException(string.Format("Row {0}: Error in ConsumptionMap n={1}, T={2}: {3}", - i + 2, entry[0], entry[1], ex.Message)); - } - } - } - } + private static void AssertMapValuesEqual(string[] lines, FuelConsumptionMap map) + { + for (var i = 1; i < lines.Count(); i++) { + var entry = lines[i].Split(',').Select(x => double.Parse(x, CultureInfo.InvariantCulture)).ToArray(); + try { + Assert.AreEqual((double) entry[2].SI().Gramm.Per.Hour.To().Kilo.Gramm.Per.Second, + (double) map.GetFuelConsumption(entry[1].SI<NewtonMeter>(), entry[0].RPMtoRad()), + Tolerance, + string.Format("Line: {0}, n={1}, T={2}", (i + 2), entry[0].SI().Rounds.Per.Minute, entry[1])); + } catch (VectoException ex) { + throw new VectoException(string.Format("Row {0}: Error in ConsumptionMap n={1}, T={2}: {3}", + i + 2, entry[0], entry[1], ex.Message)); + } + } + } + } } \ No newline at end of file diff --git a/VectoCoreTest/Utils/SITest.cs b/VectoCoreTest/Utils/SITest.cs index 05e6394e4f..23362db676 100644 --- a/VectoCoreTest/Utils/SITest.cs +++ b/VectoCoreTest/Utils/SITest.cs @@ -4,65 +4,65 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Tests.Utils { - [TestClass] - public class SITest - { - public static void AssertException<T>(Action func, string message) where T : Exception - { - try { - func(); - Assert.Fail(); - } catch (T ex) { - Assert.AreEqual(message, ex.Message); - } - } + [TestClass] + public class SITest + { + public static void AssertException<T>(Action func, string message) where T : Exception + { + try { + func(); + Assert.Fail(); + } catch (T ex) { + Assert.AreEqual(message, ex.Message); + } + } - [TestMethod] - public void TestSI() - { - var si = new SI(); - Assert.AreEqual(0.0, (double) si); - Assert.AreEqual("0 [-]", si.ToString()); - Assert.IsTrue(si.HasEqualUnit(new SI())); + [TestMethod] + public void TestSI() + { + var si = new SI(); + Assert.AreEqual(0.0, (double) si); + Assert.AreEqual("0 [-]", si.ToString()); + Assert.IsTrue(si.HasEqualUnit(new SI())); - var si2 = 5.0.SI().Watt; - Assert.AreEqual("5 [W]", si2.ToString()); + var si2 = 5.0.SI().Watt; + Assert.AreEqual("5 [W]", si2.ToString()); - var si3 = 2.SI().Radian.Per.Second; - Assert.AreEqual("2 [rad/s]", si3.ToString()); + var si3 = 2.SI().Radian.Per.Second; + Assert.AreEqual("2 [rad/s]", si3.ToString()); - var si4 = si2 * si3; - Assert.AreEqual("10 [W/s]", si4.ToString()); - Assert.IsTrue(si4.HasEqualUnit(new SI().Watt.Per.Second)); - Assert.AreEqual("10 [kgmm/ssss]", si4.ToBasicUnits().ToString()); + var si4 = si2 * si3; + Assert.AreEqual("10 [W/s]", si4.ToString()); + Assert.IsTrue(si4.HasEqualUnit(new SI().Watt.Per.Second)); + Assert.AreEqual("10 [kgmm/ssss]", si4.ToBasicUnits().ToString()); - var kg = 5.0.SI().Kilo.Gramm; - Assert.AreEqual(5.0, (double) kg); - Assert.AreEqual("5 [kg]", kg.ToString()); + var kg = 5.0.SI().Kilo.Gramm; + Assert.AreEqual(5.0, (double) kg); + Assert.AreEqual("5 [kg]", kg.ToString()); - kg = kg.To().Kilo.Gramm.Value(); - Assert.AreEqual(5.0, (double) kg); - Assert.AreEqual("5 [kg]", kg.ToString()); + kg = kg.To().Kilo.Gramm.Value(); + Assert.AreEqual(5.0, (double) kg); + Assert.AreEqual("5 [kg]", kg.ToString()); - kg = kg.To().Gramm.Value(); - Assert.AreEqual(5000, (double) kg); - Assert.AreEqual("5000 [g]", kg.ToString()); + kg = kg.To().Gramm.Value(); + Assert.AreEqual(5000, (double) kg); + Assert.AreEqual("5000 [g]", kg.ToString()); - var x = 5.SI(); - Assert.AreEqual((2.0 / 5.0).SI(), 2 / x); - Assert.AreEqual((5.0 / 2.0).SI(), x / 2); - Assert.AreEqual((2.0 * 5.0).SI(), 2 * x); - Assert.AreEqual((5.0 * 2.0).SI(), x * 2); + var x = 5.SI(); + Assert.AreEqual((2.0 / 5.0).SI(), 2 / x); + Assert.AreEqual((5.0 / 2.0).SI(), x / 2); + Assert.AreEqual((2.0 * 5.0).SI(), 2 * x); + Assert.AreEqual((5.0 * 2.0).SI(), x * 2); - Assert.AreEqual((2.0 / 5.0).SI(), 2.0 / x); - Assert.AreEqual((5.0 / 2.0).SI(), x / 2.0); - Assert.AreEqual((2 * 5).SI(), 2.0 * x); - Assert.AreEqual((5 * 2).SI(), x * 2.0); + Assert.AreEqual((2.0 / 5.0).SI(), 2.0 / x); + Assert.AreEqual((5.0 / 2.0).SI(), x / 2.0); + Assert.AreEqual((2 * 5).SI(), 2.0 * x); + Assert.AreEqual((5 * 2).SI(), x * 2.0); - var y = 2.SI(); - Assert.AreEqual((2 * 5).SI(), y * x); - } - } + var y = 2.SI(); + Assert.AreEqual((2 * 5).SI(), y * x); + } + } } \ No newline at end of file -- GitLab