2 * goal-seek.c: A generic root finder.
5 * Morten Welinder (terra@gnome.org)
11 #define DEBUG_GOAL_SEEK
14 #include <gnumeric-config.h>
17 #include <tools/goal-seek.h>
18 #include <gnm-random.h>
29 update_data (gnm_float x
, gnm_float y
, GnmGoalSeekData
*data
)
38 * When we have pos and neg, prefer the new point only
39 * if it makes the pos-neg x-interval smaller.
41 if (gnm_abs (x
- data
->xneg
) < gnm_abs (data
->xpos
- data
->xneg
)) {
45 } else if (y
< data
->ypos
) {
46 /* We have pos only and our neg y is closer to zero. */
53 data
->havexpos
= TRUE
;
60 * When we have pos and neg, prefer the new point only
61 * if it makes the pos-neg x-interval smaller.
63 if (gnm_abs (x
- data
->xpos
) < gnm_abs (data
->xpos
- data
->xneg
)) {
67 } else if (-y
< -data
->yneg
) {
68 /* We have neg only and our neg y is closer to zero. */
76 data
->havexneg
= TRUE
;
81 data
->have_root
= TRUE
;
83 #ifdef DEBUG_GOAL_SEEK
84 g_print ("update_data: got root %.20" GNM_FORMAT_g
"\n", x
);
92 * Calculate a reasonable approximation to the derivative of a function
95 static GnmGoalSeekStatus
96 fake_df (GnmGoalSeekFunction f
, gnm_float x
, gnm_float
*dfx
, gnm_float xstep
,
97 GnmGoalSeekData
*data
, void *user_data
)
99 gnm_float xl
, xr
, yl
, yr
;
100 GnmGoalSeekStatus status
;
102 #ifdef DEBUG_GOAL_SEEK
103 g_print ("fake_df (x=%.20" GNM_FORMAT_g
", xstep=%.20" GNM_FORMAT_g
")\n",
116 #ifdef DEBUG_GOAL_SEEK
117 g_print ("==> xl == xr\n");
119 return GOAL_SEEK_ERROR
;
122 status
= f (xl
, &yl
, user_data
);
123 if (status
!= GOAL_SEEK_OK
) {
124 #ifdef DEBUG_GOAL_SEEK
125 g_print ("==> failure at xl\n");
129 #ifdef DEBUG_GOAL_SEEK
130 g_print ("==> xl=%.20" GNM_FORMAT_g
"; yl=%.20" GNM_FORMAT_g
"\n",
134 status
= f (xr
, &yr
, user_data
);
135 if (status
!= GOAL_SEEK_OK
) {
136 #ifdef DEBUG_GOAL_SEEK
137 g_print ("==> failure at xr\n");
141 #ifdef DEBUG_GOAL_SEEK
142 g_print ("==> xr=%.20" GNM_FORMAT_g
"; yr=%.20" GNM_FORMAT_g
"\n",
146 *dfx
= (yr
- yl
) / (xr
- xl
);
147 #ifdef DEBUG_GOAL_SEEK
148 g_print ("==> %.20" GNM_FORMAT_g
"\n", *dfx
);
150 return gnm_finite (*dfx
) ? GOAL_SEEK_OK
: GOAL_SEEK_ERROR
;
154 goal_seek_initialize (GnmGoalSeekData
*data
)
156 data
->havexpos
= data
->havexneg
= data
->have_root
= FALSE
;
157 data
->xpos
= data
->xneg
= data
->root
= gnm_nan
;
158 data
->ypos
= data
->yneg
= gnm_nan
;
161 data
->precision
= 1e-10;
167 * @f: (scope call): object function
168 * @data: #GnmGoalSeekData state
169 * @user_data: user data for @f
172 * Seek a goal using a single point.
177 goal_seek_point (GnmGoalSeekFunction f
, GnmGoalSeekData
*data
,
178 void *user_data
, gnm_float x0
)
180 GnmGoalSeekStatus status
;
186 #ifdef DEBUG_GOAL_SEEK
187 g_print ("goal_seek_point\n");
190 if (x0
< data
->xmin
|| x0
> data
->xmax
)
191 return GOAL_SEEK_ERROR
;
193 status
= f (x0
, &y0
, user_data
);
194 if (status
!= GOAL_SEEK_OK
)
197 if (update_data (x0
, y0
, data
))
200 return GOAL_SEEK_ERROR
;
204 static GnmGoalSeekStatus
205 goal_seek_newton_polish (GnmGoalSeekFunction f
, GnmGoalSeekFunction df
,
206 GnmGoalSeekData
*data
, void *user_data
,
207 gnm_float x0
, gnm_float y0
)
210 gnm_float last_df0
= 1;
211 gboolean try_newton
= TRUE
;
212 gboolean try_square
= x0
!= 0 && gnm_abs (x0
) < 1e10
;
214 #ifdef DEBUG_GOAL_SEEK
215 g_print ("goal_seek_newton_polish\n");
218 for (iterations
= 0; iterations
< 20; iterations
++) {
220 gnm_float x1
= x0
* gnm_abs (x0
);
222 GnmGoalSeekStatus status
= f (x1
, &y1
, user_data
);
223 if (status
!= GOAL_SEEK_OK
)
226 if (update_data (x1
, y1
, data
))
229 r
= gnm_abs (y1
/ y0
);
234 #ifdef DEBUG_GOAL_SEEK
235 g_print ("polish square: x0=%.20" GNM_FORMAT_g
"\n",
248 gnm_float df0
, r
, x1
, y1
;
249 GnmGoalSeekStatus status
= df
250 ? df (x0
, &df0
, user_data
)
251 : fake_df (f
, x0
, &df0
, gnm_abs (x0
) / 1e6
, data
, user_data
);
252 if (status
!= GOAL_SEEK_OK
|| df0
== 0)
253 df0
= last_df0
; /* Bogus */
258 if (x1
< data
->xmin
|| x1
> data
->xmax
)
261 status
= f (x1
, &y1
, user_data
);
262 if (status
!= GOAL_SEEK_OK
)
265 if (update_data (x1
, y1
, data
))
268 r
= gnm_abs (y1
/ y0
);
273 #ifdef DEBUG_GOAL_SEEK
274 g_print ("polish Newton: x0=%.20" GNM_FORMAT_g
"\n",
286 /* Nothing left to try. */
290 if (goal_seek_bisection (f
, data
, user_data
) == GOAL_SEEK_OK
)
294 data
->have_root
= TRUE
;
301 * @f: (scope call): object function
302 * @df: (scope call) (nullable): object function derivative
303 * @data: #GnmGoalSeekData state
304 * @user_data: user data for @f and @df
307 * Seek a goal (root) using Newton's iterative method.
309 * The supplied function must (should) be continously differentiable in
310 * the supplied interval. If @df is %NULL, this function will
311 * estimate the derivative.
313 * This method will find a root rapidly provided the initial guess, x0,
314 * is sufficiently close to the root. (The number of significant digits
315 * (asympotically) goes like i^2 unless the root is a multiple root in
316 * which case it is only like c*i.)
319 goal_seek_newton (GnmGoalSeekFunction f
, GnmGoalSeekFunction df
,
320 GnmGoalSeekData
*data
, void *user_data
, gnm_float x0
)
323 gnm_float precision
= data
->precision
/ 2;
324 gnm_float last_df0
= 1;
325 gnm_float step_factor
= 1e-6;
330 #ifdef DEBUG_GOAL_SEEK
331 g_print ("goal_seek_newton\n");
334 for (iterations
= 0; iterations
< 100; iterations
++) {
335 gnm_float x1
, y0
, df0
, stepsize
;
336 GnmGoalSeekStatus status
;
339 #ifdef DEBUG_GOAL_SEEK
340 g_print ("x0 = %.20" GNM_FORMAT_g
" (i=%d)\n", x0
, iterations
);
343 /* Check whether we have left the valid interval. */
344 if (x0
< data
->xmin
|| x0
> data
->xmax
)
345 return GOAL_SEEK_ERROR
;
347 status
= f (x0
, &y0
, user_data
);
348 if (status
!= GOAL_SEEK_OK
)
351 #ifdef DEBUG_GOAL_SEEK
352 g_print (" y0 = %.20" GNM_FORMAT_g
"\n", y0
);
355 if (update_data (x0
, y0
, data
))
359 status
= df (x0
, &df0
, user_data
);
363 if (gnm_abs (x0
) < 1e-10)
364 if (data
->havexneg
&& data
->havexpos
)
365 xstep
= gnm_abs (data
->xpos
- data
->xneg
) / 1e6
;
367 xstep
= (data
->xmax
- data
->xmin
) / 1e6
;
369 xstep
= step_factor
* gnm_abs (x0
);
371 status
= fake_df (f
, x0
, &df0
, xstep
, data
, user_data
);
373 if (status
!= GOAL_SEEK_OK
)
376 /* If we hit a flat spot, we are in trouble. */
380 if (gnm_abs (last_df0
) <= GNM_MIN
)
381 return GOAL_SEEK_ERROR
;
382 df0
= last_df0
; /* Might be utterly bogus. */
386 if (data
->havexpos
&& data
->havexneg
)
390 * Overshoot slightly to prevent us from staying on
391 * just one side of the root.
393 x1
= x0
- 1.000001 * y0
/ df0
;
395 stepsize
= gnm_abs (x1
- x0
) / (gnm_abs (x0
) + gnm_abs (x1
));
397 #ifdef DEBUG_GOAL_SEEK
398 g_print (" df0 = %.20" GNM_FORMAT_g
"\n", df0
);
399 g_print (" ss = %.20" GNM_FORMAT_g
"\n", stepsize
);
402 if (stepsize
< precision
) {
403 goal_seek_newton_polish (f
, df
, data
, user_data
, x0
, y0
);
407 if (flat
&& iterations
> 0) {
409 * Verify that we made progress using our
410 * potentially bogus df0.
414 if (x1
< data
->xmin
|| x1
> data
->xmax
)
415 return GOAL_SEEK_ERROR
;
417 status
= f (x1
, &y1
, user_data
);
418 if (status
!= GOAL_SEEK_OK
)
421 #ifdef DEBUG_GOAL_SEEK
422 g_print (" y1 = %.20" GNM_FORMAT_g
"\n", y1
);
424 if (gnm_abs (y1
) >= 0.9 * gnm_abs (y0
))
425 return GOAL_SEEK_ERROR
;
428 if (stepsize
< step_factor
)
429 step_factor
= stepsize
;
434 return GOAL_SEEK_ERROR
;
438 * goal_seek_bisection:
439 * @f: (scope call): object function
440 * @data: #GnmGoalSeekData state.
441 * @user_data: user data for @f.
443 * Seek a goal (root) using bisection methods.
445 * The supplied function must (should) be continous over the interval.
447 * Caller must have located a positive and a negative point.
449 * This method will find a root steadily using bisection to narrow the
450 * interval in which a root lies.
452 * It alternates between mid-point-bisection (semi-slow, but guaranteed
453 * progress), secant-bisection (usually quite fast, but sometimes gets
454 * nowhere), and Ridder's Method (usually fast, harder to fool than
455 * the secant method).
458 goal_seek_bisection (GnmGoalSeekFunction f
, GnmGoalSeekData
*data
,
463 int newton_submethod
= 0;
468 #ifdef DEBUG_GOAL_SEEK
469 g_print ("goal_seek_bisection\n");
472 if (!data
->havexpos
|| !data
->havexneg
)
473 return GOAL_SEEK_ERROR
;
475 stepsize
= gnm_abs (data
->xpos
- data
->xneg
)
476 / (gnm_abs (data
->xpos
) + gnm_abs (data
->xneg
));
478 /* log_2 (10) = 3.3219 < 4. */
479 for (iterations
= 0; iterations
< 100 + GNM_DIG
* 4; iterations
++) {
480 gnm_float xmid
, ymid
;
481 GnmGoalSeekStatus status
;
482 enum { M_SECANT
, M_RIDDER
, M_NEWTON
, M_MIDPOINT
} method
;
484 method
= (iterations
% 4 == 0)
486 : ((iterations
% 4 == 2)
496 xmid
= data
->xpos
- data
->ypos
*
497 ((data
->xneg
- data
->xpos
) /
498 (data
->yneg
- data
->ypos
));
504 xmid
= (data
->xpos
+ data
->xneg
) / 2;
505 status
= f (xmid
, &ymid
, user_data
);
506 if (status
!= GOAL_SEEK_OK
)
509 update_data (xmid
, ymid
, data
);
513 det
= gnm_sqrt (ymid
* ymid
- data
->ypos
* data
->yneg
);
515 /* This might happen with underflow, I guess. */
518 xmid
+= (xmid
- data
->xpos
) * ymid
/ det
;
523 xmid
= (data
->xpos
+ data
->xneg
) / 2;
527 gnm_float x0
, y0
, xstep
, df0
;
529 /* This method is only effective close-in. */
530 if (stepsize
> 0.1) {
535 switch (newton_submethod
++ % 4) {
536 case 0: x0
= data
->xpos
; y0
= data
->ypos
; break;
537 case 2: x0
= data
->xneg
; y0
= data
->yneg
; break;
541 x0
= (data
->xpos
+ data
->xneg
) / 2;
543 status
= f (x0
, &y0
, user_data
);
544 if (status
!= GOAL_SEEK_OK
)
548 xstep
= gnm_abs (data
->xpos
- data
->xneg
) / 1e6
;
549 status
= fake_df (f
, x0
, &df0
, xstep
, data
, user_data
);
550 if (status
!= GOAL_SEEK_OK
)
557 * Overshoot by 1% to prevent us from staying on
558 * just one side of the root.
560 xmid
= x0
- 1.01 * y0
/ df0
;
564 if ((xmid
< data
->xpos
&& xmid
< data
->xneg
) ||
565 (xmid
> data
->xpos
&& xmid
> data
->xneg
)) {
566 /* We left the interval. */
567 xmid
= (data
->xpos
+ data
->xneg
) / 2;
571 status
= f (xmid
, &ymid
, user_data
);
572 if (status
!= GOAL_SEEK_OK
)
575 #ifdef DEBUG_GOAL_SEEK
577 const char *themethod
;
579 case M_MIDPOINT
: themethod
= "midpoint"; break;
580 case M_RIDDER
: themethod
= "Ridder"; break;
581 case M_SECANT
: themethod
= "secant"; break;
582 case M_NEWTON
: themethod
= "Newton"; break;
583 default: themethod
= "?";
586 g_print ("xmid = %.20" GNM_FORMAT_g
" (%s)\n", xmid
, themethod
);
587 g_print (" ymid = %.20" GNM_FORMAT_g
"\n", ymid
);
591 if (update_data (xmid
, ymid
, data
)) {
595 stepsize
= gnm_abs (data
->xpos
- data
->xneg
)
596 / (gnm_abs (data
->xpos
) + gnm_abs (data
->xneg
));
598 #ifdef DEBUG_GOAL_SEEK
599 g_print (" ss = %.20" GNM_FORMAT_g
"\n", stepsize
);
602 if (stepsize
< GNM_EPSILON
) {
603 if (data
->yneg
< ymid
)
604 ymid
= data
->yneg
, xmid
= data
->xneg
;
606 if (data
->ypos
< ymid
)
607 ymid
= data
->ypos
, xmid
= data
->xpos
;
609 data
->have_root
= TRUE
;
614 return GOAL_SEEK_ERROR
;
621 * goal_seek_trawl_uniformly:
622 * @f: (scope call): object function
623 * @data: #GnmGoalSeekData state
624 * @user_data: user data for @f
625 * @xmin: lower search bound
626 * @xmax: upper search bound
627 * @points: number of points to try.
630 goal_seek_trawl_uniformly (GnmGoalSeekFunction f
,
631 GnmGoalSeekData
*data
, void *user_data
,
632 gnm_float xmin
, gnm_float xmax
,
640 #ifdef DEBUG_GOAL_SEEK
641 g_print ("goal_seek_trawl_uniformly\n");
644 if (xmin
> xmax
|| xmin
< data
->xmin
|| xmax
> data
->xmax
)
645 return GOAL_SEEK_ERROR
;
647 for (i
= 0; i
< points
; i
++) {
649 GnmGoalSeekStatus status
;
651 if (data
->havexpos
&& data
->havexneg
)
654 x
= xmin
+ (xmax
- xmin
) * random_01 ();
655 status
= f (x
, &y
, user_data
);
656 if (status
!= GOAL_SEEK_OK
)
657 /* We are not depending on the result, so go on. */
660 #ifdef DEBUG_GOAL_SEEK
661 g_print ("x = %.20" GNM_FORMAT_g
"\n", x
);
662 g_print (" y = %.20" GNM_FORMAT_g
"\n", y
);
665 if (update_data (x
, y
, data
))
669 /* We were not (extremely) lucky, so we did not actually hit the
670 root. We report this as an error. */
671 return GOAL_SEEK_ERROR
;
675 * goal_seek_trawl_normally:
676 * @f: (scope call): object function
677 * @data: #GnmGoalSeekData state
678 * @user_data: user data for @f
680 * @sigma: search standard deviation
681 * @points: number of points to try.
684 goal_seek_trawl_normally (GnmGoalSeekFunction f
,
685 GnmGoalSeekData
*data
, void *user_data
,
686 gnm_float mu
, gnm_float sigma
,
694 #ifdef DEBUG_GOAL_SEEK
695 g_print ("goal_seek_trawl_normally\n");
698 if (sigma
<= 0 || mu
< data
->xmin
|| mu
> data
->xmax
)
699 return GOAL_SEEK_ERROR
;
701 for (i
= 0; i
< points
; i
++) {
703 GnmGoalSeekStatus status
;
705 if (data
->havexpos
&& data
->havexneg
)
708 x
= mu
+ sigma
* random_normal ();
709 if (x
< data
->xmin
|| x
> data
->xmax
)
712 status
= f (x
, &y
, user_data
);
713 if (status
!= GOAL_SEEK_OK
)
714 /* We are not depending on the result, so go on. */
717 #ifdef DEBUG_GOAL_SEEK
718 g_print ("x = %.20" GNM_FORMAT_g
"\n", x
);
719 g_print (" y = %.20" GNM_FORMAT_g
"\n", y
);
722 if (update_data (x
, y
, data
))
726 /* We were not (extremely) lucky, so we did not actually hit the
727 root. We report this as an error. */
728 return GOAL_SEEK_ERROR
;
732 * gnm_goal_seek_eval_cell:
733 * @x: x-value for which to evaluate
734 * @y: (out): location to store result
737 * Returns: An status indicating whether evaluation went ok.
740 gnm_goal_seek_eval_cell (gnm_float x
, gnm_float
*y
, gpointer data_
)
742 GnmGoalSeekCellData
const *data
= data_
;
743 GnmValue
*v
= value_new_float (x
);
745 gnm_cell_set_value (data
->xcell
, v
);
746 cell_queue_recalc (data
->xcell
);
747 gnm_cell_eval (data
->ycell
);
749 if (data
->ycell
->value
&&
750 VALUE_IS_NUMBER (data
->ycell
->value
)) {
751 *y
= value_get_as_float (data
->ycell
->value
) - data
->ytarget
;
756 return GOAL_SEEK_ERROR
;
760 gnm_goal_seek_cell (GnmGoalSeekData
*data
,
761 GnmGoalSeekCellData
*celldata
)
763 GnmGoalSeekStatus status
;
768 hadold
= !VALUE_IS_EMPTY_OR_ERROR (celldata
->xcell
->value
);
769 oldx
= hadold
? value_get_as_float (celldata
->xcell
->value
) : 0;
771 /* PLAN A: Newton's iterative method from initial or midpoint. */
775 if (hadold
&& oldx
>= data
->xmin
&& oldx
<= data
->xmax
)
778 x0
= (data
->xmin
+ data
->xmax
) / 2;
780 status
= goal_seek_newton (gnm_goal_seek_eval_cell
, NULL
,
783 if (status
== GOAL_SEEK_OK
)
787 /* PLAN B: Trawl uniformly. */
788 if (!data
->havexpos
|| !data
->havexneg
) {
789 status
= goal_seek_trawl_uniformly (gnm_goal_seek_eval_cell
,
791 data
->xmin
, data
->xmax
,
793 if (status
== GOAL_SEEK_OK
)
797 /* PLAN C: Trawl normally from middle. */
798 if (!data
->havexpos
|| !data
->havexneg
) {
802 sigma
= MIN (data
->xmax
- data
->xmin
, 1e6
);
803 mu
= (data
->xmax
+ data
->xmin
) / 2;
805 for (i
= 0; i
< 5; i
++) {
807 status
= goal_seek_trawl_normally (gnm_goal_seek_eval_cell
,
810 if (status
== GOAL_SEEK_OK
)
815 /* PLAN D: Trawl normally from left. */
816 if (!data
->havexpos
|| !data
->havexneg
) {
820 sigma
= MIN (data
->xmax
- data
->xmin
, 1e6
);
823 for (i
= 0; i
< 5; i
++) {
825 status
= goal_seek_trawl_normally (gnm_goal_seek_eval_cell
,
828 if (status
== GOAL_SEEK_OK
)
833 /* PLAN E: Trawl normally from right. */
834 if (!data
->havexpos
|| !data
->havexneg
) {
838 sigma
= MIN (data
->xmax
- data
->xmin
, 1e6
);
841 for (i
= 0; i
< 5; i
++) {
843 status
= goal_seek_trawl_normally (gnm_goal_seek_eval_cell
,
846 if (status
== GOAL_SEEK_OK
)
851 /* PLAN F: Newton iteration with uniform net of starting points. */
852 if (!data
->havexpos
|| !data
->havexneg
) {
856 for (i
= 1; i
<= N
; i
++) {
857 gnm_float x0
= data
->xmin
+
858 (data
->xmax
- data
->xmin
) / (N
+ 1) * i
;
860 status
= goal_seek_newton (gnm_goal_seek_eval_cell
, NULL
,
863 if (status
== GOAL_SEEK_OK
)
868 /* PLAN Z: Bisection. */
870 status
= goal_seek_bisection (gnm_goal_seek_eval_cell
,
872 if (status
== GOAL_SEEK_OK
)
877 if (status
== GOAL_SEEK_OK
)
878 v
= value_new_float (data
->root
);
880 v
= value_new_float (oldx
);
882 v
= value_new_empty ();
883 sheet_cell_set_value (celldata
->xcell
, v
);