@@ -139,7 +139,12 @@ private static Entity AtTheBound(Entity antiderivative, Variable x, Entity bound
139139 /// <param name="x">The variable to integrate over.</param>
140140 /// <param name="from">The lower bound.</param>
141141 /// <param name="to">The upper bound.</param>
142- /// <param name="stepCount">How many steps to take; more is more accurate and slower.</param>
142+ /// <param name="stepCount">
143+ /// How many panels to cut the segment into, each integrated by the five-point Gauss–Legendre rule; more
144+ /// is more accurate and slower. Neither bound is evaluated, so an integrable singularity at one is
145+ /// allowed, and one that diverges is NaN. The value has the digits the rule agrees on as the panels are
146+ /// doubled, at most fifteen, a double's. A singularity inside the segment is not seen.
147+ /// </param>
143148 /// <returns>The value of the integral.</returns>
144149 /// <remarks>
145150 /// This is the instance method the obsolete <c>MathS.Compute.DefiniteIntegral</c>
@@ -156,7 +161,12 @@ public Number.Complex DefiniteIntegral(Variable x, Number.Complex from, Number.C
156161 /// <param name="x">The variable to integrate over.</param>
157162 /// <param name="from">The lower bound.</param>
158163 /// <param name="to">The upper bound.</param>
159- /// <param name="stepCount">How many steps to take; more is more accurate and slower.</param>
164+ /// <param name="stepCount">
165+ /// How many panels to cut the segment into, each integrated by the five-point Gauss–Legendre rule; more
166+ /// is more accurate and slower. Neither bound is evaluated, so an integrable singularity at one is
167+ /// allowed, and one that diverges is NaN. The value has the digits the rule agrees on as the panels are
168+ /// doubled, at most fifteen, a double's. A singularity inside the segment is not seen.
169+ /// </param>
160170 /// <returns>The value of the integral.</returns>
161171 public Number . Complex DefiniteIntegral ( Variable x , EDecimal from , EDecimal to , int stepCount = 100 )
162172 => Integration . IntegrateNumerically ( this , x ,
@@ -1825,26 +1835,106 @@ internal static bool ASumOverRootsWouldAnswer
18251835 return null ;
18261836 }
18271837 /// <summary>
1828- /// Returns the approximate numeric value of a definite integral of a function. Only works for one-variable functions.
1829- /// Accuracy is limited to the number specified (default is 100).
1830- /// See more at <see cref="Entity.DefiniteIntegral(Entity.Variable, EDecimal, EDecimal, int)"/>
1838+ /// The integral of a function of one variable along the segment from <paramref name="from"/> to
1839+ /// <paramref name="to"/>, by the five-point Gauss–Legendre rule on each of
1840+ /// <paramref name="panels"/> equal panels, in double precision. See
1841+ /// <see cref="Entity.DefiniteIntegral(Entity.Variable, EDecimal, EDecimal, int)"/>.
18311842 /// </summary>
1843+ /// <remarks>
1844+ /// It was the mean of <c>panels + 1</c> equally weighted samples, the endpoints among them: a first-order
1845+ /// rule, whose error on <c>x^2</c> over <c>[0, 1]</c> halved with each doubling, 1.67e-4 at 1000 steps,
1846+ /// and printed to a hundred digits. And a convergent integral with a singular endpoint had no value:
1847+ /// <c>sin(x)/x</c> and <c>ln(x)</c> over <c>[0, 1]</c> were NaN. Gauss–Legendre never evaluates an
1848+ /// endpoint, and the five-point rule is exact on each panel for a polynomial of degree nine. It is run on
1849+ /// the panels, twice as many and four times as many: where the differences do not shrink, the integral
1850+ /// diverges at a bound and is NaN, and otherwise the rest of their geometric series is added and is the
1851+ /// error, and the value is rounded to its place, fifteen digits at most. A singularity inside the
1852+ /// segment is not seen, and a symmetric divergent one, as in 1/x over [-1, 1], comes to its principal
1853+ /// value.
1854+ /// https://github.com/asc-community/AngouriMath/issues/1675
1855+ /// </remarks>
18321856 /// <param name="expr">Expression to integrate</param>
18331857 /// <param name="x">Variable to integrate over</param>
18341858 /// <param name="from">The complex lower bound for integrating</param>
18351859 /// <param name="to">The complex upper bound for integrating</param>
1836- /// <param name="accuracy">Accuracy (for now, number of iterations) </param>
1837- internal static Complex IntegrateNumerically ( Entity expr , Entity . Variable x , Complex from , Complex to , int accuracy = 100 )
1860+ /// <param name="panels">How many panels the segment is cut into, five evaluations each </param>
1861+ internal static Complex IntegrateNumerically ( Entity expr , Entity . Variable x , Complex from , Complex to , int panels = 100 )
18381862 {
1839- System . Numerics . Complex res = 0 ;
1840- var cfunc = expr . Compile ( x ) ;
1841- for ( int i = 0 ; i <= accuracy ; i ++ )
1863+ var function = expr . Compile ( x ) ;
1864+ var start = from . ToNumerics ( ) ;
1865+ var length = to . ToNumerics ( ) - start ;
1866+ panels = System . Math . Max ( 1 , panels ) ;
1867+ // The integral on so many panels, and the integral of the modulus, which the rounding is measured
1868+ // against: an integral that comes to zero, x^3 over [-1, 1], is a sum of terms that do not.
1869+ ( System . Numerics . Complex Value , double OfTheModulus ) OnPanels ( int count )
18421870 {
1843- var share = ( ( EDecimal ) i ) / accuracy ;
1844- var tmp = Complex . Create ( from . RealPart . EDecimal * share + to . RealPart . EDecimal * ( 1 - share ) , from . ImaginaryPart . EDecimal * share + to . ImaginaryPart . EDecimal * ( 1 - share ) ) ;
1845- res += cfunc . Substitute ( tmp . ToNumerics ( ) ) ;
1871+ System . Numerics . Complex sum = 0 ;
1872+ var ofTheModulus = 0.0 ;
1873+ for ( var i = 0 ; i < count ; i ++ )
1874+ {
1875+ var centre = start + length * ( ( i + 0.5 ) / count ) ;
1876+ for ( var k = 0 ; k < GaussLegendreNodes . Length ; k ++ )
1877+ {
1878+ var term = GaussLegendreWeights [ k ] * function . Substitute ( centre + length * ( GaussLegendreNodes [ k ] / ( 2.0 * count ) ) ) ;
1879+ sum += term ;
1880+ ofTheModulus += System . Numerics . Complex . Abs ( term ) ;
1881+ }
1882+ }
1883+ var width = System . Numerics . Complex . Abs ( length ) / ( 2.0 * count ) ;
1884+ return ( sum * length / ( 2.0 * count ) , ofTheModulus * width ) ;
18461885 }
1847- return res . ToNumber ( ) / ( accuracy + 1 ) * ( to - from ) ;
1886+ // On the panels, twice as many and four times as many. Never evaluating a bound, the rule gives a
1887+ // divergent integral a finite value: 1/x over [0, 1] came to about 10. Where it converges the
1888+ // differences between the three shrink, by r = 2^-(1 - p) for an endpoint singularity like x^-p,
1889+ // p < 1; for 1/x they stay at ln(2), and they grow for anything worse. So differences that do not
1890+ // shrink, above the rounding, are NaN; and where they shrink, the rest of the geometric series,
1891+ // d r/(1 - r) for the last difference d, is both the correction and the error: for x^-0.9 the last
1892+ // difference is a fourteenth of the error, and the value without it was 7 where it is 10.
1893+ var ( coarse , _) = OnPanels ( panels ) ;
1894+ var ( middle , _) = OnPanels ( 2 * panels ) ;
1895+ var ( fine , ofTheModulus ) = OnPanels ( 4 * panels ) ;
1896+ if ( double . IsNaN ( fine . Real ) || double . IsNaN ( fine . Imaginary ) || double . IsInfinity ( fine . Real ) || double . IsInfinity ( fine . Imaginary ) )
1897+ return fine . ToNumber ( ) ;
1898+ var rounding = 1e-14 * System . Math . Max ( ofTheModulus , System . Numerics . Complex . Abs ( fine ) ) ;
1899+ if ( rounding == 0 )
1900+ return Integer . Zero ;
1901+ var first = System . Numerics . Complex . Abs ( middle - coarse ) ;
1902+ var last = System . Numerics . Complex . Abs ( fine - middle ) ;
1903+ if ( last > rounding && last >= 0.99 * first )
1904+ return Real . NaN ;
1905+ var value = fine ;
1906+ var error = System . Math . Max ( last , rounding ) ;
1907+ if ( last > rounding && first > rounding )
1908+ {
1909+ var ratio = last / first ;
1910+ var tail = ( fine - middle ) * ( ratio / ( 1 - ratio ) ) ;
1911+ value = fine + tail ;
1912+ error = System . Math . Max ( System . Numerics . Complex . Abs ( tail ) , rounding ) ;
1913+ }
1914+ // Then rounded to the place of that error, and to no more digits than a double holds: the value
1915+ // was the double's exact binary expansion, fifty digits of which most were not known.
1916+ var magnitude = System . Numerics . Complex . Abs ( value ) ;
1917+ var quantum = ( int ) System . Math . Floor ( System . Math . Log10 ( error ) ) ;
1918+ if ( magnitude > 0 )
1919+ quantum = System . Math . Max ( quantum , ( int ) System . Math . Floor ( System . Math . Log10 ( magnitude ) ) - MostDigitsOfADouble + 1 ) ;
1920+ // A part that rounds to zero is zero, which prints as 0 and not as -0E-15; and a value with no
1921+ // imaginary part is real.
1922+ EDecimal Rounded ( double part )
1923+ => EDecimal . FromDouble ( part ) . Quantize ( quantum , ERounding . HalfEven ) is var rounded && rounded . IsZero ? EDecimal . Zero : rounded ;
1924+ var ( realPart , imaginaryPart ) = ( Rounded ( value . Real ) , Rounded ( value . Imaginary ) ) ;
1925+ using var _ = MathS . Settings . DowncastingEnabled . Set ( false ) ;
1926+ return imaginaryPart . IsZero ? Real . Create ( realPart ) : Complex . Create ( realPart , imaginaryPart ) ;
18481927 }
1928+
1929+ private const int MostDigitsOfADouble = 15 ;
1930+
1931+ // The five-point Gauss–Legendre rule on [-1, 1]: the roots of the fifth Legendre polynomial,
1932+ // 0 and ±sqrt(5 ∓ 2 sqrt(10/7))/3, and their weights.
1933+ [ ConstantField ]
1934+ private static readonly double [ ] GaussLegendreNodes =
1935+ { 0.0 , - 0.5384693101056831 , 0.5384693101056831 , - 0.9061798459386640 , 0.9061798459386640 } ;
1936+ [ ConstantField ]
1937+ private static readonly double [ ] GaussLegendreWeights =
1938+ { 0.5688888888888889 , 0.4786286704993665 , 0.4786286704993665 , 0.2369268850561891 , 0.2369268850561891 } ;
18491939 }
18501940}
0 commit comments