diff --git a/VECTO.sln.DotSettings b/VECTO.sln.DotSettings index 79c8369e0b13181e7fdbd97067486f05854bd6ca..fbc71d3de43c9322a1724330c64857302970e1f1 100644 --- a/VECTO.sln.DotSettings +++ b/VECTO.sln.DotSettings @@ -19,4 +19,6 @@ <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></wpf:ResourceDictionary> \ No newline at end of file + <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 diff --git a/VectoCore/Models/Connector/Ports/ITnPort.cs b/VectoCore/Models/Connector/Ports/ITnPort.cs index 0d6ddba5b82b143d8d717d83c0dac6d754e51f96..f3df1dd0c8f1a4d839150851878c5068618e3450 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, PerSecond angularVelocity); + IResponse Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, RadianPerSecond angularVelocity); } } \ No newline at end of file diff --git a/VectoCore/Models/Simulation/Cockpit/IEngineCockpit.cs b/VectoCore/Models/Simulation/Cockpit/IEngineCockpit.cs index 828079c77468bb697f00d873955f370f93177b51..75f2ba1f12e2aca17414c3eda758019901c68d23 100644 --- a/VectoCore/Models/Simulation/Cockpit/IEngineCockpit.cs +++ b/VectoCore/Models/Simulation/Cockpit/IEngineCockpit.cs @@ -2,14 +2,14 @@ namespace TUGraz.VectoCore.Models.Simulation.Cockpit { - /// <summary> - /// Defines a method to access shared data of the engine. - /// </summary> - public interface IEngineCockpit - { - /// <summary> - /// [rad/s] The current engine speed. - /// </summary> - PerSecond EngineSpeed(); - } + /// <summary> + /// Defines a method to access shared data of the engine. + /// </summary> + public interface IEngineCockpit + { + /// <summary> + /// [rad/s] The current engine speed. + /// </summary> + RadianPerSecond EngineSpeed(); + } } \ No newline at end of file diff --git a/VectoCore/Models/Simulation/Impl/VehicleContainer.cs b/VectoCore/Models/Simulation/Impl/VehicleContainer.cs index d7e9e8a43adc91aaee73e80ddb7e1f49f637e223..f47865ef3c23343b9c8a4165c93dd5dac246ef01 100644 --- a/VectoCore/Models/Simulation/Impl/VehicleContainer.cs +++ b/VectoCore/Models/Simulation/Impl/VehicleContainer.cs @@ -9,84 +9,84 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Models.Simulation.Impl { - public class VehicleContainer : IVehicleContainer - { - private readonly IList<VectoSimulationComponent> _components = new List<VectoSimulationComponent>(); - private IEngineCockpit _engine; - private IGearboxCockpit _gearbox; - - #region IGearCockpit - - public uint Gear() - { - if (_gearbox == null) { - throw new VectoException("no gearbox available!"); - } - return _gearbox.Gear(); - } - - #endregion - - #region IEngineCockpit - - public PerSecond EngineSpeed() - { - if (_engine == null) { - throw new VectoException("no engine available!"); - } - return _engine.EngineSpeed(); - } - - #endregion - - #region IVehicleCockpit - - public MeterPerSecond VehicleSpeed() - { - throw new VectoException("no vehicle available!"); - } - - #endregion - - #region IVehicleContainer - - public virtual void AddComponent(VectoSimulationComponent component) - { - _components.Add(component); - - // TODO: refactor the following to use polymorphism? - var engine = component as IEngineCockpit; - if (engine != null) { - _engine = engine; - } - - var gearbox = component as IGearboxCockpit; - if (gearbox != null) { - _gearbox = gearbox; - } - } - - - public void CommitSimulationStep(IModalDataWriter dataWriter) - { - LogManager.GetLogger(GetType()).Info("VehicleContainer committing simulation."); - foreach (var component in _components) { - component.CommitSimulationStep(dataWriter); - } - dataWriter.CommitSimulationStep(); - } - - public void FinishSimulation(IModalDataWriter dataWriter) - { - LogManager.GetLogger(GetType()).Info("VehicleContainer finishing simulation."); - dataWriter.Finish(); - } - - #endregion - - public IReadOnlyCollection<VectoSimulationComponent> SimulationComponents() - { - return new ReadOnlyCollection<VectoSimulationComponent>(_components); - } - } + public class VehicleContainer : IVehicleContainer + { + private readonly IList<VectoSimulationComponent> _components = new List<VectoSimulationComponent>(); + private IEngineCockpit _engine; + private IGearboxCockpit _gearbox; + + #region IGearCockpit + + public uint Gear() + { + if (_gearbox == null) { + throw new VectoException("no gearbox available!"); + } + return _gearbox.Gear(); + } + + #endregion + + #region IEngineCockpit + + public RadianPerSecond EngineSpeed() + { + if (_engine == null) { + throw new VectoException("no engine available!"); + } + return _engine.EngineSpeed(); + } + + #endregion + + #region IVehicleCockpit + + public MeterPerSecond VehicleSpeed() + { + throw new VectoException("no vehicle available!"); + } + + #endregion + + #region IVehicleContainer + + public virtual void AddComponent(VectoSimulationComponent component) + { + _components.Add(component); + + // TODO: refactor the following to use polymorphism? + var engine = component as IEngineCockpit; + if (engine != null) { + _engine = engine; + } + + var gearbox = component as IGearboxCockpit; + if (gearbox != null) { + _gearbox = gearbox; + } + } + + + public void CommitSimulationStep(IModalDataWriter dataWriter) + { + LogManager.GetLogger(GetType()).Info("VehicleContainer committing simulation."); + foreach (var component in _components) { + component.CommitSimulationStep(dataWriter); + } + dataWriter.CommitSimulationStep(); + } + + public void FinishSimulation(IModalDataWriter dataWriter) + { + LogManager.GetLogger(GetType()).Info("VehicleContainer finishing simulation."); + dataWriter.Finish(); + } + + #endregion + + public IReadOnlyCollection<VectoSimulationComponent> SimulationComponents() + { + return new ReadOnlyCollection<VectoSimulationComponent>(_components); + } + } } \ No newline at end of file diff --git a/VectoCore/Models/SimulationComponent/Data/CombustionEngineData.cs b/VectoCore/Models/SimulationComponent/Data/CombustionEngineData.cs index e0f9106ea247a01282b08fea7d54c4019edd560a..9bc5d911a11f09b91e8da0ef2cca7430cc4a6f9c 100644 --- a/VectoCore/Models/SimulationComponent/Data/CombustionEngineData.cs +++ b/VectoCore/Models/SimulationComponent/Data/CombustionEngineData.cs @@ -13,499 +13,496 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Models.SimulationComponent.Data { - /// <summary> - /// Represents the CombustionEngineData. Fileformat: .veng - /// </summary> - /// <code> - /// { - /// "Header": { - /// "CreatedBy": " ()", - /// "Date": "3/4/2015 12:26:24 PM", - /// "AppVersion": "2.0.4-beta3", - /// "FileVersion": 2 - /// }, - /// "Body": { - /// "SavedInDeclMode": false, - /// "ModelName": "Generic 24t Coach", - /// "Displacement": 12730.0, - /// "IdlingSpeed": 560.0, - /// "Inertia": 3.8, - /// "FullLoadCurves": [ - /// { - /// "Path": "24t Coach.vfld", - /// "Gears": "0 - 99" - /// } - /// ], - /// "FuelMap": "24t Coach.vmap", - /// "WHTC-Urban": 0.0, - /// "WHTC-Rural": 0.0, - /// "WHTC-Motorway": 0.0 - /// } - /// } - /// </code> - [DataContract] - public class CombustionEngineData : SimulationComponentData - { - [DataMember] private readonly Dictionary<Range, FullLoadCurve> _fullLoadCurves = - new Dictionary<Range, FullLoadCurve>(); - - [DataMember] private Data _data; - - public bool SavedInDeclarationMode - { - get { return _data.Body.SavedInDeclarationMode; } - protected set { _data.Body.SavedInDeclarationMode = value; } - } - - public string ModelName - { - get { return _data.Body.ModelName; } - protected set { _data.Body.ModelName = value; } - } - - /// <summary> - /// [m^3] - /// </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; } - } - - /// <summary> - /// [rad/s] - /// </summary> - public PerSecond IdleSpeed - { - get { return _data.Body.IdleSpeed.SI().Rounds.Per.Minute.To<PerSecond>(); } - protected set { _data.Body.IdleSpeed = (double) value.To().Rounds.Per.Minute; } - } - - /// <summary> - /// [kgm^2] - /// </summary> - 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; } - } - - /// <summary> - /// [kg/Ws] - /// </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; } - } - - /// <summary> - /// [kg/Ws] - /// </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; } - } - - /// <summary> - /// [g/Ws] - /// </summary> - 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; } - } - - [DataMember] - public FuelConsumptionMap ConsumptionMap { get; set; } - - public static CombustionEngineData ReadFromFile(string fileName) - { - return ReadFromJson(File.ReadAllText(fileName), Path.GetDirectoryName(fileName)); - } - - public static CombustionEngineData ReadFromJson(string json, string basePath = "") - { - var combustionEngineData = new CombustionEngineData(); - //todo handle conversion errors - var d = JsonConvert.DeserializeObject<Data>(json); - - combustionEngineData._data = d; - - if (d.Header.FileVersion > 2) { - throw new UnsupportedFileVersionException("Unsupported Version of .veng file. Got Version: " + - d.Header.FileVersion); - } - - combustionEngineData.ConsumptionMap = FuelConsumptionMap.ReadFromFile(Path.Combine(basePath, d.Body.FuelMap)); - - foreach (var loadCurve in d.Body.FullLoadCurves) { - var fullLoadCurve = FullLoadCurve.ReadFromFile(Path.Combine(basePath, loadCurve.Path)); - var gearRange = new Range(loadCurve.Gears); - combustionEngineData._fullLoadCurves[gearRange] = fullLoadCurve; - } - - return combustionEngineData; - } - - public void WriteToFile(string fileName) - { - //todo handle file exceptions - File.WriteAllText(fileName, ToJson()); - } - - public string ToJson() - { - _data.Header.Date = DateTime.Now; - _data.Header.FileVersion = 2; - _data.Header.AppVersion = "3.0.0"; // todo: get current app version! - _data.Header.CreatedBy = ""; // todo: get current user - _data.Body.SavedInDeclarationMode = false; //todo: get declaration mode setting - return JsonConvert.SerializeObject(_data, Formatting.Indented); - } - - public FullLoadCurve GetFullLoadCurve(uint gear) - { - var curve = _fullLoadCurves.FirstOrDefault(kv => kv.Key.Contains(gear)); - if (curve.Key.Equals(null)) { - throw new KeyNotFoundException(string.Format("Gear '{0}' was not found in the FullLoadCurves.", gear)); - } - - return curve.Value; - } - - /// <summary> - /// A class which represents the json data format for serializing and deserializing the EngineData files. - /// </summary> - public class Data - { - [JsonProperty(Required = Required.Always)] public DataBody Body; - [JsonProperty(Required = Required.Always)] public DataHeader Header; - - public class DataHeader - { - [JsonProperty(Required = Required.Always)] public string AppVersion; - [JsonProperty(Required = Required.Always)] public string CreatedBy; - [JsonProperty(Required = Required.Always)] public DateTime Date; - [JsonProperty(Required = Required.Always)] public double FileVersion; - - #region Equality members - - protected bool Equals(DataHeader other) - { - return string.Equals(CreatedBy, other.CreatedBy) && Date.Equals(other.Date) && - string.Equals(AppVersion, other.AppVersion) && FileVersion.Equals(other.FileVersion); - } - - 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((DataHeader) obj); - } - - public override int GetHashCode() - { - unchecked { - var hashCode = (CreatedBy != null ? CreatedBy.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ Date.GetHashCode(); - hashCode = (hashCode * 397) ^ (AppVersion != null ? AppVersion.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ FileVersion.GetHashCode(); - return hashCode; - } - } - - #endregion - } - - public class DataBody - { - /// <summary> - /// [ccm] Displacement in cubic centimeter. - /// Used in Declaration Mode to calculate inertia. - /// </summary> - [JsonProperty(Required = Required.Always)] public double Displacement; - - /// <summary> - /// The Fuel Consumption Map is used to calculate the base Fuel Consumption (FC) value. - /// </summary> - [JsonProperty(Required = Required.Always)] public string FuelMap; - - [JsonProperty(Required = Required.Always)] public IList<DataFullLoadCurve> FullLoadCurves; - - /// <summary> - /// [rpm] Idling Engine Speed - /// Low idle, applied in simulation for vehicle standstill in neutral gear position. - /// </summary> - [JsonProperty("IdlingSpeed", Required = Required.Always)] public double IdleSpeed; - - /// <summary> - /// [kgm^2] Inertia including Flywheel - /// Inertia for rotating parts including engine flywheel. - /// In Declaration Mode the inertia is calculated automatically. - /// </summary> - [JsonProperty(Required = Required.Always)] public double Inertia; - - /// <summary> - /// Model. Free text defining the engine model, type, etc. - /// </summary> - [JsonProperty(Required = Required.Always)] public string ModelName; - - [JsonProperty("SavedInDeclMode")] public bool SavedInDeclarationMode; - - /// <summary> - /// [g/kWh] The WHTC test results are required in Declaration Mode for the motorway WHTC FC Correction. - /// </summary> - [JsonProperty("WHTC-Motorway")] public double WHTCMotorway; - - /// <summary> - /// [g/kWh] The WHTC test results are required in Declaration Mode for the rural WHTC FC Correction. - /// </summary> - [JsonProperty("WHTC-Rural")] public double WHTCRural; - - /// <summary> - /// [g/kWh] The WHTC test results are required in Declaration Mode for the urban WHTC FC Correction. - /// </summary> - [JsonProperty("WHTC-Urban")] public double WHTCUrban; - - /// <summary> - /// Multiple Full Load and Drag Curves (.vfld) can be defined and assigned to different gears. - /// Gear "0" must be assigned for idling and Engine Only Mode. - /// </summary> - public class DataFullLoadCurve - { - [JsonProperty(Required = Required.Always)] public string Gears; - [JsonProperty(Required = Required.Always)] public string Path; - - #region Equality Members - - protected bool Equals(DataFullLoadCurve other) - { - return string.Equals(Path, other.Path) && string.Equals(Gears, other.Gears); - } - - 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((DataFullLoadCurve) obj); - } - - public override int GetHashCode() - { - unchecked { - return ((Path != null ? Path.GetHashCode() : 0) * 397) ^ - (Gears != null ? Gears.GetHashCode() : 0); - } - } - - #endregion - } - - #region Equality members - - protected bool Equals(DataBody other) - { - return SavedInDeclarationMode.Equals(other.SavedInDeclarationMode) - && string.Equals(ModelName, other.ModelName) - && Displacement.Equals(other.Displacement) - && IdleSpeed.Equals(other.IdleSpeed) - && Inertia.Equals(other.Inertia) - && FullLoadCurves.SequenceEqual(other.FullLoadCurves) - && string.Equals(FuelMap, other.FuelMap) - && WHTCUrban.Equals(other.WHTCUrban) - && WHTCRural.Equals(other.WHTCRural) - && WHTCMotorway.Equals(other.WHTCMotorway); - } - - 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((DataBody) obj); - } - - public override int GetHashCode() - { - unchecked { - var hashCode = SavedInDeclarationMode.GetHashCode(); - hashCode = (hashCode * 397) ^ (ModelName != null ? ModelName.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ Displacement.GetHashCode(); - hashCode = (hashCode * 397) ^ IdleSpeed.GetHashCode(); - hashCode = (hashCode * 397) ^ Inertia.GetHashCode(); - hashCode = (hashCode * 397) ^ (FullLoadCurves != null ? FullLoadCurves.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (FuelMap != null ? FuelMap.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ WHTCUrban.GetHashCode(); - hashCode = (hashCode * 397) ^ WHTCRural.GetHashCode(); - hashCode = (hashCode * 397) ^ WHTCMotorway.GetHashCode(); - return hashCode; - } - } - - #endregion - } - - #region Equality members - - protected bool Equals(Data other) - { - return Equals(Header, other.Header) && Equals(Body, other.Body); - } - - 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((Data) obj); - } - - public override int GetHashCode() - { - unchecked { - return ((Header != null ? Header.GetHashCode() : 0) * 397) ^ (Body != null ? Body.GetHashCode() : 0); - } - } - - #endregion - } - - public class RangeConverter : TypeConverter - { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - return sourceType == typeof (string) || base.CanConvertFrom(context, sourceType); - } - - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) - { - return value.GetType() == typeof (string) - ? new Range((string) value) - : base.ConvertFrom(context, culture, value); - } - } - - [TypeConverter(typeof (RangeConverter))] - private class Range - { - private readonly uint _end; - private readonly uint _start; - - public Range(string range) - { - Contract.Requires(range != null); - - _start = uint.Parse(range.Split('-').First().Trim()); - _end = uint.Parse(range.Split('-').Last().Trim()); - } - - public override string ToString() - { - return string.Format("{0} - {1}", _start, _end); - } - - public bool Contains(uint value) - { - return _start <= value && value <= _end; - } - - #region Equality members - - protected bool Equals(Range other) - { - Contract.Requires(other != null); - return _start == other._start && _end == other._end; - } - - 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((Range) obj); - } - - public override int GetHashCode() - { - unchecked { - return (int) ((_start * 397) ^ _end); - } - } - - #endregion - } - - #region Equality members - - protected bool Equals(CombustionEngineData other) - { - return Equals(_data, other._data) - && _fullLoadCurves.Keys.SequenceEqual(other._fullLoadCurves.Keys) - && _fullLoadCurves.Values.SequenceEqual(other._fullLoadCurves.Values) - && Equals(ConsumptionMap, other.ConsumptionMap); - } - - 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((CombustionEngineData) obj); - } - - public override int GetHashCode() - { - unchecked { - var hashCode = _data.GetHashCode(); - hashCode = (hashCode * 397) ^ (_fullLoadCurves != null ? _fullLoadCurves.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (ConsumptionMap != null ? ConsumptionMap.GetHashCode() : 0); - return hashCode; - } - } - - #endregion - } + /// <summary> + /// Represents the CombustionEngineData. Fileformat: .veng + /// </summary> + /// <code> + /// { + /// "Header": { + /// "CreatedBy": " ()", + /// "Date": "3/4/2015 12:26:24 PM", + /// "AppVersion": "2.0.4-beta3", + /// "FileVersion": 2 + /// }, + /// "Body": { + /// "SavedInDeclMode": false, + /// "ModelName": "Generic 24t Coach", + /// "Displacement": 12730.0, + /// "IdlingSpeed": 560.0, + /// "Inertia": 3.8, + /// "FullLoadCurves": [ + /// { + /// "Path": "24t Coach.vfld", + /// "Gears": "0 - 99" + /// } + /// ], + /// "FuelMap": "24t Coach.vmap", + /// "WHTC-Urban": 0.0, + /// "WHTC-Rural": 0.0, + /// "WHTC-Motorway": 0.0 + /// } + /// } + /// </code> + [DataContract] + public class CombustionEngineData : SimulationComponentData + { + [DataMember] private readonly Dictionary<Range, FullLoadCurve> _fullLoadCurves = + new Dictionary<Range, FullLoadCurve>(); + + [DataMember] private Data _data; + + public bool SavedInDeclarationMode + { + get { return _data.Body.SavedInDeclarationMode; } + protected set { _data.Body.SavedInDeclarationMode = value; } + } + + public string ModelName + { + get { return _data.Body.ModelName; } + protected set { _data.Body.ModelName = value; } + } + + /// <summary> + /// [m^3] + /// </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; } + } + + /// <summary> + /// [rad/s] + /// </summary> + public RadianPerSecond IdleSpeed + { + get { return _data.Body.IdleSpeed.SI().Rounds.Per.Minute.To<RadianPerSecond>(); } + protected set { _data.Body.IdleSpeed = (double) value.To().Rounds.Per.Minute; } + } + + /// <summary> + /// [kgm^2] + /// </summary> + 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; } + } + + /// <summary> + /// [kg/Ws] + /// </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; } + } + + /// <summary> + /// [kg/Ws] + /// </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; } + } + + /// <summary> + /// [g/Ws] + /// </summary> + 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; } + } + + [DataMember] + public FuelConsumptionMap ConsumptionMap { get; set; } + + public static CombustionEngineData ReadFromFile(string fileName) + { + return ReadFromJson(File.ReadAllText(fileName), Path.GetDirectoryName(fileName)); + } + + public static CombustionEngineData ReadFromJson(string json, string basePath = "") + { + var combustionEngineData = new CombustionEngineData(); + //todo handle conversion errors + var d = JsonConvert.DeserializeObject<Data>(json); + + combustionEngineData._data = d; + + if (d.Header.FileVersion > 2) { + throw new UnsupportedFileVersionException("Unsupported Version of .veng file. Got Version: " + + d.Header.FileVersion); + } + + combustionEngineData.ConsumptionMap = FuelConsumptionMap.ReadFromFile(Path.Combine(basePath, d.Body.FuelMap)); + + foreach (var loadCurve in d.Body.FullLoadCurves) { + var fullLoadCurve = FullLoadCurve.ReadFromFile(Path.Combine(basePath, loadCurve.Path)); + var gearRange = new Range(loadCurve.Gears); + combustionEngineData._fullLoadCurves[gearRange] = fullLoadCurve; + } + + return combustionEngineData; + } + + public void WriteToFile(string fileName) + { + //todo handle file exceptions + File.WriteAllText(fileName, ToJson()); + } + + public string ToJson() + { + _data.Header.Date = DateTime.Now; + _data.Header.FileVersion = 2; + _data.Header.AppVersion = "3.0.0"; // todo: get current app version! + _data.Header.CreatedBy = ""; // todo: get current user + _data.Body.SavedInDeclarationMode = false; //todo: get declaration mode setting + return JsonConvert.SerializeObject(_data, Formatting.Indented); + } + + public FullLoadCurve GetFullLoadCurve(uint gear) + { + var curve = _fullLoadCurves.FirstOrDefault(kv => kv.Key.Contains(gear)); + if (curve.Key.Equals(null)) { + throw new KeyNotFoundException(string.Format("Gear '{0}' was not found in the FullLoadCurves.", gear)); + } + + return curve.Value; + } + + /// <summary> + /// A class which represents the json data format for serializing and deserializing the EngineData files. + /// </summary> + public class Data + { + [JsonProperty(Required = Required.Always)] public DataBody Body; + [JsonProperty(Required = Required.Always)] public DataHeader Header; + + public class DataHeader + { + [JsonProperty(Required = Required.Always)] public string AppVersion; + [JsonProperty(Required = Required.Always)] public string CreatedBy; + [JsonProperty(Required = Required.Always)] public DateTime Date; + [JsonProperty(Required = Required.Always)] public double FileVersion; + + #region Equality members + + protected bool Equals(DataHeader other) + { + return string.Equals(CreatedBy, other.CreatedBy) && Date.Equals(other.Date) && + string.Equals(AppVersion, other.AppVersion) && FileVersion.Equals(other.FileVersion); + } + + 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((DataHeader) obj); + } + + public override int GetHashCode() + { + unchecked { + var hashCode = (CreatedBy != null ? CreatedBy.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ Date.GetHashCode(); + hashCode = (hashCode * 397) ^ (AppVersion != null ? AppVersion.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ FileVersion.GetHashCode(); + return hashCode; + } + } + + #endregion + } + + public class DataBody + { + /// <summary> + /// [ccm] Displacement in cubic centimeter. + /// Used in Declaration Mode to calculate inertia. + /// </summary> + [JsonProperty(Required = Required.Always)] public double Displacement; + + /// <summary> + /// The Fuel Consumption Map is used to calculate the base Fuel Consumption (FC) value. + /// </summary> + [JsonProperty(Required = Required.Always)] public string FuelMap; + + [JsonProperty(Required = Required.Always)] public IList<DataFullLoadCurve> FullLoadCurves; + + /// <summary> + /// [rpm] Idling Engine Speed + /// Low idle, applied in simulation for vehicle standstill in neutral gear position. + /// </summary> + [JsonProperty("IdlingSpeed", Required = Required.Always)] public double IdleSpeed; + + /// <summary> + /// [kgm^2] Inertia including Flywheel + /// Inertia for rotating parts including engine flywheel. + /// In Declaration Mode the inertia is calculated automatically. + /// </summary> + [JsonProperty(Required = Required.Always)] public double Inertia; + + /// <summary> + /// Model. Free text defining the engine model, type, etc. + /// </summary> + [JsonProperty(Required = Required.Always)] public string ModelName; + + [JsonProperty("SavedInDeclMode")] public bool SavedInDeclarationMode; + + /// <summary> + /// [g/kWh] The WHTC test results are required in Declaration Mode for the motorway WHTC FC Correction. + /// </summary> + [JsonProperty("WHTC-Motorway")] public double WHTCMotorway; + + /// <summary> + /// [g/kWh] The WHTC test results are required in Declaration Mode for the rural WHTC FC Correction. + /// </summary> + [JsonProperty("WHTC-Rural")] public double WHTCRural; + + /// <summary> + /// [g/kWh] The WHTC test results are required in Declaration Mode for the urban WHTC FC Correction. + /// </summary> + [JsonProperty("WHTC-Urban")] public double WHTCUrban; + + /// <summary> + /// Multiple Full Load and Drag Curves (.vfld) can be defined and assigned to different gears. + /// Gear "0" must be assigned for idling and Engine Only Mode. + /// </summary> + public class DataFullLoadCurve + { + [JsonProperty(Required = Required.Always)] public string Gears; + [JsonProperty(Required = Required.Always)] public string Path; + + #region Equality Members + + protected bool Equals(DataFullLoadCurve other) + { + return string.Equals(Path, other.Path) && string.Equals(Gears, other.Gears); + } + + 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((DataFullLoadCurve) obj); + } + + public override int GetHashCode() + { + unchecked { + return ((Path != null ? Path.GetHashCode() : 0) * 397) ^ + (Gears != null ? Gears.GetHashCode() : 0); + } + } + + #endregion + } + + #region Equality members + + protected bool Equals(DataBody other) + { + return SavedInDeclarationMode.Equals(other.SavedInDeclarationMode) + && string.Equals(ModelName, other.ModelName) + && Displacement.Equals(other.Displacement) + && IdleSpeed.Equals(other.IdleSpeed) + && Inertia.Equals(other.Inertia) + && FullLoadCurves.SequenceEqual(other.FullLoadCurves) + && string.Equals(FuelMap, other.FuelMap) + && WHTCUrban.Equals(other.WHTCUrban) + && WHTCRural.Equals(other.WHTCRural) + && WHTCMotorway.Equals(other.WHTCMotorway); + } + + 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((DataBody) obj); + } + + public override int GetHashCode() + { + unchecked { + var hashCode = SavedInDeclarationMode.GetHashCode(); + hashCode = (hashCode * 397) ^ (ModelName != null ? ModelName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ Displacement.GetHashCode(); + hashCode = (hashCode * 397) ^ IdleSpeed.GetHashCode(); + hashCode = (hashCode * 397) ^ Inertia.GetHashCode(); + hashCode = (hashCode * 397) ^ (FullLoadCurves != null ? FullLoadCurves.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (FuelMap != null ? FuelMap.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ WHTCUrban.GetHashCode(); + hashCode = (hashCode * 397) ^ WHTCRural.GetHashCode(); + hashCode = (hashCode * 397) ^ WHTCMotorway.GetHashCode(); + return hashCode; + } + } + + #endregion + } + + #region Equality members + + protected bool Equals(Data other) + { + return Equals(Header, other.Header) && Equals(Body, other.Body); + } + + 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((Data) obj); + } + + public override int GetHashCode() + { + unchecked { + return ((Header != null ? Header.GetHashCode() : 0) * 397) ^ (Body != null ? Body.GetHashCode() : 0); + } + } + + #endregion + } + + public class RangeConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof (string) || base.CanConvertFrom(context, sourceType); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + return value.GetType() == typeof (string) + ? new Range((string) value) + : base.ConvertFrom(context, culture, value); + } + } + + [TypeConverter(typeof (RangeConverter))] + private class Range + { + private readonly uint _end; + private readonly uint _start; + + public Range(string range) + { + Contract.Requires(range != null); + + _start = uint.Parse(range.Split('-').First().Trim()); + _end = uint.Parse(range.Split('-').Last().Trim()); + } + + public override string ToString() + { + return string.Format("{0} - {1}", _start, _end); + } + + public bool Contains(uint value) + { + return _start <= value && value <= _end; + } + + #region Equality members + + protected bool Equals(Range other) + { + Contract.Requires(other != null); + return _start == other._start && _end == other._end; + } + + 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((Range) obj); + } + + public override int GetHashCode() + { + unchecked { + return (int) ((_start * 397) ^ _end); + } + } + + #endregion + } + + #region Equality members + + protected bool Equals(CombustionEngineData other) + { + return Equals(_data, other._data) + && _fullLoadCurves.Keys.SequenceEqual(other._fullLoadCurves.Keys) + && _fullLoadCurves.Values.SequenceEqual(other._fullLoadCurves.Values) + && Equals(ConsumptionMap, other.ConsumptionMap); + } + + 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((CombustionEngineData) obj); + } + + public override int GetHashCode() + { + unchecked { + var hashCode = _data.GetHashCode(); + hashCode = (hashCode * 397) ^ (_fullLoadCurves != null ? _fullLoadCurves.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (ConsumptionMap != null ? ConsumptionMap.GetHashCode() : 0); + return hashCode; + } + } + + #endregion + } } \ No newline at end of file diff --git a/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs b/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs index 9e7daa1b9d53c6da5be350a4c1fb9034c1d3b970..46396270db95553cba9756b17a6441335985390e 100644 --- a/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs +++ b/VectoCore/Models/SimulationComponent/Data/DrivingCycleData.cs @@ -178,7 +178,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data /// [rad/s] If "n" is defined VECTO uses that instead of the /// calculated engine speed value. /// </summary> - public PerSecond EngineSpeed { get; set; } + public RadianPerSecond EngineSpeed { get; set; } /// <summary> /// [-] Gear input. Overwrites the gear shift model. @@ -247,7 +247,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data 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<PerSecond>(), + 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>(), @@ -302,7 +302,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data 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<PerSecond>(), + 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), @@ -350,7 +350,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data foreach (DataRow row in table.Rows) { var entry = new DrivingCycleEntry { - EngineSpeed = row.ParseDoubleOrGetDefault(Fields.EngineSpeed).SI().Rounds.Per.Minute.To<PerSecond>(), + 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) }; diff --git a/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs b/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs index ced77ce0873a746357469a7f103c16f79da8cb70..52163fc134e782bdd8c7d103eb4c07f9238a1019 100644 --- a/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs +++ b/VectoCore/Models/SimulationComponent/Data/Engine/FuelConsumptionMap.cs @@ -32,7 +32,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine foreach (DataRow row in data.Rows) { try { var entry = new FuelConsumptionEntry { - EngineSpeed = row.ParseDouble(Fields.EngineSpeed).SI().Rounds.Per.Minute.To<PerSecond>(), + 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 }; @@ -66,7 +66,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine /// <param name="engineSpeed">[rad/sec]</param> /// <param name="torque">[Nm]</param> /// <returns>[kg/s]</returns> - public SI GetFuelConsumption(NewtonMeter torque, PerSecond engineSpeed) + public SI GetFuelConsumption(NewtonMeter torque, RadianPerSecond engineSpeed) { return _fuelMap.Interpolate((double) torque, (double) engineSpeed.To().Rounds.Per.Minute).SI().Kilo.Gramm.Per.Second; } @@ -94,7 +94,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine /// <summary> /// engine speed [rad/s] /// </summary> - public PerSecond EngineSpeed { get; set; } + public RadianPerSecond EngineSpeed { get; set; } /// <summary> /// Torque [Nm] diff --git a/VectoCore/Models/SimulationComponent/Data/Engine/FullLoadCurve.cs b/VectoCore/Models/SimulationComponent/Data/Engine/FullLoadCurve.cs index 8d5719ade0606af6a410a671d72c3eab2dab547a..78ef6b076104e00f6b432693ea170eb1d27e235a 100644 --- a/VectoCore/Models/SimulationComponent/Data/Engine/FullLoadCurve.cs +++ b/VectoCore/Models/SimulationComponent/Data/Engine/FullLoadCurve.cs @@ -11,7 +11,6 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine { - public class FullLoadCurve : SimulationComponentData { [JsonProperty] private List<FullLoadCurveEntry> _entries; @@ -60,7 +59,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<PerSecond>(), + EngineSpeed = row.ParseDouble(Fields.EngineSpeed).SI().Rounds.Per.Minute.To<RadianPerSecond>(), TorqueFullLoad = row.ParseDouble(Fields.TorqueFullLoad).SI<NewtonMeter>(), TorqueDrag = row.ParseDouble(Fields.TorqueDrag).SI<NewtonMeter>(), PT1 = row.ParseDouble(Fields.PT1).SI<Second>() @@ -72,7 +71,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<PerSecond>(), + EngineSpeed = row.ParseDouble(0).SI().Rounds.Per.Minute.To<RadianPerSecond>(), TorqueFullLoad = row.ParseDouble(1).SI<NewtonMeter>(), TorqueDrag = row.ParseDouble(2).SI<NewtonMeter>(), PT1 = row.ParseDouble(3).SI<Second>() @@ -84,7 +83,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine /// </summary> /// <param name="angularFrequency">[rad/s]</param> /// <returns>[Nm]</returns> - public NewtonMeter FullLoadStationaryTorque(PerSecond angularFrequency) + public NewtonMeter FullLoadStationaryTorque(RadianPerSecond angularFrequency) { var idx = FindIndex(angularFrequency); return VectoMath.Interpolate((double) _entries[idx - 1].EngineSpeed, (double) _entries[idx].EngineSpeed, @@ -97,7 +96,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine /// </summary> /// <param name="angularFrequency">[rad/s]</param> /// <returns>[W]</returns> - public Watt FullLoadStationaryPower(PerSecond angularFrequency) + public Watt FullLoadStationaryPower(RadianPerSecond angularFrequency) { return Formulas.TorqueToPower(FullLoadStationaryTorque(angularFrequency), angularFrequency); } @@ -107,7 +106,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine /// </summary> /// <param name="angularFrequency">[rad/s]</param> /// <returns>[Nm]</returns> - public NewtonMeter DragLoadStationaryTorque(PerSecond angularFrequency) + public NewtonMeter DragLoadStationaryTorque(RadianPerSecond angularFrequency) { var idx = FindIndex(angularFrequency); return VectoMath.Interpolate((double) _entries[idx - 1].EngineSpeed, (double) _entries[idx].EngineSpeed, @@ -120,7 +119,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine /// </summary> /// <param name="angularFrequency">[rad/s]</param> /// <returns>[W]</returns> - public Watt DragLoadStationaryPower(PerSecond angularFrequency) + public Watt DragLoadStationaryPower(RadianPerSecond angularFrequency) { Contract.Requires(angularFrequency.HasEqualUnit(new SI().Radian.Per.Second)); Contract.Ensures(Contract.Result<SI>().HasEqualUnit(new SI().Watt)); @@ -195,7 +194,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine /// <summary> /// [rad/s] engine speed /// </summary> - public PerSecond EngineSpeed { get; set; } + public RadianPerSecond EngineSpeed { get; set; } /// <summary> /// [Nm] full load torque @@ -271,10 +270,10 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine #endregion - private Tuple<PerSecond, Watt> FindMaxPower(FullLoadCurveEntry p1, FullLoadCurveEntry p2) + private Tuple<RadianPerSecond, Watt> FindMaxPower(FullLoadCurveEntry p1, FullLoadCurveEntry p2) { if (p1.EngineSpeed == p2.EngineSpeed) { - return new Tuple<PerSecond, Watt>(p1.EngineSpeed, Formulas.TorqueToPower(p1.TorqueFullLoad, p1.EngineSpeed)); + return new Tuple<RadianPerSecond, Watt>(p1.EngineSpeed, Formulas.TorqueToPower(p1.TorqueFullLoad, p1.EngineSpeed)); } if (p2.EngineSpeed < p1.EngineSpeed) { var tmp = p1; @@ -285,23 +284,24 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine var k = (p2.TorqueFullLoad - p1.TorqueFullLoad) / (p2.EngineSpeed - p1.EngineSpeed); var d = p2.TorqueFullLoad - k * p2.EngineSpeed; if (k == 0.0.SI()) { - return new Tuple<PerSecond, Watt>(p2.EngineSpeed, Formulas.TorqueToPower(p2.TorqueFullLoad, p2.EngineSpeed)); + return new Tuple<RadianPerSecond, Watt>(p2.EngineSpeed, Formulas.TorqueToPower(p2.TorqueFullLoad, p2.EngineSpeed)); } - var engineSpeedMaxPower = (-1 * d / (2 * k)).To<PerSecond>(); + var engineSpeedMaxPower = (-1 * d / (2 * k)).Radian.To<RadianPerSecond>(); if (engineSpeedMaxPower < p1.EngineSpeed || engineSpeedMaxPower > p2.EngineSpeed) { if (k > 0) { - return new Tuple<PerSecond, Watt>(p2.EngineSpeed, Formulas.TorqueToPower(p2.TorqueFullLoad, p2.EngineSpeed)); + return new Tuple<RadianPerSecond, Watt>(p2.EngineSpeed, Formulas.TorqueToPower(p2.TorqueFullLoad, p2.EngineSpeed)); } - return new Tuple<PerSecond, Watt>(p1.EngineSpeed, Formulas.TorqueToPower(p1.TorqueFullLoad, p1.EngineSpeed)); + return new Tuple<RadianPerSecond, Watt>(p1.EngineSpeed, Formulas.TorqueToPower(p1.TorqueFullLoad, p1.EngineSpeed)); } //return null; var engineTorqueMaxPower = FullLoadStationaryTorque(engineSpeedMaxPower); - return new Tuple<PerSecond, Watt>(engineSpeedMaxPower, Formulas.TorqueToPower(engineTorqueMaxPower, engineSpeedMaxPower)); + return new Tuple<RadianPerSecond, Watt>(engineSpeedMaxPower, + Formulas.TorqueToPower(engineTorqueMaxPower, engineSpeedMaxPower)); } - public PerSecond RatedSpeed() + public RadianPerSecond RatedSpeed() { - var max = new Tuple<PerSecond, Watt>(new PerSecond(), new Watt()); + var max = new Tuple<RadianPerSecond, Watt>(new RadianPerSecond(), new Watt()); for (var idx = 1; idx < _entries.Count; idx++) { var currentMax = FindMaxPower(_entries[idx - 1], _entries[idx]); if (currentMax.Item2 > max.Item2) { diff --git a/VectoCore/Models/SimulationComponent/Impl/Clutch.cs b/VectoCore/Models/SimulationComponent/Impl/Clutch.cs index a51108016d802d93c4c3bd3fb6d3fbda1106349b..b53dccb76436b512c42e00e23bc9a09e8b92832a 100644 --- a/VectoCore/Models/SimulationComponent/Impl/Clutch.cs +++ b/VectoCore/Models/SimulationComponent/Impl/Clutch.cs @@ -9,8 +9,8 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl { public class Clutch : VectoSimulationComponent, IClutch, ITnOutPort, ITnInPort { - private readonly PerSecond _idleSpeed; - private readonly PerSecond _ratedSpeed; + private readonly RadianPerSecond _idleSpeed; + private readonly RadianPerSecond _ratedSpeed; private ITnOutPort _nextComponent; private const double ClutchEff = 1; private const double CluchNormSpeed = 0.03; @@ -51,7 +51,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl return this; } - public IResponse Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, PerSecond angularVelocity) + public IResponse Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, RadianPerSecond angularVelocity) { var torqueIn = torque; var engineSpeedIn = angularVelocity; @@ -66,11 +66,12 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl if (engineSpeedNorm < CluchNormSpeed) { _clutchState = ClutchState.ClutchSlipping; - var engineSpeed0 = new PerSecond(Math.Max((double) _idleSpeed, (double) angularVelocity)); + var engineSpeed0 = new RadianPerSecond(Math.Max((double) _idleSpeed, (double) angularVelocity)); var clutchSpeedNorm = CluchNormSpeed / ((_idleSpeed + CluchNormSpeed * (_ratedSpeed - _idleSpeed)) / _ratedSpeed); engineSpeedIn = - new PerSecond((double) ((clutchSpeedNorm * engineSpeed0 / _ratedSpeed) * (_ratedSpeed - _idleSpeed) + _idleSpeed)); + ((clutchSpeedNorm * engineSpeed0 / _ratedSpeed) * (_ratedSpeed - _idleSpeed) + _idleSpeed).Radian + .To<RadianPerSecond>(); torqueIn = Formulas.PowerToTorque(Formulas.TorqueToPower(torque, angularVelocity) / ClutchEff, engineSpeedIn); } else { diff --git a/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs b/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs index 453d8695bda08c937a9d0babff8a23c586a872b6..027776435497b56a4b76c9aeb5d3dc633479eb46 100644 --- a/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs +++ b/VectoCore/Models/SimulationComponent/Impl/CombustionEngine.cs @@ -13,411 +13,410 @@ using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Models.SimulationComponent.Impl { - /// <summary> - /// Component for a combustion engine. - /// </summary> - public class CombustionEngine : VectoSimulationComponent, ICombustionEngine, ITnOutPort, IMemento - { - public enum EngineOperationMode - { - Idle, - Drag, - FullDrag, - Load, - FullLoad, - Stopped, - Undef - } - - private const int EngineIdleSpeedStopThreshold = 100; - private const double MaxPowerExceededThreshold = 1.05; - private const double ZeroThreshold = 0.0001; - private const double FullLoadMargin = 0.01; - [NonSerialized] private readonly List<TimeSpan> _enginePowerCorrections = new List<TimeSpan>(); - - /// <summary> - /// Current state is computed in request method - /// </summary> - private EngineState _currentState = new EngineState(); - - private CombustionEngineData _data; - private EngineState _previousState = new EngineState(); - - public CombustionEngine(IVehicleContainer cockpit, CombustionEngineData data) - : base(cockpit) - { - _data = data; - - _previousState.OperationMode = EngineOperationMode.Idle; - _previousState.EnginePower = 0.SI<Watt>(); - _previousState.EngineSpeed = _data.IdleSpeed; - } - - #region IEngineCockpit - - - public PerSecond EngineSpeed() - { - return _previousState.EngineSpeed; - } - - #endregion - - #region IOutShaft - - public ITnOutPort OutShaft() - { - return this; - } - - #endregion - - #region ITnOutPort - - 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>(); - - if (engineSpeed < (double) _data.IdleSpeed - EngineIdleSpeedStopThreshold) { - _currentState.OperationMode = EngineOperationMode.Stopped; - //todo: _currentState.EnginePowerLoss = enginePowerLoss; - } - - var currentGear = Cockpit.Gear(); - - _currentState.FullDragTorque = _data.GetFullLoadCurve(currentGear).DragLoadStationaryTorque(engineSpeed); - _currentState.FullDragPower = Formulas.TorqueToPower(_currentState.FullDragTorque, engineSpeed); - - ComputeFullLoadPower(currentGear, engineSpeed, dt); - - ValidatePowerDemand(requestedEnginePower); - - requestedEnginePower = LimitEnginePower(requestedEnginePower); - - UpdateEngineState(requestedEnginePower); - - _currentState.EnginePower = requestedEnginePower; //todo + _currentState.EnginePowerLoss; - _currentState.EngineTorque = Formulas.PowerToTorque(_currentState.EnginePower, - _currentState.EngineSpeed); - - //todo: use ResponseOverloadFail in case of overload - return new ResponseSuccess(); - } - - #endregion - - #region VectoSimulationComponent - - public override void CommitSimulationStep(IModalDataWriter writer) - { - writer[ModalResultField.PaEng] = (double) _currentState.EnginePowerLoss; - writer[ModalResultField.Pe_drag] = (double) _currentState.FullDragPower; - writer[ModalResultField.Pe_full] = (double) _currentState.DynamicFullLoadPower; - writer[ModalResultField.Pe_eng] = (double) _currentState.EnginePower; - - 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; - - try { - writer[ModalResultField.FC] = - (double) - _data.ConsumptionMap.GetFuelConsumption(_currentState.EngineTorque, _currentState.EngineSpeed) - .To() - .Gramm.Per.Hour; - } catch (VectoException ex) { - Log.WarnFormat("t: {0} - {1} n: {2} Tq: {3}", _currentState.AbsTime.TotalSeconds, ex.Message, - _currentState.EngineSpeed, _currentState.EngineTorque); - writer[ModalResultField.FC] = Double.NaN; - } - - _previousState = _currentState; - _currentState = new EngineState(); - } - - #endregion - - /// <summary> - /// Validates the requested power demand [W]. - /// </summary> - /// <param name="requestedEnginePower">[W]</param> - protected void ValidatePowerDemand(SI requestedEnginePower) - { - Contract.Requires(requestedEnginePower.HasEqualUnit(new SI().Watt)); - - 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)); - } - 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)); - } - } - - /// <summary> - /// [W] => [W] - /// </summary> - /// <param name="requestedEnginePower">[W]</param> - /// <returns>[W]</returns> - protected Watt LimitEnginePower(Watt requestedEnginePower) - { - if (requestedEnginePower > _currentState.DynamicFullLoadPower) { - if (requestedEnginePower / _currentState.DynamicFullLoadPower > MaxPowerExceededThreshold) { - _enginePowerCorrections.Add(_currentState.AbsTime); - Log.WarnFormat( - "t: {0} requested power > P_engine_full * 1.05 - corrected. P_request: {1} P_engine_full: {2}", - _currentState.AbsTime, requestedEnginePower, _currentState.DynamicFullLoadPower); - } - return _currentState.DynamicFullLoadPower; - } - if (requestedEnginePower < _currentState.FullDragPower) { - if (requestedEnginePower / _currentState.FullDragPower > MaxPowerExceededThreshold && - requestedEnginePower > -99999) { - _enginePowerCorrections.Add(_currentState.AbsTime); - Log.WarnFormat( - "t: {0} requested power < P_engine_drag * 1.05 - corrected. P_request: {1} P_engine_drag: {2}", - _currentState.AbsTime, requestedEnginePower, _currentState.FullDragPower); - } - return _currentState.FullDragPower; - } - return requestedEnginePower; - } - - /// <summary> - /// Updates the engine state dependend on the requested power [W]. - /// </summary> - /// <param name="requestedEnginePower">[W]</param> - protected void UpdateEngineState(Watt requestedEnginePower) - { - if (requestedEnginePower < -ZeroThreshold) { - _currentState.OperationMode = IsFullLoad(requestedEnginePower, _currentState.DynamicFullLoadPower) - ? EngineOperationMode.FullLoad - : EngineOperationMode.Load; - } else if (requestedEnginePower > ZeroThreshold) { - _currentState.OperationMode = IsFullLoad(requestedEnginePower, _currentState.FullDragPower) - ? EngineOperationMode.FullDrag - : EngineOperationMode.Drag; - } else { - // -ZeroThreshold <= requestedEnginePower <= ZeroThreshold - _currentState.OperationMode = EngineOperationMode.Idle; - } - } - - public IList<string> Warnings() - { - IList<string> retVal = new List<string>(); - retVal.Add(string.Format("Engine power corrected (>5%) in {0} time steps ", _enginePowerCorrections.Count)); - return retVal; - } - - /// <summary> - /// computes full load power from gear [-], angularFrequency [rad/s] and dt [s]. - /// </summary> - /// <param name="gear"></param> - /// <param name="angularFrequency">[rad/s]</param> - /// <param name="dt">[s]</param> - protected void ComputeFullLoadPower(uint gear, PerSecond angularFrequency, TimeSpan dt) - { - if (dt.Ticks == 0) { - throw new VectoException("ComputeFullLoadPower cannot compute at time 0."); - } - - // TODO @@@quam: handle dynamic timesteps - if (!dt.TotalSeconds.IsEqual(1)) { - throw new VectoException("simulation steps other than 1s can not be handled ATM"); - } - - //_currentState.StationaryFullLoadPower = _data.GetFullLoadCurve(gear).FullLoadStationaryPower(rpm); - _currentState.StationaryFullLoadTorque = - _data.GetFullLoadCurve(gear).FullLoadStationaryTorque(angularFrequency); - _currentState.StationaryFullLoadPower = Formulas.TorqueToPower(_currentState.StationaryFullLoadTorque, - angularFrequency); - - var pt1 = _data.GetFullLoadCurve(gear).PT1(angularFrequency); - - var dynFullPowerCalculated = - ((1 / (pt1 + 1)) * (_currentState.StationaryFullLoadPower + pt1 * _previousState.EnginePower)).To<Watt>(); - _currentState.DynamicFullLoadPower = dynFullPowerCalculated < _currentState.StationaryFullLoadPower - ? dynFullPowerCalculated - : _currentState.StationaryFullLoadPower; - _currentState.DynamicFullLoadTorque = Formulas.PowerToTorque(_currentState.DynamicFullLoadPower, - angularFrequency); - } - - protected bool IsFullLoad(Watt requestedPower, Watt maxPower) - { - var testValue = requestedPower / maxPower - 1.0; - return testValue.Abs() < FullLoadMargin; - } - - /// <summary> - /// Calculates power loss. [W] - /// </summary> - /// <param name="torque">[Nm]</param> - /// <param name="engineSpeed">[rad/s]</param> - /// <returns>[W]</returns> - protected Watt InertiaPowerLoss(NewtonMeter torque, PerSecond engineSpeed) - { - var deltaEngineSpeed = engineSpeed - _previousState.EngineSpeed; - var avgEngineSpeed = (_previousState.EngineSpeed + engineSpeed) / new SI(2).Second; - var result = _data.Inertia * deltaEngineSpeed * avgEngineSpeed; - return result.To<Watt>(); - } - - public class EngineState - { - public EngineOperationMode OperationMode { get; set; } - - /// <summary> - /// [s] - /// </summary> - public TimeSpan AbsTime { get; set; } - - /// <summary> - /// [W] - /// </summary> - public Watt EnginePower { get; set; } - - /// <summary> - /// [rad/s] - /// </summary> - public PerSecond EngineSpeed { get; set; } - - /// <summary> - /// [W] - /// </summary> - public Watt EnginePowerLoss { get; set; } - - /// <summary> - /// [W] - /// </summary> - public Watt StationaryFullLoadPower { get; set; } - - /// <summary> - /// [W] - /// </summary> - public Watt DynamicFullLoadPower { get; set; } - - /// <summary> - /// [Nm] - /// </summary> - public NewtonMeter StationaryFullLoadTorque { get; set; } - - /// <summary> - /// [Nm] - /// </summary> - public NewtonMeter DynamicFullLoadTorque { get; set; } - - /// <summary> - /// [W] - /// </summary> - public Watt FullDragPower { get; set; } - - /// <summary> - /// [Nm] - /// </summary> - public NewtonMeter FullDragTorque { get; set; } - - /// <summary> - /// [Nm] - /// </summary> - public NewtonMeter EngineTorque { get; set; } - - #region Equality members - - protected bool Equals(EngineState other) - { - return OperationMode == other.OperationMode - && Equals(EnginePower, other.EnginePower) - && Equals(EngineSpeed, other.EngineSpeed) - && Equals(EnginePowerLoss, other.EnginePowerLoss) - && AbsTime.Equals(other.AbsTime); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) { - return false; - } - if (ReferenceEquals(this, obj)) { - return true; - } - var other = obj as EngineState; - return other != null && Equals(other); - } - - public override int GetHashCode() - { - unchecked { - var hashCode = (int) OperationMode; - hashCode = (hashCode * 397) ^ (EnginePower != null ? EnginePower.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (EngineSpeed != null ? EngineSpeed.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (EnginePowerLoss != null ? EnginePowerLoss.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ AbsTime.GetHashCode(); - return hashCode; - } - } - - #endregion - } - - #region Equality members - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) { - return false; - } - if (ReferenceEquals(this, obj)) { - return true; - } - return obj.GetType() == GetType() && Equals((CombustionEngine) obj); - } - - protected bool Equals(CombustionEngine other) - { - return Equals(_data, other._data) - && Equals(_previousState, other._previousState) - && Equals(_currentState, other._currentState); - } - - public override int GetHashCode() - { - unchecked { - var hashCode = base.GetHashCode(); - hashCode = (hashCode * 397) ^ (_data != null ? _data.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (_previousState != null ? _previousState.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (_currentState != null ? _currentState.GetHashCode() : 0); - return hashCode; - } - } - - #endregion - - #region IMemento - - public string Serialize() - { - var mem = new { Data = _data, PreviousState = _previousState }; - return Memento.Serialize(mem); - } - - public void Deserialize(string data) - { - var mem = new { Data = _data, PreviousState = _previousState }; - mem = Memento.Deserialize(data, mem); - - _data = mem.Data; - _previousState = mem.PreviousState; - } - - #endregion - } + /// <summary> + /// Component for a combustion engine. + /// </summary> + public class CombustionEngine : VectoSimulationComponent, ICombustionEngine, ITnOutPort, IMemento + { + public enum EngineOperationMode + { + Idle, + Drag, + FullDrag, + Load, + FullLoad, + Stopped, + Undef + } + + private const int EngineIdleSpeedStopThreshold = 100; + private const double MaxPowerExceededThreshold = 1.05; + private const double ZeroThreshold = 0.0001; + private const double FullLoadMargin = 0.01; + [NonSerialized] private readonly List<TimeSpan> _enginePowerCorrections = new List<TimeSpan>(); + + /// <summary> + /// Current state is computed in request method + /// </summary> + private EngineState _currentState = new EngineState(); + + private CombustionEngineData _data; + private EngineState _previousState = new EngineState(); + + public CombustionEngine(IVehicleContainer cockpit, CombustionEngineData data) + : base(cockpit) + { + _data = data; + + _previousState.OperationMode = EngineOperationMode.Idle; + _previousState.EnginePower = 0.SI<Watt>(); + _previousState.EngineSpeed = _data.IdleSpeed; + } + + #region IEngineCockpit + + public RadianPerSecond EngineSpeed() + { + return _previousState.EngineSpeed; + } + + #endregion + + #region IOutShaft + + public ITnOutPort OutShaft() + { + return this; + } + + #endregion + + #region ITnOutPort + + IResponse ITnOutPort.Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, RadianPerSecond 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>(); + + if (engineSpeed < (double) _data.IdleSpeed - EngineIdleSpeedStopThreshold) { + _currentState.OperationMode = EngineOperationMode.Stopped; + //todo: _currentState.EnginePowerLoss = enginePowerLoss; + } + + var currentGear = Cockpit.Gear(); + + _currentState.FullDragTorque = _data.GetFullLoadCurve(currentGear).DragLoadStationaryTorque(engineSpeed); + _currentState.FullDragPower = Formulas.TorqueToPower(_currentState.FullDragTorque, engineSpeed); + + ComputeFullLoadPower(currentGear, engineSpeed, dt); + + ValidatePowerDemand(requestedEnginePower); + + requestedEnginePower = LimitEnginePower(requestedEnginePower); + + UpdateEngineState(requestedEnginePower); + + _currentState.EnginePower = requestedEnginePower; //todo + _currentState.EnginePowerLoss; + _currentState.EngineTorque = Formulas.PowerToTorque(_currentState.EnginePower, + _currentState.EngineSpeed); + + //todo: use ResponseOverloadFail in case of overload + return new ResponseSuccess(); + } + + #endregion + + #region VectoSimulationComponent + + public override void CommitSimulationStep(IModalDataWriter writer) + { + writer[ModalResultField.PaEng] = (double) _currentState.EnginePowerLoss; + writer[ModalResultField.Pe_drag] = (double) _currentState.FullDragPower; + writer[ModalResultField.Pe_full] = (double) _currentState.DynamicFullLoadPower; + writer[ModalResultField.Pe_eng] = (double) _currentState.EnginePower; + + 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; + + try { + writer[ModalResultField.FC] = + (double) + _data.ConsumptionMap.GetFuelConsumption(_currentState.EngineTorque, _currentState.EngineSpeed) + .To() + .Gramm.Per.Hour; + } catch (VectoException ex) { + Log.WarnFormat("t: {0} - {1} n: {2} Tq: {3}", _currentState.AbsTime.TotalSeconds, ex.Message, + _currentState.EngineSpeed, _currentState.EngineTorque); + writer[ModalResultField.FC] = Double.NaN; + } + + _previousState = _currentState; + _currentState = new EngineState(); + } + + #endregion + + /// <summary> + /// Validates the requested power demand [W]. + /// </summary> + /// <param name="requestedEnginePower">[W]</param> + protected void ValidatePowerDemand(SI requestedEnginePower) + { + Contract.Requires(requestedEnginePower.HasEqualUnit(new SI().Watt)); + + 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)); + } + 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)); + } + } + + /// <summary> + /// [W] => [W] + /// </summary> + /// <param name="requestedEnginePower">[W]</param> + /// <returns>[W]</returns> + protected Watt LimitEnginePower(Watt requestedEnginePower) + { + if (requestedEnginePower > _currentState.DynamicFullLoadPower) { + if (requestedEnginePower / _currentState.DynamicFullLoadPower > MaxPowerExceededThreshold) { + _enginePowerCorrections.Add(_currentState.AbsTime); + Log.WarnFormat( + "t: {0} requested power > P_engine_full * 1.05 - corrected. P_request: {1} P_engine_full: {2}", + _currentState.AbsTime, requestedEnginePower, _currentState.DynamicFullLoadPower); + } + return _currentState.DynamicFullLoadPower; + } + if (requestedEnginePower < _currentState.FullDragPower) { + if (requestedEnginePower / _currentState.FullDragPower > MaxPowerExceededThreshold && + requestedEnginePower > -99999) { + _enginePowerCorrections.Add(_currentState.AbsTime); + Log.WarnFormat( + "t: {0} requested power < P_engine_drag * 1.05 - corrected. P_request: {1} P_engine_drag: {2}", + _currentState.AbsTime, requestedEnginePower, _currentState.FullDragPower); + } + return _currentState.FullDragPower; + } + return requestedEnginePower; + } + + /// <summary> + /// Updates the engine state dependend on the requested power [W]. + /// </summary> + /// <param name="requestedEnginePower">[W]</param> + protected void UpdateEngineState(Watt requestedEnginePower) + { + if (requestedEnginePower < -ZeroThreshold) { + _currentState.OperationMode = IsFullLoad(requestedEnginePower, _currentState.DynamicFullLoadPower) + ? EngineOperationMode.FullLoad + : EngineOperationMode.Load; + } else if (requestedEnginePower > ZeroThreshold) { + _currentState.OperationMode = IsFullLoad(requestedEnginePower, _currentState.FullDragPower) + ? EngineOperationMode.FullDrag + : EngineOperationMode.Drag; + } else { + // -ZeroThreshold <= requestedEnginePower <= ZeroThreshold + _currentState.OperationMode = EngineOperationMode.Idle; + } + } + + public IList<string> Warnings() + { + IList<string> retVal = new List<string>(); + retVal.Add(string.Format("Engine power corrected (>5%) in {0} time steps ", _enginePowerCorrections.Count)); + return retVal; + } + + /// <summary> + /// computes full load power from gear [-], angularFrequency [rad/s] and dt [s]. + /// </summary> + /// <param name="gear"></param> + /// <param name="angularFrequency">[rad/s]</param> + /// <param name="dt">[s]</param> + protected void ComputeFullLoadPower(uint gear, RadianPerSecond angularFrequency, TimeSpan dt) + { + if (dt.Ticks == 0) { + throw new VectoException("ComputeFullLoadPower cannot compute at time 0."); + } + + // TODO @@@quam: handle dynamic timesteps + if (!dt.TotalSeconds.IsEqual(1)) { + throw new VectoException("simulation steps other than 1s can not be handled ATM"); + } + + //_currentState.StationaryFullLoadPower = _data.GetFullLoadCurve(gear).FullLoadStationaryPower(rpm); + _currentState.StationaryFullLoadTorque = + _data.GetFullLoadCurve(gear).FullLoadStationaryTorque(angularFrequency); + _currentState.StationaryFullLoadPower = Formulas.TorqueToPower(_currentState.StationaryFullLoadTorque, + angularFrequency); + + var pt1 = _data.GetFullLoadCurve(gear).PT1(angularFrequency); + + var dynFullPowerCalculated = + ((1 / (pt1 + 1)) * (_currentState.StationaryFullLoadPower + pt1 * _previousState.EnginePower)).To<Watt>(); + _currentState.DynamicFullLoadPower = dynFullPowerCalculated < _currentState.StationaryFullLoadPower + ? dynFullPowerCalculated + : _currentState.StationaryFullLoadPower; + _currentState.DynamicFullLoadTorque = Formulas.PowerToTorque(_currentState.DynamicFullLoadPower, + angularFrequency); + } + + protected bool IsFullLoad(Watt requestedPower, Watt maxPower) + { + var testValue = requestedPower / maxPower - 1.0; + return testValue.Abs() < FullLoadMargin; + } + + /// <summary> + /// Calculates power loss. [W] + /// </summary> + /// <param name="torque">[Nm]</param> + /// <param name="engineSpeed">[rad/s]</param> + /// <returns>[W]</returns> + protected Watt InertiaPowerLoss(NewtonMeter torque, RadianPerSecond engineSpeed) + { + var deltaEngineSpeed = engineSpeed - _previousState.EngineSpeed; + var avgEngineSpeed = (_previousState.EngineSpeed + engineSpeed) / new SI(2).Second; + var result = _data.Inertia * deltaEngineSpeed * avgEngineSpeed; + return result.To<Watt>(); + } + + public class EngineState + { + public EngineOperationMode OperationMode { get; set; } + + /// <summary> + /// [s] + /// </summary> + public TimeSpan AbsTime { get; set; } + + /// <summary> + /// [W] + /// </summary> + public Watt EnginePower { get; set; } + + /// <summary> + /// [rad/s] + /// </summary> + public RadianPerSecond EngineSpeed { get; set; } + + /// <summary> + /// [W] + /// </summary> + public Watt EnginePowerLoss { get; set; } + + /// <summary> + /// [W] + /// </summary> + public Watt StationaryFullLoadPower { get; set; } + + /// <summary> + /// [W] + /// </summary> + public Watt DynamicFullLoadPower { get; set; } + + /// <summary> + /// [Nm] + /// </summary> + public NewtonMeter StationaryFullLoadTorque { get; set; } + + /// <summary> + /// [Nm] + /// </summary> + public NewtonMeter DynamicFullLoadTorque { get; set; } + + /// <summary> + /// [W] + /// </summary> + public Watt FullDragPower { get; set; } + + /// <summary> + /// [Nm] + /// </summary> + public NewtonMeter FullDragTorque { get; set; } + + /// <summary> + /// [Nm] + /// </summary> + public NewtonMeter EngineTorque { get; set; } + + #region Equality members + + protected bool Equals(EngineState other) + { + return OperationMode == other.OperationMode + && Equals(EnginePower, other.EnginePower) + && Equals(EngineSpeed, other.EngineSpeed) + && Equals(EnginePowerLoss, other.EnginePowerLoss) + && AbsTime.Equals(other.AbsTime); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) { + return false; + } + if (ReferenceEquals(this, obj)) { + return true; + } + var other = obj as EngineState; + return other != null && Equals(other); + } + + public override int GetHashCode() + { + unchecked { + var hashCode = (int) OperationMode; + hashCode = (hashCode * 397) ^ (EnginePower != null ? EnginePower.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (EngineSpeed != null ? EngineSpeed.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (EnginePowerLoss != null ? EnginePowerLoss.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ AbsTime.GetHashCode(); + return hashCode; + } + } + + #endregion + } + + #region Equality members + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) { + return false; + } + if (ReferenceEquals(this, obj)) { + return true; + } + return obj.GetType() == GetType() && Equals((CombustionEngine) obj); + } + + protected bool Equals(CombustionEngine other) + { + return Equals(_data, other._data) + && Equals(_previousState, other._previousState) + && Equals(_currentState, other._currentState); + } + + public override int GetHashCode() + { + unchecked { + var hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ (_data != null ? _data.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (_previousState != null ? _previousState.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (_currentState != null ? _currentState.GetHashCode() : 0); + return hashCode; + } + } + + #endregion + + #region IMemento + + public string Serialize() + { + var mem = new { Data = _data, PreviousState = _previousState }; + return Memento.Serialize(mem); + } + + public void Deserialize(string data) + { + var mem = new { Data = _data, PreviousState = _previousState }; + mem = Memento.Deserialize(data, mem); + + _data = mem.Data; + _previousState = mem.PreviousState; + } + + #endregion + } } \ No newline at end of file diff --git a/VectoCore/Models/SimulationComponent/Impl/EngineOnlyAuxiliary.cs b/VectoCore/Models/SimulationComponent/Impl/EngineOnlyAuxiliary.cs index a9c1b8e7ff090eac97bbfc7893037b227152a7a3..84c3ca603506ade23b7e9e778fe99f718e9f57d2 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, PerSecond engineSpeed) + IResponse ITnOutPort.Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, RadianPerSecond engineSpeed) { if (_outPort == null) { Log.ErrorFormat("{0} cannot handle incoming request - no outport available", absTime); diff --git a/VectoCore/Models/SimulationComponent/Impl/EngineOnlyGearbox.cs b/VectoCore/Models/SimulationComponent/Impl/EngineOnlyGearbox.cs index 50b9421f52c7595b79f39f4cf072af8a843999bd..c50186a671dd51d0363ea44c7adc95c84d08cda7 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, PerSecond engineSpeed) + IResponse ITnOutPort.Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, RadianPerSecond 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 46abd5a94fb77d1a4688255b222e0a521fadd28f..4340c4d7860eaf4e4d2b928d473e5e416578d273 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, PerSecond engineSpeed) + IResponse ITnOutPort.Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, RadianPerSecond engineSpeed) { throw new NotImplementedException(); } diff --git a/VectoCore/Utils/DoubleExtensionMethods.cs b/VectoCore/Utils/DoubleExtensionMethods.cs index 3e2776d977efb4511040111c5fd02b8d573d7931..c77746f498884fa43a1f444ecda5ff7b69fdd2cb 100644 --- a/VectoCore/Utils/DoubleExtensionMethods.cs +++ b/VectoCore/Utils/DoubleExtensionMethods.cs @@ -43,9 +43,9 @@ namespace TUGraz.VectoCore.Utils return d.IsGreaterOrEqual(0.0, tolerance); } - public static PerSecond RPMtoRad(this double d) + public static RadianPerSecond RPMtoRad(this double d) { - return d.SI().Rounds.Per.Minute.To<PerSecond>(); + return d.SI().Rounds.Per.Minute.To<RadianPerSecond>(); } /// <summary> diff --git a/VectoCore/Utils/Formulas.cs b/VectoCore/Utils/Formulas.cs index cb7cd5c07369582eaa4b92ab486ec39b81c377b9..a25fafcd3611edf3e77d34dcc045b77a0437be70 100644 --- a/VectoCore/Utils/Formulas.cs +++ b/VectoCore/Utils/Formulas.cs @@ -11,7 +11,7 @@ namespace TUGraz.VectoCore.Utils /// <param name="angularFrequency">[rad/s]</param> /// <returns>power [W]</returns> [Pure] - public static Watt TorqueToPower(NewtonMeter torque, PerSecond angularFrequency) + public static Watt TorqueToPower(NewtonMeter torque, RadianPerSecond angularFrequency) { return (torque * angularFrequency).To<Watt>(); } @@ -23,7 +23,7 @@ namespace TUGraz.VectoCore.Utils /// <param name="angularFrequency">[rad/s]</param> /// <returns>torque [Nm]</returns> [Pure] - public static NewtonMeter PowerToTorque(SI power, PerSecond angularFrequency) + public static NewtonMeter PowerToTorque(SI power, RadianPerSecond angularFrequency) { return (power / angularFrequency).To<NewtonMeter>(); } diff --git a/VectoCore/Utils/SI.cs b/VectoCore/Utils/SI.cs index 220bf8c965a3eb194786d70064a48ea716fc1124..e18adcfa9140b2dbff6ab45dafb7515d9b1a8c64 100644 --- a/VectoCore/Utils/SI.cs +++ b/VectoCore/Utils/SI.cs @@ -8,580 +8,580 @@ 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 PerSecond : SI - { - public PerSecond(double val = 0) : base(val, new SI().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); } - } - - /// <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 : 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 + } } \ No newline at end of file diff --git a/VectoCoreTest/Models/SimulationComponent/ClutchTest.cs b/VectoCoreTest/Models/SimulationComponent/ClutchTest.cs index da4b4e947ef430fcec334f9e2369ea311d0ad401..20c9d845432a6dd180e3c77eee6e5d8608ded0d8 100644 --- a/VectoCoreTest/Models/SimulationComponent/ClutchTest.cs +++ b/VectoCoreTest/Models/SimulationComponent/ClutchTest.cs @@ -32,21 +32,21 @@ namespace TUGraz.VectoCore.Tests.Models.SimulationComponent //Test - Clutch slipping gearbox.CurrentGear = 1; - clutchOutPort.Request(new TimeSpan(), new TimeSpan(), 100.SI<NewtonMeter>(), new PerSecond(30.0)); + clutchOutPort.Request(new TimeSpan(), new TimeSpan(), 100.SI<NewtonMeter>(), new RadianPerSecond(30.0)); Assert.AreEqual(48.293649, (double)outPort.Torque, 0.001); Assert.AreEqual(62.119969, (double)outPort.AngularFrequency, 0.001); //Test - Clutch opened gearbox.CurrentGear = 0; - clutchOutPort.Request(new TimeSpan(), new TimeSpan(), 100.SI<NewtonMeter>(), new PerSecond(30.0)); + clutchOutPort.Request(new TimeSpan(), new TimeSpan(), 100.SI<NewtonMeter>(), new RadianPerSecond(30.0)); Assert.AreEqual(0, (double)outPort.Torque, 0.001); Assert.AreEqual((double)engineData.IdleSpeed, (double)outPort.AngularFrequency, 0.001); //Test - Clutch closed gearbox.CurrentGear = 1; - clutchOutPort.Request(new TimeSpan(), new TimeSpan(), 100.SI<NewtonMeter>(), new PerSecond(80.0)); + clutchOutPort.Request(new TimeSpan(), new TimeSpan(), 100.SI<NewtonMeter>(), new RadianPerSecond(80.0)); Assert.AreEqual(100.0, (double)outPort.Torque, 0.001); Assert.AreEqual(80.0, (double)outPort.AngularFrequency, 0.001); diff --git a/VectoCoreTest/Models/SimulationComponent/MockPorts.cs b/VectoCoreTest/Models/SimulationComponent/MockPorts.cs index 2795fdd2568613e24d01a3a8eb9dfe56ba92a5a7..0ede14df24ca885ccbfddd3a425ac0eeba14eb1c 100644 --- a/VectoCoreTest/Models/SimulationComponent/MockPorts.cs +++ b/VectoCoreTest/Models/SimulationComponent/MockPorts.cs @@ -11,9 +11,9 @@ namespace TUGraz.VectoCore.Tests.Models.SimulationComponent public TimeSpan AbsTime { get; set; } public TimeSpan Dt { get; set; } public NewtonMeter Torque { get; set; } - public PerSecond AngularFrequency { get; set; } + public RadianPerSecond AngularFrequency { get; set; } - public IResponse Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, PerSecond angularFrequency) + public IResponse Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, RadianPerSecond angularFrequency) { AbsTime = absTime; Dt = dt; diff --git a/VectoCoreTest/Models/SimulationComponentData/FullLoadCurveTest.cs b/VectoCoreTest/Models/SimulationComponentData/FullLoadCurveTest.cs index 6de77a4a1bcf63188d7d469e5e159dad801fc411..07f97e2aba1fc8601fb1f8f982d636d942f4832e 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<PerSecond>()); + var result = curve.FullLoadStationaryTorque(1.SI<RadianPerSecond>()); Assert.AreNotEqual((double) result, 0.0); } diff --git a/VectoCoreTest/Utils/DummyGearbox.cs b/VectoCoreTest/Utils/DummyGearbox.cs index e1bb2e2358441e9bfa7f5656f6d6838d6bb1a2d9..c40b8f055c8744ebda41b7aab05aee057a788fc1 100644 --- a/VectoCoreTest/Utils/DummyGearbox.cs +++ b/VectoCoreTest/Utils/DummyGearbox.cs @@ -13,7 +13,7 @@ namespace TUGraz.VectoCore.Tests.Utils private ITnOutPort _outPort; public uint CurrentGear { get; set; } - + public DummyGearbox(IVehicleContainer cockpit) : base(cockpit) {} public ITnInPort InShaft() @@ -36,7 +36,7 @@ namespace TUGraz.VectoCore.Tests.Utils _outPort = other; } - public IResponse Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, PerSecond engineSpeed) + public IResponse Request(TimeSpan absTime, TimeSpan dt, NewtonMeter torque, RadianPerSecond engineSpeed) { throw new NotImplementedException(); }