//method to implement richardson extrapolation with error control
        public void richardson_extrapolation_errorcontrol(funcref thefunc, double x, double h, int maxiter, double toler)
        {
            double deriv = 0;
            double derivold = 0;
            double err = 0;
            double errold = 1000000000;
            int itercount = 0;
            int count = 0;

            //firstly ensure that the "Richardsonoutput" list is empty
            if (Richardsonoutput.Count() > 0)
                Richardsonoutput.Clear();

            //next run the algorithm and populate the l8ist
            for (count = 1; count <= maxiter; count++)
            {
                //get the derivative estimate at iteration "count"
                deriv = richardson_extrapolation(thefunc, x, h, count);
                err = Math.Abs(deriv - derivold);
                //is the error less than a specified tolerance
                if (err < toler)
                {
                    itercount = count;
                    Richardsonoutput.Add(deriv);
                    Richardsonoutput.Add(err);
                    Richardsonoutput.Add((double)count);
                    break;
                }
                //is the latest error greatest than the previous error
                if (err > errold)
                {
                    deriv = derivold;
                    err = errold;
                    itercount = count - 1;
                    Richardsonoutput.Add(deriv);
                    Richardsonoutput.Add(err);
                    Richardsonoutput.Add((double)count);
                }
                derivold = deriv;
                errold = err;
                Richardsonoutput.Add(deriv);
                Richardsonoutput.Add(err);
                Richardsonoutput.Add((double)count);
            }
        }
        //recursive method to implement Richardsons extrapolation
        public double richardson_extrapolation(funcref thefunc, double x, double h, double j)
        {
            double dval1 = 0;
            double dval2 = 0;
            if (j == 1)
                return (1 / (2 * h)) * (thefunc(x + h) - thefunc(x - h));
            else
            {
                dval1 = richardson_extrapolation(thefunc, x, h / 2.0, j - 1);
                dval2 = richardson_extrapolation(thefunc, x, h, j - 1);
                return dval1 + (1 / (Math.Pow(4, (double)j - 1) - 1)) * (dval1 - dval2);
            }

            //end of richardson_extrapolation() method
        }
        //NewtonRaphson_Richardson_ErrorControl() method
        public void NewtonRaphson_Richardson_ErrorControl(double tol, funcref thefunc)
        {
            //clear the roots list
            if (Roots.Count() > 0)
                Roots.Clear();
            if (NRiterations.Count() > 0)
                NRiterations.Clear();

            //ensure that there are intervals upon which to apply the Newton Raphson method)
            if (Right.Count() == 0)
            {
                Console.WriteLine("No intervals are given, hence we cannot run the Newton Raphson method.\n");
            }
            else
            {
                int z = 0;

                //running Newton Raphson on each interval
                foreach (double d in Right)
                {
                    double upper = d;
                    double lower = Left[z];
                    double rinit = (upper + lower) / 2;
                    double rold = rinit;
                    double numer = 0, denom = 0, dx = 0;
                    double rnew = 0;

                    int counter = 0;

                    //we will use "counter" to ensure Newton Raphson does not loop infinitely
                    while (counter < 1000)
                    {
                        numer = thefunc(rold);
                        richardson_extrapolation_errorcontrol(thefunc, rold, 0.025, 20, 0.0001);
                        int k = Richardsonoutput.Count();
                        denom = Richardsonoutput[k - 3];
                        if (Double.IsNaN(denom))
                        {
                            Console.WriteLine("Stopping Newton Raphson in interval ({0},{1}) as f'[x] at {2} is not defined", Left[z], d, rold);
                            break;
                        }
                        if (denom == 0.0)
                        {
                            Console.WriteLine("Stopping Newton Raphson in interval ({0},{1}) as f'[x] at {2} equals 0", Left[z], d, rold);
                            break;
                        }
                        dx = numer / denom;
                        rnew = rold - dx;
                        //if the change "dx" is less than the tolerance, then we have found an estimate of the root
                        if (Math.Abs(dx) < Math.Abs(tol))
                        {
                            Roots.Add(rnew);
                            NRiterations.Add(counter + 1);
                            break;
                        }

                        rold = rnew;

                        //prevent an infinite loop
                        counter++;
                        if (counter > 1000)
                        {
                            Console.WriteLine("The Newton Raphson method has done more than 1000 iterations and is still not within the specified tolerance.We will now assume that the root is given by the last iteration\n");
                            Roots.Add(rnew);
                            NRiterations.Add(counter);
                        }
                    }

                    double val = rnew;
                    z++;
                    //end of Newton Raphson on interval
                }

            }
            //end of NewtonRaphson() method
        }
        //bracketing() method
        public void Bracketing(double u, double l, double intervals, funcref thefunc)
        {
            double upper = 0;
            double lower = 0;
            double dx = 0;
            //firstly we need to ensure that the Right and Left lists are empty
            if (Right.Count() > 0)
            {
                Right.Clear();
                Left.Clear();
            }

            dx = (u - l) / intervals;

            lower = l;
            upper = lower + dx;
            while (upper <= u)
            {
                //test the bracket
                if (thefunc(lower) * thefunc(upper) < 0)
                {
                    //there is a root in this interval so add the values to the relevants list
                    Left.Add(lower);
                    Right.Add(upper);
                }
                else
                {
                    //do nothing
                }

                //move to the next bracket
                lower = upper;
                upper = upper + dx;
            }

            //end of bracketing() method
        }
        static void Main(string[] args)
        {
            Solver s = new Solver();

            funcref f1 = new funcref(val => Math.Exp(val) + Math.Pow(2, -val) + 2 * Math.Sin(val) - 6);
            funcref f2 = new funcref(val => 2 * val * Math.Cos(2 * val) - (val - 2) * (val - 2));
            funcref f3 = new funcref(val => Math.Exp(val) - 3 * Math.Pow(val, 2));
            funcref f = new funcref(val => Math.Exp(val) + Math.Pow(2, -val) + 2 * Math.Sin(val) - 6);

            double linterval = 0;
            double uinterval = 0;

            //Here we allow the user to specify which one of the three equations they would like to find the roots of
            string str = "";
            int choice = 0;
            Console.Write("Three functions.\n\nf1(x) = e^x+2^-x+2Sin(x)-6\nf2(x) = 2xCos(2x) - (x-2)^2\nf3(x) = e^x -3x^2\n\nPlease enter an integer between 1 and 3:");
            str = Console.ReadLine();
            bool b = int.TryParse(str, out choice);
            while (!b || ((choice < 1) || (choice > 3)))
            {
                Console.Write("\nInvalid Input. Please renter an integer between 1 and 3: ");
                str = Console.ReadLine();
                b = int.TryParse(str, out choice);
            }

            //user chooses which function to examine
            switch (choice)
            {
                case 1:
                    Console.Write("\nWe will work with function f1(x). Enter number of intervals:");
                    f = f1;
                    linterval = -4;
                    uinterval = 4;
                    break;
                case 2:
                    Console.Write("\nWe will work with function f2(x).Enter number of intervals:");
                    f = f2;
                    linterval = 0;
                    uinterval = 4;
                    break;
                case 3:
                    Console.Write("\nWe will work with function f3(x).Enter number of intervals:");
                    linterval = -2;
                    uinterval = 5;
                    f = f3;
                    break;
            }

            //user chooses the number of intervals
            int intervals = 0;
            str = Console.ReadLine();
            b = int.TryParse(str, out intervals);
            while (!b || ((intervals < 1) || (intervals > 1000)))
            {
                Console.Write("\nInvalid Input. Please enter an integer between 1 and 1000: ");
                str = Console.ReadLine();
                b = int.TryParse(str, out intervals);
            }

            //user chooses a tolerance level
            Console.Write("\nNow enter the tolerance level:");
            double tolerance = 0;
            str = Console.ReadLine();
            b = double.TryParse(str, out tolerance);
            while (!b || (tolerance <= 0))
            {
                Console.Write("\nInvalid Input. Please enter a double >=0: ");
                str = Console.ReadLine();
                b = double.TryParse(str, out tolerance);
            }

            //bracketing
            Console.Write("\nBracketing Output\n");
            Console.Write("*****************\n");
            s.Bracketing(uinterval, linterval, intervals, f);
            s.print_bracketing_roots();

            //Bisection
            Console.Write("\nBisection Method\n");
            Console.Write("*****************\n");
            s.Bisection(tolerance, f);
            s.print_roots();

            //Newton Raphson
            Console.Write("\nNewton Raphson Method\n");
            Console.Write("*****************\n");
            s.NewtonRaphson(tolerance, f);
            s.print_roots_iterations();

            int milliseconds = 3000;
            Thread.Sleep(milliseconds);

            //Richardson extrapolation with error control
            //user chooses a tolerance level
            Console.Write("\nRichardson Extrapolation with error Control\n");
            Console.Write("*********************************************\n");
            Console.Write("Provide a double at which you wish to evaluate the derivative:");
            double x = 0;
            str = Console.ReadLine();
            b = double.TryParse(str, out x);
            while (!b)
            {
                Console.Write("\nInvalid Input. Please enter a double: ");
                str = Console.ReadLine();
                b = double.TryParse(str, out x);
            }
            s.richardson_extrapolation_errorcontrol(f, x, 0.5, 40, tolerance);
            s.print_richardson_output();

            milliseconds = 5000;
            Thread.Sleep(milliseconds);

            //Advanced test
            Console.Write("\n\n**********************************************************************");
            Console.Write("\n**********************OPTICAL WAGEGUIDE*******************************\n");
            Console.Write("\nBracketing Output\n");
            Console.Write("*****************\n");

            Waveguide w = new Waveguide();
            funcref func = new funcref(w.Calc);

            double lambda = 1.3, nc = 3.44, ns = 3.34;
            double k0 = 2.0 * Math.PI / lambda;
            linterval = k0 * ns;
            uinterval = k0 * nc;
            //bracketing
            intervals = 20;
            s.Bracketing(uinterval, linterval, intervals, func);
            s.print_bracketing_roots();
            milliseconds = 3000;
            Thread.Sleep(milliseconds);
            //Bisection
            Console.Write("\nBiscection Output\n");
            Console.Write("*****************\n");
            s.Bisection(tolerance, func);
            s.print_roots();
            milliseconds = 3000;
            Thread.Sleep(milliseconds);

            //Newton Raphson Richardson Error Control
            Console.Write("\nNewton Raphson Error Control Output\n");
            Console.Write("*************************************\n");
            s.NewtonRaphson_Richardson_ErrorControl(tolerance, func);
            s.print_roots_iterations();
            milliseconds = 3000;
            Thread.Sleep(milliseconds);
            Console.Write("\n\n*************************************\n");
            Console.Write("End of Program\n");
            Console.Write("*************************************\n");
            Console.ReadLine();
        }
        //bisection() method
        public void Bisection(double tol, funcref thefunc)
        {
            //clear the roots list
            if (Roots.Count() > 0)
                Roots.Clear();

            //ensure that there are intervals upon which to apply the bisection method)
            if (Right.Count() == 0)
            {
                Console.WriteLine("No intervals are given, hence we cannot run the bisection method.\n");
            }
            else
            {
                int z = 0;
                foreach (double d in Right)
                {
                    double upper = d;
                    double lower = Left[z];

                    int counter = 0;

                    //check upper and lower intervals are closer than the tolerance level
                    while ((upper - lower > tol) & (counter < 1000))
                    {
                        double m = (upper + lower) / 2;
                        double s = thefunc(lower) * thefunc(m);
                        if (s < 0)
                        {
                            upper = m;
                        }
                        else
                        {
                            lower = m;
                        }

                        //prevent an infinite loop
                        counter++;
                        if (counter > 1000)
                            Console.WriteLine("The Bisection method has done more than 1000 iterations and is still not within the specified tolerance.We will now assume that the root is given by the last iteration\n");
                    }

                    double val = (upper + lower) / 2;
                    Roots.Add(val);
                    z++;
                }

            }
            //end of Bisection() method
        }