public double[] AB3Step(FunctionMatrix f, double[] vals1, double[] vals2, double[] vals3, double h)
        {
            int neqns = f.Cols;
            int m = vals1.GetLength(0);
            //array to contain the output of the update
            double[] update = new double[m];

            double[] F1 = new double[neqns];
            double[] F2 = new double[neqns];
            double[] F3 = new double[neqns];
            double[] F4 = new double[neqns];

            //Calculating F1i
            for (int i = 0; i < neqns; i++)
            {
                F1[i] = 23.0 * f[0, i](vals1);
            }

            //Calculating F2i
            for (int i = 0; i < neqns; i++)
            {
                F2[i] = 16.0 * f[0, i](vals2);
            }

            //Calculating F3i
            for (int i = 0; i < neqns; i++)
            {
                F3[i] = 5.0 * f[0, i](vals3);
            }

            //calculating the update
            update[0] = vals1[0] + h;
            for (int i = 1; i < m; i++)
            {
                update[i] = vals1[i] + (h / 12.0) * (F1[i - 1] - F2[i - 1] + F3[i - 1]);
            }

            return update;
            //end of method
        }
        public double[,] AB3Solve(FunctionMatrix f, double[] vals, double h, double lower, double upper)
        {
            int steps = (int)((upper - lower) / h);
            int m = vals.GetLength(0);
            double[,] ans = new double[steps, m];

            //Using Runge Kutta to get initial starting points
            double[] v3 = new double[m];
            for (int i = 0; i < m; i++)
            {
                v3[i] = vals[i];
            }
            double[] v2 = RK4Step(f, v3, h);
            double[] v1 = RK4Step(f, v2, h);
            //now add these points to the ans
            for (int i = 0; i < m; i++)
            {
                ans[0, i] = v3[i];
                ans[1, i] = v2[i];
                ans[2, i] = v1[i];
            }

            //now we can start applying the AB3Step
            for (int i = 3; i < steps; i++)
            {
                double[] vupdate = AB3Step(f, v1, v2, v3, h);
                //now add vupdate to the matrix
                for (int j = 0; j < m; j++)
                    ans[i, j] = vupdate[j];
                //now we need to reassign the values
                for (int k = 0; k < m; k++)
                {
                    v3[k] = v2[k];
                    v2[k] = v1[k];
                    v1[k] = vupdate[k];
                }
            }

            return ans;
        }
        public double[,] RKFSolve(FunctionMatrix f, double[] vals, double hmin, double hmax, double lower, double upper, double toler)
        {
            double dx = hmax;
            int counter = 0;
            int iter = 1;
            int niter = 0;
            int m = vals.GetLength(0);
            //might need to change the size of the array later on
            double[,] ans = new double[30000, m];

            //add the initial step to the ans array
            for (int i = 0; i < m; i++)
                ans[0, i] = vals[i];

            //we will use this array in the while loop
            double[] valsnew = new double[m];
            for (int i = 0; i < m; i++)
                valsnew[i] = vals[i];

            while ((iter == 1) && (niter < 20000))
            {
                double[] valsold = new double[m];
                for (int i = 0; i < m; i++)
                    valsold[i] = valsnew[i];

                double[,] rklist = RKFStep(f, valsold, dx);
                double maxnorm = 0;
                for (int i = 0; i < m; i++)
                {
                    if (rklist[2, i] > maxnorm)
                        maxnorm = rklist[2, i];
                }
                //error per step is acceptable
                if (maxnorm < toler)
                {
                    counter = counter + 1;
                    for (int i = 0; i < m; i++)
                        valsnew[i] = rklist[0, i];
                    for (int i = 0; i < m; i++)
                        ans[counter, i] = valsnew[i];
                }
                //compute step size conversion factor for next step
                double delta = 0.84 * Math.Pow((toler / maxnorm), 0.25);
                //
                if (delta < 0.1)
                    dx = 0.1 * dx;
                else
                    if (delta > 4)
                        dx = 4.0 * dx;
                    else
                        dx = delta * dx;
                //
                if (dx < hmin)
                    dx = hmin;
                if (dx > hmax)
                    dx = hmax;

                if (ans[counter, 0] > upper)
                    iter = 0;
                if (ans[counter, 0] + dx > upper)
                    iter = 0;

                niter = niter + 1;
            }

            int row = 0;

            //now there might be alot of zeros in the matrix, can we determine where these are
            for (int i = 0; i < ans.GetLength(0); i++)
            {
                double sum = 0;
                for (int j = 0; j < ans.GetLength(1); j++)
                {
                    sum = sum + ans[i, j];
                }
                if (sum == 0)
                {
                    row = i;
                    break;
                }
            }
            //now return only those values which we are interested in
            double[,] output = new double[row, m];
            for (int i = 0; i < row; i++)
            {
                for (int j = 0; j < ans.GetLength(1); j++)
                {
                    output[i, j] = ans[i, j];
                }
            }

            return output;
        }
        //Runge Kutta step
        public double[] RK4Step(FunctionMatrix f, double[] vals, double h)
        {
            int neqns = f.Cols;
            int m = vals.GetLength(0);
            //array to contain the output of the update
            double[] update = new double[m];

            double[] F1 = new double[neqns];
            double[] F2 = new double[neqns];
            double[] F3 = new double[neqns];
            double[] F4 = new double[neqns];

            //Calculating F1i
            for (int i = 0; i < neqns; i++)
            {
                F1[i] = h * f[0, i](vals);
            }

            //next we need to get the updated vector in order to calculate F2i
            double[] f2vals = new double[m];
            f2vals[0] = vals[0] + h / 2;
            for (int i = 1; i < m; i++)
            {
                f2vals[i] = vals[i] + F1[i - 1] / 2;
            }
            //Calculating F2i
            for (int i = 0; i < neqns; i++)
            {
                F2[i] = h * f[0, i](f2vals);
            }

            //next we need to get the updated vector in order to calculate F3i
            double[] f3vals = new double[m];
            f3vals[0] = vals[0] + h / 2;
            for (int i = 1; i < m; i++)
            {
                f3vals[i] = vals[i] + F2[i - 1] / 2;
            }
            //Calculating F3i
            for (int i = 0; i < neqns; i++)
            {
                F3[i] = h * f[0, i](f3vals);
            }

            //next we need to get the updated vector in order to calculate F4i
            double[] f4vals = new double[m];
            f4vals[0] = vals[0] + h;
            for (int i = 1; i < m; i++)
            {
                f4vals[i] = vals[i] + F3[i - 1];
            }
            //Calculating F4i
            for (int i = 0; i < neqns; i++)
            {
                F4[i] = h * f[0, i](f4vals);
            }

            //calculating the update
            update[0] = vals[0] + h;
            for (int i = 1; i < m; i++)
            {
                //update[i]=vals[i]+1/6*(F1[i-1]+2*F2[i-1]+2*F3[i-1]+F4[i-1]);
                update[i] = vals[i] + (1.0 / 6.0) * (F1[i - 1] + 2 * F2[i - 1] + 2 * F3[i - 1] + F4[i - 1]);

            }

            return update;
        }
        //method to calculate the root for a system of functions given an initial guess/vector
        public double[] newtonraphson(FunctionMatrix f, double[] x, double toler)
        {
            int maxiter = 1000;
            double err = 10000000;
            int counter = 0;
            int dim = x.GetLength(0);
            int r = f.Rows;
            double[] xn = new double[dim];
            //populating vector xn
            for (int i = 0; i < dim; i++)
                xn[i] = x[i];
            double[] xnp1 = new double[dim];

            while ((counter < maxiter) && (err > toler))
            {
                Vector Fn = f.EvaluateVector(xn);
                //converting to Vector Objects
                Vector Vxn = new Vector(xn);

                //Need to calculate the Jacobi and convert it to a matrix
                double[,] j = jacobi(f, xn, 0.001);
                double[,] jinv = s.find_inverse(j, j.GetLength(0));
                Matrix Jinv = new Matrix(jinv);

                //this is just F(xn)/F'(xn)
                Vector tmp = Jinv * Fn;

                //xnp1 = xn - F(xn)/F'(xn)
                Vector Vxnp1 = Vxn - tmp;

                //what is the size of the err i.e. xnp1-xn
                Vector Diff = Vxnp1 - Vxn;
                err = Diff.maxNorm();
                double[] t = new double[dim];

                xnp1 = (double[])Vxnp1;
                xn = xnp1;
                counter++;
            }

            return xn;
            //end of method
        }
        //method to calculate the Jacobi at for a system of functions at a particular vector
        public double[,] jacobi(FunctionMatrix fmat, double[] x, double dx)
        {
            int rows = fmat.Rows;
            int cols = fmat.Cols;
            int vars = x.GetLength(0);

            //we will assume that FunctionMatrix is always of the form n*1
            double[,] jac = new double[rows, vars];
            for (int i = 0; i < rows; i++)
            {
                for (int j = 0; j < vars; j++)
                {
                    jac[i, j] = partial_derivative(fmat[i, 0], x, dx, vars, j);
                }

            }
            return jac;
            //end of method
        }
        //method to calculate the root for a system of functions given an initial guess/vector
        //Gradient Descent with Fixed Alpha
        public double[] GradientAlpha(FunctionMatrix f, double[] x, double toler)
        {
            int counter = 0;
            int dim = x.GetLength(0);
            int r = f.Rows;

            double[,] Jmat = new double[r, dim];
            Matrix MJmat = new Matrix(Jmat);
            Vector VFg1 = new Vector(r);
            Vector Vz = new Vector(dim);
            Vector VFg3 = new Vector(r);
            Vector VFg2 = new Vector(r);
            Vector VFgc = new Vector(r);
            Vector VFgnew = new Vector(r);
            Vector Vxnew = new Vector(dim);

            double alpha2 = 0;
            double alpha3 = 0;
            double[] xold = x;
            double[] xnew = new double[dim];
            double g1 = 0;
            double g2 = 0;
            double g3 = 0;
            double gc = 0;
            double gnew = 0;
            double normz = 0;
            double h1 = 0;
            double h2 = 0;
            double h3 = 0;
            double ac = 0;

            do
            {
                VFg1 = f.EvaluateVector(xold);
                g1 = VFg1.Magnitude();

                //calculate the Jacobian
                Jmat = jacobi(f, xold, 0.001);
                //now update the Matrix object which represents the Jacobian
                for (int i = 0; i < r; i++)
                    for (int j = 0; j < dim; j++)
                        MJmat[i, j] = Jmat[i, j];

                Vz = 2 * (MJmat * VFg1);

                normz = Math.Sqrt(Vz.Magnitude());

                if (normz > toler)
                {
                    Vz = (1 / normz) * Vz;
                    alpha3 = 1;

                    VFg3 = f.EvaluateVector((double[])((Vector)xold - (alpha3 * Vz)));
                    g3 = VFg3.Magnitude();

                    while (Math.Abs(g3 - g1) > toler)
                    {
                        alpha3 = 0.5 * alpha3;

                        VFg3 = f.EvaluateVector((double[])((Vector)xold - (alpha3 * Vz)));
                        g3 = VFg3.Magnitude();
                        if (Math.Abs(alpha3) < 0.5 * toler)
                            break;
                    }

                    alpha2 = 0.5 * alpha3;
                    VFg2 = f.EvaluateVector((double[])((Vector)xold - (alpha2 * Vz)));
                    g2 = VFg2.Magnitude();

                    //perform interpolation
                    h1 = (g2 - g1) / alpha2;
                    h2 = (g3 - g2) / (alpha3 - alpha2);
                    h3 = (h2 - h1) / alpha3;
                    ac = 0.5 * (alpha2 - (h1 / h3));

                    VFgc = f.EvaluateVector((double[])((Vector)xold - (ac * Vz)));
                    gc = VFgc.Magnitude();

                    if (gc < g3)
                    {
                        Vxnew = ((Vector)xold) - (ac * Vz);
                        VFgnew = f.EvaluateVector((double[])Vxnew);
                        gnew = VFgnew.Magnitude();
                    }
                    else
                    {
                        Vxnew = ((Vector)xold) - (alpha3 * Vz);
                        VFgnew = f.EvaluateVector((double[])Vxnew);
                        gnew = VFgnew.Magnitude();
                    }

                    //test for convergence
                    if (Math.Abs(gnew - g1) < toler)
                        break;
                    else
                    {
                        xold = (double[])Vxnew;
                        counter++;
                    }

                    //end if
                }

            } while (counter < 100);

            return (double[])Vxnew;
            //end of method
        }
        //method to calculate the root for a system of functions given an initial guess/vector
        public double[] BroydenShermanMorrison(FunctionMatrix f, double[] x, double toler)
        {
            double err = 10000000;
            int counter = 0;
            int dim = x.GetLength(0);
            int r = f.Rows;
            double[] xn = new double[dim];
            //populating vector xn
            for (int i = 0; i < dim; i++)
                xn[i] = x[i];
            double[] xnp1 = new double[dim];

            //first iteration i.e. use Newton Raphson for one iteration, the Inverse of the Jacobi will be used as the Ainv for Broyden
            Vector Fn = f.EvaluateVector(xn);
            Vector Vxn = new Vector(xn);
            //Need to calculate the Jacobi and convert it to a matrix
            double[,] j = jacobi(f, xn, 0.001);
            double[,] jinv = s.find_inverse(j, j.GetLength(0));
            Matrix Jinv = new Matrix(jinv);
            //this is just F(xn)/F'(xn)
            Vector tmp = Jinv * Fn;
            //xnp1 = xn - F(xn)/F'(xn)
            Vector Vxnp1 = Vxn - tmp;
            //what is the size of the err i.e. xnp1-xn
            Vector Vdeltax = Vxnp1 - Vxn;
            err = Vdeltax.maxNorm();
            xnp1 = (double[])Vxnp1;

            Vector placeholder = new Vector(1);

            //we'll use these later
            Vector VFnold = f.EvaluateVector(xn);
            Vector VFnew = f.EvaluateVector(xnp1);
            Vector VdeltaF = VFnew - VFnold;
            double[,] Aold = new double[dim, dim];
            double[,] Anew = new double[dim, dim];

            //now we'll start on the Broyden iteration element
            if (err < toler)
                return xn;
            else
            {
                while ((err > toler) && (counter < 1000))
                {
                    if (counter == 0)
                    {
                        Aold = jinv;
                        Anew = BroydenAinv(Aold, (double[])Vdeltax, (double[])VdeltaF);
                    }
                    else
                    {
                        Anew = BroydenAinv(Aold, (double[])Vdeltax, (double[])VdeltaF);
                    }
                    //now update all the variables
                    Vxnp1 = Vxn - placeholder.dot(new Matrix(Anew), VFnew);
                    Vdeltax = Vxnp1 - Vxn;
                    err = Vdeltax.maxNorm();
                    Aold = Anew;
                    VFnold = f.EvaluateVector((double[])Vxn);
                    VFnew = f.EvaluateVector((double[])Vxnp1);
                    VdeltaF = VFnew - VFnold;
                    Vxn = Vxnp1;
                    xn = (double[])Vxn;
                    counter++;
                }
                return xn;
            }
            //end of method
        }
        public double[,] ABMStepSizeSolve(FunctionMatrix f, double[] vals, double hmin, double hmax, double lower, double upper, double toler)
        {
            double dx = hmax;
            int counter = 3;
            int iter = 1;
            int niter = 0;
            int m = vals.GetLength(0);
            //might need to change the size of the array later on
            double[,] ans = new double[30000, m];

            //Using Runge Kutta to get initial starting points
            double[] v4 = new double[m];
            for (int i = 0; i < m; i++)
            {
                v4[i] = vals[i];
            }
            double[] v3 = RK4Step(f, v4, dx);
            double[] v2 = RK4Step(f, v3, dx);
            double[] v1 = RK4Step(f, v2, dx);
            //now add these points to the ans
            for (int i = 0; i < m; i++)
            {
                ans[0, i] = v4[i];
                ans[1, i] = v3[i];
                ans[2, i] = v2[i];
                ans[3, i] = v1[i];
            }

            while ((iter == 1) && (niter < 20000))
            {
                double[] valsold = AB4Step(f, v1, v2, v3, v4, dx);
                double[] valsnew = AM3Step(f, valsold, v1, v2, v3, dx);
                double[] rklist = new double[m];
                for (int i = 0; i < m; i++)
                {
                    rklist[i] = Math.Abs(valsnew[i] - valsold[i]);
                }
                double maxnorm = 0;
                for (int i = 0; i < m; i++)
                {
                    if (rklist[i] > maxnorm)
                        maxnorm = rklist[i];
                }
                //error per step is acceptable
                if (maxnorm < toler)
                {
                    counter = counter + 1;
                    for (int i = 0; i < m; i++)
                        ans[counter, i] = valsnew[i];
                    //now we need to reassign the values
                    for (int k = 0; k < m; k++)
                    {
                        v4[k] = v3[k];
                        v3[k] = v2[k];
                        v2[k] = v1[k];
                        v1[k] = valsnew[k];
                    }

                }
                //compute step size conversion factor for next step
                double delta = 1.5 * Math.Pow((toler / maxnorm), 0.25);
                //
                if (delta < 0.1)
                    dx = 0.1 * dx;
                else
                    if (delta > 4)
                        dx = 4.0 * dx;
                    else
                        dx = delta * dx;
                //
                if (dx < hmin)
                    dx = hmin;
                if (dx > hmax)
                    dx = hmax;

                if (ans[counter, 0] > upper)
                    iter = 0;
                if (ans[counter, 0] + dx > upper)
                    iter = 0;

                niter = niter + 1;
            }

            int row = 0;

            //now there might be alot of zeros in the matrix, can we determine where these are
            for (int i = 0; i < ans.GetLength(0); i++)
            {
                double sum = 0;
                for (int j = 0; j < ans.GetLength(1); j++)
                {
                    sum = sum + ans[i, j];
                }
                if (sum == 0)
                {
                    row = i;
                    break;
                }
            }
            //now return only those values which we are interested in
            double[,] output = new double[row, m];
            for (int i = 0; i < row; i++)
            {
                for (int j = 0; j < ans.GetLength(1); j++)
                {
                    output[i, j] = ans[i, j];
                }
            }

            return output;
        }
        static void Main(string[] args)
        {
            //we will use this temporary array at the end when we want to look at the error of our estimate versus the exact solution for System 1
            double[,] temp = null;

            //Here we allow the user to specify which one of the 3 systems they would like to work with
            string str = "";
            int choice1 = 0;
            int choice2 = 0;
            int choice4 = 0;
            double choice3 = 0;
            double a = 0, b = 0, e = 0, c = 0;
            Console.Write("3 systems.\nEnter an integer between 1 and 3 to choose the system:");
            str = Console.ReadLine();
            bool b1 = int.TryParse(str, out choice1);
            while (!b1 || ((choice1 < 1) || (choice1 > 3)))
            {
                Console.Write("\nInvalid Input. Please enter an integer between 1 and 3: ");
                str = Console.ReadLine();
                b1 = int.TryParse(str, out choice1);
            }

            FunctionMatrix F = new FunctionMatrix(2, 1);

            FunctionMatrix F1A = new FunctionMatrix(1, 1);
            F1A[0, 0] = x => 1 + x[1] / x[0] + (x[1] / x[0]) * (x[1] / x[0]);

            //variable which we will use to help define the startingvalues later on
            int dim = 0;

            //user chooses which system they want to work with
            switch (choice1)
            {
                case 1:
                    Console.Write("\nWe will work with\n\ny'[x] = 1 + y/x + (y/x)^2\ny[1]=0 and 1<=x<=4.8\n");
                    F = F1A;
                    dim = 2;
                    break;
                case 2:
                    Console.Write("\nWe will work with\n\ny''[x] - e y'[t] (1- (y'[t])^2) + y[t] =0\ny[0]=a and y'[0]=b\nWe will assume 0<=t<=20\n");
                    Console.Write("\nEnter an integer between 1 and 5 to choose a, b and e:");
                    str = Console.ReadLine();
                    bool b2 = int.TryParse(str, out choice2);
                    switch (choice2)
                    {
                        case 1:
                            a = 0;
                            b = -1;
                            e = 0.1;
                            break;
                        case 2:
                            a = 1;
                            b = 2;
                            e = 0.1;
                            break;
                        case 3:
                            a = 1;
                            b = 2;
                            e = 0.5;
                            break;
                        case 4:
                            a = 1;
                            b = 2;
                            e = 1;
                            break;
                        case 5:
                            a = 1;
                            b = 2;
                            e = 5;
                            break;
                        default:
                            a = 0;
                            b = -1;
                            e = 0.1;
                            break;
                    }
                    Console.Write("\nValues for (a,b,e) = ({0},{1},{2})\n", a, b, e);
                    FunctionMatrix F2A = new FunctionMatrix(1, 2);
                    F2A[0, 0] = x => x[2];
                    F2A[0, 1] = x => e * x[2] * (1 - x[2] * x[2]) - x[1];
                    F = F2A;
                    dim = 3;
                    break;
                case 3:
                    Console.Write("\nWe will work with\n\nx'[t] = -y[t] - z[t]\ny'[t] = x[t] + 0.1 y[t]\nz'[t] = 0.1 + (x[t] - c) z[t]\nx[0] = y[0] = z[0] = 2 \nWe will assume 0<=t<=200\n");
                    Console.Write("\n\nEnter a value for c (4,6,8.5,8.7,9,12,12.8,13,18):");
                    str = Console.ReadLine();
                    bool b3 = double.TryParse(str, out choice3);
                    while (!b3)
                    {
                        Console.Write("\nInvalid Input. Please enter a double for c: ");
                        str = Console.ReadLine();
                        b3 = double.TryParse(str, out choice3);
                    }
                    if (b3)
                        c = choice3;
                    else
                        c = 4;
                    FunctionMatrix F3A = new FunctionMatrix(1, 3);
                    F3A[0, 0] = x => -x[2] - x[3];
                    F3A[0, 1] = x => x[1] + 0.1 * x[2];
                    F3A[0, 2] = x => 0.1 + (x[1] - c) * x[3];
                    F = F3A;
                    dim = 4;
                    break;
                default:
                    break;
            }

            Console.Write("\n4 Methods: \n\n(1) Runga Kutta\n(2) 4 Step Adam-Bashforth predictor and 3 step Adams Moutlon corrector\n(3) Runga Kutta Fehlberg with Adaptive Step Size Control\n(4) ABM Predictor Correct with Adaptive Step Size Control\n\nEnter an integer between 1 and 4:");
            str = Console.ReadLine();
            bool b4 = int.TryParse(str, out choice4);
            while (!b4 || ((choice4 < 1) || (choice4 > 4)))
            {
                Console.Write("\nInvalid Input. Please enter an integer between 1 and 4: ");
                str = Console.ReadLine();
                b4 = int.TryParse(str, out choice4);
            }

            switch (choice4)
            {
                case 1:
                    Console.Write("\nYou chose Runga Kutta.\n");
                    break;
                case 2:
                    Console.Write("\nYou chose 4 Step Adam-Bashforth predictor and 3 step Adams Moutlon corrector.\n");
                    break;
                case 3:
                    Console.Write("\nYou chose Runga Kutta Fehlberg with Adaptive Step Size Control.\n");
                    break;
                case 4:
                    Console.Write("\nYou chose ABM Predictor Corrector with Adaptive Step Size Control.\n");
                    break;
                default:
                    break;
            }

            DR p = new DR();

            //if the user chooses either Method 1 or 2, they also need to specify the step size
            double stepsize = 0;
            bool b5 = false;
            if ((choice4 == 1) || (choice4 == 2))
            {
                Console.Write("\nNow please enter the step size, h:");
                str = Console.ReadLine();
                b5 = double.TryParse(str, out stepsize);
                while (!b5)
                {
                    Console.Write("\nInvalid Input. Please enter a double: ");
                    str = Console.ReadLine();
                    b5 = double.TryParse(str, out stepsize);
                }
            }

            bool b6 = false;
            bool b7 = false;
            bool b8 = false;
            double stepsizelower = 0;
            double tolerance = 0;
            string strstepsize = null;
            string strstepsizelower = null;
            string strtolerance = null;

            //if the user chooses 3 or 4 they have enter the step size, the lower step size and the tolerance
            if ((choice4 == 3) || (choice4 == 4))
            {
                Console.Write("\nNow please enter the step size:");
                strstepsize = Console.ReadLine();
                b6 = double.TryParse(strstepsize, out stepsize);
                while (!b6)
                {
                    Console.Write("\nInvalid Input. Please re-enter a doubles: ");
                    strstepsize = Console.ReadLine();
                    b6 = double.TryParse(strstepsize, out stepsize);

                }
                Console.Write("\nNow please enter the step size lower:");
                strstepsizelower = Console.ReadLine();
                b7 = double.TryParse(strstepsizelower, out stepsizelower);
                while (!b7)
                {
                    Console.Write("\nInvalid Input. Please re-enter a doubles: ");
                    strstepsizelower = Console.ReadLine();
                    b7 = double.TryParse(strstepsizelower, out stepsizelower);
                }
                Console.Write("\nNow please enter the tolerance:");
                strtolerance = Console.ReadLine();
                b8 = double.TryParse(strtolerance, out tolerance);
                while (!b8)
                {
                    Console.Write("\nInvalid Input. Please re-enter a doubles: ");
                    strtolerance = Console.ReadLine();
                    b8 = double.TryParse(strtolerance, out tolerance);
                }
            }

            //now we have all the parameters we need so we can call the appropriate method
            double[] startingvalues = new double[dim];
            double upper = 0;
            double lower = 0;
            if (dim == 2)
            {
                startingvalues[0] = 1;
                startingvalues[1] = 0;
                upper = 4.8;
                lower = 1;
            }
            if (dim == 3)
            {
                startingvalues[0] = 0;
                startingvalues[1] = a;
                startingvalues[2] = b;
                upper = 20;
                lower = 0;
            }
            if (dim == 4)
            {
                startingvalues[0] = 0;
                startingvalues[1] = 2;
                startingvalues[2] = 2;
                startingvalues[3] = 2;
                upper = 200;
                lower = 0;
            }

            //depending on which model the user chose, we will run a particular method
            //and print the results to screen
            switch (choice4)
            {
                case 1:
                    double[,] output1 = p.RungeKutta(F, startingvalues, stepsize, lower, upper);
                    if (dim == 2)
                        Console.Write("\nThe output (x,y[x]) is:\n\n");
                    else if (dim == 3)
                        Console.Write("\nThe output (t,y[t],y'[t]) is:\n\n");
                    else
                        Console.Write("\nThe output (t,x[t],y[t],z[t]) is:\n\n");
                    Thread.Sleep(4000);
                    for (int i = 0; i < output1.GetLength(0); i++)
                    {
                        if (dim == 2)
                            Console.Write("({0,3:F5},{1,3:F5}) ", output1[i, 0], output1[i, 1]);
                        else if (dim == 3)
                            Console.Write("({0,3:F5},{1,3:F5},{2,3:F5}) ", output1[i, 0], output1[i, 1], output1[i, 2]);
                        else
                            Console.Write("({0,3:F5},{1,3:F5},{2,3:F5},{3,3:F5}) ", output1[i, 0], output1[i, 1], output1[i, 2], output1[i, 3]);
                    }
                    int nrows = output1.GetLength(0);
                    int ncols = output1.GetLength(1);
                    temp = new double[nrows, ncols];
                    for (int i = 0; i < nrows; i++)
                        for (int j = 0; j < ncols; j++)
                            temp[i, j] = output1[i, j];
                    break;
                case 2:
                    double[,] output2 = p.ABMSolve(F, startingvalues, stepsize, lower, upper);
                    if (dim == 2)
                        Console.Write("\nThe output (x,y[x]) is:\n\n");
                    else if (dim == 3)
                        Console.Write("\nThe output (t,y[t],y'[t]) is:\n\n");
                    else
                        Console.Write("\nThe output (t,x[t],y[t],z[t]) is:\n\n");
                    Thread.Sleep(4000);
                    for (int i = 0; i < output2.GetLength(0); i++)
                    {
                        if (dim == 2)
                            Console.Write("({0,3:F5},{1,3:F5}) ", output2[i, 0], output2[i, 1]);
                        else if (dim == 3)
                            Console.Write("({0,3:F5},{1,3:F5},{2,3:F5}) ", output2[i, 0], output2[i, 1], output2[i, 2]);
                        else
                            Console.Write("({0,3:F5},{1,3:F5},{2,3:F5},{3,3:F5}) ", output2[i, 0], output2[i, 1], output2[i, 2], output2[i, 3]);
                    }
                    nrows = output2.GetLength(0);
                    ncols = output2.GetLength(1);
                    temp = new double[nrows, ncols];
                    for (int i = 0; i < nrows; i++)
                        for (int j = 0; j < ncols; j++)
                            temp[i, j] = output2[i, j];
                    break;
                case 3:
                    double[,] output3 = p.RKFSolve(F, startingvalues, stepsizelower, stepsize, lower, upper, tolerance);
                    if (dim == 2)
                        Console.Write("\nThe output (x,y[x]) is:\n\n");
                    else if (dim == 3)
                        Console.Write("\nThe output (t,y[t],y'[t]) is:\n\n");
                    else
                        Console.Write("\nThe output (t,x[t],y[t],z[t]) is:\n\n");
                    Thread.Sleep(4000);
                    for (int i = 0; i < output3.GetLength(0); i++)
                    {
                        if (dim == 2)
                            Console.Write("({0,3:F5},{1,3:F5}) ", output3[i, 0], output3[i, 1]);
                        else if (dim == 3)
                            Console.Write("({0,3:F5},{1,3:F5},{2,3:F5}) ", output3[i, 0], output3[i, 1], output3[i, 2]);
                        else
                            Console.Write("({0,3:F5},{1,3:F5},{2,3:F5},{3,3:F5}) ", output3[i, 0], output3[i, 1], output3[i, 2], output3[i, 3]);
                    }
                    nrows = output3.GetLength(0);
                    ncols = output3.GetLength(1);
                    temp = new double[nrows, ncols];
                    for (int i = 0; i < nrows; i++)
                        for (int j = 0; j < ncols; j++)
                            temp[i, j] = output3[i, j];
                    break;
                case 4:
                    double[,] output4 = p.ABMStepSizeSolve(F, startingvalues, stepsizelower, stepsize, lower, upper, tolerance);
                    if (dim == 2)
                        Console.Write("\nThe output (x,y[x]) is:\n\n");
                    else if (dim == 3)
                        Console.Write("\nThe output (t,y[t],y'[t]) is:\n\n");
                    else
                        Console.Write("\nThe output (t,x[t],y[t],z[t]) is:\n\n");
                    Thread.Sleep(4000);
                    for (int i = 0; i < output4.GetLength(0); i++)
                    {
                        if (dim == 2)
                            Console.Write("({0,3:F5},{1,3:F5}) ", output4[i, 0], output4[i, 1]);
                        else if (dim == 3)
                            Console.Write("({0,3:F5},{1,3:F5},{2,3:F5}) ", output4[i, 0], output4[i, 1], output4[i, 2]);
                        else
                            Console.Write("({0,3:F5},{1,3:F5},{2,3:F5},{3,3:F5}) ", output4[i, 0], output4[i, 1], output4[i, 2], output4[i, 3]);
                    }
                    nrows = output4.GetLength(0);
                    ncols = output4.GetLength(1);
                    temp = new double[nrows, ncols];
                    for (int i = 0; i < nrows; i++)
                        for (int j = 0; j < ncols; j++)
                            temp[i, j] = output4[i, j];
                    break;
                default:
                    break;
            }

            //if we want to see how the estimated solution compares to the exact solution for system 1, the following is executed
            if (choice1 == 1)
            {
                Console.Write("\n\nFor System 1, how good is our estimate?\nTo see this will will present (x, yexact - yest):\n\n");
                Thread.Sleep(4000);
                int r = temp.GetLength(0);
                for (int i = 0; i < r; i++)
                {
                    double x = temp[i, 0];
                    double yexact = x * Math.Tan(Math.Log(x));
                    double yest = temp[i, 1];
                    Console.Write("({0,3:F5},{1,3:F5}) ", x, yexact - yest);
                }
            }

            Console.ReadLine();
            //end of main
        }
        public double[,] RungeKutta(FunctionMatrix f, double[] vals, double h, double lower, double upper)
        {
            int steps = (int)((upper - lower) / h);
            int m = vals.GetLength(0);
            double[,] ans = new double[steps, m];
            double[] valsold = new double[m];
            for (int i = 0; i < m; i++)
            {
                valsold[i] = vals[i];
            }
            for (int i = 0; i < steps; i++)
            {
                //add the old vals to the "ans" matrix
                for (int j = 0; j < m; j++)
                {
                    ans[i, j] = valsold[j];
                }

                double[] valsnew = RK4Step(f, valsold, h);

                //now reassign the values
                for (int k = 0; k < m; k++)
                {
                    valsold[k] = valsnew[k];
                }
            }

            //return the answer
            return ans;

            //end of method
        }
        public double[,] RKFStep(FunctionMatrix f, double[] vals, double h)
        {
            int neqns = f.Cols;
            int m = vals.GetLength(0);
            //arrays to contain the output of the update
            double[] update = new double[m];
            double[] updatenew = new double[m];
            double[] R = new double[m];
            double[,] output = new double[3, m];

            double[] F1 = new double[neqns];
            double[] F2 = new double[neqns];
            double[] F3 = new double[neqns];
            double[] F4 = new double[neqns];
            double[] F5 = new double[neqns];
            double[] F6 = new double[neqns];

            //Calculating F1i
            for (int i = 0; i < neqns; i++)
            {
                F1[i] = h * f[0, i](vals);
            }

            //next we need to get the updated vector in order to calculate F2i
            double[] f2vals = new double[m];
            f2vals[0] = vals[0] + h / 4.0;
            for (int i = 1; i < m; i++)
            {
                f2vals[i] = vals[i] + F1[i - 1] / 4.0;
            }
            //Calculating F2i
            for (int i = 0; i < neqns; i++)
            {
                F2[i] = h * f[0, i](f2vals);
            }

            //next we need to get the updated vector in order to calculate F3i
            double[] f3vals = new double[m];
            f3vals[0] = vals[0] + 3.0 * h / 8.0;
            for (int i = 1; i < m; i++)
            {
                f3vals[i] = vals[i] + 3 * F1[i - 1] / 32.0 + 9 * F2[i - 1] / 32.0;
            }
            //Calculating F3i
            for (int i = 0; i < neqns; i++)
            {
                F3[i] = h * f[0, i](f3vals);
            }

            //next we need to get the updated vector in order to calculate F4i
            double[] f4vals = new double[m];
            f4vals[0] = vals[0] + 12.0 * h / 13.0;
            for (int i = 1; i < m; i++)
            {
                f4vals[i] = vals[i] + 1932.0 * F1[i - 1] / 2197.0 - 7200.0 * F2[i - 1] / 2197.0 + 7296.0 * F3[i - 1] / 2197.0;
            }
            //Calculating F4i
            for (int i = 0; i < neqns; i++)
            {
                F4[i] = h * f[0, i](f4vals);
            }

            //next we need to get the updated vector in order to calculate F5i
            double[] f5vals = new double[m];
            f5vals[0] = vals[0] + h;
            for (int i = 1; i < m; i++)
            {
                f5vals[i] = vals[i] + 439.0 * F1[i - 1] / 216.0 - 8.0 * F2[i - 1] + 3680.0 * F3[i - 1] / 513.0 - 845.0 * F4[i - 1] / 4104.0;
            }
            //Calculating F5i
            for (int i = 0; i < neqns; i++)
            {
                F5[i] = h * f[0, i](f5vals);
            }

            //next we need to get the updated vector in order to calculate F6i
            double[] f6vals = new double[m];
            f6vals[0] = vals[0] + h / 2.0;
            for (int i = 1; i < m; i++)
            {
                f6vals[i] = vals[i] - 8.0 * F1[i - 1] / 27.0 + 2.0 * F2[i - 1] - 3544.0 * F3[i - 1] / 2565.0 + 1859.0 * F4[i - 1] / 4104.0 - 11.0 * F5[i - 1] / 40.0;
            }
            //Calculating F6i
            for (int i = 0; i < neqns; i++)
            {
                F6[i] = h * f[0, i](f6vals);
            }

            //calculating the update
            update[0] = vals[0] + h;
            for (int i = 1; i < m; i++)
            {
                update[i] = vals[i] + 25.0 * F1[i - 1] / 216.0 + 1408.0 * F3[i - 1] / 2565.0 + 2197.0 * F4[i - 1] / 4104.0 - F5[i - 1] / 5.0;
            }

            //calculating updatenew
            updatenew[0] = vals[0] + h;
            for (int i = 1; i < m; i++)
            {
                updatenew[i] = vals[i] + 16.0 * F1[i - 1] / 135.0 + 6656.0 * F3[i - 1] / 12825.0 + 28561.0 * F4[i - 1] / 56430.0 - 9 * F5[i - 1] / 50.0 + 2 * F6[i - 1] / 55.0;
            }

            //calculating updatenew
            R[0] = 0;
            for (int i = 1; i < m; i++)
            {
                R[i] = 1 / h * (Math.Abs(F1[i - 1] / 360.0 - 128.0 * F3[i - 1] / 4275.0 - 2197.0 * F4[i - 1] / 75240.0 + F5[i - 1] / 50.0 + 2 * F6[i - 1] / 55.0));
            }

            //updating the output array
            for (int i = 0; i < m; i++)
            {
                output[0, i] = update[i];
                output[1, i] = updatenew[i];
                output[2, i] = R[i];
            }

            return output;
        }