diff --git a/VectoCommon/VectoCommon/Models/OperatingPoint.cs b/VectoCommon/VectoCommon/Models/OperatingPoint.cs index c5b154f942a2da0103f7c934dd234f9174b2c0e7..627ef89223fc810aff533b2b6c9981dc501ac2a5 100644 --- a/VectoCommon/VectoCommon/Models/OperatingPoint.cs +++ b/VectoCommon/VectoCommon/Models/OperatingPoint.cs @@ -35,7 +35,7 @@ using TUGraz.VectoCommon.Utils; namespace TUGraz.VectoCommon.Models { [DebuggerDisplay("a: {Acceleration}, dt: {SimulationInterval}, ds: {SimulationDistance}")] - public struct OperatingPoint + public class OperatingPoint { public MeterPerSquareSecond Acceleration; public Meter SimulationDistance; diff --git a/VectoCommon/VectoCommon/Utils/VectoMath.cs b/VectoCommon/VectoCommon/Utils/VectoMath.cs index 4a63f351383d5cdeac2a5e9d7a7351f13084cc51..ef7372a7fc1f700677817b56629f7ed8a75ec378 100644 --- a/VectoCommon/VectoCommon/Utils/VectoMath.cs +++ b/VectoCommon/VectoCommon/Utils/VectoMath.cs @@ -29,704 +29,704 @@ * Martin Rexeis, rexeis@ivt.tugraz.at, IVT, Graz University of Technology */ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Runtime.CompilerServices; -using TUGraz.VectoCommon.Exceptions; -using TUGraz.VectoCommon.Models; - -namespace TUGraz.VectoCommon.Utils -{ - /// <summary> - /// Provides helper methods for mathematical functions. - /// </summary> - public static class VectoMath - { - /// <summary> - /// Linearly interpolates a value between two points. - /// </summary> - /// <typeparam name="T"></typeparam> - /// <typeparam name="TResult">The type of the result.</typeparam> - /// <param name="x1">First Value on the X-Axis.</param> - /// <param name="x2">Second Value on the X-Axis.</param> - /// <param name="y1">First Value on the Y-Axis.</param> - /// <param name="y2">Second Value on the Y-Axis.</param> - /// <param name="xint">Value on the X-Axis, for which the Y-Value should be interpolated.</param> - /// <returns></returns> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TResult Interpolate<T, TResult>(T x1, T x2, TResult y1, TResult y2, T xint) where T : SI - where TResult : SIBase<TResult> - { - return Interpolate(x1.Value(), x2.Value(), y1.Value(), y2.Value(), xint.Value()).SI<TResult>(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TResult Interpolate<TInput, T, TResult>(this Tuple<TInput, TInput> self, Func<TInput, T> x, - Func<TInput, TResult> y, T xInterpolate) - where T : SIBase<T> - where TResult : SIBase<TResult> - { - return Interpolate(x(self.Item1).Value(), x(self.Item2).Value(), y(self.Item1).Value(), y(self.Item2).Value(), - xInterpolate.Value()).SI<TResult>(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TResult Interpolate<TInput, TResult>(this Tuple<TInput, TInput> self, Func<TInput, double> x, - Func<TInput, TResult> y, double xInterpolate) - where TResult : SIBase<TResult> - { - return - Interpolate(x(self.Item1), x(self.Item2), y(self.Item1).Value(), y(self.Item2).Value(), xInterpolate).SI<TResult>(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TResult Interpolate<TInput, TResult>(this IEnumerable<TInput> self, Func<TInput, double> x, - Func<TInput, TResult> y, double xInterpolate) - where TResult : SIBase<TResult> - { - return self.GetSection(elem => x(elem) < xInterpolate).Interpolate(x, y, xInterpolate); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double Interpolate<TInput>(this IEnumerable<TInput> self, Func<TInput, double> x, - Func<TInput, double> y, double xInterpolate) - { - return self.GetSection(elem => x(elem) < xInterpolate).Interpolate(x, y, xInterpolate); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double Interpolate<TInput>(this Tuple<TInput, TInput> self, Func<TInput, double> x, - Func<TInput, double> y, double xInterpolate) - { - return Interpolate(x(self.Item1), x(self.Item2), y(self.Item1), y(self.Item2), xInterpolate); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double Interpolate<T>(T x1, T x2, double y1, double y2, T xint) where T : SI - { - return Interpolate(x1.Value(), x2.Value(), y1, y2, xint.Value()); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TResult Interpolate<TResult>(double x1, double x2, TResult y1, TResult y2, double xint) - where TResult : SIBase<TResult> - { - return Interpolate(x1, x2, y1.Value(), y2.Value(), xint).SI<TResult>(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double Interpolate(Point p1, Point p2, double x) - { - return Interpolate(p1.X, p2.X, p1.Y, p2.Y, x); - } - - /// <summary> - /// Linearly interpolates a value between two points. - /// </summary> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double Interpolate(double x1, double x2, double y1, double y2, double xint) - { - return (xint - x1) * (y2 - y1) / (x2 - x1) + y1; - } - - /// <summary> - /// Returns the absolute value. - /// </summary> - [DebuggerStepThrough] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SI Abs(SI si) - { - return si.Abs(); - } - - /// <summary> - /// Returns the minimum of two values. - /// </summary> - [DebuggerStepThrough] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Min<T>(T c1, T c2) where T : IComparable - { - if (c1 == null) { - return c2; - } - - if (c2 == null) { - return c1; - } - - return c1.CompareTo(c2) <= 0 ? c1 : c2; - } - - /// <summary> - /// Returns the maximum of two values. - /// </summary> - [DebuggerStepThrough] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Max<T>(T c1, T c2) where T : IComparable - { - return c1.CompareTo(c2) > 0 ? c1 : c2; - } - - /// <summary> - /// Returns the maximum of two values. - /// </summary> - [DebuggerStepThrough] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Max<T>(double c1, T c2) where T : SIBase<T> - { - return c1 > c2.Value() ? c1.SI<T>() : c2; - } - - [DebuggerStepThrough] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Max<T>(T c1, T c2, T c3) where T : SIBase<T> - { - return Max(Max(c1, c2), c3); - } - - [DebuggerStepThrough] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T LimitTo<T>(this T value, T lowerBound, T upperBound) where T : IComparable - { - if (lowerBound.CompareTo(upperBound) > 0) { - throw new VectoException( - "VectoMath.LimitTo: lowerBound must not be greater than upperBound. lowerBound: {0}, upperBound: {1}", lowerBound, - upperBound); - } - - if (value.CompareTo(upperBound) > 0) { - return upperBound; - } - - if (value.CompareTo(lowerBound) < 0) { - return lowerBound; - } - - return value; - } - - /// <summary> - /// converts the given inclination in percent (0-1+) into Radians - /// </summary> - [DebuggerStepThrough] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Radian InclinationToAngle(double inclinationPercent) - { - return Math.Atan(inclinationPercent).SI<Radian>(); - } - - public static double[] QuadraticEquationSolver(double a, double b, double c) - { - var d = b * b - 4 * a * c; - - // no real solution - if (d < 0) { - return new double[0]; - } - - if (d > 0) { - // two solutions - return new[] { (-b + Math.Sqrt(d)) / (2 * a), (-b - Math.Sqrt(d)) / (2 * a) }; - } - - // one real solution - return new[] { -b / (2 * a) }; - } - - public static Point Intersect(Edge line1, Edge line2) - { - var s10X = line1.P2.X - line1.P1.X; - var s10Y = line1.P2.Y - line1.P1.Y; - var s32X = line2.P2.X - line2.P1.X; - var s32Y = line2.P2.Y - line2.P1.Y; - - var denom = s10X * s32Y - s32X * s10Y; - if (denom.IsEqual(0)) { - return null; - } - - var s02X = line1.P1.X - line2.P1.X; - var s02Y = line1.P1.Y - line2.P1.Y; - var sNumer = s10X * s02Y - s10Y * s02X; - if ((sNumer < 0) == (denom > 0)) { - return null; - } - var tNumer = s32X * s02Y - s32Y * s02X; - if ((tNumer < 0) == (denom > 0)) { - return null; - } - if (((sNumer > denom) == (denom > 0)) || ((tNumer > denom) == (denom > 0))) { - return null; - } - var t = tNumer / denom; - - return new Point(line1.P1.X + t * s10X, line1.P1.Y + t * s10Y); - } - - /// <summary> - /// Computes the time interval for driving the given distance ds with the vehicle's current speed and the given acceleration. - /// If the distance ds can not be reached (i.e., the vehicle would halt before ds is reached) then the distance parameter is adjusted. - /// Returns a new operating point (a, ds, dt) - /// </summary> - /// <param name="currentSpeed">vehicle's current speed at the beginning of the simulation interval</param> - /// <param name="acceleration">vehicle's acceleration</param> - /// <param name="distance">absolute distance at the beginning of the simulation interval (can be 0)</param> - /// <param name="ds">distance to drive in the current simulation interval</param> - /// <returns>Operating point (a, ds, dt)</returns> - public static OperatingPoint ComputeTimeInterval(MeterPerSecond currentSpeed, MeterPerSquareSecond acceleration, - Meter distance, Meter ds) - { - if (!(ds > 0)) { - throw new VectoSimulationException("ds has to be greater than 0! ds: {0}", ds); - } - - var retVal = new OperatingPoint() { Acceleration = acceleration, SimulationDistance = ds }; - if (acceleration.IsEqual(0)) { - if (currentSpeed > 0) { - retVal.SimulationInterval = ds / currentSpeed; - return retVal; - } - //Log.Error("{2}: vehicle speed is {0}, acceleration is {1}", currentSpeed.Value(), acceleration.Value(), - // distance); - throw new VectoSimulationException( - "vehicle speed has to be > 0 if acceleration = 0! v: {0}, a: {1}, distance: {2}", currentSpeed.Value(), - acceleration.Value(), distance); - } - - // we need to accelerate / decelerate. solve quadratic equation... - // ds = acceleration / 2 * dt^2 + currentSpeed * dt => solve for dt - var solutions = QuadraticEquationSolver(acceleration.Value() / 2.0, currentSpeed.Value(), - -ds.Value()); - - if (solutions.Length == 0) { - // no real-valued solutions: acceleration is so negative that vehicle stops already before the required distance can be reached. - // adapt ds to the halting-point. - // t = v / a - var dt = currentSpeed / -acceleration; - - // s = a/2*t^2 + v*t - var stopDistance = acceleration / 2 * dt * dt + currentSpeed * dt; - - if (stopDistance.IsGreater(ds)) { - // just to cover everything - does not happen... - //Log.Error( - // "Could not find solution for computing required time interval to drive distance ds: {0}. currentSpeed: {1}, acceleration: {2}, stopDistance: {3}, distance: {4}", - // ds, currentSpeed, acceleration, stopDistance,distance); - throw new VectoSimulationException("Could not find solution for time-interval! ds: {0}, stopDistance: {1}", ds, - stopDistance); - } - - //LoggingObject.Logger<>().Info( - // "Adjusted distance when computing time interval: currentSpeed: {0}, acceleration: {1}, distance: {2} -> {3}, timeInterval: {4}", - // currentSpeed, acceleration, stopDistance, stopDistance, dt); - - retVal.SimulationInterval = dt; - retVal.SimulationDistance = stopDistance; - return retVal; - } - // if there are 2 positive solutions (i.e. when decelerating), take the smaller time interval - // (the second solution means that you reach negative speed) - retVal.SimulationInterval = solutions.Where(x => x >= 0).Min().SI<Second>(); - return retVal; - } - - [DebuggerStepThrough] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Ceiling<T>(T si) where T : SIBase<T> - { - return Math.Ceiling(si.Value()).SI<T>(); - } - - public static double[] CubicEquationSolver(double a, double b, double c, double d) - { - var solutions = new List<double>(); - if (a.IsEqual(0, 1e-12)) { - return QuadraticEquationSolver(b, c, d); - } - var w = b / (3 * a); - var p = Math.Pow(c / (3 * a) - w * w, 3); - var q = -0.5 * (2 * (w * w * w) - (c * w - d) / a); - var discriminant = q * q + p; - if (discriminant < 0.0) { - // 3 real solutions - var h = q / Math.Sqrt(-p); - var phi = Math.Acos(Math.Max(-1.0, Math.Min(1.0, h))); - p = 2 * Math.Pow(-p, 1.0 / 6.0); - for (var i = 0; i < 3; i++) { - solutions.Add(p * Math.Cos((phi + 2 * i * Math.PI) / 3.0) - w); - } - } else { - // one real solution - discriminant = Math.Sqrt(discriminant); - solutions.Add(Cbrt(q + discriminant) + Cbrt(q - discriminant) - w); - } - - // 1 Newton iteration step in order to minimize round-off errors - for (var i = 0; i < solutions.Count; i++) { - var h = c + solutions[i] * (2 * b + 3 * solutions[i] * a); - if (!h.IsEqual(0, 1e-12)) { - solutions[i] -= (d + solutions[i] * (c + solutions[i] * (b + solutions[i] * a))) / h; - } - } - solutions.Sort(); - return solutions.ToArray(); - } - - private static double Cbrt(double x) - { - return x < 0 ? -Math.Pow(-x, 1.0 / 3.0) : Math.Pow(x, 1.0 / 3.0); - } - - - public static void LeastSquaresFitting<T>(IEnumerable<T> entries, Func<T, double> getX, Func<T, double> getY, - out double k, out double d, out double r) - { - // algoritm taken from http://mathworld.wolfram.com/LeastSquaresFitting.html (eqn. 27 & 28) - var count = 0; - var sumX = 0.0; - var sumY = 0.0; - var sumXSquare = 0.0; - var sumYSquare = 0.0; - var sumXY = 0.0; - foreach (var entry in entries) { - var x = getX(entry); - var y = getY(entry); - sumX += x; - sumY += y; - sumXSquare += x * x; - sumYSquare += y * y; - sumXY += x * y; - count++; - } - if (count == 0) { - k = 0; - d = 0; - r = 0; - return; - } - var ssxx = sumXSquare - sumX * sumX / count; - var ssxy = sumXY - sumX * sumY / count; - var ssyy = sumYSquare - sumY * sumY / count; - k = ssxy / ssxx; - d = (sumY - k * sumX) / count; - r = ssxy * ssxy / ssxx / ssyy; - } - } - - [DebuggerDisplay("(X:{X}, Y:{Y}, Z:{Z})")] - public class Point - { - public readonly double X; - public readonly double Y; - public readonly double Z; - - public Point(double x, double y, double z = 0) - { - X = x; - Y = y; - Z = z; - } - - public static Point operator +(Point p1, Point p2) - { - return new Point(p1.X + p2.X, p1.Y + p2.Y, p1.Z + p2.Z); - } - - public static Point operator -(Point p1, Point p2) - { - return new Point(p1.X - p2.X, p1.Y - p2.Y, p1.Z - p2.Z); - } - - public static Point operator -(Point p1) - { - return new Point(-p1.X, -p1.Y); - } - - public static Point operator *(Point p1, double scalar) - { - return new Point(p1.X * scalar, p1.Y * scalar, p1.Z * scalar); - } - - public static Point operator *(double scalar, Point p1) - { - return p1 * scalar; - } - - /// <summary> - /// Returns perpendicular vector for xy-components of this point. P = (-Y, X) - /// </summary> - /// <returns></returns> - public Point Perpendicular() - { - return new Point(-Y, X); - } - - /// <summary> - /// Returns dot product between two 3d-vectors. - /// </summary> - /// <param name="other"></param> - /// <returns></returns> - public double Dot(Point other) - { - return X * other.X + Y * other.Y + Z * other.Z; - } - - #region Equality members - - private bool Equals(Point other) - { - return X.IsEqual(other.X) && Y.IsEqual(other.Y) && Z.IsEqual(other.Z); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) { - return false; - } - return obj.GetType() == GetType() && Equals((Point)obj); - } - - public override int GetHashCode() - { - return unchecked((((X.GetHashCode() * 397) ^ Y.GetHashCode()) * 397) ^ Z.GetHashCode()); - } - - #endregion - - /// <summary> - /// Test if point is on the left side of an edge. - /// </summary> - /// <param name="e"></param> - /// <returns></returns> - public bool IsLeftOf(Edge e) - { - var abX = e.P2.X - e.P1.X; - var abY = e.P2.Y - e.P1.Y; - var acX = X - e.P1.X; - var acY = Y - e.P1.Y; - var z = abX * acY - abY * acX; - return z.IsGreater(0); - } - } - - [DebuggerDisplay("Plane({X}, {Y}, {Z}, {W})")] - public class Plane - { - public readonly double X; - public readonly double Y; - public readonly double Z; - public readonly double W; - - public Plane(Triangle tr) - { - var abX = tr.P2.X - tr.P1.X; - var abY = tr.P2.Y - tr.P1.Y; - var abZ = tr.P2.Z - tr.P1.Z; - - var acX = tr.P3.X - tr.P1.X; - var acY = tr.P3.Y - tr.P1.Y; - var acZ = tr.P3.Z - tr.P1.Z; - - X = abY * acZ - abZ * acY; - Y = abZ * acX - abX * acZ; - Z = abX * acY - abY * acX; - W = tr.P1.X * X + tr.P1.Y * Y + tr.P1.Z * Z; - } - } - - [DebuggerDisplay("Triangle(({P1.X}, {P1.Y}, {P1.Z}), ({P2.X}, {P2.Y}, {P2.Z}), ({P3.X}, {P3.Y}, {P3.Z}))")] - public class Triangle - { - public readonly Point P1; - public readonly Point P2; - public readonly Point P3; - - public Triangle(Point p1, Point p2, Point p3) - { - P1 = p1; - P2 = p2; - P3 = p3; - - if ((P1.X.IsEqual(P2.X) && P2.X.IsEqual(P3.X)) || (P1.Y.IsEqual(P2.Y) && P2.Y.IsEqual(P3.Y))) { - throw new VectoException("triangle is not extrapolatable by a plane."); - } - } - - /// <summary> - /// Check if Point is inside of Triangle. Barycentric Technique: http://www.blackpawn.com/texts/pointinpoly/default.html - /// </summary> - public bool IsInside(double x, double y, bool exact) - { - var smallerY = y - DoubleExtensionMethods.Tolerance; - var biggerY = y + DoubleExtensionMethods.Tolerance; - var smallerX = x - DoubleExtensionMethods.Tolerance; - var biggerX = x + DoubleExtensionMethods.Tolerance; - - if ((P1.Y < smallerY && P2.Y < smallerY && P3.Y < smallerY) - || (P1.X < smallerX && P2.X < smallerX && P3.X < smallerX) - || (P1.X > biggerX && P2.X > biggerX && P3.X > biggerX) - || (P1.Y > biggerY && P2.Y > biggerY && P3.Y > biggerY)) { - return false; - } - - var v0X = P3.X - P1.X; - var v0Y = P3.Y - P1.Y; - var v1X = P2.X - P1.X; - var v1Y = P2.Y - P1.Y; - var v2X = x - P1.X; - var v2Y = y - P1.Y; - - var dot00 = v0X * v0X + v0Y * v0Y; - var dot01 = v0X * v1X + v0Y * v1Y; - var dot02 = v0X * v2X + v0Y * v2Y; - var dot11 = v1X * v1X + v1Y * v1Y; - var dot12 = v1X * v2X + v1Y * v2Y; - - var invDenom = 1.0 / (dot00 * dot11 - dot01 * dot01); - var u = (dot11 * dot02 - dot01 * dot12) * invDenom; - var v = (dot00 * dot12 - dot01 * dot02) * invDenom; - - if (exact) { - return u >= 0 && v >= 0 && u + v <= 1; - } - - return u.IsPositive() && v.IsPositive() && (u + v).IsSmallerOrEqual(1); - } - - public bool ContainsInCircumcircle(Point p) - { - var p0X = P1.X - p.X; - var p0Y = P1.Y - p.Y; - var p1X = P2.X - p.X; - var p1Y = P2.Y - p.Y; - var p2X = P3.X - p.X; - var p2Y = P3.Y - p.Y; - - var p0Square = p0X * p0X + p0Y * p0Y; - var p1Square = p1X * p1X + p1Y * p1Y; - var p2Square = p2X * p2X + p2Y * p2Y; - - var det01 = p0X * p1Y - p1X * p0Y; - var det12 = p1X * p2Y - p2X * p1Y; - var det20 = p2X * p0Y - p0X * p2Y; - - var result = p0Square * det12 + p1Square * det20 + p2Square * det01; - return result > 0; - } - - public bool Contains(Point p) - { - return p.Equals(P1) || p.Equals(P2) || p.Equals(P3); - } - - public bool SharesVertexWith(Triangle t) - { - return Contains(t.P1) || Contains(t.P2) || Contains(t.P3); - } - - public IEnumerable<Edge> GetEdges() - { - return new[] { new Edge(P1, P2), new Edge(P2, P3), new Edge(P3, P1) }; - } - - #region Equality members - - protected bool Equals(Triangle other) - { - return Equals(P1, other.P1) && Equals(P2, other.P2) && Equals(P3, other.P3); - } - - 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((Triangle)obj); - } - - public override int GetHashCode() - { - unchecked { - var hashCode = P1.GetHashCode(); - hashCode = (hashCode * 397) ^ P2.GetHashCode(); - hashCode = (hashCode * 397) ^ P3.GetHashCode(); - return hashCode; - } - } - - #endregion - } - - [DebuggerDisplay("Edge(({P1.X}, {P1.Y},{P1.Z}), ({P2.X}, {P2.Y},{P2.Z}))")] - public class Edge - { - public readonly Point P1; - public readonly Point P2; - - private Point _vector; - - public Edge(Point p1, Point p2) - { - P1 = p1; - P2 = p2; - } - - public Point Vector - { - get { return _vector ?? (_vector = P2 - P1); } - } - - public double SlopeXY - { - get { return Vector.Y / Vector.X; } - } - - public double OffsetXY - { - get { return P2.Y - SlopeXY * P2.X; } - } - - #region Equality members - - protected bool Equals(Edge other) - { - return (P1.Equals(other.P1) && Equals(P2, other.P2)) || (P1.Equals(other.P2) && P2.Equals(other.P1)); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) { - return false; - } - if (ReferenceEquals(this, obj)) { - return true; - } - return obj.GetType() == GetType() && Equals((Edge)obj); - } - - public override int GetHashCode() - { - return P1.GetHashCode() ^ P2.GetHashCode(); - } - - #endregion - - public static Edge Create(Point arg1, Point arg2) - { - return new Edge(arg1, arg2); - } - - public bool ContainsXY(Point point) - { - return (SlopeXY * point.X + (P1.Y - SlopeXY * P1.X) - point.Y).IsEqual(0, 1E-9); - } - } +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using TUGraz.VectoCommon.Exceptions; +using TUGraz.VectoCommon.Models; + +namespace TUGraz.VectoCommon.Utils +{ + /// <summary> + /// Provides helper methods for mathematical functions. + /// </summary> + public static class VectoMath + { + /// <summary> + /// Linearly interpolates a value between two points. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <typeparam name="TResult">The type of the result.</typeparam> + /// <param name="x1">First Value on the X-Axis.</param> + /// <param name="x2">Second Value on the X-Axis.</param> + /// <param name="y1">First Value on the Y-Axis.</param> + /// <param name="y2">Second Value on the Y-Axis.</param> + /// <param name="xint">Value on the X-Axis, for which the Y-Value should be interpolated.</param> + /// <returns></returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TResult Interpolate<T, TResult>(T x1, T x2, TResult y1, TResult y2, T xint) where T : SI + where TResult : SIBase<TResult> + { + return Interpolate(x1.Value(), x2.Value(), y1.Value(), y2.Value(), xint.Value()).SI<TResult>(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TResult Interpolate<TInput, T, TResult>(this Tuple<TInput, TInput> self, Func<TInput, T> x, + Func<TInput, TResult> y, T xInterpolate) + where T : SIBase<T> + where TResult : SIBase<TResult> + { + return Interpolate(x(self.Item1).Value(), x(self.Item2).Value(), y(self.Item1).Value(), y(self.Item2).Value(), + xInterpolate.Value()).SI<TResult>(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TResult Interpolate<TInput, TResult>(this Tuple<TInput, TInput> self, Func<TInput, double> x, + Func<TInput, TResult> y, double xInterpolate) + where TResult : SIBase<TResult> + { + return + Interpolate(x(self.Item1), x(self.Item2), y(self.Item1).Value(), y(self.Item2).Value(), xInterpolate).SI<TResult>(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TResult Interpolate<TInput, TResult>(this IEnumerable<TInput> self, Func<TInput, double> x, + Func<TInput, TResult> y, double xInterpolate) + where TResult : SIBase<TResult> + { + return self.GetSection(elem => x(elem) < xInterpolate).Interpolate(x, y, xInterpolate); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double Interpolate<TInput>(this IEnumerable<TInput> self, Func<TInput, double> x, + Func<TInput, double> y, double xInterpolate) + { + return self.GetSection(elem => x(elem) < xInterpolate).Interpolate(x, y, xInterpolate); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double Interpolate<TInput>(this Tuple<TInput, TInput> self, Func<TInput, double> x, + Func<TInput, double> y, double xInterpolate) + { + return Interpolate(x(self.Item1), x(self.Item2), y(self.Item1), y(self.Item2), xInterpolate); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double Interpolate<T>(T x1, T x2, double y1, double y2, T xint) where T : SI + { + return Interpolate(x1.Value(), x2.Value(), y1, y2, xint.Value()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TResult Interpolate<TResult>(double x1, double x2, TResult y1, TResult y2, double xint) + where TResult : SIBase<TResult> + { + return Interpolate(x1, x2, y1.Value(), y2.Value(), xint).SI<TResult>(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double Interpolate(Point p1, Point p2, double x) + { + return Interpolate(p1.X, p2.X, p1.Y, p2.Y, x); + } + + /// <summary> + /// Linearly interpolates a value between two points. + /// </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double Interpolate(double x1, double x2, double y1, double y2, double xint) + { + return (xint - x1) * (y2 - y1) / (x2 - x1) + y1; + } + + /// <summary> + /// Returns the absolute value. + /// </summary> + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SI Abs(SI si) + { + return si.Abs(); + } + + /// <summary> + /// Returns the minimum of two values. + /// </summary> + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Min<T>(T c1, T c2) where T : IComparable + { + if (c1 == null) { + return c2; + } + + if (c2 == null) { + return c1; + } + + return c1.CompareTo(c2) <= 0 ? c1 : c2; + } + + /// <summary> + /// Returns the maximum of two values. + /// </summary> + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Max<T>(T c1, T c2) where T : IComparable + { + return c1.CompareTo(c2) > 0 ? c1 : c2; + } + + /// <summary> + /// Returns the maximum of two values. + /// </summary> + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Max<T>(double c1, T c2) where T : SIBase<T> + { + return c1 > c2.Value() ? c1.SI<T>() : c2; + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Max<T>(T c1, T c2, T c3) where T : SIBase<T> + { + return Max(Max(c1, c2), c3); + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T LimitTo<T>(this T value, T lowerBound, T upperBound) where T : IComparable + { + if (lowerBound.CompareTo(upperBound) > 0) { + throw new VectoException( + "VectoMath.LimitTo: lowerBound must not be greater than upperBound. lowerBound: {0}, upperBound: {1}", lowerBound, + upperBound); + } + + if (value.CompareTo(upperBound) > 0) { + return upperBound; + } + + if (value.CompareTo(lowerBound) < 0) { + return lowerBound; + } + + return value; + } + + /// <summary> + /// converts the given inclination in percent (0-1+) into Radians + /// </summary> + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Radian InclinationToAngle(double inclinationPercent) + { + return Math.Atan(inclinationPercent).SI<Radian>(); + } + + public static double[] QuadraticEquationSolver(double a, double b, double c) + { + var d = b * b - 4 * a * c; + + // no real solution + if (d < 0) { + return new double[0]; + } + + if (d > 0) { + // two solutions + return new[] { (-b + Math.Sqrt(d)) / (2 * a), (-b - Math.Sqrt(d)) / (2 * a) }; + } + + // one real solution + return new[] { -b / (2 * a) }; + } + + public static Point Intersect(Edge line1, Edge line2) + { + var s10X = line1.P2.X - line1.P1.X; + var s10Y = line1.P2.Y - line1.P1.Y; + var s32X = line2.P2.X - line2.P1.X; + var s32Y = line2.P2.Y - line2.P1.Y; + + var denom = s10X * s32Y - s32X * s10Y; + if (denom.IsEqual(0)) { + return null; + } + + var s02X = line1.P1.X - line2.P1.X; + var s02Y = line1.P1.Y - line2.P1.Y; + var sNumer = s10X * s02Y - s10Y * s02X; + if ((sNumer < 0) == (denom > 0)) { + return null; + } + var tNumer = s32X * s02Y - s32Y * s02X; + if ((tNumer < 0) == (denom > 0)) { + return null; + } + if (((sNumer > denom) == (denom > 0)) || ((tNumer > denom) == (denom > 0))) { + return null; + } + var t = tNumer / denom; + + return new Point(line1.P1.X + t * s10X, line1.P1.Y + t * s10Y); + } + + /// <summary> + /// Computes the time interval for driving the given distance ds with the vehicle's current speed and the given acceleration. + /// If the distance ds can not be reached (i.e., the vehicle would halt before ds is reached) then the distance parameter is adjusted. + /// Returns a new operating point (a, ds, dt) + /// </summary> + /// <param name="currentSpeed">vehicle's current speed at the beginning of the simulation interval</param> + /// <param name="acceleration">vehicle's acceleration</param> + /// <param name="distance">absolute distance at the beginning of the simulation interval (can be 0)</param> + /// <param name="ds">distance to drive in the current simulation interval</param> + /// <returns>Operating point (a, ds, dt)</returns> + public static OperatingPoint ComputeTimeInterval(MeterPerSecond currentSpeed, MeterPerSquareSecond acceleration, + Meter distance, Meter ds) + { + if (!(ds > 0)) { + throw new VectoSimulationException("ds has to be greater than 0! ds: {0}", ds); + } + + var retVal = new OperatingPoint() { Acceleration = acceleration, SimulationDistance = ds }; + if (acceleration.IsEqual(0)) { + if (currentSpeed > 0) { + retVal.SimulationInterval = ds / currentSpeed; + return retVal; + } + //Log.Error("{2}: vehicle speed is {0}, acceleration is {1}", currentSpeed.Value(), acceleration.Value(), + // distance); + throw new VectoSimulationException( + "vehicle speed has to be > 0 if acceleration = 0! v: {0}, a: {1}, distance: {2}", currentSpeed.Value(), + acceleration.Value(), distance); + } + + // we need to accelerate / decelerate. solve quadratic equation... + // ds = acceleration / 2 * dt^2 + currentSpeed * dt => solve for dt + var solutions = QuadraticEquationSolver(acceleration.Value() / 2.0, currentSpeed.Value(), + -ds.Value()); + + if (solutions.Length == 0) { + // no real-valued solutions: acceleration is so negative that vehicle stops already before the required distance can be reached. + // adapt ds to the halting-point. + // t = v / a + var dt = currentSpeed / -acceleration; + + // s = a/2*t^2 + v*t + var stopDistance = acceleration / 2 * dt * dt + currentSpeed * dt; + + if (stopDistance.IsGreater(ds)) { + // just to cover everything - does not happen... + //Log.Error( + // "Could not find solution for computing required time interval to drive distance ds: {0}. currentSpeed: {1}, acceleration: {2}, stopDistance: {3}, distance: {4}", + // ds, currentSpeed, acceleration, stopDistance,distance); + throw new VectoSimulationException("Could not find solution for time-interval! ds: {0}, stopDistance: {1}", ds, + stopDistance); + } + + //LoggingObject.Logger<>().Info( + // "Adjusted distance when computing time interval: currentSpeed: {0}, acceleration: {1}, distance: {2} -> {3}, timeInterval: {4}", + // currentSpeed, acceleration, stopDistance, stopDistance, dt); + + retVal.SimulationInterval = dt; + retVal.SimulationDistance = stopDistance; + return retVal; + } + // if there are 2 positive solutions (i.e. when decelerating), take the smaller time interval + // (the second solution means that you reach negative speed) + retVal.SimulationInterval = solutions.Where(x => x >= 0).Min().SI<Second>(); + return retVal; + } + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Ceiling<T>(T si) where T : SIBase<T> + { + return Math.Ceiling(si.Value()).SI<T>(); + } + + public static double[] CubicEquationSolver(double a, double b, double c, double d) + { + var solutions = new List<double>(); + if (a.IsEqual(0, 1e-12)) { + return QuadraticEquationSolver(b, c, d); + } + var w = b / (3 * a); + var p = Math.Pow(c / (3 * a) - w * w, 3); + var q = -0.5 * (2 * (w * w * w) - (c * w - d) / a); + var discriminant = q * q + p; + if (discriminant < 0.0) { + // 3 real solutions + var h = q / Math.Sqrt(-p); + var phi = Math.Acos(Math.Max(-1.0, Math.Min(1.0, h))); + p = 2 * Math.Pow(-p, 1.0 / 6.0); + for (var i = 0; i < 3; i++) { + solutions.Add(p * Math.Cos((phi + 2 * i * Math.PI) / 3.0) - w); + } + } else { + // one real solution + discriminant = Math.Sqrt(discriminant); + solutions.Add(Cbrt(q + discriminant) + Cbrt(q - discriminant) - w); + } + + // 1 Newton iteration step in order to minimize round-off errors + for (var i = 0; i < solutions.Count; i++) { + var h = c + solutions[i] * (2 * b + 3 * solutions[i] * a); + if (!h.IsEqual(0, 1e-12)) { + solutions[i] -= (d + solutions[i] * (c + solutions[i] * (b + solutions[i] * a))) / h; + } + } + solutions.Sort(); + return solutions.ToArray(); + } + + private static double Cbrt(double x) + { + return x < 0 ? -Math.Pow(-x, 1.0 / 3.0) : Math.Pow(x, 1.0 / 3.0); + } + + + public static void LeastSquaresFitting<T>(IEnumerable<T> entries, Func<T, double> getX, Func<T, double> getY, + out double k, out double d, out double r) + { + // algoritm taken from http://mathworld.wolfram.com/LeastSquaresFitting.html (eqn. 27 & 28) + var count = 0; + var sumX = 0.0; + var sumY = 0.0; + var sumXSquare = 0.0; + var sumYSquare = 0.0; + var sumXY = 0.0; + foreach (var entry in entries) { + var x = getX(entry); + var y = getY(entry); + sumX += x; + sumY += y; + sumXSquare += x * x; + sumYSquare += y * y; + sumXY += x * y; + count++; + } + if (count == 0) { + k = 0; + d = 0; + r = 0; + return; + } + var ssxx = sumXSquare - sumX * sumX / count; + var ssxy = sumXY - sumX * sumY / count; + var ssyy = sumYSquare - sumY * sumY / count; + k = ssxy / ssxx; + d = (sumY - k * sumX) / count; + r = ssxy * ssxy / ssxx / ssyy; + } + } + + [DebuggerDisplay("(X:{X}, Y:{Y}, Z:{Z})")] + public class Point + { + public readonly double X; + public readonly double Y; + public readonly double Z; + + public Point(double x, double y, double z = 0) + { + X = x; + Y = y; + Z = z; + } + + public static Point operator +(Point p1, Point p2) + { + return new Point(p1.X + p2.X, p1.Y + p2.Y, p1.Z + p2.Z); + } + + public static Point operator -(Point p1, Point p2) + { + return new Point(p1.X - p2.X, p1.Y - p2.Y, p1.Z - p2.Z); + } + + public static Point operator -(Point p1) + { + return new Point(-p1.X, -p1.Y); + } + + public static Point operator *(Point p1, double scalar) + { + return new Point(p1.X * scalar, p1.Y * scalar, p1.Z * scalar); + } + + public static Point operator *(double scalar, Point p1) + { + return p1 * scalar; + } + + /// <summary> + /// Returns perpendicular vector for xy-components of this point. P = (-Y, X) + /// </summary> + /// <returns></returns> + public Point Perpendicular() + { + return new Point(-Y, X); + } + + /// <summary> + /// Returns dot product between two 3d-vectors. + /// </summary> + /// <param name="other"></param> + /// <returns></returns> + public double Dot(Point other) + { + return X * other.X + Y * other.Y + Z * other.Z; + } + + #region Equality members + + private bool Equals(Point other) + { + return X.IsEqual(other.X) && Y.IsEqual(other.Y) && Z.IsEqual(other.Z); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) { + return false; + } + return obj.GetType() == GetType() && Equals((Point)obj); + } + + public override int GetHashCode() + { + return unchecked((((X.GetHashCode() * 397) ^ Y.GetHashCode()) * 397) ^ Z.GetHashCode()); + } + + #endregion + + /// <summary> + /// Test if point is on the left side of an edge. + /// </summary> + /// <param name="e"></param> + /// <returns></returns> + public bool IsLeftOf(Edge e) + { + var abX = e.P2.X - e.P1.X; + var abY = e.P2.Y - e.P1.Y; + var acX = X - e.P1.X; + var acY = Y - e.P1.Y; + var z = abX * acY - abY * acX; + return z.IsGreater(0); + } + } + + [DebuggerDisplay("Plane({X}, {Y}, {Z}, {W})")] + public class Plane + { + public readonly double X; + public readonly double Y; + public readonly double Z; + public readonly double W; + + public Plane(Triangle tr) + { + var abX = tr.P2.X - tr.P1.X; + var abY = tr.P2.Y - tr.P1.Y; + var abZ = tr.P2.Z - tr.P1.Z; + + var acX = tr.P3.X - tr.P1.X; + var acY = tr.P3.Y - tr.P1.Y; + var acZ = tr.P3.Z - tr.P1.Z; + + X = abY * acZ - abZ * acY; + Y = abZ * acX - abX * acZ; + Z = abX * acY - abY * acX; + W = tr.P1.X * X + tr.P1.Y * Y + tr.P1.Z * Z; + } + } + + [DebuggerDisplay("Triangle(({P1.X}, {P1.Y}, {P1.Z}), ({P2.X}, {P2.Y}, {P2.Z}), ({P3.X}, {P3.Y}, {P3.Z}))")] + public class Triangle + { + public readonly Point P1; + public readonly Point P2; + public readonly Point P3; + + public Triangle(Point p1, Point p2, Point p3) + { + P1 = p1; + P2 = p2; + P3 = p3; + + if ((P1.X.IsEqual(P2.X) && P2.X.IsEqual(P3.X)) || (P1.Y.IsEqual(P2.Y) && P2.Y.IsEqual(P3.Y))) { + throw new VectoException("triangle is not extrapolatable by a plane."); + } + } + + /// <summary> + /// Check if Point is inside of Triangle. Barycentric Technique: http://www.blackpawn.com/texts/pointinpoly/default.html + /// </summary> + public bool IsInside(double x, double y, bool exact) + { + var smallerY = y - DoubleExtensionMethods.Tolerance; + var biggerY = y + DoubleExtensionMethods.Tolerance; + var smallerX = x - DoubleExtensionMethods.Tolerance; + var biggerX = x + DoubleExtensionMethods.Tolerance; + + if ((P1.Y < smallerY && P2.Y < smallerY && P3.Y < smallerY) + || (P1.X < smallerX && P2.X < smallerX && P3.X < smallerX) + || (P1.X > biggerX && P2.X > biggerX && P3.X > biggerX) + || (P1.Y > biggerY && P2.Y > biggerY && P3.Y > biggerY)) { + return false; + } + + var v0X = P3.X - P1.X; + var v0Y = P3.Y - P1.Y; + var v1X = P2.X - P1.X; + var v1Y = P2.Y - P1.Y; + var v2X = x - P1.X; + var v2Y = y - P1.Y; + + var dot00 = v0X * v0X + v0Y * v0Y; + var dot01 = v0X * v1X + v0Y * v1Y; + var dot02 = v0X * v2X + v0Y * v2Y; + var dot11 = v1X * v1X + v1Y * v1Y; + var dot12 = v1X * v2X + v1Y * v2Y; + + var invDenom = 1.0 / (dot00 * dot11 - dot01 * dot01); + var u = (dot11 * dot02 - dot01 * dot12) * invDenom; + var v = (dot00 * dot12 - dot01 * dot02) * invDenom; + + if (exact) { + return u >= 0 && v >= 0 && u + v <= 1; + } + + return u.IsPositive() && v.IsPositive() && (u + v).IsSmallerOrEqual(1); + } + + public bool ContainsInCircumcircle(Point p) + { + var p0X = P1.X - p.X; + var p0Y = P1.Y - p.Y; + var p1X = P2.X - p.X; + var p1Y = P2.Y - p.Y; + var p2X = P3.X - p.X; + var p2Y = P3.Y - p.Y; + + var p0Square = p0X * p0X + p0Y * p0Y; + var p1Square = p1X * p1X + p1Y * p1Y; + var p2Square = p2X * p2X + p2Y * p2Y; + + var det01 = p0X * p1Y - p1X * p0Y; + var det12 = p1X * p2Y - p2X * p1Y; + var det20 = p2X * p0Y - p0X * p2Y; + + var result = p0Square * det12 + p1Square * det20 + p2Square * det01; + return result > 0; + } + + public bool Contains(Point p) + { + return p.Equals(P1) || p.Equals(P2) || p.Equals(P3); + } + + public bool SharesVertexWith(Triangle t) + { + return Contains(t.P1) || Contains(t.P2) || Contains(t.P3); + } + + public IEnumerable<Edge> GetEdges() + { + return new[] { new Edge(P1, P2), new Edge(P2, P3), new Edge(P3, P1) }; + } + + #region Equality members + + protected bool Equals(Triangle other) + { + return Equals(P1, other.P1) && Equals(P2, other.P2) && Equals(P3, other.P3); + } + + 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((Triangle)obj); + } + + public override int GetHashCode() + { + unchecked { + var hashCode = P1.GetHashCode(); + hashCode = (hashCode * 397) ^ P2.GetHashCode(); + hashCode = (hashCode * 397) ^ P3.GetHashCode(); + return hashCode; + } + } + + #endregion + } + + [DebuggerDisplay("Edge(({P1.X}, {P1.Y},{P1.Z}), ({P2.X}, {P2.Y},{P2.Z}))")] + public class Edge + { + public readonly Point P1; + public readonly Point P2; + + private Point _vector; + + public Edge(Point p1, Point p2) + { + P1 = p1; + P2 = p2; + } + + public Point Vector + { + get { return _vector ?? (_vector = P2 - P1); } + } + + public double SlopeXY + { + get { return Vector.Y / Vector.X; } + } + + public double OffsetXY + { + get { return P2.Y - SlopeXY * P2.X; } + } + + #region Equality members + + protected bool Equals(Edge other) + { + return (P1.Equals(other.P1) && Equals(P2, other.P2)) || (P1.Equals(other.P2) && P2.Equals(other.P1)); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) { + return false; + } + if (ReferenceEquals(this, obj)) { + return true; + } + return obj.GetType() == GetType() && Equals((Edge)obj); + } + + public override int GetHashCode() + { + return P1.GetHashCode() ^ P2.GetHashCode(); + } + + #endregion + + public static Edge Create(Point arg1, Point arg2) + { + return new Edge(arg1, arg2); + } + + public bool ContainsXY(Point point) + { + return (SlopeXY * point.X + (P1.Y - SlopeXY * P1.X) - point.Y).IsEqual(0, 1E-9); + } + } } \ No newline at end of file diff --git a/VectoConsole/Program.cs b/VectoConsole/Program.cs index 9e4d0f73b25843e4f22cecdf1fdf243531c1ba0e..0a23117745ed764a1d58c44c81354822c67c51ba 100644 --- a/VectoConsole/Program.cs +++ b/VectoConsole/Program.cs @@ -64,36 +64,36 @@ namespace VectoConsole private const string Usage = @"Usage: vectocmd.exe [-h] [-v] FILE1.vecto [FILE2.vecto ...]"; - private const string Help = @" -Commandline Interface for Vecto. - -Synopsis: - vectocmd.exe [-h] [-v] FILE1.(vecto|xml) [FILE2.(vecto|xml) ...] - -Description: - FILE1.vecto [FILE2.vecto ...]: A list of vecto-job files (with the - extension: .vecto). At least one file must be given. Delimited by - whitespace. - - -t: output information about execution times - -mod: write mod-data in addition to sum-data - -1Hz: convert mod-data to 1Hz resolution - -eng: switch to engineering mode (implies -mod) - -q: quiet - disables console output unless verbose information is enabled - -nv: skip validation of internal data structure before simulation - -v: Shows verbose information (errors and warnings will be displayed) - -vv: Shows more verbose information (infos will be displayed) - -vvv: Shows debug messages (slow!) - -vvvv: Shows all verbose information (everything, slow!) - -V: show version information - -h: Displays this help. - -Examples: - vecto.exe ""12t Delivery Truck.vecto"" 40t_Long_Haul_Truck.vecto - vecto.exe 24tCoach.vecto 40t_Long_Haul_Truck.vecto - vecto.exe -v 24tCoach.vecto - vecto.exe -v jobs\40t_Long_Haul_Truck.vecto - vecto.exe -h + private const string Help = @" +Commandline Interface for Vecto. + +Synopsis: + vectocmd.exe [-h] [-v] FILE1.(vecto|xml) [FILE2.(vecto|xml) ...] + +Description: + FILE1.vecto [FILE2.vecto ...]: A list of vecto-job files (with the + extension: .vecto). At least one file must be given. Delimited by + whitespace. + + -t: output information about execution times + -mod: write mod-data in addition to sum-data + -1Hz: convert mod-data to 1Hz resolution + -eng: switch to engineering mode (implies -mod) + -q: quiet - disables console output unless verbose information is enabled + -nv: skip validation of internal data structure before simulation + -v: Shows verbose information (errors and warnings will be displayed) + -vv: Shows more verbose information (infos will be displayed) + -vvv: Shows debug messages (slow!) + -vvvv: Shows all verbose information (everything, slow!) + -V: show version information + -h: Displays this help. + +Examples: + vecto.exe ""12t Delivery Truck.vecto"" 40t_Long_Haul_Truck.vecto + vecto.exe 24tCoach.vecto 40t_Long_Haul_Truck.vecto + vecto.exe -v 24tCoach.vecto + vecto.exe -v jobs\40t_Long_Haul_Truck.vecto + vecto.exe -h "; private static JobContainer _jobContainer; @@ -289,8 +289,9 @@ Examples: #if DEBUG Console.Error.WriteLine("done."); - if (!Console.IsInputRedirected) + if (!Console.IsInputRedirected) { Console.ReadKey(); + } #endif return Environment.ExitCode; } diff --git a/VectoCore/VectoCore/InputData/FileIO/JSON/JSONInputData.cs b/VectoCore/VectoCore/InputData/FileIO/JSON/JSONInputData.cs index b6b2f19eb55689a195e71e0aef3f912f0574eb1d..da1cacd25562735ec6d7ddd354728fa139f2c851 100644 --- a/VectoCore/VectoCore/InputData/FileIO/JSON/JSONInputData.cs +++ b/VectoCore/VectoCore/InputData/FileIO/JSON/JSONInputData.cs @@ -29,647 +29,673 @@ * Martin Rexeis, rexeis@ivt.tugraz.at, IVT, Graz University of Technology */ -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Xml.Linq; -using TUGraz.VectoCommon.Exceptions; -using TUGraz.VectoCommon.InputData; -using TUGraz.VectoCommon.Models; -using TUGraz.VectoCommon.Utils; -using TUGraz.VectoCore.Configuration; -using TUGraz.VectoCore.InputData.Impl; -using TUGraz.VectoCore.Models.Declaration; -using TUGraz.VectoCore.Models.SimulationComponent.Data; -using TUGraz.VectoCore.Utils; - -namespace TUGraz.VectoCore.InputData.FileIO.JSON -{ - public abstract class JSONFile : LoggingObject - { - public const string MissingFileSuffix = " -- (MISSING!)"; - - private readonly string _sourceFile; - - protected readonly JObject Body; - - protected JSONFile(JObject data, string filename, bool tolerateMissing = false) - { - //var header = (JObject)data.GetEx(JsonKeys.JsonHeader); - Body = (JObject)data.GetEx(JsonKeys.JsonBody); - _sourceFile = Path.GetFullPath(filename); - TolerateMissing = tolerateMissing; - } - - protected bool TolerateMissing { get; set; } - - public DataSourceType SourceType - { - get { return DataSourceType.JSONFile; } - } - - public string Source - { - get { return _sourceFile; } - } - - public bool SavedInDeclarationMode - { - get { return Body.GetEx(JsonKeys.SavedInDeclMode).Value<bool>(); } - } - - internal string BasePath - { - get { return Path.GetDirectoryName(_sourceFile); } - } - - protected TableData ReadTableData(string filename, string tableType, bool required = true) - { - if (!EmptyOrInvalidFileName(filename) && File.Exists(Path.Combine(BasePath, filename))) { - try { - return VectoCSVFile.Read(Path.Combine(BasePath, filename), true); - } catch (Exception e) { - Log.Warn("Failed to read file {0} {1}", Path.Combine(BasePath, filename), tableType); - throw new VectoException("Failed to read file for {0}: {1}", e, tableType, filename); - } - } - if (required) { - throw new VectoException("Invalid filename for {0}: {1}", tableType, filename); - } - return null; - } - - internal static bool EmptyOrInvalidFileName(string filename) - { - return filename == null || !filename.Any() || - filename.Equals("<NOFILE>", StringComparison.InvariantCultureIgnoreCase) - || filename.Equals("-"); - } - - public static JObject GetDummyJSONStructure() - { - return JObject.FromObject(new Dictionary<string, object>() { - { JsonKeys.JsonHeader, new object() }, - { JsonKeys.JsonBody, new object() } - }); - } - } - - /// <summary> - /// Class for reading json data of vecto-job-file. - /// Fileformat: .vecto - /// </summary> - public class JSONInputDataV2 : JSONFile, IEngineeringInputDataProvider, IDeclarationInputDataProvider, - IEngineeringJobInputData, IDriverEngineeringInputData, IAuxiliariesEngineeringInputData, - IAuxiliariesDeclarationInputData - { - protected readonly IGearboxEngineeringInputData Gearbox; - protected readonly IAxleGearInputData AxleGear; - protected readonly ITorqueConverterEngineeringInputData TorqueConverter; - protected readonly IAngledriveInputData Angledrive; - protected readonly IEngineEngineeringInputData Engine; - protected readonly IVehicleEngineeringInputData VehicleData; - protected readonly IRetarderInputData Retarder; - protected readonly IPTOTransmissionInputData PTOTransmission; - - private readonly string _jobname; - protected internal IAirdragEngineeringInputData AirdragData; - - public JSONInputDataV2(JObject data, string filename, bool tolerateMissing = false) - : base(data, filename, tolerateMissing) - { - _jobname = Path.GetFileNameWithoutExtension(filename); - - Engine = ReadEngine(); - - if (Body.GetEx(JsonKeys.Job_EngineOnlyMode).Value<bool>()) { - return; - } - - Gearbox = ReadGearbox(); - AxleGear = Gearbox as IAxleGearInputData; - TorqueConverter = Gearbox as ITorqueConverterEngineeringInputData; - - VehicleData = ReadVehicle(); - Angledrive = VehicleData as IAngledriveInputData; - Retarder = VehicleData as IRetarderInputData; - PTOTransmission = VehicleData as IPTOTransmissionInputData; - AirdragData = VehicleData as IAirdragEngineeringInputData; - } - - private IVehicleEngineeringInputData ReadVehicle() - { - try { - var vehicleFile = Body.GetEx(JsonKeys.Vehicle_VehicleFile).Value<string>(); - return JSONInputDataFactory.ReadJsonVehicle( - Path.Combine(BasePath, vehicleFile)); - } catch (Exception e) { - if (!TolerateMissing) { - throw new VectoException("JobFile: Failed to read Vehicle file '{0}': {1}", e, Body[JsonKeys.Vehicle_VehicleFile], - e.Message); - } - return new JSONVehicleDataV7(GetDummyJSONStructure(), - Path.Combine(BasePath, Body.GetEx(JsonKeys.Vehicle_VehicleFile).Value<string>()) + MissingFileSuffix); - } - } - - private IGearboxEngineeringInputData ReadGearbox() - { - try { - var gearboxFile = Body.GetEx(JsonKeys.Vehicle_GearboxFile).Value<string>(); - - return JSONInputDataFactory.ReadGearbox(Path.Combine(BasePath, gearboxFile)); - } catch (Exception e) { - if (!TolerateMissing) { - throw new VectoException("JobFile: Failed to read Gearbox file '{0}': {1}", e, Body[JsonKeys.Vehicle_GearboxFile], - e.Message); - } - return new JSONGearboxDataV6(GetDummyJSONStructure(), - Path.Combine(BasePath, Body.GetEx(JsonKeys.Vehicle_GearboxFile).Value<string>()) + MissingFileSuffix); - } - } - - private IEngineEngineeringInputData ReadEngine() - { - try { - return JSONInputDataFactory.ReadEngine( - Path.Combine(BasePath, Body.GetEx(JsonKeys.Vehicle_EngineFile).Value<string>())); - } catch (Exception e) { - if (!TolerateMissing) { - throw new VectoException("JobFile: Failed to read Engine file '{0}': {1}", e, Body[JsonKeys.Vehicle_EngineFile], - e.Message); - } - //JToken.FromObject(New Dictionary(Of String, Object) From {{"Header", header}, {"Body", body}}) - return - new JSONEngineDataV3(GetDummyJSONStructure(), - Path.Combine(BasePath, Body.GetEx(JsonKeys.Vehicle_EngineFile).Value<string>()) + MissingFileSuffix); - } - } - - #region IInputDataProvider - - public virtual IEngineeringJobInputData JobInputData() - { - return this; - } - - IVehicleDeclarationInputData IDeclarationInputDataProvider.VehicleInputData - { - get { return VehicleInputData; } - } - - IAirdragDeclarationInputData IDeclarationInputDataProvider.AirdragInputData - { - get { return AirdragInputData; } - } - - public IAirdragEngineeringInputData AirdragInputData - { - get { return AirdragData; } - } - - IGearboxDeclarationInputData IDeclarationInputDataProvider.GearboxInputData - { - get { return GearboxInputData; } - } - - ITorqueConverterDeclarationInputData IDeclarationInputDataProvider.TorqueConverterInputData - { - get { return TorqueConverterInputData; } - } - - public ITorqueConverterEngineeringInputData TorqueConverterInputData - { - get { - if (TorqueConverter == null) { - throw new InvalidFileFormatException("TorqueConverterData not found"); - } - return TorqueConverter; - } - } - - IDeclarationJobInputData IDeclarationInputDataProvider.JobInputData() - { - return JobInputData(); - } - - public virtual IVehicleEngineeringInputData VehicleInputData - { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", - "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] - get { - if (VehicleData == null) { - throw new InvalidFileFormatException("VehicleData not found "); - } - return VehicleData; - } - } - - public virtual IGearboxEngineeringInputData GearboxInputData - { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", - "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] - get { - if (Gearbox == null) { - throw new InvalidFileFormatException("GearboxData not found"); - } - return Gearbox; - } - } - - public virtual IAxleGearInputData AxleGearInputData - { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", - "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] - get { - if (AxleGear == null) { - throw new InvalidFileFormatException("AxleGearData not found"); - } - return AxleGear; - } - } - - public IAngledriveInputData AngledriveInputData - { - get { return Angledrive; } - } - - IEngineDeclarationInputData IDeclarationInputDataProvider.EngineInputData - { - get { return EngineInputData; } - } - - public virtual IEngineEngineeringInputData EngineInputData - { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", - "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] - get { - if (Engine == null) { - throw new InvalidFileFormatException("EngineData not found"); - } - return Engine; - } - } - - public virtual IAuxiliariesEngineeringInputData AuxiliaryInputData() - { - return this; - } - - IDriverEngineeringInputData IEngineeringInputDataProvider.DriverInputData - { - get { return this; } - } - - public IPTOTransmissionInputData PTOTransmissionInputData - { - get { return PTOTransmission; } - } - - public XElement XMLHash - { - get { return null; } - } - - IAuxiliariesDeclarationInputData IDeclarationInputDataProvider.AuxiliaryInputData() - { - return this; - } - - public virtual IRetarderInputData RetarderInputData - { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", - "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] - get { - if (Retarder == null) { - throw new InvalidFileFormatException("RetarderData not found"); - } - return Retarder; - } - } - - public virtual IDriverDeclarationInputData DriverInputData - { - get { return this; } - } - - #endregion - - #region IJobInputData - - public virtual IVehicleEngineeringInputData Vehicle - { - get { return VehicleData; } - } - - public virtual IList<ICycleData> Cycles - { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", - "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] - get { - var retVal = new List<ICycleData>(); - if (Body[JsonKeys.Job_Cycles] == null) { - return retVal; - } - foreach (var cycle in Body.GetEx(JsonKeys.Job_Cycles)) { - //.Select(cycle => - var cycleFile = Path.Combine(BasePath, cycle.Value<string>()); - TableData cycleData; - if (File.Exists(cycleFile)) { - cycleData = VectoCSVFile.Read(cycleFile); - } else { - try { - var resourceName = DeclarationData.DeclarationDataResourcePrefix + ".MissionCycles." + - cycle.Value<string>() + Constants.FileExtensions.CycleFile; - cycleData = VectoCSVFile.ReadStream(RessourceHelper.ReadStream(resourceName), source: resourceName); - } catch (Exception e) { - Log.Debug("Driving Cycle could not be read: " + cycleFile); - if (!TolerateMissing) { - throw new VectoException("Driving Cycle could not be read: " + cycleFile, e); - } - cycleData = new TableData(cycleFile + MissingFileSuffix, DataSourceType.Missing); - } - } - retVal.Add(new CycleInputData() { - Name = Path.GetFileNameWithoutExtension(cycle.Value<string>()), - CycleData = cycleData - }); - } - return retVal; - } - } - - public virtual bool EngineOnlyMode - { - get { return Body.GetEx(JsonKeys.Job_EngineOnlyMode).Value<bool>(); } - } - - IVehicleDeclarationInputData IDeclarationJobInputData.Vehicle - { - get { return Vehicle; } - } - - public virtual string JobName - { - get { return _jobname; } - } - - #endregion - - #region DriverInputData - - IOverSpeedEcoRollDeclarationInputData IDriverDeclarationInputData.OverSpeedEcoRoll - { - get { - var overspeed = Body.GetEx(JsonKeys.DriverData_OverspeedEcoRoll); - return new OverSpeedEcoRollInputData() { - Mode = DriverData.ParseDriverMode(overspeed.GetEx<string>(JsonKeys.DriverData_OverspeedEcoRoll_Mode)) - }; - } - } - - public virtual ILookaheadCoastingInputData Lookahead - { - get { - if (Body[JsonKeys.DriverData_LookaheadCoasting] == null) { - return null; - } - - var lac = Body.GetEx(JsonKeys.DriverData_LookaheadCoasting); - var distanceScalingFactor = lac["PreviewDistanceFactor"] != null - ? lac.GetEx<double>("PreviewDistanceFactor") - : DeclarationData.Driver.LookAhead.LookAheadDistanceFactor; - var lacDfOffset = lac["DF_offset"] != null - ? lac.GetEx<double>("DF_offset") - : DeclarationData.Driver.LookAhead.DecisionFactorCoastingOffset; - var lacDfScaling = lac["DF_scaling"] != null - ? lac.GetEx<double>("DF_scaling") - : DeclarationData.Driver.LookAhead.DecisionFactorCoastingScaling; - TableData speedDependentLookup = null; - if (lac["DF_targetSpeedLookup"] != null && !string.IsNullOrWhiteSpace(lac["DF_targetSpeedLookup"].Value<string>())) { - try { - speedDependentLookup = ReadTableData(lac.GetEx<string>("DF_targetSpeedLookup"), - "Lookahead Coasting Decisionfactor - Target speed"); - } catch (Exception) { - if (TolerateMissing) { - speedDependentLookup = - new TableData(Path.Combine(BasePath, lac["DF_targetSpeedLookup"].Value<string>()) + MissingFileSuffix, - DataSourceType.Missing); - } - } - } - TableData velocityDropLookup = null; - if (lac["Df_velocityDropLookup"] != null && !string.IsNullOrWhiteSpace(lac["Df_velocityDropLookup"].Value<string>())) { - try { - velocityDropLookup = ReadTableData(lac.GetEx<string>("Df_velocityDropLookup"), - "Lookahead Coasting Decisionfactor - Velocity drop"); - } catch (Exception) { - if (TolerateMissing) { - velocityDropLookup = - new TableData(Path.Combine(BasePath, lac["Df_velocityDropLookup"].Value<string>()) + MissingFileSuffix, - DataSourceType.Missing); - } - } - } - var minSpeed = lac["MinSpeed"] != null - ? lac.GetEx<double>(JsonKeys.DriverData_Lookahead_MinSpeed).KMPHtoMeterPerSecond() - : DeclarationData.Driver.LookAhead.MinimumSpeed; - return new LookAheadCoastingInputData() { - Enabled = lac.GetEx<bool>(JsonKeys.DriverData_Lookahead_Enabled), - //Deceleration = lac.GetEx<double>(JsonKeys.DriverData_Lookahead_Deceleration).SI<MeterPerSquareSecond>(), - MinSpeed = minSpeed, - LookaheadDistanceFactor = distanceScalingFactor, - CoastingDecisionFactorOffset = lacDfOffset, - CoastingDecisionFactorScaling = lacDfScaling, - CoastingDecisionFactorTargetSpeedLookup = speedDependentLookup, - CoastingDecisionFactorVelocityDropLookup = velocityDropLookup - }; - } - } - - public virtual IOverSpeedEcoRollEngineeringInputData OverSpeedEcoRoll - { - get { - var overspeed = Body.GetEx(JsonKeys.DriverData_OverspeedEcoRoll); - return new OverSpeedEcoRollInputData() { - Mode = DriverData.ParseDriverMode(overspeed.GetEx<string>(JsonKeys.DriverData_OverspeedEcoRoll_Mode)), - MinSpeed = overspeed.GetEx<double>(JsonKeys.DriverData_OverspeedEcoRoll_MinSpeed).KMPHtoMeterPerSecond(), - OverSpeed = overspeed.GetEx<double>(JsonKeys.DriverData_OverspeedEcoRoll_OverSpeed).KMPHtoMeterPerSecond(), - UnderSpeed = - overspeed.GetEx<double>(JsonKeys.DriverData_OverspeedEcoRoll_UnderSpeed).KMPHtoMeterPerSecond() - }; - } - } - - public virtual TableData AccelerationCurve - { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", - "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] - get { - var acceleration = Body[JsonKeys.DriverData_AccelerationCurve]; - if (acceleration == null || EmptyOrInvalidFileName(acceleration.Value<string>())) { - return null; - // throw new VectoException("AccelerationCurve (VACC) required"); - } - try { - return ReadTableData(acceleration.Value<string>(), "DriverAccelerationCurve"); - } catch (VectoException e) { - Log.Warn("Could not find file for acceleration curve. Trying lookup in declaration data."); - try { - var resourceName = DeclarationData.DeclarationDataResourcePrefix + ".VACC." + - acceleration.Value<string>() + - Constants.FileExtensions.DriverAccelerationCurve; - return VectoCSVFile.ReadStream(RessourceHelper.ReadStream(resourceName), source: resourceName); - } catch (Exception) { - if (!TolerateMissing) { - throw new VectoException("Failed to read Driver Acceleration Curve: " + e.Message, e); - } - return new TableData(Path.Combine(BasePath, acceleration.Value<string>()) + MissingFileSuffix, - DataSourceType.Missing); - } - } - } - } - - #endregion - - #region IAuxiliariesEngineeringInputData - - IList<IAuxiliaryEngineeringInputData> IAuxiliariesEngineeringInputData.Auxiliaries - { - get { return AuxData().Cast<IAuxiliaryEngineeringInputData>().ToList(); } - } - - IList<IAuxiliaryDeclarationInputData> IAuxiliariesDeclarationInputData.Auxiliaries - { - get { return AuxData().Cast<IAuxiliaryDeclarationInputData>().ToList(); } - } - - protected virtual IList<AuxiliaryDataInputData> AuxData() - { - var retVal = new List<AuxiliaryDataInputData>(); - foreach (var aux in Body["Aux"] ?? Enumerable.Empty<JToken>()) { - var type = AuxiliaryTypeHelper.Parse(aux.GetEx<string>("Type")); - - var auxData = new AuxiliaryDataInputData { - ID = aux.GetEx<string>("ID"), - Type = type, - Technology = new List<string>(), - }; - var tech = aux.GetEx<string>("Technology"); - - if (auxData.Type == AuxiliaryType.ElectricSystem) { - if (aux["TechList"] == null || aux["TechList"].Any()) { - auxData.Technology.Add("Standard technology"); - } else { - auxData.Technology.Add("Standard technology - LED headlights, all"); - } - } - - if (auxData.Type == AuxiliaryType.SteeringPump) { - auxData.Technology.Add(tech); - } - - if (auxData.Type == AuxiliaryType.Fan) { - switch (tech) { - case "Crankshaft mounted - Electronically controlled visco clutch (Default)": - auxData.Technology.Add("Crankshaft mounted - Electronically controlled visco clutch"); - break; - case "Crankshaft mounted - On/Off clutch": - auxData.Technology.Add("Crankshaft mounted - On/off clutch"); - break; - case "Belt driven or driven via transm. - On/Off clutch": - auxData.Technology.Add("Belt driven or driven via transm. - On/off clutch"); - break; - default: - auxData.Technology.Add(tech); - break; - } - } - - var auxFile = aux["Path"]; - retVal.Add(auxData); - - if (auxFile == null || EmptyOrInvalidFileName(auxFile.Value<string>())) { - continue; - } - - AuxiliaryFileHelper.FillAuxiliaryDataInputData(auxData, Path.Combine(BasePath, auxFile.Value<string>())); - } - return retVal; - } - - #endregion - - #region AdvancedAuxiliaries - - public AuxiliaryModel AuxiliaryAssembly - { - get { - return AuxiliaryModelHelper.Parse(Body["AuxiliaryAssembly"] == null ? "" : Body["AuxiliaryAssembly"].ToString()); - } - } - - public string AuxiliaryVersion - { - get { return Body["AuxiliaryVersion"] != null ? Body["AuxiliaryVersion"].Value<string>() : "<CLASSIC>"; } - } - - public string AdvancedAuxiliaryFilePath - { - get { - return Body["AdvancedAuxiliaryFilePath"] != null - ? Path.Combine(Path.GetFullPath(BasePath), Body["AdvancedAuxiliaryFilePath"].Value<string>()) - : ""; - } - } - - #endregion - } - - public class JSONInputDataV3 : JSONInputDataV2 - { - public JSONInputDataV3(JObject data, string filename, bool tolerateMissing = false) - : base(data, filename, tolerateMissing) {} - - protected override IList<AuxiliaryDataInputData> AuxData() - { - var retVal = new List<AuxiliaryDataInputData>(); - if (Body["Padd"] != null) { - retVal.Add(new AuxiliaryDataInputData() { - ID = "ConstantAux", - AuxiliaryType = AuxiliaryDemandType.Constant, - ConstantPowerDemand = Body.GetEx<double>("Padd").SI<Watt>() - }); - } - foreach (var aux in Body["Aux"] ?? Enumerable.Empty<JToken>()) { - try { - aux.GetEx("Technology").ToObject<List<string>>(); - } catch (Exception) { - throw new VectoException( - "Aux: Technology for aux '{0}' list could not be read. Maybe it is a single string instead of a list of strings?", - aux.GetEx<string>("ID")); - } - - var type = AuxiliaryTypeHelper.Parse(aux.GetEx<string>("Type")); - - var auxData = new AuxiliaryDataInputData { - ID = aux.GetEx<string>("ID"), - Type = type, - Technology = aux.GetEx("Technology").ToObject<List<string>>() - }; - - var auxFile = aux["Path"]; - retVal.Add(auxData); - - if (auxFile == null || EmptyOrInvalidFileName(auxFile.Value<string>())) { - continue; - } - AuxiliaryFileHelper.FillAuxiliaryDataInputData(auxData, Path.Combine(BasePath, auxFile.Value<string>())); - } - return retVal; - } - } +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using TUGraz.VectoCommon.Exceptions; +using TUGraz.VectoCommon.InputData; +using TUGraz.VectoCommon.Models; +using TUGraz.VectoCommon.Utils; +using TUGraz.VectoCore.Configuration; +using TUGraz.VectoCore.InputData.Impl; +using TUGraz.VectoCore.Models.Declaration; +using TUGraz.VectoCore.Models.SimulationComponent.Data; +using TUGraz.VectoCore.Utils; + +namespace TUGraz.VectoCore.InputData.FileIO.JSON +{ + public abstract class JSONFile : LoggingObject + { + public const string MissingFileSuffix = " -- (MISSING!)"; + + private readonly string _sourceFile; + + protected readonly JObject Body; + + protected JSONFile(JObject data, string filename, bool tolerateMissing = false) + { + //var header = (JObject)data.GetEx(JsonKeys.JsonHeader); + Body = (JObject)data.GetEx(JsonKeys.JsonBody); + _sourceFile = Path.GetFullPath(filename); + TolerateMissing = tolerateMissing; + } + + protected bool TolerateMissing { get; set; } + + public DataSourceType SourceType + { + get { return DataSourceType.JSONFile; } + } + + public string Source + { + get { return _sourceFile; } + } + + public bool SavedInDeclarationMode + { + get { return Body.GetEx(JsonKeys.SavedInDeclMode).Value<bool>(); } + } + + internal string BasePath + { + get { return Path.GetDirectoryName(_sourceFile); } + } + + protected TableData ReadTableData(string filename, string tableType, bool required = true) + { + if (!EmptyOrInvalidFileName(filename) && File.Exists(Path.Combine(BasePath, filename))) { + try { + return VectoCSVFile.Read(Path.Combine(BasePath, filename), true); + } catch (Exception e) { + Log.Warn("Failed to read file {0} {1}", Path.Combine(BasePath, filename), tableType); + throw new VectoException("Failed to read file for {0}: {1}", e, tableType, filename); + } + } + if (required) { + throw new VectoException("Invalid filename for {0}: {1}", tableType, filename); + } + return null; + } + + internal static bool EmptyOrInvalidFileName(string filename) + { + return filename == null || !filename.Any() || + filename.Equals("<NOFILE>", StringComparison.InvariantCultureIgnoreCase) + || filename.Equals("-"); + } + + public static JObject GetDummyJSONStructure() + { + return JObject.FromObject(new Dictionary<string, object>() { + { JsonKeys.JsonHeader, new object() }, + { JsonKeys.JsonBody, new object() } + }); + } + } + + /// <summary> + /// Class for reading json data of vecto-job-file. + /// Fileformat: .vecto + /// </summary> + public class JSONInputDataV2 : JSONFile, IEngineeringInputDataProvider, IDeclarationInputDataProvider, + IEngineeringJobInputData, IDriverEngineeringInputData, IAuxiliariesEngineeringInputData, + IAuxiliariesDeclarationInputData + { + protected readonly IGearboxEngineeringInputData Gearbox; + protected readonly IAxleGearInputData AxleGear; + protected readonly ITorqueConverterEngineeringInputData TorqueConverter; + protected readonly IAngledriveInputData Angledrive; + protected readonly IEngineEngineeringInputData Engine; + protected readonly IVehicleEngineeringInputData VehicleData; + protected readonly IRetarderInputData Retarder; + protected readonly IPTOTransmissionInputData PTOTransmission; + + private readonly string _jobname; + protected internal IAirdragEngineeringInputData AirdragData; + + public JSONInputDataV2(JObject data, string filename, bool tolerateMissing = false) + : base(data, filename, tolerateMissing) + { + _jobname = Path.GetFileNameWithoutExtension(filename); + + Engine = ReadEngine(); + + if (Body.GetEx(JsonKeys.Job_EngineOnlyMode).Value<bool>()) { + return; + } + + Gearbox = ReadGearbox(); + AxleGear = Gearbox as IAxleGearInputData; + TorqueConverter = Gearbox as ITorqueConverterEngineeringInputData; + + VehicleData = ReadVehicle(); + Angledrive = VehicleData as IAngledriveInputData; + Retarder = VehicleData as IRetarderInputData; + PTOTransmission = VehicleData as IPTOTransmissionInputData; + AirdragData = VehicleData as IAirdragEngineeringInputData; + } + + private IVehicleEngineeringInputData ReadVehicle() + { + try { + var vehicleFile = Body.GetEx(JsonKeys.Vehicle_VehicleFile).Value<string>(); + return JSONInputDataFactory.ReadJsonVehicle( + Path.Combine(BasePath, vehicleFile)); + } catch (Exception e) { + if (!TolerateMissing) { + throw new VectoException("JobFile: Failed to read Vehicle file '{0}': {1}", e, Body[JsonKeys.Vehicle_VehicleFile], + e.Message); + } + return new JSONVehicleDataV7(GetDummyJSONStructure(), + Path.Combine(BasePath, Body.GetEx(JsonKeys.Vehicle_VehicleFile).Value<string>()) + MissingFileSuffix); + } + } + + private IGearboxEngineeringInputData ReadGearbox() + { + try { + var gearboxFile = Body.GetEx(JsonKeys.Vehicle_GearboxFile).Value<string>(); + + return JSONInputDataFactory.ReadGearbox(Path.Combine(BasePath, gearboxFile)); + } catch (Exception e) { + if (!TolerateMissing) { + throw new VectoException("JobFile: Failed to read Gearbox file '{0}': {1}", e, Body[JsonKeys.Vehicle_GearboxFile], + e.Message); + } + return new JSONGearboxDataV6(GetDummyJSONStructure(), + Path.Combine(BasePath, Body.GetEx(JsonKeys.Vehicle_GearboxFile).Value<string>()) + MissingFileSuffix); + } + } + + private IEngineEngineeringInputData ReadEngine() + { + try { + return JSONInputDataFactory.ReadEngine( + Path.Combine(BasePath, Body.GetEx(JsonKeys.Vehicle_EngineFile).Value<string>())); + } catch (Exception e) { + if (!TolerateMissing) { + throw new VectoException("JobFile: Failed to read Engine file '{0}': {1}", e, Body[JsonKeys.Vehicle_EngineFile], + e.Message); + } + //JToken.FromObject(New Dictionary(Of String, Object) From {{"Header", header}, {"Body", body}}) + return + new JSONEngineDataV3(GetDummyJSONStructure(), + Path.Combine(BasePath, Body.GetEx(JsonKeys.Vehicle_EngineFile).Value<string>()) + MissingFileSuffix); + } + } + + #region IInputDataProvider + + public virtual IEngineeringJobInputData JobInputData() + { + return this; + } + + IVehicleDeclarationInputData IDeclarationInputDataProvider.VehicleInputData + { + get { return VehicleInputData; } + } + + IAirdragDeclarationInputData IDeclarationInputDataProvider.AirdragInputData + { + get { return AirdragInputData; } + } + + public IAirdragEngineeringInputData AirdragInputData + { + get { return AirdragData; } + } + + IGearboxDeclarationInputData IDeclarationInputDataProvider.GearboxInputData + { + get { return GearboxInputData; } + } + + ITorqueConverterDeclarationInputData IDeclarationInputDataProvider.TorqueConverterInputData + { + get { return TorqueConverterInputData; } + } + + public ITorqueConverterEngineeringInputData TorqueConverterInputData + { + get + { + if (TorqueConverter == null) { + throw new InvalidFileFormatException("TorqueConverterData not found"); + } + return TorqueConverter; + } + } + + IDeclarationJobInputData IDeclarationInputDataProvider.JobInputData() + { + return JobInputData(); + } + + public virtual IVehicleEngineeringInputData VehicleInputData + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", + "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] + get + { + if (VehicleData == null) { + throw new InvalidFileFormatException("VehicleData not found "); + } + return VehicleData; + } + } + + public virtual IGearboxEngineeringInputData GearboxInputData + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", + "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] + get + { + if (Gearbox == null) { + throw new InvalidFileFormatException("GearboxData not found"); + } + return Gearbox; + } + } + + public virtual IAxleGearInputData AxleGearInputData + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", + "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] + get + { + if (AxleGear == null) { + throw new InvalidFileFormatException("AxleGearData not found"); + } + return AxleGear; + } + } + + public IAngledriveInputData AngledriveInputData + { + get { return Angledrive; } + } + + IEngineDeclarationInputData IDeclarationInputDataProvider.EngineInputData + { + get { return EngineInputData; } + } + + public virtual IEngineEngineeringInputData EngineInputData + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", + "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] + get + { + if (Engine == null) { + throw new InvalidFileFormatException("EngineData not found"); + } + return Engine; + } + } + + public virtual IAuxiliariesEngineeringInputData AuxiliaryInputData() + { + return this; + } + + IDriverEngineeringInputData IEngineeringInputDataProvider.DriverInputData + { + get { return this; } + } + + public IPTOTransmissionInputData PTOTransmissionInputData + { + get { return PTOTransmission; } + } + + public XElement XMLHash + { + get { return null; } + } + + IAuxiliariesDeclarationInputData IDeclarationInputDataProvider.AuxiliaryInputData() + { + return this; + } + + public virtual IRetarderInputData RetarderInputData + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", + "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] + get + { + if (Retarder == null) { + throw new InvalidFileFormatException("RetarderData not found"); + } + return Retarder; + } + } + + public virtual IDriverDeclarationInputData DriverInputData + { + get { return this; } + } + + #endregion + + #region IJobInputData + + public virtual IVehicleEngineeringInputData Vehicle + { + get { return VehicleData; } + } + + public virtual IList<ICycleData> Cycles + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", + "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] + get + { + var retVal = new List<ICycleData>(); + if (Body[JsonKeys.Job_Cycles] == null) { + return retVal; + } + foreach (var cycle in Body.GetEx(JsonKeys.Job_Cycles)) { + //.Select(cycle => + var cycleFile = Path.Combine(BasePath, cycle.Value<string>()); + TableData cycleData; + if (File.Exists(cycleFile)) { + cycleData = VectoCSVFile.Read(cycleFile); + } else { + try { + var resourceName = DeclarationData.DeclarationDataResourcePrefix + ".MissionCycles." + + cycle.Value<string>() + Constants.FileExtensions.CycleFile; + cycleData = VectoCSVFile.ReadStream(RessourceHelper.ReadStream(resourceName), source: resourceName); + } catch (Exception e) { + Log.Debug("Driving Cycle could not be read: " + cycleFile); + if (!TolerateMissing) { + throw new VectoException("Driving Cycle could not be read: " + cycleFile, e); + } + cycleData = new TableData(cycleFile + MissingFileSuffix, DataSourceType.Missing); + } + } + retVal.Add(new CycleInputData() { + Name = Path.GetFileNameWithoutExtension(cycle.Value<string>()), + CycleData = cycleData + }); + } + return retVal; + } + } + + public virtual bool EngineOnlyMode + { + get { return Body.GetEx(JsonKeys.Job_EngineOnlyMode).Value<bool>(); } + } + + IVehicleDeclarationInputData IDeclarationJobInputData.Vehicle + { + get { return Vehicle; } + } + + public virtual string JobName + { + get { return _jobname; } + } + + #endregion + + #region DriverInputData + + IOverSpeedEcoRollDeclarationInputData IDriverDeclarationInputData.OverSpeedEcoRoll + { + get + { + var overspeed = Body.GetEx(JsonKeys.DriverData_OverspeedEcoRoll); + return new OverSpeedEcoRollInputData() { + Mode = DriverData.ParseDriverMode(overspeed.GetEx<string>(JsonKeys.DriverData_OverspeedEcoRoll_Mode)) + }; + } + } + + public virtual ILookaheadCoastingInputData Lookahead + { + get + { + if (Body[JsonKeys.DriverData_LookaheadCoasting] == null) { + return null; + } + + var lac = Body.GetEx(JsonKeys.DriverData_LookaheadCoasting); + var distanceScalingFactor = lac["PreviewDistanceFactor"] != null + ? lac.GetEx<double>("PreviewDistanceFactor") + : DeclarationData.Driver.LookAhead.LookAheadDistanceFactor; + var lacDfOffset = lac["DF_offset"] != null + ? lac.GetEx<double>("DF_offset") + : DeclarationData.Driver.LookAhead.DecisionFactorCoastingOffset; + var lacDfScaling = lac["DF_scaling"] != null + ? lac.GetEx<double>("DF_scaling") + : DeclarationData.Driver.LookAhead.DecisionFactorCoastingScaling; + var speedDependentLookup = GetSpeedDependentLookupTable(lac); + var velocityDropLookup = GetVelocityDropLookupTable(lac); + var minSpeed = lac["MinSpeed"] != null + ? lac.GetEx<double>(JsonKeys.DriverData_Lookahead_MinSpeed).KMPHtoMeterPerSecond() + : DeclarationData.Driver.LookAhead.MinimumSpeed; + return new LookAheadCoastingInputData() { + Enabled = lac.GetEx<bool>(JsonKeys.DriverData_Lookahead_Enabled), + //Deceleration = lac.GetEx<double>(JsonKeys.DriverData_Lookahead_Deceleration).SI<MeterPerSquareSecond>(), + MinSpeed = minSpeed, + LookaheadDistanceFactor = distanceScalingFactor, + CoastingDecisionFactorOffset = lacDfOffset, + CoastingDecisionFactorScaling = lacDfScaling, + CoastingDecisionFactorTargetSpeedLookup = speedDependentLookup, + CoastingDecisionFactorVelocityDropLookup = velocityDropLookup + }; + } + } + + private TableData GetVelocityDropLookupTable(JToken lac) + { + if (lac["Df_velocityDropLookup"] == null || string.IsNullOrWhiteSpace(lac["Df_velocityDropLookup"].Value<string>())) { + return null; + } + try { + return ReadTableData(lac.GetEx<string>("Df_velocityDropLookup"), + "Lookahead Coasting Decisionfactor - Velocity drop"); + } catch (Exception) { + if (TolerateMissing) { + return + new TableData(Path.Combine(BasePath, lac["Df_velocityDropLookup"].Value<string>()) + MissingFileSuffix, + DataSourceType.Missing); + } + } + return null; + } + + private TableData GetSpeedDependentLookupTable(JToken lac) + { + if (lac["DF_targetSpeedLookup"] == null || string.IsNullOrWhiteSpace(lac["DF_targetSpeedLookup"].Value<string>())) { + return null; + } + try { + return ReadTableData(lac.GetEx<string>("DF_targetSpeedLookup"), + "Lookahead Coasting Decisionfactor - Target speed"); + } catch (Exception) { + if (TolerateMissing) { + return + new TableData(Path.Combine(BasePath, lac["DF_targetSpeedLookup"].Value<string>()) + MissingFileSuffix, + DataSourceType.Missing); + } + } + return null; + } + + public virtual IOverSpeedEcoRollEngineeringInputData OverSpeedEcoRoll + { + get + { + var overspeed = Body.GetEx(JsonKeys.DriverData_OverspeedEcoRoll); + return new OverSpeedEcoRollInputData() { + Mode = DriverData.ParseDriverMode(overspeed.GetEx<string>(JsonKeys.DriverData_OverspeedEcoRoll_Mode)), + MinSpeed = overspeed.GetEx<double>(JsonKeys.DriverData_OverspeedEcoRoll_MinSpeed).KMPHtoMeterPerSecond(), + OverSpeed = overspeed.GetEx<double>(JsonKeys.DriverData_OverspeedEcoRoll_OverSpeed).KMPHtoMeterPerSecond(), + UnderSpeed = + overspeed.GetEx<double>(JsonKeys.DriverData_OverspeedEcoRoll_UnderSpeed).KMPHtoMeterPerSecond() + }; + } + } + + public virtual TableData AccelerationCurve + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", + "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] + get + { + var acceleration = Body[JsonKeys.DriverData_AccelerationCurve]; + if (acceleration == null || EmptyOrInvalidFileName(acceleration.Value<string>())) { + return null; + // throw new VectoException("AccelerationCurve (VACC) required"); + } + try { + return ReadTableData(acceleration.Value<string>(), "DriverAccelerationCurve"); + } catch (VectoException e) { + Log.Warn("Could not find file for acceleration curve. Trying lookup in declaration data."); + try { + var resourceName = DeclarationData.DeclarationDataResourcePrefix + ".VACC." + + acceleration.Value<string>() + + Constants.FileExtensions.DriverAccelerationCurve; + return VectoCSVFile.ReadStream(RessourceHelper.ReadStream(resourceName), source: resourceName); + } catch (Exception) { + if (!TolerateMissing) { + throw new VectoException("Failed to read Driver Acceleration Curve: " + e.Message, e); + } + return new TableData(Path.Combine(BasePath, acceleration.Value<string>()) + MissingFileSuffix, + DataSourceType.Missing); + } + } + } + } + + #endregion + + #region IAuxiliariesEngineeringInputData + + IList<IAuxiliaryEngineeringInputData> IAuxiliariesEngineeringInputData.Auxiliaries + { + get { return AuxData().Cast<IAuxiliaryEngineeringInputData>().ToList(); } + } + + IList<IAuxiliaryDeclarationInputData> IAuxiliariesDeclarationInputData.Auxiliaries + { + get { return AuxData().Cast<IAuxiliaryDeclarationInputData>().ToList(); } + } + + protected virtual IList<AuxiliaryDataInputData> AuxData() + { + var retVal = new List<AuxiliaryDataInputData>(); + foreach (var aux in Body["Aux"] ?? Enumerable.Empty<JToken>()) { + var type = AuxiliaryTypeHelper.Parse(aux.GetEx<string>("Type")); + + var auxData = new AuxiliaryDataInputData { + ID = aux.GetEx<string>("ID"), + Type = type, + Technology = new List<string>(), + }; + var tech = aux.GetEx<string>("Technology"); + + if (auxData.Type == AuxiliaryType.ElectricSystem) { + if (aux["TechList"] == null || aux["TechList"].Any()) { + auxData.Technology.Add("Standard technology"); + } else { + auxData.Technology.Add("Standard technology - LED headlights, all"); + } + } + + if (auxData.Type == AuxiliaryType.SteeringPump) { + auxData.Technology.Add(tech); + } + + if (auxData.Type == AuxiliaryType.Fan) { + DeclarationData.Fan.GetTechnologies(); + switch (tech) { + case "Crankshaft mounted - Electronically controlled visco clutch (Default)": + auxData.Technology.Add("Crankshaft mounted - Electronically controlled visco clutch"); + break; + case "Crankshaft mounted - On/Off clutch": + auxData.Technology.Add("Crankshaft mounted - On/off clutch"); + break; + case "Belt driven or driven via transm. - On/Off clutch": + auxData.Technology.Add("Belt driven or driven via transm. - On/off clutch"); + break; + default: + auxData.Technology.Add(tech); + break; + } + } + + var auxFile = aux["Path"]; + retVal.Add(auxData); + + if (auxFile == null || EmptyOrInvalidFileName(auxFile.Value<string>())) { + continue; + } + + AuxiliaryFileHelper.FillAuxiliaryDataInputData(auxData, Path.Combine(BasePath, auxFile.Value<string>())); + } + return retVal; + } + + #endregion + + #region AdvancedAuxiliaries + + public AuxiliaryModel AuxiliaryAssembly + { + get + { + return AuxiliaryModelHelper.Parse(Body["AuxiliaryAssembly"] == null ? "" : Body["AuxiliaryAssembly"].ToString()); + } + } + + public string AuxiliaryVersion + { + get { return Body["AuxiliaryVersion"] != null ? Body["AuxiliaryVersion"].Value<string>() : "<CLASSIC>"; } + } + + public string AdvancedAuxiliaryFilePath + { + get + { + return Body["AdvancedAuxiliaryFilePath"] != null + ? Path.Combine(Path.GetFullPath(BasePath), Body["AdvancedAuxiliaryFilePath"].Value<string>()) + : ""; + } + } + + #endregion + } + + public class JSONInputDataV3 : JSONInputDataV2 + { + public JSONInputDataV3(JObject data, string filename, bool tolerateMissing = false) + : base(data, filename, tolerateMissing) {} + + protected override IList<AuxiliaryDataInputData> AuxData() + { + var retVal = new List<AuxiliaryDataInputData>(); + if (Body["Padd"] != null) { + retVal.Add(new AuxiliaryDataInputData() { + ID = "ConstantAux", + AuxiliaryType = AuxiliaryDemandType.Constant, + ConstantPowerDemand = Body.GetEx<double>("Padd").SI<Watt>() + }); + } + foreach (var aux in Body["Aux"] ?? Enumerable.Empty<JToken>()) { + try { + aux.GetEx("Technology").ToObject<List<string>>(); + } catch (Exception) { + throw new VectoException( + "Aux: Technology for aux '{0}' list could not be read. Maybe it is a single string instead of a list of strings?", + aux.GetEx<string>("ID")); + } + + var type = AuxiliaryTypeHelper.Parse(aux.GetEx<string>("Type")); + + var auxData = new AuxiliaryDataInputData { + ID = aux.GetEx<string>("ID"), + Type = type, + Technology = aux.GetEx("Technology").ToObject<List<string>>() + }; + + var auxFile = aux["Path"]; + retVal.Add(auxData); + + if (auxFile == null || EmptyOrInvalidFileName(auxFile.Value<string>())) { + continue; + } + AuxiliaryFileHelper.FillAuxiliaryDataInputData(auxData, Path.Combine(BasePath, auxFile.Value<string>())); + } + return retVal; + } + } } \ No newline at end of file diff --git a/VectoCore/VectoCore/Models/SimulationComponent/Impl/AMTShiftStrategy.cs b/VectoCore/VectoCore/Models/SimulationComponent/Impl/AMTShiftStrategy.cs index 1aa3d8a4ad9e4af5af8a753c9b2cf7514a13cc6b..137d9a7e9123283d7c2f732fe0a4d21169002507 100644 --- a/VectoCore/VectoCore/Models/SimulationComponent/Impl/AMTShiftStrategy.cs +++ b/VectoCore/VectoCore/Models/SimulationComponent/Impl/AMTShiftStrategy.cs @@ -29,301 +29,306 @@ * Martin Rexeis, rexeis@ivt.tugraz.at, IVT, Graz University of Technology */ -using System.Linq; -using TUGraz.VectoCommon.Utils; -using TUGraz.VectoCore.Configuration; -using TUGraz.VectoCore.Models.Connector.Ports.Impl; -using TUGraz.VectoCore.Models.Simulation.Data; -using TUGraz.VectoCore.Models.Simulation.DataBus; - -namespace TUGraz.VectoCore.Models.SimulationComponent.Impl -{ - /// <summary> - /// AMTShiftStrategy implements the AMT Shifting Behaviour. - /// </summary> - public class AMTShiftStrategy : ShiftStrategy - { - protected readonly uint MaxStartGear; - protected uint _nextGear; - - public AMTShiftStrategy(VectoRunData runData, IDataBus dataBus) : base(runData.GearboxData, dataBus) - { - EarlyShiftUp = true; - SkipGears = true; - - var transmissionRatio = runData.AxleGearData.AxleGear.Ratio * - (runData.AngledriveData == null ? 1.0 : runData.AngledriveData.Angledrive.Ratio) / - runData.VehicleData.DynamicTyreRadius; - var minEngineSpeed = (runData.EngineData.FullLoadCurves[0].RatedSpeed - runData.EngineData.IdleSpeed) * - Constants.SimulationSettings.ClutchClosingSpeedNorm + runData.EngineData.IdleSpeed; - foreach (var gearData in ModelData.Gears.Reverse()) { - if (ModelData.StartSpeed * transmissionRatio * gearData.Value.Ratio > minEngineSpeed) { - MaxStartGear = gearData.Key; - break; - } - } - } - - private bool SpeedTooLowForEngine(uint gear, PerSecond outAngularSpeed) - { - return (outAngularSpeed * ModelData.Gears[gear].Ratio).IsSmaller(DataBus.EngineIdleSpeed); - } - - private bool SpeedTooHighForEngine(uint gear, PerSecond outAngularSpeed) - { - return - (outAngularSpeed * ModelData.Gears[gear].Ratio).IsGreaterOrEqual(ModelData.Gears[gear].MaxSpeed ?? - DataBus.EngineN95hSpeed); - } - - public override GearInfo NextGear - { - get { return new GearInfo(_nextGear, false); } - } - - public override uint Engage(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity) - { - while (_nextGear > 1 && SpeedTooLowForEngine(_nextGear, outAngularVelocity)) { - _nextGear--; - } - while (_nextGear < ModelData.Gears.Count && SpeedTooHighForEngine(_nextGear, outAngularVelocity)) { - _nextGear++; - } - - return _nextGear; - } - - public override void Disengage(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outEngineSpeed) {} - - public override uint InitGear(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity) - { - if (DataBus.VehicleSpeed.IsEqual(0)) { - for (var gear = MaxStartGear; gear > 1; gear--) { - var inAngularSpeed = outAngularVelocity * ModelData.Gears[gear].Ratio; - - var ratedSpeed = DataBus.EngineRatedSpeed; - if (inAngularSpeed > ratedSpeed || inAngularSpeed.IsEqual(0)) { - continue; - } - - var response = _gearbox.Initialize(gear, outTorque, outAngularVelocity); - - var fullLoadPower = response.DynamicFullLoadPower; //EnginePowerRequest - response.DeltaFullLoad; - var reserve = 1 - response.EnginePowerRequest / fullLoadPower; - - if (response.EngineSpeed > DataBus.EngineIdleSpeed && reserve >= ModelData.StartTorqueReserve) { - _nextGear = gear; - return gear; - } - } - _nextGear = 1; - return 1; - } - for (var gear = (uint)ModelData.Gears.Count; gear > 1; gear--) { - var response = _gearbox.Initialize(gear, outTorque, outAngularVelocity); - - var inAngularSpeed = outAngularVelocity * ModelData.Gears[gear].Ratio; - var fullLoadPower = response.EnginePowerRequest - response.DeltaFullLoad; - var reserve = 1 - response.EnginePowerRequest / fullLoadPower; - var inTorque = response.ClutchPowerRequest / inAngularSpeed; - - // if in shift curve and torque reserve is provided: return the current gear - if (!IsBelowDownShiftCurve(gear, inTorque, inAngularSpeed) && !IsAboveUpShiftCurve(gear, inTorque, inAngularSpeed) && - reserve >= ModelData.StartTorqueReserve) { - if ((inAngularSpeed - DataBus.EngineIdleSpeed) / (DataBus.EngineRatedSpeed - DataBus.EngineIdleSpeed) < - Constants.SimulationSettings.ClutchClosingSpeedNorm && gear > 1) { - gear--; - } - _nextGear = gear; - return gear; - } - - // if over the up shift curve: return the previous gear (even thou it did not provide the required torque reserve) - if (IsAboveUpShiftCurve(gear, inTorque, inAngularSpeed) && gear < ModelData.Gears.Count) { - _nextGear = gear; - return gear + 1; - } - } - - // fallback: return first gear - _nextGear = 1; - return 1; - } - - public override bool ShiftRequired(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, - NewtonMeter inTorque, PerSecond inAngularVelocity, uint gear, Second lastShiftTime) - { - // no shift when vehicle stands - if (DataBus.VehicleStopped) { - return false; - } - - // emergency shift to not stall the engine ------------------------ - if (gear == 1 && SpeedTooLowForEngine(_nextGear, inAngularVelocity / ModelData.Gears[gear].Ratio)) { - return true; - } - _nextGear = gear; - while (_nextGear > 1 && SpeedTooLowForEngine(_nextGear, inAngularVelocity / ModelData.Gears[gear].Ratio)) { - _nextGear--; - } - while (_nextGear < ModelData.Gears.Count && - SpeedTooHighForEngine(_nextGear, inAngularVelocity / ModelData.Gears[gear].Ratio)) { - _nextGear++; - } - if (_nextGear != gear) { - return true; - } - - // normal shift when all requirements are fullfilled ------------------ - var minimumShiftTimePassed = (lastShiftTime + ModelData.ShiftTime).IsSmallerOrEqual(absTime); - if (!minimumShiftTimePassed) { - return false; - } - - _nextGear = CheckDownshift(absTime, dt, outTorque, outAngularVelocity, inTorque, inAngularVelocity, gear); - if (_nextGear != gear) { - return true; - } - - _nextGear = CheckUpshift(absTime, dt, outTorque, outAngularVelocity, inTorque, inAngularVelocity, gear); - - //if ((ModelData.Gears[_nextGear].Ratio * outAngularVelocity - DataBus.EngineIdleSpeed) / - // (DataBus.EngineRatedSpeed - DataBus.EngineIdleSpeed) < - // Constants.SimulationSettings.ClutchClosingSpeedNorm && _nextGear > 1) { - // _nextGear--; - //} - - return _nextGear != gear; - } - - protected virtual uint CheckUpshift(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, - NewtonMeter inTorque, PerSecond inAngularVelocity, uint currentGear) - { - // if the driver's intention is _not_ to accelerate or drive along then don't upshift - if (DataBus.DriverBehavior != DrivingBehavior.Accelerating && DataBus.DriverBehavior != DrivingBehavior.Driving) { - return currentGear; - } - if ((absTime - _gearbox.LastDownshift).IsSmaller(_gearbox.ModelData.UpshiftAfterDownshiftDelay)) { - return currentGear; - } - var nextGear = DoCheckUpshift(absTime, dt, outTorque, outAngularVelocity, inTorque, inAngularVelocity, currentGear); - if (nextGear == currentGear) { - return nextGear; - } - - // estimate acceleration for selected gear - if (EstimateAccelerationForGear(nextGear, outAngularVelocity).IsSmaller(_gearbox.ModelData.UpshiftMinAcceleration)) { - // if less than 0.1 for next gear, don't shift - if (nextGear - currentGear == 1) { - return currentGear; - } - // if a gear is skipped but acceleration is less than 0.1, try for next gear. if acceleration is still below 0.1 don't shift! - if (nextGear > currentGear && - EstimateAccelerationForGear(currentGear + 1, outAngularVelocity) - .IsSmaller(_gearbox.ModelData.UpshiftMinAcceleration)) { - return currentGear; - } - nextGear = currentGear + 1; - } - - return nextGear; - } - - protected virtual uint CheckDownshift(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, - NewtonMeter inTorque, PerSecond inAngularVelocity, uint currentGear) - { - if ((absTime - _gearbox.LastUpshift).IsSmaller(_gearbox.ModelData.DownshiftAfterUpshiftDelay)) { - return currentGear; - } - return DoCheckDownshift(absTime, dt, outTorque, outAngularVelocity, inTorque, inAngularVelocity, currentGear); - } - - protected virtual uint DoCheckUpshift(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, - NewtonMeter inTorque, PerSecond inAngularVelocity, uint currentGear) - { - // upshift - if (IsAboveUpShiftCurve(currentGear, inTorque, inAngularVelocity)) { - currentGear++; - - while (SkipGears && currentGear < ModelData.Gears.Count) { - currentGear++; - var tmpGear = Gearbox.Gear; - _gearbox.Gear = currentGear; - var response = (ResponseDryRun)_gearbox.Request(absTime, dt, outTorque, outAngularVelocity, true); - _gearbox.Gear = tmpGear; - - inAngularVelocity = response.EngineSpeed; //ModelData.Gears[currentGear].Ratio * outAngularVelocity; - inTorque = response.ClutchPowerRequest / inAngularVelocity; - - var maxTorque = VectoMath.Min(response.DynamicFullLoadPower / ((DataBus.EngineSpeed + response.EngineSpeed) / 2), - currentGear > 1 - ? ModelData.Gears[currentGear].ShiftPolygon.InterpolateDownshift(response.EngineSpeed) - : double.MaxValue.SI<NewtonMeter>()); - var reserve = 1 - inTorque / maxTorque; - - if (reserve >= ModelData.TorqueReserve && IsAboveDownShiftCurve(currentGear, inTorque, inAngularVelocity)) { - continue; - } - - currentGear--; - break; - } - } - - // early up shift to higher gear --------------------------------------- - if (EarlyShiftUp && currentGear < ModelData.Gears.Count) { - // try if next gear would provide enough torque reserve - var tryNextGear = currentGear + 1; - var tmpGear = Gearbox.Gear; - _gearbox.Gear = tryNextGear; - var response = (ResponseDryRun)_gearbox.Request(absTime, dt, outTorque, outAngularVelocity, true); - _gearbox.Gear = tmpGear; - - inAngularVelocity = ModelData.Gears[tryNextGear].Ratio * outAngularVelocity; - inTorque = response.ClutchPowerRequest / inAngularVelocity; - - // if next gear supplied enough power reserve: take it - // otherwise take - if (!IsBelowDownShiftCurve(tryNextGear, inTorque, inAngularVelocity)) { - var fullLoadPower = response.EnginePowerRequest - response.DeltaFullLoad; - var reserve = 1 - response.EnginePowerRequest / fullLoadPower; - - if (reserve >= ModelData.TorqueReserve) { - currentGear = tryNextGear; - } - } - } - return currentGear; - } - - protected virtual uint DoCheckDownshift(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, - NewtonMeter inTorque, PerSecond inAngularVelocity, uint currentGear) - { - // down shift - if (IsBelowDownShiftCurve(currentGear, inTorque, inAngularVelocity)) { - currentGear--; - while (SkipGears && currentGear > 1) { - currentGear--; - var tmpGear = Gearbox.Gear; - _gearbox.Gear = currentGear; - var response = (ResponseDryRun)_gearbox.Request(absTime, dt, outTorque, outAngularVelocity, true); - _gearbox.Gear = tmpGear; - - inAngularVelocity = ModelData.Gears[currentGear].Ratio * outAngularVelocity; - inTorque = response.ClutchPowerRequest / inAngularVelocity; - var maxTorque = VectoMath.Min(response.DynamicFullLoadPower / ((DataBus.EngineSpeed + response.EngineSpeed) / 2), - currentGear > 1 - ? ModelData.Gears[currentGear].ShiftPolygon.InterpolateDownshift(response.EngineSpeed) - : double.MaxValue.SI<NewtonMeter>()); - var reserve = maxTorque.IsEqual(0) ? -1 : (1 - inTorque / maxTorque).Value(); - if (reserve >= ModelData.TorqueReserve && IsBelowUpShiftCurve(currentGear, inTorque, inAngularVelocity)) { - continue; - } - currentGear++; - break; - } - } - return currentGear; - } - } +using System.Linq; +using TUGraz.VectoCommon.Utils; +using TUGraz.VectoCore.Configuration; +using TUGraz.VectoCore.Models.Connector.Ports.Impl; +using TUGraz.VectoCore.Models.Simulation.Data; +using TUGraz.VectoCore.Models.Simulation.DataBus; + +namespace TUGraz.VectoCore.Models.SimulationComponent.Impl +{ + /// <summary> + /// AMTShiftStrategy implements the AMT Shifting Behaviour. + /// </summary> + public class AMTShiftStrategy : ShiftStrategy + { + protected readonly uint MaxStartGear; + protected uint _nextGear; + + public AMTShiftStrategy(VectoRunData runData, IDataBus dataBus) : base(runData.GearboxData, dataBus) + { + EarlyShiftUp = true; + SkipGears = true; + + var transmissionRatio = runData.AxleGearData.AxleGear.Ratio * + (runData.AngledriveData == null ? 1.0 : runData.AngledriveData.Angledrive.Ratio) / + runData.VehicleData.DynamicTyreRadius; + var minEngineSpeed = (runData.EngineData.FullLoadCurves[0].RatedSpeed - runData.EngineData.IdleSpeed) * + Constants.SimulationSettings.ClutchClosingSpeedNorm + runData.EngineData.IdleSpeed; + foreach (var gearData in ModelData.Gears.Reverse()) { + if (ModelData.StartSpeed * transmissionRatio * gearData.Value.Ratio > minEngineSpeed) { + MaxStartGear = gearData.Key; + break; + } + } + } + + private bool SpeedTooLowForEngine(uint gear, PerSecond outAngularSpeed) + { + return (outAngularSpeed * ModelData.Gears[gear].Ratio).IsSmaller(DataBus.EngineIdleSpeed); + } + + private bool SpeedTooHighForEngine(uint gear, PerSecond outAngularSpeed) + { + return + (outAngularSpeed * ModelData.Gears[gear].Ratio).IsGreaterOrEqual(ModelData.Gears[gear].MaxSpeed ?? + DataBus.EngineN95hSpeed); + } + + public override GearInfo NextGear + { + get { return new GearInfo(_nextGear, false); } + } + + public override uint Engage(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity) + { + while (_nextGear > 1 && SpeedTooLowForEngine(_nextGear, outAngularVelocity)) { + _nextGear--; + } + while (_nextGear < ModelData.Gears.Count && SpeedTooHighForEngine(_nextGear, outAngularVelocity)) { + _nextGear++; + } + + return _nextGear; + } + + public override void Disengage(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outEngineSpeed) {} + + public override uint InitGear(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity) + { + if (DataBus.VehicleSpeed.IsEqual(0)) { + return InitStartGear(outTorque, outAngularVelocity); + } + for (var gear = (uint)ModelData.Gears.Count; gear > 1; gear--) { + var response = _gearbox.Initialize(gear, outTorque, outAngularVelocity); + + var inAngularSpeed = outAngularVelocity * ModelData.Gears[gear].Ratio; + var fullLoadPower = response.EnginePowerRequest - response.DeltaFullLoad; + var reserve = 1 - response.EnginePowerRequest / fullLoadPower; + var inTorque = response.ClutchPowerRequest / inAngularSpeed; + + // if in shift curve and torque reserve is provided: return the current gear + if (!IsBelowDownShiftCurve(gear, inTorque, inAngularSpeed) && !IsAboveUpShiftCurve(gear, inTorque, inAngularSpeed) && + reserve >= ModelData.StartTorqueReserve) { + if ((inAngularSpeed - DataBus.EngineIdleSpeed) / (DataBus.EngineRatedSpeed - DataBus.EngineIdleSpeed) < + Constants.SimulationSettings.ClutchClosingSpeedNorm && gear > 1) { + gear--; + } + _nextGear = gear; + return gear; + } + + // if over the up shift curve: return the previous gear (even thou it did not provide the required torque reserve) + if (IsAboveUpShiftCurve(gear, inTorque, inAngularSpeed) && gear < ModelData.Gears.Count) { + _nextGear = gear; + return gear + 1; + } + } + + // fallback: return first gear + _nextGear = 1; + return 1; + } + + private uint InitStartGear(NewtonMeter outTorque, PerSecond outAngularVelocity) + { + for (var gear = MaxStartGear; gear > 1; gear--) { + var inAngularSpeed = outAngularVelocity * ModelData.Gears[gear].Ratio; + + var ratedSpeed = DataBus.EngineRatedSpeed; + if (inAngularSpeed > ratedSpeed || inAngularSpeed.IsEqual(0)) { + continue; + } + + var response = _gearbox.Initialize(gear, outTorque, outAngularVelocity); + + var fullLoadPower = response.DynamicFullLoadPower; //EnginePowerRequest - response.DeltaFullLoad; + var reserve = 1 - response.EnginePowerRequest / fullLoadPower; + + if (response.EngineSpeed > DataBus.EngineIdleSpeed && reserve >= ModelData.StartTorqueReserve) { + _nextGear = gear; + return gear; + } + } + _nextGear = 1; + return 1; + } + + public override bool ShiftRequired(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, + NewtonMeter inTorque, PerSecond inAngularVelocity, uint gear, Second lastShiftTime) + { + // no shift when vehicle stands + if (DataBus.VehicleStopped) { + return false; + } + + // emergency shift to not stall the engine ------------------------ + if (gear == 1 && SpeedTooLowForEngine(_nextGear, inAngularVelocity / ModelData.Gears[gear].Ratio)) { + return true; + } + _nextGear = gear; + while (_nextGear > 1 && SpeedTooLowForEngine(_nextGear, inAngularVelocity / ModelData.Gears[gear].Ratio)) { + _nextGear--; + } + while (_nextGear < ModelData.Gears.Count && + SpeedTooHighForEngine(_nextGear, inAngularVelocity / ModelData.Gears[gear].Ratio)) { + _nextGear++; + } + if (_nextGear != gear) { + return true; + } + + // normal shift when all requirements are fullfilled ------------------ + var minimumShiftTimePassed = (lastShiftTime + ModelData.ShiftTime).IsSmallerOrEqual(absTime); + if (!minimumShiftTimePassed) { + return false; + } + + _nextGear = CheckDownshift(absTime, dt, outTorque, outAngularVelocity, inTorque, inAngularVelocity, gear); + if (_nextGear != gear) { + return true; + } + + _nextGear = CheckUpshift(absTime, dt, outTorque, outAngularVelocity, inTorque, inAngularVelocity, gear); + + //if ((ModelData.Gears[_nextGear].Ratio * outAngularVelocity - DataBus.EngineIdleSpeed) / + // (DataBus.EngineRatedSpeed - DataBus.EngineIdleSpeed) < + // Constants.SimulationSettings.ClutchClosingSpeedNorm && _nextGear > 1) { + // _nextGear--; + //} + + return _nextGear != gear; + } + + protected virtual uint CheckUpshift(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, + NewtonMeter inTorque, PerSecond inAngularVelocity, uint currentGear) + { + // if the driver's intention is _not_ to accelerate or drive along then don't upshift + if (DataBus.DriverBehavior != DrivingBehavior.Accelerating && DataBus.DriverBehavior != DrivingBehavior.Driving) { + return currentGear; + } + if ((absTime - _gearbox.LastDownshift).IsSmaller(_gearbox.ModelData.UpshiftAfterDownshiftDelay)) { + return currentGear; + } + var nextGear = DoCheckUpshift(absTime, dt, outTorque, outAngularVelocity, inTorque, inAngularVelocity, currentGear); + if (nextGear == currentGear) { + return nextGear; + } + + // estimate acceleration for selected gear + if (EstimateAccelerationForGear(nextGear, outAngularVelocity).IsSmaller(_gearbox.ModelData.UpshiftMinAcceleration)) { + // if less than 0.1 for next gear, don't shift + if (nextGear - currentGear == 1) { + return currentGear; + } + // if a gear is skipped but acceleration is less than 0.1, try for next gear. if acceleration is still below 0.1 don't shift! + if (nextGear > currentGear && + EstimateAccelerationForGear(currentGear + 1, outAngularVelocity) + .IsSmaller(_gearbox.ModelData.UpshiftMinAcceleration)) { + return currentGear; + } + nextGear = currentGear + 1; + } + + return nextGear; + } + + protected virtual uint CheckDownshift(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, + NewtonMeter inTorque, PerSecond inAngularVelocity, uint currentGear) + { + if ((absTime - _gearbox.LastUpshift).IsSmaller(_gearbox.ModelData.DownshiftAfterUpshiftDelay)) { + return currentGear; + } + return DoCheckDownshift(absTime, dt, outTorque, outAngularVelocity, inTorque, inAngularVelocity, currentGear); + } + + protected virtual uint DoCheckUpshift(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, + NewtonMeter inTorque, PerSecond inAngularVelocity, uint currentGear) + { + // upshift + if (IsAboveUpShiftCurve(currentGear, inTorque, inAngularVelocity)) { + currentGear++; + + while (SkipGears && currentGear < ModelData.Gears.Count) { + currentGear++; + var tmpGear = Gearbox.Gear; + _gearbox.Gear = currentGear; + var response = (ResponseDryRun)_gearbox.Request(absTime, dt, outTorque, outAngularVelocity, true); + _gearbox.Gear = tmpGear; + + inAngularVelocity = response.EngineSpeed; //ModelData.Gears[currentGear].Ratio * outAngularVelocity; + inTorque = response.ClutchPowerRequest / inAngularVelocity; + + var maxTorque = VectoMath.Min(response.DynamicFullLoadPower / ((DataBus.EngineSpeed + response.EngineSpeed) / 2), + currentGear > 1 + ? ModelData.Gears[currentGear].ShiftPolygon.InterpolateDownshift(response.EngineSpeed) + : double.MaxValue.SI<NewtonMeter>()); + var reserve = 1 - inTorque / maxTorque; + + if (reserve >= ModelData.TorqueReserve && IsAboveDownShiftCurve(currentGear, inTorque, inAngularVelocity)) { + continue; + } + + currentGear--; + break; + } + } + + // early up shift to higher gear --------------------------------------- + if (EarlyShiftUp && currentGear < ModelData.Gears.Count) { + // try if next gear would provide enough torque reserve + var tryNextGear = currentGear + 1; + var tmpGear = Gearbox.Gear; + _gearbox.Gear = tryNextGear; + var response = (ResponseDryRun)_gearbox.Request(absTime, dt, outTorque, outAngularVelocity, true); + _gearbox.Gear = tmpGear; + + inAngularVelocity = ModelData.Gears[tryNextGear].Ratio * outAngularVelocity; + inTorque = response.ClutchPowerRequest / inAngularVelocity; + + // if next gear supplied enough power reserve: take it + // otherwise take + if (!IsBelowDownShiftCurve(tryNextGear, inTorque, inAngularVelocity)) { + var fullLoadPower = response.EnginePowerRequest - response.DeltaFullLoad; + var reserve = 1 - response.EnginePowerRequest / fullLoadPower; + + if (reserve >= ModelData.TorqueReserve) { + currentGear = tryNextGear; + } + } + } + return currentGear; + } + + protected virtual uint DoCheckDownshift(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, + NewtonMeter inTorque, PerSecond inAngularVelocity, uint currentGear) + { + // down shift + if (IsBelowDownShiftCurve(currentGear, inTorque, inAngularVelocity)) { + currentGear--; + while (SkipGears && currentGear > 1) { + currentGear--; + var tmpGear = Gearbox.Gear; + _gearbox.Gear = currentGear; + var response = (ResponseDryRun)_gearbox.Request(absTime, dt, outTorque, outAngularVelocity, true); + _gearbox.Gear = tmpGear; + + inAngularVelocity = ModelData.Gears[currentGear].Ratio * outAngularVelocity; + inTorque = response.ClutchPowerRequest / inAngularVelocity; + var maxTorque = VectoMath.Min(response.DynamicFullLoadPower / ((DataBus.EngineSpeed + response.EngineSpeed) / 2), + currentGear > 1 + ? ModelData.Gears[currentGear].ShiftPolygon.InterpolateDownshift(response.EngineSpeed) + : double.MaxValue.SI<NewtonMeter>()); + var reserve = maxTorque.IsEqual(0) ? -1 : (1 - inTorque / maxTorque).Value(); + if (reserve >= ModelData.TorqueReserve && IsBelowUpShiftCurve(currentGear, inTorque, inAngularVelocity)) { + continue; + } + currentGear++; + break; + } + } + return currentGear; + } + } } \ No newline at end of file diff --git a/VectoCore/VectoCore/Models/SimulationComponent/Impl/Driver.cs b/VectoCore/VectoCore/Models/SimulationComponent/Impl/Driver.cs index d6427262a92f48cca756403e27ea624d13a4fe1f..c407266aeb7870562b92531c4ee65fbf5502c9af 100644 --- a/VectoCore/VectoCore/Models/SimulationComponent/Impl/Driver.cs +++ b/VectoCore/VectoCore/Models/SimulationComponent/Impl/Driver.cs @@ -29,907 +29,939 @@ * Martin Rexeis, rexeis@ivt.tugraz.at, IVT, Graz University of Technology */ -using System; -using TUGraz.VectoCommon.Exceptions; -using TUGraz.VectoCommon.Models; -using TUGraz.VectoCommon.Utils; -using TUGraz.VectoCore.Configuration; -using TUGraz.VectoCore.Models.Connector.Ports; -using TUGraz.VectoCore.Models.Connector.Ports.Impl; -using TUGraz.VectoCore.Models.Simulation; -using TUGraz.VectoCore.Models.Simulation.Data; -using TUGraz.VectoCore.Models.Simulation.DataBus; -using TUGraz.VectoCore.OutputData; -using TUGraz.VectoCore.Utils; -using DriverData = TUGraz.VectoCore.Models.SimulationComponent.Data.DriverData; - -namespace TUGraz.VectoCore.Models.SimulationComponent.Impl -{ - public class Driver : - StatefulProviderComponent<Driver.DriverState, IDrivingCycleOutPort, IDriverDemandInPort, IDriverDemandOutPort>, - IDriver, IDrivingCycleOutPort, IDriverDemandInPort, IDriverActions, IDriverInfo - { - public DriverData DriverData { get; protected set; } - - protected readonly IDriverStrategy DriverStrategy; - public string CurrentAction = ""; - - public Driver(IVehicleContainer container, DriverData driverData, IDriverStrategy strategy) : base(container) - { - DriverData = driverData; - DriverStrategy = strategy; - strategy.Driver = this; - DriverAcceleration = 0.SI<MeterPerSquareSecond>(); - } - - public IResponse Initialize(MeterPerSecond vehicleSpeed, Radian roadGradient) - { - DriverBehavior = vehicleSpeed.IsEqual(0) ? DrivingBehavior.Halted : DrivingBehavior.Driving; - return NextComponent.Initialize(vehicleSpeed, roadGradient); - } - - public IResponse Initialize(MeterPerSecond vehicleSpeed, Radian roadGradient, - MeterPerSquareSecond startAcceleration) - { - DriverBehavior = vehicleSpeed.IsEqual(0) ? DrivingBehavior.Halted : DrivingBehavior.Driving; - var retVal = NextComponent.Initialize(vehicleSpeed, roadGradient, startAcceleration); - - return retVal; - } - - public IResponse Request(Second absTime, Meter ds, MeterPerSecond targetVelocity, Radian gradient) - { - IterationStatistics.Increment(this, "Requests"); - - Log.Debug("==== DRIVER Request (distance) ===="); - Log.Debug( - "Request: absTime: {0}, ds: {1}, targetVelocity: {2}, gradient: {3} | distance: {4}, velocity: {5}, vehicle stopped: {6}", - absTime, ds, targetVelocity, gradient, DataBus.Distance, DataBus.VehicleSpeed, DataBus.VehicleStopped); - - var retVal = DriverStrategy.Request(absTime, ds, targetVelocity, gradient); - - CurrentState.Response = retVal; - retVal.SimulationInterval = CurrentState.dt; - retVal.Acceleration = CurrentState.Acceleration; - - return retVal; - } - - public IResponse Request(Second absTime, Second dt, MeterPerSecond targetVelocity, Radian gradient) - { - IterationStatistics.Increment(this, "Requests"); - - Log.Debug("==== DRIVER Request (time) ===="); - Log.Debug( - "Request: absTime: {0}, dt: {1}, targetVelocity: {2}, gradient: {3} | distance: {4}, velocity: {5} gear: {6}: vehicle stopped: {7}", - absTime, dt, targetVelocity, gradient, DataBus.Distance, DataBus.VehicleSpeed, DataBus.Gear, - DataBus.VehicleStopped); - - var retVal = DriverStrategy.Request(absTime, dt, targetVelocity, gradient); - - CurrentState.Response = retVal; - retVal.SimulationInterval = CurrentState.dt; - retVal.Acceleration = CurrentState.Acceleration; - - return retVal; - } - - public new IDataBus DataBus - { - get { return base.DataBus; } - } - - /// <summary> - /// see documentation of IDriverActions - /// </summary> - /// <param name="absTime"></param> - /// <param name="ds"></param> - /// <param name="targetVelocity"></param> - /// <param name="gradient"></param> - /// <param name="previousResponse"></param> - /// <returns></returns> - public IResponse DrivingActionAccelerate(Second absTime, Meter ds, MeterPerSecond targetVelocity, - Radian gradient, - IResponse previousResponse = null) - { - CurrentAction = "ACCELERATE"; - IterationStatistics.Increment(this, "Accelerate"); - Log.Debug("DrivingAction Accelerate"); - var operatingPoint = ComputeAcceleration(ds, targetVelocity); - - IResponse retVal = null; - DriverAcceleration = operatingPoint.Acceleration; - var response = previousResponse ?? - NextComponent.Request(absTime, operatingPoint.SimulationInterval, operatingPoint.Acceleration, - gradient); - response.Acceleration = operatingPoint.Acceleration; - - response.Switch(). - Case<ResponseSuccess>(r => { retVal = r; // => return - }). - Case<ResponseOverload>(). // do nothing, searchOperatingPoint is called later on - Case<ResponseEngineSpeedTooHigh>(). // do nothing, searchOperatingPoint is called later on - Case<ResponseUnderload>(r => { - // Delta is negative we are already below the Drag-load curve. activate brakes - retVal = r; // => return, strategy should brake - }). - Case<ResponseFailTimeInterval>(r => { - // occurs only with AT gearboxes - extend time interval after gearshift! - retVal = new ResponseDrivingCycleDistanceExceeded { - Source = this, - MaxDistance = r.Acceleration / 2 * r.DeltaT * r.DeltaT + DataBus.VehicleSpeed * r.DeltaT - }; - }). - Case<ResponseGearShift>(r => { retVal = r; }). - Default(r => { throw new UnexpectedResponseException("DrivingAction Accelerate.", r); }); - - if (retVal == null) { - // unhandled response (overload, delta > 0) - we need to search for a valid operating point.. - - OperatingPoint nextOperatingPoint; - try { - nextOperatingPoint = SearchOperatingPoint(absTime, ds, gradient, operatingPoint.Acceleration, - response); - } catch (VectoEngineSpeedTooLowException) { - // in case of an exception during search the engine-speed got too low - gear disengaged, try roll action. - nextOperatingPoint = SearchOperatingPoint(absTime, ds, gradient, operatingPoint.Acceleration, - response); - } - - var limitedOperatingPoint = nextOperatingPoint; - if (!(retVal is ResponseEngineSpeedTooHigh || DataBus.ClutchClosed(absTime))) { - limitedOperatingPoint = LimitAccelerationByDriverModel(nextOperatingPoint, - LimitationMode.LimitDecelerationDriver); - Log.Debug("Found operating point for Drive/Accelerate. dt: {0}, acceleration: {1}", - limitedOperatingPoint.SimulationInterval, limitedOperatingPoint.Acceleration); - } - DriverAcceleration = limitedOperatingPoint.Acceleration; - retVal = NextComponent.Request(absTime, limitedOperatingPoint.SimulationInterval, - limitedOperatingPoint.Acceleration, - gradient); - if (retVal != null) { - retVal.Acceleration = limitedOperatingPoint.Acceleration; - } - retVal.Switch(). - Case<ResponseUnderload>(() => operatingPoint = limitedOperatingPoint) - . // acceleration is limited by driver model, operating point moves below drag curve - Case<ResponseOverload>(() => { - // deceleration is limited by driver model, operating point moves above full load (e.g., steep uphill) - // the vehicle/driver can't achieve an acceleration higher than deceleration curve, try again with higher deceleration - if (DataBus.GearboxType.AutomaticTransmission()) { - Log.Info("AT Gearbox - Operating point resulted in an overload, searching again..."); - // search again for operating point, transmission may have shifted inbetween - nextOperatingPoint = SearchOperatingPoint(absTime, ds, gradient, operatingPoint.Acceleration, - response); - DriverAcceleration = nextOperatingPoint.Acceleration; - retVal = NextComponent.Request(absTime, nextOperatingPoint.SimulationInterval, - nextOperatingPoint.Acceleration, gradient); - } else { - if (absTime > 0 && DataBus.VehicleStopped) { - Log.Info( - "Operating point with limited acceleration resulted in an overload! Vehicle stopped! trying HALT action {0}", - nextOperatingPoint.Acceleration); - DataBus.BrakePower = 1.SI<Watt>(); - retVal = DrivingActionHalt(absTime, nextOperatingPoint.SimulationInterval, 0.SI<MeterPerSecond>(), gradient); - ds = 0.SI<Meter>(); - //retVal.Acceleration = 0.SI<MeterPerSquareSecond>(); - } else { - if (response is ResponseEngineSpeedTooHigh) { - Log.Info( - "Operating point with limited acceleration due to high engine speed resulted in an overload, searching again..."); - nextOperatingPoint = SearchOperatingPoint(absTime, ds, gradient, operatingPoint.Acceleration, - retVal); - DriverAcceleration = nextOperatingPoint.Acceleration; - retVal = NextComponent.Request(absTime, nextOperatingPoint.SimulationInterval, - nextOperatingPoint.Acceleration, gradient); - } else { - Log.Info( - "Operating point with limited acceleration resulted in an overload! trying again with original acceleration {0}", - nextOperatingPoint.Acceleration); - DriverAcceleration = nextOperatingPoint.Acceleration; - retVal = NextComponent.Request(absTime, nextOperatingPoint.SimulationInterval, - nextOperatingPoint.Acceleration, - gradient); - } - } - } - retVal.Switch(). - Case<ResponseSuccess>(() => operatingPoint = nextOperatingPoint). - Case<ResponseGearShift>(() => operatingPoint = nextOperatingPoint). - Default( - r => { throw new UnexpectedResponseException("DrivingAction Accelerate after Overload", r); }); - }). - Case<ResponseGearShift>(() => operatingPoint = limitedOperatingPoint). - Case<ResponseFailTimeInterval>(r => { - // occurs only with AT gearboxes - extend time interval after gearshift! - retVal = new ResponseDrivingCycleDistanceExceeded { - Source = this, - MaxDistance = r.Acceleration / 2 * r.DeltaT * r.DeltaT + DataBus.VehicleSpeed * r.DeltaT - }; - }). - Case<ResponseSuccess>(() => operatingPoint = limitedOperatingPoint). - Default( - r => { - throw new UnexpectedResponseException( - "DrivingAction Accelerate after SearchOperatingPoint.", r); - }); - } - CurrentState.Acceleration = operatingPoint.Acceleration; - CurrentState.dt = operatingPoint.SimulationInterval; - CurrentState.Response = retVal; - - retVal.Acceleration = operatingPoint.Acceleration; - retVal.SimulationInterval = operatingPoint.SimulationInterval; - retVal.SimulationDistance = ds; - retVal.OperatingPoint = operatingPoint; - - return retVal; - } - - /// <summary> - /// see documentation of IDriverActions - /// </summary> - /// <param name="absTime"></param> - /// <param name="ds"></param> - /// <param name="maxVelocity"></param> - /// <param name="gradient"></param> - /// <returns></returns> - public IResponse DrivingActionCoast(Second absTime, Meter ds, MeterPerSecond maxVelocity, Radian gradient) - { - CurrentAction = "COAST"; - IterationStatistics.Increment(this, "Coast"); - Log.Debug("DrivingAction Coast"); - - return CoastOrRollAction(absTime, ds, maxVelocity, gradient, false); - } - - /// <summary> - /// see documentation of IDriverActions - /// </summary> - /// <param name="absTime"></param> - /// <param name="ds"></param> - /// <param name="maxVelocity"></param> - /// <param name="gradient"></param> - /// <returns></returns> - public IResponse DrivingActionRoll(Second absTime, Meter ds, MeterPerSecond maxVelocity, Radian gradient) - { - CurrentAction = "ROLL"; - IterationStatistics.Increment(this, "Roll"); - - Log.Debug("DrivingAction Roll"); - - var retVal = CoastOrRollAction(absTime, ds, maxVelocity, gradient, true); - retVal.Switch(). - Case<ResponseGearShift>( - () => { - throw new UnexpectedResponseException("DrivingAction Roll: Gearshift during Roll action.", - retVal); - }); - - return retVal; - } - - /// <summary> - /// - /// </summary> - /// <param name="absTime"></param> - /// <param name="ds"></param> - /// <param name="maxVelocity"></param> - /// <param name="gradient"></param> - /// <param name="rollAction"></param> - /// <returns> - /// * ResponseSuccess - /// * ResponseDrivingCycleDistanceExceeded: vehicle is at low speed, coasting would lead to stop before ds is reached. - /// * ResponseSpeedLimitExceeded: vehicle accelerates during coasting which would lead to exceeding the given maxVelocity (e.g., driving downhill, engine's drag load is not sufficient) - /// * ResponseUnderload: engine's operating point is below drag curve (vehicle accelerates more than driver model allows; engine's drag load is not sufficient for limited acceleration - /// * ResponseGearShift: gearbox needs to shift gears, vehicle can not accelerate (traction interruption) - /// * ResponseFailTimeInterval: - /// </returns> - protected IResponse CoastOrRollAction(Second absTime, Meter ds, MeterPerSecond maxVelocity, Radian gradient, - bool rollAction) - { - var requestedOperatingPoint = ComputeAcceleration(ds, DataBus.VehicleSpeed); - DriverAcceleration = requestedOperatingPoint.Acceleration; - var initialResponse = NextComponent.Request(absTime, requestedOperatingPoint.SimulationInterval, - requestedOperatingPoint.Acceleration, gradient, dryRun: true); - - OperatingPoint searchedOperatingPoint; - try { - searchedOperatingPoint = SearchOperatingPoint(absTime, requestedOperatingPoint.SimulationDistance, - gradient, - requestedOperatingPoint.Acceleration, initialResponse, coastingOrRoll: true); - } catch (VectoEngineSpeedTooLowException) { - // in case of an exception during search the engine-speed got too low - gear disengaged --> try again with disengaged gear. - searchedOperatingPoint = SearchOperatingPoint(absTime, requestedOperatingPoint.SimulationDistance, - gradient, - requestedOperatingPoint.Acceleration, initialResponse, coastingOrRoll: true); - } - - if (!ds.IsEqual(searchedOperatingPoint.SimulationDistance)) { - // vehicle is at low speed, coasting would lead to stop before ds is reached: reduce simulated distance to stop distance. - Log.Debug( - "SearchOperatingPoint reduced the max. distance: {0} -> {1}. Issue new request from driving cycle!", - searchedOperatingPoint.SimulationDistance, ds); - CurrentState.Response = new ResponseDrivingCycleDistanceExceeded { - Source = this, - MaxDistance = searchedOperatingPoint.SimulationDistance, - Acceleration = searchedOperatingPoint.Acceleration, - SimulationInterval = searchedOperatingPoint.SimulationInterval, - OperatingPoint = searchedOperatingPoint - }; - return CurrentState.Response; - } - - Log.Debug("Found operating point for {2}. dt: {0}, acceleration: {1}", - searchedOperatingPoint.SimulationInterval, - searchedOperatingPoint.Acceleration, rollAction ? "ROLL" : "COAST"); - - var limitedOperatingPoint = LimitAccelerationByDriverModel(searchedOperatingPoint, - rollAction ? LimitationMode.NoLimitation : LimitationMode.LimitDecelerationDriver); - - // compute speed at the end of the simulation interval. if it exceeds the limit -> return - var v2 = DataBus.VehicleSpeed + - limitedOperatingPoint.Acceleration * limitedOperatingPoint.SimulationInterval; - if (v2 > maxVelocity && limitedOperatingPoint.Acceleration.IsGreaterOrEqual(0)) { - Log.Debug("vehicle's velocity would exceed given max speed. v2: {0}, max speed: {1}", v2, maxVelocity); - return new ResponseSpeedLimitExceeded() { Source = this }; - } - - DriverAcceleration = limitedOperatingPoint.Acceleration; - var response = NextComponent.Request(absTime, limitedOperatingPoint.SimulationInterval, - limitedOperatingPoint.Acceleration, gradient); - - response.SimulationInterval = limitedOperatingPoint.SimulationInterval; - response.SimulationDistance = ds; - response.Acceleration = limitedOperatingPoint.Acceleration; - response.OperatingPoint = limitedOperatingPoint; - - response.Switch(). - Case<ResponseSuccess>(). - Case<ResponseUnderload>(). // driver limits acceleration, operating point may be below engine's - //drag load resp. below 0 - Case<ResponseOverload>(). // driver limits acceleration, operating point may be above 0 (GBX), use brakes - Case<ResponseEngineSpeedTooHigh>(). // reduce acceleration/vehicle speed - Case<ResponseGearShift>(). - Case<ResponseFailTimeInterval>(r => { - response = new ResponseDrivingCycleDistanceExceeded { - Source = this, - MaxDistance = r.Acceleration / 2 * r.DeltaT * r.DeltaT + DataBus.VehicleSpeed * r.DeltaT - }; - }). - Default( - () => { - throw new UnexpectedResponseException( - "CoastOrRoll Action: unhandled response from powertrain.", response); - }); - - CurrentState.Response = response; - CurrentState.Acceleration = response.Acceleration; - CurrentState.dt = response.SimulationInterval; - return response; - } - - public IResponse DrivingActionBrake(Second absTime, Meter ds, MeterPerSecond nextTargetSpeed, Radian gradient, - IResponse previousResponse = null, Meter targetDistance = null) - { - CurrentAction = "BRAKE"; - IterationStatistics.Increment(this, "Brake"); - Log.Debug("DrivingAction Brake"); - - IResponse retVal = null; - - var operatingPoint = ComputeAcceleration(ds, nextTargetSpeed); - - //if (operatingPoint.Acceleration.IsSmaller(0)) { - // if we should brake with the max. deceleration and the deceleration changes within the current interval, take the larger deceleration... - if ( - operatingPoint.Acceleration.IsEqual( - DriverData.AccelerationCurve.Lookup(DataBus.VehicleSpeed).Deceleration)) { - var v2 = DataBus.VehicleSpeed + operatingPoint.Acceleration * operatingPoint.SimulationInterval; - var nextAcceleration = DriverData.AccelerationCurve.Lookup(v2).Deceleration; - var tmp = ComputeTimeInterval(VectoMath.Min(operatingPoint.Acceleration, nextAcceleration), - operatingPoint.SimulationDistance); - if (!operatingPoint.Acceleration.IsEqual(nextAcceleration) && - operatingPoint.SimulationDistance.IsEqual(tmp.SimulationDistance)) { - // only adjust operating point if the acceleration is different but the simulation distance is not modified - // i.e., braking to the next sample point (but a little bit slower) - Log.Debug("adjusting acceleration from {0} to {1}", operatingPoint.Acceleration, tmp.Acceleration); - operatingPoint = tmp; - // ComputeTimeInterval((operatingPoint.Acceleration + tmp.Acceleration) / 2, operatingPoint.SimulationDistance); - } - } - if (targetDistance != null && targetDistance > DataBus.Distance) { - var tmp = ComputeAcceleration(targetDistance - DataBus.Distance, nextTargetSpeed, false); - if (tmp.Acceleration.IsGreater(operatingPoint.Acceleration)) { - operatingPoint = ComputeTimeInterval(tmp.Acceleration, ds); - if (!ds.IsEqual(operatingPoint.SimulationDistance)) { - Log.Error( - "Unexpected Condition: Distance has been adjusted from {0} to {1}, currentVelocity: {2} acceleration: {3}, targetVelocity: {4}", - operatingPoint.SimulationDistance, ds, DataBus.VehicleSpeed, operatingPoint.Acceleration, - nextTargetSpeed); - throw new VectoSimulationException("Simulation distance unexpectedly adjusted! {0} -> {1}", ds, - operatingPoint.SimulationDistance); - } - } - } - - DriverAcceleration = operatingPoint.Acceleration; - var response = previousResponse ?? - NextComponent.Request(absTime, operatingPoint.SimulationInterval, operatingPoint.Acceleration, - gradient); - - var point = operatingPoint; - response.Switch(). - Case<ResponseSuccess>(r => retVal = r). - Case<ResponseOverload>(r => retVal = r) - . // i.e., driving uphill, clutch open, deceleration higher than desired deceleration - Case<ResponseUnderload>(). // will be handled in SearchBrakingPower - Case<ResponseEngineSpeedTooHigh>(r => { - Log.Debug("Engine speeed was too high, search for appropriate acceleration first."); - operatingPoint = SearchOperatingPoint(absTime, ds, gradient, point.Acceleration, - response); - }). // will be handled in SearchBrakingPower - Case<ResponseGearShift>(). // will be handled in SearchBrakingPower - Case<ResponseFailTimeInterval>(r => - retVal = new ResponseDrivingCycleDistanceExceeded() { - Source = this, - MaxDistance = DataBus.VehicleSpeed * r.DeltaT + point.Acceleration / 2 * r.DeltaT * r.DeltaT - }). - Default(r => { throw new UnexpectedResponseException("DrivingAction Brake: first request.", r); }); - - if (retVal != null) { - CurrentState.Acceleration = operatingPoint.Acceleration; - CurrentState.dt = operatingPoint.SimulationInterval; - CurrentState.Response = retVal; - retVal.Acceleration = operatingPoint.Acceleration; - retVal.SimulationInterval = operatingPoint.SimulationInterval; - retVal.SimulationDistance = ds; - retVal.OperatingPoint = operatingPoint; - return retVal; - } - - operatingPoint = SearchBrakingPower(absTime, operatingPoint.SimulationDistance, gradient, - operatingPoint.Acceleration, response); - - if (!ds.IsEqual(operatingPoint.SimulationDistance, 1E-15.SI<Meter>())) { - Log.Info( - "SearchOperatingPoint Braking reduced the max. distance: {0} -> {1}. Issue new request from driving cycle!", - operatingPoint.SimulationDistance, ds); - return new ResponseDrivingCycleDistanceExceeded { - Source = this, - MaxDistance = operatingPoint.SimulationDistance - }; - } - - Log.Debug("Found operating point for braking. dt: {0}, acceleration: {1} brakingPower: {2}", - operatingPoint.SimulationInterval, - operatingPoint.Acceleration, DataBus.BrakePower); - if (DataBus.BrakePower < 0) { - var overload = new ResponseOverload { - Source = this, - BrakePower = DataBus.BrakePower, - Acceleration = operatingPoint.Acceleration - }; - DataBus.BrakePower = 0.SI<Watt>(); - return overload; - } - - DriverAcceleration = operatingPoint.Acceleration; - retVal = NextComponent.Request(absTime, operatingPoint.SimulationInterval, operatingPoint.Acceleration, - gradient); - - retVal.Switch(). - Case<ResponseSuccess>(). - Case<ResponseGearShift>(). - Case<ResponseFailTimeInterval>(r => - retVal = new ResponseDrivingCycleDistanceExceeded() { - Source = this, - MaxDistance = - DataBus.VehicleSpeed * r.DeltaT + operatingPoint.Acceleration / 2 * r.DeltaT * r.DeltaT - }). - Case<ResponseUnderload>(r => { - if (DataBus.GearboxType.AutomaticTransmission()) { - operatingPoint = SearchBrakingPower(absTime, operatingPoint.SimulationDistance, gradient, - operatingPoint.Acceleration, response); - DriverAcceleration = operatingPoint.Acceleration; - retVal = NextComponent.Request(absTime, operatingPoint.SimulationInterval, - operatingPoint.Acceleration, gradient); - } - }). - Case<ResponseOverload>(r => { - if (DataBus.GearboxType.AutomaticTransmission()) { - // overload may happen because of gearshift between search and actual request, search again - var i = 5; - while (i-- > 0 && !(retVal is ResponseSuccess)) { - DataBus.BrakePower = 0.SI<Watt>(); - operatingPoint = SearchBrakingPower(absTime, operatingPoint.SimulationDistance, gradient, - operatingPoint.Acceleration, response); - DriverAcceleration = operatingPoint.Acceleration; - if (DataBus.BrakePower.IsSmaller(0)) { - DataBus.BrakePower = 0.SI<Watt>(); - - operatingPoint = SearchOperatingPoint(absTime, ds, gradient, 0.SI<MeterPerSquareSecond>(), r); - } - retVal = NextComponent.Request(absTime, operatingPoint.SimulationInterval, - operatingPoint.Acceleration, gradient); - } - } else { - throw new UnexpectedResponseException( - "DrivingAction Brake: request failed after braking power was found.", r); - } - }). - Default( - r => { - throw new UnexpectedResponseException( - "DrivingAction Brake: request failed after braking power was found.", r); - }); - CurrentState.Acceleration = operatingPoint.Acceleration; - CurrentState.dt = operatingPoint.SimulationInterval; - CurrentState.Response = retVal; - retVal.Acceleration = operatingPoint.Acceleration; - retVal.SimulationInterval = operatingPoint.SimulationInterval; - retVal.SimulationDistance = ds; - retVal.OperatingPoint = operatingPoint; - - return retVal; - } - - // ================================================ - - /// <summary> - /// - /// </summary> - /// <param name="operatingPoint"></param> - /// <param name="limits"></param> - /// <returns></returns> - private OperatingPoint LimitAccelerationByDriverModel(OperatingPoint operatingPoint, - LimitationMode limits) - { - var limitApplied = false; - var originalAcceleration = operatingPoint.Acceleration; - //if (((limits & LimitationMode.LimitDecelerationLookahead) != 0) && - // operatingPoint.Acceleration < DriverData.LookAheadCoasting.Deceleration) { - // operatingPoint.Acceleration = DriverData.LookAheadCoasting.Deceleration; - // limitApplied = true; - //} - var accelerationLimits = DriverData.AccelerationCurve.Lookup(DataBus.VehicleSpeed); - if (operatingPoint.Acceleration > accelerationLimits.Acceleration) { - operatingPoint.Acceleration = accelerationLimits.Acceleration; - limitApplied = true; - } - if (((limits & LimitationMode.LimitDecelerationDriver) != 0) && - operatingPoint.Acceleration < accelerationLimits.Deceleration) { - operatingPoint.Acceleration = accelerationLimits.Deceleration; - limitApplied = true; - } - if (limitApplied) { - operatingPoint.SimulationInterval = - ComputeTimeInterval(operatingPoint.Acceleration, operatingPoint.SimulationDistance) - .SimulationInterval; - Log.Debug("Limiting acceleration from {0} to {1}, dt: {2}", originalAcceleration, - operatingPoint.Acceleration, operatingPoint.SimulationInterval); - } - return operatingPoint; - } - - /// <summary> - /// Performs a search for the required braking power such that the vehicle accelerates with the given acceleration. - /// Returns a new operating point (a, ds, dt) where ds may be shorter due to vehicle stopping - /// </summary> - /// <returns>operating point (a, ds, dt) such that the vehicle accelerates with the given acceleration.</returns> - private OperatingPoint SearchBrakingPower(Second absTime, Meter ds, Radian gradient, - MeterPerSquareSecond acceleration, IResponse initialResponse) - { - IterationStatistics.Increment(this, "SearchBrakingPower", 0); - - var operatingPoint = new OperatingPoint { SimulationDistance = ds, Acceleration = acceleration }; - operatingPoint = ComputeTimeInterval(operatingPoint.Acceleration, ds); - Watt deltaPower = null; - initialResponse.Switch(). - Case<ResponseGearShift>(r => { - IterationStatistics.Increment(this, "SearchBrakingPower"); - DriverAcceleration = operatingPoint.Acceleration; - var nextResp = NextComponent.Request(absTime, operatingPoint.SimulationInterval, - operatingPoint.Acceleration, - gradient, true); - deltaPower = nextResp.GearboxPowerRequest; - }). - Case<ResponseEngineSpeedTooHigh>(r => { - IterationStatistics.Increment(this, "SearchBrakingPower"); - DriverAcceleration = operatingPoint.Acceleration; - var nextResp = NextComponent.Request(absTime, operatingPoint.SimulationInterval, - operatingPoint.Acceleration, - gradient, true); - deltaPower = nextResp.GearboxPowerRequest; - }). - Case<ResponseUnderload>(r => - deltaPower = DataBus.ClutchClosed(absTime) ? r.Delta : r.GearboxPowerRequest). - Default( - r => { throw new UnexpectedResponseException("cannot use response for searching braking power!", r); }); - - try { - DataBus.BrakePower = SearchAlgorithm.Search(DataBus.BrakePower, deltaPower, - deltaPower.Abs() * (DataBus.GearboxType.AutomaticTransmission() ? 0.5 : 1), - getYValue: result => { - var response = (ResponseDryRun)result; - return DataBus.ClutchClosed(absTime) ? response.DeltaDragLoad : response.GearboxPowerRequest; - }, - evaluateFunction: x => { - DataBus.BrakePower = x; - operatingPoint = ComputeTimeInterval(operatingPoint.Acceleration, ds); - - IterationStatistics.Increment(this, "SearchBrakingPower"); - DriverAcceleration = operatingPoint.Acceleration; - return NextComponent.Request(absTime, operatingPoint.SimulationInterval, - operatingPoint.Acceleration, gradient, - true); - }, - criterion: result => { - var response = (ResponseDryRun)result; - var delta = DataBus.ClutchClosed(absTime) - ? response.DeltaDragLoad - : response.GearboxPowerRequest; - return delta.Value(); - }); - - return operatingPoint; - } catch (Exception) { - Log.Error("Failed to find operating point for braking power! absTime: {0}", absTime); - throw; - } - } - - protected OperatingPoint SearchOperatingPoint(Second absTime, Meter ds, Radian gradient, - MeterPerSquareSecond acceleration, IResponse initialResponse, bool coastingOrRoll = false) - { - IterationStatistics.Increment(this, "SearchOperatingPoint", 0); - - var retVal = new OperatingPoint { Acceleration = acceleration, SimulationDistance = ds }; - - var actionRoll = !DataBus.ClutchClosed(absTime); - var searchEngineSpeed = false; - - Watt origDelta = null; - if (actionRoll) { - initialResponse.Switch(). - Case<ResponseDryRun>(r => origDelta = r.GearboxPowerRequest). - Case<ResponseFailTimeInterval>(r => origDelta = r.GearboxPowerRequest). - Default(r => { throw new UnexpectedResponseException("SearchOperatingPoint: Unknown response type.", r); }); - } else { - initialResponse.Switch(). - Case<ResponseOverload>(r => origDelta = r.Delta). - Case<ResponseEngineSpeedTooHigh>(r => { - searchEngineSpeed = true; - origDelta = r.DeltaEngineSpeed * 1.SI<NewtonMeter>(); - }). // search operating point in drive action after overload - Case<ResponseDryRun>(r => origDelta = coastingOrRoll ? r.DeltaDragLoad : r.DeltaFullLoad). - Default(r => { throw new UnexpectedResponseException("SearchOperatingPoint: Unknown response type.", r); }); - } - var delta = origDelta; - try { - retVal.Acceleration = SearchAlgorithm.Search(acceleration, delta, - Constants.SimulationSettings.OperatingPointInitialSearchIntervalAccelerating, - getYValue: response => { - var r = (ResponseDryRun)response; - if (searchEngineSpeed) { - return r.DeltaEngineSpeed * 1.SI<NewtonMeter>(); - } - return actionRoll ? r.GearboxPowerRequest : (coastingOrRoll ? r.DeltaDragLoad : r.DeltaFullLoad); - }, - evaluateFunction: - acc => { - // calculate new time interval only when vehiclespeed and acceleration are != 0 - // else: use same timeinterval as before. - if (!(acc.IsEqual(0) && DataBus.VehicleSpeed.IsEqual(0))) { - var tmp = ComputeTimeInterval(acc, ds); - if (tmp.SimulationInterval.IsEqual(0.SI<Second>(), 1e-9.SI<Second>())) { - throw new VectoSearchAbortedException( - "next TimeInterval is 0. a: {0}, v: {1}, dt: {2}", acc, - DataBus.VehicleSpeed, tmp.SimulationInterval); - } - retVal.Acceleration = tmp.Acceleration; - retVal.SimulationInterval = tmp.SimulationInterval; - retVal.SimulationDistance = tmp.SimulationDistance; - } - IterationStatistics.Increment(this, "SearchOperatingPoint"); - DriverAcceleration = acc; - var response = NextComponent.Request(absTime, retVal.SimulationInterval, acc, gradient, true); - response.OperatingPoint = retVal; - return response; - }, - criterion: response => { - var r = (ResponseDryRun)response; - if (searchEngineSpeed) { - return r.DeltaEngineSpeed.Value(); - } - delta = actionRoll - ? r.GearboxPowerRequest - : (coastingOrRoll ? r.DeltaDragLoad : r.DeltaFullLoad); - return delta.Value(); - }, - abortCriterion: - (response, cnt) => { - var r = (ResponseDryRun)response; - if (r == null) { - return false; - } - - return !actionRoll && !ds.IsEqual(r.OperatingPoint.SimulationDistance); - }); - } catch (VectoSearchAbortedException) { - // search aborted, try to go ahead with the last acceleration - } catch (Exception) { - Log.Error("Failed to find operating point! absTime: {0}", absTime); - throw; - } - - if (!retVal.Acceleration.IsBetween(DriverData.AccelerationCurve.MaxDeceleration(), - DriverData.AccelerationCurve.MaxAcceleration())) { - Log.Info("Operating Point outside driver acceleration limits: a: {0}", retVal.Acceleration); - } - return ComputeTimeInterval(retVal.Acceleration, retVal.SimulationDistance); - } - - /// <summary> - /// compute the acceleration and time-interval such that the vehicle's velocity approaches the given target velocity - /// - first compute the acceleration to reach the targetVelocity within the given distance - /// - limit the acceleration/deceleration by the driver's acceleration curve - /// - compute the time interval required to drive the given distance with the computed acceleration - /// computed acceleration and time interval are stored in CurrentState! - /// </summary> - /// <param name="ds">distance to reach the next target speed</param> - /// <param name="targetVelocity">next vehicle speed to decelerate to</param> - /// <param name="limitByDriverModel">if set to false the required acceleration will be computed, regardless of the driver's acceleration curve</param> - public OperatingPoint ComputeAcceleration(Meter ds, MeterPerSecond targetVelocity, - bool limitByDriverModel = true) - { - var currentSpeed = DataBus.VehicleSpeed; - var retVal = new OperatingPoint() { SimulationDistance = ds }; - - // Δx = (v0+v1)/2 * Δt - // => Δt = 2*Δx/(v0+v1) - var dt = 2 * ds / (currentSpeed + targetVelocity); - - // a = Δv / Δt - var requiredAcceleration = (targetVelocity - currentSpeed) / dt; - - if (!limitByDriverModel) { - return ComputeTimeInterval(requiredAcceleration, ds); - } - - var maxAcceleration = DriverData.AccelerationCurve.Lookup(currentSpeed); - - if (requiredAcceleration > maxAcceleration.Acceleration) { - requiredAcceleration = maxAcceleration.Acceleration; - } - if (requiredAcceleration < maxAcceleration.Deceleration) { - requiredAcceleration = maxAcceleration.Deceleration; - } - - retVal.Acceleration = requiredAcceleration; - retVal = ComputeTimeInterval(retVal.Acceleration, ds); - - if (ds.IsEqual(retVal.SimulationDistance)) { - return retVal; - } - - // this case should not happen, acceleration has been computed such that the target speed - // can be reached within ds. - Log.Error( - "Unexpected Condition: Distance has been adjusted from {0} to {1}, currentVelocity: {2} acceleration: {3}, targetVelocity: {4}", - retVal.SimulationDistance, ds, currentSpeed, CurrentState.Acceleration, targetVelocity); - throw new VectoSimulationException("Simulation distance unexpectedly adjusted! {0} -> {1}", ds, - retVal.SimulationDistance); - } - - /// <summary> - /// computes the distance required to decelerate from the current velocity to the given target velocity considering - /// the drivers acceleration/deceleration curve. - /// </summary> - /// <param name="targetSpeed"></param> - /// <returns></returns> - public Meter ComputeDecelerationDistance(MeterPerSecond targetSpeed) - { - return DriverData.AccelerationCurve.ComputeAccelerationDistance(DataBus.VehicleSpeed, targetSpeed); - } - - /// <summary> - /// Computes the time interval for driving the given distance ds with the vehicle's current speed and the given acceleration. - /// If the distance ds can not be reached (i.e., the vehicle would halt before ds is reached) then the distance parameter is adjusted. - /// Returns a new operating point (a, ds, dt) - /// </summary> - /// <param name="acceleration"></param> - /// <param name="ds"></param> - /// <returns>Operating point (a, ds, dt)</returns> - private OperatingPoint ComputeTimeInterval(MeterPerSquareSecond acceleration, Meter ds) - { - return VectoMath.ComputeTimeInterval(DataBus.VehicleSpeed, acceleration, DataBus.Distance, ds); - } - - /// <summary> - /// simulate a certain time interval where the vehicle is stopped. - /// </summary> - /// <param name="absTime"></param> - /// <param name="dt"></param> - /// <param name="targetVelocity"></param> - /// <param name="gradient"></param> - /// <returns></returns> - public IResponse DrivingActionHalt(Second absTime, Second dt, MeterPerSecond targetVelocity, Radian gradient) - { - CurrentAction = "HALT"; - if (!targetVelocity.IsEqual(0) || !DataBus.VehicleStopped) { - Log.Error("TargetVelocity ({0}) and VehicleVelocity ({1}) must be zero when vehicle is halting!", - targetVelocity, - DataBus.VehicleSpeed); - throw new VectoSimulationException( - "TargetVelocity ({0}) and VehicleVelocity ({1}) must be zero when vehicle is halting!", - targetVelocity, - DataBus.VehicleSpeed); - } - - DriverAcceleration = 0.SI<MeterPerSquareSecond>(); - var retVal = NextComponent.Request(absTime, dt, 0.SI<MeterPerSquareSecond>(), gradient); - - retVal.Switch(). - Case<ResponseGearShift>(r => { - DriverAcceleration = 0.SI<MeterPerSquareSecond>(); - retVal = NextComponent.Request(absTime, dt, 0.SI<MeterPerSquareSecond>(), gradient); - }); - CurrentState.dt = dt; - CurrentState.Acceleration = 0.SI<MeterPerSquareSecond>(); - return retVal; - } - - protected override void DoWriteModalResults(IModalDataContainer container) - { - container[ModalResultField.acc] = CurrentState.Acceleration; - container.SetDataValue("DriverAction", ActionToNumber(CurrentAction)); - } - - private int ActionToNumber(string currentAction) - { - switch (currentAction.ToUpper()) { - case "HALT": - return 0; - case "ROLL": - return 2; - case "COAST": - return 4; - case "ACCELERATE": - return 6; - case "BRAKE": - return -5; - default: - return -10; - } - } - - protected override void DoCommitSimulationStep() - { - if (!(CurrentState.Response is ResponseSuccess)) { - throw new VectoSimulationException("Previous request did not succeed!"); - } - CurrentState.Response = null; - } - - public class DriverState - { - // ReSharper disable once InconsistentNaming - public Second dt; - public MeterPerSquareSecond Acceleration; - public IResponse Response; - } - - [Flags] - protected enum LimitationMode - { - NoLimitation = 0x0, - LimitDecelerationDriver = 0x2, - //LimitDecelerationLookahead = 0x4 - } - - public DrivingBehavior DriverBehavior { get; set; } - - public MeterPerSquareSecond DriverAcceleration { get; protected set; } - } +using System; +using TUGraz.VectoCommon.Exceptions; +using TUGraz.VectoCommon.Models; +using TUGraz.VectoCommon.Utils; +using TUGraz.VectoCore.Configuration; +using TUGraz.VectoCore.Models.Connector.Ports; +using TUGraz.VectoCore.Models.Connector.Ports.Impl; +using TUGraz.VectoCore.Models.Simulation; +using TUGraz.VectoCore.Models.Simulation.Data; +using TUGraz.VectoCore.Models.Simulation.DataBus; +using TUGraz.VectoCore.OutputData; +using TUGraz.VectoCore.Utils; +using DriverData = TUGraz.VectoCore.Models.SimulationComponent.Data.DriverData; + +namespace TUGraz.VectoCore.Models.SimulationComponent.Impl +{ + public class Driver : + StatefulProviderComponent<Driver.DriverState, IDrivingCycleOutPort, IDriverDemandInPort, IDriverDemandOutPort>, + IDriver, IDrivingCycleOutPort, IDriverDemandInPort, IDriverActions, IDriverInfo + { + public DriverData DriverData { get; protected set; } + + protected readonly IDriverStrategy DriverStrategy; + public string CurrentAction = ""; + + public Driver(IVehicleContainer container, DriverData driverData, IDriverStrategy strategy) : base(container) + { + DriverData = driverData; + DriverStrategy = strategy; + strategy.Driver = this; + DriverAcceleration = 0.SI<MeterPerSquareSecond>(); + } + + public IResponse Initialize(MeterPerSecond vehicleSpeed, Radian roadGradient) + { + DriverBehavior = vehicleSpeed.IsEqual(0) ? DrivingBehavior.Halted : DrivingBehavior.Driving; + return NextComponent.Initialize(vehicleSpeed, roadGradient); + } + + public IResponse Initialize(MeterPerSecond vehicleSpeed, Radian roadGradient, + MeterPerSquareSecond startAcceleration) + { + DriverBehavior = vehicleSpeed.IsEqual(0) ? DrivingBehavior.Halted : DrivingBehavior.Driving; + var retVal = NextComponent.Initialize(vehicleSpeed, roadGradient, startAcceleration); + + return retVal; + } + + public IResponse Request(Second absTime, Meter ds, MeterPerSecond targetVelocity, Radian gradient) + { + IterationStatistics.Increment(this, "Requests"); + + Log.Debug("==== DRIVER Request (distance) ===="); + Log.Debug( + "Request: absTime: {0}, ds: {1}, targetVelocity: {2}, gradient: {3} | distance: {4}, velocity: {5}, vehicle stopped: {6}", + absTime, ds, targetVelocity, gradient, DataBus.Distance, DataBus.VehicleSpeed, DataBus.VehicleStopped); + + var retVal = DriverStrategy.Request(absTime, ds, targetVelocity, gradient); + + CurrentState.Response = retVal; + retVal.SimulationInterval = CurrentState.dt; + retVal.Acceleration = CurrentState.Acceleration; + + return retVal; + } + + public IResponse Request(Second absTime, Second dt, MeterPerSecond targetVelocity, Radian gradient) + { + IterationStatistics.Increment(this, "Requests"); + + Log.Debug("==== DRIVER Request (time) ===="); + Log.Debug( + "Request: absTime: {0}, dt: {1}, targetVelocity: {2}, gradient: {3} | distance: {4}, velocity: {5} gear: {6}: vehicle stopped: {7}", + absTime, dt, targetVelocity, gradient, DataBus.Distance, DataBus.VehicleSpeed, DataBus.Gear, + DataBus.VehicleStopped); + + var retVal = DriverStrategy.Request(absTime, dt, targetVelocity, gradient); + + CurrentState.Response = retVal; + retVal.SimulationInterval = CurrentState.dt; + retVal.Acceleration = CurrentState.Acceleration; + + return retVal; + } + + public new IDataBus DataBus + { + get { return base.DataBus; } + } + + /// <summary> + /// see documentation of IDriverActions + /// </summary> + /// <param name="absTime"></param> + /// <param name="ds"></param> + /// <param name="targetVelocity"></param> + /// <param name="gradient"></param> + /// <param name="previousResponse"></param> + /// <returns></returns> + public IResponse DrivingActionAccelerate(Second absTime, Meter ds, MeterPerSecond targetVelocity, + Radian gradient, + IResponse previousResponse = null) + { + CurrentAction = "ACCELERATE"; + IterationStatistics.Increment(this, "Accelerate"); + Log.Debug("DrivingAction Accelerate"); + var operatingPoint = ComputeAcceleration(ds, targetVelocity); + + IResponse retVal = null; + DriverAcceleration = operatingPoint.Acceleration; + var response = previousResponse ?? + NextComponent.Request(absTime, operatingPoint.SimulationInterval, operatingPoint.Acceleration, + gradient); + response.Acceleration = operatingPoint.Acceleration; + + response.Switch(). + Case<ResponseSuccess>(r => { + retVal = r; // => return + }). + Case<ResponseOverload>(). // do nothing, searchOperatingPoint is called later on + Case<ResponseEngineSpeedTooHigh>(). // do nothing, searchOperatingPoint is called later on + Case<ResponseUnderload>(r => { + // Delta is negative we are already below the Drag-load curve. activate brakes + retVal = r; // => return, strategy should brake + }). + Case<ResponseFailTimeInterval>(r => { + // occurs only with AT gearboxes - extend time interval after gearshift! + retVal = new ResponseDrivingCycleDistanceExceeded { + Source = this, + MaxDistance = r.Acceleration / 2 * r.DeltaT * r.DeltaT + DataBus.VehicleSpeed * r.DeltaT + }; + }). + Case<ResponseGearShift>(r => { + retVal = r; + }). + Default(r => { + throw new UnexpectedResponseException("DrivingAction Accelerate.", r); + }); + + if (retVal == null) { + // unhandled response (overload, delta > 0) - we need to search for a valid operating point.. + + OperatingPoint nextOperatingPoint; + try { + nextOperatingPoint = SearchOperatingPoint(absTime, ds, gradient, operatingPoint.Acceleration, + response); + } catch (VectoEngineSpeedTooLowException) { + // in case of an exception during search the engine-speed got too low - gear disengaged, try roll action. + nextOperatingPoint = SearchOperatingPoint(absTime, ds, gradient, operatingPoint.Acceleration, + response); + } + + var limitedOperatingPoint = nextOperatingPoint; + if (!(retVal is ResponseEngineSpeedTooHigh || DataBus.ClutchClosed(absTime))) { + limitedOperatingPoint = LimitAccelerationByDriverModel(nextOperatingPoint, + LimitationMode.LimitDecelerationDriver); + Log.Debug("Found operating point for Drive/Accelerate. dt: {0}, acceleration: {1}", + limitedOperatingPoint.SimulationInterval, limitedOperatingPoint.Acceleration); + } + DriverAcceleration = limitedOperatingPoint.Acceleration; + retVal = NextComponent.Request(absTime, limitedOperatingPoint.SimulationInterval, + limitedOperatingPoint.Acceleration, + gradient); + if (retVal != null) { + retVal.Acceleration = limitedOperatingPoint.Acceleration; + } + retVal.Switch(). + Case<ResponseUnderload>(() => operatingPoint = limitedOperatingPoint) + . // acceleration is limited by driver model, operating point moves below drag curve + Case<ResponseOverload>(() => { + // deceleration is limited by driver model, operating point moves above full load (e.g., steep uphill) + // the vehicle/driver can't achieve an acceleration higher than deceleration curve, try again with higher deceleration + if (DataBus.GearboxType.AutomaticTransmission()) { + Log.Info("AT Gearbox - Operating point resulted in an overload, searching again..."); + // search again for operating point, transmission may have shifted inbetween + nextOperatingPoint = SearchOperatingPoint(absTime, ds, gradient, operatingPoint.Acceleration, + response); + DriverAcceleration = nextOperatingPoint.Acceleration; + retVal = NextComponent.Request(absTime, nextOperatingPoint.SimulationInterval, + nextOperatingPoint.Acceleration, gradient); + } else { + if (absTime > 0 && DataBus.VehicleStopped) { + Log.Info( + "Operating point with limited acceleration resulted in an overload! Vehicle stopped! trying HALT action {0}", + nextOperatingPoint.Acceleration); + DataBus.BrakePower = 1.SI<Watt>(); + retVal = DrivingActionHalt(absTime, nextOperatingPoint.SimulationInterval, 0.SI<MeterPerSecond>(), gradient); + ds = 0.SI<Meter>(); + //retVal.Acceleration = 0.SI<MeterPerSquareSecond>(); + } else { + if (response is ResponseEngineSpeedTooHigh) { + Log.Info( + "Operating point with limited acceleration due to high engine speed resulted in an overload, searching again..."); + nextOperatingPoint = SearchOperatingPoint(absTime, ds, gradient, operatingPoint.Acceleration, + retVal); + DriverAcceleration = nextOperatingPoint.Acceleration; + retVal = NextComponent.Request(absTime, nextOperatingPoint.SimulationInterval, + nextOperatingPoint.Acceleration, gradient); + } else { + Log.Info( + "Operating point with limited acceleration resulted in an overload! trying again with original acceleration {0}", + nextOperatingPoint.Acceleration); + DriverAcceleration = nextOperatingPoint.Acceleration; + retVal = NextComponent.Request(absTime, nextOperatingPoint.SimulationInterval, + nextOperatingPoint.Acceleration, + gradient); + } + } + } + retVal.Switch(). + Case<ResponseSuccess>(() => operatingPoint = nextOperatingPoint). + Case<ResponseGearShift>(() => operatingPoint = nextOperatingPoint). + Default( + r => { + throw new UnexpectedResponseException("DrivingAction Accelerate after Overload", r); + }); + }). + Case<ResponseGearShift>(() => operatingPoint = limitedOperatingPoint). + Case<ResponseFailTimeInterval>(r => { + // occurs only with AT gearboxes - extend time interval after gearshift! + retVal = new ResponseDrivingCycleDistanceExceeded { + Source = this, + MaxDistance = r.Acceleration / 2 * r.DeltaT * r.DeltaT + DataBus.VehicleSpeed * r.DeltaT + }; + }). + Case<ResponseSuccess>(() => operatingPoint = limitedOperatingPoint). + Default( + r => { + throw new UnexpectedResponseException( + "DrivingAction Accelerate after SearchOperatingPoint.", r); + }); + } + CurrentState.Acceleration = operatingPoint.Acceleration; + CurrentState.dt = operatingPoint.SimulationInterval; + CurrentState.Response = retVal; + + retVal.Acceleration = operatingPoint.Acceleration; + retVal.SimulationInterval = operatingPoint.SimulationInterval; + retVal.SimulationDistance = ds; + retVal.OperatingPoint = operatingPoint; + + return retVal; + } + + /// <summary> + /// see documentation of IDriverActions + /// </summary> + /// <param name="absTime"></param> + /// <param name="ds"></param> + /// <param name="maxVelocity"></param> + /// <param name="gradient"></param> + /// <returns></returns> + public IResponse DrivingActionCoast(Second absTime, Meter ds, MeterPerSecond maxVelocity, Radian gradient) + { + CurrentAction = "COAST"; + IterationStatistics.Increment(this, "Coast"); + Log.Debug("DrivingAction Coast"); + + return CoastOrRollAction(absTime, ds, maxVelocity, gradient, false); + } + + /// <summary> + /// see documentation of IDriverActions + /// </summary> + /// <param name="absTime"></param> + /// <param name="ds"></param> + /// <param name="maxVelocity"></param> + /// <param name="gradient"></param> + /// <returns></returns> + public IResponse DrivingActionRoll(Second absTime, Meter ds, MeterPerSecond maxVelocity, Radian gradient) + { + CurrentAction = "ROLL"; + IterationStatistics.Increment(this, "Roll"); + + Log.Debug("DrivingAction Roll"); + + var retVal = CoastOrRollAction(absTime, ds, maxVelocity, gradient, true); + retVal.Switch(). + Case<ResponseGearShift>( + () => { + throw new UnexpectedResponseException("DrivingAction Roll: Gearshift during Roll action.", + retVal); + }); + + return retVal; + } + + /// <summary> + /// + /// </summary> + /// <param name="absTime"></param> + /// <param name="ds"></param> + /// <param name="maxVelocity"></param> + /// <param name="gradient"></param> + /// <param name="rollAction"></param> + /// <returns> + /// * ResponseSuccess + /// * ResponseDrivingCycleDistanceExceeded: vehicle is at low speed, coasting would lead to stop before ds is reached. + /// * ResponseSpeedLimitExceeded: vehicle accelerates during coasting which would lead to exceeding the given maxVelocity (e.g., driving downhill, engine's drag load is not sufficient) + /// * ResponseUnderload: engine's operating point is below drag curve (vehicle accelerates more than driver model allows; engine's drag load is not sufficient for limited acceleration + /// * ResponseGearShift: gearbox needs to shift gears, vehicle can not accelerate (traction interruption) + /// * ResponseFailTimeInterval: + /// </returns> + protected IResponse CoastOrRollAction(Second absTime, Meter ds, MeterPerSecond maxVelocity, Radian gradient, + bool rollAction) + { + var requestedOperatingPoint = ComputeAcceleration(ds, DataBus.VehicleSpeed); + DriverAcceleration = requestedOperatingPoint.Acceleration; + var initialResponse = NextComponent.Request(absTime, requestedOperatingPoint.SimulationInterval, + requestedOperatingPoint.Acceleration, gradient, dryRun: true); + + OperatingPoint searchedOperatingPoint; + try { + searchedOperatingPoint = SearchOperatingPoint(absTime, requestedOperatingPoint.SimulationDistance, + gradient, + requestedOperatingPoint.Acceleration, initialResponse, coastingOrRoll: true); + } catch (VectoEngineSpeedTooLowException) { + // in case of an exception during search the engine-speed got too low - gear disengaged --> try again with disengaged gear. + searchedOperatingPoint = SearchOperatingPoint(absTime, requestedOperatingPoint.SimulationDistance, + gradient, + requestedOperatingPoint.Acceleration, initialResponse, coastingOrRoll: true); + } + + if (!ds.IsEqual(searchedOperatingPoint.SimulationDistance)) { + // vehicle is at low speed, coasting would lead to stop before ds is reached: reduce simulated distance to stop distance. + Log.Debug( + "SearchOperatingPoint reduced the max. distance: {0} -> {1}. Issue new request from driving cycle!", + searchedOperatingPoint.SimulationDistance, ds); + CurrentState.Response = new ResponseDrivingCycleDistanceExceeded { + Source = this, + MaxDistance = searchedOperatingPoint.SimulationDistance, + Acceleration = searchedOperatingPoint.Acceleration, + SimulationInterval = searchedOperatingPoint.SimulationInterval, + OperatingPoint = searchedOperatingPoint + }; + return CurrentState.Response; + } + + Log.Debug("Found operating point for {2}. dt: {0}, acceleration: {1}", + searchedOperatingPoint.SimulationInterval, + searchedOperatingPoint.Acceleration, rollAction ? "ROLL" : "COAST"); + + var limitedOperatingPoint = LimitAccelerationByDriverModel(searchedOperatingPoint, + rollAction ? LimitationMode.NoLimitation : LimitationMode.LimitDecelerationDriver); + + // compute speed at the end of the simulation interval. if it exceeds the limit -> return + var v2 = DataBus.VehicleSpeed + + limitedOperatingPoint.Acceleration * limitedOperatingPoint.SimulationInterval; + if (v2 > maxVelocity && limitedOperatingPoint.Acceleration.IsGreaterOrEqual(0)) { + Log.Debug("vehicle's velocity would exceed given max speed. v2: {0}, max speed: {1}", v2, maxVelocity); + return new ResponseSpeedLimitExceeded() { Source = this }; + } + + DriverAcceleration = limitedOperatingPoint.Acceleration; + var response = NextComponent.Request(absTime, limitedOperatingPoint.SimulationInterval, + limitedOperatingPoint.Acceleration, gradient); + + response.SimulationInterval = limitedOperatingPoint.SimulationInterval; + response.SimulationDistance = ds; + response.Acceleration = limitedOperatingPoint.Acceleration; + response.OperatingPoint = limitedOperatingPoint; + + response.Switch(). + Case<ResponseSuccess>(). + Case<ResponseUnderload>(). // driver limits acceleration, operating point may be below engine's + //drag load resp. below 0 + Case<ResponseOverload>(). // driver limits acceleration, operating point may be above 0 (GBX), use brakes + Case<ResponseEngineSpeedTooHigh>(). // reduce acceleration/vehicle speed + Case<ResponseGearShift>(). + Case<ResponseFailTimeInterval>(r => { + response = new ResponseDrivingCycleDistanceExceeded { + Source = this, + MaxDistance = r.Acceleration / 2 * r.DeltaT * r.DeltaT + DataBus.VehicleSpeed * r.DeltaT + }; + }). + Default( + () => { + throw new UnexpectedResponseException( + "CoastOrRoll Action: unhandled response from powertrain.", response); + }); + + CurrentState.Response = response; + CurrentState.Acceleration = response.Acceleration; + CurrentState.dt = response.SimulationInterval; + return response; + } + + public IResponse DrivingActionBrake(Second absTime, Meter ds, MeterPerSecond nextTargetSpeed, Radian gradient, + IResponse previousResponse = null, Meter targetDistance = null) + { + CurrentAction = "BRAKE"; + IterationStatistics.Increment(this, "Brake"); + Log.Debug("DrivingAction Brake"); + + IResponse retVal = null; + + var operatingPoint = ComputeAcceleration(ds, nextTargetSpeed); + + //if (operatingPoint.Acceleration.IsSmaller(0)) { + operatingPoint = IncreaseDecelerationToMaxWithinSpeedRange(operatingPoint); + + operatingPoint = + AdaptDecelerationToTargetDistance(ds, nextTargetSpeed, targetDistance, operatingPoint.Acceleration) ?? + operatingPoint; + + DriverAcceleration = operatingPoint.Acceleration; + var response = previousResponse ?? + NextComponent.Request(absTime, operatingPoint.SimulationInterval, operatingPoint.Acceleration, + gradient); + + var point = operatingPoint; + response.Switch(). + Case<ResponseSuccess>(r => retVal = r). + Case<ResponseOverload>(r => retVal = r) + . // i.e., driving uphill, clutch open, deceleration higher than desired deceleration + Case<ResponseUnderload>(). // will be handled in SearchBrakingPower + Case<ResponseEngineSpeedTooHigh>(r => { + Log.Debug("Engine speeed was too high, search for appropriate acceleration first."); + operatingPoint = SearchOperatingPoint(absTime, ds, gradient, point.Acceleration, + response); + }). // will be handled in SearchBrakingPower + Case<ResponseGearShift>(). // will be handled in SearchBrakingPower + Case<ResponseFailTimeInterval>(r => + retVal = new ResponseDrivingCycleDistanceExceeded() { + Source = this, + MaxDistance = DataBus.VehicleSpeed * r.DeltaT + point.Acceleration / 2 * r.DeltaT * r.DeltaT + }). + Default(r => { + throw new UnexpectedResponseException("DrivingAction Brake: first request.", r); + }); + + if (retVal != null) { + CurrentState.Acceleration = operatingPoint.Acceleration; + CurrentState.dt = operatingPoint.SimulationInterval; + CurrentState.Response = retVal; + retVal.Acceleration = operatingPoint.Acceleration; + retVal.SimulationInterval = operatingPoint.SimulationInterval; + retVal.SimulationDistance = ds; + retVal.OperatingPoint = operatingPoint; + return retVal; + } + + operatingPoint = SearchBrakingPower(absTime, operatingPoint.SimulationDistance, gradient, + operatingPoint.Acceleration, response); + + if (!ds.IsEqual(operatingPoint.SimulationDistance, 1E-15.SI<Meter>())) { + Log.Info( + "SearchOperatingPoint Braking reduced the max. distance: {0} -> {1}. Issue new request from driving cycle!", + operatingPoint.SimulationDistance, ds); + return new ResponseDrivingCycleDistanceExceeded { + Source = this, + MaxDistance = operatingPoint.SimulationDistance + }; + } + + Log.Debug("Found operating point for braking. dt: {0}, acceleration: {1} brakingPower: {2}", + operatingPoint.SimulationInterval, + operatingPoint.Acceleration, DataBus.BrakePower); + if (DataBus.BrakePower < 0) { + var overload = new ResponseOverload { + Source = this, + BrakePower = DataBus.BrakePower, + Acceleration = operatingPoint.Acceleration + }; + DataBus.BrakePower = 0.SI<Watt>(); + return overload; + } + + DriverAcceleration = operatingPoint.Acceleration; + retVal = NextComponent.Request(absTime, operatingPoint.SimulationInterval, operatingPoint.Acceleration, + gradient); + + retVal.Switch(). + Case<ResponseSuccess>(). + Case<ResponseGearShift>(). + Case<ResponseFailTimeInterval>(r => + retVal = new ResponseDrivingCycleDistanceExceeded() { + Source = this, + MaxDistance = + DataBus.VehicleSpeed * r.DeltaT + operatingPoint.Acceleration / 2 * r.DeltaT * r.DeltaT + }). + Case<ResponseUnderload>(r => { + if (DataBus.GearboxType.AutomaticTransmission()) { + operatingPoint = SearchBrakingPower(absTime, operatingPoint.SimulationDistance, gradient, + operatingPoint.Acceleration, response); + DriverAcceleration = operatingPoint.Acceleration; + retVal = NextComponent.Request(absTime, operatingPoint.SimulationInterval, + operatingPoint.Acceleration, gradient); + } + }). + Case<ResponseOverload>(r => { + if (DataBus.GearboxType.AutomaticTransmission()) { + // overload may happen because of gearshift between search and actual request, search again + var i = 5; + while (i-- > 0 && !(retVal is ResponseSuccess)) { + DataBus.BrakePower = 0.SI<Watt>(); + operatingPoint = SearchBrakingPower(absTime, operatingPoint.SimulationDistance, gradient, + operatingPoint.Acceleration, response); + DriverAcceleration = operatingPoint.Acceleration; + if (DataBus.BrakePower.IsSmaller(0)) { + DataBus.BrakePower = 0.SI<Watt>(); + + operatingPoint = SearchOperatingPoint(absTime, ds, gradient, 0.SI<MeterPerSquareSecond>(), r); + } + retVal = NextComponent.Request(absTime, operatingPoint.SimulationInterval, + operatingPoint.Acceleration, gradient); + } + } else { + throw new UnexpectedResponseException( + "DrivingAction Brake: request failed after braking power was found.", r); + } + }). + Default( + r => { + throw new UnexpectedResponseException( + "DrivingAction Brake: request failed after braking power was found.", r); + }); + CurrentState.Acceleration = operatingPoint.Acceleration; + CurrentState.dt = operatingPoint.SimulationInterval; + CurrentState.Response = retVal; + retVal.Acceleration = operatingPoint.Acceleration; + retVal.SimulationInterval = operatingPoint.SimulationInterval; + retVal.SimulationDistance = ds; + retVal.OperatingPoint = operatingPoint; + + return retVal; + } + + private OperatingPoint AdaptDecelerationToTargetDistance(Meter ds, MeterPerSecond nextTargetSpeed, + Meter targetDistance, MeterPerSquareSecond acceleration) + { + if (targetDistance != null && targetDistance > DataBus.Distance) { + var tmp = ComputeAcceleration(targetDistance - DataBus.Distance, nextTargetSpeed, false); + if (tmp.Acceleration.IsGreater(acceleration)) { + var operatingPoint = ComputeTimeInterval(tmp.Acceleration, ds); + if (!ds.IsEqual(operatingPoint.SimulationDistance)) { + Log.Error( + "Unexpected Condition: Distance has been adjusted from {0} to {1}, currentVelocity: {2} acceleration: {3}, targetVelocity: {4}", + operatingPoint.SimulationDistance, ds, DataBus.VehicleSpeed, operatingPoint.Acceleration, + nextTargetSpeed); + throw new VectoSimulationException("Simulation distance unexpectedly adjusted! {0} -> {1}", ds, + operatingPoint.SimulationDistance); + } + return operatingPoint; + } + } + return null; + } + + private OperatingPoint IncreaseDecelerationToMaxWithinSpeedRange(OperatingPoint operatingPoint) + { + // if we should brake with the max. deceleration and the deceleration changes within the current interval, take the larger deceleration... + if ( + operatingPoint.Acceleration.IsEqual( + DriverData.AccelerationCurve.Lookup(DataBus.VehicleSpeed).Deceleration)) { + var v2 = DataBus.VehicleSpeed + operatingPoint.Acceleration * operatingPoint.SimulationInterval; + var nextAcceleration = DriverData.AccelerationCurve.Lookup(v2).Deceleration; + var tmp = ComputeTimeInterval(VectoMath.Min(operatingPoint.Acceleration, nextAcceleration), + operatingPoint.SimulationDistance); + if (!operatingPoint.Acceleration.IsEqual(nextAcceleration) && + operatingPoint.SimulationDistance.IsEqual(tmp.SimulationDistance)) { + // only adjust operating point if the acceleration is different but the simulation distance is not modified + // i.e., braking to the next sample point (but a little bit slower) + Log.Debug("adjusting acceleration from {0} to {1}", operatingPoint.Acceleration, tmp.Acceleration); + operatingPoint = tmp; + // ComputeTimeInterval((operatingPoint.Acceleration + tmp.Acceleration) / 2, operatingPoint.SimulationDistance); + } + } + return operatingPoint; + } + + // ================================================ + + /// <summary> + /// + /// </summary> + /// <param name="operatingPoint"></param> + /// <param name="limits"></param> + /// <returns></returns> + private OperatingPoint LimitAccelerationByDriverModel(OperatingPoint operatingPoint, + LimitationMode limits) + { + var limitApplied = false; + var originalAcceleration = operatingPoint.Acceleration; + //if (((limits & LimitationMode.LimitDecelerationLookahead) != 0) && + // operatingPoint.Acceleration < DriverData.LookAheadCoasting.Deceleration) { + // operatingPoint.Acceleration = DriverData.LookAheadCoasting.Deceleration; + // limitApplied = true; + //} + var accelerationLimits = DriverData.AccelerationCurve.Lookup(DataBus.VehicleSpeed); + if (operatingPoint.Acceleration > accelerationLimits.Acceleration) { + operatingPoint.Acceleration = accelerationLimits.Acceleration; + limitApplied = true; + } + if (((limits & LimitationMode.LimitDecelerationDriver) != 0) && + operatingPoint.Acceleration < accelerationLimits.Deceleration) { + operatingPoint.Acceleration = accelerationLimits.Deceleration; + limitApplied = true; + } + if (limitApplied) { + operatingPoint.SimulationInterval = + ComputeTimeInterval(operatingPoint.Acceleration, operatingPoint.SimulationDistance) + .SimulationInterval; + Log.Debug("Limiting acceleration from {0} to {1}, dt: {2}", originalAcceleration, + operatingPoint.Acceleration, operatingPoint.SimulationInterval); + } + return operatingPoint; + } + + /// <summary> + /// Performs a search for the required braking power such that the vehicle accelerates with the given acceleration. + /// Returns a new operating point (a, ds, dt) where ds may be shorter due to vehicle stopping + /// </summary> + /// <returns>operating point (a, ds, dt) such that the vehicle accelerates with the given acceleration.</returns> + private OperatingPoint SearchBrakingPower(Second absTime, Meter ds, Radian gradient, + MeterPerSquareSecond acceleration, IResponse initialResponse) + { + IterationStatistics.Increment(this, "SearchBrakingPower", 0); + + var operatingPoint = new OperatingPoint { SimulationDistance = ds, Acceleration = acceleration }; + operatingPoint = ComputeTimeInterval(operatingPoint.Acceleration, ds); + Watt deltaPower = null; + initialResponse.Switch(). + Case<ResponseGearShift>(r => { + IterationStatistics.Increment(this, "SearchBrakingPower"); + DriverAcceleration = operatingPoint.Acceleration; + var nextResp = NextComponent.Request(absTime, operatingPoint.SimulationInterval, + operatingPoint.Acceleration, + gradient, true); + deltaPower = nextResp.GearboxPowerRequest; + }). + Case<ResponseEngineSpeedTooHigh>(r => { + IterationStatistics.Increment(this, "SearchBrakingPower"); + DriverAcceleration = operatingPoint.Acceleration; + var nextResp = NextComponent.Request(absTime, operatingPoint.SimulationInterval, + operatingPoint.Acceleration, + gradient, true); + deltaPower = nextResp.GearboxPowerRequest; + }). + Case<ResponseUnderload>(r => + deltaPower = DataBus.ClutchClosed(absTime) ? r.Delta : r.GearboxPowerRequest). + Default( + r => { + throw new UnexpectedResponseException("cannot use response for searching braking power!", r); + }); + + try { + DataBus.BrakePower = SearchAlgorithm.Search(DataBus.BrakePower, deltaPower, + deltaPower.Abs() * (DataBus.GearboxType.AutomaticTransmission() ? 0.5 : 1), + getYValue: result => { + var response = (ResponseDryRun)result; + return DataBus.ClutchClosed(absTime) ? response.DeltaDragLoad : response.GearboxPowerRequest; + }, + evaluateFunction: x => { + DataBus.BrakePower = x; + operatingPoint = ComputeTimeInterval(operatingPoint.Acceleration, ds); + + IterationStatistics.Increment(this, "SearchBrakingPower"); + DriverAcceleration = operatingPoint.Acceleration; + return NextComponent.Request(absTime, operatingPoint.SimulationInterval, + operatingPoint.Acceleration, gradient, + true); + }, + criterion: result => { + var response = (ResponseDryRun)result; + var delta = DataBus.ClutchClosed(absTime) + ? response.DeltaDragLoad + : response.GearboxPowerRequest; + return delta.Value(); + }); + + return operatingPoint; + } catch (Exception) { + Log.Error("Failed to find operating point for braking power! absTime: {0}", absTime); + throw; + } + } + + protected OperatingPoint SearchOperatingPoint(Second absTime, Meter ds, Radian gradient, + MeterPerSquareSecond acceleration, IResponse initialResponse, bool coastingOrRoll = false) + { + IterationStatistics.Increment(this, "SearchOperatingPoint", 0); + + var retVal = new OperatingPoint { Acceleration = acceleration, SimulationDistance = ds }; + + var actionRoll = !DataBus.ClutchClosed(absTime); + var searchEngineSpeed = false; + + Watt origDelta = null; + if (actionRoll) { + initialResponse.Switch(). + Case<ResponseDryRun>(r => origDelta = r.GearboxPowerRequest). + Case<ResponseFailTimeInterval>(r => origDelta = r.GearboxPowerRequest). + Default(r => { + throw new UnexpectedResponseException("SearchOperatingPoint: Unknown response type.", r); + }); + } else { + initialResponse.Switch(). + Case<ResponseOverload>(r => origDelta = r.Delta). + Case<ResponseEngineSpeedTooHigh>(r => { + searchEngineSpeed = true; + origDelta = r.DeltaEngineSpeed * 1.SI<NewtonMeter>(); + }). // search operating point in drive action after overload + Case<ResponseDryRun>(r => origDelta = coastingOrRoll ? r.DeltaDragLoad : r.DeltaFullLoad). + Default(r => { + throw new UnexpectedResponseException("SearchOperatingPoint: Unknown response type.", r); + }); + } + var delta = origDelta; + try { + retVal.Acceleration = SearchAlgorithm.Search(acceleration, delta, + Constants.SimulationSettings.OperatingPointInitialSearchIntervalAccelerating, + getYValue: response => { + var r = (ResponseDryRun)response; + if (searchEngineSpeed) { + return r.DeltaEngineSpeed * 1.SI<NewtonMeter>(); + } + return actionRoll ? r.GearboxPowerRequest : (coastingOrRoll ? r.DeltaDragLoad : r.DeltaFullLoad); + }, + evaluateFunction: + acc => { + // calculate new time interval only when vehiclespeed and acceleration are != 0 + // else: use same timeinterval as before. + if (!(acc.IsEqual(0) && DataBus.VehicleSpeed.IsEqual(0))) { + var tmp = ComputeTimeInterval(acc, ds); + if (tmp.SimulationInterval.IsEqual(0.SI<Second>(), 1e-9.SI<Second>())) { + throw new VectoSearchAbortedException( + "next TimeInterval is 0. a: {0}, v: {1}, dt: {2}", acc, + DataBus.VehicleSpeed, tmp.SimulationInterval); + } + retVal.Acceleration = tmp.Acceleration; + retVal.SimulationInterval = tmp.SimulationInterval; + retVal.SimulationDistance = tmp.SimulationDistance; + } + IterationStatistics.Increment(this, "SearchOperatingPoint"); + DriverAcceleration = acc; + var response = NextComponent.Request(absTime, retVal.SimulationInterval, acc, gradient, true); + response.OperatingPoint = retVal; + return response; + }, + criterion: response => { + var r = (ResponseDryRun)response; + if (searchEngineSpeed) { + return r.DeltaEngineSpeed.Value(); + } + delta = actionRoll + ? r.GearboxPowerRequest + : (coastingOrRoll ? r.DeltaDragLoad : r.DeltaFullLoad); + return delta.Value(); + }, + abortCriterion: + (response, cnt) => { + var r = (ResponseDryRun)response; + if (r == null) { + return false; + } + + return !actionRoll && !ds.IsEqual(r.OperatingPoint.SimulationDistance); + }); + } catch (VectoSearchAbortedException) { + // search aborted, try to go ahead with the last acceleration + } catch (Exception) { + Log.Error("Failed to find operating point! absTime: {0}", absTime); + throw; + } + + if (!retVal.Acceleration.IsBetween(DriverData.AccelerationCurve.MaxDeceleration(), + DriverData.AccelerationCurve.MaxAcceleration())) { + Log.Info("Operating Point outside driver acceleration limits: a: {0}", retVal.Acceleration); + } + return ComputeTimeInterval(retVal.Acceleration, retVal.SimulationDistance); + } + + /// <summary> + /// compute the acceleration and time-interval such that the vehicle's velocity approaches the given target velocity + /// - first compute the acceleration to reach the targetVelocity within the given distance + /// - limit the acceleration/deceleration by the driver's acceleration curve + /// - compute the time interval required to drive the given distance with the computed acceleration + /// computed acceleration and time interval are stored in CurrentState! + /// </summary> + /// <param name="ds">distance to reach the next target speed</param> + /// <param name="targetVelocity">next vehicle speed to decelerate to</param> + /// <param name="limitByDriverModel">if set to false the required acceleration will be computed, regardless of the driver's acceleration curve</param> + public OperatingPoint ComputeAcceleration(Meter ds, MeterPerSecond targetVelocity, + bool limitByDriverModel = true) + { + var currentSpeed = DataBus.VehicleSpeed; + var retVal = new OperatingPoint() { SimulationDistance = ds }; + + // Δx = (v0+v1)/2 * Δt + // => Δt = 2*Δx/(v0+v1) + var dt = 2 * ds / (currentSpeed + targetVelocity); + + // a = Δv / Δt + var requiredAcceleration = (targetVelocity - currentSpeed) / dt; + + if (!limitByDriverModel) { + return ComputeTimeInterval(requiredAcceleration, ds); + } + + var maxAcceleration = DriverData.AccelerationCurve.Lookup(currentSpeed); + + if (requiredAcceleration > maxAcceleration.Acceleration) { + requiredAcceleration = maxAcceleration.Acceleration; + } + if (requiredAcceleration < maxAcceleration.Deceleration) { + requiredAcceleration = maxAcceleration.Deceleration; + } + + retVal.Acceleration = requiredAcceleration; + retVal = ComputeTimeInterval(retVal.Acceleration, ds); + + if (ds.IsEqual(retVal.SimulationDistance)) { + return retVal; + } + + // this case should not happen, acceleration has been computed such that the target speed + // can be reached within ds. + Log.Error( + "Unexpected Condition: Distance has been adjusted from {0} to {1}, currentVelocity: {2} acceleration: {3}, targetVelocity: {4}", + retVal.SimulationDistance, ds, currentSpeed, CurrentState.Acceleration, targetVelocity); + throw new VectoSimulationException("Simulation distance unexpectedly adjusted! {0} -> {1}", ds, + retVal.SimulationDistance); + } + + /// <summary> + /// computes the distance required to decelerate from the current velocity to the given target velocity considering + /// the drivers acceleration/deceleration curve. + /// </summary> + /// <param name="targetSpeed"></param> + /// <returns></returns> + public Meter ComputeDecelerationDistance(MeterPerSecond targetSpeed) + { + return DriverData.AccelerationCurve.ComputeAccelerationDistance(DataBus.VehicleSpeed, targetSpeed); + } + + /// <summary> + /// Computes the time interval for driving the given distance ds with the vehicle's current speed and the given acceleration. + /// If the distance ds can not be reached (i.e., the vehicle would halt before ds is reached) then the distance parameter is adjusted. + /// Returns a new operating point (a, ds, dt) + /// </summary> + /// <param name="acceleration"></param> + /// <param name="ds"></param> + /// <returns>Operating point (a, ds, dt)</returns> + private OperatingPoint ComputeTimeInterval(MeterPerSquareSecond acceleration, Meter ds) + { + return VectoMath.ComputeTimeInterval(DataBus.VehicleSpeed, acceleration, DataBus.Distance, ds); + } + + /// <summary> + /// simulate a certain time interval where the vehicle is stopped. + /// </summary> + /// <param name="absTime"></param> + /// <param name="dt"></param> + /// <param name="targetVelocity"></param> + /// <param name="gradient"></param> + /// <returns></returns> + public IResponse DrivingActionHalt(Second absTime, Second dt, MeterPerSecond targetVelocity, Radian gradient) + { + CurrentAction = "HALT"; + if (!targetVelocity.IsEqual(0) || !DataBus.VehicleStopped) { + Log.Error("TargetVelocity ({0}) and VehicleVelocity ({1}) must be zero when vehicle is halting!", + targetVelocity, + DataBus.VehicleSpeed); + throw new VectoSimulationException( + "TargetVelocity ({0}) and VehicleVelocity ({1}) must be zero when vehicle is halting!", + targetVelocity, + DataBus.VehicleSpeed); + } + + DriverAcceleration = 0.SI<MeterPerSquareSecond>(); + var retVal = NextComponent.Request(absTime, dt, 0.SI<MeterPerSquareSecond>(), gradient); + + retVal.Switch(). + Case<ResponseGearShift>(r => { + DriverAcceleration = 0.SI<MeterPerSquareSecond>(); + retVal = NextComponent.Request(absTime, dt, 0.SI<MeterPerSquareSecond>(), gradient); + }); + CurrentState.dt = dt; + CurrentState.Acceleration = 0.SI<MeterPerSquareSecond>(); + return retVal; + } + + protected override void DoWriteModalResults(IModalDataContainer container) + { + container[ModalResultField.acc] = CurrentState.Acceleration; + container.SetDataValue("DriverAction", ActionToNumber(CurrentAction)); + } + + private int ActionToNumber(string currentAction) + { + switch (currentAction.ToUpper()) { + case "HALT": + return 0; + case "ROLL": + return 2; + case "COAST": + return 4; + case "ACCELERATE": + return 6; + case "BRAKE": + return -5; + default: + return -10; + } + } + + protected override void DoCommitSimulationStep() + { + if (!(CurrentState.Response is ResponseSuccess)) { + throw new VectoSimulationException("Previous request did not succeed!"); + } + CurrentState.Response = null; + } + + public class DriverState + { + // ReSharper disable once InconsistentNaming + public Second dt; + public MeterPerSquareSecond Acceleration; + public IResponse Response; + } + + [Flags] + protected enum LimitationMode + { + NoLimitation = 0x0, + LimitDecelerationDriver = 0x2, + //LimitDecelerationLookahead = 0x4 + } + + public DrivingBehavior DriverBehavior { get; set; } + + public MeterPerSquareSecond DriverAcceleration { get; protected set; } + } } \ No newline at end of file diff --git a/VectoCore/VectoCore/Utils/DelaunayMap.cs b/VectoCore/VectoCore/Utils/DelaunayMap.cs index 57a7739e6136db7e0ccba3d683fc4c3f74c18bd6..c91dc714f38f3d18b0f5b3c2a1f61135855e9c6e 100644 --- a/VectoCore/VectoCore/Utils/DelaunayMap.cs +++ b/VectoCore/VectoCore/Utils/DelaunayMap.cs @@ -29,315 +29,316 @@ * Martin Rexeis, rexeis@ivt.tugraz.at, IVT, Graz University of Technology */ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Drawing; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Windows.Forms.DataVisualization.Charting; -using TUGraz.VectoCommon.Exceptions; -using TUGraz.VectoCommon.Models; -using TUGraz.VectoCommon.Utils; -using Point = TUGraz.VectoCommon.Utils.Point; - -namespace TUGraz.VectoCore.Utils -{ - public sealed class DelaunayMap : LoggingObject - { - private ICollection<Point> _points = new HashSet<Point>(); - private Triangle[] _triangles; - private Edge[] _convexHull; - - private readonly string _mapName; - private double _minY; - private double _minX; - private double _maxY; - private double _maxX; - - public DelaunayMap(string name) - { - _mapName = name; - } - - public void AddPoint(double x, double y, double z) - { - _points.Add(new Point(x, y, z)); - } - - public IReadOnlyCollection<Point> Entries - { - get { - var retVal = new Point[_points.Count]; - var i = 0; - foreach (var pt in _points) { - retVal[i++] = new Point(pt.X * (_maxX - _minX) + _minX, pt.Y * (_maxY - _minY) + _minY, pt.Z); - } - return retVal; - } - } - - /// <summary> - /// Triangulate the points. - /// </summary> - /// <remarks> - /// Triangulation with the Bowyer-Watson algorithm (iteratively insert points into a super triangle). - /// https://en.wikipedia.org/wiki/Bowyer%E2%80%93Watson_algorithm - /// </remarks> - public void Triangulate() - { - if (_points.Count < 3) { - throw new ArgumentException(string.Format("{0}: Triangulation needs at least 3 Points. Got {1} Points.", _mapName, - _points.Count)); - } - - SanitycheckInputPoints(); - - // The "supertriangle" encompasses all triangulation points. - // This is just a helper triangle which initializes the algorithm and will be removed in the end of the algorithm. - _maxX = _points.Max(p => p.X); - _maxY = _points.Max(p => p.Y); - _minX = _points.Min(p => p.X); - _minY = _points.Min(p => p.Y); - _points = - _points.Select(p => new Point((p.X - _minX) / (_maxX - _minX), (p.Y - _minY) / (_maxY - _minY), p.Z)).ToList(); - var superTriangle = new Triangle(new Point(-1, -1), new Point(4, -1), new Point(-1, 4)); - var triangles = new List<Triangle> { superTriangle }; - - var pointCount = 0; - - var points = _points.ToArray(); - - // iteratively add each point into the correct triangle and split up the triangle - foreach (var point in points) { - // If the vertex lies inside the circumcircle of a triangle, the edges of this triangle are - // added to the edge buffer and the triangle is removed from list. - // Remove duplicate edges. This leaves the convex hull of the edges. - // The edges in this convex hull are oriented counterclockwise! - - var newTriangles = triangles.Select((t, i) => Tuple.Create(i, t, t.ContainsInCircumcircle(point))) - .Where(t => t.Item3) - .Reverse() - .SelectMany(t => { - triangles.RemoveAt(t.Item1); - return t.Item2.GetEdges(); - }) - .GroupBy(edge => edge) - .Where(group => group.Count() == 1) - .Select(group => new Triangle(group.Key.P1, group.Key.P2, point)).ToList(); - - triangles.AddRange(newTriangles); - - //DrawGraph(pointCount, triangles, superTriangle, xmin, xmax, ymin, ymax, point); - pointCount++; - - // check invariant: m = 2n-2-k - // m...triangle count - // n...point count (pointCount +3 points on the supertriangle) - // k...points on convex hull (exactly 3 --> supertriangle) - if (triangles.Count != 2 * (pointCount + 3) - 2 - 3) { - throw new VectoException( - "Delaunay-Triangulation invariant violated! Triangle count and point count doesn't fit together."); - } - } - -#if TRACE - DrawGraph(pointCount, triangles, superTriangle, points); -#endif - _convexHull = triangles.FindAll(t => t.SharesVertexWith(superTriangle)). - SelectMany(t => t.GetEdges()). - Where(e => !(superTriangle.Contains(e.P1) || superTriangle.Contains(e.P2))).ToArray(); - - _triangles = triangles.FindAll(t => !t.SharesVertexWith(superTriangle)).ToArray(); - } - - private void SanitycheckInputPoints() - { - var duplicates = _points.GroupBy(pt => new { pt.X, pt.Y }, x => x).Where(g => g.Count() > 1).ToList(); - - foreach (var duplicate in duplicates) { - Log.Error("{0}: Input Point appears twice: x: {1}, y: {2}", duplicate.Key.X, duplicate.Key.Y); - } - if (duplicates.Any()) { - throw new VectoException("{0}: Input Data for Delaunay map contains duplicates! \n{1}", _mapName, - string.Join("\n", duplicates.Select(pt => string.Format("{0} / {1}", pt.Key.X, pt.Key.Y)))); - } - } - - public void DrawGraph() - { - var superTriangle = new Triangle(new Point(-1, -1), new Point(4, -1), new Point(-1, 4)); - DrawGraph(0, _triangles, superTriangle, _points.ToArray()); - } - - /// <summary> - /// Draws the delaunay map (except supertriangle). - /// </summary> - private static void DrawGraph(int i, IEnumerable<Triangle> triangles, Triangle superTriangle, Point[] points, - Point lastPoint = null) - { - var xmin = Math.Min(points.Min(p => p.X), lastPoint != null ? lastPoint.X : double.NaN); - var xmax = Math.Max(points.Max(p => p.X), lastPoint != null ? lastPoint.X : double.NaN); - var ymin = Math.Min(points.Min(p => p.Y), lastPoint != null ? lastPoint.Y : double.NaN); - var ymax = Math.Max(points.Max(p => p.Y), lastPoint != null ? lastPoint.Y : double.NaN); - - using (var chart = new Chart { Width = 1000, Height = 1000 }) { - chart.ChartAreas.Add(new ChartArea("main") { - AxisX = new Axis { Minimum = Math.Min(xmin, xmin), Maximum = Math.Max(xmax, xmax) }, - AxisY = new Axis { Minimum = Math.Min(ymin, ymin), Maximum = Math.Max(ymax, ymax) } - }); - - foreach (var tr in triangles) { - if (tr.SharesVertexWith(superTriangle)) { - continue; - } - - var series = new Series { - ChartType = SeriesChartType.FastLine, - Color = lastPoint != null && tr.Contains(lastPoint) ? Color.Red : Color.Blue - }; - series.Points.AddXY(tr.P1.X, tr.P1.Y); - series.Points.AddXY(tr.P2.X, tr.P2.Y); - series.Points.AddXY(tr.P3.X, tr.P3.Y); - series.Points.AddXY(tr.P1.X, tr.P1.Y); - chart.Series.Add(series); - } - - if (lastPoint != null) { - var series = new Series { - ChartType = SeriesChartType.Point, - Color = Color.Red, - MarkerSize = 5, - MarkerStyle = MarkerStyle.Circle - }; - series.Points.AddXY(lastPoint.X, lastPoint.Y); - chart.Series.Add(series); - } - - var frame = new StackFrame(2); - var method = frame.GetMethod(); - System.Diagnostics.Debug.Assert(method.DeclaringType != null, "method.DeclaringType != null"); - var type = string.Join("", method.DeclaringType.Name.Split(Path.GetInvalidFileNameChars())); - var methodName = string.Join("", method.Name.Split(Path.GetInvalidFileNameChars())); - Directory.CreateDirectory("delaunay"); - chart.SaveImage(string.Format("delaunay\\{0}_{1}_{2}_{3}.png", type, methodName, superTriangle.GetHashCode(), i), - ChartImageFormat.Png); - } - } - - public double? Interpolate(SI x, SI y) - { - return Interpolate(x.Value(), y.Value()); - } - - /// <summary> - /// Interpolates the value of an point in the delaunay map. - /// </summary> - /// <param name="x"></param> - /// <param name="y"></param> - /// <returns>a value if interpolation is successfull, - /// null if interpolation has failed.</returns> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double? Interpolate(double x, double y) - { - if (_triangles == null) { - throw new VectoException("Interpolation not possible. Call DelaunayMap.Triangulate first."); - } - - x = (x - _minX) / (_maxX - _minX); - y = (y - _minY) / (_maxY - _minY); - - var i = 0; - while (i < _triangles.Length && !_triangles[i].IsInside(x, y, true)) { - i++; - } - if (i == _triangles.Length) { - i = 0; - while (i < _triangles.Length && !_triangles[i].IsInside(x, y, false)) { - i++; - } - } - - if (i == _triangles.Length) { - return null; - } - - var tr = _triangles[i]; - var plane = new Plane(tr); - return (plane.W - plane.X * x - plane.Y * y) / plane.Z; - } - - public double Extrapolate(SI x, SI y) - { - return Extrapolate(x.Value(), y.Value()); - } - - /// <summary> - /// Extrapolates the value of an point on the edges of a delaunay map. - /// </summary> - /// <param name="x"></param> - /// <param name="y"></param> - /// <returns></returns> - public double Extrapolate(double x, double y) - { - x = (x - _minX) / (_maxX - _minX); - y = (y - _minY) / (_maxY - _minY); - var point = new Point(x, y); - - // get nearest point on convex hull - var nearestPoint = _convexHull.Select(e => e.P1).MinBy(p => Math.Pow(p.X - x, 2) + Math.Pow(p.Y - y, 2)); - - // test if point is on left side of the perpendicular vector (to x,y coordinates) of edge1 in the nearest point - // ^ - // (point) | - // | - // (p1)--edge1-->(nearestPoint) - var edge1 = _convexHull.First(e => e.P2.Equals(nearestPoint)); - if (point.IsLeftOf(new Edge(nearestPoint, edge1.Vector.Perpendicular() + nearestPoint))) { - return ExtrapolateOnEdge(x, y, edge1); - } - - // test if point is on right side of the perpendicular vector of edge2 in the nearest point - // ^ - // | (point) - // | - // (nearestPoint)--edge2-->(p2) - var edge2 = _convexHull.First(e => e.P1.Equals(nearestPoint)); - if (!point.IsLeftOf(new Edge(nearestPoint, edge2.Vector.Perpendicular() + nearestPoint))) { - return ExtrapolateOnEdge(x, y, edge2); - } - - // if point is right of perpendicular vector of edge1 and left of perpendicular vector of edge2: take the nearest point z-value - return nearestPoint.Z; - } - - /// <summary> - /// Constant z-axis-extrapolation of a point from a line - /// </summary> - /// <remarks> - /// https://en.wikibooks.org/wiki/Linear_Algebra/Orthogonal_Projection_Onto_a_Line - /// </remarks> - /// <param name="x"></param> - /// <param name="y"></param> - /// <param name="edge"></param> - /// <returns></returns> - private static double ExtrapolateOnEdge(double x, double y, Edge edge) - { - // shortcut if edge end points have same Z values - if (edge.P1.Z.IsEqual(edge.P2.Z)) { - return edge.P1.Z; - } - - // 2d vector of the edge: A--->B - var ab = new Point(edge.Vector.X, edge.Vector.Y); - - // 2d vector of the point: A---->P - var ap = new Point(x - edge.P1.X, y - edge.P1.Y); - - // projection of point (x,y) onto the edge - var z = edge.P1.Z + edge.Vector.Z * (ap.Dot(ab) / ab.Dot(ab)); - return z; - } - } +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Windows.Forms.DataVisualization.Charting; +using TUGraz.VectoCommon.Exceptions; +using TUGraz.VectoCommon.Models; +using TUGraz.VectoCommon.Utils; +using Point = TUGraz.VectoCommon.Utils.Point; + +namespace TUGraz.VectoCore.Utils +{ + public sealed class DelaunayMap : LoggingObject + { + private ICollection<Point> _points = new HashSet<Point>(); + private Triangle[] _triangles; + private Edge[] _convexHull; + + private readonly string _mapName; + private double _minY; + private double _minX; + private double _maxY; + private double _maxX; + + public DelaunayMap(string name) + { + _mapName = name; + } + + public void AddPoint(double x, double y, double z) + { + _points.Add(new Point(x, y, z)); + } + + public IReadOnlyCollection<Point> Entries + { + get + { + var retVal = new Point[_points.Count]; + var i = 0; + foreach (var pt in _points) { + retVal[i++] = new Point(pt.X * (_maxX - _minX) + _minX, pt.Y * (_maxY - _minY) + _minY, pt.Z); + } + return retVal; + } + } + + /// <summary> + /// Triangulate the points. + /// </summary> + /// <remarks> + /// Triangulation with the Bowyer-Watson algorithm (iteratively insert points into a super triangle). + /// https://en.wikipedia.org/wiki/Bowyer%E2%80%93Watson_algorithm + /// </remarks> + public void Triangulate() + { + if (_points.Count < 3) { + throw new ArgumentException(string.Format("{0}: Triangulation needs at least 3 Points. Got {1} Points.", _mapName, + _points.Count)); + } + + SanitycheckInputPoints(); + + // The "supertriangle" encompasses all triangulation points. + // This is just a helper triangle which initializes the algorithm and will be removed in the end of the algorithm. + _maxX = _points.Max(p => p.X); + _maxY = _points.Max(p => p.Y); + _minX = _points.Min(p => p.X); + _minY = _points.Min(p => p.Y); + _points = + _points.Select(p => new Point((p.X - _minX) / (_maxX - _minX), (p.Y - _minY) / (_maxY - _minY), p.Z)).ToList(); + var superTriangle = new Triangle(new Point(-1, -1), new Point(4, -1), new Point(-1, 4)); + var triangles = new List<Triangle> { superTriangle }; + + var pointCount = 0; + + var points = _points.ToArray(); + + // iteratively add each point into the correct triangle and split up the triangle + foreach (var point in points) { + // If the vertex lies inside the circumcircle of a triangle, the edges of this triangle are + // added to the edge buffer and the triangle is removed from list. + // Remove duplicate edges. This leaves the convex hull of the edges. + // The edges in this convex hull are oriented counterclockwise! + + var newTriangles = triangles.Select((t, i) => Tuple.Create(i, t, t.ContainsInCircumcircle(point))) + .Where(t => t.Item3) + .Reverse() + .SelectMany(t => { + triangles.RemoveAt(t.Item1); + return t.Item2.GetEdges(); + }) + .GroupBy(edge => edge) + .Where(group => group.Count() == 1) + .Select(group => new Triangle(group.Key.P1, group.Key.P2, point)).ToList(); + + triangles.AddRange(newTriangles); + + //DrawGraph(pointCount, triangles, superTriangle, xmin, xmax, ymin, ymax, point); + pointCount++; + + // check invariant: m = 2n-2-k + // m...triangle count + // n...point count (pointCount +3 points on the supertriangle) + // k...points on convex hull (exactly 3 --> supertriangle) + if (triangles.Count != 2 * (pointCount + 3) - 2 - 3) { + throw new VectoException( + "Delaunay-Triangulation invariant violated! Triangle count and point count doesn't fit together."); + } + } + +#if TRACE + DrawGraph(pointCount, triangles, superTriangle, points); +#endif + _convexHull = triangles.FindAll(t => t.SharesVertexWith(superTriangle)). + SelectMany(t => t.GetEdges()). + Where(e => !(superTriangle.Contains(e.P1) || superTriangle.Contains(e.P2))).ToArray(); + + _triangles = triangles.FindAll(t => !t.SharesVertexWith(superTriangle)).ToArray(); + } + + private void SanitycheckInputPoints() + { + var duplicates = _points.GroupBy(pt => new { pt.X, pt.Y }, x => x).Where(g => g.Count() > 1).ToList(); + + foreach (var duplicate in duplicates) { + Log.Error("{0}: Input Point appears twice: x: {1}, y: {2}", duplicate.Key.X, duplicate.Key.Y); + } + if (duplicates.Any()) { + throw new VectoException("{0}: Input Data for Delaunay map contains duplicates! \n{1}", _mapName, + string.Join("\n", duplicates.Select(pt => string.Format("{0} / {1}", pt.Key.X, pt.Key.Y)))); + } + } + + public void DrawGraph() + { + var superTriangle = new Triangle(new Point(-1, -1), new Point(4, -1), new Point(-1, 4)); + DrawGraph(0, _triangles, superTriangle, _points.ToArray()); + } + + /// <summary> + /// Draws the delaunay map (except supertriangle). + /// </summary> + private static void DrawGraph(int i, IEnumerable<Triangle> triangles, Triangle superTriangle, Point[] points, + Point lastPoint = null) + { + var xmin = Math.Min(points.Min(p => p.X), lastPoint != null ? lastPoint.X : double.NaN); + var xmax = Math.Max(points.Max(p => p.X), lastPoint != null ? lastPoint.X : double.NaN); + var ymin = Math.Min(points.Min(p => p.Y), lastPoint != null ? lastPoint.Y : double.NaN); + var ymax = Math.Max(points.Max(p => p.Y), lastPoint != null ? lastPoint.Y : double.NaN); + + using (var chart = new Chart { Width = 1000, Height = 1000 }) { + chart.ChartAreas.Add(new ChartArea("main") { + AxisX = new Axis { Minimum = Math.Min(xmin, xmin), Maximum = Math.Max(xmax, xmax) }, + AxisY = new Axis { Minimum = Math.Min(ymin, ymin), Maximum = Math.Max(ymax, ymax) } + }); + + foreach (var tr in triangles) { + if (tr.SharesVertexWith(superTriangle)) { + continue; + } + + var series = new Series { + ChartType = SeriesChartType.FastLine, + Color = lastPoint != null && tr.Contains(lastPoint) ? Color.Red : Color.Blue + }; + series.Points.AddXY(tr.P1.X, tr.P1.Y); + series.Points.AddXY(tr.P2.X, tr.P2.Y); + series.Points.AddXY(tr.P3.X, tr.P3.Y); + series.Points.AddXY(tr.P1.X, tr.P1.Y); + chart.Series.Add(series); + } + + if (lastPoint != null) { + var series = new Series { + ChartType = SeriesChartType.Point, + Color = Color.Red, + MarkerSize = 5, + MarkerStyle = MarkerStyle.Circle + }; + series.Points.AddXY(lastPoint.X, lastPoint.Y); + chart.Series.Add(series); + } + + var frame = new StackFrame(2); + var method = frame.GetMethod(); + System.Diagnostics.Debug.Assert(method.DeclaringType != null, "method.DeclaringType != null"); + var type = string.Join("", method.DeclaringType.Name.Split(Path.GetInvalidFileNameChars())); + var methodName = string.Join("", method.Name.Split(Path.GetInvalidFileNameChars())); + Directory.CreateDirectory("delaunay"); + chart.SaveImage(string.Format("delaunay\\{0}_{1}_{2}_{3}.png", type, methodName, superTriangle.GetHashCode(), i), + ChartImageFormat.Png); + } + } + + public double? Interpolate(SI x, SI y) + { + return Interpolate(x.Value(), y.Value()); + } + + /// <summary> + /// Interpolates the value of an point in the delaunay map. + /// </summary> + /// <param name="x"></param> + /// <param name="y"></param> + /// <returns>a value if interpolation is successfull, + /// null if interpolation has failed.</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double? Interpolate(double x, double y) + { + if (_triangles == null) { + throw new VectoException("Interpolation not possible. Call DelaunayMap.Triangulate first."); + } + + x = (x - _minX) / (_maxX - _minX); + y = (y - _minY) / (_maxY - _minY); + + var i = 0; + while (i < _triangles.Length && !_triangles[i].IsInside(x, y, true)) { + i++; + } + if (i == _triangles.Length) { + i = 0; + while (i < _triangles.Length && !_triangles[i].IsInside(x, y, false)) { + i++; + } + } + + if (i == _triangles.Length) { + return null; + } + + var tr = _triangles[i]; + var plane = new Plane(tr); + return (plane.W - plane.X * x - plane.Y * y) / plane.Z; + } + + public double Extrapolate(SI x, SI y) + { + return Extrapolate(x.Value(), y.Value()); + } + + /// <summary> + /// Extrapolates the value of an point on the edges of a delaunay map. + /// </summary> + /// <param name="x"></param> + /// <param name="y"></param> + /// <returns></returns> + public double Extrapolate(double x, double y) + { + x = (x - _minX) / (_maxX - _minX); + y = (y - _minY) / (_maxY - _minY); + var point = new Point(x, y); + + // get nearest point on convex hull + var nearestPoint = _convexHull.Select(e => e.P1).MinBy(p => Math.Pow(p.X - x, 2) + Math.Pow(p.Y - y, 2)); + + // test if point is on left side of the perpendicular vector (to x,y coordinates) of edge1 in the nearest point + // ^ + // (point) | + // | + // (p1)--edge1-->(nearestPoint) + var edge1 = _convexHull.First(e => e.P2.Equals(nearestPoint)); + if (point.IsLeftOf(new Edge(nearestPoint, edge1.Vector.Perpendicular() + nearestPoint))) { + return ExtrapolateOnEdge(x, y, edge1); + } + + // test if point is on right side of the perpendicular vector of edge2 in the nearest point + // ^ + // | (point) + // | + // (nearestPoint)--edge2-->(p2) + var edge2 = _convexHull.First(e => e.P1.Equals(nearestPoint)); + if (!point.IsLeftOf(new Edge(nearestPoint, edge2.Vector.Perpendicular() + nearestPoint))) { + return ExtrapolateOnEdge(x, y, edge2); + } + + // if point is right of perpendicular vector of edge1 and left of perpendicular vector of edge2: take the nearest point z-value + return nearestPoint.Z; + } + + /// <summary> + /// Constant z-axis-extrapolation of a point from a line + /// </summary> + /// <remarks> + /// https://en.wikibooks.org/wiki/Linear_Algebra/Orthogonal_Projection_Onto_a_Line + /// </remarks> + /// <param name="x"></param> + /// <param name="y"></param> + /// <param name="edge"></param> + /// <returns></returns> + private static double ExtrapolateOnEdge(double x, double y, Edge edge) + { + // shortcut if edge end points have same Z values + if (edge.P1.Z.IsEqual(edge.P2.Z)) { + return edge.P1.Z; + } + + // 2d vector of the edge: A--->B + var ab = new Point(edge.Vector.X, edge.Vector.Y); + + // 2d vector of the point: A---->P + var ap = new Point(x - edge.P1.X, y - edge.P1.Y); + + // projection of point (x,y) onto the edge + var z = edge.P1.Z + edge.Vector.Z * (ap.Dot(ab) / ab.Dot(ab)); + return z; + } + } } \ No newline at end of file diff --git a/VectoCore/VectoCoreTest/VectoCoreTest.csproj b/VectoCore/VectoCoreTest/VectoCoreTest.csproj index 11a4c5a1abdaf69bdc8a960b7bf3c5e063e42dab..cdd9fae36fee393db5b57eba25b39c30b919bf1e 100644 --- a/VectoCore/VectoCoreTest/VectoCoreTest.csproj +++ b/VectoCore/VectoCoreTest/VectoCoreTest.csproj @@ -36,10 +36,6 @@ <WarningLevel>4</WarningLevel> </PropertyGroup> <ItemGroup> - <Reference Include="itextsharp, Version=5.5.7.0, Culture=neutral, PublicKeyToken=8354ae6d2174ddca, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - <HintPath>..\..\packages\iTextSharp.5.5.9\lib\itextsharp.dll</HintPath> - </Reference> <Reference Include="Microsoft.CSharp" /> <Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion>