home *** CD-ROM | disk | FTP | other *** search
- /*
-
- zero - find a zero of a function evaluated by the calling program
-
- SYNOPSIS...
- inversion = init_zero(&x, inversion); -- initialize
- advise_zero(x2, inversion); -- suggest 2nd x value
- ...
- err = zero(y, inversion); -- ask zero() to update x
-
- USAGE...
- init_zero() performs initialization. Its first argument points
- to the independent variable. init_zero() returns a pointer to
- a record allocated from the heap which contains variables
- required by zero(). If the second argument is non-NULL it is
- assumed to be a pointer to a previously allocated record. In
- that case the old record is reused. If the allocation fails,
- init_zero() returns NULL.
-
- zero() performs the actual function inversion. Its first
- argument is the value of the function for the current value of
- the independent variable. The second argument is a pointer to
- the variable record which was allocated and initialized by
- init_zero(). zero() updates the independent variable in an
- attempt to force the function value to zero. It returns an
- error code (see below for values).
-
- The calling program may reset the independent variable before
- evaluating the function. This would be useful for initiation
- (to "show" zero() points straddling the desired value) or if
- zero() happened to set the independent variable just outside
- its legal range. However, use of the same value twice in a row
- will result in an error (code 5).
-
- EXAMPLE...
-
- double target; -- desired function value
- double x=START_VALUE; -- independent variable: initialized here,
- updated by zero()
- double y; -- function value, evaluated here
- int error; -- error status, set nonzero by zero()...
- 0: ok so far
- 1: local extremum
- 2: function value not changing
- 3: not converging fast enough
- 4: repeated x value
- 5: null inversion record pointer
-
- char *inversion=0, *init_zero();
-
- ...
-
- inversion = init_zero(&x, inversion); -- initialize
- do
- {y = (x + 1.)*x*(x - 1.); -- evaluate the function
- del = y - target;
- if(fabs(del) < .00000001) break;
- error = zero(del, inversion); -- ask zero() to update x
- } while(!error);
-
- or simply
-
- inversion = init_zero(&x, 0); -- initialize
- do
- {y = (x + 1.)*x*(x - 1.); -- evaluate the function
- if(fabs(y - target) < .00000001) break;
- } while(!zero(y - target, inversion));
-
- NOTES...
- A programmer might state his problem as follows: "the result of
- this computation is <value>, but I want to adjust <variable> to
- move the result closer to <target>" In such a case, zero()
- can be used to adjust <variable>.
-
- Note that only the interpolation logic, not the function
- evaluation, is relegated to a subprogram. Thus, zero() can
- invert an expression without the programmer having to code it
- as a separate function. This can simplify the source code,
- since the programmer need not create separate functions just
- for the evaluation of simple expressions.
-
- The programmer also maintains full control - he codes the
- stopping criterion and can even reject the values proposed by
- zero().
-
- advise_zero() can be used to suggest a second value of x.
- This may speed the initial part of the search, which normally
- uses small steps for ruggedness.
-
- METHOD...
- The search procedes in two phases. In the first phase, while
- all y values are of the same sign, zero() uses two previous x,y
- pairs to predict the desired x value (a secant search). This
- is an extrapolation and the x step size is restricted to help
- prevent divergence. At most, the step size can double on each
- iteration. The new x,y pair replaces the one further from the
- target.
-
- As soon as both positive and negative y values are found,
- zero() switches to interpolation which is much more reliable.
- In general, zero() performs quadratic interpolation using three
- previous x,y pairs to speed convergence (usually doubling the
- number of correct bits each iteration). In this case, the new
- x,y pair replaces the existing pair on the same side of the
- target. Therefore, zero() will never switch from interpolating
- back to extrapolating.
-
- IMPLEMENTATION...
- zero() must save its state between returns so it can pick up
- where it left off. Recursive calls may also be needed - when
- the calling program evaluates its function, it may use zero()
- to do so. Therefore, zero() needs an independent collection
- of variables for each inversion being performed. Init_zero()
- allocates storage for this, of type INVERSION_REC, in the heap.
- An INVERSION_REC may be considered an "object", init_zero()
- its "constructor", and calls to zero() its "messages". Its
- "destructor" is free(). However, reuse is encouraged (see the
- example).
-
- */
-
- #include <math.h>
- #ifdef MAIN
- #include <assert.h>
- #define __assertfail printf
- #include <stdio.h>
- /*
- #else
- #define assert(x) 0
- */
- #endif
-
- #ifdef MAIN
- int verbose = 1;
- #endif
-
- #define ITERATIONS 50
- #define MAX_REGISTERED 4
-
- typedef struct
- {
- int state_rec, straddling_rec, quadratic_rec;
- int i_rec, *error_rec;
- double extrapolation_limit_rec;
- double x1_rec, x2_rec, x3_rec;
- double f1_rec, f2_rec, f3_rec;
- double d1_rec, d2_rec, d3_rec;
- double del_rec, dx_rec, slope_rec;
- double *xp_rec, difference_rec;
- int suggestion_cnt;
- double suggestion[MAX_REGISTERED];
- } INVERSION_REC;
-
- enum error_status{OK, LOCAL_EXTREMUM, NOT_CHANGING, SLOW_CONVERGENCE,
- REPEATED_X, NULL_POINTER, OUT_OF_MEMORY};
-
- #define RR(x, s) /* printf("%20.5g %s\n", x, s); /**/
-
- #define state irp->state_rec
- #define straddling irp->straddling_rec
- #define quadratic irp->quadratic_rec
- #define i irp->i_rec
- #define error irp->error_rec
- #define x1 irp->x1_rec
- #define x2 irp->x2_rec
- #define x3 irp->x3_rec
- #define f1 irp->f1_rec
- #define f2 irp->f2_rec
- #define f3 irp->f3_rec
- #define d1 irp->d1_rec
- #define d2 irp->d2_rec
- #define d3 irp->d3_rec
- #define del irp->del_rec
- #define difference irp->difference_rec
- #define dx irp->dx_rec
- #define extrapolation_limit irp->extrapolation_limit_rec
- #define slope irp->slope_rec
- #define xp irp->xp_rec
-
- static int junk;
-
- INVERSION_REC *
- init_zero(xp_, former_rec)
- double *xp_; /* ptr to user's variable which holds initial value of
- function's independent variable, updated by zero() */
-
- INVERSION_REC *former_rec; /* points to previous record (which is reused),
- or NULL (so that a new record is malloc'ed) */
- { INVERSION_REC *irp, *malloc();
-
- if(former_rec) irp = former_rec;
- else irp = malloc(sizeof(INVERSION_REC));
- if(irp)
- {
- xp = xp_;
- straddling = 0;
- extrapolation_limit = 2.;
- state = 1;
- irp->suggestion_cnt = 0;
- }
- return irp;
- }
-
- advise_zero(value, irp) double value; INVERSION_REC *irp;
- { if(irp->suggestion_cnt < MAX_REGISTERED)
- irp->suggestion[irp->suggestion_cnt++] = value;
- }
-
- zero(value, irp) double value; INVERSION_REC *irp;
- { double x31, x32, x312;
- register double temp;
-
- if(!irp)
- {return NULL_POINTER; /* FAIL - null pointer */
- }
-
- switch(state)
- {
- case 1: goto loc1;
- case 2: goto loc2;
- default: goto loc3;
- }
-
- loc1:
-
- x1 = *xp;
- f1 = value;
- d1 = fabs(f1);
- if(x1 == 0.) del = .01; else del = .01*x1;
-
-
- /*
- first, look for two x's with
- different function values
- */
- for (i = 0; i < ITERATIONS; i++)
- {
- if(irp->suggestion_cnt)
- {*xp = irp->suggestion[--irp->suggestion_cnt];
- #ifdef MAIN
- if(verbose) printf("suggested -- \n");
- #endif
- }
- else
- {
- RR(del, "del (zero)");
- *xp = x1 + del;
- #ifdef MAIN
- if(verbose) printf("step -- \n");
- #endif
- }
- RR(*xp, "*xp (zero)");
-
- state = 2;
- return OK; /* get 2nd function value */
- loc2:
-
- RR(value, "value (zero)");
- if(value != f1) break;
- del *= -1.5; /* look on both sides of the starting point */
- }
- /* save 2nd function value */
- x2 = *xp;
- f2 = value;
- d2 = fabs(f2);
- if(f1*f2 <= 0.) straddling = 1;
- else if(d1 < d2)
- { /* swap points 1 and 2 so f2 will be closer to target */
- temp = x1; x1 = x2; x2 = temp;
- temp = f1; f1 = f2; f2 = temp;
- temp = d1; d1 = d2; d2 = temp;
- }
-
- del = fabs(del);
- difference = d2;
- quadratic = 0;
- for (i = 0; i < ITERATIONS; i++)
- {
- if(irp->suggestion_cnt)
- {*xp = irp->suggestion[--irp->suggestion_cnt];
- del = fabs(*xp - x2);
- #ifdef MAIN
- if(verbose) printf("suggested -- \n");
- #endif
- }
- else if(quadratic)
- {
- #ifdef MAIN
- if(verbose) printf("quadratic -- \n");
- #endif
- del = fabs(*xp - x2);
- quadratic = 0;
- }
- else
- {
- #ifdef MAIN
- if(verbose) printf(" linear -- \n");
- #endif
- if(x1 == x2)
- {return REPEATED_X; /* FAIL - same x value twice in a row */
- }
-
- slope = (f2 - f1)/(x2 - x1);
-
- if(slope == 0.)
- {return NOT_CHANGING; /* FAIL - function value not changing */
- }
-
- dx = -f2/slope;
-
- if(!straddling && fabs(dx) > extrapolation_limit*del)
- dx *= extrapolation_limit*del/fabs(dx); /* limit step size */
-
- del = fabs(dx);
- *xp = x2 + dx;
- }
-
- state = 3;
- return OK; /* get 3rd or following function value */
- loc3:
-
- x3 = *xp;
- f3 = value;
- d3 = f3;
- if(straddling)
- {
- #ifndef LINEAR
- if(quadratic = ((x3-x1)*(x2-x3) > 0. && (f3-f1)*(f2-f3) > 0.))
- {
- /*
- x3 is strictly between x1 and x2 (preventing division
- by 0), and f3 is strictly between f1 and f2 (permitting
- interpolation). Use Aitkin's Iteration method to find
- a polynomial in y that passes through the three most
- recent points (see Abramowitz & Stegun 25.2.23).
- Evaluate the polynomial at the target value of y to
- find a candidate value for x.
- */
- #define aitkins(x0, f0, x1, f1, x) (f0*(x1-x) - f1*(x0-x))/(x1-x0)
- x31 = aitkins(f3, x3, f1, x1, 0.);
- x32 = aitkins(f3, x3, f2, x2, 0.);
- x312 = aitkins(f1, x31, f2, x32, 0.);
- /* insist that the new x be between x1 and x2 */
- if((x312 - x1)*(x2 - x312) > 0.) *xp = x312;
- else quadratic = 0;
- }
- #endif
- #define CASE(num) if(verbose) printf("-- case %d ", num)
- if(d2*d3 <= 0.)
- {
- /* we have: 2
- /
- --------------- <-- y = 0
- /
- 3
- /
- 1
-
- ...so we discard point 1
- */
- #ifdef MAIN
- CASE(1); /* straddling, new point same side as point 1 */
- #endif
- d1 = d3;
- x1 = x3;
- f1 = f3;
- }
- else
- {
- /* we have: 2
- /
- 3
- /
- --------------- <-- y = 0
- /
- 1
-
- ...so we discard point 2
- */
- #ifdef MAIN
- CASE(2); /* straddling, new point same side as point 2 */
- #endif
- d2 = d3;
- x2 = x3;
- f2 = f3;
- }
- difference = fabs(d3);
- }
- else if(d3*f2 <= 0.)
- {straddling = 1; /* just now straddling target */
- #ifdef MAIN
- if(verbose) printf("-- straddling --\n");
- #endif
- x31 = aitkins(f3, x3, f1, x1, 0.);
- x32 = aitkins(f3, x3, f2, x2, 0.);
- x312 = aitkins(f1, x31, f2, x32, 0.);
-
- /* insist that the new x be between x3 and x2 */
- if(quadratic = ((x312 - x3)*(x2 - x312) > 0.))
- *xp = x312;
- if(d2 < d1)
- {
- /* we have: 1
- /
- 2
- /
- --------------- <-- y = 0
- /
- 3
-
- ...so we discard point 1
- */
- #ifdef MAIN
- CASE(3); /* just now straddling */
- #endif
- d1 = d3;
- x1 = x3;
- f1 = f3;
- d2 = f2;
- difference = fabs(d3);
- }
- else
- {
- /* we have: 2
- /
- 1
- /
- --------------- <-- y = 0
- /
- 3
-
- ...so we discard point 2
- */
- #ifdef MAIN
- CASE(4); /* just now straddling */
- #endif
- d2 = d3;
- x2 = x3;
- f2 = f3;
- d1 = f1;
- difference = fabs(d3);
- }
- }
- else
- {
- d3 = fabs(d3);
- if(d3 < d2)
- {
- /* we have: 1
- /
- 2
- /
- 3
- /
- --------------- <-- y = 0
-
- ...so we discard point 1
- */
- #ifdef MAIN
- CASE(5); /* still extrapolating, new point is closer */
- #endif
- /* this extrapolation will speed
- convergence for a high order zero,
- but has little effect for normal cases */
- if((x2-x3)*(x1-x2) > 0 && (f2-f3)*(f1-f2) > 0)
- {
- x31 = aitkins(f3, x3, f1, x1, 0.);
- x32 = aitkins(f3, x3, f2, x2, 0.);
- x312 = aitkins(f1, x31, f2, x32, 0.);
- if(quadratic = ((x3-x312)*(x2-x3) > 0)
- && (fabs(x312-x3) < extrapolation_limit*del))
- *xp = x312;
- }
-
- d1 = d2; d2 = d3;
- x1 = x2; x2 = x3;
- f1 = f2; f2 = f3;
- }
- else if(d3 < d1)
- {
- /* we have: 1
- 3 /
- \ 2
- \____/
- --------------- <-- y = 0
- ...so we discard point 1 but look between 2 and 3
- */
- #ifdef MAIN
- CASE(6); /* still extrapolating, new point between 1 and 2 */
- #endif
- if(i > 7)
- {return LOCAL_EXTREMUM; /* FAIL - local extremum */
- }
- d1 = d3;
- x1 = x3;
- f1 = f3;
- *xp = (x2 + x3)/2.;
- quadratic = 1;
- }
- else
- {
- /* we have: 3
- \ 1
- \ /
- \ 2
- \____/
- --------------- <-- y = 0
- ...so we look closer to point 2
- */
- #ifdef MAIN
- CASE(7); /* still extrapolating, new point worse than previous */
- #endif
- if(i > 7)
- {return LOCAL_EXTREMUM; /* FAIL - local extremum */
- }
- extrapolation_limit = .5;
- }
- difference = d2;
-
- #ifdef MAIN
- assert (d2<=d1);
- #endif
- }
- }
- return SLOW_CONVERGENCE; /* FAIL - not converging fast enough */
- }
-
-
- #ifdef MAIN
-
- show_history(irp) INVERSION_REC *irp;
- {
- printf("x1 =%11.7f f1 =%11.7f\n", x1, f1);
- printf("x2 =%11.7f f2 =%11.7f\n", x2, f2);
- printf("x3 =%11.7f f3 =%11.7f\n", x3, f3);
- }
-
- #undef state
- #undef straddling
- #undef i
- #undef error
- #undef x1
- #undef x2
- #undef x3
- #undef f1
- #undef f2
- #undef f3
- #undef d1
- #undef d2
- #undef d3
- #undef del
- #undef difference
- #undef dx
- #undef extrapolation_limit
- #undef slope
- #undef xp
-
- double fun(x) double x;
- { double y;
- /* this is x - x^3, which has extrema f(-.57735) = -.3849
- and f( .57735) = .3849 */
- y = -(x+1.)*x*(x-1.);
- return y;
- }
-
- double targets[]={-.5, -.4, -.3849, -.3, -.2, -.1, .0, .1};
-
- main(int argc, char **argv)
- {
- double x, y, del, target, bits, dif;
- int error, eval, it;
- INVERSION_REC *inversion=NULL, *init_zero();
-
- if(argc > 1 && strcmp(argv[1], "-s")==0) verbose = 0;
-
-
- printf("; searching...\n");
- inversion = init_zero(&x, inversion);
- advise_zero(-0.9, inversion);
- /*
- y = fun(x=-.9);
- show_progress(x, y, target);
- error = zero(y - target, inversion);
- */
- x = -1.0;
- target = -.1;
- do
- {y = fun(x);
- del = y - target;
- if(fabs(del) < .00000001) break;
- show_progress(x, y, target);
- error = zero(y - target, inversion);
- } while(!error);
- y = fun(x);
- printf("; target = %11.7f\n", target);
- printf("; error = %11.7f\n", y-target);
- printf("; del = %11.7f\n", del);
- printf("; status = %11d (%s)\n\n", error,
- error?"FAILURE":"success");
-
-
- /*
- the sample function reaches a local minimum near -0.3849 for
- x = -0.57735, so the first two inversion attempts will fail.
- for (target = -.5; target < .3; target += .1)
- */
- for (it = 0; it < sizeof(targets)/sizeof(double); it++)
- {target = targets[it];
- x = -0.7;
- if(verbose) printf("; searching...\n");
- inversion = init_zero(&x, inversion);
- eval = 0;
- do
- {y = fun(x); eval++;
- del = y - target;
- if(fabs(del) < .00000001) break;
- if(verbose) show_progress(x, y, target);
- error = zero(del, inversion);
- } while(fabs(del) > .00000001 && !error);
- y = fun(x);
- if(verbose)
- {if(error) printf("\n");
- printf("; target = %11.7f\n", target);
- printf("; error = %11.7f\n", y-target);
- printf("; del = %11.7f\n", del);
- printf("; status = %11d (%s)\n\n", error,
- error?"FAILURE":"success");
- }
- else
- if(error)
- printf(" fun( nothing ) = %11.7f in %2d evaluations\n",
- target, eval);
- else
- printf(" fun(%11.7f ) = %11.7f in %2d evaluations\n",
- x, y, eval);
- }
-
- }
-
- show_progress(x, y, target) double x, y, target;
- { double dif, bits;
-
- dif = y-target;
- if(!target) bits = 0.;
- else if(dif) bits = -log(fabs(dif/target))/log(2.);
- else bits = 60.;
- printf(" fun(%11.7f ) = %11.7f %5.2f bits ok\n", x, y, bits);
- }
-
- #endif /* MAIN */
-
-
-