Code development platform for open source projects from the European Union institutions :large_blue_circle: EU Login authentication by SMS will be completely phased out by mid-2025. To see alternatives please check here

Skip to content
Snippets Groups Projects
Commit 0f3debe8 authored by Raphael LUZ's avatar Raphael LUZ
Browse files

Implemented rated speed calculation in full load curve component and test case

parent 63dca4b8
No related branches found
No related tags found
No related merge requests found
......@@ -2,6 +2,7 @@
using System.Data;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using Common.Logging;
using Newtonsoft.Json;
using TUGraz.VectoCore.Exceptions;
......@@ -9,263 +10,309 @@ using TUGraz.VectoCore.Utils;
namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine
{
public class FullLoadCurve : SimulationComponentData
{
[JsonProperty] private List<FullLoadCurveEntry> _entries;
public static FullLoadCurve ReadFromFile(string fileName)
{
var data = VectoCSVFile.Read(fileName);
//todo Contract.Requires<VectoException>(data.Columns.Count != 4, "FullLoadCurve Data File must consist of 4 columns.");
if (data.Columns.Count != 4) {
throw new VectoException("FullLoadCurve Data File must consist of 4 columns.");
}
//todo Contract.Requires<VectoException>(data.Rows.Count < 2, "FullLoadCurve must consist of at least two lines with numeric values (below file header)");
if (data.Rows.Count < 2) {
throw new VectoException("FullLoadCurve must consist of at least two lines with numeric values (below file header)");
}
List<FullLoadCurveEntry> entries;
if (HeaderIsValid(data.Columns)) {
entries = CreateFromColumnNames(data);
} else {
var log = LogManager.GetLogger<FullLoadCurve>();
log.WarnFormat(
"FullLoadCurve: Header Line is not valid. Expected: '{0}, {1}, {2}, {3}', Got: '{4}'. Falling back to column index.",
Fields.EngineSpeed, Fields.TorqueFullLoad, Fields.TorqueDrag, Fields.PT1,
string.Join(", ", data.Columns.Cast<DataColumn>().Select(c => c.ColumnName).Reverse()));
entries = CreateFromColumnIndizes(data);
}
return new FullLoadCurve {_entries = entries};
}
private static bool HeaderIsValid(DataColumnCollection columns)
{
Contract.Requires(columns != null);
return columns.Contains(Fields.EngineSpeed)
&& columns.Contains(Fields.TorqueDrag)
&& columns.Contains(Fields.TorqueFullLoad)
&& columns.Contains(Fields.PT1);
}
private static List<FullLoadCurveEntry> CreateFromColumnNames(DataTable data)
{
Contract.Requires(data != null);
return (from DataRow row in data.Rows
select new FullLoadCurveEntry {
EngineSpeed = row.ParseDouble(Fields.EngineSpeed).SI().Rounds.Per.Minute.To<RadianPerSecond>(),
TorqueFullLoad = row.ParseDouble(Fields.TorqueFullLoad).SI<NewtonMeter>(),
TorqueDrag = row.ParseDouble(Fields.TorqueDrag).SI<NewtonMeter>(),
PT1 = row.ParseDouble(Fields.PT1).SI<Second>()
}).ToList();
}
private static List<FullLoadCurveEntry> CreateFromColumnIndizes(DataTable data)
{
Contract.Requires(data != null);
return (from DataRow row in data.Rows
select new FullLoadCurveEntry {
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>()
}).ToList();
}
/// <summary>
/// [rad/s] => [Nm]
/// </summary>
/// <param name="angularFrequency">[rad/s]</param>
/// <returns>[Nm]</returns>
public NewtonMeter FullLoadStationaryTorque(RadianPerSecond angularFrequency)
{
var idx = FindIndex(angularFrequency);
return VectoMath.Interpolate((double) _entries[idx - 1].EngineSpeed, (double) _entries[idx].EngineSpeed,
(double) _entries[idx - 1].TorqueFullLoad, (double) _entries[idx].TorqueFullLoad,
(double) angularFrequency).SI<NewtonMeter>();
}
/// <summary>
/// [rad/s] => [W]
/// </summary>
/// <param name="angularFrequency">[rad/s]</param>
/// <returns>[W]</returns>
public Watt FullLoadStationaryPower(RadianPerSecond angularFrequency)
{
return Formulas.TorqueToPower(FullLoadStationaryTorque(angularFrequency), angularFrequency);
}
/// <summary>
/// [rad/s] => [Nm]
/// </summary>
/// <param name="angularFrequency">[rad/s]</param>
/// <returns>[Nm]</returns>
public NewtonMeter DragLoadStationaryTorque(RadianPerSecond angularFrequency)
{
var idx = FindIndex(angularFrequency);
return VectoMath.Interpolate((double) _entries[idx - 1].EngineSpeed, (double) _entries[idx].EngineSpeed,
(double) _entries[idx - 1].TorqueDrag, (double) _entries[idx].TorqueDrag,
(double) angularFrequency).SI<NewtonMeter>();
}
/// <summary>
/// [rad/s] => [W].
/// </summary>
/// <param name="angularFrequency">[rad/s]</param>
/// <returns>[W]</returns>
public Watt DragLoadStationaryPower(RadianPerSecond angularFrequency)
{
Contract.Requires(angularFrequency.HasEqualUnit(new SI().Radian.Per.Second));
Contract.Ensures(Contract.Result<SI>().HasEqualUnit(new SI().Watt));
return Formulas.TorqueToPower(DragLoadStationaryTorque(angularFrequency), angularFrequency);
}
/// <summary>
/// [rad/s] => [-]
/// </summary>
/// <param name="angularFrequency">[rad/s]</param>
/// <returns>[-]</returns>
public SI PT1(SI angularFrequency)
{
Contract.Requires(angularFrequency.HasEqualUnit(new SI().Radian.Per.Second));
Contract.Ensures(Contract.Result<SI>().HasEqualUnit(new SI()));
var idx = FindIndex(angularFrequency);
return VectoMath.Interpolate((double) _entries[idx - 1].EngineSpeed, (double) _entries[idx].EngineSpeed,
(double) _entries[idx - 1].PT1, (double) _entries[idx].PT1,
(double) angularFrequency).SI();
}
/// <summary>
/// [rad/s] => index. Get item index for engineSpeed.
/// </summary>
/// <param name="engineSpeed">[rad/s]</param>
/// <returns>index</returns>
protected int FindIndex(SI engineSpeed)
{
Contract.Requires(engineSpeed.HasEqualUnit(new SI().Radian.Per.Second));
int idx;
if (engineSpeed < _entries[0].EngineSpeed) {
Log.ErrorFormat("requested rpm below minimum rpm in FLD curve - extrapolating. n: {0}, rpm_min: {1}",
engineSpeed.To().Rounds.Per.Minute, _entries[0].EngineSpeed.To().Rounds.Per.Minute);
idx = 1;
} else {
idx = _entries.FindIndex(x => x.EngineSpeed > engineSpeed);
}
if (idx <= 0) {
idx = engineSpeed > _entries[0].EngineSpeed ? _entries.Count - 1 : 1;
}
return idx;
}
private static class Fields
{
/// <summary>
/// [rpm] engine speed
/// </summary>
public const string EngineSpeed = "engine speed";
/// <summary>
/// [Nm] full load torque
/// </summary>
public const string TorqueFullLoad = "full load torque";
/// <summary>
/// [Nm] motoring torque
/// </summary>
public const string TorqueDrag = "motoring torque";
/// <summary>
/// [s] time constant
/// </summary>
public const string PT1 = "PT1";
}
private class FullLoadCurveEntry
{
/// <summary>
/// [rad/s] engine speed
/// </summary>
public RadianPerSecond EngineSpeed { get; set; }
/// <summary>
/// [Nm] full load torque
/// </summary>
public NewtonMeter TorqueFullLoad { get; set; }
/// <summary>
/// [Nm] motoring torque
/// </summary>
public NewtonMeter TorqueDrag { get; set; }
/// <summary>
/// [s] PT1 time constant
/// </summary>
public Second PT1 { get; set; }
#region Equality members
protected bool Equals(FullLoadCurveEntry other)
{
Contract.Requires(other != null);
return EngineSpeed.Equals(other.EngineSpeed)
&& TorqueFullLoad.Equals(other.TorqueFullLoad)
&& TorqueDrag.Equals(other.TorqueDrag)
&& PT1.Equals(other.PT1);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
return obj.GetType() == GetType() && Equals((FullLoadCurveEntry) obj);
}
public override int GetHashCode()
{
var hashCode = EngineSpeed.GetHashCode();
hashCode = (hashCode * 397) ^ TorqueFullLoad.GetHashCode();
hashCode = (hashCode * 397) ^ TorqueDrag.GetHashCode();
hashCode = (hashCode * 397) ^ PT1.GetHashCode();
return hashCode;
}
#endregion
}
#region Equality members
protected bool Equals(FullLoadCurve other)
{
return _entries.SequenceEqual(other._entries);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
return obj.GetType() == GetType() && Equals((FullLoadCurve) obj);
}
public override int GetHashCode()
{
return (_entries != null ? _entries.GetHashCode() : 0);
}
#endregion
}
public class FullLoadCurve : SimulationComponentData
{
[JsonProperty]
private List<FullLoadCurveEntry> _entries;
public static FullLoadCurve ReadFromFile(string fileName)
{
var data = VectoCSVFile.Read(fileName);
//todo Contract.Requires<VectoException>(data.Columns.Count != 4, "FullLoadCurve Data File must consist of 4 columns.");
if (data.Columns.Count != 4)
{
throw new VectoException("FullLoadCurve Data File must consist of 4 columns.");
}
//todo Contract.Requires<VectoException>(data.Rows.Count < 2, "FullLoadCurve must consist of at least two lines with numeric values (below file header)");
if (data.Rows.Count < 2)
{
throw new VectoException("FullLoadCurve must consist of at least two lines with numeric values (below file header)");
}
List<FullLoadCurveEntry> entries;
if (HeaderIsValid(data.Columns))
{
entries = CreateFromColumnNames(data);
}
else
{
var log = LogManager.GetLogger<FullLoadCurve>();
log.WarnFormat(
"FullLoadCurve: Header Line is not valid. Expected: '{0}, {1}, {2}, {3}', Got: '{4}'. Falling back to column index.",
Fields.EngineSpeed, Fields.TorqueFullLoad, Fields.TorqueDrag, Fields.PT1,
string.Join(", ", data.Columns.Cast<DataColumn>().Select(c => c.ColumnName).Reverse()));
entries = CreateFromColumnIndizes(data);
}
return new FullLoadCurve { _entries = entries };
}
private static bool HeaderIsValid(DataColumnCollection columns)
{
Contract.Requires(columns != null);
return columns.Contains(Fields.EngineSpeed)
&& columns.Contains(Fields.TorqueDrag)
&& columns.Contains(Fields.TorqueFullLoad)
&& columns.Contains(Fields.PT1);
}
private static List<FullLoadCurveEntry> CreateFromColumnNames(DataTable data)
{
Contract.Requires(data != null);
return (from DataRow row in data.Rows
select new FullLoadCurveEntry
{
EngineSpeed = row.ParseDouble(Fields.EngineSpeed).SI().Rounds.Per.Minute.To<RadianPerSecond>(),
TorqueFullLoad = row.ParseDouble(Fields.TorqueFullLoad).SI<NewtonMeter>(),
TorqueDrag = row.ParseDouble(Fields.TorqueDrag).SI<NewtonMeter>(),
PT1 = row.ParseDouble(Fields.PT1).SI<Second>()
}).ToList();
}
private static List<FullLoadCurveEntry> CreateFromColumnIndizes(DataTable data)
{
Contract.Requires(data != null);
return (from DataRow row in data.Rows
select new FullLoadCurveEntry
{
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>()
}).ToList();
}
/// <summary>
/// [rad/s] => [Nm]
/// </summary>
/// <param name="angularFrequency">[rad/s]</param>
/// <returns>[Nm]</returns>
public NewtonMeter FullLoadStationaryTorque(RadianPerSecond angularFrequency)
{
var idx = FindIndex(angularFrequency);
return VectoMath.Interpolate((double)_entries[idx - 1].EngineSpeed, (double)_entries[idx].EngineSpeed,
(double)_entries[idx - 1].TorqueFullLoad, (double)_entries[idx].TorqueFullLoad,
(double)angularFrequency).SI<NewtonMeter>();
}
/// <summary>
/// [rad/s] => [W]
/// </summary>
/// <param name="angularFrequency">[rad/s]</param>
/// <returns>[W]</returns>
public Watt FullLoadStationaryPower(RadianPerSecond angularFrequency)
{
return Formulas.TorqueToPower(FullLoadStationaryTorque(angularFrequency), angularFrequency);
}
/// <summary>
/// [rad/s] => [Nm]
/// </summary>
/// <param name="angularFrequency">[rad/s]</param>
/// <returns>[Nm]</returns>
public NewtonMeter DragLoadStationaryTorque(RadianPerSecond angularFrequency)
{
var idx = FindIndex(angularFrequency);
return VectoMath.Interpolate((double)_entries[idx - 1].EngineSpeed, (double)_entries[idx].EngineSpeed,
(double)_entries[idx - 1].TorqueDrag, (double)_entries[idx].TorqueDrag,
(double)angularFrequency).SI<NewtonMeter>();
}
/// <summary>
/// [rad/s] => [W].
/// </summary>
/// <param name="angularFrequency">[rad/s]</param>
/// <returns>[W]</returns>
public Watt DragLoadStationaryPower(RadianPerSecond angularFrequency)
{
Contract.Requires(angularFrequency.HasEqualUnit(new SI().Radian.Per.Second));
Contract.Ensures(Contract.Result<SI>().HasEqualUnit(new SI().Watt));
return Formulas.TorqueToPower(DragLoadStationaryTorque(angularFrequency), angularFrequency);
}
/// <summary>
/// [rad/s] => [-]
/// </summary>
/// <param name="angularFrequency">[rad/s]</param>
/// <returns>[-]</returns>
public SI PT1(SI angularFrequency)
{
Contract.Requires(angularFrequency.HasEqualUnit(new SI().Radian.Per.Second));
Contract.Ensures(Contract.Result<SI>().HasEqualUnit(new SI()));
var idx = FindIndex(angularFrequency);
return VectoMath.Interpolate((double)_entries[idx - 1].EngineSpeed, (double)_entries[idx].EngineSpeed,
(double)_entries[idx - 1].PT1, (double)_entries[idx].PT1,
(double)angularFrequency).SI();
}
/// <summary>
/// [rad/s] => index. Get item index for engineSpeed.
/// </summary>
/// <param name="engineSpeed">[rad/s]</param>
/// <returns>index</returns>
protected int FindIndex(SI engineSpeed)
{
Contract.Requires(engineSpeed.HasEqualUnit(new SI().Radian.Per.Second));
int idx;
if (engineSpeed < _entries[0].EngineSpeed)
{
Log.ErrorFormat("requested rpm below minimum rpm in FLD curve - extrapolating. n: {0}, rpm_min: {1}",
engineSpeed.To().Rounds.Per.Minute, _entries[0].EngineSpeed.To().Rounds.Per.Minute);
idx = 1;
}
else
{
idx = _entries.FindIndex(x => x.EngineSpeed > engineSpeed);
}
if (idx <= 0)
{
idx = engineSpeed > _entries[0].EngineSpeed ? _entries.Count - 1 : 1;
}
return idx;
}
private static class Fields
{
/// <summary>
/// [rpm] engine speed
/// </summary>
public const string EngineSpeed = "engine speed";
/// <summary>
/// [Nm] full load torque
/// </summary>
public const string TorqueFullLoad = "full load torque";
/// <summary>
/// [Nm] motoring torque
/// </summary>
public const string TorqueDrag = "motoring torque";
/// <summary>
/// [s] time constant
/// </summary>
public const string PT1 = "PT1";
}
private class FullLoadCurveEntry
{
/// <summary>
/// [rad/s] engine speed
/// </summary>
public RadianPerSecond EngineSpeed { get; set; }
/// <summary>
/// [Nm] full load torque
/// </summary>
public NewtonMeter TorqueFullLoad { get; set; }
/// <summary>
/// [Nm] motoring torque
/// </summary>
public NewtonMeter TorqueDrag { get; set; }
/// <summary>
/// [s] PT1 time constant
/// </summary>
public Second PT1 { get; set; }
#region Equality members
protected bool Equals(FullLoadCurveEntry other)
{
Contract.Requires(other != null);
return EngineSpeed.Equals(other.EngineSpeed)
&& TorqueFullLoad.Equals(other.TorqueFullLoad)
&& TorqueDrag.Equals(other.TorqueDrag)
&& PT1.Equals(other.PT1);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
return obj.GetType() == GetType() && Equals((FullLoadCurveEntry)obj);
}
public override int GetHashCode()
{
var hashCode = EngineSpeed.GetHashCode();
hashCode = (hashCode * 397) ^ TorqueFullLoad.GetHashCode();
hashCode = (hashCode * 397) ^ TorqueDrag.GetHashCode();
hashCode = (hashCode * 397) ^ PT1.GetHashCode();
return hashCode;
}
#endregion
}
#region Equality members
protected bool Equals(FullLoadCurve other)
{
return _entries.SequenceEqual(other._entries);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
return obj.GetType() == GetType() && Equals((FullLoadCurve)obj);
}
public override int GetHashCode()
{
return (_entries != null ? _entries.GetHashCode() : 0);
}
#endregion
public RadianPerSecond RatedSpeed()
{
var powerMax = new Watt();
var engineSpeed = new RadianPerSecond();
var engineSpeedMax = new RadianPerSecond();
var engineSpeedRated = new RadianPerSecond();
var engineSpeedStep = 1.0.RPMtoRad();
var power = new Watt();
engineSpeed = _entries.First().EngineSpeed;
engineSpeedMax = _entries.Last().EngineSpeed;
engineSpeedRated = engineSpeed;
do {
power = Formulas.TorqueToPower(FullLoadStationaryTorque(engineSpeed), engineSpeed);
if (power > powerMax) {
powerMax = power;
engineSpeedRated = engineSpeed;
}
engineSpeed = (engineSpeed + engineSpeedStep).To<RadianPerSecond>();
} while (engineSpeed <= engineSpeedMax);
return engineSpeedRated;
}
}
}
\ No newline at end of file
......@@ -13,14 +13,16 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
{
public class Clutch:VectoSimulationComponent, IClutch,ITnOutPort,ITnInPort
{
public Clutch(IVehicleContainer cockpit, CombustionEngineData engineData) : base(cockpit)
{
//engineData.IdleSpeed;
private RadianPerSecond _idleSpeed;
private RadianPerSecond _ratedSpeed;
//engineData.GetFullLoadCurve(0).RatedSpeed();
public Clutch(IVehicleContainer cockpit, CombustionEngineData engineData) : base(cockpit)
{
_idleSpeed = engineData.IdleSpeed;
_ratedSpeed= engineData.GetFullLoadCurve(0).RatedSpeed();
}
public override void CommitSimulationStep(IModalDataWriter writer)
{
throw new NotImplementedException();
......
......@@ -21,6 +21,13 @@ namespace TUGraz.VectoCore.Tests.Models.SimulationComponentData
Assert.AreEqual(1231, (double) fldCurve.FullLoadStationaryTorque(580.0.RPMtoRad()), Tolerance);
}
[TestMethod]
public void TestFullLoadEngineSpeedRated()
{
var fldCurve = FullLoadCurve.ReadFromFile(CoachEngineFLD);
Assert.AreEqual(181.79349, (double)fldCurve.RatedSpeed(), Tolerance);
}
[TestMethod]
public void TestFullLoadStaticPower()
{
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment