/// <summary> /// Finds a vector argument which makes a vector function zero. /// </summary> /// <param name="f">The vector function.</param> /// <param name="x0">The vector argument.</param> /// <returns>The vector argument which makes all components of the vector function zero.</returns> /// <exception cref="ArgumentNullException"><paramref name="f"/> or <paramref name="x0"/> is null.</exception> /// <exception cref="DimensionMismatchException">The dimension of <paramref name="f"/> is not equal to the /// dimension of <paramref name="x0"/>.</exception> public static ColumnVector FindZero(Func <IList <double>, IList <double> > f, IList <double> x0) { if (f == null) { throw new ArgumentNullException("f"); } if (x0 == null) { throw new ArgumentNullException("x0"); } // we will use Broyden's method, which is a generalization of the secant method to the multi-dimensional problem // just as the secant method is essentially Newton's method with a crude, numerical value for the slope, // Broyden's method is a multi-dimensional Newton's method with a crude, numerical value for the Jacobian matrix int d = x0.Count; // we should re-engineer this code to work directly on vector/matrix storage rather than go back and forth // between vectors and arrays, but for the moment this works; implementing Blas2 rank-1 update would help // starting values ColumnVector x = new ColumnVector(x0); //double[] x = new double[d]; Blas1.dCopy(x0, 0, 1, x, 0, 1, d); ColumnVector F = new ColumnVector(f(x.ToArray())); //double[] F = f(x); if (F.Dimension != d) { throw new DimensionMismatchException(); } SquareMatrix B = ApproximateJacobian(f, x0); double g = MoreMath.Pow2(F.Norm()); //double g = Blas1.dDot(F, 0, 1, F, 0, 1, d); for (int n = 0; n < Global.SeriesMax; n++) { // determine the Newton step LUDecomposition LU = B.LUDecomposition(); ColumnVector dx = -LU.Solve(F); //for (int i = 0; i < d; i++) { // Console.WriteLine("F[{0}]={1} x[{0}]={2} dx[{0}]={3}", i, F[i], x[i], dx[i]); //} // determine how far we will move along the Newton step ColumnVector x1 = x + dx; ColumnVector F1 = new ColumnVector(f(x1)); double g1 = MoreMath.Pow2(F1.Norm()); // check whether the Newton step decreases the function vector magnitude // NR suggest that it's necessary to ensure that it decrease by a certain amount, but I have yet to see that make a diference double gm = g - 0.0; if (g1 > gm) { // the Newton step did not reduce the function vector magnitude, so we won't step that far // determine how far along the descent direction we will step by parabolic interpolation double z = g / (g + g1); //Console.WriteLine("z={0}", z); // take at least a small step in the descent direction if (z < (1.0 / 16.0)) { z = 1.0 / 16.0; } dx = z * dx; x1 = x + dx; F1 = new ColumnVector(f(x1.ToArray())); g1 = MoreMath.Pow2(F1.Norm()); // NR suggest that this be repeated, but I have yet to see that make a difference } // take the step x = x + dx; // check for convergence if ((F1.InfinityNorm() < Global.Accuracy) || (dx.InfinityNorm() < Global.Accuracy)) { //if ((g1 < Global.Accuracy) || (dx.Norm() < Global.Accuracy)) { return(x); } // update B ColumnVector dF = F1 - F; RectangularMatrix dB = (dF - B * dx) * (dx / (dx.Transpose() * dx)).Transpose(); //RectangularMatrix dB = F1 * ( dx / MoreMath.Pow(dx.Norm(), 2) ).Transpose(); for (int i = 0; i < d; i++) { for (int j = 0; j < d; j++) { B[i, j] += dB[i, j]; } } // prepare for the next iteration F = F1; g = g1; } throw new NonconvergenceException(); }