Code development platform for open source projects from the European Union institutions

Skip to content
Snippets Groups Projects
Commit 9277afdc authored by Markus Quaritsch's avatar Markus Quaritsch
Browse files

more generous parsing of input files (loss maps, acceleration curve, ...)

parent 955105a6
No related branches found
No related tags found
No related merge requests found
......@@ -9,158 +9,203 @@ using TUGraz.VectoCore.Utils;
namespace TUGraz.VectoCore.Models.SimulationComponent.Data
{
public class AccelerationCurveData : SimulationComponentData
{
private List<KeyValuePair<MeterPerSecond, AccelerationEntry>> _entries;
public static AccelerationCurveData ReadFromStream(Stream stream)
{
var data = VectoCSVFile.ReadStream(stream);
return ParseData(data);
}
public static AccelerationCurveData ReadFromFile(string fileName)
{
var data = VectoCSVFile.Read(fileName);
return ParseData(data);
}
private static AccelerationCurveData ParseData(DataTable data)
{
if (data.Columns.Count != 3) {
throw new VectoException("Acceleration Limiting File must consist of 3 columns.");
}
if (data.Rows.Count < 2) {
throw new VectoException("Acceleration Limiting File must consist of at least two entries.");
}
return new AccelerationCurveData {
_entries = data.Rows.Cast<DataRow>()
.Select(r => new KeyValuePair<MeterPerSecond, AccelerationEntry>(
r.ParseDouble("v").KMPHtoMeterPerSecond(),
new AccelerationEntry {
Acceleration = r.ParseDouble("acc").SI<MeterPerSquareSecond>(),
Deceleration = r.ParseDouble("dec").SI<MeterPerSquareSecond>()
}))
.OrderBy(x => x.Key)
.ToList()
};
}
public AccelerationEntry Lookup(MeterPerSecond key)
{
var index = FindIndex(key);
return new AccelerationEntry {
Acceleration =
VectoMath.Interpolate(_entries[index - 1].Key, _entries[index].Key, _entries[index - 1].Value.Acceleration,
_entries[index].Value.Acceleration, key),
Deceleration =
VectoMath.Interpolate(_entries[index - 1].Key, _entries[index].Key, _entries[index - 1].Value.Deceleration,
_entries[index].Value.Deceleration, key)
};
}
protected int FindIndex(MeterPerSecond key)
{
var index = 1;
if (key < _entries[0].Key) {
Log.Error("requested velocity below minimum - extrapolating. velocity: {0}, min: {1}",
key.ConvertTo().Kilo.Meter.Per.Hour, _entries[0].Key.ConvertTo().Kilo.Meter.Per.Hour);
} else {
index = _entries.FindIndex(x => x.Key > key);
if (index <= 0) {
index = (key > _entries[0].Key) ? _entries.Count - 1 : 1;
}
}
return index;
}
public MeterPerSquareSecond MinDeceleration()
{
return _entries.Max(x => x.Value.Deceleration);
}
public MeterPerSquareSecond MaxDeceleration()
{
return _entries.Min(x => x.Value.Deceleration);
}
public MeterPerSquareSecond MinAcceleration()
{
return _entries.Min(x => x.Value.Acceleration);
}
public MeterPerSquareSecond MaxAcceleration()
{
return _entries.Max(x => x.Value.Acceleration);
}
[DebuggerDisplay("Acceleration: {Acceleration}, Deceleration: {Deceleration}")]
public class AccelerationEntry
{
public MeterPerSquareSecond Acceleration { get; set; }
public MeterPerSquareSecond Deceleration { get; set; }
}
/// <summary>
///
/// </summary>
/// <param name="v1">current speed of the vehicle</param>
/// <param name="v2">desired speed of the vehicle at the end of acceleration/deceleration phase</param>
/// <returns>distance required to accelerate/decelerate the vehicle from v1 to v2 according to the acceleration curve</returns>
public Meter ComputeAccelerationDistance(MeterPerSecond v1, MeterPerSecond v2)
{
var index1 = FindIndex(v1);
var index2 = FindIndex(v2);
var distance = 0.SI<Meter>();
for (var i = index2; i <= index1; i++) {
distance += ComputeAccelerationSegmentDistance(i, v1, v2);
}
return distance;
}
/// <summary>
///
/// </summary>
/// <param name="i">segment of the acceleration curve to use [(i-1) ... i]</param>
/// <param name="v1">current speed of the vehicle</param>
/// <param name="v2">desired speed of the vehicle at the end of acceleration/deceleration phase</param>
/// <returns>distance required to accelerate/decelerate the vehicle from v1 to v2 according to the acceleration curve</returns>
private Meter ComputeAccelerationSegmentDistance(int i, MeterPerSecond v1, MeterPerSecond v2)
{
var leftEntry = _entries[i - 1]; // entry with lower velocity
var rightEntry = _entries[i]; // entry with higher velocity
v2 = VectoMath.Max(v2, leftEntry.Key); // min. velocity within current segment
v1 = VectoMath.Min(v1, rightEntry.Key); // max. velocity within current segment
if (leftEntry.Value.Deceleration.IsEqual(rightEntry.Value.Deceleration)) {
// v(t) = a * t + v1 => t = (v2 - v1) / a
// s(t) = a/2 * t^2 + v1 * t + s0 {s0 == 0} => s(t)
var acceleration = v2 > v1 ? leftEntry.Value.Acceleration : leftEntry.Value.Deceleration;
return ((v2 - v1) * (v2 - v1) / 2.0 / acceleration + v1 * (v2 - v1) / acceleration).Cast<Meter>();
}
// a(v) = k * v + d
// dv/dt = a(v) = d * v + d ==> v(t) = sgn(k * v1 + d) * exp(-k * c) / k * exp(t * k) - d / k
// v(0) = v1 => c = - ln(|v1 * k + d|) / k
// v(t) = (v1 + d / k) * exp(t * k) - d / k => t = 1 / k * ln((v2 * k + d) / (v1 * k + d))
// s(t) = m / k* exp(t * k) + b * t + c' {m = v1 + d / k, b = -d / k}
var k = (leftEntry.Value.Deceleration - rightEntry.Value.Deceleration) / (leftEntry.Key - rightEntry.Key);
var d = leftEntry.Value.Deceleration - k * leftEntry.Key;
if (v2 > v1) {
k = (leftEntry.Value.Acceleration - rightEntry.Value.Acceleration) / (leftEntry.Key - rightEntry.Key);
d = leftEntry.Value.Acceleration - k * leftEntry.Key;
}
var m = v1 + d / k;
var b = -d / k;
var c = 0.SI<Meter>() - m / k;
var t = Math.Log(((v2 * k + d) / (v1 * k + d)).Cast<Scalar>()) / k;
return m / k * Math.Exp((k * t).Value()) + b * t + c;
}
}
public class AccelerationCurveData : SimulationComponentData
{
private List<KeyValuePair<MeterPerSecond, AccelerationEntry>> _entries;
public static AccelerationCurveData ReadFromStream(Stream stream)
{
var data = VectoCSVFile.ReadStream(stream);
return ParseData(data);
}
public static AccelerationCurveData ReadFromFile(string fileName)
{
var data = VectoCSVFile.Read(fileName);
return ParseData(data);
}
private static AccelerationCurveData ParseData(DataTable data)
{
if (data.Columns.Count != 3) {
throw new VectoException("Acceleration Limiting File must consist of 3 columns.");
}
if (data.Rows.Count < 2) {
throw new VectoException("Acceleration Limiting File must consist of at least two entries.");
}
if (HeaderIsValid(data.Columns)) {
return CreateFromColumnNames(data);
}
Logger<AccelerationCurveData>().Warn(
"Acceleration Curve: Header Line is not valid. Expected: '{0}, {1}, {2}', Got: {3}",
Fields.Velocity, Fields.Acceleration, Fields.Deceleration,
string.Join(", ", data.Columns.Cast<DataColumn>().Select(c => c.ColumnName)));
return CreateFromColumnIndizes(data);
}
private static AccelerationCurveData CreateFromColumnIndizes(DataTable data)
{
return new AccelerationCurveData {
_entries = data.Rows.Cast<DataRow>()
.Select(r => new KeyValuePair<MeterPerSecond, AccelerationEntry>(
r.ParseDouble(0).KMPHtoMeterPerSecond(),
new AccelerationEntry {
Acceleration = r.ParseDouble(1).SI<MeterPerSquareSecond>(),
Deceleration = r.ParseDouble(2).SI<MeterPerSquareSecond>()
}))
.OrderBy(x => x.Key)
.ToList()
};
}
private static AccelerationCurveData CreateFromColumnNames(DataTable data)
{
return new AccelerationCurveData {
_entries = data.Rows.Cast<DataRow>()
.Select(r => new KeyValuePair<MeterPerSecond, AccelerationEntry>(
r.ParseDouble(Fields.Velocity).KMPHtoMeterPerSecond(),
new AccelerationEntry {
Acceleration = r.ParseDouble(Fields.Acceleration).SI<MeterPerSquareSecond>(),
Deceleration = r.ParseDouble(Fields.Deceleration).SI<MeterPerSquareSecond>()
}))
.OrderBy(x => x.Key)
.ToList()
};
}
private static bool HeaderIsValid(DataColumnCollection columns)
{
return columns.Contains(Fields.Velocity) &&
columns.Contains(Fields.Acceleration) &&
columns.Contains(Fields.Deceleration);
}
public AccelerationEntry Lookup(MeterPerSecond key)
{
var index = FindIndex(key);
return new AccelerationEntry {
Acceleration =
VectoMath.Interpolate(_entries[index - 1].Key, _entries[index].Key,
_entries[index - 1].Value.Acceleration,
_entries[index].Value.Acceleration, key),
Deceleration =
VectoMath.Interpolate(_entries[index - 1].Key, _entries[index].Key,
_entries[index - 1].Value.Deceleration,
_entries[index].Value.Deceleration, key)
};
}
protected int FindIndex(MeterPerSecond key)
{
var index = 1;
if (key < _entries[0].Key) {
Log.Error("requested velocity below minimum - extrapolating. velocity: {0}, min: {1}",
key.ConvertTo().Kilo.Meter.Per.Hour, _entries[0].Key.ConvertTo().Kilo.Meter.Per.Hour);
} else {
index = _entries.FindIndex(x => x.Key > key);
if (index <= 0) {
index = (key > _entries[0].Key) ? _entries.Count - 1 : 1;
}
}
return index;
}
public MeterPerSquareSecond MinDeceleration()
{
return _entries.Max(x => x.Value.Deceleration);
}
public MeterPerSquareSecond MaxDeceleration()
{
return _entries.Min(x => x.Value.Deceleration);
}
public MeterPerSquareSecond MinAcceleration()
{
return _entries.Min(x => x.Value.Acceleration);
}
public MeterPerSquareSecond MaxAcceleration()
{
return _entries.Max(x => x.Value.Acceleration);
}
[DebuggerDisplay("Acceleration: {Acceleration}, Deceleration: {Deceleration}")]
public class AccelerationEntry
{
public MeterPerSquareSecond Acceleration { get; set; }
public MeterPerSquareSecond Deceleration { get; set; }
}
/// <summary>
///
/// </summary>
/// <param name="v1">current speed of the vehicle</param>
/// <param name="v2">desired speed of the vehicle at the end of acceleration/deceleration phase</param>
/// <returns>distance required to accelerate/decelerate the vehicle from v1 to v2 according to the acceleration curve</returns>
public Meter ComputeAccelerationDistance(MeterPerSecond v1, MeterPerSecond v2)
{
var index1 = FindIndex(v1);
var index2 = FindIndex(v2);
var distance = 0.SI<Meter>();
for (var i = index2; i <= index1; i++) {
distance += ComputeAccelerationSegmentDistance(i, v1, v2);
}
return distance;
}
/// <summary>
///
/// </summary>
/// <param name="i">segment of the acceleration curve to use [(i-1) ... i]</param>
/// <param name="v1">current speed of the vehicle</param>
/// <param name="v2">desired speed of the vehicle at the end of acceleration/deceleration phase</param>
/// <returns>distance required to accelerate/decelerate the vehicle from v1 to v2 according to the acceleration curve</returns>
private Meter ComputeAccelerationSegmentDistance(int i, MeterPerSecond v1, MeterPerSecond v2)
{
var leftEntry = _entries[i - 1]; // entry with lower velocity
var rightEntry = _entries[i]; // entry with higher velocity
v2 = VectoMath.Max(v2, leftEntry.Key); // min. velocity within current segment
v1 = VectoMath.Min(v1, rightEntry.Key); // max. velocity within current segment
if (leftEntry.Value.Deceleration.IsEqual(rightEntry.Value.Deceleration)) {
// v(t) = a * t + v1 => t = (v2 - v1) / a
// s(t) = a/2 * t^2 + v1 * t + s0 {s0 == 0} => s(t)
var acceleration = v2 > v1 ? leftEntry.Value.Acceleration : leftEntry.Value.Deceleration;
return ((v2 - v1) * (v2 - v1) / 2.0 / acceleration + v1 * (v2 - v1) / acceleration).Cast<Meter>();
}
// a(v) = k * v + d
// dv/dt = a(v) = d * v + d ==> v(t) = sgn(k * v1 + d) * exp(-k * c) / k * exp(t * k) - d / k
// v(0) = v1 => c = - ln(|v1 * k + d|) / k
// v(t) = (v1 + d / k) * exp(t * k) - d / k => t = 1 / k * ln((v2 * k + d) / (v1 * k + d))
// s(t) = m / k* exp(t * k) + b * t + c' {m = v1 + d / k, b = -d / k}
var k = (leftEntry.Value.Deceleration - rightEntry.Value.Deceleration) / (leftEntry.Key - rightEntry.Key);
var d = leftEntry.Value.Deceleration - k * leftEntry.Key;
if (v2 > v1) {
k = (leftEntry.Value.Acceleration - rightEntry.Value.Acceleration) / (leftEntry.Key - rightEntry.Key);
d = leftEntry.Value.Acceleration - k * leftEntry.Key;
}
var m = v1 + d / k;
var b = -d / k;
var c = 0.SI<Meter>() - m / k;
var t = Math.Log(((v2 * k + d) / (v1 * k + d)).Cast<Scalar>()) / k;
return m / k * Math.Exp((k * t).Value()) + b * t + c;
}
private static class Fields
{
public const string Velocity = "v";
public const string Acceleration = "acc";
public const string Deceleration = "dec";
}
}
}
\ No newline at end of file
using System.Collections;
using System.Data;
using System.IO;
using System.Linq;
......@@ -7,50 +8,85 @@ using TUGraz.VectoCore.Utils;
namespace TUGraz.VectoCore.Models.SimulationComponent.Data
{
public class AuxiliaryData
{
public double EfficiencyToSupply { get; set; }
public double TransitionRatio { get; set; }
public double EfficiencyToEngine { get; set; }
private readonly DelauneyMap _map = new DelauneyMap();
public Watt GetPowerDemand(PerSecond nAuxiliary, Watt powerAuxOut)
{
return _map.Interpolate(nAuxiliary.Value(), powerAuxOut.Value()).SI<Watt>();
}
public static AuxiliaryData ReadFromFile(string fileName)
{
var auxData = new AuxiliaryData();
try {
var stream = new StreamReader(fileName);
stream.ReadLine(); // skip header "Transmission ration to engine rpm [-]"
auxData.TransitionRatio = stream.ReadLine().ToDouble();
stream.ReadLine(); // skip header "Efficiency to engine [-]"
auxData.EfficiencyToEngine = stream.ReadLine().ToDouble();
stream.ReadLine(); // skip header "Efficiency auxiliary to supply [-]"
auxData.EfficiencyToSupply = stream.ReadLine().ToDouble();
var m = new MemoryStream(Encoding.UTF8.GetBytes(stream.ReadToEnd()));
var table = VectoCSVFile.ReadStream(m);
var data = table.Rows.Cast<DataRow>().Select(row => new {
AuxiliarySpeed = row.ParseDouble("Auxiliary speed").RPMtoRad(),
MechanicalPower = row.ParseDouble("Mechanical power").SI().Kilo.Watt.Cast<Watt>(),
SupplyPower = row.ParseDouble("Supply power").SI().Kilo.Watt.Cast<Watt>()
});
foreach (var d in data) {
auxData._map.AddPoint(d.AuxiliarySpeed.Value(), d.SupplyPower.Value(), d.MechanicalPower.Value());
}
auxData._map.Triangulate();
return auxData;
} catch (FileNotFoundException e) {
throw new VectoException("Auxiliary file not found: " + fileName, e);
}
}
}
public class AuxiliaryData
{
public double EfficiencyToSupply { get; set; }
public double TransitionRatio { get; set; }
public double EfficiencyToEngine { get; set; }
private readonly DelauneyMap _map = new DelauneyMap();
public Watt GetPowerDemand(PerSecond nAuxiliary, Watt powerAuxOut)
{
return _map.Interpolate(nAuxiliary.Value(), powerAuxOut.Value()).SI<Watt>();
}
public static AuxiliaryData ReadFromFile(string fileName)
{
var auxData = new AuxiliaryData();
try {
var stream = new StreamReader(fileName);
stream.ReadLine(); // skip header "Transmission ration to engine rpm [-]"
auxData.TransitionRatio = stream.ReadLine().IndulgentParse();
stream.ReadLine(); // skip header "Efficiency to engine [-]"
auxData.EfficiencyToEngine = stream.ReadLine().IndulgentParse();
stream.ReadLine(); // skip header "Efficiency auxiliary to supply [-]"
auxData.EfficiencyToSupply = stream.ReadLine().IndulgentParse();
var m = new MemoryStream(Encoding.UTF8.GetBytes(stream.ReadToEnd()));
var table = VectoCSVFile.ReadStream(m);
// todo: @@@ check for valid header columns, otherwise use index
if (HeaderIsValid(table.Columns)) {
FillFromColumnNames(table, auxData._map);
} else {
FillFromColumnIndizes(table, auxData._map);
}
auxData._map.Triangulate();
return auxData;
} catch (FileNotFoundException e) {
throw new VectoException("Auxiliary file not found: " + fileName, e);
}
}
private static void FillFromColumnIndizes(DataTable table, DelauneyMap map)
{
var data = table.Rows.Cast<DataRow>().Select(row => new {
AuxiliarySpeed = row.ParseDouble(0).RPMtoRad(),
MechanicalPower = row.ParseDouble(1).SI().Kilo.Watt.Cast<Watt>(),
SupplyPower = row.ParseDouble(2).SI().Kilo.Watt.Cast<Watt>()
});
foreach (var d in data) {
map.AddPoint(d.AuxiliarySpeed.Value(), d.SupplyPower.Value(), d.MechanicalPower.Value());
}
}
private static void FillFromColumnNames(DataTable table, DelauneyMap map)
{
var data = table.Rows.Cast<DataRow>().Select(row => new {
AuxiliarySpeed = row.ParseDouble(Fields.AuxSpeed).RPMtoRad(),
MechanicalPower = row.ParseDouble(Fields.MechPower).SI().Kilo.Watt.Cast<Watt>(),
SupplyPower = row.ParseDouble(Fields.SupplyPower).SI().Kilo.Watt.Cast<Watt>()
});
foreach (var d in data) {
map.AddPoint(d.AuxiliarySpeed.Value(), d.SupplyPower.Value(), d.MechanicalPower.Value());
}
}
private static bool HeaderIsValid(DataColumnCollection columns)
{
return columns.Contains(Fields.AuxSpeed) && columns.Contains(Fields.MechPower) &&
columns.Contains(Fields.SupplyPower);
}
private static class Fields
{
public const string AuxSpeed = "Auxiliary speed";
public const string MechPower = "Mechanical power";
public const string SupplyPower = "Supply power";
}
}
}
\ No newline at end of file
......@@ -9,166 +9,194 @@ using TUGraz.VectoCore.Utils;
namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine
{
[JsonObject(MemberSerialization.Fields)]
public class FuelConsumptionMap : SimulationComponentData
{
private readonly IList<FuelConsumptionEntry> _entries = new List<FuelConsumptionEntry>();
private readonly DelauneyMap _fuelMap = new DelauneyMap();
private FuelConsumptionMap() {}
public static FuelConsumptionMap ReadFromFile(string fileName)
{
var fuelConsumptionMap = new FuelConsumptionMap();
var data = VectoCSVFile.Read(fileName);
try {
foreach (DataRow row in data.Rows) {
try {
var entry = new FuelConsumptionEntry {
EngineSpeed =
row.ParseDouble(Fields.EngineSpeed).SI().Rounds.Per.Minute.Cast<PerSecond>(),
Torque = row.ParseDouble(Fields.Torque).SI<NewtonMeter>(),
FuelConsumption =
row.ParseDouble(Fields.FuelConsumption).SI().Gramm.Per.Hour.ConvertTo().Kilo.Gramm.Per.Second
};
if (entry.FuelConsumption < 0) {
throw new ArgumentOutOfRangeException("FuelConsumption", "FuelConsumption < 0 not allowed.");
}
fuelConsumptionMap._entries.Add(entry);
// Delauney map works only as expected, when the engineSpeed is in rpm.
fuelConsumptionMap._fuelMap.AddPoint(entry.Torque.Value(), row.ParseDouble(Fields.EngineSpeed),
entry.FuelConsumption.Value());
} catch (Exception e) {
throw new VectoException(string.Format("Line {0}: {1}", data.Rows.IndexOf(row), e.Message), e);
}
}
} catch (Exception e) {
throw new VectoException(string.Format("File {0}: {1}", fileName, e.Message), e);
}
fuelConsumptionMap._fuelMap.Triangulate();
return fuelConsumptionMap;
}
/// <summary>
/// [kg/s] Calculates the fuel consumption based on the given fuel map,
/// the engineSpeed [rad/s] and the torque [Nm].
/// </summary>
/// <param name="engineSpeed">[rad/sec]</param>
/// <param name="torque">[Nm]</param>
/// <returns>[kg/s]</returns>
public KilogramPerSecond GetFuelConsumption(NewtonMeter torque, PerSecond engineSpeed)
{
// delauney map needs is initialised with rpm, therefore the engineSpeed has to be converted.
return
_fuelMap.Interpolate(torque.Value(), engineSpeed.ConvertTo().Rounds.Per.Minute.Value())
.SI()
.Kilo.Gramm.Per.Second.Cast<KilogramPerSecond>();
}
private static class Fields
{
/// <summary>
/// [rpm]
/// </summary>
public const string EngineSpeed = "engine speed";
/// <summary>
/// [Nm]
/// </summary>
public const string Torque = "torque";
/// <summary>
/// [g/h]
/// </summary>
public const string FuelConsumption = "fuel consumption";
};
private class FuelConsumptionEntry
{
/// <summary>
/// engine speed [rad/s]
/// </summary>
public PerSecond EngineSpeed { get; set; }
/// <summary>
/// Torque [Nm]
/// </summary>
public NewtonMeter Torque { get; set; }
/// <summary>
/// Fuel consumption [kg/s]
/// </summary>
public SI FuelConsumption { get; set; }
#region Equality members
private bool Equals(FuelConsumptionEntry other)
{
Contract.Requires(other != null);
return EngineSpeed.Equals(other.EngineSpeed) && Torque.Equals(other.Torque) &&
FuelConsumption.Equals(other.FuelConsumption);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
if (obj.GetType() != GetType()) {
return false;
}
return Equals((FuelConsumptionEntry)obj);
}
public override int GetHashCode()
{
unchecked {
var hashCode = EngineSpeed.GetHashCode();
hashCode = (hashCode * 397) ^ Torque.GetHashCode();
hashCode = (hashCode * 397) ^ FuelConsumption.GetHashCode();
return hashCode;
}
}
#endregion
}
#region Equality members
protected bool Equals(FuelConsumptionMap other)
{
return _entries.SequenceEqual(other._entries) && Equals(_fuelMap, other._fuelMap);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
if (obj.GetType() != GetType()) {
return false;
}
return Equals((FuelConsumptionMap)obj);
}
public override int GetHashCode()
{
unchecked {
return ((_entries != null ? _entries.GetHashCode() : 0) * 397) ^
(_fuelMap != null ? _fuelMap.GetHashCode() : 0);
}
}
#endregion
}
[JsonObject(MemberSerialization.Fields)]
public class FuelConsumptionMap : SimulationComponentData
{
private readonly IList<FuelConsumptionEntry> _entries = new List<FuelConsumptionEntry>();
private readonly DelauneyMap _fuelMap = new DelauneyMap();
private FuelConsumptionMap() {}
public static FuelConsumptionMap ReadFromFile(string fileName)
{
var fuelConsumptionMap = new FuelConsumptionMap();
var data = VectoCSVFile.Read(fileName);
var headerValid = HeaderIsValid(data.Columns);
if (!headerValid) {
Logger<FuelConsumptionMap>().Warn(
"FuelConsumptionMap: Header Line is not valid. Expected: '{0}, {1}, {2}', Got: {3}",
Fields.EngineSpeed, Fields.Torque, Fields.FuelConsumption,
string.Join(", ", data.Columns.Cast<DataColumn>().Select(c => c.ColumnName)));
}
try {
foreach (DataRow row in data.Rows) {
try {
var entry = headerValid ? CreateFromColumNames(row) : CreateFromColumnIndizes(row);
if (entry.FuelConsumption < 0) {
throw new ArgumentOutOfRangeException("FuelConsumption", "FuelConsumption < 0 not allowed.");
}
fuelConsumptionMap._entries.Add(entry);
// Delauney map works only as expected, when the engineSpeed is in rpm.
fuelConsumptionMap._fuelMap.AddPoint(entry.Torque.Value(),
headerValid ? row.ParseDouble(Fields.EngineSpeed) : row.ParseDouble(0),
entry.FuelConsumption.Value());
} catch (Exception e) {
throw new VectoException(string.Format("Line {0}: {1}", data.Rows.IndexOf(row), e.Message), e);
}
}
} catch (Exception e) {
throw new VectoException(string.Format("File {0}: {1}", fileName, e.Message), e);
}
fuelConsumptionMap._fuelMap.Triangulate();
return fuelConsumptionMap;
}
private static bool HeaderIsValid(DataColumnCollection columns)
{
return columns.Contains(Fields.EngineSpeed) && columns.Contains(Fields.Torque) &&
columns.Contains(Fields.FuelConsumption);
}
private static FuelConsumptionEntry CreateFromColumnIndizes(DataRow row)
{
return new FuelConsumptionEntry {
EngineSpeed = row.ParseDouble(0).RPMtoRad(),
Torque = row.ParseDouble(1).SI<NewtonMeter>(),
FuelConsumption =
row.ParseDouble(2).SI().Gramm.Per.Hour.ConvertTo().Kilo.Gramm.Per.Second
};
}
private static FuelConsumptionEntry CreateFromColumNames(DataRow row)
{
return new FuelConsumptionEntry {
EngineSpeed = row.ParseDouble(Fields.EngineSpeed).SI().Rounds.Per.Minute.Cast<PerSecond>(),
Torque = row.ParseDouble(Fields.Torque).SI<NewtonMeter>(),
FuelConsumption =
row.ParseDouble(Fields.FuelConsumption).SI().Gramm.Per.Hour.ConvertTo().Kilo.Gramm.Per.Second
};
}
/// <summary>
/// [kg/s] Calculates the fuel consumption based on the given fuel map,
/// the engineSpeed [rad/s] and the torque [Nm].
/// </summary>
/// <param name="engineSpeed">[rad/sec]</param>
/// <param name="torque">[Nm]</param>
/// <returns>[kg/s]</returns>
public KilogramPerSecond GetFuelConsumption(NewtonMeter torque, PerSecond engineSpeed)
{
// delauney map needs is initialised with rpm, therefore the engineSpeed has to be converted.
return
_fuelMap.Interpolate(torque.Value(), engineSpeed.ConvertTo().Rounds.Per.Minute.Value())
.SI()
.Kilo.Gramm.Per.Second.Cast<KilogramPerSecond>();
}
private static class Fields
{
/// <summary>
/// [rpm]
/// </summary>
public const string EngineSpeed = "engine speed";
/// <summary>
/// [Nm]
/// </summary>
public const string Torque = "torque";
/// <summary>
/// [g/h]
/// </summary>
public const string FuelConsumption = "fuel consumption";
};
private class FuelConsumptionEntry
{
/// <summary>
/// engine speed [rad/s]
/// </summary>
public PerSecond EngineSpeed { get; set; }
/// <summary>
/// Torque [Nm]
/// </summary>
public NewtonMeter Torque { get; set; }
/// <summary>
/// Fuel consumption [kg/s]
/// </summary>
public SI FuelConsumption { get; set; }
#region Equality members
private bool Equals(FuelConsumptionEntry other)
{
Contract.Requires(other != null);
return EngineSpeed.Equals(other.EngineSpeed) && Torque.Equals(other.Torque) &&
FuelConsumption.Equals(other.FuelConsumption);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
if (obj.GetType() != GetType()) {
return false;
}
return Equals((FuelConsumptionEntry)obj);
}
public override int GetHashCode()
{
unchecked {
var hashCode = EngineSpeed.GetHashCode();
hashCode = (hashCode * 397) ^ Torque.GetHashCode();
hashCode = (hashCode * 397) ^ FuelConsumption.GetHashCode();
return hashCode;
}
}
#endregion
}
#region Equality members
protected bool Equals(FuelConsumptionMap other)
{
return _entries.SequenceEqual(other._entries) && Equals(_fuelMap, other._fuelMap);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
if (obj.GetType() != GetType()) {
return false;
}
return Equals((FuelConsumptionMap)obj);
}
public override int GetHashCode()
{
unchecked {
return ((_entries != null ? _entries.GetHashCode() : 0) * 397) ^
(_fuelMap != null ? _fuelMap.GetHashCode() : 0);
}
}
#endregion
}
}
\ No newline at end of file
This diff is collapsed.
......@@ -16,5 +16,10 @@ namespace TUGraz.VectoCore.Utils
{
return double.Parse(self, CultureInfo.InvariantCulture);
}
public static double IndulgentParse(this string self)
{
return double.Parse(new string(self.Trim().TakeWhile(c => char.IsDigit(c) || c == '.').ToArray()), CultureInfo.InvariantCulture);
}
}
}
\ No newline at end of file
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