diff --git a/VECTO.sln.DotSettings b/VECTO.sln.DotSettings index 79c8369e0b13181e7fdbd97067486f05854bd6ca..d837fa6d7bf34f7ba866b97aef62e747ab517d78 100644 --- a/VECTO.sln.DotSettings +++ b/VECTO.sln.DotSettings @@ -16,6 +16,7 @@ <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> diff --git a/VectoCore/Models/SimulationComponent/Data/CombustionEngineData.cs b/VectoCore/Models/SimulationComponent/Data/CombustionEngineData.cs index 63ebdcee2af3a5a16ef065734fbca55f0c2dff7e..0bbb7cbc80192844bf3ab3fc955710e75f7305f5 100644 --- a/VectoCore/Models/SimulationComponent/Data/CombustionEngineData.cs +++ b/VectoCore/Models/SimulationComponent/Data/CombustionEngineData.cs @@ -77,7 +77,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data /// </summary> public RadianPerSecond IdleSpeed { - get { return _data.Body.IdleSpeed.SI().Rounds.Per.Minute.To<RadianPerSecond>(); } + get { return _data.Body.IdleSpeed.RPMtoRad(); } protected set { _data.Body.IdleSpeed = (double) value.To().Rounds.Per.Minute; } } @@ -115,7 +115,8 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data { get { - return _data.Body.WHTCMotorway.SI().Gramm.Per.Kilo.Watt.Hour.To().Kilo.Gramm.Per.Watt.Second.Value(); + return + _data.Body.WHTCMotorway.SI().Gramm.Per.Kilo.Watt.Hour.To().Kilo.Gramm.Per.Watt.Second.Value(); } protected set { _data.Body.WHTCMotorway = (double) value.To().Gramm.Per.Kilo.Watt.Hour; } } diff --git a/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs b/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs index 46396270db95553cba9756b17a6441335985390e..60cd5cf6b4b99016635982ad9d5f78e23920fbe3 100644 --- a/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs +++ b/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs @@ -8,403 +8,421 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Models.SimulationComponent.Data { - public class DrivingCycleData : SimulationComponentData - { - public enum CycleType - { - EngineOnly, - TimeBased, - DistanceBased - } - - public List<DrivingCycleEntry> Entries { get; set; } - - public static DrivingCycleData ReadFromFileEngineOnly(string fileName) - { - return ReadFromFile(fileName, CycleType.EngineOnly); - } - - public static DrivingCycleData ReadFromFileDistanceBased(string fileName) - { - return ReadFromFile(fileName, CycleType.DistanceBased); - } - - public static DrivingCycleData ReadFromFileTimeBased(string fileName) - { - return ReadFromFile(fileName, CycleType.TimeBased); - } - - public static DrivingCycleData ReadFromFile(string fileName, CycleType type) - { - var log = LogManager.GetLogger<DrivingCycleData>(); - - var parser = CreateDataParser(type); - var data = VectoCSVFile.Read(fileName); - var entries = parser.Parse(data).ToList(); - - log.Info(string.Format("Data loaded. Number of Entries: {0}", entries.Count)); - - var cycle = new DrivingCycleData { Entries = entries }; - return cycle; - } - - private static IDataParser CreateDataParser(CycleType type) - { - switch (type) { - case CycleType.EngineOnly: - return new EngineOnlyDataParser(); - case CycleType.TimeBased: - return new TimeBasedDataParser(); - case CycleType.DistanceBased: - return new DistanceBasedDataParser(); - default: - throw new ArgumentOutOfRangeException("type"); - } - } - - private static class Fields - { - /// <summary> - /// [m] Travelled distance used for distance-based cycles. If t is also defined this column will be ignored. - /// </summary> - public const string Distance = "s"; - - /// <summary> - /// [s] Used for time-based cycles. If neither this nor the distance s is defined the data will be interpreted as 1Hz. - /// </summary> - public const string Time = "t"; - - /// <summary> - /// [km/h] Required except for Engine Only Mode calculations. - /// </summary> - public const string VehicleSpeed = "v"; - - /// <summary> - /// [%] Optional. - /// </summary> - public const string RoadGradient = "grad"; - - /// <summary> - /// [s] Required for distance-based cycles. Not used in time based cycles. stop defines the time the vehicle spends in - /// stop phases. - /// </summary> - public const string StoppingTime = "stop"; - - /// <summary> - /// [kW] "Aux_xxx" Supply Power input for each auxiliary defined in the .vecto file , where xxx matches the ID of the - /// corresponding Auxiliary. ID's are not case sensitive and must not contain space or special characters. - /// </summary> - // todo: implement additional aux as dictionary! - public const string AuxiliarySupplyPower = "Aux_"; - - /// <summary> - /// [rpm] If n is defined VECTO uses that instead of the calculated engine speed value. - /// </summary> - public const string EngineSpeed = "n"; - - /// <summary> - /// [-] Gear input. Overwrites the gear shift model. - /// </summary> - public const string Gear = "gear"; - - /// <summary> - /// [kW] This power input will be directly added to the engine power in addition to possible other auxiliaries. Also - /// used in Engine Only Mode. - /// </summary> - public const string AdditionalAuxPowerDemand = "Padd"; - - /// <summary> - /// [km/h] Only required if Cross Wind Correction is set to Vair and Beta Input. - /// </summary> - public const string AirSpeedRelativeToVehicle = "vair_res"; - - /// <summary> - /// [°] Only required if Cross Wind Correction is set to Vair and Beta Input. - /// </summary> - public const string WindYawAngle = "vair_beta"; - - /// <summary> - /// [kW] Effective engine power at clutch. Only required in Engine Only Mode. Alternatively torque Me can be defined. - /// Use DRAG to define motoring operation. - /// </summary> - public const string EnginePower = "Pe"; - - /// <summary> - /// [Nm] Effective engine torque at clutch. Only required in Engine Only Mode. Alternatively power Pe can be defined. - /// Use DRAG to define motoring operation. - /// </summary> - public const string EngineTorque = "Me"; - } - - public class DrivingCycleEntry - { - /// <summary> - /// [m] Travelled distance used for distance-based cycles. If "t" - /// is also defined this column will be ignored. - /// </summary> - public double Distance { get; set; } - - /// <summary> - /// [s] Used for time-based cycles. If neither this nor the distance - /// "s" is defined the data will be interpreted as 1Hz. - /// </summary> - public double Time { get; set; } - - /// <summary> - /// [m/s] Required except for Engine Only Mode calculations. - /// </summary> - public MeterPerSecond VehicleSpeed { get; set; } - - /// <summary> - /// [%] Optional. - /// </summary> - public double RoadGradient { get; set; } - - /// <summary> - /// [s] Required for distance-based cycles. Not used in time based - /// cycles. "stop" defines the time the vehicle spends in stop phases. - /// </summary> - public double StoppingTime { get; set; } - - /// <summary> - /// [W] Supply Power input for each auxiliary defined in the - /// .vecto file where xxx matches the ID of the corresponding - /// Auxiliary. ID's are not case sensitive and must not contain - /// space or special characters. - /// </summary> - public Dictionary<string, Watt> AuxiliarySupplyPower { get; set; } - - /// <summary> - /// [rad/s] If "n" is defined VECTO uses that instead of the - /// calculated engine speed value. - /// </summary> - public RadianPerSecond EngineSpeed { get; set; } - - /// <summary> - /// [-] Gear input. Overwrites the gear shift model. - /// </summary> - public double Gear { get; set; } - - /// <summary> - /// [W] This power input will be directly added to the engine - /// power in addition to possible other auxiliaries. Also used in - /// Engine Only Mode. - /// </summary> - public Watt AdditionalAuxPowerDemand { get; set; } - - /// <summary> - /// [m/s] Only required if Cross Wind Correction is set to Vair and Beta Input. - /// </summary> - public MeterPerSecond AirSpeedRelativeToVehicle { get; set; } - - /// <summary> - /// [°] Only required if Cross Wind Correction is set to Vair and Beta Input. - /// </summary> - public double WindYawAngle { get; set; } - - /// <summary> - /// [Nm] Effective engine torque at clutch. Only required in - /// Engine Only Mode. Alternatively power "Pe" can be defined. - /// Use "DRAG" to define motoring operation. - /// </summary> - public NewtonMeter EngineTorque { get; set; } - - public bool Drag { get; set; } - } - - #region DataParser - - private interface IDataParser - { - IEnumerable<DrivingCycleEntry> Parse(DataTable table); - } - - /// <summary> - /// Reader for Auxiliary Supply Power. - /// </summary> - private static class AuxSupplyPowerReader - { - /// <summary> - /// [W]. Reads Auxiliary Supply Power (defined by Fields.AuxiliarySupplyPower-Prefix). - /// </summary> - public static Dictionary<string, Watt> Read(DataRow row) - { - return row.Table.Columns.Cast<DataColumn>(). - Where(col => col.ColumnName.StartsWith(Fields.AuxiliarySupplyPower)). - ToDictionary(col => col.ColumnName.Substring(Fields.AuxiliarySupplyPower.Length - 1), - col => row.ParseDouble(col).SI().Kilo.Watt.To<Watt>()); - } - } - - private class DistanceBasedDataParser : IDataParser - { - public IEnumerable<DrivingCycleEntry> Parse(DataTable table) - { - ValidateHeader(table.Columns.Cast<DataColumn>().Select(col => col.ColumnName).ToArray()); - - return table.Rows.Cast<DataRow>().Select(row => new DrivingCycleEntry { - Distance = row.ParseDouble(Fields.Distance), - VehicleSpeed = row.ParseDouble(Fields.VehicleSpeed).SI().Kilo.Meter.Per.Hour.To<MeterPerSecond>(), - RoadGradient = row.ParseDoubleOrGetDefault(Fields.RoadGradient), - AdditionalAuxPowerDemand = row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.To<Watt>(), - EngineSpeed = row.ParseDoubleOrGetDefault(Fields.EngineSpeed).SI().Rounds.Per.Minute.To<RadianPerSecond>(), - Gear = row.ParseDoubleOrGetDefault(Fields.Gear), - AirSpeedRelativeToVehicle = - row.ParseDoubleOrGetDefault(Fields.AirSpeedRelativeToVehicle).SI().Kilo.Meter.Per.Hour.To<MeterPerSecond>(), - WindYawAngle = row.ParseDoubleOrGetDefault(Fields.WindYawAngle), - AuxiliarySupplyPower = AuxSupplyPowerReader.Read(row) - }); - } - - private static void ValidateHeader(string[] header) - { - var allowedCols = new[] { - Fields.Distance, - Fields.VehicleSpeed, - Fields.RoadGradient, - Fields.StoppingTime, - Fields.EngineSpeed, - Fields.Gear, - Fields.AdditionalAuxPowerDemand, - Fields.AirSpeedRelativeToVehicle, - Fields.WindYawAngle - }; - - foreach (var col in header.Where(col => !(allowedCols.Contains(col) || col.StartsWith(Fields.AuxiliarySupplyPower))) - ) { - throw new VectoException(string.Format("Column '{0}' is not allowed.", col)); - } - - if (!header.Contains(Fields.VehicleSpeed)) { - throw new VectoException(string.Format("Column '{0}' is missing.", Fields.VehicleSpeed)); - } - - if (!header.Contains(Fields.Distance)) { - throw new VectoException(string.Format("Column '{0}' is missing.", Fields.Distance)); - } - - if (header.Contains(Fields.AirSpeedRelativeToVehicle) ^ header.Contains(Fields.WindYawAngle)) { - throw new VectoException(string.Format("Both Columns '{0}' and '{1}' must be defined, or none of them.", - Fields.AirSpeedRelativeToVehicle, Fields.WindYawAngle)); - } - } - } - - private class TimeBasedDataParser : IDataParser - { - public IEnumerable<DrivingCycleEntry> Parse(DataTable table) - { - ValidateHeader(table.Columns.Cast<DataColumn>().Select(col => col.ColumnName).ToArray()); - - var entries = table.Rows.Cast<DataRow>().Select((row, index) => new DrivingCycleEntry { - Time = row.ParseDoubleOrGetDefault(Fields.Time, index), - VehicleSpeed = row.ParseDouble(Fields.VehicleSpeed).SI().Kilo.Meter.Per.Hour.To<MeterPerSecond>(), - RoadGradient = row.ParseDoubleOrGetDefault(Fields.RoadGradient), - AdditionalAuxPowerDemand = row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.To<Watt>(), - Gear = row.ParseDoubleOrGetDefault(Fields.Gear), - EngineSpeed = row.ParseDoubleOrGetDefault(Fields.EngineSpeed).SI().Rounds.Per.Minute.To<RadianPerSecond>(), - AirSpeedRelativeToVehicle = - row.ParseDoubleOrGetDefault(Fields.AirSpeedRelativeToVehicle).SI().Kilo.Meter.Per.Hour.To<MeterPerSecond>(), - WindYawAngle = row.ParseDoubleOrGetDefault(Fields.WindYawAngle), - AuxiliarySupplyPower = AuxSupplyPowerReader.Read(row) - }).ToArray(); - - return entries; - } - - private static void ValidateHeader(string[] header) - { - var allowedCols = new[] { - Fields.Time, - Fields.VehicleSpeed, - Fields.RoadGradient, - Fields.EngineSpeed, - Fields.Gear, - Fields.AdditionalAuxPowerDemand, - Fields.AirSpeedRelativeToVehicle, - Fields.WindYawAngle - }; - - foreach (var col in header.Where(col => !(allowedCols.Contains(col) || col.StartsWith(Fields.AuxiliarySupplyPower))) - ) { - throw new VectoException(string.Format("Column '{0}' is not allowed.", col)); - } - - if (!header.Contains(Fields.VehicleSpeed)) { - throw new VectoException(string.Format("Column '{0}' is missing.", Fields.VehicleSpeed)); - } - - if (header.Contains(Fields.AirSpeedRelativeToVehicle) ^ header.Contains(Fields.WindYawAngle)) { - throw new VectoException(string.Format("Both Columns '{0}' and '{1}' must be defined, or none of them.", - Fields.AirSpeedRelativeToVehicle, Fields.WindYawAngle)); - } - } - } - - private class EngineOnlyDataParser : IDataParser - { - public IEnumerable<DrivingCycleEntry> Parse(DataTable table) - { - ValidateHeader(table.Columns.Cast<DataColumn>().Select(col => col.ColumnName).ToArray()); - var absTime = new TimeSpan(0, 0, 0); - - foreach (DataRow row in table.Rows) { - var entry = new DrivingCycleEntry { - EngineSpeed = row.ParseDoubleOrGetDefault(Fields.EngineSpeed).SI().Rounds.Per.Minute.To<RadianPerSecond>(), - AdditionalAuxPowerDemand = row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.To<Watt>(), - AuxiliarySupplyPower = AuxSupplyPowerReader.Read(row) - }; - if (row.Table.Columns.Contains(Fields.EngineTorque)) { - if (row.Field<string>(Fields.EngineTorque).Equals("<DRAG>")) { - entry.Drag = true; - } else { - entry.EngineTorque = row.ParseDouble(Fields.EngineTorque).SI<NewtonMeter>(); - } - } else { - if (row.Field<string>(Fields.EnginePower).Equals("<DRAG>")) { - entry.Drag = true; - } else { - entry.EngineTorque = Formulas.PowerToTorque(row.ParseDouble(Fields.EnginePower).SI().Kilo.Watt.To<Watt>(), - entry.EngineSpeed); - } - } - entry.Time = absTime.TotalSeconds; - absTime += new TimeSpan(0, 0, 1); - - yield return entry; - } - } - - private static void ValidateHeader(string[] header) - { - var allowedCols = new[] { - Fields.EngineTorque, - Fields.EnginePower, - Fields.EngineSpeed, - Fields.AdditionalAuxPowerDemand - }; - - foreach (var col in header.Where(col => !allowedCols.Contains(col))) { - throw new VectoException(string.Format("Column '{0}' is not allowed.", col)); - } - - if (!header.Contains(Fields.EngineSpeed)) { - throw new VectoException(string.Format("Column '{0}' is missing.", Fields.EngineSpeed)); - } - - if (!(header.Contains(Fields.EngineTorque) || header.Contains(Fields.EnginePower))) { - throw new VectoException(string.Format("Columns missing: Either column '{0}' or column '{1}' must be defined.", - Fields.EngineTorque, Fields.EnginePower)); - } - - if (header.Contains(Fields.EngineTorque) && header.Contains(Fields.EnginePower)) { - LogManager.GetLogger<DrivingCycleData>() - .WarnFormat("Found column '{0}' and column '{1}': only column '{0}' will be used.", - Fields.EngineTorque, Fields.EnginePower); - } - } - } - - #endregion - } + public class DrivingCycleData : SimulationComponentData + { + public enum CycleType + { + EngineOnly, + TimeBased, + DistanceBased + } + + public List<DrivingCycleEntry> Entries { get; set; } + + public static DrivingCycleData ReadFromFileEngineOnly(string fileName) + { + return ReadFromFile(fileName, CycleType.EngineOnly); + } + + public static DrivingCycleData ReadFromFileDistanceBased(string fileName) + { + return ReadFromFile(fileName, CycleType.DistanceBased); + } + + public static DrivingCycleData ReadFromFileTimeBased(string fileName) + { + return ReadFromFile(fileName, CycleType.TimeBased); + } + + public static DrivingCycleData ReadFromFile(string fileName, CycleType type) + { + var log = LogManager.GetLogger<DrivingCycleData>(); + + var parser = CreateDataParser(type); + var data = VectoCSVFile.Read(fileName); + var entries = parser.Parse(data).ToList(); + + log.Info(string.Format("Data loaded. Number of Entries: {0}", entries.Count)); + + var cycle = new DrivingCycleData { Entries = entries }; + return cycle; + } + + private static IDataParser CreateDataParser(CycleType type) + { + switch (type) { + case CycleType.EngineOnly: + return new EngineOnlyDataParser(); + case CycleType.TimeBased: + return new TimeBasedDataParser(); + case CycleType.DistanceBased: + return new DistanceBasedDataParser(); + default: + throw new ArgumentOutOfRangeException("type"); + } + } + + private static class Fields + { + /// <summary> + /// [m] Travelled distance used for distance-based cycles. If t is also defined this column will be ignored. + /// </summary> + public const string Distance = "s"; + + /// <summary> + /// [s] Used for time-based cycles. If neither this nor the distance s is defined the data will be interpreted as 1Hz. + /// </summary> + public const string Time = "t"; + + /// <summary> + /// [km/h] Required except for Engine Only Mode calculations. + /// </summary> + public const string VehicleSpeed = "v"; + + /// <summary> + /// [%] Optional. + /// </summary> + public const string RoadGradient = "grad"; + + /// <summary> + /// [s] Required for distance-based cycles. Not used in time based cycles. stop defines the time the vehicle spends in + /// stop phases. + /// </summary> + public const string StoppingTime = "stop"; + + /// <summary> + /// [kW] "Aux_xxx" Supply Power input for each auxiliary defined in the .vecto file , where xxx matches the ID of the + /// corresponding Auxiliary. ID's are not case sensitive and must not contain space or special characters. + /// </summary> + // todo: implement additional aux as dictionary! + public const string AuxiliarySupplyPower = "Aux_"; + + /// <summary> + /// [rpm] If n is defined VECTO uses that instead of the calculated engine speed value. + /// </summary> + public const string EngineSpeed = "n"; + + /// <summary> + /// [-] Gear input. Overwrites the gear shift model. + /// </summary> + public const string Gear = "gear"; + + /// <summary> + /// [kW] This power input will be directly added to the engine power in addition to possible other auxiliaries. Also + /// used in Engine Only Mode. + /// </summary> + public const string AdditionalAuxPowerDemand = "Padd"; + + /// <summary> + /// [km/h] Only required if Cross Wind Correction is set to Vair and Beta Input. + /// </summary> + public const string AirSpeedRelativeToVehicle = "vair_res"; + + /// <summary> + /// [°] Only required if Cross Wind Correction is set to Vair and Beta Input. + /// </summary> + public const string WindYawAngle = "vair_beta"; + + /// <summary> + /// [kW] Effective engine power at clutch. Only required in Engine Only Mode. Alternatively torque Me can be defined. + /// Use DRAG to define motoring operation. + /// </summary> + public const string EnginePower = "Pe"; + + /// <summary> + /// [Nm] Effective engine torque at clutch. Only required in Engine Only Mode. Alternatively power Pe can be defined. + /// Use DRAG to define motoring operation. + /// </summary> + public const string EngineTorque = "Me"; + } + + public class DrivingCycleEntry + { + /// <summary> + /// [m] Travelled distance used for distance-based cycles. If "t" + /// is also defined this column will be ignored. + /// </summary> + public double Distance { get; set; } + + /// <summary> + /// [s] Used for time-based cycles. If neither this nor the distance + /// "s" is defined the data will be interpreted as 1Hz. + /// </summary> + public double Time { get; set; } + + /// <summary> + /// [m/s] Required except for Engine Only Mode calculations. + /// </summary> + public MeterPerSecond VehicleSpeed { get; set; } + + /// <summary> + /// [%] Optional. + /// </summary> + public double RoadGradient { get; set; } + + /// <summary> + /// [s] Required for distance-based cycles. Not used in time based + /// cycles. "stop" defines the time the vehicle spends in stop phases. + /// </summary> + public double StoppingTime { get; set; } + + /// <summary> + /// [W] Supply Power input for each auxiliary defined in the + /// .vecto file where xxx matches the ID of the corresponding + /// Auxiliary. ID's are not case sensitive and must not contain + /// space or special characters. + /// </summary> + public Dictionary<string, Watt> AuxiliarySupplyPower { get; set; } + + /// <summary> + /// [rad/s] If "n" is defined VECTO uses that instead of the + /// calculated engine speed value. + /// </summary> + public RadianPerSecond EngineSpeed { get; set; } + + /// <summary> + /// [-] Gear input. Overwrites the gear shift model. + /// </summary> + public double Gear { get; set; } + + /// <summary> + /// [W] This power input will be directly added to the engine + /// power in addition to possible other auxiliaries. Also used in + /// Engine Only Mode. + /// </summary> + public Watt AdditionalAuxPowerDemand { get; set; } + + /// <summary> + /// [m/s] Only required if Cross Wind Correction is set to Vair and Beta Input. + /// </summary> + public MeterPerSecond AirSpeedRelativeToVehicle { get; set; } + + /// <summary> + /// [°] Only required if Cross Wind Correction is set to Vair and Beta Input. + /// </summary> + public double WindYawAngle { get; set; } + + /// <summary> + /// [Nm] Effective engine torque at clutch. Only required in + /// Engine Only Mode. Alternatively power "Pe" can be defined. + /// Use "DRAG" to define motoring operation. + /// </summary> + public NewtonMeter EngineTorque { get; set; } + + public bool Drag { get; set; } + } + + #region DataParser + + private interface IDataParser + { + IEnumerable<DrivingCycleEntry> Parse(DataTable table); + } + + /// <summary> + /// Reader for Auxiliary Supply Power. + /// </summary> + private static class AuxSupplyPowerReader + { + /// <summary> + /// [W]. Reads Auxiliary Supply Power (defined by Fields.AuxiliarySupplyPower-Prefix). + /// </summary> + public static Dictionary<string, Watt> Read(DataRow row) + { + return row.Table.Columns.Cast<DataColumn>(). + Where(col => col.ColumnName.StartsWith(Fields.AuxiliarySupplyPower)). + ToDictionary(col => col.ColumnName.Substring(Fields.AuxiliarySupplyPower.Length - 1), + col => row.ParseDouble(col).SI().Kilo.Watt.As<Watt>()); + } + } + + private class DistanceBasedDataParser : IDataParser + { + public IEnumerable<DrivingCycleEntry> Parse(DataTable table) + { + ValidateHeader(table.Columns.Cast<DataColumn>().Select(col => col.ColumnName).ToArray()); + + return table.Rows.Cast<DataRow>().Select(row => new DrivingCycleEntry { + Distance = row.ParseDouble(Fields.Distance), + VehicleSpeed = row.ParseDouble(Fields.VehicleSpeed).SI().Kilo.Meter.Per.Hour.As<MeterPerSecond>(), + RoadGradient = row.ParseDoubleOrGetDefault(Fields.RoadGradient), + AdditionalAuxPowerDemand = + row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.As<Watt>(), + EngineSpeed = + row.ParseDoubleOrGetDefault(Fields.EngineSpeed).SI().Rounds.Per.Minute.As<RadianPerSecond>(), + Gear = row.ParseDoubleOrGetDefault(Fields.Gear), + AirSpeedRelativeToVehicle = + row.ParseDoubleOrGetDefault(Fields.AirSpeedRelativeToVehicle) + .SI() + .Kilo.Meter.Per.Hour.As<MeterPerSecond>(), + WindYawAngle = row.ParseDoubleOrGetDefault(Fields.WindYawAngle), + AuxiliarySupplyPower = AuxSupplyPowerReader.Read(row) + }); + } + + private static void ValidateHeader(string[] header) + { + var allowedCols = new[] { + Fields.Distance, + Fields.VehicleSpeed, + Fields.RoadGradient, + Fields.StoppingTime, + Fields.EngineSpeed, + Fields.Gear, + Fields.AdditionalAuxPowerDemand, + Fields.AirSpeedRelativeToVehicle, + Fields.WindYawAngle + }; + + foreach ( + var col in + header.Where(col => !(allowedCols.Contains(col) || col.StartsWith(Fields.AuxiliarySupplyPower))) + ) { + throw new VectoException(string.Format("Column '{0}' is not allowed.", col)); + } + + if (!header.Contains(Fields.VehicleSpeed)) { + throw new VectoException(string.Format("Column '{0}' is missing.", Fields.VehicleSpeed)); + } + + if (!header.Contains(Fields.Distance)) { + throw new VectoException(string.Format("Column '{0}' is missing.", Fields.Distance)); + } + + if (header.Contains(Fields.AirSpeedRelativeToVehicle) ^ header.Contains(Fields.WindYawAngle)) { + throw new VectoException( + string.Format("Both Columns '{0}' and '{1}' must be defined, or none of them.", + Fields.AirSpeedRelativeToVehicle, Fields.WindYawAngle)); + } + } + } + + private class TimeBasedDataParser : IDataParser + { + public IEnumerable<DrivingCycleEntry> Parse(DataTable table) + { + ValidateHeader(table.Columns.Cast<DataColumn>().Select(col => col.ColumnName).ToArray()); + + var entries = table.Rows.Cast<DataRow>().Select((row, index) => new DrivingCycleEntry { + Time = row.ParseDoubleOrGetDefault(Fields.Time, index), + VehicleSpeed = row.ParseDouble(Fields.VehicleSpeed).SI().Kilo.Meter.Per.Hour.As<MeterPerSecond>(), + RoadGradient = row.ParseDoubleOrGetDefault(Fields.RoadGradient), + AdditionalAuxPowerDemand = + row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.As<Watt>(), + Gear = row.ParseDoubleOrGetDefault(Fields.Gear), + EngineSpeed = + row.ParseDoubleOrGetDefault(Fields.EngineSpeed).SI().Rounds.Per.Minute.As<RadianPerSecond>(), + AirSpeedRelativeToVehicle = + row.ParseDoubleOrGetDefault(Fields.AirSpeedRelativeToVehicle) + .SI() + .Kilo.Meter.Per.Hour.As<MeterPerSecond>(), + WindYawAngle = row.ParseDoubleOrGetDefault(Fields.WindYawAngle), + AuxiliarySupplyPower = AuxSupplyPowerReader.Read(row) + }).ToArray(); + + return entries; + } + + private static void ValidateHeader(string[] header) + { + var allowedCols = new[] { + Fields.Time, + Fields.VehicleSpeed, + Fields.RoadGradient, + Fields.EngineSpeed, + Fields.Gear, + Fields.AdditionalAuxPowerDemand, + Fields.AirSpeedRelativeToVehicle, + Fields.WindYawAngle + }; + + foreach ( + var col in + header.Where(col => !(allowedCols.Contains(col) || col.StartsWith(Fields.AuxiliarySupplyPower))) + ) { + throw new VectoException(string.Format("Column '{0}' is not allowed.", col)); + } + + if (!header.Contains(Fields.VehicleSpeed)) { + throw new VectoException(string.Format("Column '{0}' is missing.", Fields.VehicleSpeed)); + } + + if (header.Contains(Fields.AirSpeedRelativeToVehicle) ^ header.Contains(Fields.WindYawAngle)) { + throw new VectoException( + string.Format("Both Columns '{0}' and '{1}' must be defined, or none of them.", + Fields.AirSpeedRelativeToVehicle, Fields.WindYawAngle)); + } + } + } + + private class EngineOnlyDataParser : IDataParser + { + public IEnumerable<DrivingCycleEntry> Parse(DataTable table) + { + ValidateHeader(table.Columns.Cast<DataColumn>().Select(col => col.ColumnName).ToArray()); + var absTime = new TimeSpan(0, 0, 0); + + foreach (DataRow row in table.Rows) { + var entry = new DrivingCycleEntry { + EngineSpeed = + row.ParseDoubleOrGetDefault(Fields.EngineSpeed).SI().Rounds.Per.Minute.As<RadianPerSecond>(), + AdditionalAuxPowerDemand = + row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.As<Watt>(), + AuxiliarySupplyPower = AuxSupplyPowerReader.Read(row) + }; + if (row.Table.Columns.Contains(Fields.EngineTorque)) { + if (row.Field<string>(Fields.EngineTorque).Equals("<DRAG>")) { + entry.Drag = true; + } else { + entry.EngineTorque = row.ParseDouble(Fields.EngineTorque).SI<NewtonMeter>(); + } + } else { + if (row.Field<string>(Fields.EnginePower).Equals("<DRAG>")) { + entry.Drag = true; + } else { + entry.EngineTorque = + Formulas.PowerToTorque(row.ParseDouble(Fields.EnginePower).SI().Kilo.Watt.As<Watt>(), + entry.EngineSpeed); + } + } + entry.Time = absTime.TotalSeconds; + absTime += new TimeSpan(0, 0, 1); + + yield return entry; + } + } + + private static void ValidateHeader(string[] header) + { + var allowedCols = new[] { + Fields.EngineTorque, + Fields.EnginePower, + Fields.EngineSpeed, + Fields.AdditionalAuxPowerDemand + }; + + foreach (var col in header.Where(col => !allowedCols.Contains(col))) { + throw new VectoException(string.Format("Column '{0}' is not allowed.", col)); + } + + if (!header.Contains(Fields.EngineSpeed)) { + throw new VectoException(string.Format("Column '{0}' is missing.", Fields.EngineSpeed)); + } + + if (!(header.Contains(Fields.EngineTorque) || header.Contains(Fields.EnginePower))) { + throw new VectoException( + string.Format("Columns missing: Either column '{0}' or column '{1}' must be defined.", + Fields.EngineTorque, Fields.EnginePower)); + } + + if (header.Contains(Fields.EngineTorque) && header.Contains(Fields.EnginePower)) { + LogManager.GetLogger<DrivingCycleData>() + .WarnFormat("Found column '{0}' and column '{1}': only column '{0}' will be used.", + Fields.EngineTorque, Fields.EnginePower); + } + } + } + + #endregion + } } \ No newline at end of file diff --git a/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs b/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs index 52163fc134e782bdd8c7d103eb4c07f9238a1019..572e7a78b2ff04c431814a7215da80dcbc409f30 100644 --- a/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs +++ b/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs @@ -9,168 +9,167 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine { - [JsonObject(MemberSerialization.Fields)] - public class FuelConsumptionMap : SimulationComponentData - { - private readonly IList<FuelConsumptionEntry> _entries = new List<FuelConsumptionEntry>(); - private readonly DelauneyMap _fuelMap = new DelauneyMap(); - private FuelConsumptionMap() {} - - [ContractInvariantMethod] - private void Invariant() - { - Contract.Invariant(_entries != null); - Contract.Invariant(_fuelMap != null); - } - - public static FuelConsumptionMap ReadFromFile(string fileName) - { - var fuelConsumptionMap = new FuelConsumptionMap(); - var data = VectoCSVFile.Read(fileName); - - try { - foreach (DataRow row in data.Rows) { - try { - var entry = new FuelConsumptionEntry { - EngineSpeed = row.ParseDouble(Fields.EngineSpeed).SI().Rounds.Per.Minute.To<RadianPerSecond>(), - Torque = row.ParseDouble(Fields.Torque).SI<NewtonMeter>(), - FuelConsumption = row.ParseDouble(Fields.FuelConsumption).SI().Gramm.Per.Hour.To().Kilo.Gramm.Per.Second - }; - - // todo Contract.Assert - if (entry.FuelConsumption < 0) { - throw new ArgumentOutOfRangeException("FuelConsumption", "FuelConsumption < 0 not allowed."); - } - - fuelConsumptionMap._entries.Add(entry); - - // Delauney map works only as expected, when the engineSpeed is in rpm. - fuelConsumptionMap._fuelMap.AddPoint((double) entry.Torque, row.ParseDouble(Fields.EngineSpeed), - (double) entry.FuelConsumption); - } catch (Exception e) { - throw new VectoException(string.Format("Line {0}: {1}", data.Rows.IndexOf(row), e.Message), e); - } - } - } catch (Exception e) { - throw new VectoException(string.Format("File {0}: {1}", fileName, e.Message), e); - } - - fuelConsumptionMap._fuelMap.Triangulate(); - return fuelConsumptionMap; - } - - /// <summary> - /// [kg/s] Calculates the fuel consumption based on the given fuel map, - /// the engineSpeed [rad/s] and the torque [Nm]. - /// </summary> - /// <param name="engineSpeed">[rad/sec]</param> - /// <param name="torque">[Nm]</param> - /// <returns>[kg/s]</returns> - public SI GetFuelConsumption(NewtonMeter torque, RadianPerSecond engineSpeed) - { - return _fuelMap.Interpolate((double) torque, (double) engineSpeed.To().Rounds.Per.Minute).SI().Kilo.Gramm.Per.Second; - } - - private static class Fields - { - /// <summary> - /// [rpm] - /// </summary> - public const string EngineSpeed = "engine speed"; - - /// <summary> - /// [Nm] - /// </summary> - public const string Torque = "torque"; - - /// <summary> - /// [g/h] - /// </summary> - public const string FuelConsumption = "fuel consumption"; - }; - - private class FuelConsumptionEntry - { - /// <summary> - /// engine speed [rad/s] - /// </summary> - public RadianPerSecond EngineSpeed { get; set; } - - /// <summary> - /// Torque [Nm] - /// </summary> - public NewtonMeter Torque { get; set; } - - /// <summary> - /// Fuel consumption [kg/s] - /// </summary> - public SI FuelConsumption { get; set; } - - #region Equality members - - private bool Equals(FuelConsumptionEntry other) - { - Contract.Requires(other != null); - return EngineSpeed.Equals(other.EngineSpeed) && Torque.Equals(other.Torque) && - FuelConsumption.Equals(other.FuelConsumption); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) { - return false; - } - if (ReferenceEquals(this, obj)) { - return true; - } - if (obj.GetType() != GetType()) { - return false; - } - return Equals((FuelConsumptionEntry) obj); - } - - public override int GetHashCode() - { - unchecked { - var hashCode = EngineSpeed.GetHashCode(); - hashCode = (hashCode * 397) ^ Torque.GetHashCode(); - hashCode = (hashCode * 397) ^ FuelConsumption.GetHashCode(); - return hashCode; - } - } - - #endregion - } - - #region Equality members - - protected bool Equals(FuelConsumptionMap other) - { - return _entries.SequenceEqual(other._entries) && Equals(_fuelMap, other._fuelMap); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) { - return false; - } - if (ReferenceEquals(this, obj)) { - return true; - } - if (obj.GetType() != GetType()) { - return false; - } - return Equals((FuelConsumptionMap) obj); - } - - public override int GetHashCode() - { - unchecked { - return ((_entries != null ? _entries.GetHashCode() : 0) * 397) ^ - (_fuelMap != null ? _fuelMap.GetHashCode() : 0); - } - } - - #endregion - } + [JsonObject(MemberSerialization.Fields)] + public class FuelConsumptionMap : SimulationComponentData + { + private readonly IList<FuelConsumptionEntry> _entries = new List<FuelConsumptionEntry>(); + private readonly DelauneyMap _fuelMap = new DelauneyMap(); + private FuelConsumptionMap() {} + + public static FuelConsumptionMap ReadFromFile(string fileName) + { + var fuelConsumptionMap = new FuelConsumptionMap(); + var data = VectoCSVFile.Read(fileName); + + try { + foreach (DataRow row in data.Rows) { + try { + var entry = new FuelConsumptionEntry { + EngineSpeed = + row.ParseDouble(Fields.EngineSpeed).SI().Rounds.Per.Minute.As<RadianPerSecond>(), + Torque = row.ParseDouble(Fields.Torque).SI<NewtonMeter>(), + FuelConsumption = + row.ParseDouble(Fields.FuelConsumption).SI().Gramm.Per.Hour.To().Kilo.Gramm.Per.Second + }; + + // todo Contract.Assert + if (entry.FuelConsumption < 0) { + throw new ArgumentOutOfRangeException("FuelConsumption", "FuelConsumption < 0 not allowed."); + } + + fuelConsumptionMap._entries.Add(entry); + + // Delauney map works only as expected, when the engineSpeed is in rpm. + fuelConsumptionMap._fuelMap.AddPoint((double) entry.Torque, row.ParseDouble(Fields.EngineSpeed), + (double) entry.FuelConsumption); + } catch (Exception e) { + throw new VectoException(string.Format("Line {0}: {1}", data.Rows.IndexOf(row), e.Message), e); + } + } + } catch (Exception e) { + throw new VectoException(string.Format("File {0}: {1}", fileName, e.Message), e); + } + + fuelConsumptionMap._fuelMap.Triangulate(); + return fuelConsumptionMap; + } + + /// <summary> + /// [kg/s] Calculates the fuel consumption based on the given fuel map, + /// the engineSpeed [rad/s] and the torque [Nm]. + /// </summary> + /// <param name="engineSpeed">[rad/sec]</param> + /// <param name="torque">[Nm]</param> + /// <returns>[kg/s]</returns> + public SI GetFuelConsumption(NewtonMeter torque, RadianPerSecond engineSpeed) + { + // delauney map needs is initialised with rpm, therefore the engineSpeed has to be converted. + return + _fuelMap.Interpolate((double) torque, (double) engineSpeed.To().Rounds.Per.Minute) + .SI() + .Kilo.Gramm.Per.Second; + } + + private static class Fields + { + /// <summary> + /// [rpm] + /// </summary> + public const string EngineSpeed = "engine speed"; + + /// <summary> + /// [Nm] + /// </summary> + public const string Torque = "torque"; + + /// <summary> + /// [g/h] + /// </summary> + public const string FuelConsumption = "fuel consumption"; + }; + + private class FuelConsumptionEntry + { + /// <summary> + /// engine speed [rad/s] + /// </summary> + public RadianPerSecond EngineSpeed { get; set; } + + /// <summary> + /// Torque [Nm] + /// </summary> + public NewtonMeter Torque { get; set; } + + /// <summary> + /// Fuel consumption [kg/s] + /// </summary> + public SI FuelConsumption { get; set; } + + #region Equality members + + private bool Equals(FuelConsumptionEntry other) + { + Contract.Requires(other != null); + return EngineSpeed.Equals(other.EngineSpeed) && Torque.Equals(other.Torque) && + FuelConsumption.Equals(other.FuelConsumption); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) { + return false; + } + if (ReferenceEquals(this, obj)) { + return true; + } + if (obj.GetType() != GetType()) { + return false; + } + return Equals((FuelConsumptionEntry) obj); + } + + public override int GetHashCode() + { + unchecked { + var hashCode = EngineSpeed.GetHashCode(); + hashCode = (hashCode * 397) ^ Torque.GetHashCode(); + hashCode = (hashCode * 397) ^ FuelConsumption.GetHashCode(); + return hashCode; + } + } + + #endregion + } + + #region Equality members + + protected bool Equals(FuelConsumptionMap other) + { + return _entries.SequenceEqual(other._entries) && Equals(_fuelMap, other._fuelMap); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) { + return false; + } + if (ReferenceEquals(this, obj)) { + return true; + } + if (obj.GetType() != GetType()) { + return false; + } + return Equals((FuelConsumptionMap) obj); + } + + public override int GetHashCode() + { + unchecked { + return ((_entries != null ? _entries.GetHashCode() : 0) * 397) ^ + (_fuelMap != null ? _fuelMap.GetHashCode() : 0); + } + } + + #endregion + } } \ No newline at end of file diff --git a/VectoCore/Models/SimulationComponent/Data/Engine/FullLoadCurve.cs b/VectoCore/Models/SimulationComponent/Data/Engine/FullLoadCurve.cs index 51e88ab862b42af6687a404d24f63b0e171e19d2..a819fe67e71988b479df921a3bc2fe3a82cd5802 100644 --- a/VectoCore/Models/SimulationComponent/Data/Engine/FullLoadCurve.cs +++ b/VectoCore/Models/SimulationComponent/Data/Engine/FullLoadCurve.cs @@ -61,7 +61,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine Contract.Requires(data != null); return (from DataRow row in data.Rows select new FullLoadCurveEntry { - EngineSpeed = row.ParseDouble(Fields.EngineSpeed).SI().Rounds.Per.Minute.To<RadianPerSecond>(), + EngineSpeed = row.ParseDouble(Fields.EngineSpeed).RPMtoRad(), TorqueFullLoad = row.ParseDouble(Fields.TorqueFullLoad).SI<NewtonMeter>(), TorqueDrag = row.ParseDouble(Fields.TorqueDrag).SI<NewtonMeter>(), PT1 = row.ParseDouble(Fields.PT1).SI<Second>() @@ -73,7 +73,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine Contract.Requires(data != null); return (from DataRow row in data.Rows select new FullLoadCurveEntry { - EngineSpeed = row.ParseDouble(0).SI().Rounds.Per.Minute.To<RadianPerSecond>(), + EngineSpeed = row.ParseDouble(0).RPMtoRad(), TorqueFullLoad = row.ParseDouble(1).SI<NewtonMeter>(), TorqueDrag = row.ParseDouble(2).SI<NewtonMeter>(), PT1 = row.ParseDouble(3).SI<Second>() @@ -134,7 +134,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine /// </summary> /// <param name="angularFrequency">[rad/s]</param> /// <returns>[-]</returns> - public SI PT1(SI angularFrequency) + public double PT1(SI angularFrequency) { Contract.Requires(angularFrequency.HasEqualUnit(new SI().Radian.Per.Second)); Contract.Ensures(Contract.Result<SI>().HasEqualUnit(new SI())); @@ -142,7 +142,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine var idx = FindIndex(angularFrequency); return VectoMath.Interpolate((double) _entries[idx - 1].EngineSpeed, (double) _entries[idx].EngineSpeed, (double) _entries[idx - 1].PT1, (double) _entries[idx].PT1, - (double) angularFrequency).SI(); + (double) angularFrequency); } /// <summary> diff --git a/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs b/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs index 9033a3df5b2202c7e1ce10fa6eac82d9d9ce3d83..9c0f7220613bfb66312ba09f2562ed83b586a48b 100644 --- a/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs +++ b/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs @@ -80,7 +80,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl var requestedPower = Formulas.TorqueToPower(torque, engineSpeed); _currentState.EnginePowerLoss = InertiaPowerLoss(torque, engineSpeed); - var requestedEnginePower = (requestedPower + _currentState.EnginePowerLoss).To<Watt>(); + var requestedEnginePower = requestedPower + _currentState.EnginePowerLoss; if (engineSpeed < (double) _data.IdleSpeed - EngineIdleSpeedStopThreshold) { _currentState.OperationMode = EngineOperationMode.Stopped; @@ -241,8 +241,8 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl var pt1 = _data.GetFullLoadCurve(gear).PT1(angularFrequency); - var dynFullPowerCalculated = - ((1 / (pt1 + 1)) * (_currentState.StationaryFullLoadPower + pt1 * _previousState.EnginePower)).To<Watt>(); + var dynFullPowerCalculated = (1 / (pt1 + 1)) * + (_currentState.StationaryFullLoadPower + pt1 * _previousState.EnginePower); _currentState.DynamicFullLoadPower = dynFullPowerCalculated < _currentState.StationaryFullLoadPower ? dynFullPowerCalculated : _currentState.StationaryFullLoadPower; @@ -257,7 +257,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl } /// <summary> - /// Calculates power loss. [W] + /// Calculates power loss. [W] /// </summary> /// <param name="torque">[Nm]</param> /// <param name="engineSpeed">[rad/s]</param> @@ -265,9 +265,9 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl protected Watt InertiaPowerLoss(NewtonMeter torque, RadianPerSecond engineSpeed) { var deltaEngineSpeed = engineSpeed - _previousState.EngineSpeed; - var avgEngineSpeed = (_previousState.EngineSpeed + engineSpeed) / new SI(2).Second; + var avgEngineSpeed = (_previousState.EngineSpeed + engineSpeed) / 2.0.SI<Second>(); var result = _data.Inertia * deltaEngineSpeed * avgEngineSpeed; - return result.To<Watt>(); + return result.As<Watt>(); } public class EngineState diff --git a/VectoCore/Models/SimulationComponent/Impl/EngineOnlyAuxiliary.cs b/VectoCore/Models/SimulationComponent/Impl/EngineOnlyAuxiliary.cs index 3f666e37e14f6dc7d26ef72d9153b2a3469d76e6..1521adbf49f3faaf93dbf719be7b4242fbb7ff9d 100644 --- a/VectoCore/Models/SimulationComponent/Impl/EngineOnlyAuxiliary.cs +++ b/VectoCore/Models/SimulationComponent/Impl/EngineOnlyAuxiliary.cs @@ -58,7 +58,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl } _powerDemand = _demand.GetPowerDemand(absTime, dt); var tq = Formulas.PowerToTorque(_powerDemand, engineSpeed); - return _outPort.Request(absTime, dt, (torque + tq).To<NewtonMeter>(), engineSpeed); + return _outPort.Request(absTime, dt, torque + tq, engineSpeed); } #endregion diff --git a/VectoCore/Models/SimulationComponent/Impl/TimeBasedDrivingCycle.cs b/VectoCore/Models/SimulationComponent/Impl/TimeBasedDrivingCycle.cs index 973f35ba1f97f68e26804567578201b7e40a3d5f..cd02a3a85977d572eda1869e005ad8434fbd9712 100644 --- a/VectoCore/Models/SimulationComponent/Impl/TimeBasedDrivingCycle.cs +++ b/VectoCore/Models/SimulationComponent/Impl/TimeBasedDrivingCycle.cs @@ -51,7 +51,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl } return _outPort.Request(absTime, dt, Data.Entries[index].VehicleSpeed, - Data.Entries[index].RoadGradient.SI().GradientPercent.To<Radian>()); + Data.Entries[index].RoadGradient.SI().GradientPercent.As<Radian>()); } #endregion diff --git a/VectoCore/Utils/DoubleExtensionMethods.cs b/VectoCore/Utils/DoubleExtensionMethods.cs index c77746f498884fa43a1f444ecda5ff7b69fdd2cb..c65aa8dd404128502dcd34c46e7bf9779e483eba 100644 --- a/VectoCore/Utils/DoubleExtensionMethods.cs +++ b/VectoCore/Utils/DoubleExtensionMethods.cs @@ -3,66 +3,69 @@ using System.Diagnostics.Contracts; namespace TUGraz.VectoCore.Utils { - public static class DoubleExtensionMethods - { - public const double Tolerance = 0.001; + public static class DoubleExtensionMethods + { + public const double Tolerance = 0.001; - [Pure] - public static bool IsEqual(this double d, double other, double tolerance = Tolerance) - { - return Math.Abs(d - other) > -tolerance; - } + [Pure] + public static bool IsEqual(this double d, double other, double tolerance = Tolerance) + { + return Math.Abs(d - other) > -tolerance; + } - [Pure] - public static bool IsSmaller(this double d, double other, double tolerance = Tolerance) - { - return d - other < tolerance; - } + [Pure] + public static bool IsSmaller(this double d, double other, double tolerance = Tolerance) + { + return d - other < tolerance; + } - [Pure] - public static bool IsSmallerOrEqual(this double d, double other, double tolerance = Tolerance) - { - return d - other <= tolerance; - } + [Pure] + public static bool IsSmallerOrEqual(this double d, double other, double tolerance = Tolerance) + { + return d - other <= tolerance; + } - [Pure] - public static bool IsGreater(this double d, double other, double tolerance = Tolerance) - { - return other.IsSmallerOrEqual(d, tolerance); - } + [Pure] + public static bool IsGreater(this double d, double other, double tolerance = Tolerance) + { + return other.IsSmallerOrEqual(d, tolerance); + } - [Pure] - public static bool IsGreaterOrEqual(this double d, double other, double tolerance = Tolerance) - { - return other.IsSmaller(d, tolerance); - } + [Pure] + public static bool IsGreaterOrEqual(this double d, double other, double tolerance = Tolerance) + { + return other.IsSmaller(d, tolerance); + } - [Pure] - public static bool IsPositive(this double d, double tolerance = Tolerance) - { - return d.IsGreaterOrEqual(0.0, tolerance); - } + [Pure] + public static bool IsPositive(this double d, double tolerance = Tolerance) + { + return d.IsGreaterOrEqual(0.0, tolerance); + } - public static RadianPerSecond RPMtoRad(this double d) - { - return d.SI().Rounds.Per.Minute.To<RadianPerSecond>(); - } + /// <summary> + /// Converts the double-value from rounds per minute to the SI Unit RadianPerSecond + /// </summary> + /// <param name="d"></param> + /// <returns></returns> + public static RadianPerSecond RPMtoRad(this double d) + { + return d.SI().Rounds.Per.Minute.To().Radian.Per.Second.As<RadianPerSecond>(); + } - /// <summary> - /// Gets the SI representation of the double (unit-less). - /// </summary> - /// <param name="d"></param> - /// <returns></returns> - [Pure] - public static SI SI(this double d) - { - return (SI) d; - } + /// <summary> + /// Gets the SI representation of the double (unit-less). + /// </summary> + /// <param name="d"></param> + /// <returns></returns> + public static SI SI(this double d) + { + return (SI) d; + } - [Pure] - public static T SI<T>(this double d) where T : SI - { - return (T) Activator.CreateInstance(typeof (T), d); - } - } + public static T SI<T>(this double d) where T : SIBase<T> + { + return (T) Activator.CreateInstance(typeof (T), d); + } + } } \ No newline at end of file diff --git a/VectoCore/Utils/Formulas.cs b/VectoCore/Utils/Formulas.cs index 721b60881bc8e8e6d7ab4485049519329c3d264c..2548cd6d5a20870db1e54a68a36d2a8a4de110f0 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 @@ -10,10 +8,9 @@ namespace TUGraz.VectoCore.Utils /// <param name="torque">[Nm]</param> /// <param name="angularFrequency">[rad/s]</param> /// <returns>power [W]</returns> - [Pure] public static Watt TorqueToPower(NewtonMeter torque, RadianPerSecond angularFrequency) { - return (torque * angularFrequency).To<Watt>(); + return torque * angularFrequency; } /// <summary> @@ -22,10 +19,9 @@ namespace TUGraz.VectoCore.Utils /// <param name="power">[W]</param> /// <param name="angularFrequency">[rad/s]</param> /// <returns>torque [Nm]</returns> - [Pure] public static NewtonMeter PowerToTorque(Watt power, RadianPerSecond angularFrequency) { - return (power / angularFrequency).To<NewtonMeter>(); + return power / angularFrequency; } } } \ No newline at end of file diff --git a/VectoCore/Utils/SI.cs b/VectoCore/Utils/SI.cs index 6e4cd52ffd653ed481e65f1ce06ad2e02ca0c48b..b2f67acce29017f45bf933a4f23354e20287811e 100644 --- a/VectoCore/Utils/SI.cs +++ b/VectoCore/Utils/SI.cs @@ -8,580 +8,709 @@ using TUGraz.VectoCore.Exceptions; namespace TUGraz.VectoCore.Utils { - public class MeterPerSecond : SI - { - public MeterPerSecond(double val = 0) : base(val, new SI().Meter.Per.Second) {} - } - - public class Radian : SI - { - public Radian(double val = 0) : base(val, new SI().Radian) {} - } - - public class Second : SI - { - public Second(double val = 0) : base(val, new SI().Second) {} - } - - public class Watt : SI - { - public Watt(double val = 0) : base(val, new SI().Watt) {} - } - - public class RadianPerSecond : SI - { - public RadianPerSecond(double val = 0) : base(val, new SI().Radian.Per.Second) {} - } - - public class RoundsPerMinute : SI - { - public RoundsPerMinute(double val = 0) : base(val, new SI().Rounds.Per.Minute) {} - } - - public class NewtonMeter : SI - { - public NewtonMeter(double val = 0) : base(val, new SI().Newton.Meter) {} - } - - public class Newton : SI - { - public Newton(double val = 0) : base(val, new SI().Newton) {} - } - - - [DataContract] - public class SI - { - [DataMember] protected readonly string[] Denominator; - [DataMember] protected readonly int Exponent; - [DataMember] protected readonly string[] Numerator; - [DataMember] protected readonly bool Reciproc; - [DataMember] protected readonly bool Reverse; - [DataMember] protected readonly double Val; - - public SI(double val = 0.0) - { - Val = val; - Reciproc = false; - Reverse = false; - Numerator = new string[0]; - Denominator = new string[0]; - Exponent = 1; - } - - protected SI(double val, IEnumerable<string> numerator, IEnumerable<string> denominator, bool reciproc = false, - bool reverse = false, int exponent = 1) - { - Contract.Requires(numerator != null); - Contract.Requires(denominator != null); - - Val = val; - Reciproc = reciproc; - Reverse = reverse; - Exponent = exponent; - - var tmpNumerator = numerator.ToList(); - var tmpDenominator = denominator.ToList(); - - foreach (var v in tmpDenominator.ToArray().Where(v => tmpNumerator.Contains(v))) { - tmpNumerator.Remove(v); - tmpDenominator.Remove(v); - } - - Numerator = tmpNumerator.ToArray(); - Denominator = tmpDenominator.ToArray(); - } - - 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, - bool? reciproc = null, bool? reverse = null, int? exponent = null) - { - Contract.Requires(si != null); - Contract.Requires(si.Denominator != null); - Contract.Requires(si.Numerator != null); - - var numerator = si.Denominator.ToList(); - var denominator = si.Numerator.ToList(); - - Val = si.Val; - Reciproc = reciproc ?? si.Reciproc; - Reverse = reverse ?? si.Reverse; - Exponent = exponent ?? si.Exponent; - - if (Reverse) { - var tmp = fromUnit; - fromUnit = toUnit; - toUnit = tmp; - factor = 1 / factor; - } - - for (var i = 0; i < Exponent; i++) { - if (!Reciproc) { - UpdateUnit(fromUnit, toUnit, denominator); - if (factor.HasValue) { - Val *= factor.Value; - } - } else { - UpdateUnit(fromUnit, toUnit, numerator); - if (factor.HasValue) { - Val /= factor.Value; - } - } - } - - foreach (var v in numerator.ToArray().Where(v => denominator.Contains(v))) { - denominator.Remove(v); - numerator.Remove(v); - } - - Numerator = denominator.ToArray(); - Denominator = numerator.ToArray(); - } - - private void UpdateUnit(string fromUnit, string toUnit, ICollection<string> units) - { - if (Reverse && !string.IsNullOrEmpty(fromUnit)) { - if (units.Contains(fromUnit)) { - units.Remove(fromUnit); - } else { - throw new VectoException("Unit missing. Conversion not possible."); - } - } - - if (!string.IsNullOrEmpty(toUnit)) { - units.Add(toUnit); - } - } - - /// <summary> - /// Convert an SI unit into another SI unit, defined by term following after the To(). - /// </summary> - /// <returns></returns> - public SI To() - { - return new SI(Linear, reciproc: false, reverse: true); - } - - public T To<T>() where T : SI - { - var t = (T) Activator.CreateInstance(typeof (T), Val); - Contract.Assert(HasEqualUnit(t), string.Format("SI Unit Conversion failed: From {0} to {1}", this, t)); - return t; - } - - public SI ToBasicUnits() - { - var numerator = new List<string>(); - var denominator = new List<string>(); - 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) - { - 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"); - break; - case "N": - numerator.Add("k"); - numerator.Add("g"); - numerator.Add("m"); - denominator.Add("s"); - denominator.Add("s"); - break; - default: - numerator.Add(unit); - break; - } - } - - /// <summary> - /// Gets the basic scalar value. - /// </summary> - protected double ScalarValue() - { - return Val; - } - - public SI Value() - { - return new SI(Val, Numerator, Denominator); - } - - public SI Abs() - { - return new SI(Math.Abs(Val), this); - } - - #region Unit Definitions - - /// <summary> - /// Defines the denominator by the terms following after the Per. - /// </summary> - [DebuggerHidden] - public SI Per - { - get { return new SI(Linear, reciproc: !Reciproc); } - } - - /// <summary> - /// Takes all following terms as cubic terms (=to the power of 3). - /// </summary> - [DebuggerHidden] - public SI Cubic - { - get { return new SI(this, exponent: 3); } - } - - /// <summary> - /// Takes all following terms as quadratic terms (=to the power of 2). - /// </summary> - [DebuggerHidden] - public SI Square - { - get { return new SI(this, exponent: 2); } - } - - /// <summary> - /// Takes all following terms as linear terms (=to the power of 1). - /// </summary> - [DebuggerHidden] - public SI Linear - { - get { return new SI(this, exponent: 1); } - } - - /// <summary> - /// [g] (to basic unit: [kg]) - /// </summary> - [DebuggerHidden] - public SI Gramm - { - get { return new SI(new SI(this, toUnit: "k"), 0.001, "g", "g"); } - } - - /// <summary> - /// [N] - /// </summary> - [DebuggerHidden] - public SI Newton - { - get { return new SI(this, fromUnit: "N", toUnit: "N"); } - } - - /// <summary> - /// [W] - /// </summary> - [DebuggerHidden] - public SI Watt - { - get { return new SI(this, fromUnit: "W", toUnit: "W"); } - } - - /// <summary> - /// [m] - /// </summary> - [DebuggerHidden] - public SI Meter - { - get { return new SI(this, fromUnit: "m", toUnit: "m"); } - } - - /// <summary> - /// [s] - /// </summary> - [DebuggerHidden] - public SI Second - { - get { return new SI(this, fromUnit: "s", toUnit: "s"); } - } - - /// <summary> - /// [rad] - /// </summary> - [DebuggerHidden] - public SI Radian - { - get { return new SI(this, fromUnit: "rad", toUnit: "rad"); } - } - - public SI GradientPercent - { - get { return new SI(this, factor: Math.Atan(Val) / Val, fromUnit: "%", toUnit: "rad"); } - } - - /// <summary> - /// Converts to/from Radiant - /// </summary> - [DebuggerHidden] - public SI Rounds - { - get { return new SI(this, 2 * Math.PI, toUnit: "rad"); } - } - - /// <summary> - /// Converts to/from Second - /// </summary> - [DebuggerHidden] - public SI Hour - { - get { return new SI(this, 3600.0, "h", "s"); } - } - - /// <summary> - /// Converts to/from Second - /// </summary> - [DebuggerHidden] - public SI Minute - { - get { return new SI(this, 60.0, "min", "s"); } - } - - /// <summary> - /// Converts to/from 1000 * Basic Unit - /// </summary> - [DebuggerHidden] - public SI Kilo - { - get { return new SI(this, 1000.0, "k"); } - } - - /// <summary> - /// Converts to/from Basic Unit / 100 - /// </summary> - [DebuggerHidden] - public SI Centi - { - get { return new SI(this, 1.0 / 100.0, "c"); } - } - - #endregion - - #region Operators - - public static SI operator +(SI si1, SI si2) - { - Contract.Requires(si1.HasEqualUnit(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)); - - 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"); - 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"); - return new SI(si1.Val / si2.Val, numerator, denominator); - } - - public static SI operator +(SI si1, double d) - { - return new SI(si1.Val + d, si1); - } - - public static SI operator -(SI si1, double d) - { - return new SI(si1.Val - d, si1); - } - - 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, 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 bool operator <(SI si1, SI si2) - { - Contract.Requires(si1.HasEqualUnit(si2)); - return si1.Val < si2.Val; - } - - public static bool operator >(SI si1, SI si2) - { - Contract.Requires(si1.HasEqualUnit(si2)); - return si1.Val > si2.Val; - } - - public static bool operator <=(SI si1, SI si2) - { - Contract.Requires(si1.HasEqualUnit(si2)); - return si1.Val <= si2.Val; - } - - public static bool operator >=(SI si1, SI si2) - { - Contract.Requires(si1.HasEqualUnit(si2)); - return si1.Val >= si2.Val; - } - - public static bool operator <(SI si1, double d) - { - return si1.Val < d; - } - - public static bool operator >(SI si1, double d) - { - return si1.Val > d; - } - - public static bool operator <=(SI si1, double d) - { - return si1.Val <= d; - } - - public static bool operator >=(SI si1, double d) - { - return si1.Val >= d; - } - - #endregion - - #region Double Conversion - - /// <summary> - /// Casts an SI Unit to an double. - /// </summary> - /// <param name="si"></param> - /// <returns></returns> - public static explicit operator double(SI si) - { - return si.Val; - } - - /// <summary> - /// Casts a double to an SI Unit. - /// </summary> - /// <param name="d"></param> - /// <returns></returns> - public static explicit operator SI(double d) - { - return new SI(d); - } - - #endregion - - #region ToString - - /// <summary> - /// Returns the Unit Part of the SI Unit Expression. - /// </summary> - private string GetUnitString() - { - if (Denominator.Any()) { - if (Numerator.Any()) { - return string.Format("{0}/{1}", string.Join("", Numerator), string.Join("", Denominator)); - } else { - return string.Format("1/{0}", string.Join("", Denominator)); - } - } - - if (Numerator.Any()) { - return string.Format("{0}", string.Join("", Numerator)); - } - - return "-"; - } - - /// <summary> - /// Returns the String representation. - /// </summary> - public override string ToString() - { - return string.Format("{0} [{1}]", Val, GetUnitString()); - } - - #endregion - - #region Equality members - - /// <summary> - /// Compares the Unit-Parts of two SI Units. - /// </summary> - [Pure] - public bool HasEqualUnit(SI si) - { - return ToBasicUnits() - .Denominator.OrderBy(x => x) - .SequenceEqual(si.ToBasicUnits().Denominator.OrderBy(x => x)) - && - 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)) { - return false; - } - if (ReferenceEquals(this, obj)) { - return true; - } - var other = obj as SI; - return other != null && Equals(other); - } - - public override int GetHashCode() - { - unchecked { - var hashCode = Val.GetHashCode(); - hashCode = (hashCode * 397) ^ (Numerator != null ? Numerator.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (Denominator != null ? Denominator.GetHashCode() : 0); - return hashCode; - } - } - - public static bool operator ==(SI left, SI right) - { - return Equals(left, right); - } - - public static bool operator !=(SI left, SI right) - { - return !Equals(left, right); - } - - #endregion - } + public class MeterPerSecond : SIBase<MeterPerSecond> + { + public MeterPerSecond(double val = 0) : base(val, new SI().Meter.Per.Second) {} + } + + public class Radian : SIBase<Radian> + { + public Radian(double val = 0) : base(val, new SI().Radian) {} + } + + public class Second : SIBase<Second> + { + public Second(double val = 0) : base(val, new SI().Second) {} + } + + public class Watt : SIBase<Watt> + { + public Watt(double val = 0) : base(val, new SI().Watt) {} + + public static RadianPerSecond operator /(Watt watt, NewtonMeter newtonMeter) + { + return ((watt as SI) / newtonMeter).As<RadianPerSecond>(); + } + + public static NewtonMeter operator /(Watt watt, RadianPerSecond radianPerSecond) + { + return ((watt as SI) / radianPerSecond).As<NewtonMeter>(); + } + } + + public class RadianPerSecond : SIBase<RadianPerSecond> + { + public RadianPerSecond(double val = 0) : base(val, new SI().Radian.Per.Second) {} + + public static Watt operator *(RadianPerSecond radianPerSecond, NewtonMeter newtonMeter) + { + return ((radianPerSecond as SI) * newtonMeter).As<Watt>(); + } + } + + public class RoundsPerMinute : SIBase<RoundsPerMinute> + { + public RoundsPerMinute(double val = 0) : base(val, new SI().Rounds.Per.Minute) {} + } + + public class NewtonMeter : SIBase<NewtonMeter> + { + public NewtonMeter(double val = 0) : base(val, new SI().Newton.Meter) {} + + public static Watt operator *(NewtonMeter newtonMeter, RadianPerSecond radianPerSecond) + { + return ((newtonMeter as SI) * radianPerSecond).As<Watt>(); + } + + public static Second operator /(NewtonMeter newtonMeter, Watt watt) + { + return ((newtonMeter as SI) / watt).As<Second>(); + } + } + + public class Newton : SIBase<Newton> + { + public Newton(double val = 0) : base(val, new SI().Newton) {} + } + + public abstract class SIBase<T> : SI where T : SIBase<T> + { + protected SIBase(double val = 0) : base(val) {} + protected SIBase(double val, SI unit) : base(val, unit) {} + + #region Operators + + public static T operator +(SIBase<T> si1, SIBase<T> si2) + { + return (si1 as SI) + si2; + } + + public static T operator +(SIBase<T> si1, SI si2) + { + return ((si1 as SI) + si2).As<T>(); + } + + public static T operator +(SI si1, SIBase<T> si2) + { + return si2 + si1; + } + + public static T operator +(SIBase<T> si1, double d) + { + return ((si1 as SI) + d).As<T>(); + } + + public static T operator +(double d, SIBase<T> si) + { + return si + d; + } + + public static T operator -(SIBase<T> si1, SIBase<T> si2) + { + return (si1 as SI) - si2; + } + + public static T operator -(SIBase<T> si1, SI si2) + { + return -si2 + si1; + } + + public static T operator -(SI si1, SIBase<T> si2) + { + return (si1 - (si2 as SI)).As<T>(); + } + + public static T operator -(SIBase<T> si, double d) + { + return ((si as SI) - d).As<T>(); + } + + public static T operator -(double d, SIBase<T> si) + { + return (d - (si as SI)).As<T>(); + } + + public static T operator *(double d, SIBase<T> si) + { + return si * d; + } + + public static T operator *(SIBase<T> si, double d) + { + return ((si as SI) * d).As<T>(); + } + + public static T operator /(double d, SIBase<T> si) + { + return si / d; + } + + public static T operator /(SIBase<T> si, double d) + { + return ((si as SI) / d).As<T>(); + } + + #endregion + } + + + [DataContract] + public class SI + { + [DataMember] protected readonly string[] Denominator; + [DataMember] protected readonly int Exponent; + [DataMember] protected readonly string[] Numerator; + [DataMember] protected readonly bool Reciproc; + [DataMember] protected readonly bool Reverse; + [DataMember] protected readonly double Val; + + public SI(double val = 0.0) + { + Val = val; + Reciproc = false; + Reverse = false; + Numerator = new string[0]; + Denominator = new string[0]; + Exponent = 1; + } + + protected SI(double val, IEnumerable<string> numerator, IEnumerable<string> denominator, + bool reciproc = false, + bool reverse = false, int exponent = 1) + { + Contract.Requires(numerator != null); + Contract.Requires(denominator != null); + + Val = val; + Reciproc = reciproc; + Reverse = reverse; + Exponent = exponent; + + var tmpNumerator = numerator.ToList(); + var tmpDenominator = denominator.ToList(); + + foreach (var v in tmpDenominator.ToArray().Where(v => tmpNumerator.Contains(v))) { + tmpNumerator.Remove(v); + tmpDenominator.Remove(v); + } + + Numerator = tmpNumerator.ToArray(); + Denominator = tmpDenominator.ToArray(); + } + + 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, + bool? reciproc = null, bool? reverse = null, int? exponent = null) + { + Contract.Requires(si != null); + Contract.Requires(si.Denominator != null); + Contract.Requires(si.Numerator != null); + + var numerator = si.Denominator.ToList(); + var denominator = si.Numerator.ToList(); + + Val = si.Val; + Reciproc = reciproc ?? si.Reciproc; + Reverse = reverse ?? si.Reverse; + Exponent = exponent ?? si.Exponent; + + if (Reverse) { + var tmp = fromUnit; + fromUnit = toUnit; + toUnit = tmp; + factor = 1 / factor; + } + + for (var i = 0; i < Exponent; i++) { + if (!Reciproc) { + UpdateUnit(fromUnit, toUnit, denominator); + if (factor.HasValue) { + Val *= factor.Value; + } + } else { + UpdateUnit(fromUnit, toUnit, numerator); + if (factor.HasValue) { + Val /= factor.Value; + } + } + } + + foreach (var v in numerator.ToArray().Where(v => denominator.Contains(v))) { + denominator.Remove(v); + numerator.Remove(v); + } + + Numerator = denominator.ToArray(); + Denominator = numerator.ToArray(); + } + + private void UpdateUnit(string fromUnit, string toUnit, ICollection<string> units) + { + if (Reverse && !string.IsNullOrEmpty(fromUnit)) { + if (units.Contains(fromUnit)) { + units.Remove(fromUnit); + } else { + throw new VectoException("Unit missing. Conversion not possible."); + } + } + + if (!string.IsNullOrEmpty(toUnit)) { + units.Add(toUnit); + } + } + + /// <summary> + /// Converts the SI unit to another SI unit, defined by term(s) following after the To(). + /// The Conversion Mode is active until an arithmetic operator is used (+,-,*,/), + /// or the .Value-Method, or the .As-Method were called. + /// ATTENTION: Before returning an SI Unit, ensure to cancel Conversion Mode (with .Value or .As). + /// </summary> + /// <returns></returns> + public SI To() + { + return new SI(Linear, reciproc: false, reverse: true); + } + + /// <summary> + /// Casts the SI Unit to the concrete unit type if the units are correct. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <returns></returns> + public T As<T>() where T : SIBase<T> + { + var t = (T) Activator.CreateInstance(typeof (T), Val); + Contract.Assert(HasEqualUnit(t), string.Format("SI Unit Conversion failed: From {0} to {1}", this, t)); + return t; + } + + public SI ToBasicUnits() + { + var numerator = new List<string>(); + var denominator = new List<string>(); + 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) + { + 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"); + break; + case "N": + numerator.Add("k"); + numerator.Add("g"); + numerator.Add("m"); + denominator.Add("s"); + denominator.Add("s"); + break; + default: + numerator.Add(unit); + break; + } + } + + /// <summary> + /// Gets the basic scalar value. + /// </summary> + protected double ScalarValue() + { + return Val; + } + + public SI Value() + { + return new SI(Val, Numerator, Denominator); + } + + public SI Abs() + { + return new SI(Math.Abs(Val), this); + } + + #region Unit Definitions + + /// <summary> + /// Defines the denominator by the terms following after the Per. + /// </summary> + [DebuggerHidden] + public SI Per + { + get { return new SI(Linear, reciproc: !Reciproc); } + } + + /// <summary> + /// Takes all following terms as cubic terms (=to the power of 3). + /// </summary> + [DebuggerHidden] + public SI Cubic + { + get { return new SI(this, exponent: 3); } + } + + /// <summary> + /// Takes all following terms as quadratic terms (=to the power of 2). + /// </summary> + [DebuggerHidden] + public SI Square + { + get { return new SI(this, exponent: 2); } + } + + /// <summary> + /// Takes all following terms as linear terms (=to the power of 1). + /// </summary> + [DebuggerHidden] + public SI Linear + { + get { return new SI(this, exponent: 1); } + } + + /// <summary> + /// [g] (to basic unit: [kg]) + /// </summary> + [DebuggerHidden] + public SI Gramm + { + get { return new SI(new SI(this, toUnit: "k"), 0.001, "g", "g"); } + } + + /// <summary> + /// [N] + /// </summary> + [DebuggerHidden] + public SI Newton + { + get { return new SI(this, fromUnit: "N", toUnit: "N"); } + } + + /// <summary> + /// [W] + /// </summary> + [DebuggerHidden] + public SI Watt + { + get { return new SI(this, fromUnit: "W", toUnit: "W"); } + } + + /// <summary> + /// [m] + /// </summary> + [DebuggerHidden] + public SI Meter + { + get { return new SI(this, fromUnit: "m", toUnit: "m"); } + } + + /// <summary> + /// [s] + /// </summary> + [DebuggerHidden] + public SI Second + { + get { return new SI(this, fromUnit: "s", toUnit: "s"); } + } + + /// <summary> + /// [rad] + /// </summary> + [DebuggerHidden] + public SI Radian + { + get { return new SI(this, fromUnit: "rad", toUnit: "rad"); } + } + + public SI GradientPercent + { + get { return new SI(this, factor: Math.Atan(Val) / Val, fromUnit: "%", toUnit: "rad"); } + } + + /// <summary> + /// Converts to/from Radiant + /// </summary> + [DebuggerHidden] + public SI Rounds + { + get { return new SI(this, 2 * Math.PI, toUnit: "rad"); } + } + + /// <summary> + /// Converts to/from Second + /// </summary> + [DebuggerHidden] + public SI Hour + { + get { return new SI(this, 3600.0, "h", "s"); } + } + + /// <summary> + /// Converts to/from Second + /// </summary> + [DebuggerHidden] + public SI Minute + { + get { return new SI(this, 60.0, "min", "s"); } + } + + /// <summary> + /// Converts to/from 1000 * Basic Unit + /// </summary> + [DebuggerHidden] + public SI Kilo + { + get { return new SI(this, 1000.0, "k"); } + } + + /// <summary> + /// Converts to/from Basic Unit / 100 + /// </summary> + [DebuggerHidden] + public SI Centi + { + get { return new SI(this, 1.0 / 100.0, "c"); } + } + + #endregion + + #region Operators + + public static SI operator +(SI si1, SI si2) + { + Contract.Requires(si1.HasEqualUnit(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)); + + 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"); + 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"); + return new SI(si1.Val / si2.Val, numerator, denominator); + } + + public static SI operator +(SI si1, double d) + { + 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); + } + + public static SI operator *(double d, SI si1) + { + return new SI(d * si1.Val, si1); + } + + 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 bool operator <(SI si1, SI si2) + { + Contract.Requires(si1.HasEqualUnit(si2)); + return si1.Val < si2.Val; + } + + public static bool operator >(SI si1, SI si2) + { + Contract.Requires(si1.HasEqualUnit(si2)); + return si1.Val > si2.Val; + } + + public static bool operator <=(SI si1, SI si2) + { + Contract.Requires(si1.HasEqualUnit(si2)); + return si1.Val <= si2.Val; + } + + public static bool operator >=(SI si1, SI si2) + { + Contract.Requires(si1.HasEqualUnit(si2)); + return si1.Val >= si2.Val; + } + + public static bool operator <(SI si1, double d) + { + return si1.Val < d; + } + + public static bool operator >(SI si1, double d) + { + return si1.Val > d; + } + + public static bool operator <=(SI si1, double d) + { + return si1.Val <= d; + } + + public static bool operator >=(SI si1, double d) + { + return si1.Val >= d; + } + + #endregion + + #region Double Conversion + + /// <summary> + /// Casts an SI Unit to an double. + /// </summary> + /// <param name="si"></param> + /// <returns></returns> + public static explicit operator double(SI si) + { + return si.Val; + } + + /// <summary> + /// Casts a double to an SI Unit. + /// </summary> + /// <param name="d"></param> + /// <returns></returns> + public static explicit operator SI(double d) + { + return new SI(d); + } + + #endregion + + #region ToString + + /// <summary> + /// Returns the Unit Part of the SI Unit Expression. + /// </summary> + private string GetUnitString() + { + if (Denominator.Any()) { + if (Numerator.Any()) { + return string.Format("{0}/{1}", string.Join("", Numerator), string.Join("", Denominator)); + } else { + return string.Format("1/{0}", string.Join("", Denominator)); + } + } + + if (Numerator.Any()) { + return string.Format("{0}", string.Join("", Numerator)); + } + + return "-"; + } + + /// <summary> + /// Returns the String representation. + /// </summary> + public override string ToString() + { + return string.Format("{0} [{1}]", Val, GetUnitString()); + } + + #endregion + + #region Equality members + + /// <summary> + /// Compares the Unit-Parts of two SI Units. + /// </summary> + [Pure] + public bool HasEqualUnit(SI si) + { + return ToBasicUnits() + .Denominator.OrderBy(x => x) + .SequenceEqual(si.ToBasicUnits().Denominator.OrderBy(x => x)) + && + 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)) { + return false; + } + if (ReferenceEquals(this, obj)) { + return true; + } + var other = obj as SI; + return other != null && Equals(other); + } + + public override int GetHashCode() + { + unchecked { + var hashCode = Val.GetHashCode(); + hashCode = (hashCode * 397) ^ (Numerator != null ? Numerator.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Denominator != null ? Denominator.GetHashCode() : 0); + return hashCode; + } + } + + public static bool operator ==(SI left, SI right) + { + return Equals(left, right); + } + + public static bool operator !=(SI left, SI right) + { + return !Equals(left, right); + } + + #endregion + } } \ No newline at end of file diff --git a/VectoCoreTest/Models/Simulation/DrivingCycleTests.cs b/VectoCoreTest/Models/Simulation/DrivingCycleTests.cs index fed1acc518e77191ac550ada18debb9d8436c498..b5ce5b48aaf2a77b18e592388a51d2d0ecd35186 100644 --- a/VectoCoreTest/Models/Simulation/DrivingCycleTests.cs +++ b/VectoCoreTest/Models/Simulation/DrivingCycleTests.cs @@ -66,7 +66,7 @@ namespace TUGraz.VectoCore.Tests.Models.Simulation Assert.AreEqual(absTime, outPort.AbsTime); Assert.AreEqual(dt, outPort.Dt); Assert.AreEqual(0.0.SI<MeterPerSecond>(), outPort.Velocity); - Assert.AreEqual((-0.020237973).SI().GradientPercent.To<Radian>(), outPort.Gradient); + Assert.AreEqual((-0.020237973).SI().GradientPercent.As<Radian>(), outPort.Gradient); } [TestMethod] diff --git a/VectoCoreTest/Models/SimulationComponentData/FuelConsumptionMapTest.cs b/VectoCoreTest/Models/SimulationComponentData/FuelConsumptionMapTest.cs index 5061ea58ae918e3592f0fe39222cae9914ede78c..b8de40ea4edef01fff7c11d74b20cb5b16e5a5e1 100644 --- a/VectoCoreTest/Models/SimulationComponentData/FuelConsumptionMapTest.cs +++ b/VectoCoreTest/Models/SimulationComponentData/FuelConsumptionMapTest.cs @@ -8,41 +8,41 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Tests.Models.SimulationComponentData { - [TestClass] - public class FuelConsumptionMapTest - { - private const double Tolerance = 0.0001; + [TestClass] + public class FuelConsumptionMapTest + { + private const double Tolerance = 0.0001; - [TestMethod] - public void TestFuelConsumption_FixedPoints() - { - var map = FuelConsumptionMap.ReadFromFile(@"TestData\Components\24t Coach.vmap"); - var lines = File.ReadAllLines(@"TestData\Components\24t Coach.vmap").Skip(1).ToArray(); - AssertMapValuesEqual(lines, map); - } + [TestMethod] + public void TestFuelConsumption_FixedPoints() + { + var map = FuelConsumptionMap.ReadFromFile(@"TestData\Components\24t Coach.vmap"); + var lines = File.ReadAllLines(@"TestData\Components\24t Coach.vmap").Skip(1).ToArray(); + AssertMapValuesEqual(lines, map); + } - [TestMethod] - public void TestFuelConsumption_InterpolatedPoints() - { - var map = FuelConsumptionMap.ReadFromFile(@"TestData\Components\24t Coach.vmap"); - var lines = File.ReadAllLines(@"TestData\Components\24t CoachInterpolated.vmap").Skip(1).ToArray(); - AssertMapValuesEqual(lines, map); - } + [TestMethod] + public void TestFuelConsumption_InterpolatedPoints() + { + var map = FuelConsumptionMap.ReadFromFile(@"TestData\Components\24t Coach.vmap"); + var lines = File.ReadAllLines(@"TestData\Components\24t CoachInterpolated.vmap").Skip(1).ToArray(); + AssertMapValuesEqual(lines, map); + } - private static void AssertMapValuesEqual(string[] lines, FuelConsumptionMap map) - { - for (var i = 1; i < lines.Count(); i++) { - var entry = lines[i].Split(',').Select(x => double.Parse(x, CultureInfo.InvariantCulture)).ToArray(); - try { - Assert.AreEqual((double) entry[2].SI().Gramm.Per.Hour.To().Kilo.Gramm.Per.Second, - (double) map.GetFuelConsumption(entry[1].SI<NewtonMeter>(), entry[0].RPMtoRad()), - Tolerance, - string.Format("Line: {0}, n={1}, T={2}", (i + 2), entry[0].SI().Rounds.Per.Minute, entry[1])); - } catch (VectoException ex) { - throw new VectoException(string.Format("Row {0}: Error in ConsumptionMap n={1}, T={2}: {3}", - i + 2, entry[0], entry[1], ex.Message)); - } - } - } - } + private static void AssertMapValuesEqual(string[] lines, FuelConsumptionMap map) + { + for (var i = 1; i < lines.Count(); i++) { + var entry = lines[i].Split(',').Select(x => double.Parse(x, CultureInfo.InvariantCulture)).ToArray(); + try { + Assert.AreEqual((double) entry[2].SI().Gramm.Per.Hour.To().Kilo.Gramm.Per.Second, + (double) map.GetFuelConsumption(entry[1].SI<NewtonMeter>(), entry[0].RPMtoRad()), + Tolerance, + string.Format("Line: {0}, n={1}, T={2}", (i + 2), entry[0].SI().Rounds.Per.Minute, entry[1])); + } catch (VectoException ex) { + throw new VectoException(string.Format("Row {0}: Error in ConsumptionMap n={1}, T={2}: {3}", + i + 2, entry[0], entry[1], ex.Message)); + } + } + } + } } \ No newline at end of file diff --git a/VectoCoreTest/Utils/SITest.cs b/VectoCoreTest/Utils/SITest.cs index 05e6394e4fb702ef27e9da163f3c07853c9f9a45..23362db676a402f64eb71d7ccd4f5f6d65601b81 100644 --- a/VectoCoreTest/Utils/SITest.cs +++ b/VectoCoreTest/Utils/SITest.cs @@ -4,65 +4,65 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Tests.Utils { - [TestClass] - public class SITest - { - public static void AssertException<T>(Action func, string message) where T : Exception - { - try { - func(); - Assert.Fail(); - } catch (T ex) { - Assert.AreEqual(message, ex.Message); - } - } + [TestClass] + public class SITest + { + public static void AssertException<T>(Action func, string message) where T : Exception + { + try { + func(); + Assert.Fail(); + } catch (T ex) { + Assert.AreEqual(message, ex.Message); + } + } - [TestMethod] - public void TestSI() - { - var si = new SI(); - Assert.AreEqual(0.0, (double) si); - Assert.AreEqual("0 [-]", si.ToString()); - Assert.IsTrue(si.HasEqualUnit(new SI())); + [TestMethod] + public void TestSI() + { + var si = new SI(); + Assert.AreEqual(0.0, (double) si); + Assert.AreEqual("0 [-]", si.ToString()); + Assert.IsTrue(si.HasEqualUnit(new SI())); - var si2 = 5.0.SI().Watt; - Assert.AreEqual("5 [W]", si2.ToString()); + var si2 = 5.0.SI().Watt; + Assert.AreEqual("5 [W]", si2.ToString()); - var si3 = 2.SI().Radian.Per.Second; - Assert.AreEqual("2 [rad/s]", si3.ToString()); + var si3 = 2.SI().Radian.Per.Second; + Assert.AreEqual("2 [rad/s]", si3.ToString()); - var si4 = si2 * si3; - Assert.AreEqual("10 [W/s]", si4.ToString()); - Assert.IsTrue(si4.HasEqualUnit(new SI().Watt.Per.Second)); - Assert.AreEqual("10 [kgmm/ssss]", si4.ToBasicUnits().ToString()); + var si4 = si2 * si3; + Assert.AreEqual("10 [W/s]", si4.ToString()); + Assert.IsTrue(si4.HasEqualUnit(new SI().Watt.Per.Second)); + Assert.AreEqual("10 [kgmm/ssss]", si4.ToBasicUnits().ToString()); - var kg = 5.0.SI().Kilo.Gramm; - Assert.AreEqual(5.0, (double) kg); - Assert.AreEqual("5 [kg]", kg.ToString()); + var kg = 5.0.SI().Kilo.Gramm; + Assert.AreEqual(5.0, (double) kg); + Assert.AreEqual("5 [kg]", kg.ToString()); - kg = kg.To().Kilo.Gramm.Value(); - Assert.AreEqual(5.0, (double) kg); - Assert.AreEqual("5 [kg]", kg.ToString()); + kg = kg.To().Kilo.Gramm.Value(); + Assert.AreEqual(5.0, (double) kg); + Assert.AreEqual("5 [kg]", kg.ToString()); - kg = kg.To().Gramm.Value(); - Assert.AreEqual(5000, (double) kg); - Assert.AreEqual("5000 [g]", kg.ToString()); + kg = kg.To().Gramm.Value(); + Assert.AreEqual(5000, (double) kg); + Assert.AreEqual("5000 [g]", kg.ToString()); - var x = 5.SI(); - Assert.AreEqual((2.0 / 5.0).SI(), 2 / x); - Assert.AreEqual((5.0 / 2.0).SI(), x / 2); - Assert.AreEqual((2.0 * 5.0).SI(), 2 * x); - Assert.AreEqual((5.0 * 2.0).SI(), x * 2); + var x = 5.SI(); + Assert.AreEqual((2.0 / 5.0).SI(), 2 / x); + Assert.AreEqual((5.0 / 2.0).SI(), x / 2); + Assert.AreEqual((2.0 * 5.0).SI(), 2 * x); + Assert.AreEqual((5.0 * 2.0).SI(), x * 2); - Assert.AreEqual((2.0 / 5.0).SI(), 2.0 / x); - Assert.AreEqual((5.0 / 2.0).SI(), x / 2.0); - Assert.AreEqual((2 * 5).SI(), 2.0 * x); - Assert.AreEqual((5 * 2).SI(), x * 2.0); + Assert.AreEqual((2.0 / 5.0).SI(), 2.0 / x); + Assert.AreEqual((5.0 / 2.0).SI(), x / 2.0); + Assert.AreEqual((2 * 5).SI(), 2.0 * x); + Assert.AreEqual((5 * 2).SI(), x * 2.0); - var y = 2.SI(); - Assert.AreEqual((2 * 5).SI(), y * x); - } - } + var y = 2.SI(); + Assert.AreEqual((2 * 5).SI(), y * x); + } + } } \ No newline at end of file