|
8 | 8 | using System; |
9 | 9 | using PeterO.Numbers; |
10 | 10 | using static AngouriMath.Entity; |
| 11 | +using NumericsComplex = System.Numerics.Complex; |
11 | 12 |
|
12 | 13 | namespace AngouriMath.Numerics |
13 | 14 | { |
@@ -382,6 +383,27 @@ private static ComplexInterval Evaluate(Entity expr) |
382 | 383 | return RealOnly(Evaluate(argument), static x => (Interval.Exactly(1) / x).Acos()); |
383 | 384 | case Arccosecantf(var argument): |
384 | 385 | return RealOnly(Evaluate(argument), static x => (Interval.Exactly(1) / x).Asin()); |
| 386 | + // The special functions, from their double-precision routines. |
| 387 | + // https://github.com/asc-community/AngouriMath/issues/1607 |
| 388 | + case Erff(var argument): |
| 389 | + return Special(Evaluate(argument), SpecialFunctions.Erf, static z => TwoOverSqrtPi * (-(z * z)).Exp(), static _ => false, static _ => true); |
| 390 | + case Erfcf(var argument): |
| 391 | + return Special(Evaluate(argument), SpecialFunctions.Erfc, static z => TwoOverSqrtPi * (-(z * z)).Exp(), static _ => false, static _ => true); |
| 392 | + case Erfif(var argument): |
| 393 | + return Special(Evaluate(argument), SpecialFunctions.Erfi, static z => TwoOverSqrtPi * (z * z).Exp(), static _ => false, static _ => true); |
| 394 | + case Eif(var argument): |
| 395 | + return Special(Evaluate(argument), SpecialFunctions.Ei, static z => z.Exp() / z, static z => StraddlesTheAxisLeftOf(z, 0), static _ => true); |
| 396 | + case Lif(var argument): |
| 397 | + // Its cut is where the logarithm's is, and where Ei's is under it: 0 < x < 1. |
| 398 | + return Special(Evaluate(argument), SpecialFunctions.Li, static z => One / z.Log(), static z => StraddlesTheAxisLeftOf(z, 1), static x => x.Low >= 0); |
| 399 | + case Sif(var argument): |
| 400 | + return Special(Evaluate(argument), SpecialFunctions.Si, static z => z.Sin() / z, static _ => false, static _ => true); |
| 401 | + case Cif(var argument): |
| 402 | + return Special(Evaluate(argument), SpecialFunctions.Ci, static z => z.Cos() / z, static z => StraddlesTheAxisLeftOf(z, 0), static x => x.Low > 0); |
| 403 | + case Shif(var argument): |
| 404 | + return Special(Evaluate(argument), SpecialFunctions.Shi, static z => Sinh(z) / z, static _ => false, static _ => true); |
| 405 | + case Chif(var argument): |
| 406 | + return Special(Evaluate(argument), SpecialFunctions.Chi, static z => Cosh(z) / z, static z => StraddlesTheAxisLeftOf(z, 0), static x => x.Low > 0); |
385 | 407 | case Absf(var argument): |
386 | 408 | { |
387 | 409 | var inner = Evaluate(argument); |
@@ -523,6 +545,57 @@ private static ComplexInterval Evaluate(Entity expr) |
523 | 545 | return null; |
524 | 546 | } |
525 | 547 |
|
| 548 | + [AngouriMath.Core.ConstantField] private static readonly ComplexInterval One = ComplexInterval.Real(Interval.Exactly(1)); |
| 549 | + [AngouriMath.Core.ConstantField] private static readonly ComplexInterval TwoOverSqrtPi = ComplexInterval.Real(Interval.Around(2 / Math.Sqrt(Math.PI))); |
| 550 | + |
| 551 | + private static ComplexInterval Sinh(ComplexInterval z) => (z.Exp() - (-z).Exp()) * ComplexInterval.Real(Interval.Exactly(0.5)); |
| 552 | + private static ComplexInterval Cosh(ComplexInterval z) => (z.Exp() + (-z).Exp()) * ComplexInterval.Real(Interval.Exactly(0.5)); |
| 553 | + |
| 554 | + /// <summary>Whether the rectangle crosses the real axis left of <paramref name="end"/>, where a cut runs.</summary> |
| 555 | + private static bool StraddlesTheAxisLeftOf(ComplexInterval z, double end) |
| 556 | + => !z.IsReal && z.Im.ContainsZero && z.Re.Low < end; |
| 557 | + |
| 558 | + /// <summary> |
| 559 | + /// A special function over <paramref name="argument"/>. The value is the double-precision |
| 560 | + /// routine's at the rectangle's middle. It is widened by the routine's error, and by the most |
| 561 | + /// the function can move across the rectangle: the largest its derivative is there, which |
| 562 | + /// these intervals bound, times the rectangle's half diagonal. The value is real where the |
| 563 | + /// argument is real and <paramref name="realOn"/> says the function is real there. |
| 564 | + /// </summary> |
| 565 | + /// <remarks> |
| 566 | + /// The routines agree with the interpreter's kernels to within <c>3e-13</c> of the value, |
| 567 | + /// measured on some ten thousand points rather than proved. They are allowed <c>1e-10</c> of |
| 568 | + /// the value, and <c>1e-10</c> more for a value near a zero, where a relative error means |
| 569 | + /// nothing. A rectangle that straddles a cut, where the function jumps, has no enclosure |
| 570 | + /// this way. It is left undecided, and the caller asks the decimal evaluation. |
| 571 | + /// </remarks> |
| 572 | + private static ComplexInterval Special(ComplexInterval argument, Func<NumericsComplex, NumericsComplex> function, |
| 573 | + Func<ComplexInterval, ComplexInterval> derivative, Func<ComplexInterval, bool> straddlesACut, Func<Interval, bool> realOn) |
| 574 | + { |
| 575 | + if (!argument.IsFinite || straddlesACut(argument)) |
| 576 | + return Undefined; |
| 577 | + var middle = new NumericsComplex(argument.Re.Middle, argument.Im.Middle); |
| 578 | + var value = function(middle); |
| 579 | + if (!Interval.IsFiniteDouble(value.Real) || !Interval.IsFiniteDouble(value.Imaginary)) |
| 580 | + return Undefined; |
| 581 | + var reach = Math.Max(argument.Re.High - middle.Real, middle.Real - argument.Re.Low); |
| 582 | + var imReach = Math.Max(argument.Im.High - middle.Imaginary, middle.Imaginary - argument.Im.Low); |
| 583 | + var radius = Interval.Up(Math.Sqrt(reach * reach + imReach * imReach)); |
| 584 | + var spread = 0.0; |
| 585 | + if (radius > 0) |
| 586 | + { |
| 587 | + var slope = derivative(argument); |
| 588 | + if (!slope.IsFinite) |
| 589 | + return Undefined; |
| 590 | + spread = Interval.Up(Interval.Up(slope.Magnitude) * radius); |
| 591 | + } |
| 592 | + var error = Interval.Up(1e-10 * (Math.Sqrt(value.Real * value.Real + value.Imaginary * value.Imaginary) + 1) + spread); |
| 593 | + var re = new Interval(Interval.Down(value.Real - error), Interval.Up(value.Real + error)); |
| 594 | + if (argument.IsReal && realOn(argument.Re)) |
| 595 | + return ComplexInterval.Real(re); |
| 596 | + return new(re, new Interval(Interval.Down(value.Imaginary - error), Interval.Up(value.Imaginary + error))); |
| 597 | + } |
| 598 | + |
526 | 599 | /// <summary>A function read on real arguments only, in this pilot.</summary> |
527 | 600 | private static ComplexInterval RealOnly(ComplexInterval argument, Func<Interval, Interval> function) |
528 | 601 | => argument.IsReal && argument.Re.IsFinite ? ComplexInterval.Real(function(argument.Re)) : Undefined; |
|
0 commit comments