private static PointD FindMostSignificant(Calculator calc, CalculatedLine line, double leftBound, double rightBound, Mode mode) { if (line.Expression.Type != ExpressionType.Linear) { throw new ArgumentException("The line must be a linear one."); } if (line.PointData.Count == 0) { throw new ArgumentException("The calculated line must contain at least one line segment."); } RoughSignificant roughMax = GetRoughSignificant(line, calc, leftBound, rightBound, mode); return RefineSignificant(calc, line, roughMax, mode); }
private static PointD[] CalculateLine(Calculator calc, CalculatedLine line, double end, ref double pos) { if (pos > end) { throw new ArgumentException("The current position is greater than the end."); } int i = 0; var filled = new PointD[(int)Math.Round(Math.Abs(end - pos) / line.Increment)]; while (end - pos > 1E-10 || pos < end) { calc.GraphingArgumentValue = pos; filled[i++] = new PointD(pos * line.XScale, calc.Evaluate(((StandardExpression)line.Expression).Expression) * line.YScale); pos += line.Increment; } return filled; }
/// <summary> /// Finds every point on the specified line where that line crosses the X axis. /// </summary> /// <param name="calc">The calculator used to evaluate the expression.</param> /// <param name="line">The line to test for zeros.</param> /// <returns> /// A list of points where the specified line crosses the X axis. If the line does not /// cross the X axis at any point, the list will be empty. /// </returns> /// <remarks><para> /// The algorithm used to find the zeros of a line is similar to the binary search /// algorithm. /// </para><para> /// First, each point on the line is iterated through. In each iteration, it checks if the /// sign of the Y values of the current point and the last point on the line are different. /// For example, if we are iterating through the line "5x + 5," once we iterate through the /// point (-1.5, -2.5) and (-0.5, 2.5), we find that the Y values have changed from negative /// to positive. Those two points are then stored for later use. We repeat this process until /// we reach the end of the line. /// </para><para> /// After we store each place where the Y values change signs, the binary search begins. /// In a single iteration, we first determine if the left point of the pair (the minimum) /// begins in the negative or positive side of the X axis. If it begins in the negative, it /// is said to be moving "upward." Otherwise, it is moving "downward." This is important /// because it determines what values are set to the high and low values for the binary /// search. One of two things then happen: /// </para><para> /// In the case of the line going upward, if the sign of the middle value is negative, then /// the low value becomes the middle value for the next iteration. Otherwise, the high value /// becomes the middle value. /// </para><para> /// In the case of the line going downward, if the sign of the middle value is positive, then /// the low value becomes the middle value for the next iteration. Otherwise, the high value /// becomes the middle value. /// </para><para> /// This continues until the middle values for two consecutive iterations are the same value. /// Once this happens, the limits of the precision and accuracy of the datatype we are using /// (64-bit floating point in this specific implementation's case) has been reached, and the /// search stops. The final middle value is the X value of the zero. /// </para></remarks> public static List<PointD> FindZeros(Calculator calc, CalculatedLine line) { if (line.Expression.Type != ExpressionType.Linear) { throw new ArgumentException("The expression type must be Linear."); } // Each PointF array represents the two points where the // sign changes from positive to negative or vice versa var signChanges = from roughZero in line.PointData.SelectMany(FindRoughZero) select new { LowerBound = roughZero[0].X / line.XScale, UpperBound = roughZero[1].X / line.XScale, InitialSign = roughZero[0].Y < 0 ? Sign.Negative : Sign.Positive }; // Find the corresponding zero for each sign change var zeros = from change in signChanges select RefineZero(calc, (StandardExpression)line.Expression, change.LowerBound, change.UpperBound, change.InitialSign); return zeros.ToList(); }
private static IList<IList<PointD>> GetCompleteLine(Calculator calc, CalculatedLine line) { var regions = new List<IList<PointD>>(); double xScale = line.XScale; double yScale = line.YScale; int segmentIndex = 1; int projSegments = line.PointData.Count - 1; double pos = line.Window.MinimumX; if (Math.Abs(line.PointData[0][0].X / xScale - pos) > 1E-10) { // There is a void region between the left side of the window and the first segment segmentIndex = 0; projSegments++; } for (int i = 0; i < projSegments; i++) { PointD[] filled = CalculateLine(calc, line, line.PointData[segmentIndex][0].X / xScale, ref pos); regions.Add(filled); regions.Add(line.PointData[segmentIndex]); pos = line.PointData[segmentIndex++].Last().X / xScale + line.Increment; } IList<PointD> lastSegment = line.PointData.Last(); if (Math.Abs(line.Window.MaximumX - lastSegment.Last().X / xScale) > 1E-10) { // There is a void region between the last segment and the right side of the window regions.Add(CalculateLine(calc, line, line.Window.MaximumX, ref pos)); } return regions; }
public static PointD FindMinimum(Calculator calc, CalculatedLine line, double leftBound, double rightBound) { return FindMostSignificant(calc, line, leftBound, rightBound, Mode.Minimum); }
private static PointD RefineSignificant(Calculator calc, CalculatedLine line, RoughSignificant roughSig, Mode mode) { double lo = roughSig.Left.X / line.XScale; double hi = roughSig.Right.X / line.XScale; double mid = (lo + hi) / 2; double lastMid = double.NaN; var stdExpr = (StandardExpression)line.Expression; Func<double, int> derivComparer = mode == Mode.Maximum ? _maxDerivComparer : _minDerivComparer; double y; while (lastMid != mid) { double deriv = DerivativeCalculator.CalculateDerivative(calc, stdExpr, mid, out y); int result = derivComparer(deriv); if (result == -1) { lo = mid; } else if (result == 1) { hi = mid; } else { // Unlikely, but possible break; } lastMid = mid; mid = (lo + hi) / 2; } mid = Math.Round(mid, DerivativeCalculator.DeltaXPrecision); calc.GraphingArgumentValue = mid; y = calc.Evaluate(stdExpr.Expression); return new PointD(mid, y); }
private static RoughSignificant GetRoughSignificant(CalculatedLine line, Calculator calc, double leftBound, double rightBound, Mode mode) { IList<IList<PointD>> segments = line.PointData.Count > 1 ? GetCompleteLine(calc, line) : line.PointData; double scaledLeftBound = leftBound * line.XScale; double scaledRightBound = rightBound * line.XScale; Func<double, double, bool> comparer = mode == Mode.Maximum ? _maxValComparer : _minValComparer; var significant = new PointD(0, mode == Mode.Maximum ? double.MinValue : double.MaxValue); int signifSegment = -1, signifPoint = -1; for (int i = 0; i < segments.Count; i++) { var segment = segments[i]; for (int j = 0; j < segment.Count; j++) { var point = segment[j]; if (comparer(significant.Y, point.Y) && point.X >= scaledLeftBound && point.X <= scaledRightBound) { significant = point; signifSegment = i; signifPoint = j; } } } if (signifSegment == -1 || signifPoint == -1) { throw new Exception("No significant value could be found."); } // Get the points to the left and right of the rough maximum PointD left; PointD right; GetSurroundingPoints(segments, signifSegment, signifPoint, out left, out right); return new RoughSignificant(left, right); }
private static List<PointD[]> FindRoughIntersections(CalculatedLine line1, CalculatedLine line2) { if (line1.XScale != line2.XScale || line1.YScale != line2.YScale) { throw new ArgumentException("The X and Y scales of both lines must be equal."); } double xScale = line1.XScale; double yScale = line1.YScale; var intersections = new List<PointD[]>(); IList<LineSegment> line1Segments = FlattenLine(line1); IList<LineSegment> line2Segments = FlattenLine(line2); int line1Pos = 1, line2Pos = 1; while (line1Pos < line1Segments.Count && line2Pos < line2Segments.Count) { LineSegment last1 = line1Segments[line1Pos - 1]; LineSegment last2 = line2Segments[line2Pos - 1]; LineSegment current1 = line1Segments[line1Pos]; LineSegment current2 = line2Segments[line2Pos]; // If either current points are Starts, then the last points are not // immediately "behind" them, and can't be considered for intersections if (current1.Type == SegmentType.Continuation && current2.Type == SegmentType.Continuation) { bool finished = false; // As long as either line is ahead of each other, advance the line that is behind // This prevents false intersections from asymptotes and other singularities while (!finished && line1Pos < line1Segments.Count && line2Pos < line2Segments.Count && (current1.Point.X < current2.Point.X || current1.Point.X > current2.Point.X)) { line1Pos = SynchronizeLinePosition(line1Pos, line1Segments, current2, ref current1, ref last1, ref finished); if (finished) { break; } line2Pos = SynchronizeLinePosition(line2Pos, line2Segments, current1, ref current2, ref last2, ref finished); } bool oneOnTop = last1.Point.Y > last2.Point.Y; if (finished) { break; } // Make sure neither of the points are the start of a line segment, because if they // are, the point before either one won't be immediately behind the current point if (current1.Type != SegmentType.Start && current2.Type != SegmentType.Start && (current1.Point.Y > current2.Point.Y) != oneOnTop) { // Store the intersection and the four points that make it intersections.Add(new[] { last1.Point.ScaleDown(xScale, yScale), current1.Point.ScaleDown(xScale, yScale), last2.Point.ScaleDown(xScale, yScale), current2.Point.ScaleDown(xScale, yScale), }); } } line1Pos++; line2Pos++; } return intersections; }
private static List<LineSegment> FlattenLine(CalculatedLine line) { var flattened = line.PointData.SelectMany(t => t.Select((t1, i) => new LineSegment { Point = t1, Type = i == 0 ? SegmentType.Start : SegmentType.Continuation })); return flattened.ToList(); }