Skip to content

Commit 009003c

Browse files
The interval evaluation reads the special functions (#1621)
The interval evaluation, the fast tier the integrator's checks run on (#1497), read no special function. A check with erf, Ei or Si in it was undecided there and went on to the decimal evaluation, which calls the arbitrary-precision kernels at 40 and then 80 digits. It now takes the double-precision routines in AngouriMath.Numerics, as suggested on #1607. Each value is enclosed as its routine's value at the rectangle's middle, widened by the routine's error and by how far the function can move across the rectangle. The routines agree with the kernels to within 3e-13 of the value, measured and not proved, and are allowed 1e-10 of it, and 1e-10 more near a zero. The movement is the largest the derivative is on the rectangle times its half diagonal; the derivatives are elementary, so the intervals bound them themselves. A rectangle that straddles a cut of Ei, li, Ci or Chi, or where the value or the derivative overflows a double, has no enclosure and stays undecided, as before. A real value stays real where the function is. Every enclosure was checked against the kernels at 30 digits on #1613's grid, about 17,800 per function, each point as an exact double and nudged by a relative 1e-20: no kernel value falls outside its enclosure. IntervalEvaluationTest gains 17 special-function rows. Over Rubi's problems in families 0-7 whose antiderivative uses one of the nine functions, 2,354 in all, master solves 1975 and this 1976, with 0 wrong and 0 errors on both. The one move, 6.1.3:32 (sinh(a + b x^2)^3/x^3, answered in Chi and Shi), is a timeout on master. Run on its own it takes 27 to 29 s on both builds, so the change does not bring it inside the budget by itself. Part of #1607. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 58a75c7 commit 009003c

2 files changed

Lines changed: 119 additions & 0 deletions

File tree

‎Sources/AngouriMath/Numerics/IntervalEvaluation.cs‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System;
99
using PeterO.Numbers;
1010
using static AngouriMath.Entity;
11+
using NumericsComplex = System.Numerics.Complex;
1112

1213
namespace AngouriMath.Numerics
1314
{
@@ -382,6 +383,27 @@ private static ComplexInterval Evaluate(Entity expr)
382383
return RealOnly(Evaluate(argument), static x => (Interval.Exactly(1) / x).Acos());
383384
case Arccosecantf(var argument):
384385
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);
385407
case Absf(var argument):
386408
{
387409
var inner = Evaluate(argument);
@@ -523,6 +545,57 @@ private static ComplexInterval Evaluate(Entity expr)
523545
return null;
524546
}
525547

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+
526599
/// <summary>A function read on real arguments only, in this pilot.</summary>
527600
private static ComplexInterval RealOnly(ComplexInterval argument, Func<Interval, Interval> function)
528601
=> argument.IsReal && argument.Re.IsFinite ? ComplexInterval.Real(function(argument.Re)) : Undefined;

‎Sources/Tests/UnitTests/Core/IntervalEvaluationTest.cs‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,52 @@ private static void Holds(Interval interval, EDecimal value, string expression,
7373
$"the {part} part of {expression} is [{interval.Low:R}, {interval.High:R}], wider than it has to be");
7474
}
7575

76+
/// <summary>
77+
/// The special functions, from their double-precision routines. Each is allowed <c>1e-10</c>
78+
/// of its value, and <c>1e-10</c> more near a zero, against a measured error of at most
79+
/// <c>3e-13</c>. An argument that is itself an interval, <c>sqrt(2)</c> or <c>pi</c>, widens the
80+
/// value by the most the function moves across it. A real value stays real where the
81+
/// function is real, and <c>Ci(-2)</c> is <c>Ci(2) + i pi</c>.
82+
/// <a href="https://github.com/asc-community/AngouriMath/issues/1607">#1607</a>
83+
/// </summary>
84+
[Theory]
85+
[InlineData("erf(1/2)")]
86+
[InlineData("erf(sqrt(2))")]
87+
[InlineData("erfc(3)")]
88+
[InlineData("erfi(1 + i)")]
89+
[InlineData("erf(2 - 3i)")]
90+
[InlineData("Ei(2)")]
91+
[InlineData("Ei(-3/2)")]
92+
[InlineData("Ei(1 + 2i)")]
93+
[InlineData("li(5)")]
94+
[InlineData("li(1/3)")]
95+
[InlineData("Si(pi)")]
96+
[InlineData("Si(7)")]
97+
[InlineData("Ci(3)")]
98+
[InlineData("Ci(-2)")]
99+
[InlineData("Shi(-1)")]
100+
[InlineData("Chi(4)")]
101+
[InlineData("erf(1/2) + Si(2) * Ei(1)")]
102+
public void TheIntervalHoldsASpecialFunctionsValue(string expression)
103+
{
104+
var expr = expression.ToEntity();
105+
var interval = IntervalEvaluation.Of(expr);
106+
Assert.True(interval.HasValue, $"{expression} was not evaluated");
107+
var value = expr.EvalNumerical();
108+
HoldsWithin(interval!.Value.Re, value.RealPart.EDecimal, 1e-9, expression, "real");
109+
HoldsWithin(interval.Value.Im, value.ImaginaryPart.EDecimal, 1e-9, expression, "imaginary");
110+
if (value.ImaginaryPart.EDecimal.IsZero)
111+
Assert.True(interval.Value.IsReal, $"{expression} is real, and its interval is not");
112+
}
113+
114+
private static void HoldsWithin(Interval interval, EDecimal value, double width, string expression, string part)
115+
{
116+
Assert.True(EDecimal.FromDouble(interval.Low).CompareTo(value) <= 0 && EDecimal.FromDouble(interval.High).CompareTo(value) >= 0,
117+
$"the {part} part of {expression}, {value}, is outside [{interval.Low:R}, {interval.High:R}]");
118+
Assert.True(interval.High - interval.Low <= width * System.Math.Max(1.0, interval.Magnitude),
119+
$"the {part} part of {expression} is [{interval.Low:R}, {interval.High:R}], wider than it has to be");
120+
}
121+
76122
/// <summary>
77123
/// Declined, in doubles and in decimals: a variable, a pole, the logarithm of zero, a node
78124
/// the evaluation does not read, and a value whose condition fails.

0 commit comments

Comments
 (0)