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

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

adapt file-I/O to read new input files for gearbox, engine.

fix engine-only simulation runs / time-based run
parent c8de6cef
No related branches found
No related tags found
No related merge requests found
Showing
with 639 additions and 596 deletions
......@@ -34,7 +34,7 @@ namespace TUGraz.VectoCore.FileIO.DeclarationFile
/// }
/// }
/// </code>
internal class EngineFileV2Declaration : VectoEngineFile
internal class EngineFileV3Declaration : VectoEngineFile
{
[JsonProperty(Required = Required.Always)] public JsonDataHeader Header;
[JsonProperty(Required = Required.Always)] public DataBodyDecl Body;
......@@ -60,7 +60,8 @@ namespace TUGraz.VectoCore.FileIO.DeclarationFile
/// </summary>
[JsonProperty("IdlingSpeed", Required = Required.Always)] public double IdleSpeed;
[JsonProperty(Required = Required.Always)] public IList<DataFullLoadCurve> FullLoadCurves;
//[JsonProperty(Required = Required.Always)] public IList<DataFullLoadCurve> FullLoadCurves;
[JsonProperty(Required = Required.Always)] public string FullLoadCurve;
/// <summary>
/// The Fuel Consumption Map is used to calculate the base Fuel Consumption (FC) value.
......
......@@ -30,7 +30,7 @@ namespace TUGraz.VectoCore.FileIO.DeclarationFile
/// ...
/// ]
/// }
public class GearboxFileV4Declaration : VectoGearboxFile
public class GearboxFileV5Declaration : VectoGearboxFile
{
[JsonProperty(Required = Required.Always)] public JsonDataHeader Header;
[JsonProperty(Required = Required.Always)] public DataBodyDecl Body;
......@@ -53,6 +53,7 @@ namespace TUGraz.VectoCore.FileIO.DeclarationFile
{
[JsonProperty(Required = Required.Always)] public double Ratio;
[JsonProperty(Required = Required.Always)] public string LossMap;
[JsonProperty] public string FullLoadCurve;
}
}
}
\ No newline at end of file
......@@ -3,7 +3,7 @@ using TUGraz.VectoCore.FileIO.DeclarationFile;
namespace TUGraz.VectoCore.FileIO.EngineeringFile
{
internal class EngineFileV2Engineering : EngineFileV2Declaration
internal class EngineFileV3Engineering : EngineFileV3Declaration
{
[JsonProperty(Required = Required.Always)] public new DataBodyEng Body;
......
......@@ -48,7 +48,7 @@ namespace TUGraz.VectoCore.FileIO.EngineeringFile
/// ...
/// ]
/// }
public class GearboxFileV4Engineering : GearboxFileV4Declaration
public class GearboxFileV5Engineering : GearboxFileV5Declaration
{
[JsonProperty(Required = Required.Always)] public new DataBodyEng Body;
......
......@@ -51,7 +51,7 @@ namespace TUGraz.VectoCore.FileIO.Reader.DataObjectAdaper
return retVal;
}
internal CombustionEngineData SetCommonCombustionEngineData(EngineFileV2Declaration.DataBodyDecl data, string basePath)
internal CombustionEngineData SetCommonCombustionEngineData(EngineFileV3Declaration.DataBodyDecl data, string basePath)
{
var retVal = new CombustionEngineData() {
SavedInDeclarationMode = data.SavedInDeclarationMode,
......@@ -66,7 +66,7 @@ namespace TUGraz.VectoCore.FileIO.Reader.DataObjectAdaper
return retVal;
}
internal GearboxData SetCommonGearboxData(GearboxFileV4Declaration.DataBodyDecl data)
internal GearboxData SetCommonGearboxData(GearboxFileV5Declaration.DataBodyDecl data)
{
return new GearboxData() {
SavedInDeclarationMode = data.SavedInDeclarationMode,
......
......@@ -36,7 +36,7 @@ namespace TUGraz.VectoCore.FileIO.Reader.DataObjectAdaper
public override CombustionEngineData CreateEngineData(VectoEngineFile engine)
{
var fileV2Decl = engine as EngineFileV2Declaration;
var fileV2Decl = engine as EngineFileV3Declaration;
if (fileV2Decl != null) {
return CreateEngineData(fileV2Decl);
}
......@@ -45,7 +45,7 @@ namespace TUGraz.VectoCore.FileIO.Reader.DataObjectAdaper
public override GearboxData CreateGearboxData(VectoGearboxFile gearbox, CombustionEngineData engine)
{
var fileV5Decl = gearbox as GearboxFileV4Declaration;
var fileV5Decl = gearbox as GearboxFileV5Declaration;
if (fileV5Decl != null) {
return CreateGearboxData(fileV5Decl, engine);
}
......@@ -143,18 +143,17 @@ namespace TUGraz.VectoCore.FileIO.Reader.DataObjectAdaper
return retVal;
}
internal CombustionEngineData CreateEngineData(EngineFileV2Declaration engine)
internal CombustionEngineData CreateEngineData(EngineFileV3Declaration engine)
{
var retVal = SetCommonCombustionEngineData(engine.Body, engine.BasePath);
retVal.Inertia = DeclarationData.Engine.EngineInertia(retVal.Displacement);
foreach (var entry in engine.Body.FullLoadCurves) {
retVal.AddFullLoadCurve(entry.Gears, FullLoadCurve.ReadFromFile(Path.Combine(engine.BasePath, entry.Path), true));
}
retVal.FullLoadCurve = EngineFullLoadCurve.ReadFromFile(Path.Combine(engine.BasePath, engine.Body.FullLoadCurve),
true);
return retVal;
}
internal GearboxData CreateGearboxData(GearboxFileV4Declaration gearbox, CombustionEngineData engine)
internal GearboxData CreateGearboxData(GearboxFileV5Declaration gearbox, CombustionEngineData engine)
{
var retVal = SetCommonGearboxData(gearbox.Body);
......@@ -181,17 +180,29 @@ namespace TUGraz.VectoCore.FileIO.Reader.DataObjectAdaper
for (uint i = 0; i < gearbox.Body.Gears.Count; i++) {
var gearSettings = gearbox.Body.Gears[(int)i];
var lossMapPath = Path.Combine(gearbox.BasePath, gearSettings.LossMap);
TransmissionLossMap lossMap = TransmissionLossMap.ReadFromFile(lossMapPath, gearSettings.Ratio);
var lossMap = TransmissionLossMap.ReadFromFile(lossMapPath, gearSettings.Ratio);
if (i == 0) {
retVal.AxleGearData = new GearData(lossMap, null, gearSettings.Ratio, false);
retVal.AxleGearData = new GearData() {
LossMap = lossMap,
Ratio = gearSettings.Ratio,
TorqueConverterActive = false
};
} else {
var shiftPolygon = DeclarationData.Gearbox.ComputeShiftPolygon(engine, i);
retVal._gearData.Add(i, new GearData(lossMap, shiftPolygon, gearSettings.Ratio, false));
var fullLoad = !String.IsNullOrEmpty(gearSettings.FullLoadCurve) && gearSettings.FullLoadCurve.Equals("<NOFILE>")
? GearFullLoadCurve.ReadFromFile(Path.Combine(gearbox.BasePath, gearSettings.FullLoadCurve))
: null;
var shiftPolygon = DeclarationData.Gearbox.ComputeShiftPolygon(fullLoad, engine);
retVal._gearData.Add(i, new GearData() {
LossMap = lossMap,
ShiftPolygon = shiftPolygon,
Ratio = gearSettings.Ratio,
TorqueConverterActive = false
});
}
}
return retVal;
}
}
......
......@@ -31,7 +31,7 @@ namespace TUGraz.VectoCore.FileIO.Reader.DataObjectAdaper
public override CombustionEngineData CreateEngineData(VectoEngineFile engine)
{
var fileV2Eng = engine as EngineFileV2Engineering;
var fileV2Eng = engine as EngineFileV3Engineering;
if (fileV2Eng != null) {
return CreateEngineData(fileV2Eng);
}
......@@ -40,7 +40,7 @@ namespace TUGraz.VectoCore.FileIO.Reader.DataObjectAdaper
public override GearboxData CreateGearboxData(VectoGearboxFile gearbox, CombustionEngineData engine)
{
var fileV5Eng = gearbox as GearboxFileV4Engineering;
var fileV5Eng = gearbox as GearboxFileV5Engineering;
if (fileV5Eng != null) {
return CreateGearboxData(fileV5Eng);
}
......@@ -125,13 +125,12 @@ namespace TUGraz.VectoCore.FileIO.Reader.DataObjectAdaper
/// </summary>
/// <param name="engine">Engin-Data file (Engineering mode)</param>
/// <returns></returns>
internal CombustionEngineData CreateEngineData(EngineFileV2Engineering engine)
internal CombustionEngineData CreateEngineData(EngineFileV3Engineering engine)
{
var retVal = SetCommonCombustionEngineData(engine.Body, engine.BasePath);
retVal.Inertia = engine.Body.Inertia.SI<KilogramSquareMeter>();
foreach (var entry in engine.Body.FullLoadCurves) {
retVal.AddFullLoadCurve(entry.Gears, FullLoadCurve.ReadFromFile(Path.Combine(engine.BasePath, entry.Path), false));
}
retVal.FullLoadCurve = EngineFullLoadCurve.ReadFromFile(Path.Combine(engine.BasePath, engine.Body.FullLoadCurve),
false);
return retVal;
}
......@@ -143,7 +142,7 @@ namespace TUGraz.VectoCore.FileIO.Reader.DataObjectAdaper
/// </summary>
/// <param name="gearbox"></param>
/// <returns></returns>
internal GearboxData CreateGearboxData(GearboxFileV4Engineering gearbox)
internal GearboxData CreateGearboxData(GearboxFileV5Engineering gearbox)
{
var retVal = SetCommonGearboxData(gearbox.Body);
......@@ -169,8 +168,17 @@ namespace TUGraz.VectoCore.FileIO.Reader.DataObjectAdaper
var shiftPolygon = !String.IsNullOrEmpty(gearSettings.ShiftPolygon)
? ShiftPolygon.ReadFromFile(Path.Combine(gearbox.BasePath, gearSettings.ShiftPolygon))
: null;
var fullLoad = !String.IsNullOrEmpty(gearSettings.FullLoadCurve) && !gearSettings.FullLoadCurve.Equals("<NOFILE>")
? GearFullLoadCurve.ReadFromFile(Path.Combine(gearbox.BasePath, gearSettings.FullLoadCurve))
: null;
var gear = new GearData(lossMap, shiftPolygon, gearSettings.Ratio, gearSettings.TCactive);
var gear = new GearData() {
LossMap = lossMap,
ShiftPolygon = shiftPolygon,
FullLoadCurve = fullLoad,
Ratio = gearSettings.Ratio,
TorqueConverterActive = gearSettings.TCactive
};
if (i == 0) {
retVal.AxleGearData = gear;
} else {
......
......@@ -115,8 +115,8 @@ namespace TUGraz.VectoCore.FileIO.Reader.Impl
CheckForDeclarationMode(fileInfo, "Engine");
switch (fileInfo.Version) {
case 2:
Engine = JsonConvert.DeserializeObject<EngineFileV2Declaration>(json);
case 3:
Engine = JsonConvert.DeserializeObject<EngineFileV3Declaration>(json);
Engine.BasePath = Path.GetDirectoryName(file);
break;
default:
......@@ -131,8 +131,8 @@ namespace TUGraz.VectoCore.FileIO.Reader.Impl
CheckForDeclarationMode(fileInfo, "Gearbox");
switch (fileInfo.Version) {
case 4:
Gearbox = JsonConvert.DeserializeObject<GearboxFileV4Declaration>(json);
case 5:
Gearbox = JsonConvert.DeserializeObject<GearboxFileV5Declaration>(json);
Gearbox.BasePath = Path.GetDirectoryName(file);
break;
default:
......
......@@ -196,8 +196,8 @@ namespace TUGraz.VectoCore.FileIO.Reader.Impl
CheckForEngineeringMode(fileInfo, "Engine");
switch (fileInfo.Version) {
case 2:
var tmp = JsonConvert.DeserializeObject<EngineFileV2Engineering>(json);
case 3:
var tmp = JsonConvert.DeserializeObject<EngineFileV3Engineering>(json);
tmp.BasePath = Path.GetDirectoryName(file) + Path.DirectorySeparatorChar;
return tmp;
default:
......@@ -217,8 +217,8 @@ namespace TUGraz.VectoCore.FileIO.Reader.Impl
CheckForEngineeringMode(fileInfo, "Gearbox");
switch (fileInfo.Version) {
case 4:
var tmp = JsonConvert.DeserializeObject<GearboxFileV4Engineering>(json);
case 5:
var tmp = JsonConvert.DeserializeObject<GearboxFileV5Engineering>(json);
tmp.BasePath = Path.GetDirectoryName(file) + Path.DirectorySeparatorChar;
return tmp;
default:
......
......@@ -210,9 +210,10 @@ namespace TUGraz.VectoCore.Models.Declaration
return false;
}
internal static ShiftPolygon ComputeShiftPolygon(CombustionEngineData engine, uint gear)
internal static ShiftPolygon ComputeShiftPolygon(GearFullLoadCurve gear, CombustionEngineData engine)
{
var fullLoadCurve = engine.GetFullLoadCurve(gear);
// TODO: How to compute shift-polygons exactly? (merge with engine full load?)
var fullLoadCurve = engine.FullLoadCurve;
var idleSpeed = engine.IdleSpeed;
var maxTorque = fullLoadCurve.MaxLoadTorque;
......
......@@ -10,10 +10,8 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
{
public DistanceRun(IVehicleContainer container) : base(container) {}
protected override Connector.Ports.IResponse DoSimulationStep()
protected override IResponse DoSimulationStep()
{
//_dt = TimeSpan.FromSeconds(1) - TimeSpan.FromMilliseconds(_dt.Milliseconds);
// estimate distance to be traveled within the next TargetTimeInterval
var ds = (Container.VehicleSpeed() * Constants.SimulationSettings.TargetTimeInterval).Cast<Meter>();
......@@ -21,7 +19,7 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
ds = Constants.SimulationSettings.DriveOffDistance;
}
var response = CyclePort.Request((Second)AbsTime, ds);
var response = CyclePort.Request(AbsTime, ds);
//while (response is ResponseFailTimeInterval) {
// _dt = (response as ResponseFailTimeInterval).DeltaT;
......@@ -32,7 +30,7 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
return response;
}
AbsTime = (AbsTime + response.SimulationInterval / 2);
AbsTime = AbsTime + response.SimulationInterval;
dt = response.SimulationInterval;
return response;
}
......
......@@ -36,8 +36,12 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
if (_engineOnly) {
cycle = new TimeBasedDrivingCycle(_container, data.Cycle);
} else {
//todo: make distinction between time based and distance based driving cycle!
cycle = new DistanceBasedDrivingCycle(_container, data.Cycle);
if (data.IsEngineOnly) {
cycle = new TimeBasedDrivingCycle(_container, data.Cycle);
} else {
//todo: make distinction between time based and distance based driving cycle!
cycle = new DistanceBasedDrivingCycle(_container, data.Cycle);
}
}
// connect cycle --> driver --> vehicle --> wheels --> axleGear --> gearBox --> retarder --> clutch
dynamic tmp = AddComponent(cycle, new Driver(_container, data.DriverData));
......
......@@ -78,7 +78,14 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
var sumWriterDecorator = DecorateSumWriter(data.IsEngineOnly, SumWriter, data.JobFileName, jobName, data.Cycle.Name);
var builder = new PowertrainBuilder(modWriter, sumWriterDecorator, DataReader.IsEngineOnly);
yield return new DistanceRun(builder.Build(data));
VectoRun run;
if (data.IsEngineOnly) {
run = new TimeRun(builder.Build(data));
} else {
run = new DistanceRun(builder.Build(data));
}
yield return run;
}
}
......
using TUGraz.VectoCore.Models.Connector.Ports;
using TUGraz.VectoCore.Models.Connector.Ports.Impl;
using TUGraz.VectoCore.Utils;
namespace TUGraz.VectoCore.Models.Simulation.Impl
{
public class TimeRun : VectoRun
{
public TimeRun(IVehicleContainer container) : base(container) {}
protected override IResponse DoSimulationStep()
{
var dt = 1.SI<Second>();
var response = CyclePort.Request(AbsTime, dt);
if (response is ResponseCycleFinished) {
return response;
}
AbsTime = AbsTime + dt;
return response;
}
protected override IResponse Initialize()
{
throw new System.NotImplementedException();
}
}
}
\ No newline at end of file
......@@ -45,7 +45,9 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
do {
response = DoSimulationStep();
Container.CommitSimulationStep(AbsTime, dt);
if (response.ResponseType == ResponseType.Success) {
Container.CommitSimulationStep(AbsTime, dt);
}
// set _dt to difference to next full second.
AbsTime += dt;
......
......@@ -120,8 +120,8 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
}
if (_dataWriter != null) {
_dataWriter[ModalResultField.time] = time;
_dataWriter[ModalResultField.simulationInterval] = simulationInterval;
_dataWriter[ModalResultField.time] = time.Value();
_dataWriter[ModalResultField.simulationInterval] = simulationInterval.Value();
_dataWriter.CommitSimulationStep();
}
}
......
......@@ -15,10 +15,6 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data
{
public class CombustionEngineData : SimulationComponentData
{
private readonly Dictionary<Range, FullLoadCurve> _fullLoadCurves =
new Dictionary<Range, FullLoadCurve>();
public string ModelName { get; internal set; }
......@@ -56,30 +52,11 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data
public FuelConsumptionMap ConsumptionMap { get; internal set; }
public FullLoadCurve GetFullLoadCurve(uint gear)
{
var curve = _fullLoadCurves.FirstOrDefault(kv => kv.Key.Contains(gear));
if (curve.Key == null) {
throw new KeyNotFoundException(string.Format("GearData '{0}' was not found in the FullLoadCurves.", gear));
}
return curve.Value;
}
internal void AddFullLoadCurve(string gears, FullLoadCurve fullLoadCurve)
{
var range = new Range(gears);
if (!_fullLoadCurves.ContainsKey(range)) {
fullLoadCurve.EngineData = this;
_fullLoadCurves.Add(range, fullLoadCurve);
} else {
throw new VectoException(String.Format("FullLoadCurve for gears {0} already specified!", gears));
}
}
public EngineFullLoadCurve FullLoadCurve { get; internal set; }
protected bool Equals(CombustionEngineData other)
{
return Equals(_fullLoadCurves, other._fullLoadCurves) && string.Equals(ModelName, other.ModelName) &&
return Equals(FullLoadCurve, other.FullLoadCurve) && string.Equals(ModelName, other.ModelName) &&
Equals(Displacement, other.Displacement) && Equals(IdleSpeed, other.IdleSpeed) && Equals(Inertia, other.Inertia) &&
Equals(WHTCUrban, other.WHTCUrban) && Equals(WHTCRural, other.WHTCRural) &&
Equals(WHTCMotorway, other.WHTCMotorway) && Equals(ConsumptionMap, other.ConsumptionMap);
......@@ -96,13 +73,13 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data
if (obj.GetType() != this.GetType()) {
return false;
}
return Equals((CombustionEngineData) obj);
return Equals((CombustionEngineData)obj);
}
public override int GetHashCode()
{
unchecked {
var hashCode = (_fullLoadCurves != null ? _fullLoadCurves.GetHashCode() : 0);
var hashCode = (FullLoadCurve != null ? FullLoadCurve.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (ModelName != null ? ModelName.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (Displacement != null ? Displacement.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (IdleSpeed != null ? IdleSpeed.GetHashCode() : 0);
......@@ -120,18 +97,18 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof (string) || base.CanConvertFrom(context, 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)
return value.GetType() == typeof(string)
? new Range((string)value)
: base.ConvertFrom(context, culture, value);
}
}
[TypeConverter(typeof (RangeConverter))]
[TypeConverter(typeof(RangeConverter))]
private class Range
{
private readonly uint _end;
......@@ -174,13 +151,13 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data
if (obj.GetType() != GetType()) {
return false;
}
return Equals((Range) obj);
return Equals((Range)obj);
}
public override int GetHashCode()
{
unchecked {
return (int) ((_start * 397) ^ _end);
return (int)((_start * 397) ^ _end);
}
}
......
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics.Contracts;
using System.Linq;
using Common.Logging;
using Newtonsoft.Json;
using TUGraz.VectoCore.Exceptions;
using TUGraz.VectoCore.Models.Declaration;
using TUGraz.VectoCore.Models.SimulationComponent.Data;
using TUGraz.VectoCore.Utils;
namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine
{
/// <summary>
/// Represents the Full load curve.
/// </summary>
public class FullLoadCurve : SimulationComponentData
{
private List<FullLoadCurveEntry> _fullLoadEntries;
private LookupData<PerSecond, Second> _pt1Data;
private Watt _maxPower;
private PerSecond _ratedSpeed;
private PerSecond _preferredSpeed;
private PerSecond _engineSpeedLo; // 55% of Pmax
private PerSecond _engineSpeedHi; // 70% of Pmax
private PerSecond _n95hSpeed; // 95% of Pmax
public static FullLoadCurve ReadFromFile(string fileName, bool declarationMode = false)
{
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 < 3) {
throw new VectoException("FullLoadCurve Data File must consist of at least 3 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> entriesFld;
if (HeaderIsValid(data.Columns)) {
entriesFld = CreateFromColumnNames(data);
} else {
var log = LogManager.GetLogger<FullLoadCurve>();
log.WarnFormat(
"FullLoadCurve: Header Line is not valid. Expected: '{0}, {1}, {2}', Got: '{3}'. Falling back to column index.",
Fields.EngineSpeed, Fields.TorqueFullLoad, Fields.TorqueDrag,
string.Join(", ", data.Columns.Cast<DataColumn>().Select(c => c.ColumnName).Reverse()));
entriesFld = CreateFromColumnIndizes(data);
}
LookupData<PerSecond, Second> tmp;
if (declarationMode) {
tmp = new PT1();
} else {
tmp = PT1Curve.ReadFromFile(fileName);
}
return new FullLoadCurve { _fullLoadEntries = entriesFld, _pt1Data = tmp };
}
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).RPMtoRad(),
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).RPMtoRad(),
TorqueFullLoad = row.ParseDouble(1).SI<NewtonMeter>(),
TorqueDrag = row.ParseDouble(2).SI<NewtonMeter>(),
//PT1 = row.ParseDouble(3).SI<Second>()
}).ToList();
}
public CombustionEngineData EngineData { get; internal set; }
/// <summary>
/// [rad/s] => [Nm]
/// </summary>
/// <param name="angularVelocity">[rad/s]</param>
/// <returns>[Nm]</returns>
public NewtonMeter FullLoadStationaryTorque(PerSecond angularVelocity)
{
var idx = FindIndex(angularVelocity);
return VectoMath.Interpolate(_fullLoadEntries[idx - 1].EngineSpeed, _fullLoadEntries[idx].EngineSpeed,
_fullLoadEntries[idx - 1].TorqueFullLoad, _fullLoadEntries[idx].TorqueFullLoad,
angularVelocity);
}
/// <summary>
/// [rad/s] => [W]
/// </summary>
/// <param name="angularVelocity">[rad/s]</param>
/// <returns>[W]</returns>
public Watt FullLoadStationaryPower(PerSecond angularVelocity)
{
return Formulas.TorqueToPower(FullLoadStationaryTorque(angularVelocity), angularVelocity);
}
/// <summary>
/// [rad/s] => [Nm]
/// </summary>
/// <param name="angularVelocity">[rad/s]</param>
/// <returns>[Nm]</returns>
public NewtonMeter DragLoadStationaryTorque(PerSecond angularVelocity)
{
var idx = FindIndex(angularVelocity);
return VectoMath.Interpolate(_fullLoadEntries[idx - 1].EngineSpeed, _fullLoadEntries[idx].EngineSpeed,
_fullLoadEntries[idx - 1].TorqueDrag, _fullLoadEntries[idx].TorqueDrag,
angularVelocity);
}
/// <summary>
/// [rad/s] => [W].
/// </summary>
/// <param name="angularVelocity">[rad/s]</param>
/// <returns>[W]</returns>
public Watt DragLoadStationaryPower(PerSecond angularVelocity)
{
Contract.Requires(angularVelocity.HasEqualUnit(new SI().Radian.Per.Second));
Contract.Ensures(Contract.Result<SI>().HasEqualUnit(new SI().Watt));
return Formulas.TorqueToPower(DragLoadStationaryTorque(angularVelocity), angularVelocity);
}
/// <summary>
/// [rad/s] => [s]
/// </summary>
/// <param name="angularVelocity">[rad/s]</param>
/// <returns>[s]</returns>
public Second PT1(PerSecond angularVelocity)
{
return _pt1Data.Lookup(angularVelocity);
}
/// <summary>
/// Get the engine's rated speed from the given full-load curve (i.e. engine speed with max. power)
/// </summary>
/// <returns>[1/s]</returns>
public PerSecond RatedSpeed
{
get
{
if (_ratedSpeed == null) {
ComputeRatedSpeed();
}
return _ratedSpeed;
}
}
public Watt MaxPower
{
get
{
if (_maxPower == null) {
ComputeRatedSpeed();
}
return _maxPower;
}
}
/// <summary>
/// Get the engine's preferred speed from the given full-load curve (i.e. Speed at 51% torque/speed-integral between idling and N95h.)
/// </summary>
public PerSecond PreferredSpeed
{
get
{
if (_preferredSpeed == null) {
ComputePreferredSpeed();
}
return _preferredSpeed;
}
}
public PerSecond N95hSpeed
{
get { return _n95hSpeed ?? (_n95hSpeed = FindEngineSpeedForPower(0.95 * MaxPower).Last()); }
}
public PerSecond LoSpeed
{
get { return _engineSpeedLo ?? (_engineSpeedLo = FindEngineSpeedForPower(0.55 * MaxPower).First()); }
}
public PerSecond HiSpeed
{
get { return _engineSpeedHi ?? (_engineSpeedHi = FindEngineSpeedForPower(0.7 * MaxPower).Last()); }
}
public NewtonMeter MaxLoadTorque
{
get { return _fullLoadEntries.Max(x => x.TorqueFullLoad); }
}
public NewtonMeter MaxDragTorque
{
get { return _fullLoadEntries.Min(x => x.TorqueDrag); }
}
/// <summary>
/// Compute the engine's rated speed from the given full-load curve (i.e. engine speed with max. power)
/// </summary>
/// <returns>[1/s]</returns>
private void ComputeRatedSpeed()
{
var max = new Tuple<PerSecond, Watt>(0.SI<PerSecond>(), 0.SI<Watt>());
for (var idx = 1; idx < _fullLoadEntries.Count; idx++) {
var currentMax = FindMaxPower(_fullLoadEntries[idx - 1], _fullLoadEntries[idx]);
if (currentMax.Item2 > max.Item2) {
max = currentMax;
}
}
_ratedSpeed = max.Item1;
_maxPower = max.Item2;
}
private void ComputePreferredSpeed()
{
var maxArea = ComputeArea(EngineData.IdleSpeed, N95hSpeed);
var area = 0.0;
var idx = 0;
while (++idx < _fullLoadEntries.Count) {
var additionalArea = ComputeArea(_fullLoadEntries[idx - 1].EngineSpeed, _fullLoadEntries[idx].EngineSpeed);
if (area + additionalArea > 0.51 * maxArea) {
var deltaArea = 0.51 * maxArea - area;
_preferredSpeed = ComputeEngineSpeedForSegmentArea(_fullLoadEntries[idx - 1], _fullLoadEntries[idx], deltaArea);
return;
}
area += additionalArea;
}
Log.WarnFormat("Could not compute preferred speed, check FullLoadCurve! N95h: {0}, maxArea: {1}", N95hSpeed, maxArea);
}
private PerSecond ComputeEngineSpeedForSegmentArea(FullLoadCurveEntry p1, FullLoadCurveEntry p2, double area)
{
var k = (p2.TorqueFullLoad - p1.TorqueFullLoad) / (p2.EngineSpeed - p1.EngineSpeed);
var d = p2.TorqueFullLoad - k * p2.EngineSpeed;
if (k.IsEqual(0.0)) {
// rectangle
// area = M * n
return (p1.EngineSpeed + (area / d.Value()));
}
// non-constant torque, M(n) = k * n + d
// area = M(n1) * (n2 - n1) + (M(n1) + M(n2))/2 * (n2 - n1) => solve for n2
var retVal = VectoMath.QuadraticEquationSolver(k.Value() / 2.0, d.Value(),
(k * p1.EngineSpeed * p1.EngineSpeed + 2 * p1.EngineSpeed * d).Value());
if (retVal.Count == 0) {
Log.InfoFormat("No real solution found for requested area: P: {0}, p1: {1}, p2: {2}", area, p1, p2);
}
return retVal.First(x => x >= p1.EngineSpeed.Value() && x <= p2.EngineSpeed.Value()).SI<PerSecond>();
}
/// <summary>
/// [rad/s] => index. Get item index for angularVelocity.
/// </summary>
/// <param name="angularVelocity">[rad/s]</param>
/// <returns>index</returns>
protected int FindIndex(PerSecond angularVelocity)
{
int idx;
if (angularVelocity < _fullLoadEntries[0].EngineSpeed) {
Log.ErrorFormat("requested rpm below minimum rpm in FLD curve - extrapolating. n: {0}, rpm_min: {1}",
angularVelocity.ConvertTo().Rounds.Per.Minute, _fullLoadEntries[0].EngineSpeed.ConvertTo().Rounds.Per.Minute);
idx = 1;
} else {
idx = _fullLoadEntries.FindIndex(x => x.EngineSpeed > angularVelocity);
}
if (idx <= 0) {
idx = angularVelocity > _fullLoadEntries[0].EngineSpeed ? _fullLoadEntries.Count - 1 : 1;
}
return idx;
}
private List<PerSecond> FindEngineSpeedForPower(Watt power)
{
var retVal = new List<PerSecond>();
for (var idx = 1; idx < _fullLoadEntries.Count; idx++) {
var solutions = FindEngineSpeedForPower(_fullLoadEntries[idx - 1], _fullLoadEntries[idx], power);
retVal.AddRange(solutions);
}
retVal.Sort();
return retVal;
}
private List<PerSecond> FindEngineSpeedForPower(FullLoadCurveEntry p1, FullLoadCurveEntry p2, Watt power)
{
var k = (p2.TorqueFullLoad - p1.TorqueFullLoad) / (p2.EngineSpeed - p1.EngineSpeed);
var d = p2.TorqueFullLoad - k * p2.EngineSpeed;
var retVal = new List<PerSecond>();
if (k.IsEqual(0, 0.0001)) {
// constant torque, solve linear equation
// power = M * n
retVal.Add((power.Value() / d.Value()).SI<PerSecond>());
} else {
// non-constant torque, solve quadratic equation for engine speed (n)
// power = M(n) * n = (k * n + d) * n = k * n^2 + d * n
retVal = VectoMath.QuadraticEquationSolver(k.Value(), d.Value(), -power.Value()).SI<PerSecond>().ToList();
if (retVal.Count == 0) {
Log.InfoFormat("No real solution found for requested power demand: P: {0}, p1: {1}, p2: {2}", power, p1, p2);
}
}
retVal = retVal.Where(x => x >= p1.EngineSpeed && x <= p2.EngineSpeed).ToList();
return retVal;
}
private double ComputeArea(PerSecond lowEngineSpeed, PerSecond highEngineSpeed)
{
var startSegment = FindIndex(lowEngineSpeed);
var endSegment = FindIndex(highEngineSpeed);
var area = 0.0;
if (lowEngineSpeed < _fullLoadEntries[startSegment].EngineSpeed) {
// add part of the first segment
area += ((_fullLoadEntries[startSegment].EngineSpeed - lowEngineSpeed) *
(FullLoadStationaryTorque(lowEngineSpeed) + _fullLoadEntries[startSegment].TorqueFullLoad) / 2.0).Value();
}
for (var i = startSegment + 1; i <= endSegment; i++) {
var speedHigh = _fullLoadEntries[i].EngineSpeed;
var torqueHigh = _fullLoadEntries[i].TorqueFullLoad;
if (highEngineSpeed < _fullLoadEntries[i].EngineSpeed) {
// add part of the last segment
speedHigh = highEngineSpeed;
torqueHigh = FullLoadStationaryTorque(highEngineSpeed);
}
area += ((speedHigh - _fullLoadEntries[i - 1].EngineSpeed) *
(torqueHigh + _fullLoadEntries[i - 1].TorqueFullLoad) / 2.0).Value();
}
return area;
}
private Tuple<PerSecond, Watt> FindMaxPower(FullLoadCurveEntry p1, FullLoadCurveEntry p2)
{
if (p1.EngineSpeed == p2.EngineSpeed) {
return new Tuple<PerSecond, Watt>(p1.EngineSpeed, Formulas.TorqueToPower(p1.TorqueFullLoad, p1.EngineSpeed));
}
if (p2.EngineSpeed < p1.EngineSpeed) {
var tmp = p1;
p1 = p2;
p2 = tmp;
}
// y = kx + d
var k = (p2.TorqueFullLoad - p1.TorqueFullLoad) / (p2.EngineSpeed - p1.EngineSpeed);
var d = p2.TorqueFullLoad - k * p2.EngineSpeed;
if (k == 0.SI()) {
return new Tuple<PerSecond, Watt>(p2.EngineSpeed, Formulas.TorqueToPower(p2.TorqueFullLoad, p2.EngineSpeed));
}
var engineSpeedMaxPower = (-1 * d / (2 * k)).Cast<PerSecond>();
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<PerSecond, 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));
}
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";
}
private class FullLoadCurveEntry
{
/// <summary>
/// [rad/s] engine speed
/// </summary>
public PerSecond 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)
{
return Equals(EngineSpeed, other.EngineSpeed) && Equals(TorqueFullLoad, other.TorqueFullLoad) &&
Equals(TorqueDrag, other.TorqueDrag);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
if (obj.GetType() != this.GetType()) {
return false;
}
return Equals((FullLoadCurveEntry)obj);
}
public override int GetHashCode()
{
unchecked {
var hashCode = (EngineSpeed != null ? EngineSpeed.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (TorqueFullLoad != null ? TorqueFullLoad.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (TorqueDrag != null ? TorqueDrag.GetHashCode() : 0);
return hashCode;
}
}
#endregion
}
#region Equality members
protected bool Equals(FullLoadCurve other)
{
return Equals(_fullLoadEntries, other._fullLoadEntries) && Equals(_pt1Data, other._pt1Data);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
if (obj.GetType() != this.GetType()) {
return false;
}
return Equals((FullLoadCurve)obj);
}
public override int GetHashCode()
{
unchecked {
return ((_fullLoadEntries != null ? _fullLoadEntries.GetHashCode() : 0) * 397) ^
(_pt1Data != null ? _pt1Data.GetHashCode() : 0);
}
}
#endregion
}
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics.Contracts;
using System.Linq;
using Common.Logging;
using Newtonsoft.Json;
using TUGraz.VectoCore.Exceptions;
using TUGraz.VectoCore.Models.Declaration;
using TUGraz.VectoCore.Models.SimulationComponent.Data;
using TUGraz.VectoCore.Utils;
namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine
{
/// <summary>
/// Represents the Full load curve.
/// </summary>
public class EngineFullLoadCurve : SimulationComponentData
{
private List<FullLoadCurveEntry> _fullLoadEntries;
private LookupData<PerSecond, Second> _pt1Data;
private Watt _maxPower;
private PerSecond _ratedSpeed;
private PerSecond _preferredSpeed;
private PerSecond _engineSpeedLo; // 55% of Pmax
private PerSecond _engineSpeedHi; // 70% of Pmax
private PerSecond _n95hSpeed; // 95% of Pmax
public static EngineFullLoadCurve ReadFromFile(string fileName, bool declarationMode = false)
{
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 < 3) {
throw new VectoException("FullLoadCurve Data File must consist of at least 3 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> entriesFld;
if (HeaderIsValid(data.Columns)) {
entriesFld = CreateFromColumnNames(data);
} else {
var log = LogManager.GetLogger<EngineFullLoadCurve>();
log.WarnFormat(
"FullLoadCurve: Header Line is not valid. Expected: '{0}, {1}, {2}', Got: '{3}'. Falling back to column index.",
Fields.EngineSpeed, Fields.TorqueFullLoad, Fields.TorqueDrag,
string.Join(", ", data.Columns.Cast<DataColumn>().Select(c => c.ColumnName).Reverse()));
entriesFld = CreateFromColumnIndizes(data);
}
LookupData<PerSecond, Second> tmp;
if (declarationMode) {
tmp = new PT1();
} else {
tmp = PT1Curve.ReadFromFile(fileName);
}
return new EngineFullLoadCurve { _fullLoadEntries = entriesFld, _pt1Data = tmp };
}
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).RPMtoRad(),
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).RPMtoRad(),
TorqueFullLoad = row.ParseDouble(1).SI<NewtonMeter>(),
TorqueDrag = row.ParseDouble(2).SI<NewtonMeter>(),
//PT1 = row.ParseDouble(3).SI<Second>()
}).ToList();
}
public CombustionEngineData EngineData { get; internal set; }
/// <summary>
/// [rad/s] => [Nm]
/// </summary>
/// <param name="angularVelocity">[rad/s]</param>
/// <returns>[Nm]</returns>
public NewtonMeter FullLoadStationaryTorque(PerSecond angularVelocity)
{
var idx = FindIndex(angularVelocity);
return VectoMath.Interpolate(_fullLoadEntries[idx - 1].EngineSpeed, _fullLoadEntries[idx].EngineSpeed,
_fullLoadEntries[idx - 1].TorqueFullLoad, _fullLoadEntries[idx].TorqueFullLoad,
angularVelocity);
}
/// <summary>
/// [rad/s] => [W]
/// </summary>
/// <param name="angularVelocity">[rad/s]</param>
/// <returns>[W]</returns>
public Watt FullLoadStationaryPower(PerSecond angularVelocity)
{
return Formulas.TorqueToPower(FullLoadStationaryTorque(angularVelocity), angularVelocity);
}
/// <summary>
/// [rad/s] => [Nm]
/// </summary>
/// <param name="angularVelocity">[rad/s]</param>
/// <returns>[Nm]</returns>
public NewtonMeter DragLoadStationaryTorque(PerSecond angularVelocity)
{
var idx = FindIndex(angularVelocity);
return VectoMath.Interpolate(_fullLoadEntries[idx - 1].EngineSpeed, _fullLoadEntries[idx].EngineSpeed,
_fullLoadEntries[idx - 1].TorqueDrag, _fullLoadEntries[idx].TorqueDrag,
angularVelocity);
}
/// <summary>
/// [rad/s] => [W].
/// </summary>
/// <param name="angularVelocity">[rad/s]</param>
/// <returns>[W]</returns>
public Watt DragLoadStationaryPower(PerSecond angularVelocity)
{
Contract.Requires(angularVelocity.HasEqualUnit(new SI().Radian.Per.Second));
Contract.Ensures(Contract.Result<SI>().HasEqualUnit(new SI().Watt));
return Formulas.TorqueToPower(DragLoadStationaryTorque(angularVelocity), angularVelocity);
}
/// <summary>
/// [rad/s] => [s]
/// </summary>
/// <param name="angularVelocity">[rad/s]</param>
/// <returns>[s]</returns>
public Second PT1(PerSecond angularVelocity)
{
return _pt1Data.Lookup(angularVelocity);
}
/// <summary>
/// Get the engine's rated speed from the given full-load curve (i.e. engine speed with max. power)
/// </summary>
/// <returns>[1/s]</returns>
public PerSecond RatedSpeed
{
get
{
if (_ratedSpeed == null) {
ComputeRatedSpeed();
}
return _ratedSpeed;
}
}
public Watt MaxPower
{
get
{
if (_maxPower == null) {
ComputeRatedSpeed();
}
return _maxPower;
}
}
/// <summary>
/// Get the engine's preferred speed from the given full-load curve (i.e. Speed at 51% torque/speed-integral between idling and N95h.)
/// </summary>
public PerSecond PreferredSpeed
{
get
{
if (_preferredSpeed == null) {
ComputePreferredSpeed();
}
return _preferredSpeed;
}
}
public PerSecond N95hSpeed
{
get { return _n95hSpeed ?? (_n95hSpeed = FindEngineSpeedForPower(0.95 * MaxPower).Last()); }
}
public PerSecond LoSpeed
{
get { return _engineSpeedLo ?? (_engineSpeedLo = FindEngineSpeedForPower(0.55 * MaxPower).First()); }
}
public PerSecond HiSpeed
{
get { return _engineSpeedHi ?? (_engineSpeedHi = FindEngineSpeedForPower(0.7 * MaxPower).Last()); }
}
public NewtonMeter MaxLoadTorque
{
get { return _fullLoadEntries.Max(x => x.TorqueFullLoad); }
}
public NewtonMeter MaxDragTorque
{
get { return _fullLoadEntries.Min(x => x.TorqueDrag); }
}
/// <summary>
/// Compute the engine's rated speed from the given full-load curve (i.e. engine speed with max. power)
/// </summary>
/// <returns>[1/s]</returns>
private void ComputeRatedSpeed()
{
var max = new Tuple<PerSecond, Watt>(0.SI<PerSecond>(), 0.SI<Watt>());
for (var idx = 1; idx < _fullLoadEntries.Count; idx++) {
var currentMax = FindMaxPower(_fullLoadEntries[idx - 1], _fullLoadEntries[idx]);
if (currentMax.Item2 > max.Item2) {
max = currentMax;
}
}
_ratedSpeed = max.Item1;
_maxPower = max.Item2;
}
private void ComputePreferredSpeed()
{
var maxArea = ComputeArea(EngineData.IdleSpeed, N95hSpeed);
var area = 0.0;
var idx = 0;
while (++idx < _fullLoadEntries.Count) {
var additionalArea = ComputeArea(_fullLoadEntries[idx - 1].EngineSpeed, _fullLoadEntries[idx].EngineSpeed);
if (area + additionalArea > 0.51 * maxArea) {
var deltaArea = 0.51 * maxArea - area;
_preferredSpeed = ComputeEngineSpeedForSegmentArea(_fullLoadEntries[idx - 1], _fullLoadEntries[idx], deltaArea);
return;
}
area += additionalArea;
}
Log.WarnFormat("Could not compute preferred speed, check FullLoadCurve! N95h: {0}, maxArea: {1}", N95hSpeed, maxArea);
}
private PerSecond ComputeEngineSpeedForSegmentArea(FullLoadCurveEntry p1, FullLoadCurveEntry p2, double area)
{
var k = (p2.TorqueFullLoad - p1.TorqueFullLoad) / (p2.EngineSpeed - p1.EngineSpeed);
var d = p2.TorqueFullLoad - k * p2.EngineSpeed;
if (k.IsEqual(0.0)) {
// rectangle
// area = M * n
return (p1.EngineSpeed + (area / d.Value()));
}
// non-constant torque, M(n) = k * n + d
// area = M(n1) * (n2 - n1) + (M(n1) + M(n2))/2 * (n2 - n1) => solve for n2
var retVal = VectoMath.QuadraticEquationSolver(k.Value() / 2.0, d.Value(),
(k * p1.EngineSpeed * p1.EngineSpeed + 2 * p1.EngineSpeed * d).Value());
if (retVal.Count == 0) {
Log.InfoFormat("No real solution found for requested area: P: {0}, p1: {1}, p2: {2}", area, p1, p2);
}
return retVal.First(x => x >= p1.EngineSpeed.Value() && x <= p2.EngineSpeed.Value()).SI<PerSecond>();
}
/// <summary>
/// [rad/s] => index. Get item index for angularVelocity.
/// </summary>
/// <param name="angularVelocity">[rad/s]</param>
/// <returns>index</returns>
protected int FindIndex(PerSecond angularVelocity)
{
int idx;
if (angularVelocity < _fullLoadEntries[0].EngineSpeed) {
Log.ErrorFormat("requested rpm below minimum rpm in FLD curve - extrapolating. n: {0}, rpm_min: {1}",
angularVelocity.ConvertTo().Rounds.Per.Minute, _fullLoadEntries[0].EngineSpeed.ConvertTo().Rounds.Per.Minute);
idx = 1;
} else {
idx = _fullLoadEntries.FindIndex(x => x.EngineSpeed > angularVelocity);
}
if (idx <= 0) {
idx = angularVelocity > _fullLoadEntries[0].EngineSpeed ? _fullLoadEntries.Count - 1 : 1;
}
return idx;
}
private List<PerSecond> FindEngineSpeedForPower(Watt power)
{
var retVal = new List<PerSecond>();
for (var idx = 1; idx < _fullLoadEntries.Count; idx++) {
var solutions = FindEngineSpeedForPower(_fullLoadEntries[idx - 1], _fullLoadEntries[idx], power);
retVal.AddRange(solutions);
}
retVal.Sort();
return retVal;
}
private List<PerSecond> FindEngineSpeedForPower(FullLoadCurveEntry p1, FullLoadCurveEntry p2, Watt power)
{
var k = (p2.TorqueFullLoad - p1.TorqueFullLoad) / (p2.EngineSpeed - p1.EngineSpeed);
var d = p2.TorqueFullLoad - k * p2.EngineSpeed;
var retVal = new List<PerSecond>();
if (k.IsEqual(0, 0.0001)) {
// constant torque, solve linear equation
// power = M * n
retVal.Add((power.Value() / d.Value()).SI<PerSecond>());
} else {
// non-constant torque, solve quadratic equation for engine speed (n)
// power = M(n) * n = (k * n + d) * n = k * n^2 + d * n
retVal = VectoMath.QuadraticEquationSolver(k.Value(), d.Value(), -power.Value()).SI<PerSecond>().ToList();
if (retVal.Count == 0) {
Log.InfoFormat("No real solution found for requested power demand: P: {0}, p1: {1}, p2: {2}", power, p1, p2);
}
}
retVal = retVal.Where(x => x >= p1.EngineSpeed && x <= p2.EngineSpeed).ToList();
return retVal;
}
private double ComputeArea(PerSecond lowEngineSpeed, PerSecond highEngineSpeed)
{
var startSegment = FindIndex(lowEngineSpeed);
var endSegment = FindIndex(highEngineSpeed);
var area = 0.0;
if (lowEngineSpeed < _fullLoadEntries[startSegment].EngineSpeed) {
// add part of the first segment
area += ((_fullLoadEntries[startSegment].EngineSpeed - lowEngineSpeed) *
(FullLoadStationaryTorque(lowEngineSpeed) + _fullLoadEntries[startSegment].TorqueFullLoad) / 2.0).Value();
}
for (var i = startSegment + 1; i <= endSegment; i++) {
var speedHigh = _fullLoadEntries[i].EngineSpeed;
var torqueHigh = _fullLoadEntries[i].TorqueFullLoad;
if (highEngineSpeed < _fullLoadEntries[i].EngineSpeed) {
// add part of the last segment
speedHigh = highEngineSpeed;
torqueHigh = FullLoadStationaryTorque(highEngineSpeed);
}
area += ((speedHigh - _fullLoadEntries[i - 1].EngineSpeed) *
(torqueHigh + _fullLoadEntries[i - 1].TorqueFullLoad) / 2.0).Value();
}
return area;
}
private Tuple<PerSecond, Watt> FindMaxPower(FullLoadCurveEntry p1, FullLoadCurveEntry p2)
{
if (p1.EngineSpeed == p2.EngineSpeed) {
return new Tuple<PerSecond, Watt>(p1.EngineSpeed, Formulas.TorqueToPower(p1.TorqueFullLoad, p1.EngineSpeed));
}
if (p2.EngineSpeed < p1.EngineSpeed) {
var tmp = p1;
p1 = p2;
p2 = tmp;
}
// y = kx + d
var k = (p2.TorqueFullLoad - p1.TorqueFullLoad) / (p2.EngineSpeed - p1.EngineSpeed);
var d = p2.TorqueFullLoad - k * p2.EngineSpeed;
if (k == 0.SI()) {
return new Tuple<PerSecond, Watt>(p2.EngineSpeed, Formulas.TorqueToPower(p2.TorqueFullLoad, p2.EngineSpeed));
}
var engineSpeedMaxPower = (-1 * d / (2 * k)).Cast<PerSecond>();
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<PerSecond, 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));
}
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";
}
private class FullLoadCurveEntry
{
/// <summary>
/// [rad/s] engine speed
/// </summary>
public PerSecond 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)
{
return Equals(EngineSpeed, other.EngineSpeed) && Equals(TorqueFullLoad, other.TorqueFullLoad) &&
Equals(TorqueDrag, other.TorqueDrag);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
if (obj.GetType() != this.GetType()) {
return false;
}
return Equals((FullLoadCurveEntry)obj);
}
public override int GetHashCode()
{
unchecked {
var hashCode = (EngineSpeed != null ? EngineSpeed.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (TorqueFullLoad != null ? TorqueFullLoad.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (TorqueDrag != null ? TorqueDrag.GetHashCode() : 0);
return hashCode;
}
}
#endregion
}
#region Equality members
protected bool Equals(EngineFullLoadCurve other)
{
return Equals(_fullLoadEntries, other._fullLoadEntries) && Equals(_pt1Data, other._pt1Data);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
if (obj.GetType() != this.GetType()) {
return false;
}
return Equals((EngineFullLoadCurve)obj);
}
public override int GetHashCode()
{
unchecked {
return ((_fullLoadEntries != null ? _fullLoadEntries.GetHashCode() : 0) * 397) ^
(_pt1Data != null ? _pt1Data.GetHashCode() : 0);
}
}
#endregion
}
}
\ No newline at end of file
namespace TUGraz.VectoCore.Models.SimulationComponent.Data
{
public class GearFullLoadCurve
{
public static GearFullLoadCurve ReadFromFile(string file)
{
throw new System.NotImplementedException();
}
}
}
\ No newline at end of file
......@@ -2,23 +2,16 @@
{
public class GearData
{
public ShiftPolygon ShiftPolygon { get; protected set; }
public ShiftPolygon ShiftPolygon { get; internal set; }
public TransmissionLossMap LossMap { get; protected set; }
public TransmissionLossMap LossMap { get; internal set; }
public double Ratio { get; protected set; }
public GearFullLoadCurve FullLoadCurve { get; internal set; }
public bool TorqueConverterActive { get; protected set; } // TODO: think about refactoring...
public double Ratio { get; internal set; }
public double AverageEfficiency { get; set; }
public bool TorqueConverterActive { get; internal set; } // TODO: think about refactoring...
public GearData(TransmissionLossMap lossMap, ShiftPolygon shiftPolygon, double ratio,
bool torqueconverterActive)
{
LossMap = lossMap;
ShiftPolygon = shiftPolygon;
Ratio = ratio;
TorqueConverterActive = torqueconverterActive;
}
// public double AverageEfficiency { get; internal set; }
}
}
\ 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