diff --git a/VectoCommon/VectoCommon/Utils/VectoMath.cs b/VectoCommon/VectoCommon/Utils/VectoMath.cs index e3c10c4302dca7c8666acebd50fc14500329ec2d..f488301deff3abe1ba54ea3a72f3d846e2576ff8 100644 --- a/VectoCommon/VectoCommon/Utils/VectoMath.cs +++ b/VectoCommon/VectoCommon/Utils/VectoMath.cs @@ -122,6 +122,12 @@ namespace TUGraz.VectoCommon.Utils return Interpolate(p1.X, p2.X, p1.Y, p2.Y, x); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double Interpolate(Edge edge, double x) + { + return Interpolate(edge.P1, edge.P2, x); + } + /// <summary> /// Linearly interpolates a value between two points. /// </summary> @@ -332,6 +338,8 @@ namespace TUGraz.VectoCommon.Utils return retVal; } + + [DebuggerStepThrough] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T Ceiling<T>(T si) where T : SIBase<T> diff --git a/VectoCore/VectoCore/Models/SimulationComponent/Data/Gearbox/TorqueConverterData.cs b/VectoCore/VectoCore/Models/SimulationComponent/Data/Gearbox/TorqueConverterData.cs index 18b7a47d7e104470f2445ad1a3b93e08e013c09f..8b38730ef69a83f9abaa0a260ca1023c1e7d814d 100644 --- a/VectoCore/VectoCore/Models/SimulationComponent/Data/Gearbox/TorqueConverterData.cs +++ b/VectoCore/VectoCore/Models/SimulationComponent/Data/Gearbox/TorqueConverterData.cs @@ -29,264 +29,287 @@ * Martin Rexeis, rexeis@ivt.tugraz.at, IVT, Graz University of Technology */ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using TUGraz.VectoCommon.Exceptions; -using TUGraz.VectoCommon.Utils; - -namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Gearbox -{ - [CustomValidation(typeof(TorqueConverterData), "ValidateData")] - public class TorqueConverterData : SimulationComponentData - { - protected internal readonly TorqueConverterEntry[] TorqueConverterEntries; - - [Required, SIRange(0, double.MaxValue)] - public PerSecond ReferenceSpeed { get; protected internal set; } - - [Required, SIRange(0, double.MaxValue)] - public MeterPerSquareSecond CLUpshiftMinAcceleration { get; internal set; } - - [Required, SIRange(0, double.MaxValue)] - public MeterPerSquareSecond CCUpshiftMinAcceleration { get; internal set; } - - [Required, SIRange(0, double.MaxValue)] - public PerSecond TorqueConverterSpeedLimit { get; protected internal set; } - - internal double RequiredSpeedRatio; // only used for validation! - - protected internal TorqueConverterData(IEnumerable<TorqueConverterEntry> torqueConverterEntries, - PerSecond referenceSpeed, PerSecond maxRpm, MeterPerSquareSecond clUpshiftMinAcceleration, - MeterPerSquareSecond ccUpshiftMinAcceleration) - { - TorqueConverterEntries = torqueConverterEntries.ToArray(); - ReferenceSpeed = referenceSpeed; - TorqueConverterSpeedLimit = maxRpm; - CLUpshiftMinAcceleration = clUpshiftMinAcceleration; - CCUpshiftMinAcceleration = ccUpshiftMinAcceleration; - } - - /// <summary> - /// find an operating point for the torque converter - /// - /// find the input speed and input torque for the given output torque and output speed. - /// </summary> - /// <param name="torqueOut">torque provided at the TC output</param> - /// <param name="angularSpeedOut">angular speed at the TC output</param> - /// <param name="minSpeed"></param> - /// <returns></returns> - public IList<TorqueConverterOperatingPoint> FindOperatingPoint(NewtonMeter torqueOut, PerSecond angularSpeedOut, - PerSecond minSpeed) - { - var solutions = new List<double>(); - var mpNorm = ReferenceSpeed.Value(); - - var min = minSpeed == null ? 0 : minSpeed.Value(); - - // Find analytic solution for torque converter operating point - // mu = f(nu) = f(n_out / n_in) = T_out / T_in - // MP1000 = f(nu) = f(n_out / n_in) - // Tq_in = MP1000(nu) * (n_in/1000)^2 = T_out / mu - // - // mu(nu) and MP1000(nu) are provided as piecewise linear functions (y = k*x+d) - // solving the equation above for n_in results in a quadratic equation - foreach (var segment in TorqueConverterEntries.Pairwise(Tuple.Create)) { - var mpEdge = Edge.Create(new Point(segment.Item1.SpeedRatio, segment.Item1.Torque.Value()), - new Point(segment.Item2.SpeedRatio, segment.Item2.Torque.Value())); - var muEdge = Edge.Create(new Point(segment.Item1.SpeedRatio, segment.Item1.TorqueRatio), - new Point(segment.Item2.SpeedRatio, segment.Item2.TorqueRatio)); - - var a = muEdge.OffsetXY * mpEdge.OffsetXY / (mpNorm * mpNorm); - var b = angularSpeedOut.Value() * (muEdge.SlopeXY * mpEdge.OffsetXY + mpEdge.SlopeXY * muEdge.OffsetXY) / - (mpNorm * mpNorm); - var c = angularSpeedOut.Value() * angularSpeedOut.Value() * mpEdge.SlopeXY * muEdge.SlopeXY / (mpNorm * mpNorm) - - torqueOut.Value(); - - solutions.AddRange(VectoMath.QuadraticEquationSolver(a, b, c).Where(x => { - var ratio = angularSpeedOut.Value() / x; - return x > min && muEdge.P1.X <= ratio && ratio < muEdge.P2.X; - })); - } - - if (solutions.Count == 0) { - Log.Debug( - "TorqueConverterData::FindOperatingPoint No solution for input torque/input speed found! n_out: {0}, tq_out: {1}", - angularSpeedOut, torqueOut); - } - - return solutions.Select(sol => { - var s = sol.SI<PerSecond>(); - var mu = MuLookup(angularSpeedOut / s); - return new TorqueConverterOperatingPoint { - OutTorque = torqueOut, - OutAngularVelocity = angularSpeedOut, - InAngularVelocity = s, - SpeedRatio = angularSpeedOut / s, - TorqueRatio = mu, - InTorque = torqueOut / mu, - }; - }).ToList(); - } - - /// <summary> - /// find an operating point for the torque converter - /// - /// find the input torque and output torque for the given input and output speeds. - /// Computes the speed ratio nu of input and output. Interpolates MP1000 and mu, Computes input torque and output torque - /// </summary> - /// <param name="inAngularVelocity">speed at the input of the TC</param> - /// <param name="outAngularVelocity">speed at the output of the TC</param> - /// <returns></returns> - public TorqueConverterOperatingPoint FindOperatingPoint(PerSecond inAngularVelocity, PerSecond outAngularVelocity) - { - var retVal = new TorqueConverterOperatingPoint { - InAngularVelocity = inAngularVelocity, - OutAngularVelocity = outAngularVelocity, - SpeedRatio = outAngularVelocity.Value() / inAngularVelocity.Value(), - }; - - foreach (var segment in TorqueConverterEntries.Pairwise()) { - if (retVal.SpeedRatio.IsBetween(segment.Item1.SpeedRatio, segment.Item2.SpeedRatio)) { - var mpTorque = segment.Interpolate(x => x.SpeedRatio, y => y.Torque, retVal.SpeedRatio); - retVal.TorqueRatio = segment.Interpolate(x => x.SpeedRatio, y => y.TorqueRatio, retVal.SpeedRatio); - retVal.InTorque = mpTorque * (inAngularVelocity * inAngularVelocity / ReferenceSpeed / ReferenceSpeed).Value(); - retVal.OutTorque = retVal.InTorque * retVal.TorqueRatio; - return retVal; - } - } - - // No solution found. Throw Error - var nu = outAngularVelocity / inAngularVelocity; - var nuMax = TorqueConverterEntries.Last().SpeedRatio; - - if (nu.IsGreater(nuMax)) { - throw new VectoException( - "Torque Converter: Range of torque converter data is not sufficient. Required nu: {0}, Got nu_max: {1}", nu, nuMax); - } - - throw new VectoException( - "Torque Converter: No solution for output speed/input speed found! n_out: {0}, n_in: {1}, nu: {2}, nu_max: {3}", - outAngularVelocity, inAngularVelocity, nu, nuMax); - } - - /// <summary> - /// find an operating point for the torque converter - /// - /// find the output torque and output speed for the given input torque and input speed - /// </summary> - /// <param name="inTorque"></param> - /// <param name="inAngularVelocity"></param> - /// <param name="outAngularSpeedEstimated"></param> - /// <returns></returns> - public TorqueConverterOperatingPoint FindOperatingPointForward(NewtonMeter inTorque, PerSecond inAngularVelocity, - PerSecond outAngularSpeedEstimated) - { - var referenceTorque = inTorque.Value() / inAngularVelocity.Value() / inAngularVelocity.Value() * - ReferenceSpeed.Value() * ReferenceSpeed.Value(); - var maxTorque = TorqueConverterEntries.Max(x => x.Torque.Value()); - if (referenceTorque.IsGreaterOrEqual(maxTorque)) { - referenceTorque = outAngularSpeedEstimated != null - ? ReferenceTorqueLookup(outAngularSpeedEstimated / inAngularVelocity).Value() - : 0.9 * maxTorque; - } - - var solutions = new List<double>(); - foreach (var edge in TorqueConverterEntries.Pairwise( - (p1, p2) => Edge.Create(new Point(p1.SpeedRatio, p1.Torque.Value()), new Point(p2.SpeedRatio, p2.Torque.Value())))) { - var x = (referenceTorque - edge.OffsetXY) / edge.SlopeXY; - if (x >= edge.P1.X && x < edge.P2.X) { - solutions.Add(x * inAngularVelocity.Value()); - } - } - if (solutions.Count == 0) { - throw new VectoSimulationException( - "Torque Converter: Failed to find operating point for inputTorque/inputSpeed! n_in: {0}, tq_in: {1}", - inAngularVelocity, inTorque); - } - return FindOperatingPoint(inAngularVelocity, solutions.Max().SI<PerSecond>()); - } - - public TorqueConverterOperatingPoint FindOperatingPointForPowerDemand(Watt power, PerSecond prevInputSpeed, - PerSecond nextOutputSpeed, KilogramSquareMeter inertia, Second dt, Watt previousPower) - { - var solutions = new List<double>(); - var mpNorm = ReferenceSpeed.Value(); - - foreach (var segment in TorqueConverterEntries.Pairwise(Tuple.Create)) { - var mpEdge = Edge.Create(new Point(segment.Item1.SpeedRatio, segment.Item1.Torque.Value()), - new Point(segment.Item2.SpeedRatio, segment.Item2.Torque.Value())); - - var a = mpEdge.OffsetXY / (2 * mpNorm * mpNorm); - var b = inertia.Value() / (2 * dt.Value()) + mpEdge.SlopeXY * nextOutputSpeed.Value() / (2 * mpNorm * mpNorm); - var c = 0; - var d = -inertia.Value() * prevInputSpeed.Value() * prevInputSpeed.Value() / (2 * dt.Value()) - power.Value() + - previousPower.Value() / 2; - var sol = VectoMath.CubicEquationSolver(a, b, c, d); - - var selected = sol.Where(x => x > 0 && nextOutputSpeed / x >= mpEdge.P1.X && nextOutputSpeed / x < mpEdge.P2.X); - solutions.AddRange(selected); - } - - if (solutions.Count == 0) { - throw new VectoException( - "Failed to find operating point for power {0}, prevInputSpeed {1}, nextOutputSpeed {2}", power, - prevInputSpeed, nextOutputSpeed); - } - solutions.Sort(); - - return FindOperatingPoint(solutions.First().SI<PerSecond>(), nextOutputSpeed); - } - - private double MuLookup(double speedRatio) - { - return TorqueConverterEntries.Interpolate(x => x.SpeedRatio, y => y.TorqueRatio, speedRatio); - } - - private NewtonMeter ReferenceTorqueLookup(double speedRatio) - { - return TorqueConverterEntries.Interpolate(x => x.SpeedRatio, y => y.Torque, speedRatio); - } - - // ReSharper disable once UnusedMember.Global -- used by validation - public static ValidationResult ValidateData(TorqueConverterData data, ValidationContext validationContext) - { - var min = data.TorqueConverterEntries.Min(e => e.SpeedRatio); - var max = data.TorqueConverterEntries.Max(e => e.SpeedRatio); - if (min > 0 || max < data.RequiredSpeedRatio) { - return new ValidationResult(string.Format( - "Torque Converter Data invalid - Speedratio has to cover the range from 0.0 to {2}: given data only goes from {0} to {1}", - min, max, data.RequiredSpeedRatio)); - } - - return ValidationResult.Success; - } - } - - public class TorqueConverterOperatingPoint - { - public PerSecond OutAngularVelocity; - public NewtonMeter OutTorque; - - public PerSecond InAngularVelocity; - public NewtonMeter InTorque; - - public double SpeedRatio; - public double TorqueRatio; - public bool Creeping; - - public override string ToString() - { - return string.Format("n_out: {0}, n_in: {1}, tq_out: {2}, tq_in {3}, nu: {4}, my: {5}", OutAngularVelocity, - InAngularVelocity, OutTorque, InTorque, SpeedRatio, TorqueRatio); - } - } - - public class TorqueConverterEntry - { - public double SpeedRatio; - public NewtonMeter Torque; - public double TorqueRatio; - } +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using TUGraz.VectoCommon.Exceptions; +using TUGraz.VectoCommon.Utils; +using TUGraz.VectoCore.Models.Declaration; + +namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Gearbox +{ + [CustomValidation(typeof(TorqueConverterData), "ValidateData")] + public class TorqueConverterData : SimulationComponentData + { + protected internal readonly TorqueConverterEntry[] TorqueConverterEntries; + + [Required, SIRange(0, double.MaxValue)] + public PerSecond ReferenceSpeed { get; protected internal set; } + + [Required, SIRange(0, double.MaxValue)] + public MeterPerSquareSecond CLUpshiftMinAcceleration { get; internal set; } + + [Required, SIRange(0, double.MaxValue)] + public MeterPerSquareSecond CCUpshiftMinAcceleration { get; internal set; } + + [Required, SIRange(0, double.MaxValue)] + public PerSecond TorqueConverterSpeedLimit { get; protected internal set; } + + internal double RequiredSpeedRatio; // only used for validation! + + protected internal TorqueConverterData(IEnumerable<TorqueConverterEntry> torqueConverterEntries, + PerSecond referenceSpeed, PerSecond maxRpm, MeterPerSquareSecond clUpshiftMinAcceleration, + MeterPerSquareSecond ccUpshiftMinAcceleration) + { + TorqueConverterEntries = torqueConverterEntries.ToArray(); + ReferenceSpeed = referenceSpeed; + TorqueConverterSpeedLimit = maxRpm; + CLUpshiftMinAcceleration = clUpshiftMinAcceleration; + CCUpshiftMinAcceleration = ccUpshiftMinAcceleration; + } + + /// <summary> + /// find an operating point for the torque converter + /// + /// find the input speed and input torque for the given output torque and output speed. + /// </summary> + /// <param name="torqueOut">torque provided at the TC output</param> + /// <param name="angularSpeedOut">angular speed at the TC output</param> + /// <param name="minSpeed"></param> + /// <returns></returns> + public IList<TorqueConverterOperatingPoint> FindOperatingPoint(NewtonMeter torqueOut, PerSecond angularSpeedOut, + PerSecond minSpeed) + { + var solutions = new List<double>(); + var mpNorm = ReferenceSpeed.Value(); + + var min = minSpeed == null ? 0 : minSpeed.Value(); + + // Find analytic solution for torque converter operating point + // mu = f(nu) = f(n_out / n_in) = T_out / T_in + // MP1000 = f(nu) = f(n_out / n_in) + // Tq_in = MP1000(nu) * (n_in/1000)^2 = T_out / mu + // + // mu(nu) and MP1000(nu) are provided as piecewise linear functions (y = k*x+d) + // solving the equation above for n_in results in a quadratic equation + foreach (var segment in TorqueConverterEntries.Pairwise(Tuple.Create)) { + var mpEdge = Edge.Create(new Point(segment.Item1.SpeedRatio, segment.Item1.Torque.Value()), + new Point(segment.Item2.SpeedRatio, segment.Item2.Torque.Value())); + var muEdge = Edge.Create(new Point(segment.Item1.SpeedRatio, segment.Item1.TorqueRatio), + new Point(segment.Item2.SpeedRatio, segment.Item2.TorqueRatio)); + + var a = muEdge.OffsetXY * mpEdge.OffsetXY / (mpNorm * mpNorm); + var b = angularSpeedOut.Value() * (muEdge.SlopeXY * mpEdge.OffsetXY + mpEdge.SlopeXY * muEdge.OffsetXY) / + (mpNorm * mpNorm); + var c = angularSpeedOut.Value() * angularSpeedOut.Value() * mpEdge.SlopeXY * muEdge.SlopeXY / (mpNorm * mpNorm) - + torqueOut.Value(); + + solutions.AddRange(VectoMath.QuadraticEquationSolver(a, b, c).Where(x => { + var ratio = angularSpeedOut.Value() / x; + return x > min && muEdge.P1.X <= ratio && ratio < muEdge.P2.X; + })); + } + + if (solutions.Count == 0) { + Log.Debug( + "TorqueConverterData::FindOperatingPoint No solution for input torque/input speed found! n_out: {0}, tq_out: {1}", + angularSpeedOut, torqueOut); + } + + return solutions.Select(sol => { + var s = sol.SI<PerSecond>(); + var mu = MuLookup(angularSpeedOut / s); + return new TorqueConverterOperatingPoint { + OutTorque = torqueOut, + OutAngularVelocity = angularSpeedOut, + InAngularVelocity = s, + SpeedRatio = angularSpeedOut / s, + TorqueRatio = mu, + InTorque = torqueOut / mu, + }; + }).ToList(); + } + + /// <summary> + /// find an operating point for the torque converter + /// + /// find the input torque and output torque for the given input and output speeds. + /// Computes the speed ratio nu of input and output. Interpolates MP1000 and mu, Computes input torque and output torque + /// </summary> + /// <param name="inAngularVelocity">speed at the input of the TC</param> + /// <param name="outAngularVelocity">speed at the output of the TC</param> + /// <returns></returns> + public TorqueConverterOperatingPoint FindOperatingPoint(PerSecond inAngularVelocity, PerSecond outAngularVelocity) + { + var retVal = new TorqueConverterOperatingPoint { + InAngularVelocity = inAngularVelocity, + OutAngularVelocity = outAngularVelocity, + SpeedRatio = outAngularVelocity.Value() / inAngularVelocity.Value(), + }; + + foreach (var segment in TorqueConverterEntries.Pairwise()) { + if (retVal.SpeedRatio.IsBetween(segment.Item1.SpeedRatio, segment.Item2.SpeedRatio)) { + var mpTorque = segment.Interpolate(x => x.SpeedRatio, y => y.Torque, retVal.SpeedRatio); + retVal.TorqueRatio = segment.Interpolate(x => x.SpeedRatio, y => y.TorqueRatio, retVal.SpeedRatio); + retVal.InTorque = mpTorque * (inAngularVelocity * inAngularVelocity / ReferenceSpeed / ReferenceSpeed).Value(); + retVal.OutTorque = retVal.InTorque * retVal.TorqueRatio; + return retVal; + } + } + + // No solution found. Throw Error + var nu = outAngularVelocity / inAngularVelocity; + var nuMax = TorqueConverterEntries.Last().SpeedRatio; + + if (nu.IsGreater(nuMax)) { + throw new VectoException( + "Torque Converter: Range of torque converter data is not sufficient. Required nu: {0}, Got nu_max: {1}", nu, nuMax); + } + + throw new VectoException( + "Torque Converter: No solution for output speed/input speed found! n_out: {0}, n_in: {1}, nu: {2}, nu_max: {3}", + outAngularVelocity, inAngularVelocity, nu, nuMax); + } + + /// <summary> + /// find an operating point for the torque converter + /// + /// find the output torque and output speed for the given input torque and input speed + /// </summary> + /// <param name="inTorque"></param> + /// <param name="inAngularVelocity"></param> + /// <param name="outAngularSpeedEstimated"></param> + /// <returns></returns> + public TorqueConverterOperatingPoint FindOperatingPointForward(NewtonMeter inTorque, PerSecond inAngularVelocity, + PerSecond outAngularSpeedEstimated) + { + var referenceTorque = inTorque.Value() / inAngularVelocity.Value() / inAngularVelocity.Value() * + ReferenceSpeed.Value() * ReferenceSpeed.Value(); + var maxTorque = TorqueConverterEntries.Max(x => x.Torque.Value()); + if (referenceTorque.IsGreaterOrEqual(maxTorque)) { + referenceTorque = outAngularSpeedEstimated != null + ? ReferenceTorqueLookup(outAngularSpeedEstimated / inAngularVelocity).Value() + : 0.9 * maxTorque; + } + + var solutions = new List<double>(); + foreach (var edge in TorqueConverterEntries.Pairwise( + (p1, p2) => Edge.Create(new Point(p1.SpeedRatio, p1.Torque.Value()), new Point(p2.SpeedRatio, p2.Torque.Value())))) { + var x = (referenceTorque - edge.OffsetXY) / edge.SlopeXY; + if (x >= edge.P1.X && x < edge.P2.X) { + solutions.Add(x * inAngularVelocity.Value()); + } + } + if (solutions.Count == 0) { + throw new VectoSimulationException( + "Torque Converter: Failed to find operating point for inputTorque/inputSpeed! n_in: {0}, tq_in: {1}", + inAngularVelocity, inTorque); + } + return FindOperatingPoint(inAngularVelocity, solutions.Max().SI<PerSecond>()); + } + + public TorqueConverterOperatingPoint LookupOperatingPoint( + PerSecond outAngularVelocity, PerSecond inAngularVelocity, NewtonMeter outTorque) + { + var nu = outAngularVelocity / inAngularVelocity; + foreach (var edge in TorqueConverterEntries.Pairwise((p1, p2) => Edge.Create(new Point(p1.SpeedRatio, p1.TorqueRatio), new Point(p2.SpeedRatio, p2.TorqueRatio)))) { + if (nu >= edge.P1.X && nu < edge.P2.X) { + var my = VectoMath.Interpolate(edge, nu); + return new TorqueConverterOperatingPoint() { + InAngularVelocity = inAngularVelocity, + OutAngularVelocity = outAngularVelocity, + OutTorque = outTorque, + InTorque = outTorque / my, + SpeedRatio = nu, + TorqueRatio = my, + }; + } + } + throw new VectoSimulationException( + "Torque Converter: Failed to find operating point for outputSpeed/outputTorque/inputSpeed! n_out: {0}, n_in: {1}, tq_out: {2}", + outAngularVelocity, inAngularVelocity, outTorque); + } + + public TorqueConverterOperatingPoint FindOperatingPointForPowerDemand(Watt power, PerSecond prevInputSpeed, + PerSecond nextOutputSpeed, KilogramSquareMeter inertia, Second dt, Watt previousPower) + { + var solutions = new List<double>(); + var mpNorm = ReferenceSpeed.Value(); + + foreach (var segment in TorqueConverterEntries.Pairwise(Tuple.Create)) { + var mpEdge = Edge.Create(new Point(segment.Item1.SpeedRatio, segment.Item1.Torque.Value()), + new Point(segment.Item2.SpeedRatio, segment.Item2.Torque.Value())); + + var a = mpEdge.OffsetXY / (2 * mpNorm * mpNorm); + var b = inertia.Value() / (2 * dt.Value()) + mpEdge.SlopeXY * nextOutputSpeed.Value() / (2 * mpNorm * mpNorm); + var c = 0; + var d = -inertia.Value() * prevInputSpeed.Value() * prevInputSpeed.Value() / (2 * dt.Value()) - power.Value() + + previousPower.Value() / 2; + var sol = VectoMath.CubicEquationSolver(a, b, c, d); + + var selected = sol.Where(x => x > 0 && nextOutputSpeed / x >= mpEdge.P1.X && nextOutputSpeed / x < mpEdge.P2.X); + solutions.AddRange(selected); + } + + if (solutions.Count == 0) { + throw new VectoException( + "Failed to find operating point for power {0}, prevInputSpeed {1}, nextOutputSpeed {2}", power, + prevInputSpeed, nextOutputSpeed); + } + solutions.Sort(); + + return FindOperatingPoint(solutions.First().SI<PerSecond>(), nextOutputSpeed); + } + + private double MuLookup(double speedRatio) + { + return TorqueConverterEntries.Interpolate(x => x.SpeedRatio, y => y.TorqueRatio, speedRatio); + } + + private NewtonMeter ReferenceTorqueLookup(double speedRatio) + { + return TorqueConverterEntries.Interpolate(x => x.SpeedRatio, y => y.Torque, speedRatio); + } + + // ReSharper disable once UnusedMember.Global -- used by validation + public static ValidationResult ValidateData(TorqueConverterData data, ValidationContext validationContext) + { + var min = data.TorqueConverterEntries.Min(e => e.SpeedRatio); + var max = data.TorqueConverterEntries.Max(e => e.SpeedRatio); + if (min > 0 || max < data.RequiredSpeedRatio) { + return new ValidationResult(string.Format( + "Torque Converter Data invalid - Speedratio has to cover the range from 0.0 to {2}: given data only goes from {0} to {1}", + min, max, data.RequiredSpeedRatio)); + } + + return ValidationResult.Success; + } + } + + public class TorqueConverterOperatingPoint + { + public PerSecond OutAngularVelocity; + public NewtonMeter OutTorque; + + public PerSecond InAngularVelocity; + public NewtonMeter InTorque; + + public double SpeedRatio; + public double TorqueRatio; + public bool Creeping; + + public override string ToString() + { + return string.Format("n_out: {0}, n_in: {1}, tq_out: {2}, tq_in {3}, nu: {4}, my: {5}", OutAngularVelocity, + InAngularVelocity, OutTorque, InTorque, SpeedRatio, TorqueRatio); + } + } + + public class TorqueConverterEntry + { + public double SpeedRatio; + public NewtonMeter Torque; + public double TorqueRatio; + } } \ No newline at end of file diff --git a/VectoCore/VectoCore/Models/SimulationComponent/Impl/CycleGearbox.cs b/VectoCore/VectoCore/Models/SimulationComponent/Impl/CycleGearbox.cs index 5d585fe3cc9a00ba6f3022460954c7de2c7ad518..6ba54d2fecbd2fefd407111aecfd536fa8f1d153 100644 --- a/VectoCore/VectoCore/Models/SimulationComponent/Impl/CycleGearbox.cs +++ b/VectoCore/VectoCore/Models/SimulationComponent/Impl/CycleGearbox.cs @@ -54,7 +54,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl protected bool? TorqueConverterActive; - protected internal readonly TorqueConverter TorqueConverter; + protected internal readonly CycleTorqueConverter TorqueConverter; public CycleGearbox(IVehicleContainer container, VectoRunData runData) : base(container, runData) @@ -62,8 +62,9 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl if (!ModelData.Type.AutomaticTransmission()) { return; } + var strategy = new CycleShiftStrategy(ModelData, null); - TorqueConverter = new TorqueConverter(this, strategy, container, ModelData.TorqueConverterData, runData); + TorqueConverter = new CycleTorqueConverter(container, ModelData.TorqueConverterData); if (TorqueConverter == null) { throw new VectoException("Torque Converter required for AT transmission!"); } @@ -106,7 +107,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl inTorque += torqueLossInertia; response = TorqueConverterActive != null && TorqueConverterActive.Value && TorqueConverter != null - ? TorqueConverter.Initialize(inTorque, inAngularVelocity) + ? TorqueConverter.Initialize(inTorque, inAngularVelocity, GetEngineSpeedFromCycle()) : NextComponent.Initialize(inTorque, inAngularVelocity); } else { response = NextComponent.Initialize(inTorque, inAngularVelocity); @@ -130,7 +131,8 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl /// <item><description>ResponseGearshift</description></item> /// </list> /// </returns> - public override IResponse Request(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, + public override IResponse Request( + Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, bool dryRun = false) { Log.Debug("Gearbox Power Request: torque: {0}, angularVelocity: {1}", outTorque, outAngularVelocity); @@ -150,6 +152,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl // mk 2016-11-30: added additional check for outAngularVelocity due to failing test: MeasuredSpeed_Gear_AT_PS_Run // mq 2016-12-16: changed check to vehicle halted due to failing test: MeasuredSpeed_Gear_AT_* var retVal = gear == 0 || DataBus.DriverBehavior == DrivingBehavior.Halted + //|| (outAngularVelocity.IsSmallerOrEqual(0, 1) && outTorque.IsSmallerOrEqual(0, 1)) ? RequestDisengaged(absTime, dt, outTorque, outAngularVelocity, dryRun) : RequestEngaged(absTime, dt, outTorque, outAngularVelocity, dryRun); @@ -165,6 +168,13 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl : DataBus.CycleData.RightSample.Gear; } + protected virtual PerSecond GetEngineSpeedFromCycle() + { + return DataBus.DriverBehavior == DrivingBehavior.Braking + ? DataBus.CycleData.LeftSample.EngineSpeed + : DataBus.CycleData.RightSample.EngineSpeed; + } + /// <summary> /// Handles requests when a gear is engaged /// </summary> @@ -174,7 +184,8 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl /// <param name="outAngularVelocity"></param> /// <param name="dryRun"></param> /// <returns></returns> - private IResponse RequestEngaged(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, + private IResponse RequestEngaged( + Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, bool dryRun) { Disengaged = null; @@ -222,15 +233,17 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl CurrentState.SetState(inTorque, inAngularVelocity, outTorque, outAngularVelocity); CurrentState.Gear = Gear; + // end critical section if (TorqueConverter != null && !torqueConverterLocked) { CurrentState.TorqueConverterActive = true; - return TorqueConverter.Request(absTime, dt, inTorque, inAngularVelocity); + return TorqueConverter.Request(absTime, dt, inTorque, inAngularVelocity, GetEngineSpeedFromCycle()); } if (TorqueConverter != null) { - TorqueConverter.Locked(CurrentState.InTorque, CurrentState.InAngularVelocity, CurrentState.InTorque, + TorqueConverter.Locked( + CurrentState.InTorque, CurrentState.InAngularVelocity, CurrentState.InTorque, CurrentState.InAngularVelocity); } var response = NextComponent.Request(absTime, dt, inTorque, inAngularVelocity); @@ -241,7 +254,8 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl private void CheckModelData(TransmissionLossMap effectiveLossMap, double effectiveRatio, bool torqueConverterLocked) { if (effectiveLossMap == null || double.IsNaN(effectiveRatio)) { - throw new VectoSimulationException("Ratio or loss-map for gear {0}{1} invalid. Please check input data", Gear, + throw new VectoSimulationException( + "Ratio or loss-map for gear {0}{1} invalid. Please check input data", Gear, torqueConverterLocked ? "L" : "C"); } if (!torqueConverterLocked && !ModelData.Gears[Gear].HasTorqueConverter) { @@ -249,12 +263,14 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl } } - private IResponse HandleDryRunRequest(Second absTime, Second dt, bool torqueConverterLocked, NewtonMeter inTorque, + private IResponse HandleDryRunRequest( + Second absTime, Second dt, bool torqueConverterLocked, NewtonMeter inTorque, PerSecond inAngularVelocity) { if (TorqueConverter != null && !torqueConverterLocked) { - return TorqueConverter.Request(absTime, dt, inTorque, inAngularVelocity, true); + return TorqueConverter.Request(absTime, dt, inTorque, inAngularVelocity, GetEngineSpeedFromCycle(), true); } + // mk 2016-12-13 //if (outTorque.IsSmaller(0) && inAngularVelocity.IsSmaller(DataBus.EngineIdleSpeed)) { // //Log.Warn("engine speed would fall below idle speed - disengage! gear from cycle: {0}, vehicle speed: {1}", Gear, @@ -274,7 +290,8 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl /// <param name="outAngularVelocity"></param> /// <param name="dryRun"></param> /// <returns></returns> - private IResponse RequestDisengaged(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, + private IResponse RequestDisengaged( + Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, bool dryRun) { if (Disengaged == null) { @@ -314,16 +331,19 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl disengagedResponse = EngineIdleRequest(absTime, dt); } else { disengagedResponse = NextGear.Gear > 0 - ? NextComponent.Request(absTime, dt, 0.SI<NewtonMeter>(), + ? NextComponent.Request( + absTime, dt, 0.SI<NewtonMeter>(), outAngularVelocity * ModelData.Gears[NextGear.Gear].Ratio) : EngineIdleRequest(absTime, dt); } if (TorqueConverter != null) { if (DataBus.VehicleStopped) { - TorqueConverter.Locked(0.SI<NewtonMeter>(), disengagedResponse.EngineSpeed, CurrentState.InTorque, + TorqueConverter.Locked( + 0.SI<NewtonMeter>(), disengagedResponse.EngineSpeed, CurrentState.InTorque, outAngularVelocity); } else { - TorqueConverter.Locked(CurrentState.InTorque, disengagedResponse.EngineSpeed, CurrentState.InTorque, + TorqueConverter.Locked( + CurrentState.InTorque, disengagedResponse.EngineSpeed, CurrentState.InTorque, disengagedResponse.EngineSpeed); } } @@ -340,11 +360,13 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl if (disengagedResponse is ResponseSuccess) { return disengagedResponse; } + var motoringSpeed = DataBus.EngineSpeed; if (motoringSpeed.IsGreater(DataBus.EngineIdleSpeed)) { var first = (ResponseDryRun)NextComponent.Request(absTime, dt, 0.SI<NewtonMeter>(), motoringSpeed, true); try { - motoringSpeed = SearchAlgorithm.Search(motoringSpeed, first.DeltaDragLoad, + motoringSpeed = SearchAlgorithm.Search( + motoringSpeed, first.DeltaDragLoad, Constants.SimulationSettings.EngineIdlingSearchInterval, getYValue: result => ((ResponseDryRun)result).DeltaDragLoad, evaluateFunction: n => NextComponent.Request(absTime, dt, 0.SI<NewtonMeter>(), n, true), @@ -378,6 +400,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl ? 0.SI<Watt>() : CurrentState.PowershiftLosses * avgInAngularSpeed; } + // torque converter fields are written by TorqueConverter (if present), called from Vehicle container } @@ -395,6 +418,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl } } } + base.DoCommitSimulationStep(); } @@ -402,11 +426,11 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl public override GearInfo NextGear { - get - { + get { if (Disengaged == null) { return new GearInfo(Gear, !TorqueConverterActive ?? true); } + var future = DataBus.LookAhead(ModelData.TractionInterruption * 5); var nextGear = 0u; var torqueConverterLocked = false; @@ -419,24 +443,27 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl // vehicle is stopped, no next gear, engine should go to idle break; } + if (entry.Gear == 0) { continue; } + nextGear = entry.Gear; torqueConverterLocked = !entry.TorqueConverterActive ?? false; break; } + return new GearInfo(nextGear, torqueConverterLocked); } } public override Second TractionInterruption { - get - { + get { if (Disengaged == null) { return ModelData.TractionInterruption; } + var future = DataBus.LookAhead(ModelData.TractionInterruption * 5); foreach (var entry in future) { if (entry.VehicleTargetSpeed != null && entry.VehicleTargetSpeed.IsEqual(0)) { @@ -447,11 +474,14 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl // vehicle is stopped, no next gear, engine should go to idle break; } + if (entry.Gear == 0) { continue; } + return entry.Time - Disengaged; } + return ModelData.TractionInterruption; } } @@ -459,8 +489,8 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl public override bool ClutchClosed(Second absTime) { return (DataBus.DriverBehavior == DrivingBehavior.Braking - ? DataBus.CycleData.LeftSample.Gear - : DataBus.CycleData.RightSample.Gear) != 0; + ? DataBus.CycleData.LeftSample.Gear + : DataBus.CycleData.RightSample.Gear) != 0; } #endregion @@ -473,11 +503,12 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl public class CycleShiftStrategy : BaseShiftStrategy { - public CycleShiftStrategy(GearboxData data, IDataBus dataBus) : base(data, dataBus) {} + public CycleShiftStrategy(GearboxData data, IDataBus dataBus) : base(data, dataBus) { } public override IGearbox Gearbox { get; set; } - public override bool ShiftRequired(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, + public override bool ShiftRequired( + Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, NewtonMeter inTorque, PerSecond inAngularVelocity, uint gear, Second lastShiftTime) { @@ -505,4 +536,69 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl } } } + + public class CycleTorqueConverter : StatefulVectoSimulationComponent<TorqueConverter.TorqueConverterComponentState> + { + protected internal ITnOutPort NextComponent; + private TorqueConverterData ModelData; + + public CycleTorqueConverter(IVehicleContainer container, TorqueConverterData modelData) : base(container) + { + ModelData = modelData; + } + + public IResponse Initialize(NewtonMeter outTorque, PerSecond outAngularVelocity, PerSecond inAngularVelocity) + { + + var operatingPoint = ModelData.LookupOperatingPoint(outAngularVelocity, inAngularVelocity, outTorque); + + PreviousState.OperatingPoint = operatingPoint; + return NextComponent.Initialize(operatingPoint.InTorque, inAngularVelocity); + } + + public IResponse Request( + Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, PerSecond inAngularVelocity, + bool dryRun = false) + { + var operatingPoint = ModelData.LookupOperatingPoint(outAngularVelocity, inAngularVelocity, outTorque); + if (!dryRun) { + CurrentState.OperatingPoint = operatingPoint; + } + return NextComponent.Request(absTime, dt, operatingPoint.InTorque, inAngularVelocity, dryRun); + } + + public void Locked( + NewtonMeter inTorque, PerSecond inAngularVelocity, NewtonMeter outTorque, + PerSecond outAngularVelocity) { } + + #region Overrides of VectoSimulationComponent + + protected override void DoWriteModalResults(IModalDataContainer container) + { + if (CurrentState.OperatingPoint == null) { + container[ModalResultField.TorqueConverterTorqueRatio] = 1.0; + container[ModalResultField.TorqueConverterSpeedRatio] = 1.0; + } else { + container[ModalResultField.TorqueConverterTorqueRatio] = CurrentState.OperatingPoint.TorqueRatio; + container[ModalResultField.TorqueConverterSpeedRatio] = CurrentState.OperatingPoint.SpeedRatio; + } + container[ModalResultField.TC_TorqueIn] = CurrentState.InTorque; + container[ModalResultField.TC_TorqueOut] = CurrentState.OutTorque; + container[ModalResultField.TC_angularSpeedIn] = CurrentState.InAngularVelocity; + container[ModalResultField.TC_angularSpeedOut] = CurrentState.OutAngularVelocity; + + var avgOutVelocity = (PreviousState.OutAngularVelocity + CurrentState.OutAngularVelocity) / 2.0; + var avgInVelocity = (PreviousState.InAngularVelocity + CurrentState.InAngularVelocity) / 2.0; + container[ModalResultField.P_TC_out] = CurrentState.OutTorque * avgOutVelocity; + container[ModalResultField.P_TC_loss] = CurrentState.InTorque * avgInVelocity - + CurrentState.OutTorque * avgOutVelocity; + } + + protected override void DoCommitSimulationStep() + { + AdvanceState(); + } + + #endregion + } } \ No newline at end of file diff --git a/VectoCore/VectoCore/Models/SimulationComponent/Impl/VTPCycle.cs b/VectoCore/VectoCore/Models/SimulationComponent/Impl/VTPCycle.cs index a0edc11e16c94a0f2680e939615c173f6382cc90..95c9d50e4747e3addc97d75f7001048d39374c54 100644 --- a/VectoCore/VectoCore/Models/SimulationComponent/Impl/VTPCycle.cs +++ b/VectoCore/VectoCore/Models/SimulationComponent/Impl/VTPCycle.cs @@ -47,7 +47,9 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl { internal class VTPCycle : PWheelCycle { - private uint StartGear; + protected uint StartGear; + + protected Second SimulationIntervalEndTime; public VTPCycle(VehicleContainer container, IDrivingCycleData cycle) : base(container, cycle) { } @@ -311,6 +313,11 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl DeltaT = CycleIterator.RightSample.Time - absTime }; } + + SimulationIntervalEndTime = absTime + dt; + if (CycleIterator.LeftSample.Time > absTime) { + Log.Warn("absTime: {0} cycle: {1}", absTime, CycleIterator.LeftSample.Time); + } var tmp = NextComponent.Initialize(CycleIterator.LeftSample.Torque, CycleIterator.LeftSample.WheelAngularVelocity); return DoHandleRequest(absTime, dt, CycleIterator.LeftSample.WheelAngularVelocity); @@ -325,6 +332,14 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl } } + protected override void DoCommitSimulationStep() + { + if (SimulationIntervalEndTime.IsGreaterOrEqual(CycleIterator.RightSample.Time)) { + CycleIterator.MoveNext(); + } + AdvanceState(); + } + protected override void DoWriteModalResults(IModalDataContainer container) { base.DoWriteModalResults(container);