1 /++ 2 Base numeric algorithms. 3 4 Reworked part of `std.numeric`. 5 6 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) 7 Authors: Ilia Ki (API, findLocalMin, findRoot extension), Don Clugston (findRoot), Lars Tandle Kyllingstad (diff) 8 +/ 9 module mir.numeric; 10 11 import mir.internal.utility: isFloatingPoint; 12 import mir.math.common; 13 import mir.math.ieee; 14 import mir.exception: toMutable; 15 16 version(D_Exceptions) 17 { 18 private static immutable findRoot_badBounds = new Exception("findRoot: f(ax) and f(bx) must have opposite signs to bracket the root."); 19 private static immutable findRoot_nanX = new Exception("findRoot/findLocalMin: ax or bx is NaN."); 20 private static immutable findRoot_nanY = new Exception("findRoot/findLocalMin: f(x) returned NaN."); 21 } 22 23 /++ 24 +/ 25 enum mir_find_root_status 26 { 27 /// Success 28 success, 29 /// 30 badBounds, 31 /// 32 nanX, 33 /// 34 nanY, 35 } 36 37 /// ditto 38 alias FindRootStatus = mir_find_root_status; 39 40 /++ 41 +/ 42 struct mir_find_root_result(T) 43 { 44 /// Left bound 45 T ax = 0; 46 /// Rifht bound 47 T bx = 0; 48 /// `f(ax)` or `f(ax).fabs.fmin(T.max / 2).copysign(f(ax))`. 49 T ay = 0; 50 /// `f(bx)` or `f(bx).fabs.fmin(T.max / 2).copysign(f(bx))`. 51 T by = 0; 52 /// Amount of target function calls. 53 uint iterations; 54 55 @safe pure @nogc scope const @property: 56 57 /++ 58 Returns: self 59 Required_versions:`D_Exceptions` 60 Throws: `Exception` if $(LREF FindRootResult.status) isn't $(LREF mir_find_root_status.success). 61 +/ 62 version(D_Exceptions) 63 ref validate() inout return 64 { 65 with(FindRootStatus) final switch(status) 66 { 67 case success: return this; 68 case badBounds: throw findRoot_badBounds.toMutable; 69 case nanX: throw findRoot_nanX.toMutable; 70 case nanY: throw findRoot_nanY.toMutable; 71 } 72 } 73 74 extern(C++) nothrow: 75 76 /++ 77 Returns: $(LREF mir_find_root_status) 78 +/ 79 FindRootStatus status() 80 { 81 with(FindRootStatus) return 82 ax != ax || bx != bx ? nanX : 83 ay != ay || by != by ? nanY : 84 ay.signbit == by.signbit && ay != 0 && by != 0 ? badBounds : 85 success; 86 } 87 88 /++ 89 A bound that corresponds to the minimal absolute function value. 90 91 Returns: `!(ay.fabs > by.fabs) ? ax : bx` 92 +/ 93 T x() 94 { 95 return !(ay.fabs > by.fabs) ? ax : bx; 96 } 97 98 /++ 99 The minimal of absolute function values. 100 101 Returns: `!(ay.fabs > by.fabs) ? ay : by` 102 +/ 103 T y() 104 { 105 return !(ay.fabs > by.fabs) ? ay : by; 106 } 107 } 108 109 /// ditto 110 alias FindRootResult = mir_find_root_result; 111 112 /++ 113 Find root of a real function f(x) by bracketing, allowing the 114 termination condition to be specified. 115 116 Given a function `f` and a range `[a .. b]` such that `f(a)` 117 and `f(b)` have opposite signs or at least one of them equals ±0, 118 returns the value of `x` in 119 the range which is closest to a root of `f(x)`. If `f(x)` 120 has more than one root in the range, one will be chosen 121 arbitrarily. If `f(x)` returns NaN, NaN will be returned; 122 otherwise, this algorithm is guaranteed to succeed. 123 124 Uses an algorithm based on TOMS748, which uses inverse cubic 125 interpolation whenever possible, otherwise reverting to parabolic 126 or secant interpolation. Compared to TOMS748, this implementation 127 improves worst-case performance by a factor of more than 100, and 128 typical performance by a factor of 2. For 80-bit reals, most 129 problems require 8 to 15 calls to `f(x)` to achieve full machine 130 precision. The worst-case performance (pathological cases) is 131 approximately twice the number of bits. 132 133 References: "On Enclosing Simple Roots of Nonlinear Equations", 134 G. Alefeld, F.A. Potra, Yixun Shi, Mathematics of Computation 61, 135 pp733-744 (1993). Fortran code available from $(HTTP 136 www.netlib.org,www.netlib.org) as algorithm TOMS478. 137 138 Params: 139 f = Function to be analyzed. `f(ax)` and `f(bx)` should have opposite signs, or `lowerBound` and/or `upperBound` 140 should be defined to perform initial interval extension. 141 tolerance = Defines an early termination condition. Receives the 142 current upper and lower bounds on the root. The 143 delegate must return `true` when these bounds are 144 acceptable. If this function always returns `false` or 145 it is null, full machine precision will be achieved. 146 ax = Left inner bound of initial range of `f` known to contain the root. 147 bx = Right inner bound of initial range of `f` known to contain the root. Can be equal to `ax`. 148 fax = Value of `f(ax)` (optional). 149 fbx = Value of `f(bx)` (optional). 150 lowerBound = Lower outer bound for interval extension (optional) 151 upperBound = Upper outer bound for interval extension (optional) 152 maxIterations = Appr. maximum allowed number of function calls (inluciding function calls for grid). 153 steps = Number of nodes in the left side and right side regular grids (optional). If it equals to `0` (default), 154 then the algorithm uses `ieeeMean` for searching. The algorithm performs searching if 155 `sgn(fax)` equals to `sgn(fbx)` and at least one outer bound allows to extend the inner initial range. 156 157 Returns: $(LREF FindRootResult) 158 +/ 159 @fmamath 160 FindRootResult!T findRoot(alias f, alias tolerance = null, T)( 161 const T ax, 162 const T bx, 163 const T fax = T.nan, 164 const T fbx = T.nan, 165 const T lowerBound = T.nan, 166 const T upperBound = T.nan, 167 uint maxIterations = T.sizeof * 16, 168 uint steps = 0, 169 ) 170 if ( 171 isFloatingPoint!T && __traits(compiles, T(f(T.init))) && 172 ( 173 is(typeof(tolerance) == typeof(null)) || 174 __traits(compiles, {auto _ = bool(tolerance(T.init, T.init));} 175 ) 176 )) 177 { 178 if (false) // break attributes 179 T y = f(T(1)); 180 scope funInst = delegate(T x) { 181 return T(f(x)); 182 }; 183 scope fun = funInst.trustedAllAttr; 184 185 static if (is(typeof(tolerance) == typeof(null))) 186 { 187 alias tol = tolerance; 188 } 189 else 190 { 191 if (false) // break attributes 192 bool b = tolerance(T(1), T(1)); 193 scope tolInst = delegate(T a, T b) { return bool(tolerance(a, b)); }; 194 scope tol = tolInst.trustedAllAttr; 195 } 196 return findRootImpl(ax, bx, fax, fbx, lowerBound, upperBound, maxIterations, steps, fun, tol); 197 } 198 199 /// 200 // @nogc 201 version(mir_test) @safe unittest 202 { 203 import mir.math.common: log, exp, fabs; 204 205 auto logRoot = findRoot!log(0, double.infinity).validate.x; 206 assert(logRoot == 1); 207 208 auto shift = 1; 209 auto expm1Root = findRoot!(x => exp(x) - shift) 210 (-double.infinity, double.infinity).validate.x; 211 assert(expm1Root == 0); 212 213 auto approxLogRoot = findRoot!(log, (a, b) => fabs(a - b) < 1e-5)(0, double.infinity).validate.x; 214 assert(fabs(approxLogRoot - 1) < 1e-5); 215 } 216 217 /// With adaptive bounds 218 version(mir_test) @safe unittest 219 { 220 import mir.math.common: log, exp, fabs; 221 222 auto logRoot = findRoot!log( 223 10, 10, // assume we have one initial point 224 double.nan, double.nan, // fa, fb aren't provided by user 225 -double.infinity, double.infinity, // all space is available for the bounds extension. 226 ).validate.x; 227 assert(logRoot == 1); 228 229 auto shift = 1; 230 alias expm1Fun = (double x) => exp(x) - shift; 231 auto expm1RootRet = findRoot!expm1Fun 232 ( 233 11, 10, // reversed order for interval is always OK 234 expm1Fun(11), expm1Fun(10), // the order must be the same as bounds 235 0, double.infinity, // space for the bounds extension. 236 ).validate; 237 assert(expm1Fun(expm1RootRet.x) == 0); 238 239 auto approxLogRoot = findRoot!(log, (a, b) => fabs(a - b) < 1e-5)( 240 -1e10, +1e10, 241 double.nan, double.nan, 242 0, double.infinity, 243 ).validate.x; 244 assert(fabs(approxLogRoot - 1) < 1e-5); 245 } 246 247 /// ditto 248 version(mir_test) 249 unittest 250 { 251 import core.stdc.tgmath: atan; 252 import mir.math; 253 import std.meta: AliasSeq; 254 255 const double[2][3] boundaries = [ 256 [0.4, 0.6], 257 [1.4, 1.6], 258 [0.1, 2.1]]; 259 260 enum root = 1.0; 261 262 foreach(fun; AliasSeq!( 263 (double x) => x ^^ 2 - root, 264 (double x) => root - x ^^ 2, 265 (double x) => atan(x - root), 266 )) 267 { 268 foreach(ref bounds; boundaries) 269 { 270 auto result = findRoot!fun( 271 bounds[0], bounds[1], 272 double.nan, double.nan, // f(a) and f(b) not provided 273 -double.max, double.max, // user provided outer bounds 274 ); 275 assert(result.validate.x == root); 276 } 277 } 278 279 foreach(ref bounds; boundaries) 280 { 281 auto result = findRoot!(x => sin(x - root))( 282 bounds[0], bounds[1], 283 double.nan, double.nan, // f(a) and f(b) not provided 284 -10, 10, // user provided outer bounds 285 100, // max iterations, 286 10, // steps for grid 287 ); 288 assert(result.validate.x == root); 289 } 290 // single initial point, grid, positive direction 291 auto result = findRoot!((double x ) => sin(x - root))( 292 0.1, 0.1, // initial point, a = b 293 double.nan, double.nan, // f(a) = f(b) not provided 294 -100.0, 100.0, // user provided outer bounds 295 150, // max iterations, 296 100, // number of steps for grid 297 ); 298 assert(result.validate.x == root); 299 300 // single initial point, grid, negative direction 301 result = findRoot!((double x ) => sin(x - root))( 302 0.1, 0.1, // initial point a = b 303 double.nan, double.nan, // f(a) = f(b) not provided 304 100.0, -100.0, // user provided outer bounds, reversed order 305 150, // max iterations, 306 100, // number of steps for grid 307 ); 308 assert(result.validate.x == double(root - PI)); // Left side root! 309 } 310 311 /++ 312 With adaptive bounds and single initial point. 313 Reverse outer bound order controls first step direction 314 in case of `f(a) == f(b)`. 315 +/ 316 version(mir_test) 317 unittest 318 { 319 enum root = 1.0; 320 321 // roots are +/- `root` 322 alias fun = (double x) => x * x - root; 323 324 double lowerBound = -10.0; 325 double upperBound = 10.0; 326 327 assert( 328 findRoot!fun( 329 0, 0, // initial interval 330 double.nan, double.nan, 331 lowerBound, upperBound, 332 // positive direction has higher priority 333 ).validate.x == root 334 ); 335 336 assert( 337 findRoot!fun( 338 0, 0, // initial interval 339 double.nan, double.nan, 340 upperBound, lowerBound, 341 // reversed order 342 ).validate.x == -root // other root 343 ); 344 } 345 346 /// $(LREF findRoot) implementations. 347 export @fmamath FindRootResult!float findRootImpl( 348 float ax, 349 float bx, 350 float fax, 351 float fbx, 352 float lowerBound, 353 float upperBound, 354 uint maxIterations, 355 uint steps, 356 scope float delegate(float) @safe pure nothrow @nogc f, 357 scope bool delegate(float, float) @safe pure nothrow @nogc tolerance, //can be null 358 ) @safe pure nothrow @nogc 359 { 360 pragma(inline, false); 361 return findRootImplGen!float(ax, bx, fax, fbx, lowerBound, upperBound, maxIterations, steps, f, tolerance); 362 } 363 364 /// ditto 365 export @fmamath FindRootResult!double findRootImpl( 366 double ax, 367 double bx, 368 double fax, 369 double fbx, 370 double lowerBound, 371 double upperBound, 372 uint maxIterations, 373 uint steps, 374 scope double delegate(double) @safe pure nothrow @nogc f, 375 scope bool delegate(double, double) @safe pure nothrow @nogc tolerance, //can be null 376 ) @safe pure nothrow @nogc 377 { 378 pragma(inline, false); 379 return findRootImplGen!double(ax, bx, fax, fbx, lowerBound, upperBound, maxIterations, steps, f, tolerance); 380 } 381 382 /// ditto 383 export @fmamath FindRootResult!real findRootImpl( 384 real ax, 385 real bx, 386 real fax, 387 real fbx, 388 real lowerBound, 389 real upperBound, 390 uint maxIterations, 391 uint steps, 392 scope real delegate(real) @safe pure nothrow @nogc f, 393 scope bool delegate(real, real) @safe pure nothrow @nogc tolerance, //can be null 394 ) @safe pure nothrow @nogc 395 { 396 pragma(inline, false); 397 return findRootImplGen!real(ax, bx, fax, fbx, lowerBound, upperBound, maxIterations, steps, f, tolerance); 398 } 399 400 private @fmamath FindRootResult!T findRootImplGen(T)( 401 T a, 402 T b, 403 T fa, 404 T fb, 405 T lb, 406 T ub, 407 uint maxIterations, 408 uint steps, 409 scope const T delegate(T) @safe pure nothrow @nogc f, 410 scope const bool delegate(T, T) @safe pure nothrow @nogc tolerance, //can be null 411 ) @safe pure nothrow @nogc 412 if (isFloatingPoint!T) 413 { 414 version(LDC) pragma(inline, true); 415 // Author: Don Clugston. This code is (heavily) modified from TOMS748 416 // (www.netlib.org). The changes to improve the worst-cast performance are 417 // entirely original. 418 419 // Author: Ilia Ki (Bounds extension logic, 420 // API improvements, infinity and huge numbers handing, compiled code size reduction) 421 422 T d; // [a .. b] is our current bracket. d is the third best guess. 423 T fd; // Value of f at d. 424 bool done = false; // Has a root been found? 425 uint iterations; 426 427 static void swap(ref T a, ref T b) 428 { 429 T t = a; a = b; b = t; 430 } 431 432 bool exit() 433 { 434 pragma(inline, false); 435 return done 436 || iterations >= maxIterations 437 || b == nextUp(a) 438 || tolerance !is null && tolerance(a, b); 439 } 440 441 // Test the function at point c; update brackets accordingly 442 void bracket(T c) 443 { 444 pragma(inline, false); 445 T fc = f(c); 446 iterations++; 447 if (fc == 0 || fc != fc) // Exact solution, or NaN 448 { 449 a = c; 450 fa = fc; 451 d = c; 452 fd = fc; 453 done = true; 454 return; 455 } 456 457 fc = fc.fabs.fmin(T.max / 2).copysign(fc); 458 459 // Determine new enclosing interval 460 if (signbit(fa) != signbit(fc)) 461 { 462 d = b; 463 fd = fb; 464 b = c; 465 fb = fc; 466 } 467 else 468 { 469 d = a; 470 fd = fa; 471 a = c; 472 fa = fc; 473 } 474 } 475 476 /* Perform a secant interpolation. If the result would lie on a or b, or if 477 a and b differ so wildly in magnitude that the result would be meaningless, 478 perform a bisection instead. 479 */ 480 static T secantInterpolate(T a, T b, T fa, T fb) 481 { 482 pragma(inline, false); 483 if (a - b == a && b != 0 484 || b - a == b && a != 0) 485 { 486 // Catastrophic cancellation 487 return ieeeMean(a, b); 488 } 489 // avoid overflow 490 T m = fa - fb; 491 T wa = fa / m; 492 T wb = fb / m; 493 T c = b * wa - a * wb; 494 if (c == a || c == b || c != c || c.fabs == T.infinity) 495 c = a.half + b.half; 496 return c; 497 } 498 499 /* Uses 'numsteps' newton steps to approximate the zero in [a .. b] of the 500 quadratic polynomial interpolating f(x) at a, b, and d. 501 Returns: 502 The approximate zero in [a .. b] of the quadratic polynomial. 503 */ 504 T newtonQuadratic(int numsteps) 505 { 506 // Find the coefficients of the quadratic polynomial. 507 const T a0 = fa; 508 const T a1 = (fb - fa) / (b - a); 509 const T a2 = ((fd - fb) / (d - b) - a1) / (d - a); 510 511 // Determine the starting point of newton steps. 512 T c = a2.signbit != fa.signbit ? a : b; 513 514 // start the safeguarded newton steps. 515 foreach (int i; 0 .. numsteps) 516 { 517 const T pc = a0 + (a1 + a2 * (c - b))*(c - a); 518 const T pdc = a1 + a2*((2 * c) - (a + b)); 519 if (pdc == 0) 520 return a - a0 / a1; 521 else 522 c = c - pc / pdc; 523 } 524 return c; 525 } 526 527 // Starting with the second iteration, higher-order interpolation can 528 // be used. 529 int itnum = 1; // Iteration number 530 int baditer = 1; // Num bisections to take if an iteration is bad. 531 T c, e; // e is our fourth best guess 532 T fe; 533 bool left; 534 535 // Allow a and b to be provided in reverse order 536 if (a > b) 537 { 538 swap(a, b); 539 swap(fa, fb); 540 } 541 542 if (a != a || b != b) 543 { 544 done = true; 545 goto whileloop; 546 } 547 548 if (lb != lb) 549 { 550 lb = a; 551 } 552 553 if (ub != ub) 554 { 555 ub = b; 556 } 557 558 if (lb > ub) 559 { 560 swap(lb, ub); 561 left = true; 562 } 563 564 if (lb == -T.infinity) 565 { 566 lb = -T.max; 567 } 568 569 if (ub == +T.infinity) 570 { 571 ub = +T.max; 572 } 573 574 if (!(lb <= a)) 575 { 576 a = lb; 577 fa = T.nan; 578 } 579 580 if (!(b <= ub)) 581 { 582 a = lb; 583 fa = T.nan; 584 } 585 586 if (fa != fa) 587 { 588 fa = f(a); 589 iterations++; 590 } 591 592 // On the first iteration we take a secant step: 593 if (fa == 0 || fa != fa) 594 { 595 done = true; 596 b = a; 597 fb = fa; 598 goto whileloop; 599 } 600 601 if (fb != fb) 602 { 603 if (a == b) 604 { 605 fb = fa; 606 } 607 else 608 { 609 fb = f(b); 610 iterations++; 611 } 612 } 613 614 if (fb == 0 || fb != fb) 615 { 616 done = true; 617 a = b; 618 fa = fb; 619 goto whileloop; 620 } 621 622 if (fa.fabs < fb.fabs) 623 { 624 left = true; 625 } 626 else 627 if (fa.fabs > fb.fabs) 628 { 629 left = false; 630 } 631 632 // extend inner boundaries 633 if (fa.signbit == fb.signbit) 634 { 635 T lx = a; 636 T ux = b; 637 T ly = fa; 638 T uy = fb; 639 const sb = fa.signbit; 640 641 import mir.ndslice.topology: linspace; 642 643 typeof(linspace!T([2], [lx, lb])) lgrid, ugrid; 644 if (steps) 645 { 646 lgrid = linspace!T([steps + 1], [lx, lb]); 647 lgrid.popFront; 648 ugrid = linspace!T([steps + 1], [ux, ub]); 649 ugrid.popFront; 650 } 651 652 for(T mx;;) 653 { 654 bool lw = lb < lx; 655 bool uw = ux < ub; 656 657 if (!lw && !uw || iterations >= maxIterations) 658 { 659 done = true; 660 goto whileloop; 661 } 662 663 if (lw && (!uw || left)) 664 { 665 if (lgrid.empty) 666 { 667 mx = ieeeMean(lb, lx); 668 if (mx == lx) 669 mx = lb; 670 } 671 else 672 { 673 mx = lgrid.front; 674 lgrid.popFront; 675 if (mx == lx) 676 continue; 677 } 678 T my = f(mx); 679 iterations++; 680 if (my == 0) 681 { 682 a = b = mx; 683 fa = fb = my; 684 done = true; 685 goto whileloop; 686 } 687 if (mx != mx) 688 { 689 lb = mx; 690 continue; 691 } 692 if (my.signbit == sb) 693 { 694 lx = mx; 695 ly = my; 696 if (lgrid.empty) 697 { 698 left = ly.fabs < uy.fabs; 699 } 700 continue; 701 } 702 a = mx; 703 fa = my; 704 b = lx; 705 fb = ly; 706 break; 707 } 708 else 709 { 710 if (ugrid.empty) 711 { 712 mx = ieeeMean(ub, ux); 713 if (mx == ux) 714 mx = ub; 715 } 716 else 717 { 718 mx = ugrid.front; 719 ugrid.popFront; 720 if (mx == ux) 721 continue; 722 } 723 T my = f(mx); 724 iterations++; 725 if (my == 0) 726 { 727 a = b = mx; 728 fa = fb = my; 729 done = true; 730 goto whileloop; 731 } 732 if (mx != mx) 733 { 734 ub = mx; 735 continue; 736 } 737 if (my.signbit == sb) 738 { 739 ux = mx; 740 uy = my; 741 if (ugrid.empty) 742 { 743 left = !(ly.fabs > uy.fabs); 744 } 745 continue; 746 } 747 b = mx; 748 fb = my; 749 a = ux; 750 fa = uy; 751 break; 752 } 753 } 754 } 755 756 fa = fa.fabs.fmin(T.max / 2).copysign(fa); 757 fb = fb.fabs.fmin(T.max / 2).copysign(fb); 758 bracket(secantInterpolate(a, b, fa, fb)); 759 760 whileloop: 761 while (!exit) 762 { 763 T a0 = a, b0 = b; // record the brackets 764 765 // Do two higher-order (cubic or parabolic) interpolation steps. 766 foreach (int QQ; 0 .. 2) 767 { 768 // Cubic inverse interpolation requires that 769 // all four function values fa, fb, fd, and fe are distinct; 770 // otherwise use quadratic interpolation. 771 bool distinct = (fa != fb) && (fa != fd) && (fa != fe) 772 && (fb != fd) && (fb != fe) && (fd != fe); 773 // The first time, cubic interpolation is impossible. 774 if (itnum<2) distinct = false; 775 bool ok = distinct; 776 if (distinct) 777 { 778 // Cubic inverse interpolation of f(x) at a, b, d, and e 779 const q11 = (d - e) * fd / (fe - fd); 780 const q21 = (b - d) * fb / (fd - fb); 781 const q31 = (a - b) * fa / (fb - fa); 782 const d21 = (b - d) * fd / (fd - fb); 783 const d31 = (a - b) * fb / (fb - fa); 784 785 const q22 = (d21 - q11) * fb / (fe - fb); 786 const q32 = (d31 - q21) * fa / (fd - fa); 787 const d32 = (d31 - q21) * fd / (fd - fa); 788 const q33 = (d32 - q22) * fa / (fe - fa); 789 c = a + (q31 + q32 + q33); 790 if (c != c || (c <= a) || (c >= b)) 791 { 792 // DAC: If the interpolation predicts a or b, it's 793 // probable that it's the actual root. Only allow this if 794 // we're already close to the root. 795 if (c == a && (a - b != a || a - b != -b)) 796 { 797 auto down = !(a - b != a); 798 if (down) 799 c = -c; 800 c = c.nextUp; 801 if (down) 802 c = -c; 803 } 804 else 805 { 806 ok = false; 807 } 808 809 } 810 } 811 if (!ok) 812 { 813 // DAC: Alefeld doesn't explain why the number of newton steps 814 // should vary. 815 c = newtonQuadratic(2 + distinct); 816 if (c != c || (c <= a) || (c >= b)) 817 { 818 // Failure, try a secant step: 819 c = secantInterpolate(a, b, fa, fb); 820 } 821 } 822 ++itnum; 823 e = d; 824 fe = fd; 825 bracket(c); 826 if (exit) 827 break whileloop; 828 if (itnum == 2) 829 continue whileloop; 830 } 831 832 // Now we take a double-length secant step: 833 T u; 834 T fu; 835 if (fabs(fa) < fabs(fb)) 836 { 837 u = a; 838 fu = fa; 839 } 840 else 841 { 842 u = b; 843 fu = fb; 844 } 845 c = u - 2 * (fu / (fb - fa)) * (b - a); 846 847 // DAC: If the secant predicts a value equal to an endpoint, it's 848 // probably false. 849 if (c == a || c == b || c != c || fabs(c - u) > (b - a) * 0.5f) 850 { 851 if ((a - b) == a || (b - a) == b) 852 { 853 c = ieeeMean(a, b); 854 } 855 else 856 { 857 c = a.half + b.half; 858 } 859 } 860 e = d; 861 fe = fd; 862 bracket(c); 863 if (exit) 864 break; 865 866 // IMPROVE THE WORST-CASE PERFORMANCE 867 // We must ensure that the bounds reduce by a factor of 2 868 // in binary space! every iteration. If we haven't achieved this 869 // yet, or if we don't yet know what the exponent is, 870 // perform a binary chop. 871 872 if ((a == 0 873 || b == 0 874 || fabs(a) >= 0.5f * fabs(b) 875 && fabs(b) >= 0.5f * fabs(a)) 876 && b - a < 0.25f * (b0 - a0)) 877 { 878 baditer = 1; 879 continue; 880 } 881 882 // DAC: If this happens on consecutive iterations, we probably have a 883 // pathological function. Perform a number of bisections equal to the 884 // total number of consecutive bad iterations. 885 886 if (b - a < 0.25f * (b0 - a0)) 887 baditer = 1; 888 foreach (int QQ; 0 .. baditer) 889 { 890 e = d; 891 fe = fd; 892 bracket(ieeeMean(a, b)); 893 } 894 ++baditer; 895 } 896 return typeof(return)(a, b, fa, fb, iterations); 897 } 898 899 version(mir_test) @safe unittest 900 { 901 import mir.math.constant; 902 903 int numProblems = 0; 904 int numCalls; 905 906 void testFindRoot(real delegate(real) @nogc @safe nothrow pure f , real x1, real x2, int line = __LINE__) //@nogc @safe nothrow pure 907 { 908 //numCalls=0; 909 //++numProblems; 910 assert(x1 == x1 && x2 == x2); 911 auto result = findRoot!f(x1, x2).validate; 912 913 auto flo = f(result.ax); 914 auto fhi = f(result.bx); 915 if (flo != 0) 916 { 917 assert(flo.signbit != fhi.signbit); 918 } 919 } 920 921 // Test functions 922 real cubicfn(real x) @nogc @safe nothrow pure 923 { 924 //++numCalls; 925 if (x>float.max) 926 x = float.max; 927 if (x<-float.max) 928 x = -float.max; 929 // This has a single real root at -59.286543284815 930 return 0.386*x*x*x + 23*x*x + 15.7*x + 525.2; 931 } 932 // Test a function with more than one root. 933 real multisine(real x) { ++numCalls; return sin(x); } 934 testFindRoot( &multisine, 6, 90); 935 testFindRoot(&cubicfn, -100, 100); 936 testFindRoot( &cubicfn, -double.max, real.max); 937 938 939 /* Tests from the paper: 940 * "On Enclosing Simple Roots of Nonlinear Equations", G. Alefeld, F.A. Potra, 941 * Yixun Shi, Mathematics of Computation 61, pp733-744 (1993). 942 */ 943 // Parameters common to many alefeld tests. 944 int n; 945 real ale_a, ale_b; 946 947 int powercalls = 0; 948 949 real power(real x) 950 { 951 ++powercalls; 952 ++numCalls; 953 return pow(x, n) + double.min_normal; 954 } 955 int [] power_nvals = [3, 5, 7, 9, 19, 25]; 956 // Alefeld paper states that pow(x, n) is a very poor case, where bisection 957 // outperforms his method, and gives total numcalls = 958 // 921 for bisection (2.4 calls per bit), 1830 for Alefeld (4.76/bit), 959 // 0.5f624 for brent (6.8/bit) 960 // ... but that is for double, not real80. 961 // This poor performance seems mainly due to catastrophic cancellation, 962 // which is avoided here by the use of ieeeMean(). 963 // I get: 231 (0.48/bit). 964 // IE this is 10X faster in Alefeld's worst case 965 numProblems=0; 966 foreach (k; power_nvals) 967 { 968 n = k; 969 //testFindRoot(&power, -1, 10); 970 } 971 972 int powerProblems = numProblems; 973 974 // Tests from Alefeld paper 975 976 int [9] alefeldSums; 977 real alefeld0(real x) 978 { 979 ++alefeldSums[0]; 980 ++numCalls; 981 real q = sin(x) - x/2; 982 for (int i=1; i<20; ++i) 983 q+=(2*i-5.0)*(2*i-5.0) / ((x-i*i)*(x-i*i)*(x-i*i)); 984 return q; 985 } 986 real alefeld1(real x) 987 { 988 ++numCalls; 989 ++alefeldSums[1]; 990 return ale_a*x + exp(ale_b * x); 991 } 992 real alefeld2(real x) 993 { 994 ++numCalls; 995 ++alefeldSums[2]; 996 return pow(x, n) - ale_a; 997 } 998 real alefeld3(real x) 999 { 1000 ++numCalls; 1001 ++alefeldSums[3]; 1002 return (1.0 +pow(1.0L-n, 2))*x - pow(1.0L-n*x, 2); 1003 } 1004 real alefeld4(real x) 1005 { 1006 ++numCalls; 1007 ++alefeldSums[4]; 1008 return x*x - pow(1-x, n); 1009 } 1010 real alefeld5(real x) 1011 { 1012 ++numCalls; 1013 ++alefeldSums[5]; 1014 return (1+pow(1.0L-n, 4))*x - pow(1.0L-n*x, 4); 1015 } 1016 real alefeld6(real x) 1017 { 1018 ++numCalls; 1019 ++alefeldSums[6]; 1020 return exp(-n*x)*(x-1.01L) + pow(x, n); 1021 } 1022 real alefeld7(real x) 1023 { 1024 ++numCalls; 1025 ++alefeldSums[7]; 1026 return (n*x-1) / ((n-1)*x); 1027 } 1028 1029 numProblems=0; 1030 testFindRoot(&alefeld0, PI_2, PI); 1031 for (n=1; n <= 10; ++n) 1032 { 1033 testFindRoot(&alefeld0, n*n+1e-9L, (n+1)*(n+1)-1e-9L); 1034 } 1035 ale_a = -40; ale_b = -1; 1036 testFindRoot(&alefeld1, -9, 31); 1037 ale_a = -100; ale_b = -2; 1038 testFindRoot(&alefeld1, -9, 31); 1039 ale_a = -200; ale_b = -3; 1040 testFindRoot(&alefeld1, -9, 31); 1041 int [] nvals_3 = [1, 2, 5, 10, 15, 20]; 1042 int [] nvals_5 = [1, 2, 4, 5, 8, 15, 20]; 1043 int [] nvals_6 = [1, 5, 10, 15, 20]; 1044 int [] nvals_7 = [2, 5, 15, 20]; 1045 1046 for (int i=4; i<12; i+=2) 1047 { 1048 n = i; 1049 ale_a = 0.2; 1050 testFindRoot(&alefeld2, 0, 5); 1051 ale_a=1; 1052 testFindRoot(&alefeld2, 0.95, 4.05); 1053 testFindRoot(&alefeld2, 0, 1.5); 1054 } 1055 foreach (i; nvals_3) 1056 { 1057 n=i; 1058 testFindRoot(&alefeld3, 0, 1); 1059 } 1060 foreach (i; nvals_3) 1061 { 1062 n=i; 1063 testFindRoot(&alefeld4, 0, 1); 1064 } 1065 foreach (i; nvals_5) 1066 { 1067 n=i; 1068 testFindRoot(&alefeld5, 0, 1); 1069 } 1070 foreach (i; nvals_6) 1071 { 1072 n=i; 1073 testFindRoot(&alefeld6, 0, 1); 1074 } 1075 foreach (i; nvals_7) 1076 { 1077 n=i; 1078 testFindRoot(&alefeld7, 0.01L, 1); 1079 } 1080 real worstcase(real x) 1081 { 1082 ++numCalls; 1083 return x<0.3*real.max? -0.999e-3: 1.0; 1084 } 1085 testFindRoot(&worstcase, -real.max, real.max); 1086 1087 // just check that the double + float cases compile 1088 findRoot!(x => 0)(-double.max, double.max); 1089 findRoot!(x => -0.0)(-float.max, float.max); 1090 /* 1091 int grandtotal=0; 1092 foreach (calls; alefeldSums) 1093 { 1094 grandtotal+=calls; 1095 } 1096 grandtotal-=2*numProblems; 1097 printf("\nALEFELD TOTAL = %d avg = %f (alefeld avg=19.3 for double)\n", 1098 grandtotal, (1.0*grandtotal)/numProblems); 1099 powercalls -= 2*powerProblems; 1100 printf("POWER TOTAL = %d avg = %f ", powercalls, 1101 (1.0*powercalls)/powerProblems); 1102 */ 1103 //Issue 14231 1104 auto xp = findRoot!(x => x)(0f, 1f); 1105 auto xn = findRoot!(x => x)(-1f, -0f); 1106 } 1107 1108 /++ 1109 +/ 1110 struct FindLocalMinResult(T) 1111 { 1112 /// 1113 T x = 0; 1114 /// 1115 T y = 0; 1116 /// 1117 T error = 0; 1118 1119 @safe pure @nogc scope const @property: 1120 1121 /++ 1122 Returns: self 1123 Required_versions:`D_Exceptions` 1124 Throws: `Exception` if $(LREF FindRootResult.status) isn't $(LREF mir_find_root_status.success). 1125 +/ 1126 version(D_Exceptions) 1127 ref validate() return 1128 { 1129 with(FindRootStatus) final switch(status) 1130 { 1131 case success: return this; 1132 case badBounds: throw findRoot_badBounds.toMutable; 1133 case nanX: throw findRoot_nanX.toMutable; 1134 case nanY: throw findRoot_nanY.toMutable; 1135 } 1136 } 1137 1138 extern(C++) nothrow: 1139 1140 /++ 1141 Returns: $(LREF mir_find_root_status) 1142 +/ 1143 FindRootStatus status() 1144 { 1145 with(FindRootStatus) return 1146 x != x ? nanX : 1147 y != y ? nanY : 1148 success; 1149 } 1150 } 1151 1152 /++ 1153 Find a real minimum of a real function `f(x)` via bracketing. 1154 Given a function `f` and a range `(ax .. bx)`, 1155 returns the value of `x` in the range which is closest to a minimum of `f(x)`. 1156 `f` is never evaluted at the endpoints of `ax` and `bx`. 1157 If `f(x)` has more than one minimum in the range, one will be chosen arbitrarily. 1158 If `f(x)` returns NaN or -Infinity, `(x, f(x), NaN)` will be returned; 1159 otherwise, this algorithm is guaranteed to succeed. 1160 Params: 1161 f = Function to be analyzed 1162 ax = Left bound of initial range of f known to contain the minimum. 1163 bx = Right bound of initial range of f known to contain the minimum. 1164 relTolerance = Relative tolerance. 1165 absTolerance = Absolute tolerance. 1166 N = number of addition inner points of uniform grid. 1167 The algorithm computes function value for the N points. 1168 Then reassing ax to the point that preceeds the grid's argmin, 1169 reassing bx to the point that follows the the grid's argmin. 1170 The search interval reduces (N + 1) / 2 times. 1171 Preconditions: 1172 `ax` and `bx` shall be finite reals. $(BR) 1173 `relTolerance` shall be normal positive real. $(BR) 1174 `absTolerance` shall be normal positive real no less then `T.epsilon*2`. 1175 Returns: 1176 A $(LREF FindLocalMinResult) consisting of `x`, `y = f(x)` and `error = 3 * (absTolerance * fabs(x) + relTolerance)`. 1177 The method used is a combination of golden section search and 1178 successive parabolic interpolation. Convergence is never much slower 1179 than that for a Fibonacci search. 1180 References: 1181 "Algorithms for Minimization without Derivatives", Richard Brent, Prentice-Hall, Inc. (1973) 1182 See_Also: $(LREF findRoot), $(REF isNormal, std,math) 1183 +/ 1184 FindLocalMinResult!T findLocalMin(alias f, T)( 1185 const T ax, 1186 const T bx, 1187 const T relTolerance = sqrt(T.epsilon), 1188 const T absTolerance = sqrt(T.epsilon), 1189 size_t N = 0, 1190 ) 1191 if (isFloatingPoint!T && __traits(compiles, T(f(T.init)))) 1192 { 1193 if (false) // break attributes 1194 { 1195 T y = f(T(123)); 1196 } 1197 scope funInst = delegate(T x) { 1198 return T(f(x)); 1199 }; 1200 scope fun = funInst.trustedAllAttr; 1201 1202 return findLocalMinImpl(ax, bx, relTolerance, absTolerance, fun, N); 1203 } 1204 1205 @fmamath 1206 private FindLocalMinResult!float findLocalMinImpl( 1207 const float ax, 1208 const float bx, 1209 const float relTolerance, 1210 const float absTolerance, 1211 scope const float delegate(float) @safe pure nothrow @nogc f, 1212 size_t N, 1213 ) @safe pure nothrow @nogc 1214 { 1215 pragma(inline, false); 1216 return findLocalMinImplGen!float(ax, bx, relTolerance, absTolerance, f, N); 1217 } 1218 1219 @fmamath 1220 private FindLocalMinResult!double findLocalMinImpl( 1221 const double ax, 1222 const double bx, 1223 const double relTolerance, 1224 const double absTolerance, 1225 scope const double delegate(double) @safe pure nothrow @nogc f, 1226 size_t N, 1227 ) @safe pure nothrow @nogc 1228 { 1229 pragma(inline, false); 1230 return findLocalMinImplGen!double(ax, bx, relTolerance, absTolerance, f, N); 1231 } 1232 1233 @fmamath 1234 private FindLocalMinResult!real findLocalMinImpl( 1235 const real ax, 1236 const real bx, 1237 const real relTolerance, 1238 const real absTolerance, 1239 scope const real delegate(real) @safe pure nothrow @nogc f, 1240 size_t N, 1241 ) @safe pure nothrow @nogc 1242 { 1243 pragma(inline, false); 1244 return findLocalMinImplGen!real(ax, bx, relTolerance, absTolerance, f, N); 1245 } 1246 1247 @fmamath 1248 private FindLocalMinResult!T findLocalMinImplGen(T)( 1249 T ax, 1250 T bx, 1251 const T relTolerance, 1252 const T absTolerance, 1253 scope const T delegate(T) @safe pure nothrow @nogc f, 1254 size_t N, 1255 ) 1256 if (isFloatingPoint!T && __traits(compiles, {T _ = f(T.init);})) 1257 in 1258 { 1259 assert(relTolerance.fabs >= T.min_normal && relTolerance.fabs < T.infinity, "relTolerance is not normal floating point number"); 1260 assert(absTolerance.fabs >= T.min_normal && absTolerance.fabs < T.infinity, "absTolerance is not normal floating point number"); 1261 assert(relTolerance >= 0, "absTolerance is not positive"); 1262 assert(absTolerance >= T.epsilon*2, "absTolerance is not greater then `2*T.epsilon`"); 1263 } 1264 do 1265 { 1266 if (N > 1) 1267 { 1268 import mir.ndslice.topology: linspace; 1269 1270 auto xPoints = linspace!T([N + 2], [ax, bx]); 1271 size_t idx; 1272 double value = double.infinity; 1273 foreach (i; 0 .. N) 1274 { 1275 auto y = f(xPoints[i + 1]); 1276 if (y < value) 1277 { 1278 value = y; 1279 idx = i; 1280 } 1281 } 1282 ax = xPoints[idx + 0]; 1283 bx = xPoints[idx + 2]; 1284 } 1285 1286 version(LDC) pragma(inline, true); 1287 // c is the squared inverse of the golden ratio 1288 // (3 - sqrt(5))/2 1289 // Value obtained from Wolfram Alpha. 1290 enum T c = 0x0.61c8864680b583ea0c633f9fa31237p+0L; 1291 enum T cm1 = 0x0.9e3779b97f4a7c15f39cc0605cedc8p+0L; 1292 T tolerance; 1293 T a = ax > bx ? bx : ax; 1294 T b = ax > bx ? ax : bx; 1295 if (a < -T.max) 1296 a = -T.max; 1297 if (b > +T.max) 1298 b = +T.max; 1299 // sequence of declarations suitable for SIMD instructions 1300 T v = a * cm1 + b * c; 1301 assert(v.fabs < T.infinity); 1302 T fv = v == v ? f(v) : v; 1303 if (fv != fv || fv == -T.infinity) 1304 { 1305 return typeof(return)(v, fv, T.init); 1306 } 1307 T w = v; 1308 T fw = fv; 1309 T x = v; 1310 T fx = fv; 1311 size_t i; 1312 for (T d = 0, e = 0;;) 1313 { 1314 i++; 1315 T m = (a + b) * 0.5f; 1316 // This fix is not part of the original algorithm 1317 if (!(m.fabs < T.infinity)) // fix infinity loop. Issue can be reproduced in R. 1318 { 1319 m = a.half + b.half; 1320 } 1321 tolerance = absTolerance * fabs(x) + relTolerance; 1322 const t2 = tolerance * 2; 1323 // check stopping criterion 1324 if (!(fabs(x - m) > t2 - (b - a) * 0.5f)) 1325 { 1326 break; 1327 } 1328 T p = 0; 1329 T q = 0; 1330 T r = 0; 1331 // fit parabola 1332 if (fabs(e) > tolerance) 1333 { 1334 const xw = x - w; 1335 const fxw = fx - fw; 1336 const xv = x - v; 1337 const fxv = fx - fv; 1338 const xwfxv = xw * fxv; 1339 const xvfxw = xv * fxw; 1340 p = xv * xvfxw - xw * xwfxv; 1341 q = (xvfxw - xwfxv) * 2; 1342 if (q > 0) 1343 p = -p; 1344 else 1345 q = -q; 1346 r = e; 1347 e = d; 1348 } 1349 T u; 1350 // a parabolic-interpolation step 1351 if (fabs(p) < fabs(q * r * 0.5f) && p > q * (a - x) && p < q * (b - x)) 1352 { 1353 d = p / q; 1354 u = x + d; 1355 // f must not be evaluated too close to a or b 1356 if (u - a < t2 || b - u < t2) 1357 d = x < m ? tolerance: -tolerance; 1358 } 1359 // a golden-section step 1360 else 1361 { 1362 e = (x < m ? b : a) - x; 1363 d = c * e; 1364 } 1365 // f must not be evaluated too close to x 1366 u = x + (fabs(d) >= tolerance ? d: d > 0 ? tolerance: -tolerance); 1367 const fu = f(u); 1368 if (fu != fu || fu == -T.infinity) 1369 { 1370 return typeof(return)(u, fu, T.init); 1371 } 1372 // update a, b, v, w, and x 1373 if (fu <= fx) 1374 { 1375 (u < x ? b : a) = x; 1376 v = w; fv = fw; 1377 w = x; fw = fx; 1378 x = u; fx = fu; 1379 } 1380 else 1381 { 1382 (u < x ? a : b) = u; 1383 if (fu <= fw || w == x) 1384 { 1385 v = w; fv = fw; 1386 w = u; fw = fu; 1387 } 1388 else if (fu <= fv || v == x || v == w) 1389 { // do not remove this braces 1390 v = u; fv = fu; 1391 } 1392 } 1393 } 1394 return typeof(return)(x, fx, tolerance * 3); 1395 } 1396 1397 /// 1398 //@nogc 1399 version(mir_test) @safe unittest 1400 { 1401 import mir.math.common: approxEqual; 1402 1403 double shift = 4; 1404 1405 auto ret = findLocalMin!(x => (x-shift)^^2)(-1e7, 1e7).validate; 1406 assert(ret.x.approxEqual(shift)); 1407 assert(ret.y.approxEqual(0.0)); 1408 } 1409 1410 /// 1411 version(mir_test) @safe unittest 1412 { 1413 import mir.math.common: approxEqual, log, fabs; 1414 alias AliasSeq(T...) = T; 1415 static foreach (T; AliasSeq!(double, float, real)) 1416 { 1417 { 1418 auto ret = findLocalMin!(x => (x-4)^^2)(T.min_normal, T(1e7)).validate; 1419 assert(ret.x.approxEqual(T(4))); 1420 assert(ret.y.approxEqual(T(0))); 1421 } 1422 { 1423 auto ret = findLocalMin!(x => fabs(x-1))(-T.max/4, T.max/4, T.min_normal, 2*T.epsilon).validate; 1424 assert(approxEqual(ret.x, T(1))); 1425 assert(approxEqual(ret.y, T(0))); 1426 assert(ret.error <= 10 * T.epsilon); 1427 } 1428 { 1429 auto ret = findLocalMin!(x => T.init)(0, 1, T.min_normal, 2*T.epsilon); 1430 assert(ret.status == FindRootStatus.nanY); 1431 } 1432 { 1433 auto ret = findLocalMin!log( 0, 1, T.min_normal, 2*T.epsilon).validate; 1434 assert(ret.error < 3.00001 * ((2*T.epsilon)*fabs(ret.x)+ T.min_normal)); 1435 assert(ret.x >= 0 && ret.x <= ret.error); 1436 } 1437 { 1438 auto ret = findLocalMin!log(0, T.max, T.min_normal, 2*T.epsilon).validate; 1439 assert(ret.y < -18); 1440 assert(ret.error < 5e-08); 1441 assert(ret.x >= 0 && ret.x <= ret.error); 1442 } 1443 { 1444 auto ret = findLocalMin!(x => -fabs(x))(-1, 1, T.min_normal, 2*T.epsilon).validate; 1445 assert(ret.x.fabs.approxEqual(T(1))); 1446 assert(ret.y.fabs.approxEqual(T(1))); 1447 assert(ret.error.approxEqual(T(0))); 1448 } 1449 } 1450 } 1451 1452 /// Tries to find a global minimum using uniform grid to reduce the search interval 1453 version(mir_test) 1454 unittest 1455 { 1456 import mir.math.common: cos; 1457 import mir.test; 1458 1459 alias f = x => cos(x) + x / 10; 1460 1461 findLocalMin!f(-4.0, 6.0, double.epsilon, 2 * double.epsilon) 1462 .validate.x.shouldApprox == 3.041425233955413; 1463 // Use 10 inner points on uniform grid to reduce the interval 1464 // reduces the interval of search 5.5 = (10 + 1) / 2 times 1465 findLocalMin!f(-4.0, 6.0, double.epsilon, 2 * double.epsilon, 10) 1466 .validate.x.shouldApprox == -3.2417600767368255; 1467 } 1468 1469 /++ 1470 A set of one or two smile roots. 1471 1472 Because we assume that volatility smile is convex the equantion above can have no more then two roots. 1473 1474 The `left` and `right` members are equal if the smile has only one root. 1475 +/ 1476 struct SmileRoots(T) 1477 if (__traits(isFloating, T)) 1478 { 1479 import mir.small_array: SmallArray; 1480 1481 /// 1482 T left; 1483 /// 1484 T right; 1485 1486 @safe pure nothrow @nogc: 1487 1488 /// 1489 this(T value) 1490 { 1491 left = right = value; 1492 } 1493 1494 /// 1495 this(T left, T right) 1496 { 1497 this.left = left; 1498 this.right = right; 1499 } 1500 1501 /// 1502 this(SmallArray!(T, 2) array) 1503 { 1504 if (array.length == 2) 1505 this(array[0], array[1]); 1506 else 1507 if (array.length == 1) 1508 this(array[0]); 1509 else 1510 this(T.nan, T.nan); 1511 } 1512 1513 /// 1514 inout(T)[] opIndex() @trusted inout 1515 { 1516 return (&left)[0 .. 2]; 1517 } 1518 1519 /// 1520 size_t count() const 1521 { 1522 return 1 + (left != right); 1523 } 1524 } 1525 1526 /++ 1527 +/ 1528 struct FindSmileRootsResult(T) 1529 if (__traits(isFloating, T)) 1530 { 1531 import mir.algebraic: Nullable; 1532 1533 /++ 1534 Left result if any 1535 +/ 1536 Nullable!(FindRootResult!T) leftResult; 1537 /++ 1538 Right result if any 1539 +/ 1540 Nullable!(FindRootResult!T) rightResult; 1541 /++ 1542 +/ 1543 Nullable!(FindLocalMinResult!T) localMinResult; 1544 1545 @safe pure @nogc scope const @property: 1546 1547 /++ 1548 Returns: $(LREF mir_find_root_status) 1549 +/ 1550 FindRootStatus status() 1551 { 1552 if (leftResult && leftResult.get.status) 1553 return leftResult.get.status; 1554 if (rightResult && rightResult.get.status) 1555 return rightResult.get.status; 1556 if (!leftResult && !rightResult) 1557 return FindRootStatus.badBounds; 1558 return FindRootStatus.success; 1559 } 1560 1561 /// 1562 version(D_Exceptions) 1563 ref validate() inout return 1564 { 1565 with(FindRootStatus) final switch(status) 1566 { 1567 case success: return this; 1568 case badBounds: throw findRoot_badBounds.toMutable; 1569 case nanX: throw findRoot_nanX.toMutable; 1570 case nanY: throw findRoot_nanY.toMutable; 1571 } 1572 } 1573 1574 /++ 1575 Returns: `SmallArray!(T, 2)` of roots if any. 1576 +/ 1577 auto roots()() 1578 { 1579 import mir.small_array; 1580 SmallArray!(T, 2) ret; 1581 if (leftResult) 1582 ret.append(leftResult.get.x); 1583 if (rightResult) 1584 ret.append(rightResult.get.x); 1585 return ret; 1586 } 1587 1588 /++ 1589 Returns: $(LREF SmileRoots). 1590 +/ 1591 SmileRoots!double smileRoots()() 1592 { 1593 return typeof(return)(roots); 1594 } 1595 } 1596 1597 /++ 1598 Find one or two roots of a real function f(x) using combination of $(LREF FindRoot) and $(LREF FindLocalMin). 1599 1600 Params: 1601 f = Function to be analyzed. If `f(ax)` and `f(bx)` have opposite signs one root is returned, 1602 otherwise the implementation tries to find a local minimum and returns two roots. 1603 At least one of `f(ax)` and `f(bx)` should be greater or equal to zero. 1604 tolerance = Defines an early termination condition. Receives the 1605 current upper and lower bounds on the root. The delegate must return `true` when these bounds are 1606 acceptable. If this function always returns `false` or it is null, full machine precision will be achieved. 1607 ax = Left inner bound of initial range of `f` known to contain the roots. 1608 bx = Right inner bound of initial range of `f` known to contain the roots. Can be equal to `ax`. 1609 fax = Value of `f(ax)` (optional). 1610 fbx = Value of `f(bx)` (optional). 1611 relTolerance = Relative tolerance used by $(LREF findLocalMin). 1612 absTolerance = Absolute tolerance used by $(LREF findLocalMin). 1613 lowerBound = 1614 upperBound = 1615 maxIterations = Appr. maximum allowed number of function calls for each $(LREF findRoot) call. 1616 steps = 1617 1618 Returns: $(LREF FindSmileRootsResult) 1619 +/ 1620 FindSmileRootsResult!T findSmileRoots(alias f, alias tolerance = null, T)( 1621 const T ax, 1622 const T bx, 1623 const T fax = T.nan, 1624 const T fbx = T.nan, 1625 const T relTolerance = sqrt(T.epsilon), 1626 const T absTolerance = sqrt(T.epsilon), 1627 const T lowerBound = T.nan, 1628 const T upperBound = T.nan, 1629 uint maxIterations = T.sizeof * 16, 1630 uint steps = 0, 1631 ) 1632 if (__traits(isFloating, T)) 1633 { 1634 FindSmileRootsResult!T ret; 1635 auto res = findRoot!(f, tolerance)(ax, bx, fax, fbx, T.nan, T.nan, maxIterations); 1636 if (res.status == FindRootStatus.success) 1637 { 1638 (res.bx.signbit ? ret.leftResult : ret.rightResult) = res; 1639 } 1640 else 1641 if (res.status == FindRootStatus.badBounds) 1642 { 1643 ret.localMinResult = findLocalMin!f(ax, bx, relTolerance, absTolerance); 1644 ret.leftResult = findRoot!(f, tolerance)(ax, ret.localMinResult.x, T.nan, ret.localMinResult.y, T.nan, T.nan, maxIterations); 1645 ret.rightResult = findRoot!(f, tolerance)(ret.localMinResult.x, bx, ret.localMinResult.y, T.nan, T.nan, T.nan, maxIterations); 1646 } 1647 return ret; 1648 } 1649 1650 /// Smile 1651 version(mir_test) 1652 unittest 1653 { 1654 import mir.math.common: approxEqual; 1655 auto result = findSmileRoots!(x => x ^^ 2 - 1)(-10.0, 10.0); 1656 assert(result.roots.length == 2); 1657 assert(result.roots[0].approxEqual(-1)); 1658 assert(result.roots[1].approxEqual(+1)); 1659 assert(result.smileRoots.left.approxEqual(-1)); 1660 assert(result.smileRoots.right.approxEqual(+1)); 1661 assert(result.leftResult); 1662 assert(result.rightResult); 1663 assert(result.localMinResult); 1664 assert(result.localMinResult.get.x.approxEqual(0)); 1665 assert(result.localMinResult.get.y.approxEqual(-1)); 1666 } 1667 1668 /// Skew 1669 version(mir_test) 1670 unittest 1671 { 1672 import mir.math.common: approxEqual; 1673 auto result = findSmileRoots!(x => x ^^ 2 - 1)(0.5, 10.0); 1674 assert(result.roots.length == 1); 1675 assert(result.roots[0].approxEqual(+1)); 1676 assert(result.smileRoots.left.approxEqual(+1)); 1677 assert(result.smileRoots.right.approxEqual(+1)); 1678 assert(!result.leftResult); 1679 assert(result.rightResult); 1680 } 1681 1682 version(mir_test) 1683 unittest 1684 { 1685 auto result = findSmileRoots!(x => x ^^ 2 - 1)(-10.0, -0.5); 1686 assert(result.roots.length == 1); 1687 assert(result.roots[0].approxEqual(-1)); 1688 assert(result.smileRoots.left.approxEqual(-1)); 1689 assert(result.smileRoots.right.approxEqual(-1)); 1690 assert(result.leftResult); 1691 assert(!result.rightResult); 1692 } 1693 1694 // force disabled FMA math 1695 private static auto half(T)(const T x) 1696 { 1697 pragma(inline, false); 1698 return x * 0.5f; 1699 } 1700 1701 private auto trustedAllAttr(T)(T t) @trusted 1702 { 1703 import std.traits; 1704 enum attrs = (functionAttributes!T & ~FunctionAttribute.system) 1705 | FunctionAttribute.pure_ 1706 | FunctionAttribute.safe 1707 | FunctionAttribute.nogc 1708 | FunctionAttribute.nothrow_; 1709 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 1710 } 1711 1712 /++ 1713 Calculate the derivative of a function. 1714 This function uses Ridders' method of extrapolating the results 1715 of finite difference formulas for consecutively smaller step sizes, 1716 with an improved stopping criterion described in the Numerical Recipes 1717 books by Press et al. 1718 1719 This method gives a much higher degree of accuracy in the answer 1720 compared to a single finite difference calculation, but requires 1721 more function evaluations; typically 6 to 12. The maximum number 1722 of function evaluations is $(D 24). 1723 1724 Params: 1725 f = The function of which to take the derivative. 1726 x = The point at which to take the derivative. 1727 h = A "characteristic scale" over which the function changes. 1728 factor = Stepsize is decreased by factor at each iteration. 1729 safe = Return when error is SAFE worse than the best so far. 1730 1731 References: 1732 $(UL 1733 $(LI 1734 C. J. F. Ridders, 1735 $(I Accurate computation of F'(x) and F'(x)F''(x)). 1736 Advances in Engineering Software, vol. 4 (1982), issue 2, p. 75.) 1737 $(LI 1738 W. H. Press, S. A. Teukolsky, W. T. Vetterling, and B. P. Flannery, 1739 $(I Numerical Recipes in C++) (2nd ed.). 1740 Cambridge University Press, 2003.) 1741 ) 1742 +/ 1743 @fmamath 1744 DiffResult!T diff(alias f, T)(const T x, const T h, const T factor = T(2).sqrt, const T safe = 2) 1745 { 1746 if (false) // break attributes 1747 T y = f(T(1)); 1748 scope funInst = delegate(T x) { 1749 return T(f(x)); 1750 }; 1751 scope fun = funInst.trustedAllAttr; 1752 return diffImpl(fun, x, h, factor, safe); 1753 } 1754 1755 ///ditto 1756 DiffResult!T diffImpl(T) 1757 (scope const T delegate(T) @safe pure nothrow @nogc f, const T x, const T h, const T factor = T(2).sqrt, const T safe = 2) 1758 @trusted pure nothrow @nogc 1759 in { 1760 assert(h < T.max); 1761 assert(h > T.min_normal); 1762 } 1763 do { 1764 // Set up Romberg tableau. 1765 import mir.ndslice.slice: sliced; 1766 pragma(inline, false); 1767 1768 enum tableauSize = 16; 1769 T[tableauSize ^^ 2] workspace = void; 1770 auto tab = workspace[].sliced(tableauSize, tableauSize); 1771 1772 // From the NR book: Stop when the difference between consecutive 1773 // approximations is bigger than SAFE*error, where error is an 1774 // estimate of the absolute error in the current (best) approximation. 1775 1776 // First approximation: A_0 1777 T result = void; 1778 T error = T.max; 1779 T hh = h; 1780 1781 tab[0,0] = (f(x + h) - f(x - h)) / (2 * h); 1782 foreach (n; 1 .. tableauSize) 1783 { 1784 // Decrease h. 1785 hh /= factor; 1786 1787 // Compute A_n 1788 tab[0, n] = (f(x + hh) - f(x - hh)) / (2 * hh); 1789 1790 T facm = 1; 1791 foreach (m; 1 .. n + 1) 1792 { 1793 facm *= factor ^^ 2; 1794 1795 // Compute B_(n-1), C_(n-2), ... 1796 T upLeft = tab[m - 1, n - 1]; 1797 T up = tab[m - 1, n]; 1798 T current = (facm * up - upLeft) / (facm - 1); 1799 tab[m, n] = current; 1800 1801 // Calculate and check error. 1802 T currentError = fmax(fabs(current - upLeft), fabs(current - up)); 1803 if (currentError <= error) 1804 { 1805 result = current; 1806 error = currentError; 1807 } 1808 } 1809 1810 if (fabs(tab[n, n] - tab[n - 1, n - 1]) >= safe * error) 1811 break; 1812 } 1813 1814 return typeof(return)(result, error); 1815 } 1816 1817 /// 1818 version(mir_test) 1819 unittest 1820 { 1821 import mir.math.common; 1822 1823 auto f(double x) { return exp(x) / (sin(x) - x ^^ 2); } 1824 auto d(double x) { return -(exp(x) * ((x - 2) * x - sin(x) + cos(x)))/(x^^2 - sin(x))^^2; } 1825 auto r = diff!f(1.0, 0.01); 1826 assert (approxEqual(r.value, d(1))); 1827 } 1828 1829 /++ 1830 +/ 1831 struct DiffResult(T) 1832 if (__traits(isFloating, T)) 1833 { 1834 /// 1835 T value = 0; 1836 /// 1837 T error = 0; 1838 } 1839 1840 /++ 1841 Integrates function on the interval `[a, b]` using adaptive Gauss-Lobatto algorithm. 1842 1843 References: 1844 W. Gander and W. Gautschi. Adaptive Quadrature — Revisited 1845 1846 Params: 1847 f = function to integrate. `f` should be valid on interval `[a, b]` including the bounds. 1848 a = finite left interval bound 1849 b = finite right interval bound 1850 tolerance = (optional) relative tolerance should be greater or equal to `T.epsilon` 1851 1852 Returns: 1853 Integral value 1854 +/ 1855 T integrate(alias f, T)(const T a, const T b, const T tolerance = T.epsilon) 1856 if (__traits(isFloating, T)) 1857 { 1858 if (false) // break attributes 1859 T y = f(T(1)); 1860 scope funInst = delegate(T x) { 1861 return T(f(x)); 1862 }; 1863 scope fun = funInst.trustedAllAttr; 1864 return integrateImpl(fun, a, b, tolerance); 1865 } 1866 1867 /// ditto 1868 @fmamath 1869 T integrateImpl(T)( 1870 scope const T delegate(T) @safe pure nothrow @nogc f, 1871 const T a, 1872 const T b, 1873 const T tolerance = T.epsilon, 1874 ) @safe pure nothrow @nogc 1875 if (__traits(isFloating, T)) 1876 in { 1877 assert(-T.infinity < a); 1878 assert(b < +T.infinity); 1879 assert(a < b); 1880 assert(tolerance >= T.epsilon); 1881 } 1882 do { 1883 pragma(inline, false); 1884 enum T alpha = 0.816496580927726032732428024901963797322; 1885 enum T beta = 0.447213595499957939281834733746255247088; 1886 enum T x1 = 0.94288241569947971905635175843185720232; 1887 enum T x2 = 0.64185334234978130978123554132903188394; 1888 enum T x3 = 0.23638319966214988028222377349205292599; 1889 enum T A = 0.015827191973480183087169986733305510591; 1890 enum T B = 0.094273840218850045531282505077108171960; 1891 enum T C = 0.19507198733658539625363597980210298680; 1892 enum T D = 0.18882157396018245442000533937297167125; 1893 enum T E = 0.19977340522685852679206802206648840246; 1894 enum T F = 0.22492646533333952701601768799639508076; 1895 enum T G = 0.24261107190140773379964095790325635233; 1896 enum T A1 = 77 / 1470.0L; 1897 enum T B1 = 432 / 1470.0L; 1898 enum T C1 = 625 / 1470.0L; 1899 enum T D1 = 672 / 1470.0L; 1900 enum T A2 = 1 / 6.0L; 1901 enum T B2 = 5 / 6.0L; 1902 1903 T m = (a + b) * 0.5f; 1904 // This fix is not part of the original algorithm 1905 if (!(m.fabs < T.infinity)) 1906 { 1907 m = a.half + b.half; 1908 } 1909 T h = (b - a) * 0.5f; 1910 // This fix is not part of the original algorithm 1911 if (!(h.fabs < T.infinity)) 1912 { 1913 h = b.half - a.half; 1914 } 1915 T[13] x = [ 1916 a, 1917 m - x1 * h, 1918 m - alpha * h, 1919 m - x2 * h, 1920 m - beta * h, 1921 m - x3 * h, 1922 m, 1923 m + x3 * h, 1924 m + beta * h, 1925 m + x2 * h, 1926 m + alpha * h, 1927 m + x1 * h, 1928 b, 1929 ]; 1930 T[13] y = [ 1931 f(x[ 0]), 1932 f(x[ 1]), 1933 f(x[ 2]), 1934 f(x[ 3]), 1935 f(x[ 4]), 1936 f(x[ 5]), 1937 f(x[ 6]), 1938 f(x[ 7]), 1939 f(x[ 8]), 1940 f(x[ 9]), 1941 f(x[10]), 1942 f(x[11]), 1943 f(x[12]), 1944 ]; 1945 T i2 = h * ( 1946 + A2 * (y[0] + y[12]) 1947 + B2 * (y[4] + y[ 8]) 1948 ); 1949 T i1 = h * ( 1950 + A1 * (y[0] + y[12]) 1951 + B1 * (y[2] + y[10]) 1952 + C1 * (y[4] + y[ 8]) 1953 + D1 * (y[6] ) 1954 ); 1955 T si = h * ( 1956 + A * (y[0] + y[12]) 1957 + B * (y[1] + y[11]) 1958 + C * (y[2] + y[10]) 1959 + D * (y[3] + y[ 9]) 1960 + E * (y[4] + y[ 8]) 1961 + F * (y[5] + y[ 7]) 1962 + G * (y[6] ) 1963 ); 1964 T erri1 = fabs(i1 - si); 1965 T erri2 = fabs(i2 - si); 1966 T R = erri1 / erri2; 1967 T tol = tolerance; 1968 if (tol < T.epsilon) 1969 tol = T.epsilon; 1970 if (0 < R && R < 1) 1971 tol /= R; 1972 si *= tol / T.epsilon; 1973 if (si == 0) 1974 si = b - a; 1975 1976 if (!(si.fabs < T.infinity)) 1977 return T.nan; 1978 1979 T step(const T a, const T b, const T fa, const T fb) 1980 { 1981 T m = (a + b) * 0.5f; 1982 // This fix is not part of the original algorithm 1983 if (!(m.fabs < T.infinity)) 1984 { 1985 m = a.half + b.half; 1986 } 1987 T h = (b - a) * 0.5f; 1988 if (!(h.fabs < T.infinity)) 1989 { 1990 h = b.half - a.half; 1991 } 1992 T[5] x = [ 1993 m - alpha * h, 1994 m - beta * h, 1995 m, 1996 m + beta * h, 1997 m + alpha * h, 1998 ]; 1999 T[5] y = [ 2000 f(x[0]), 2001 f(x[1]), 2002 f(x[2]), 2003 f(x[3]), 2004 f(x[4]), 2005 ]; 2006 T i2 = h * ( 2007 + A2 * (fa + fb) 2008 + B2 * (y[1] + y[3]) 2009 ); 2010 T i1 = h * ( 2011 + A1 * (fa + fb) 2012 + B1 * (y[0] + y[4]) 2013 + C1 * (y[1] + y[3]) 2014 + D1 * y[2] 2015 ); 2016 auto sic = si + (i1 - i2); 2017 if (!(i1.fabs < T.infinity) || sic == si || !(a < x[0]) || !(x[4] < b)) 2018 { 2019 return i1; 2020 } 2021 return 2022 + (step( a, x[0], fa, y[0]) 2023 + step(x[0], x[1], y[0], y[1])) 2024 + (step(x[1], x[2], y[1], y[2]) 2025 + step(x[2], x[3], y[2], y[3])) 2026 + (step(x[3], x[4], y[3], y[4]) 2027 + step(x[4], b, y[4], fb)); 2028 } 2029 2030 foreach (i; 0 .. 12) 2031 x[i] = step(x[i], x[i + 1], y[i], y[i + 1]); 2032 2033 return 2034 + ((x[ 0] 2035 + x[ 1]) 2036 + (x[ 2] 2037 + x[ 3])) 2038 + ((x[ 4] 2039 + x[ 5]) 2040 + (x[ 6] 2041 + x[ 7])) 2042 + ((x[ 8] 2043 + x[ 9]) 2044 + (x[10] 2045 + x[11])); 2046 } 2047 2048 /// 2049 version(mir_test) 2050 unittest 2051 { 2052 import mir.math.common; 2053 import mir.math.constant; 2054 2055 alias cosh = x => 0.5 * (exp(x) + exp(-x)); 2056 enum Pi = double(PI); 2057 2058 assert(integrate!exp(0.0, 1.0).approxEqual(double(E - 1))); 2059 assert(integrate!(x => x >= 3)(0.0, 10.0).approxEqual(7.0)); 2060 assert(integrate!sqrt(0.0, 1.0).approxEqual(2.0 / 3)); 2061 assert(integrate!(x => 23.0 / 25 * cosh(x) - cos(x))(-1.0, 1.0).approxEqual(0.479428226688801667)); 2062 assert(integrate!(x => 1 / (x ^^ 4 + x ^^ 2 + 0.9))(-1.0, 1.0).approxEqual(1.5822329637294)); 2063 assert(integrate!(x => sqrt(x ^^ 3))(0.0, 1.0).approxEqual(0.4)); 2064 assert(integrate!(x => x ? 1 / sqrt(x) : 0)(0.0, 1.0).approxEqual(2)); 2065 assert(integrate!(x => 1 / (1 + x ^^ 4))(0.0, 1.0).approxEqual(0.866972987339911)); 2066 assert(integrate!(x => 2 / (2 + sin(10 * Pi * x)))(0.0, 1.0).approxEqual(1.1547005383793)); 2067 assert(integrate!(x => 1 / (1 + x))(0.0, 1.0).approxEqual(0.6931471805599)); 2068 assert(integrate!(x => 1 / (1 + exp(x)))(0.0, 1.0).approxEqual(0.3798854930417)); 2069 assert(integrate!(x => exp(x) - 1 ? x / (exp(x) - 1) : 0)(0.0, 1.0).approxEqual(0.777504634112248)); 2070 assert(integrate!(x => sin(100 * Pi * x) / (Pi * x))(0.1, 1.0).approxEqual(0.0090986375391668)); 2071 assert(integrate!(x => sqrt(50.0) * exp(-50 * Pi * x ^^ 2))(0.0, 10.0).approxEqual(0.5)); 2072 assert(integrate!(x => 25 * exp(-25 * x))(0.0, 10.0).approxEqual(1.0)); 2073 assert(integrate!(x => 50 / Pi * (2500 * x ^^ 2 + 1))(0.0, 10.0).approxEqual(1.3263071079268e+7)); 2074 assert(integrate!(x => 50 * (sin(50 * Pi * x) / (50 * Pi * x)) ^^ 2)(0.01, 1.0).approxEqual(0.11213930374164)); 2075 assert(integrate!(x => cos(cos(x) + 3 * sin(x) + 2 * cos(2 * x) + 3 * sin(2 * x) + 3 * cos(3 * x)))(0.0, Pi).approxEqual(0.83867634269443)); 2076 assert(integrate!(x => x > 1e-15 ? log(x) : 0)(0.0, 1.0).approxEqual(-1)); 2077 assert(integrate!(x => 1 / (x ^^ 2 + 1.005))(-1.0, 1.0).approxEqual(1.5643964440690)); 2078 assert(integrate!(x => 1 / cosh(20 * (x - 0.2)) + 1 / cosh(400 * (x - 0.04)) + 1 / cosh(8000 * (x - 0.008)))(0.0, 1.0).approxEqual(0.16349495585710)); 2079 assert(integrate!(x => 4 * Pi ^^ 2 * x * sin(20 * Pi * x) * cos(2 * Pi * x))(0.0, 1.0).approxEqual(-0.6346651825434)); 2080 assert(integrate!(x => 1 / (1 + (230 * x - 30) ^^ 2))(0.0, 1.0).approxEqual(0.013492485649468)); 2081 }