diff --git a/VECTO.sln.DotSettings b/VECTO.sln.DotSettings index fbc71d3de43c9322a1724330c64857302970e1f1..11271d92f6489738bb9f961d2278206600823f3c 100644 --- a/VECTO.sln.DotSettings +++ b/VECTO.sln.DotSettings @@ -16,9 +16,10 @@ <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SIMPLE_EMBEDDED_STATEMENT_STYLE/@EntryValue">LINE_BREAK</s:String> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AROUND_MULTIPLICATIVE_OP/@EntryValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_SINGLE_LINE_ARRAY_INITIALIZER_BRACES/@EntryValue">True</s:Boolean> + <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SI/@EntryIndexedValue">SI</s:String> <s:Boolean x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=2BF7A1E51991F2458D2D1F0B29CF888B/@KeyIndexDefined">True</s:Boolean> <s:String x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=2BF7A1E51991F2458D2D1F0B29CF888B/AbsolutePath/@EntryValue">C:\Workspaces\VisualStudio\VECTO_quam\VECTO.sln.DotSettings</s:String> <s:Boolean x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=File2BF7A1E51991F2458D2D1F0B29CF888B/@KeyIndexDefined">True</s:Boolean> <s:Double x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=File2BF7A1E51991F2458D2D1F0B29CF888B/RelativePriority/@EntryValue">1</s:Double> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> - <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> \ No newline at end of file + <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> diff --git a/VectoCore/Models/Connector/Ports/ITnPort.cs b/VectoCore/Models/Connector/Ports/ITnPort.cs index f3df1dd0c8f1a4d839150851878c5068618e3450..0d6ddba5b82b143d8d717d83c0dac6d754e51f96 100644 --- a/VectoCore/Models/Connector/Ports/ITnPort.cs +++ b/VectoCore/Models/Connector/Ports/ITnPort.cs @@ -26,6 +26,6 @@ namespace TUGraz.VectoCore.Models.Connector.Ports /// <param name="dt">[s]</param> /// <param name="torque">[Nm]</param> /// <param name="angularVelocity">[rad/s]</param> - IResponse Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, RadianPerSecond angularVelocity); + IResponse Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, PerSecond angularVelocity); } } \ No newline at end of file diff --git a/VectoCore/Models/Simulation/Cockpit/IEngineCockpit.cs b/VectoCore/Models/Simulation/Cockpit/IEngineCockpit.cs index 75f2ba1f12e2aca17414c3eda758019901c68d23..27c6a586729a40915fcb13822fe9adbe4bf3d477 100644 --- a/VectoCore/Models/Simulation/Cockpit/IEngineCockpit.cs +++ b/VectoCore/Models/Simulation/Cockpit/IEngineCockpit.cs @@ -10,6 +10,6 @@ namespace TUGraz.VectoCore.Models.Simulation.Cockpit /// <summary> /// [rad/s] The current engine speed. /// </summary> - RadianPerSecond EngineSpeed(); + PerSecond EngineSpeed(); } } \ No newline at end of file diff --git a/VectoCore/Models/Simulation/Impl/VehicleContainer.cs b/VectoCore/Models/Simulation/Impl/VehicleContainer.cs index f47865ef3c23343b9c8a4165c93dd5dac246ef01..dc2aa6bf36ce98aedd0bfd5a4dde971bb69c1ba3 100644 --- a/VectoCore/Models/Simulation/Impl/VehicleContainer.cs +++ b/VectoCore/Models/Simulation/Impl/VehicleContainer.cs @@ -29,7 +29,7 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl #region IEngineCockpit - public RadianPerSecond EngineSpeed() + public PerSecond EngineSpeed() { if (_engine == null) { throw new VectoException("no engine available!"); diff --git a/VectoCore/Models/SimulationComponent/Data/CombustionEngineData.cs b/VectoCore/Models/SimulationComponent/Data/CombustionEngineData.cs index 9bc5d911a11f09b91e8da0ef2cca7430cc4a6f9c..9d01541e3a768124825432a29ae3d904560d5e66 100644 --- a/VectoCore/Models/SimulationComponent/Data/CombustionEngineData.cs +++ b/VectoCore/Models/SimulationComponent/Data/CombustionEngineData.cs @@ -68,17 +68,17 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data /// </summary> public SI Displacement { - get { return _data.Body.Displacement.SI().Cubic.Centi.Meter.To().Cubic.Meter.Value(); } - protected set { _data.Body.Displacement = (double) value.To().Cubic.Centi.Meter; } + get { return _data.Body.Displacement.SI().Cubic.Centi.Meter.ConvertTo().Cubic.Meter.Value(); } + protected set { _data.Body.Displacement = (double) value.ConvertTo().Cubic.Centi.Meter; } } /// <summary> /// [rad/s] /// </summary> - public RadianPerSecond IdleSpeed + public PerSecond IdleSpeed { - get { return _data.Body.IdleSpeed.SI().Rounds.Per.Minute.To<RadianPerSecond>(); } - protected set { _data.Body.IdleSpeed = (double) value.To().Rounds.Per.Minute; } + get { return _data.Body.IdleSpeed.RPMtoRad(); } + protected set { _data.Body.IdleSpeed = (double) value.ConvertTo().Rounds.Per.Minute; } } /// <summary> @@ -87,7 +87,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data public SI Inertia { get { return _data.Body.Inertia.SI().Kilo.Gramm.Square.Meter; } - protected set { _data.Body.Inertia = (double) value.To().Kilo.Gramm.Square.Meter; } + protected set { _data.Body.Inertia = (double) value.ConvertTo().Kilo.Gramm.Square.Meter; } } /// <summary> @@ -95,8 +95,8 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data /// </summary> public SI WHTCUrban { - get { return _data.Body.WHTCUrban.SI().Gramm.Per.Kilo.Watt.Hour.To().Kilo.Gramm.Per.Watt.Second.Value(); } - protected set { _data.Body.WHTCUrban = (double) value.To().Gramm.Per.Kilo.Watt.Hour; } + get { return _data.Body.WHTCUrban.SI().Gramm.Per.Kilo.Watt.Hour.ConvertTo().Kilo.Gramm.Per.Watt.Second.Value(); } + protected set { _data.Body.WHTCUrban = (double) value.ConvertTo().Gramm.Per.Kilo.Watt.Hour; } } /// <summary> @@ -104,8 +104,8 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data /// </summary> public SI WHTCRural { - get { return _data.Body.WHTCRural.SI().Gramm.Per.Kilo.Watt.Hour.To().Kilo.Gramm.Per.Watt.Second.Value(); } - protected set { _data.Body.WHTCRural = (double) value.To().Gramm.Per.Kilo.Watt.Hour; } + get { return _data.Body.WHTCRural.SI().Gramm.Per.Kilo.Watt.Hour.ConvertTo().Kilo.Gramm.Per.Watt.Second.Value(); } + protected set { _data.Body.WHTCRural = (double) value.ConvertTo().Gramm.Per.Kilo.Watt.Hour; } } /// <summary> @@ -114,7 +114,9 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data public SI WHTCMotorway { get { 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; } + return + _data.Body.WHTCMotorway.SI().Gramm.Per.Kilo.Watt.Hour.ConvertTo().Kilo.Gramm.Per.Watt.Second.Value(); + protected set { _data.Body.WHTCMotorway = (double) value.ConvertTo().Gramm.Per.Kilo.Watt.Hour; } } [DataMember] diff --git a/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs b/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs index 46396270db95553cba9756b17a6441335985390e..a1e4032d5a4a9070912bc18e1267e7841a2506b1 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 PerSecond 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.Cast<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.Cast<MeterPerSecond>(), + RoadGradient = row.ParseDoubleOrGetDefault(Fields.RoadGradient), + AdditionalAuxPowerDemand = + row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.Cast<Watt>(), + EngineSpeed = + row.ParseDoubleOrGetDefault(Fields.EngineSpeed).SI().Rounds.Per.Minute.Cast<PerSecond>(), + Gear = row.ParseDoubleOrGetDefault(Fields.Gear), + AirSpeedRelativeToVehicle = + row.ParseDoubleOrGetDefault(Fields.AirSpeedRelativeToVehicle) + .SI() + .Kilo.Meter.Per.Hour.Cast<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.Cast<MeterPerSecond>(), + RoadGradient = row.ParseDoubleOrGetDefault(Fields.RoadGradient), + AdditionalAuxPowerDemand = + row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.Cast<Watt>(), + Gear = row.ParseDoubleOrGetDefault(Fields.Gear), + EngineSpeed = + row.ParseDoubleOrGetDefault(Fields.EngineSpeed).SI().Rounds.Per.Minute.Cast<PerSecond>(), + AirSpeedRelativeToVehicle = + row.ParseDoubleOrGetDefault(Fields.AirSpeedRelativeToVehicle) + .SI() + .Kilo.Meter.Per.Hour.Cast<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.Cast<PerSecond>(), + AdditionalAuxPowerDemand = + row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.Cast<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.Cast<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 52163fc134e782bdd8c7d103eb4c07f9238a1019..f1ef8218c1b7f94cb5820c1446974730e96d65ab 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.Cast<PerSecond>(), + Torque = row.ParseDouble(Fields.Torque).SI<NewtonMeter>(), + FuelConsumption = + row.ParseDouble(Fields.FuelConsumption).SI().Gramm.Per.Hour.ConvertTo().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, PerSecond engineSpeed) + { + // delauney map needs is initialised with rpm, therefore the engineSpeed has to be converted. + return + _fuelMap.Interpolate((double) torque, (double) engineSpeed.ConvertTo().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 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/Data/Engine/FullLoadCurve.cs b/VectoCore/Models/SimulationComponent/Data/Engine/FullLoadCurve.cs index 78ef6b076104e00f6b432693ea170eb1d27e235a..f27ff9bb425571e519bda435e38f7e250bef9977 100644 --- a/VectoCore/Models/SimulationComponent/Data/Engine/FullLoadCurve.cs +++ b/VectoCore/Models/SimulationComponent/Data/Engine/FullLoadCurve.cs @@ -11,6 +11,9 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine { + /// <summary> + /// Represents the Full load curve. + /// </summary> public class FullLoadCurve : SimulationComponentData { [JsonProperty] private List<FullLoadCurveEntry> _entries; @@ -26,7 +29,8 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine //todo Contract.Requires<VectoException>(data.Rows.Count < 2, "FullLoadCurve must consist of at least two lines with numeric values (below file header)"); if (data.Rows.Count < 2) { - throw new VectoException("FullLoadCurve must consist of at least two lines with numeric values (below file header)"); + throw new VectoException( + "FullLoadCurve must consist of at least two lines with numeric values (below file header)"); } List<FullLoadCurveEntry> entries; @@ -59,7 +63,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>() @@ -71,7 +75,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>() @@ -81,87 +85,88 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine /// <summary> /// [rad/s] => [Nm] /// </summary> - /// <param name="angularFrequency">[rad/s]</param> + /// <param name="angularVelocity">[rad/s]</param> /// <returns>[Nm]</returns> - public NewtonMeter FullLoadStationaryTorque(RadianPerSecond angularFrequency) + public NewtonMeter FullLoadStationaryTorque(PerSecond angularVelocity) { - var idx = FindIndex(angularFrequency); + var idx = FindIndex(angularVelocity); return VectoMath.Interpolate((double) _entries[idx - 1].EngineSpeed, (double) _entries[idx].EngineSpeed, (double) _entries[idx - 1].TorqueFullLoad, (double) _entries[idx].TorqueFullLoad, - (double) angularFrequency).SI<NewtonMeter>(); + (double) angularVelocity).SI<NewtonMeter>(); } /// <summary> /// [rad/s] => [W] /// </summary> - /// <param name="angularFrequency">[rad/s]</param> + /// <param name="angularVelocity">[rad/s]</param> /// <returns>[W]</returns> - public Watt FullLoadStationaryPower(RadianPerSecond angularFrequency) + public Watt FullLoadStationaryPower(PerSecond angularVelocity) { - return Formulas.TorqueToPower(FullLoadStationaryTorque(angularFrequency), angularFrequency); + return Formulas.TorqueToPower(FullLoadStationaryTorque(angularVelocity), angularVelocity); } /// <summary> /// [rad/s] => [Nm] /// </summary> - /// <param name="angularFrequency">[rad/s]</param> + /// <param name="angularVelocity">[rad/s]</param> /// <returns>[Nm]</returns> - public NewtonMeter DragLoadStationaryTorque(RadianPerSecond angularFrequency) + public NewtonMeter DragLoadStationaryTorque(PerSecond angularVelocity) { - var idx = FindIndex(angularFrequency); + var idx = FindIndex(angularVelocity); return VectoMath.Interpolate((double) _entries[idx - 1].EngineSpeed, (double) _entries[idx].EngineSpeed, (double) _entries[idx - 1].TorqueDrag, (double) _entries[idx].TorqueDrag, - (double) angularFrequency).SI<NewtonMeter>(); + (double) angularVelocity).SI<NewtonMeter>(); } /// <summary> /// [rad/s] => [W]. /// </summary> - /// <param name="angularFrequency">[rad/s]</param> + /// <param name="angularVelocity">[rad/s]</param> /// <returns>[W]</returns> - public Watt DragLoadStationaryPower(RadianPerSecond angularFrequency) + public Watt DragLoadStationaryPower(PerSecond angularVelocity) { - Contract.Requires(angularFrequency.HasEqualUnit(new SI().Radian.Per.Second)); + Contract.Requires(angularVelocity.HasEqualUnit(new SI().Radian.Per.Second)); Contract.Ensures(Contract.Result<SI>().HasEqualUnit(new SI().Watt)); - return Formulas.TorqueToPower(DragLoadStationaryTorque(angularFrequency), angularFrequency); + return Formulas.TorqueToPower(DragLoadStationaryTorque(angularVelocity), angularVelocity); } /// <summary> /// [rad/s] => [-] /// </summary> - /// <param name="angularFrequency">[rad/s]</param> + /// <param name="angularVelocity">[rad/s]</param> /// <returns>[-]</returns> - public SI PT1(SI angularFrequency) + public double PT1(PerSecond angularVelocity) { - Contract.Requires(angularFrequency.HasEqualUnit(new SI().Radian.Per.Second)); + Contract.Requires(angularVelocity.HasEqualUnit(new SI().Radian.Per.Second)); Contract.Ensures(Contract.Result<SI>().HasEqualUnit(new SI())); - 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(); + var idx = FindIndex(angularVelocity); + return VectoMath.Interpolate(_entries[idx - 1].EngineSpeed.Double(), + _entries[idx].EngineSpeed.Double(), + _entries[idx - 1].PT1.Double(), _entries[idx].PT1.Double(), + angularVelocity.Double()); } /// <summary> - /// [rad/s] => index. Get item index for engineSpeed. + /// [rad/s] => index. Get item index for angularVelocity. /// </summary> - /// <param name="engineSpeed">[rad/s]</param> + /// <param name="angularVelocity">[rad/s]</param> /// <returns>index</returns> - protected int FindIndex(SI engineSpeed) + protected int FindIndex(PerSecond angularVelocity) { - Contract.Requires(engineSpeed.HasEqualUnit(new SI().Radian.Per.Second)); + Contract.Requires(angularVelocity.HasEqualUnit(new SI().Radian.Per.Second)); int idx; - if (engineSpeed < _entries[0].EngineSpeed) { + if (angularVelocity < _entries[0].EngineSpeed) { Log.ErrorFormat("requested rpm below minimum rpm in FLD curve - extrapolating. n: {0}, rpm_min: {1}", - engineSpeed.To().Rounds.Per.Minute, _entries[0].EngineSpeed.To().Rounds.Per.Minute); + angularVelocity.ConvertTo().Rounds.Per.Minute, _entries[0].EngineSpeed.ConvertTo().Rounds.Per.Minute); idx = 1; } else { - idx = _entries.FindIndex(x => x.EngineSpeed > engineSpeed); + idx = _entries.FindIndex(x => x.EngineSpeed > angularVelocity); } if (idx <= 0) { - idx = engineSpeed > _entries[0].EngineSpeed ? _entries.Count - 1 : 1; + idx = angularVelocity > _entries[0].EngineSpeed ? _entries.Count - 1 : 1; } return idx; } @@ -194,7 +199,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine /// <summary> /// [rad/s] engine speed /// </summary> - public RadianPerSecond EngineSpeed { get; set; } + public PerSecond EngineSpeed { get; set; } /// <summary> /// [Nm] full load torque diff --git a/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs b/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs index 027776435497b56a4b76c9aeb5d3dc633479eb46..6ee631a1149a7a2500a0ac476c39f7d49c68ffc4 100644 --- a/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs +++ b/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs @@ -55,7 +55,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl #region IEngineCockpit - public RadianPerSecond EngineSpeed() + PerSecond IEngineCockpit.EngineSpeed() { return _previousState.EngineSpeed; } @@ -73,14 +73,14 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl #region ITnOutPort - IResponse ITnOutPort.Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, RadianPerSecond engineSpeed) + IResponse ITnOutPort.Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, PerSecond engineSpeed) { _currentState.EngineSpeed = engineSpeed; _currentState.AbsTime = absTime; 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; @@ -122,13 +122,13 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl writer[ModalResultField.Tq_drag] = (double) _currentState.FullDragTorque; writer[ModalResultField.Tq_full] = (double) _currentState.DynamicFullLoadTorque; writer[ModalResultField.Tq_eng] = (double) _currentState.EngineTorque; - writer[ModalResultField.n] = (double) _currentState.EngineSpeed.To().Rounds.Per.Minute; + writer[ModalResultField.n] = (double) _currentState.EngineSpeed.ConvertTo().Rounds.Per.Minute; try { writer[ModalResultField.FC] = (double) _data.ConsumptionMap.GetFuelConsumption(_currentState.EngineTorque, _currentState.EngineSpeed) - .To() + .ConvertTo() .Gramm.Per.Hour; } catch (VectoException ex) { Log.WarnFormat("t: {0} - {1} n: {2} Tq: {3}", _currentState.AbsTime.TotalSeconds, ex.Message, @@ -152,11 +152,11 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl if (_currentState.FullDragPower >= 0 && requestedEnginePower < 0) { throw new VectoSimulationException(String.Format("t: {0} P_engine_drag > 0! n: {1} [1/min] ", - _currentState.AbsTime, _currentState.EngineSpeed.To().Rounds.Per.Minute)); + _currentState.AbsTime, _currentState.EngineSpeed.ConvertTo().Rounds.Per.Minute)); } if (_currentState.DynamicFullLoadPower <= 0 && requestedEnginePower > 0) { throw new VectoSimulationException(String.Format("t: {0} P_engine_full < 0! n: {1} [1/min] ", - _currentState.AbsTime, _currentState.EngineSpeed.To().Rounds.Per.Minute)); + _currentState.AbsTime, _currentState.EngineSpeed.ConvertTo().Rounds.Per.Minute)); } } @@ -217,12 +217,12 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl } /// <summary> - /// computes full load power from gear [-], angularFrequency [rad/s] and dt [s]. + /// computes full load power from gear [-], angularVelocity [rad/s] and dt [s]. /// </summary> /// <param name="gear"></param> - /// <param name="angularFrequency">[rad/s]</param> + /// <param name="angularVelocity">[rad/s]</param> /// <param name="dt">[s]</param> - protected void ComputeFullLoadPower(uint gear, RadianPerSecond angularFrequency, TimeSpan dt) + protected void ComputeFullLoadPower(uint gear, PerSecond angularVelocity, TimeSpan dt) { if (dt.Ticks == 0) { throw new VectoException("ComputeFullLoadPower cannot compute at time 0."); @@ -235,19 +235,19 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl //_currentState.StationaryFullLoadPower = _data.GetFullLoadCurve(gear).FullLoadStationaryPower(rpm); _currentState.StationaryFullLoadTorque = - _data.GetFullLoadCurve(gear).FullLoadStationaryTorque(angularFrequency); + _data.GetFullLoadCurve(gear).FullLoadStationaryTorque(angularVelocity); _currentState.StationaryFullLoadPower = Formulas.TorqueToPower(_currentState.StationaryFullLoadTorque, - angularFrequency); + angularVelocity); - var pt1 = _data.GetFullLoadCurve(gear).PT1(angularFrequency); + var pt1 = _data.GetFullLoadCurve(gear).PT1(angularVelocity); - 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; _currentState.DynamicFullLoadTorque = Formulas.PowerToTorque(_currentState.DynamicFullLoadPower, - angularFrequency); + angularVelocity); } protected bool IsFullLoad(Watt requestedPower, Watt maxPower) @@ -257,17 +257,17 @@ 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> + /// <param name="angularVelocity">[rad/s]</param> /// <returns>[W]</returns> - protected Watt InertiaPowerLoss(NewtonMeter torque, RadianPerSecond engineSpeed) + protected Watt InertiaPowerLoss(NewtonMeter torque, PerSecond angularVelocity) { - var deltaEngineSpeed = engineSpeed - _previousState.EngineSpeed; - var avgEngineSpeed = (_previousState.EngineSpeed + engineSpeed) / new SI(2).Second; + var deltaEngineSpeed = angularVelocity - _previousState.EngineSpeed; + var avgEngineSpeed = (_previousState.EngineSpeed + angularVelocity) / 2.0.SI<Second>(); var result = _data.Inertia * deltaEngineSpeed * avgEngineSpeed; - return result.To<Watt>(); + return result.Cast<Watt>(); } public class EngineState @@ -287,7 +287,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl /// <summary> /// [rad/s] /// </summary> - public RadianPerSecond EngineSpeed { get; set; } + public PerSecond EngineSpeed { get; set; } /// <summary> /// [W] diff --git a/VectoCore/Models/SimulationComponent/Impl/EngineOnlyAuxiliary.cs b/VectoCore/Models/SimulationComponent/Impl/EngineOnlyAuxiliary.cs index 84c3ca603506ade23b7e9e778fe99f718e9f57d2..405bb80e321d12ae8e03ae0f284d075a275bd419 100644 --- a/VectoCore/Models/SimulationComponent/Impl/EngineOnlyAuxiliary.cs +++ b/VectoCore/Models/SimulationComponent/Impl/EngineOnlyAuxiliary.cs @@ -50,7 +50,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl #region ITnOutPort - IResponse ITnOutPort.Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, RadianPerSecond engineSpeed) + IResponse ITnOutPort.Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, PerSecond engineSpeed) { if (_outPort == null) { Log.ErrorFormat("{0} cannot handle incoming request - no outport available", absTime); @@ -60,7 +60,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/EngineOnlyGearbox.cs b/VectoCore/Models/SimulationComponent/Impl/EngineOnlyGearbox.cs index c50186a671dd51d0363ea44c7adc95c84d08cda7..50b9421f52c7595b79f39f4cf072af8a843999bd 100644 --- a/VectoCore/Models/SimulationComponent/Impl/EngineOnlyGearbox.cs +++ b/VectoCore/Models/SimulationComponent/Impl/EngineOnlyGearbox.cs @@ -52,7 +52,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl #region ITnOutPort - IResponse ITnOutPort.Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, RadianPerSecond engineSpeed) + IResponse ITnOutPort.Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, PerSecond engineSpeed) { if (_outPort == null) { Log.ErrorFormat("{0} cannot handle incoming request - no outport available", absTime); diff --git a/VectoCore/Models/SimulationComponent/Impl/Gearbox.cs b/VectoCore/Models/SimulationComponent/Impl/Gearbox.cs index 4340c4d7860eaf4e4d2b928d473e5e416578d273..46abd5a94fb77d1a4688255b222e0a521fadd28f 100644 --- a/VectoCore/Models/SimulationComponent/Impl/Gearbox.cs +++ b/VectoCore/Models/SimulationComponent/Impl/Gearbox.cs @@ -41,7 +41,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl #region ITnOutPort - IResponse ITnOutPort.Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, RadianPerSecond engineSpeed) + IResponse ITnOutPort.Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, PerSecond engineSpeed) { throw new NotImplementedException(); } diff --git a/VectoCore/Models/SimulationComponent/Impl/TimeBasedDrivingCycle.cs b/VectoCore/Models/SimulationComponent/Impl/TimeBasedDrivingCycle.cs index 973f35ba1f97f68e26804567578201b7e40a3d5f..ad01e68fe628a215ea5b18f64bc62480dc3e0c31 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.Cast<Radian>()); } #endregion diff --git a/VectoCore/Utils/DoubleExtensionMethods.cs b/VectoCore/Utils/DoubleExtensionMethods.cs index c77746f498884fa43a1f444ecda5ff7b69fdd2cb..1daa25650ce682602be33a5de5f4a6afaad90726 100644 --- a/VectoCore/Utils/DoubleExtensionMethods.cs +++ b/VectoCore/Utils/DoubleExtensionMethods.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics.Contracts; namespace TUGraz.VectoCore.Utils { @@ -7,62 +6,62 @@ namespace TUGraz.VectoCore.Utils { 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 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 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 IsPositive(this double d, double tolerance = Tolerance) { return d.IsGreaterOrEqual(0.0, tolerance); } - public static RadianPerSecond RPMtoRad(this double d) + /// <summary> + /// Converts the double-value from rounds per minute to the SI Unit PerSecond + /// </summary> + /// <param name="d"></param> + /// <returns></returns> + public static PerSecond RPMtoRad(this double d) { - return d.SI().Rounds.Per.Minute.To<RadianPerSecond>(); + return d.SI().Rounds.Per.Minute.ConvertTo().Radian.Per.Second.Cast<PerSecond>(); } /// <summary> - /// Gets the SI representation of the double (unit-less). + /// Gets the SI representation of the number (unit-less). /// </summary> - /// <param name="d"></param> - /// <returns></returns> - [Pure] public static SI SI(this double d) { return (SI) d; } - [Pure] - public static T SI<T>(this double d) where T : SI + + /// <summary> + /// Gets the special SI class of the number. + /// </summary> + public static T SI<T>(this double d) where T : SIBase<T>, new() { - return (T) Activator.CreateInstance(typeof (T), d); + return SIBase<T>.Create(d); } } } \ No newline at end of file diff --git a/VectoCore/Utils/Formulas.cs b/VectoCore/Utils/Formulas.cs index a25fafcd3611edf3e77d34dcc045b77a0437be70..1dfe96caaef92dd32107f51052b42accfe10d2dc 100644 --- a/VectoCore/Utils/Formulas.cs +++ b/VectoCore/Utils/Formulas.cs @@ -1,5 +1,3 @@ -using System.Diagnostics.Contracts; - namespace TUGraz.VectoCore.Utils { public static class Formulas @@ -8,24 +6,22 @@ namespace TUGraz.VectoCore.Utils /// [Nm], [rad/s] => [W]. Calculates the power from torque and angular velocity. /// </summary> /// <param name="torque">[Nm]</param> - /// <param name="angularFrequency">[rad/s]</param> + /// <param name="angularVelocity">[rad/s]</param> /// <returns>power [W]</returns> - [Pure] - public static Watt TorqueToPower(NewtonMeter torque, RadianPerSecond angularFrequency) + public static Watt TorqueToPower(NewtonMeter torque, PerSecond angularVelocity) { - return (torque * angularFrequency).To<Watt>(); + return torque * angularVelocity; } /// <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> + /// <param name="angularVelocity">[rad/s]</param> /// <returns>torque [Nm]</returns> - [Pure] - public static NewtonMeter PowerToTorque(SI power, RadianPerSecond angularFrequency) + public static NewtonMeter PowerToTorque(Watt power, PerSecond angularVelocity) { - return (power / angularFrequency).To<NewtonMeter>(); + return power / angularVelocity; } } } \ No newline at end of file diff --git a/VectoCore/Utils/IntExtensionMethods.cs b/VectoCore/Utils/IntExtensionMethods.cs index 3c1dde275d53f6a0fdb5c726bd7577cfc9d6f170..0075574fbf4b245c924c328da76a9b59cec7f012 100644 --- a/VectoCore/Utils/IntExtensionMethods.cs +++ b/VectoCore/Utils/IntExtensionMethods.cs @@ -5,15 +5,34 @@ namespace TUGraz.VectoCore.Utils { public static class IntExtensionMethods { - public static SI SI(this int i) + /// <summary> + /// Converts the value from rounds per minute to the SI Unit PerSecond + /// </summary> + /// <param name="d"></param> + /// <returns></returns> + public static PerSecond RPMtoRad(this int d) { - return new SI(i); + return d.SI().Rounds.Per.Minute.ConvertTo().Radian.Per.Second.Cast<PerSecond>(); } - [Pure] - public static T SI<T>(this int d) where T : SI + /// <summary> + /// Gets the SI representation of the number (unit-less). + /// </summary> + /// <param name="d"></param> + /// <returns></returns> + public static SI SI(this int d) { - return (T) Activator.CreateInstance(typeof (T), d); + return (SI) d; + } + + /// <summary> + /// Gets the special SI class of the number. + /// </summary> + /// <param name="d"></param> + /// <returns></returns> + public static T SI<T>(this int d) where T : SIBase<T>, new() + { + return SIBase<T>.Create(d); } } } \ No newline at end of file diff --git a/VectoCore/Utils/SI.cs b/VectoCore/Utils/SI.cs index e18adcfa9140b2dbff6ab45dafb7515d9b1a8c64..82a168b371c360553dd1345147f699c56ad7568d 100644 --- a/VectoCore/Utils/SI.cs +++ b/VectoCore/Utils/SI.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Linq; using System.Runtime.Serialization; @@ -8,68 +9,250 @@ 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 MeterPerSecond() : this(0) {} + protected MeterPerSecond(double val) : base(val, new SI().Meter.Per.Second) {} } - public class Radian : SI + public class Second : SIBase<Second> { - public Radian(double val = 0) : base(val, new SI().Radian) {} + public Second() : this(0) {} + protected Second(double val) : base(val, new SI().Second) {} } - public class Second : SI + public class Watt : SIBase<Watt> { - public Second(double val = 0) : base(val, new SI().Second) {} + public Watt() : this(0) {} + protected Watt(double val) : base(val, new SI().Watt) {} + + public static PerSecond operator /(Watt watt, NewtonMeter newtonMeter) + { + return ((watt as SI) / newtonMeter).Cast<PerSecond>(); + } + + public static NewtonMeter operator /(Watt watt, PerSecond perSecond) + { + return ((watt as SI) / perSecond).Cast<NewtonMeter>(); + } } - public class Watt : SI + public class PerSecond : SIBase<PerSecond> { - public Watt(double val = 0) : base(val, new SI().Watt) {} + public PerSecond() : this(0) {} + protected PerSecond(double val) : base(val, new SI().Radian.Per.Second) {} } - public class RadianPerSecond : SI + public class RoundsPerMinute : SIBase<RoundsPerMinute> { - public RadianPerSecond(double val = 0) : base(val, new SI().Radian.Per.Second) {} + public RoundsPerMinute() : this(0) {} + protected RoundsPerMinute(double val) : base(val, new SI().Rounds.Per.Minute) {} } - public class RoundsPerMinute : SI + + public class Newton : SIBase<Newton> { - public RoundsPerMinute(double val = 0) : base(val, new SI().Rounds.Per.Minute) {} + public Newton() : this(0) {} + protected Newton(double val) : base(val, new SI().Newton) {} } - public class NewtonMeter : SI + public class Radian : SIBase<Radian> { - public NewtonMeter(double val = 0) : base(val, new SI().Newton.Meter) {} + public Radian() : this(0) {} + protected Radian(double val) : base(val, new SI().Radian) {} } - public class Newton : SI + public class NewtonMeter : SIBase<NewtonMeter> { - public Newton(double val = 0) : base(val, new SI().Newton) {} + public NewtonMeter() : this(0) {} + protected NewtonMeter(double val) : base(val, new SI().Newton.Meter) {} + + public static Watt operator *(NewtonMeter newtonMeter, PerSecond perSecond) + { + return ((newtonMeter as SI) * perSecond).Cast<Watt>(); + } + + public static Watt operator *(PerSecond perSecond, NewtonMeter newtonMeter) + { + return ((perSecond as SI) * newtonMeter).Cast<Watt>(); + } + + public static Second operator /(NewtonMeter newtonMeter, Watt watt) + { + return ((newtonMeter as SI) / watt).Cast<Second>(); + } } + public abstract class SIBase<T> : SI where T : SIBase<T>, new() + { + public static T Create(double val) + { + return new T { Val = val }; + } + + protected SIBase() {} + protected SIBase(double val) : 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).Cast<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).Cast<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)).Cast<T>(); + } + + public static T operator -(SIBase<T> si, double d) + { + return ((si as SI) - d).Cast<T>(); + } + + public static T operator -(double d, SIBase<T> si) + { + return (d - (si as SI)).Cast<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).Cast<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).Cast<T>(); + } + + #endregion + } + + /// <summary> + /// Class for Representing SI Units. + /// </summary> [DataContract] - public class SI + public class SI : IComparable { - [DataMember] protected readonly string[] Denominator; + [DataMember] protected readonly Unit[] Denominator; [DataMember] protected readonly int Exponent; - [DataMember] protected readonly string[] Numerator; + [DataMember] protected readonly Unit[] Numerator; [DataMember] protected readonly bool Reciproc; [DataMember] protected readonly bool Reverse; - [DataMember] protected readonly double Val; + [DataMember] protected double Val; + + [SuppressMessage("ReSharper", "InconsistentNaming")] + protected enum Unit + { + /// <summary> + /// kilo + /// </summary> + k, + + /// <summary> + /// seconds + /// </summary> + s, + + /// <summary> + /// meter + /// </summary> + m, + + /// <summary> + /// gramm + /// </summary> + g, + + /// <summary> + /// Watt + /// </summary> + W, + + /// <summary> + /// Newton + /// </summary> + N, + + /// <summary> + /// % + /// </summary> + Percent, + + /// <summary> + /// minutes + /// </summary> + min, + + /// <summary> + /// centi + /// </summary> + c, + + /// <summary> + /// Hour + /// </summary> + h + } + /// <summary> + /// Creates a new dimensionless SI Unit. + /// </summary> + /// <param name="val"></param> public SI(double val = 0.0) { Val = val; Reciproc = false; Reverse = false; - Numerator = new string[0]; - Denominator = new string[0]; + Numerator = new Unit[0]; + Denominator = new Unit[0]; Exponent = 1; } - protected SI(double val, IEnumerable<string> numerator, IEnumerable<string> denominator, bool reciproc = false, + protected SI(double val, IEnumerable<Unit> numerator, IEnumerable<Unit> denominator, + bool reciproc = false, bool reverse = false, int exponent = 1) { Contract.Requires(numerator != null); @@ -95,7 +278,7 @@ namespace TUGraz.VectoCore.Utils protected SI(double val, SI unit) : this(val, unit.Numerator, unit.Denominator) {} - protected SI(SI si, double? factor = null, string fromUnit = null, string toUnit = null, + protected SI(SI si, double? factor = null, Unit? fromUnit = null, Unit? toUnit = null, bool? reciproc = null, bool? reverse = null, int? exponent = null) { Contract.Requires(si != null); @@ -140,65 +323,81 @@ namespace TUGraz.VectoCore.Utils Denominator = numerator.ToArray(); } - private void UpdateUnit(string fromUnit, string toUnit, ICollection<string> units) + private void UpdateUnit(Unit? fromUnit, Unit? toUnit, ICollection<Unit> units) { - if (Reverse && !string.IsNullOrEmpty(fromUnit)) { - if (units.Contains(fromUnit)) { - units.Remove(fromUnit); + if (Reverse && fromUnit.HasValue) { + if (units.Contains(fromUnit.Value)) { + units.Remove(fromUnit.Value); } else { - throw new VectoException("Unit missing. Conversion not possible."); + throw new VectoException(string.Format("Unit missing. Conversion not possible. [{0}] does not contain a [{1}].", + string.Join(", ", units), fromUnit)); } } - if (!string.IsNullOrEmpty(toUnit)) { - units.Add(toUnit); + if (toUnit.HasValue) { + units.Add(toUnit.Value); } } /// <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 ConvertTo(). + /// The Conversion Mode is active until an arithmetic operator is used (+,-,*,/), + /// or the .Value-Method, or the .Cast-Method were called. + /// ATTENTION: Before returning an SI Unit, ensure to cancel Conversion Mode (with .Value or .Cast). /// </summary> /// <returns></returns> - public SI To() + public SI ConvertTo() { 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 allow such an cast). + /// </summary> + /// <typeparam name="T"></typeparam> + /// <returns></returns> + public T Cast<T>() where T : SIBase<T>, new() { - var t = (T) Activator.CreateInstance(typeof (T), Val); - Contract.Assert(HasEqualUnit(t), string.Format("SI Unit Conversion failed: From {0} to {1}", this, t)); + var t = SIBase<T>.Create(Val); + if (!HasEqualUnit(t)) { + throw new VectoException(string.Format("SI Unit Conversion failed: From {0} to {1}", this, t)); + } return t; } + /// <summary> + /// Converts the derived SI units to the basic units and returns this as a new SI object. + /// </summary> + /// <returns></returns> public SI ToBasicUnits() { - var numerator = new List<string>(); - var denominator = new List<string>(); + var numerator = new List<Unit>(); + var denominator = new List<Unit>(); Numerator.ToList().ForEach(unit => ConvertToBasicUnits(unit, numerator, denominator)); Denominator.ToList().ForEach(unit => ConvertToBasicUnits(unit, denominator, numerator)); return new SI(Val, numerator, denominator); } - private static void ConvertToBasicUnits(string unit, ICollection<string> numerator, - ICollection<string> denominator) + + private static void ConvertToBasicUnits(Unit unit, ICollection<Unit> numerator, + ICollection<Unit> denominator) { switch (unit) { - case "W": - numerator.Add("k"); - numerator.Add("g"); - numerator.Add("m"); - numerator.Add("m"); - denominator.Add("s"); - denominator.Add("s"); - denominator.Add("s"); + case Unit.W: + numerator.Add(Unit.k); + numerator.Add(Unit.g); + numerator.Add(Unit.m); + numerator.Add(Unit.m); + denominator.Add(Unit.s); + denominator.Add(Unit.s); + denominator.Add(Unit.s); break; - case "N": - numerator.Add("k"); - numerator.Add("g"); - numerator.Add("m"); - denominator.Add("s"); - denominator.Add("s"); + case Unit.N: + numerator.Add(Unit.k); + numerator.Add(Unit.g); + numerator.Add(Unit.m); + denominator.Add(Unit.s); + denominator.Add(Unit.s); break; default: numerator.Add(unit); @@ -207,9 +406,9 @@ namespace TUGraz.VectoCore.Utils } /// <summary> - /// Gets the basic scalar value. + /// Gets the basic double value. /// </summary> - protected double ScalarValue() + public double Double() { return Val; } @@ -268,7 +467,7 @@ namespace TUGraz.VectoCore.Utils [DebuggerHidden] public SI Gramm { - get { return new SI(new SI(this, toUnit: "k"), 0.001, "g", "g"); } + get { return new SI(new SI(this, toUnit: Unit.k), 0.001, Unit.g, Unit.g); } } /// <summary> @@ -277,7 +476,7 @@ namespace TUGraz.VectoCore.Utils [DebuggerHidden] public SI Newton { - get { return new SI(this, fromUnit: "N", toUnit: "N"); } + get { return new SI(this, fromUnit: Unit.N, toUnit: Unit.N); } } /// <summary> @@ -286,7 +485,7 @@ namespace TUGraz.VectoCore.Utils [DebuggerHidden] public SI Watt { - get { return new SI(this, fromUnit: "W", toUnit: "W"); } + get { return new SI(this, fromUnit: Unit.W, toUnit: Unit.W); } } /// <summary> @@ -295,7 +494,7 @@ namespace TUGraz.VectoCore.Utils [DebuggerHidden] public SI Meter { - get { return new SI(this, fromUnit: "m", toUnit: "m"); } + get { return new SI(this, fromUnit: Unit.m, toUnit: Unit.m); } } /// <summary> @@ -304,7 +503,7 @@ namespace TUGraz.VectoCore.Utils [DebuggerHidden] public SI Second { - get { return new SI(this, fromUnit: "s", toUnit: "s"); } + get { return new SI(this, fromUnit: Unit.s, toUnit: Unit.s); } } /// <summary> @@ -313,12 +512,12 @@ namespace TUGraz.VectoCore.Utils [DebuggerHidden] public SI Radian { - get { return new SI(this, fromUnit: "rad", toUnit: "rad"); } + get { return new SI(this); } } public SI GradientPercent { - get { return new SI(this, factor: Math.Atan(Val) / Val, fromUnit: "%", toUnit: "rad"); } + get { return new SI(this, factor: Math.Atan(Val) / Val, fromUnit: Unit.Percent); } } /// <summary> @@ -327,7 +526,7 @@ namespace TUGraz.VectoCore.Utils [DebuggerHidden] public SI Rounds { - get { return new SI(this, 2 * Math.PI, toUnit: "rad"); } + get { return new SI(this, 2 * Math.PI); } } /// <summary> @@ -336,7 +535,7 @@ namespace TUGraz.VectoCore.Utils [DebuggerHidden] public SI Hour { - get { return new SI(this, 3600.0, "h", "s"); } + get { return new SI(this, 3600.0, Unit.h, Unit.s); } } /// <summary> @@ -345,7 +544,7 @@ namespace TUGraz.VectoCore.Utils [DebuggerHidden] public SI Minute { - get { return new SI(this, 60.0, "min", "s"); } + get { return new SI(this, 60.0, Unit.min, Unit.s); } } /// <summary> @@ -354,7 +553,7 @@ namespace TUGraz.VectoCore.Utils [DebuggerHidden] public SI Kilo { - get { return new SI(this, 1000.0, "k"); } + get { return new SI(this, 1000.0, Unit.k); } } /// <summary> @@ -363,7 +562,7 @@ namespace TUGraz.VectoCore.Utils [DebuggerHidden] public SI Centi { - get { return new SI(this, 1.0 / 100.0, "c"); } + get { return new SI(this, 1.0 / 100.0, Unit.c); } } #endregion @@ -372,29 +571,34 @@ namespace TUGraz.VectoCore.Utils public static SI operator +(SI si1, SI si2) { - Contract.Requires(si1.HasEqualUnit(si2)); + if (!si1.HasEqualUnit(si2)) { + throw new VectoException( + string.Format("Operator '+' can only operate on SI Objects with the same unit. Got: {0} + {1}", si1, si2)); + } return new SI(si1.Val + si2.Val, si1.Numerator, si1.Denominator); } public static SI operator -(SI si1, SI si2) { - Contract.Requires(si1.HasEqualUnit(si2)); - + if (!si1.HasEqualUnit(si2)) { + throw new VectoException( + string.Format("Operator '-' can only operate on SI Objects with the same unit. Got: {0} + {1}", si1, si2)); + } return new SI(si1.Val - si2.Val, si1.Numerator, si1.Denominator); } public static SI operator *(SI si1, SI si2) { - var numerator = si1.Numerator.Concat(si2.Numerator).Where(d => d != "rad"); - var denominator = si1.Denominator.Concat(si2.Denominator).Where(d => d != "rad"); + var numerator = si1.Numerator.Concat(si2.Numerator); + var denominator = si1.Denominator.Concat(si2.Denominator); return new SI(si1.Val * si2.Val, numerator, denominator); } public static SI operator /(SI si1, SI si2) { - var numerator = si1.Numerator.Concat(si2.Denominator).Where(d => d != "rad"); - var denominator = si1.Denominator.Concat(si2.Numerator).Where(d => d != "rad"); + var numerator = si1.Numerator.Concat(si2.Denominator); + var denominator = si1.Denominator.Concat(si2.Numerator); return new SI(si1.Val / si2.Val, numerator, denominator); } @@ -403,11 +607,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); @@ -430,25 +649,37 @@ namespace TUGraz.VectoCore.Utils public static bool operator <(SI si1, SI si2) { - Contract.Requires(si1.HasEqualUnit(si2)); + if (!si1.HasEqualUnit(si2)) { + throw new VectoException( + string.Format("Operator '<' can only operate on SI Objects with the same unit. Got: {0} + {1}", si1, si2)); + } return si1.Val < si2.Val; } public static bool operator >(SI si1, SI si2) { - Contract.Requires(si1.HasEqualUnit(si2)); + if (!si1.HasEqualUnit(si2)) { + throw new VectoException( + string.Format("Operator '>' can only operate on SI Objects with the same unit. Got: {0} + {1}", si1, si2)); + } return si1.Val > si2.Val; } public static bool operator <=(SI si1, SI si2) { - Contract.Requires(si1.HasEqualUnit(si2)); + if (!si1.HasEqualUnit(si2)) { + throw new VectoException( + string.Format("Operator '<=' can only operate on SI Objects with the same unit. Got: {0} + {1}", si1, si2)); + } return si1.Val <= si2.Val; } public static bool operator >=(SI si1, SI si2) { - Contract.Requires(si1.HasEqualUnit(si2)); + if (!si1.HasEqualUnit(si2)) { + throw new VectoException( + string.Format("Operator '>=' can only operate on SI Objects with the same unit. Got: {0} + {1}", si1, si2)); + } return si1.Val >= si2.Val; } @@ -528,6 +759,14 @@ namespace TUGraz.VectoCore.Utils return string.Format("{0} [{1}]", Val, GetUnitString()); } + public virtual string ToString(string format) + { + if (string.IsNullOrEmpty(format)) { + format = ""; + } + return string.Format("{0:" + format + "} [{2}]", Val, format, GetUnitString()); + } + #endregion #region Equality members @@ -545,11 +784,6 @@ namespace TUGraz.VectoCore.Utils ToBasicUnits().Numerator.OrderBy(x => x).SequenceEqual(si.ToBasicUnits().Numerator.OrderBy(x => x)); } - protected bool Equals(SI other) - { - return Val.Equals(other.Val) && HasEqualUnit(other); - } - public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { @@ -559,12 +793,13 @@ namespace TUGraz.VectoCore.Utils return true; } var other = obj as SI; - return other != null && Equals(other); + return other != null && Val.Equals(other.Val) && HasEqualUnit(other); } public override int GetHashCode() { unchecked { + // ReSharper disable once NonReadonlyMemberInGetHashCode var hashCode = Val.GetHashCode(); hashCode = (hashCode * 397) ^ (Numerator != null ? Numerator.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (Denominator != null ? Denominator.GetHashCode() : 0); @@ -572,6 +807,26 @@ namespace TUGraz.VectoCore.Utils } } + public int CompareTo(object obj) + { + var si = (obj as SI); + if (si == null) { + return 1; + } + + if (!HasEqualUnit(si)) { + if (si.Numerator.Length + si.Denominator.Length <= Numerator.Length + Denominator.Length) { + return -1; + } + return 1; + } + + if (this > si) { + return 1; + } + return this < si ? -1 : 0; + } + public static bool operator ==(SI left, SI right) { return Equals(left, right); diff --git a/VectoCore/Utils/VectoMath.cs b/VectoCore/Utils/VectoMath.cs index 61f2f91eb4350b906c8ecdbe49164db7898b615d..71a8fb44a3a0e8bc681175a8b94db1da1f51ac87 100644 --- a/VectoCore/Utils/VectoMath.cs +++ b/VectoCore/Utils/VectoMath.cs @@ -1,3 +1,7 @@ +using System; +using Common.Logging.Factory; +using NLog.LayoutRenderers.Wrappers; + namespace TUGraz.VectoCore.Utils { public class VectoMath @@ -6,5 +10,26 @@ namespace TUGraz.VectoCore.Utils { return (xint - x1) * (y2 - y1) / (x2 - x1) + y1; } + + public static SI Abs(SI si) + { + return si.Abs(); + } + + + public static T Abs<T>(T si) where T : SIBase<T>, new() + { + return si.Abs().Cast<T>(); + } + + public static T Min<T>(T c1, T c2) where T : IComparable + { + return c1.CompareTo(c2) <= 0 ? c1 : c2; + } + + public static T Max<T>(T c1, T c2) where T : IComparable + { + return c1.CompareTo(c2) >= 0 ? c1 : c2; + } } } \ No newline at end of file diff --git a/VectoCoreTest/Models/Simulation/DrivingCycleTests.cs b/VectoCoreTest/Models/Simulation/DrivingCycleTests.cs index fed1acc518e77191ac550ada18debb9d8436c498..35d4f7f444d6702a42e9ce00314e7fd64c073b71 100644 --- a/VectoCoreTest/Models/Simulation/DrivingCycleTests.cs +++ b/VectoCoreTest/Models/Simulation/DrivingCycleTests.cs @@ -10,91 +10,91 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Tests.Models.Simulation { - [TestClass] - public class DrivingCycleTests - { - [TestMethod] - public void TestEngineOnly() - { - var container = new VehicleContainer(); + [TestClass] + public class DrivingCycleTests + { + [TestMethod] + public void TestEngineOnly() + { + var container = new VehicleContainer(); - var cycleData = DrivingCycleData.ReadFromFileEngineOnly(@"TestData\Cycles\Coach Engine Only.vdri"); - var cycle = new EngineOnlyDrivingCycle(container, cycleData); + var cycleData = DrivingCycleData.ReadFromFileEngineOnly(@"TestData\Cycles\Coach Engine Only.vdri"); + var cycle = new EngineOnlyDrivingCycle(container, cycleData); - var outPort = new MockTnOutPort(); - var inPort = cycle.InShaft(); - var cycleOut = cycle.OutPort(); + var outPort = new MockTnOutPort(); + var inPort = cycle.InShaft(); + var cycleOut = cycle.OutPort(); - inPort.Connect(outPort); + inPort.Connect(outPort); - var absTime = new TimeSpan(); - var dt = TimeSpan.FromSeconds(1); + var absTime = new TimeSpan(); + var dt = TimeSpan.FromSeconds(1); - var response = cycleOut.Request(absTime, dt); - Assert.IsInstanceOfType(response, typeof (ResponseSuccess)); + var response = cycleOut.Request(absTime, dt); + Assert.IsInstanceOfType(response, typeof (ResponseSuccess)); - var dataWriter = new TestModalDataWriter(); - container.CommitSimulationStep(dataWriter); + var dataWriter = new TestModalDataWriter(); + container.CommitSimulationStep(dataWriter); - Assert.AreEqual(absTime, outPort.AbsTime); - Assert.AreEqual(dt, outPort.Dt); - Assert.AreEqual(600.0.RPMtoRad(), outPort.AngularFrequency); - Assert.AreEqual(0.SI<NewtonMeter>(), outPort.Torque); - } + Assert.AreEqual(absTime, outPort.AbsTime); + Assert.AreEqual(dt, outPort.Dt); + Assert.AreEqual(600.0.RPMtoRad(), outPort.AngularVelocity); + Assert.AreEqual(0.SI<NewtonMeter>(), outPort.Torque); + } - [TestMethod] - public void Test_TimeBased_FirstCycle() - { - var container = new VehicleContainer(); + [TestMethod] + public void Test_TimeBased_FirstCycle() + { + var container = new VehicleContainer(); - var cycleData = DrivingCycleData.ReadFromFileTimeBased(@"TestData\Cycles\Coach time based.vdri"); - var cycle = new TimeBasedDrivingCycle(container, cycleData); + var cycleData = DrivingCycleData.ReadFromFileTimeBased(@"TestData\Cycles\Coach time based.vdri"); + var cycle = new TimeBasedDrivingCycle(container, cycleData); - var outPort = new MockDriverDemandOutPort(); + var outPort = new MockDriverDemandOutPort(); - var inPort = cycle.InPort(); - var cycleOut = cycle.OutPort(); + var inPort = cycle.InPort(); + var cycleOut = cycle.OutPort(); - inPort.Connect(outPort); + inPort.Connect(outPort); - var absTime = new TimeSpan(); - var dt = TimeSpan.FromSeconds(1); + var absTime = new TimeSpan(); + var dt = TimeSpan.FromSeconds(1); - var response = cycleOut.Request(absTime, dt); - Assert.IsInstanceOfType(response, typeof (ResponseSuccess)); + var response = cycleOut.Request(absTime, dt); + Assert.IsInstanceOfType(response, typeof (ResponseSuccess)); - 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(absTime, outPort.AbsTime); + Assert.AreEqual(dt, outPort.Dt); + Assert.AreEqual(0.0.SI<MeterPerSecond>(), outPort.Velocity); + Assert.AreEqual((-0.020237973).SI().GradientPercent.Cast<Radian>(), outPort.Gradient); + } - [TestMethod] - public void Test_TimeBased_TimeFieldMissing() - { - var container = new VehicleContainer(); + [TestMethod] + public void Test_TimeBased_TimeFieldMissing() + { + var container = new VehicleContainer(); - var cycleData = DrivingCycleData.ReadFromFileTimeBased(@"TestData\Cycles\Cycle time field missing.vdri"); - var cycle = new TimeBasedDrivingCycle(container, cycleData); + var cycleData = DrivingCycleData.ReadFromFileTimeBased(@"TestData\Cycles\Cycle time field missing.vdri"); + var cycle = new TimeBasedDrivingCycle(container, cycleData); - var outPort = new MockDriverDemandOutPort(); + var outPort = new MockDriverDemandOutPort(); - var inPort = cycle.InPort(); - var cycleOut = cycle.OutPort(); + var inPort = cycle.InPort(); + var cycleOut = cycle.OutPort(); - inPort.Connect(outPort); + inPort.Connect(outPort); - var dataWriter = new TestModalDataWriter(); - var absTime = new TimeSpan(); - var dt = TimeSpan.FromSeconds(1); + var dataWriter = new TestModalDataWriter(); + var absTime = new TimeSpan(); + var dt = TimeSpan.FromSeconds(1); - while (cycleOut.Request(absTime, dt) is ResponseSuccess) { - Assert.AreEqual(absTime, outPort.AbsTime); - Assert.AreEqual(dt, outPort.Dt); - container.CommitSimulationStep(dataWriter); + while (cycleOut.Request(absTime, dt) is ResponseSuccess) { + Assert.AreEqual(absTime, outPort.AbsTime); + Assert.AreEqual(dt, outPort.Dt); + container.CommitSimulationStep(dataWriter); - absTime += dt; - } - } - } + absTime += dt; + } + } + } } \ No newline at end of file diff --git a/VectoCoreTest/Models/SimulationComponent/CombustionEngineTest.cs b/VectoCoreTest/Models/SimulationComponent/CombustionEngineTest.cs index 9ba69f9cdf8ea0d23c3826618a656bc0c731252b..dd0e4772335baa21345738d91082a6de59a9d8d9 100644 --- a/VectoCoreTest/Models/SimulationComponent/CombustionEngineTest.cs +++ b/VectoCoreTest/Models/SimulationComponent/CombustionEngineTest.cs @@ -65,7 +65,7 @@ namespace TUGraz.VectoCore.Tests.Models.SimulationComponent var absTime = new TimeSpan(seconds: 0, minutes: 0, hours: 0); var dt = new TimeSpan(seconds: 1, minutes: 0, hours: 0); - var torque = new NewtonMeter(); + var torque = 0.SI<NewtonMeter>(); var engineSpeed = 600.0.RPMtoRad(); var dataWriter = new TestModalDataWriter(); diff --git a/VectoCoreTest/Models/SimulationComponent/MockPorts.cs b/VectoCoreTest/Models/SimulationComponent/MockPorts.cs index 0ede14df24ca885ccbfddd3a425ac0eeba14eb1c..d0f5b37d5036f6495ec5591da8cf7b89e8a1c5a6 100644 --- a/VectoCoreTest/Models/SimulationComponent/MockPorts.cs +++ b/VectoCoreTest/Models/SimulationComponent/MockPorts.cs @@ -6,41 +6,41 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Tests.Models.SimulationComponent { - public class MockTnOutPort : ITnOutPort - { - public TimeSpan AbsTime { get; set; } - public TimeSpan Dt { get; set; } - public NewtonMeter Torque { get; set; } - public RadianPerSecond AngularFrequency { get; set; } + public class MockTnOutPort : ITnOutPort + { + public TimeSpan AbsTime { get; set; } + public TimeSpan Dt { get; set; } + public NewtonMeter Torque { get; set; } + public PerSecond AngularVelocity { get; set; } - public IResponse Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, RadianPerSecond angularFrequency) - { - AbsTime = absTime; - Dt = dt; - Torque = torque; - AngularFrequency = angularFrequency; - LogManager.GetLogger(GetType()).DebugFormat("Request: absTime: {0}, dt: {1}, torque: {3}, engineSpeed: {4}", - absTime, dt, torque, angularFrequency); - return new ResponseSuccess(); - } - } + public IResponse Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, PerSecond angularVelocity) + { + AbsTime = absTime; + Dt = dt; + Torque = torque; + AngularVelocity = angularVelocity; + LogManager.GetLogger(GetType()).DebugFormat("Request: absTime: {0}, dt: {1}, torque: {3}, engineSpeed: {4}", + absTime, dt, torque, angularVelocity); + return new ResponseSuccess(); + } + } - public class MockDriverDemandOutPort : IDriverDemandOutPort - { - public TimeSpan AbsTime { get; set; } - public TimeSpan Dt { get; set; } - public MeterPerSecond Velocity { get; set; } - public Radian Gradient { get; set; } + public class MockDriverDemandOutPort : IDriverDemandOutPort + { + public TimeSpan AbsTime { get; set; } + public TimeSpan Dt { get; set; } + public MeterPerSecond Velocity { get; set; } + public Radian Gradient { get; set; } - public IResponse Request(TimeSpan absTime, TimeSpan dt, MeterPerSecond velocity, Radian gradient) - { - AbsTime = absTime; - Dt = dt; - Velocity = velocity; - Gradient = gradient; - LogManager.GetLogger(GetType()).DebugFormat("Request: absTime: {0}, dt: {1}, velocity: {3}, gradient: {4}", - absTime, dt, velocity, gradient); - return new ResponseSuccess(); - } - } + public IResponse Request(TimeSpan absTime, TimeSpan dt, MeterPerSecond velocity, Radian gradient) + { + AbsTime = absTime; + Dt = dt; + Velocity = velocity; + Gradient = gradient; + LogManager.GetLogger(GetType()).DebugFormat("Request: absTime: {0}, dt: {1}, velocity: {3}, gradient: {4}", + absTime, dt, velocity, gradient); + return new ResponseSuccess(); + } + } } \ No newline at end of file diff --git a/VectoCoreTest/Models/SimulationComponentData/FuelConsumptionMapTest.cs b/VectoCoreTest/Models/SimulationComponentData/FuelConsumptionMapTest.cs index 5061ea58ae918e3592f0fe39222cae9914ede78c..965f39a46cf39a3dbd150c770b95f39bc0af5f2d 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.ConvertTo().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/Models/SimulationComponentData/FullLoadCurveTest.cs b/VectoCoreTest/Models/SimulationComponentData/FullLoadCurveTest.cs index 07f97e2aba1fc8601fb1f8f982d636d942f4832e..6de77a4a1bcf63188d7d469e5e159dad801fc411 100644 --- a/VectoCoreTest/Models/SimulationComponentData/FullLoadCurveTest.cs +++ b/VectoCoreTest/Models/SimulationComponentData/FullLoadCurveTest.cs @@ -101,7 +101,7 @@ namespace TUGraz.VectoCore.Tests.Models.SimulationComponentData public void Test_FileRead_NoHeader() { var curve = FullLoadCurve.ReadFromFile(@"TestData\Components\FullLoadCurve no header.vfld"); - var result = curve.FullLoadStationaryTorque(1.SI<RadianPerSecond>()); + var result = curve.FullLoadStationaryTorque(1.SI<PerSecond>()); Assert.AreNotEqual((double) result, 0.0); } diff --git a/VectoCoreTest/Utils/DoubleExtensionMethodTest.cs b/VectoCoreTest/Utils/DoubleExtensionMethodTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..21d802107dedb390e230f7ed1e36d8b7e2d2525e --- /dev/null +++ b/VectoCoreTest/Utils/DoubleExtensionMethodTest.cs @@ -0,0 +1,29 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TUGraz.VectoCore.Utils; + +namespace TUGraz.VectoCore.Tests.Utils +{ + [TestClass] + public class DoubleExtensionMethodTest + { + [TestMethod] + public void DoubleExtensions_SI_Test() + { + var val = 600.0.RPMtoRad(); + Assert.AreEqual(600 / 60 * 2 * Math.PI, val.Double()); + + Assert.IsTrue(0.0.SI<PerSecond>().HasEqualUnit(val)); + + var val2 = 1200.0.SI().Rounds.Per.Minute.ConvertTo().Radian.Per.Second.Cast<PerSecond>(); + val = val * 2; + Assert.AreEqual(val, val2); + + val2 = val2 / 2; + val = val / 2; + Assert.AreEqual(val, val2); + Assert.AreEqual(600.SI().Rounds.Per.Minute.Cast<PerSecond>(), val2); + Assert.AreEqual(600.SI().Rounds.Per.Minute.Cast<PerSecond>().Double(), val2.Double()); + } + } +} \ No newline at end of file diff --git a/VectoCoreTest/Utils/SITest.cs b/VectoCoreTest/Utils/SITest.cs index 05e6394e4fb702ef27e9da163f3c07853c9f9a45..94325d67b9e5c293821c32d2461d040534d13769 100644 --- a/VectoCoreTest/Utils/SITest.cs +++ b/VectoCoreTest/Utils/SITest.cs @@ -1,5 +1,7 @@ using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.VisualStudio.TestTools.UnitTesting; +using TUGraz.VectoCore.Exceptions; using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Tests.Utils @@ -7,21 +9,98 @@ namespace TUGraz.VectoCore.Tests.Utils [TestClass] public class SITest { - public static void AssertException<T>(Action func, string message) where T : Exception + /// <summary> + /// Assert an expected Exception. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="func"></param> + /// <param name="message"></param> + public static void AssertException<T>(Action func, string message = null) where T : Exception { try { func(); - Assert.Fail(); + Assert.Fail("Expected Exception {0}, but no exception occured.", typeof (T)); } catch (T ex) { - Assert.AreEqual(message, ex.Message); + if (message != null) { + Assert.AreEqual(message, ex.Message); + } } } + public void SI_TypicalUsageTest() + { + //mult + var angularVelocity = 600.RPMtoRad(); + var torque = 1500.SI<NewtonMeter>(); + var power = angularVelocity * torque; + Assert.IsInstanceOfType(power, typeof (Watt)); + Assert.AreEqual(600 * 1500, power.Double()); + + var siStandardMult = power * torque; + Assert.IsInstanceOfType(siStandardMult, typeof (SI)); + Assert.AreEqual(600 * 1500 * 1500, siStandardMult.Double()); + Assert.IsTrue(siStandardMult.HasEqualUnit(new SI().Watt.Newton.Meter)); + + //div + var torque2 = power / angularVelocity; + Assert.IsInstanceOfType(torque2, typeof (NewtonMeter)); + Assert.AreEqual(1500, torque2.Double()); + + var siStandardDiv = power / power; + Assert.IsInstanceOfType(siStandardMult, typeof (SI)); + Assert.IsTrue(siStandardDiv.HasEqualUnit(new SI())); + Assert.AreEqual(600 * 1500 * 1500, siStandardMult.Double()); + + + //add + var angularVelocity2 = 400.SI<RoundsPerMinute>().Cast<PerSecond>(); + var angVeloSum = angularVelocity + angularVelocity2; + Assert.IsInstanceOfType(angVeloSum, typeof (PerSecond)); + Assert.AreEqual(400 + 600, angVeloSum.Double()); + AssertException<VectoException>(() => { var x = 500.SI().Watt + 300.SI().Newton; }); + + //subtract + var angVeloDiff = angularVelocity - angularVelocity2; + Assert.IsInstanceOfType(angVeloDiff, typeof (PerSecond)); + Assert.AreEqual(600 - 400, angVeloDiff.Double()); + + //general si unit + var generalSIUnit = 60000.SI().Gramm.Per.Kilo.Watt.Hour.ConvertTo().Kilo.Gramm.Per.Watt.Second; + Assert.IsInstanceOfType(generalSIUnit, typeof (SI)); + Assert.AreEqual(1, generalSIUnit.Double()); + + + //type conversion + var engineSpeed = 600; + var angularVelocity3 = engineSpeed.RPMtoRad(); + + // convert between units measures + var angularVelocity4 = engineSpeed.SI().Rounds.Per.Minute.ConvertTo().Radian.Per.Second; + + // cast SI to specialized unit classes. + var angularVelocity5 = angularVelocity2.Cast<PerSecond>(); + Assert.AreEqual(angularVelocity3, angularVelocity5); + Assert.AreEqual(angularVelocity3.Double(), angularVelocity4.Double()); + Assert.IsInstanceOfType(angularVelocity3, typeof (PerSecond)); + Assert.IsInstanceOfType(angularVelocity5, typeof (PerSecond)); + Assert.IsInstanceOfType(angularVelocity4, typeof (SI)); + + + // ConvertTo only allows conversion if the units are correct. + AssertException<VectoException>(() => { var x = 40.SI<Newton>().ConvertTo().Watt; }); + var res1 = 40.SI<Newton>().ConvertTo().Newton; + + // Cast only allows the cast if the units are correct. + AssertException<VectoException>(() => { var x = 40.SI().Newton.Cast<Watt>(); }); + var res2 = 40.SI().Newton.Cast<Newton>(); + } + + [TestMethod] - public void TestSI() + public void SI_Test() { var si = new SI(); - Assert.AreEqual(0.0, (double) si); + Assert.AreEqual(0.0, si.Double()); Assert.AreEqual("0 [-]", si.ToString()); Assert.IsTrue(si.HasEqualUnit(new SI())); @@ -29,7 +108,7 @@ namespace TUGraz.VectoCore.Tests.Utils Assert.AreEqual("5 [W]", si2.ToString()); var si3 = 2.SI().Radian.Per.Second; - Assert.AreEqual("2 [rad/s]", si3.ToString()); + Assert.AreEqual("2 [1/s]", si3.ToString()); var si4 = si2 * si3; Assert.AreEqual("10 [W/s]", si4.ToString()); @@ -38,15 +117,15 @@ namespace TUGraz.VectoCore.Tests.Utils var kg = 5.0.SI().Kilo.Gramm; - Assert.AreEqual(5.0, (double) kg); + Assert.AreEqual(5.0, kg.Double()); Assert.AreEqual("5 [kg]", kg.ToString()); - kg = kg.To().Kilo.Gramm.Value(); - Assert.AreEqual(5.0, (double) kg); + kg = kg.ConvertTo().Kilo.Gramm.Value(); + Assert.AreEqual(5.0, kg.Double()); Assert.AreEqual("5 [kg]", kg.ToString()); - kg = kg.To().Gramm.Value(); - Assert.AreEqual(5000, (double) kg); + kg = kg.ConvertTo().Gramm.Value(); + Assert.AreEqual(5000, kg.Double()); Assert.AreEqual("5000 [g]", kg.ToString()); var x = 5.SI(); @@ -63,6 +142,40 @@ namespace TUGraz.VectoCore.Tests.Utils var y = 2.SI(); Assert.AreEqual((2 * 5).SI(), y * x); + + var percent = 10.SI<Radian>().ConvertTo().GradientPercent; + Assert.AreEqual(67.975.ToString("F3") + " [Percent]", percent.ToString("F3")); + Assert.AreEqual(67.975, percent.Double(), 0.001); + } + + [TestMethod] + [SuppressMessage("ReSharper", "SuggestVarOrType_SimpleTypes")] + public void SI_Test_Addition_Subtraction() + { + var v1 = 600.SI<NewtonMeter>(); + var v2 = 455.SI<NewtonMeter>(); + NewtonMeter v3 = v1 + v2; + + NewtonMeter v4 = v1 - v2; + + var v5 = v1 * v2; + Assert.IsTrue(v5.HasEqualUnit(0.SI().Square.Newton.Meter)); + Assert.AreEqual(v1.Double() * v2.Double(), v5.Double()); + + var v6 = v1 / v2; + Assert.IsTrue(v6.HasEqualUnit(0.SI())); + Assert.AreEqual(v1.Double() / v2.Double(), v6.Double()); + + var t = 10.SI<NewtonMeter>(); + var angVelo = 5.SI<PerSecond>(); + + Watt w = t * angVelo; + Watt w1 = angVelo * t; + + NewtonMeter t1 = w / angVelo; + + PerSecond angVelo1 = w / t; + Second sec = t / w; } } } \ No newline at end of file diff --git a/VectoCoreTest/Utils/VectoMathTest.cs b/VectoCoreTest/Utils/VectoMathTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..2c163c8e67055adf5aba109fb75ee29ec58e5c41 --- /dev/null +++ b/VectoCoreTest/Utils/VectoMathTest.cs @@ -0,0 +1,37 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TUGraz.VectoCore.Utils; + +namespace TUGraz.VectoCore.Tests.Utils +{ + [TestClass] + public class VectoMathTest + { + [TestMethod] + public void VectoMath_Min() + { + var smaller = 0.SI(); + var bigger = 5.SI(); + var negative = -10.SI(); + var positive = 10.SI(); + Assert.AreEqual(smaller, VectoMath.Min(smaller, bigger)); + + Assert.AreEqual(bigger, VectoMath.Max(smaller, bigger)); + + Assert.AreEqual(positive, VectoMath.Abs(negative)); + Assert.AreEqual(positive, VectoMath.Abs(positive)); + + + var smallerWatt = 0.SI<Watt>(); + var biggerWatt = 5.SI<Watt>(); + var negativeWatt = -10.SI<Watt>(); + var positiveWatt = 10.SI<Watt>(); + Assert.AreEqual(smallerWatt, VectoMath.Min(smallerWatt, biggerWatt)); + + Assert.AreEqual(biggerWatt, VectoMath.Max(smallerWatt, biggerWatt)); + + + Assert.AreEqual(positiveWatt, VectoMath.Abs(negativeWatt)); + Assert.AreEqual(positiveWatt, VectoMath.Abs(positiveWatt)); + } + } +} \ No newline at end of file diff --git a/VectoCoreTest/VectoCoreTest.csproj b/VectoCoreTest/VectoCoreTest.csproj index 65929aa594cf78dc57bff7d7f1fade892684b5ca..35c2226a409022a7222b092583735f95ae37ed24 100644 --- a/VectoCoreTest/VectoCoreTest.csproj +++ b/VectoCoreTest/VectoCoreTest.csproj @@ -88,6 +88,8 @@ <Compile Include="Utils\SITest.cs" /> <Compile Include="Utils\DelauneyMapTest.cs" /> <Compile Include="Utils\TestModalDataWriter.cs" /> + <Compile Include="Utils\DoubleExtensionMethodTest.cs" /> + <Compile Include="Utils\VectoMathTest.cs" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\VectoCore\VectoCore.csproj">