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();
        }