/* Finding roots. * * Contents: * 1. The ESL_ROOTFINDER object. * 2. One-dimensional root finding. * 3. Unit tests. * 4. Test driver. * 5. Examples. */ #include #include #include "easel.h" #include "esl_rootfinder.h" /***************************************************************** * 1. The ESL_ROOTFINDER object. *****************************************************************/ /* Function: esl_rootfinder_Create() * Synopsis: Creates ESL_ROOTFINDER for an $f(x)$ * Incept: SRE, Tue Apr 10 19:54:09 2007 [Janelia] * * Purpose: Create a rootfinder to find a root of a function $f(x) = 0$. * <(*func)()> is a pointer to an implementation of the * function $f(x)$. is a generic pointer to any * parameters or storage needed in <(*func)()> other than * the value of $x$. * * Caller implements a that takes three arguments. * The first two are the value , and a void pointer to * any additional parameters that $f(x)$ depends on. The * result, $f(x)$, is returned via the third argument. This * function must return to indicate success. Upon * error, it may throw any error code it wishes. * * * Args: (*func)() - ptr to function that evaluates f(x) * params - ptr to parameters to be passed to (*func)() * * Returns: pointer to a new structure. * * Throws: on allocation failure. */ ESL_ROOTFINDER * esl_rootfinder_Create(int (*func)(double, void*, double*), void *params) { int status; ESL_ROOTFINDER *R = NULL; ESL_ALLOC(R, sizeof(ESL_ROOTFINDER)); R->func = func; R->fdf = NULL; /* unused */ R->params = params; R->xl = -eslINFINITY; /* not set yet */ R->fl = 0.; /* not set yet */ R->xr = eslINFINITY; /* not set yet */ R->fr = 0.; /* not set yet */ R->x0 = 0.; /* not set yet */ R->f0 = 0.; /* not set yet */ R->x = 0.; /* not set yet */ R->fx = 0.; /* not set yet */ R->dfx = 0.; /* unused */ R->iter = 0; R->abs_tolerance = 1e-12; R->rel_tolerance = 1e-12; R->residual_tol = 0.; R->max_iter = 100; return R; ERROR: esl_rootfinder_Destroy(R); return NULL; } /* Function: esl_rootfinder_CreateFDF() * Synopsis: Creates ESL_ROOTFINDER that uses both $f(x)$, $f'(x)$ * Incept: SRE, Tue Apr 10 20:47:42 2007 [Janelia] * * Purpose: Create a rootfinder that will find * a root of a function $f(x) = 0$ using first derivative * information $f'(x)$. * * Caller provides a pointer <*fdf()> to a function that * takes four arguments. The first two are the current * value, and a void pointer to any additional parameters * that $f(x)$ depends on. <*fdf()> calculates the function * $f(x)$ and the derivative $f'(x)$ and returns them * through the remaining two arguments. * * Args: (*fdf)() - ptr to function that returns f(x) and f'(x) * params - ptr to parameters to be passed to (*fdf)() * * Returns: pointer to a new structure. * * Throws: on allocation failure. */ ESL_ROOTFINDER * esl_rootfinder_CreateFDF(int (*fdf)(double, void*, double*, double*), void *params) { int status; ESL_ROOTFINDER *R = NULL; ESL_ALLOC(R, sizeof(ESL_ROOTFINDER)); R->func = NULL; R->fdf = fdf; R->params = params; R->xl = -eslINFINITY; R->fl = 0.; /* unused */ R->xr = eslINFINITY; R->fr = 0.; /* unused */ R->x0 = 0.; R->f0 = 0.; R->x = 0.; /* not set yet */ R->fx = 0.; /* not set yet */ R->dfx = 0.; /* not set yet */ R->iter = 0; R->abs_tolerance = 1e-15; R->rel_tolerance = 1e-15; R->residual_tol = 0.; R->max_iter = 100; return R; ERROR: esl_rootfinder_Destroy(R); return NULL; } /* Function: esl_rootfinder_SetBrackets() * Incept: SRE, Wed Apr 11 08:35:10 2007 [Janelia] * * Purpose: Declare that a root is in the open interval * <(xl..xr)>. * * The function will be evaluated at both points. * * Args: R - rootfinder structure * xl,xr - root lies in open interval (xl..xr) * * Returns: on success. * * Throws: if cannot bracket a root, * because $f(x_l)$ and $f(x_r)$ do not have opposite * signs. * * Additionally, if either evaluation fails in the * caller-provided function, the error code from that * failure will be thrown. */ int esl_rootfinder_SetBrackets(ESL_ROOTFINDER *R, double xl, double xr) { int status; double dfx; R->xl = xl; R->xr = xr; if (R->func != NULL) { if ((status = (*R->func)(R->xl, R->params, &(R->fl))) != eslOK) return status; if ((status = (*R->func)(R->xr, R->params, &(R->fr))) != eslOK) return status; } else { if ((status = (*R->fdf) (R->xl, R->params, &(R->fl), &dfx)) != eslOK) return status; if ((status = (*R->fdf) (R->xr, R->params, &(R->fr), &dfx)) != eslOK) return status; } if (R->fl * R->fr >= 0) ESL_EXCEPTION(eslEINVAL, "xl,xr do not bracket a root"); return eslOK; } int esl_rootfinder_SetAbsoluteTolerance(ESL_ROOTFINDER *R, double tol) { R->abs_tolerance = tol; return eslOK; } int esl_rootfinder_SetRelativeTolerance(ESL_ROOTFINDER *R, double tol) { R->rel_tolerance = tol; return eslOK; } int esl_rootfinder_SetResidualTolerance(ESL_ROOTFINDER *R, double tol) { R->residual_tol = tol; return eslOK; } int esl_rootfinder_SetMaxIterations(ESL_ROOTFINDER *R, int maxiter) { R->max_iter = maxiter; return eslOK; } void esl_rootfinder_Destroy(ESL_ROOTFINDER *R) { if (R == NULL) return; free(R); } /***************************************************************** * 2. One-dimensional root finding. *****************************************************************/ /* Function: esl_root_Bisection() * Synopsis: Find a root of $f(x)$ by bisection method. * Incept: SRE, Wed Apr 11 08:40:11 2007 [Janelia] * * Purpose: Find a root in the open interval by the bisection method, * and return it in . * * The bisection method is guaranteed to succeed, provided * that , do indeed bracket a root, though it may * be slow. * * The rootfinder can be created either by * or * ; if the latter (if the * function in the rootfinder includes derivative * information), the bisection method will just ignore * the derivative. * * Args: R - a rootfinder object for the function * xl,xr - bounds of an open interval in which a root lies * ret_x - RETURN: a root that satisfies $f(x) = 0$. * * Returns: on success, and points to a root. * * Throws: if do not bracket a root. * if the method exceeds the maximum number of * iterations set in . * * Additionally, any failure code that the caller-provided * function $f(x)$ throws. */ int esl_root_Bisection(ESL_ROOTFINDER *R, double xl, double xr, double *ret_x) { int status; double xmag; if ((status = esl_rootfinder_SetBrackets(R, xl, xr)) != eslOK) goto ERROR; while (1) { R->iter++; if (R->iter > R->max_iter) ESL_XEXCEPTION(eslENOHALT, "failed to converge in Bisection"); /* Bisect and evaluate the function */ R->x = (R->xl+R->xr)/2.; if (R->func != NULL) { if ((status = (*R->func)(R->x, R->params, &(R->fx))) != eslOK) ESL_XEXCEPTION(status, "user-provided function failed"); } else { if ((status = (*R->fdf) (R->x, R->params, &(R->fx), &(R->dfx))) != eslOK) ESL_XEXCEPTION(status, "user-provided function failed"); } /* Test for convergence */ xmag = (R->xl < 0. && R->xr > 0.) ? 0. : R->x; if (R->fx == 0.) break; /* an exact root, lucky */ if (((R->xr-R->xl) < R->abs_tolerance + R->rel_tolerance*xmag) || fabs(R->fx) < R->residual_tol) break; /* Narrow the bracket; pay attention to directionality */ if (R->fl > 0.) { if (R->fx > 0.) { R->xl = R->x; R->fl = R->fx; } else { R->xr = R->x; R->fr = R->fx; } } else { if (R->fx < 0.) { R->xl = R->x; R->fl = R->fx; } else { R->xr = R->x; R->fr = R->fx; } } } *ret_x = R->x; return eslOK; ERROR: *ret_x = 0.0; return status; } /* Function: esl_root_NewtonRaphson() * Synopsis: Find a root of $f(x)$ by Newton/Raphson method. * Incept: SRE, Wed Apr 11 08:56:28 2007 [Janelia] * * Purpose: Find a root by the Newton/Raphson method, starting from * an initial guess . Return the root in . * * The Newton/Raphson method is not guaranteed to succeed, * but when it does, it is much faster than bisection. * * Newton/Raphson uses first derivative information, so the * rootfinder must be created with * for a function that evaluates * both $f(x)$ and $f'(x)$. * * Args: R - a rootfinder object for $f(x)$ and $f'(x)$ * guess - an initial guess for the root * ret_x - RETURN: a root that satisfies $f(x) = 0$. * * Returns: on success, and points to a root. * * Throws: if the method exceeds the maximum number of * iterations set in . * * Additionally, any failure code that the caller-provided * function $f(x)$ throws. */ int esl_root_NewtonRaphson(ESL_ROOTFINDER *R, double guess, double *ret_x) { int status; R->x = guess; if ((status = (*R->fdf)(R->x, R->params, &(R->fx), &(R->dfx))) != eslOK) return status; while (1) { R->iter++; if (R->iter > R->max_iter) ESL_EXCEPTION(eslENOHALT, "failed to converge in Newton"); /* printf("current: x=%20g f(x) = %20g f'(x) = %20g\n", R->x, R->fx, R->dfx); */ /* Take a Newton/Raphson step. */ R->x0 = R->x; R->f0 = R->fx; R->x = R->x - R->fx / R->dfx; (*R->fdf)(R->x, R->params, &(R->fx), &(R->dfx)); /* Test for convergence. */ if (R->fx == 0) break; /* an exact root, lucky */ if ( (fabs(R->x - R->x0) < R->abs_tolerance + R->rel_tolerance*R->x) || fabs(R->fx) < R->residual_tol) break; } *ret_x = R->x; return eslOK; } /***************************************************************** * 3. Unit tests. *****************************************************************/ #ifdef eslROOTFINDER_TESTDRIVE /* For the unit tests, we'll use a quadratic function * f(x) = ax^2 + bx + c = 0 * f'(x) = 2ax + b * where it's easy to set up known roots. */ struct polyparams { double a,b,c; }; static int quadratic_f(double x, void *params, double *ret_fx) { struct polyparams *p = (struct polyparams *) params; *ret_fx = (p->a * x * x + p->b * x + p->c); return eslOK; } static int quadratic_fdf(double x, void *params, double *ret_fx, double *ret_dfx) { struct polyparams *p = (struct polyparams *) params; *ret_fx = (p->a * x * x + p->b * x + p->c); *ret_dfx = (2 * p->a) * x + p->b; return eslOK; } static void utest_Bisection(void) { char msg[] = "esl_rootfinder:: bisection unit test failed"; ESL_ROOTFINDER *R = NULL; struct polyparams p; double x; /* (5x-1)(x+2) = 5x^2 + 9x - 2 with roots 0.2, -2 */ p.a = 5.; p.b = 9.; p.c = -2.; /* find the positive root, 0.2 */ if (( R = esl_rootfinder_Create(quadratic_f, &p) ) == NULL) esl_fatal(msg); if ( esl_root_Bisection(R, 0., 100., &x) != eslOK) esl_fatal(msg); if ( fabs(x-0.2) > R->abs_tolerance) esl_fatal(msg); esl_rootfinder_Destroy(R); /* find the negative root, -2.0 */ if (( R = esl_rootfinder_CreateFDF(quadratic_fdf, &p) ) == NULL) esl_fatal(msg); if ( esl_root_Bisection(R, -100., 0., &x) != eslOK) esl_fatal(msg); if ( fabs(x+2.) > R->abs_tolerance) esl_fatal(msg); esl_rootfinder_Destroy(R); } static void utest_Newton(void) { ESL_ROOTFINDER *R = NULL; struct polyparams p; double x; /* (5x-1)(x+2) = 5x^2 + 9x - 2 with roots 0.2, -2 */ p.a = 5.; p.b = 9.; p.c = -2.; R = esl_rootfinder_CreateFDF(quadratic_fdf, &p); esl_root_NewtonRaphson(R, 1., &x); /* find the positive root, 0.2 */ if (fabs(x-0.2) > R->abs_tolerance) esl_fatal("didn't find root 0.2"); esl_rootfinder_Destroy(R); R = esl_rootfinder_CreateFDF(quadratic_fdf, &p); esl_root_NewtonRaphson(R, -3., &x); /* find the negative root, -2.0 */ if (fabs(x+2.) > R->abs_tolerance) esl_fatal("didn't find root -2"); esl_rootfinder_Destroy(R); } #endif /*eslROOTFINDER_TESTDRIVE*/ /***************************************************************** * 4. Test driver. *****************************************************************/ /* gcc -g -Wall -I. -L. -DeslROOTFINDER_TESTDRIVE -o test esl_rootfinder.c -leasel -lm ./test */ #ifdef eslROOTFINDER_TESTDRIVE int main(int argc, char **argv) { utest_Bisection(); utest_Newton(); return eslOK; } #endif /*eslROOTFINDER_TESTDRIVE*/ /***************************************************************** * 5. Examples. *****************************************************************/ /* An example of bisection. * gcc -g -Wall -o example -I. -DeslROOTFINDER_EXAMPLE esl_rootfinder.c easel.c -lm */ #ifdef eslROOTFINDER_EXAMPLE /*::cexcerpt::rootfinder_example::begin::*/ #include "easel.h" #include "esl_rootfinder.h" struct polyparams { double a,b,c; }; int quadratic_f(double x, void *params, double *ret_fx) { struct polyparams *p = (struct polyparams *) params; *ret_fx = (p->a * x * x + p->b * x + p->c); return eslOK; } int main(void) { ESL_ROOTFINDER *R = NULL; struct polyparams p; double x, fx; p.a = 5.; p.b = 2.; p.c = -1.; R = esl_rootfinder_Create(quadratic_f, &p); esl_root_Bisection(R, 0., 100., &x); quadratic_f(x, &p, &fx); printf("Find an x such that f(x) = %.0fx^2 + %.0fx + %.0f = 0 ...\n", p.a, p.b, p.c); printf("x = %f (f(x) = %f)\n", x, fx); esl_rootfinder_Destroy(R); return 0; } /*::cexcerpt::rootfinder_example::end::*/ #endif /*eslROOTFINDER_EXAMPLE*/ /* An example of Newton/Raphson. * gcc -g -Wall -o example -I. -DeslROOTFINDER_EXAMPLE2 esl_rootfinder.c easel.c -lm */ #ifdef eslROOTFINDER_EXAMPLE2 /*::cexcerpt::rootfinder_example2::begin::*/ #include "easel.h" #include "esl_rootfinder.h" struct polyparams { double a,b,c; }; int quadratic_fdf(double x, void *params, double *ret_fx, double *ret_dfx) { struct polyparams *p = (struct polyparams *) params; *ret_fx = (p->a * x * x + p->b * x + p->c); *ret_dfx = (2 * p->a) * x + p->b; return eslOK; } int main(void) { ESL_ROOTFINDER *R = NULL; struct polyparams p; double x; p.a = 5.; p.b = 2.; p.c = -1.; R = esl_rootfinder_CreateFDF(quadratic_fdf, &p); esl_root_NewtonRaphson(R, -1., &x); printf("Find an x such that f(x) = %.0fx^2 + %.0fx + %.0f = 0 ...\n", p.a, p.b, p.c); printf("x = %f\n", x); esl_rootfinder_Destroy(R); return 0; } /*::cexcerpt::rootfinder_example2::end::*/ #endif /*eslROOTFINDER_EXAMPLE2*/