diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 58bd36482..3e5c5aa24 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -1004,6 +1004,21 @@ roots, `1 - x^2`, is taken as its two ([#718](https://github.com/asc-community/A | `"1/((1 - x^2)*(a + b*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,600 characters | | `"1/(x*(1 - x^2)*(a + b*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,637 characters | +### A hyperbolic quotient over a quadratic in its sine or cosine is asked as the sine's, before the tangent + +**Answers where there were none, and one longer spelling.** Rubi's 6.7.1 `sinh(x)^2/(a + b sinh(x) + c sinh(x)^2)` +and `sinh(x)^3/(a + b sinh(x) + c sinh(x)^2)` ran past the budget. The tangent of the half argument made each a +quartic with symbols in it, and its partial fractions did not return. Under Osborn's rule each is a rational +function of a sine over a symbolic quadratic in it, and the sine's rule for that shape is now asked first, before +the tangent. A quotient master answered some other way can come back spelled through the sine instead +([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sinh(x)^2/(a+b*sinh(x)+c*sinh(x)^2)".ToEntity().Integrate("x")` | no answer in twenty seconds | 681 characters | +| `"sinh(x)^3/(a+b*sinh(x)+c*sinh(x)^2)".ToEntity().Integrate("x")` | no answer in twenty seconds | 810 characters | +| `"cosh(x)^2/(a+b*cosh(x)+c*cosh(x)^2)".ToEntity().Integrate("x")` | no answer in twenty seconds; 791 characters on the unreleased master | 813 characters | + ### A hyperbolic integrand is asked as a trigonometric one **Answers where there were none.** The parser writes the hyperbolic functions as exponentials, so the trigonometric diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 4f63bd3f5..4edce37ab 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -1429,7 +1429,8 @@ static bool APowerOfAQuotientOfLinearsVanishingAtPlusOrMinusOne(Entity inU, Vari /// the bar of degree above two: the second pass answers it in e^L, and the question in /// s took seconds to decline, five gigabytes for 1/(1 + sinh(x)^3) against 0.2. /// - internal static Entity? SolveAHyperbolicIntegrandAsATrigonometricOne(Entity expr, Entity.Variable x, bool integrateByParts, bool rationalToo) + internal static Entity? SolveAHyperbolicIntegrandAsATrigonometricOne(Entity expr, Entity.Variable x, bool integrateByParts, bool rationalToo, + bool overAQuadraticInTheSine = false) { // Only the question asked: a question in s is not cheap to decline, and below the top every // sub-question a route gives up on would ask it. @@ -1497,7 +1498,11 @@ static bool APowerOfAQuotientOfLinearsVanishingAtPlusOrMinusOne(Entity inU, Vari && AFactorAboveAQuadraticInS(inS, s)) return null; var question = MathS.i / slopeOfL * inS; - if (Integration.ComputeAsAQuestionOfItsOwn(question, s, integrateByParts) is not { } inTermsOfS + // Or the one rule for a rational function of a sine or a cosine over a symbolic quadratic in + // it, asked ahead of the exponential substitution and cheap to decline. + if ((overAQuadraticInTheSine + ? SolveARationalFunctionOfASineOverASymbolicQuadratic(inS, s, integrateByParts) is { } overTheQuadratic ? MathS.i / slopeOfL * overTheQuadratic : null + : Integration.ComputeAsAQuestionOfItsOwn(question, s, integrateByParts)) is not { } inTermsOfS || inTermsOfS.Nodes.Any(node => node is Integralf || node == MathS.NaN)) return null; var linear = interceptOfL.Evaled is Number.Complex { IsZero: true } ? slopeOfL * x : interceptOfL + slopeOfL * x; diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 1544c7c7b..1e7d235de 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -1229,6 +1229,11 @@ internal static bool ASumOverRootsWouldAnswer // `cosh(x)^7/(a + b cosh(x)^2)` and `sech(x)^3/(a + b sinh(x))^2` are a second here // and past the budget there. if ((answer = IndefiniteIntegralSolver.SolveByBiochesOddHyperbolicSubstitution(expr, x, integrateByParts)) is { }) return answer; + // A rational function of e^L that Osborn's rule makes one of a sine or a cosine over a symbolic + // quadratic in it, asked as that before the tangent: under it Rubi's + // `sinh(x)^2/(a + b sinh(x) + c sinh(x)^2)` is a quartic with symbols, and its partial + // fractions ran past the budget, where the sine's rule answers the question in s at once. + if ((answer = IndefiniteIntegralSolver.SolveAHyperbolicIntegrandAsATrigonometricOne(expr, x, integrateByParts, rationalToo: true, overAQuadraticInTheSine: true)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionOfTheHyperbolicTangent(expr, x, integrateByParts)) is { }) return answer; // A rational function of a sine or a cosine over a quadratic in it with a symbol // among the coefficients, split over the quadratic's two roots: under the diff --git a/Sources/Tests/UnitTests/Calculus/AHyperbolicQuotientOverAQuadraticInItAsTheSinesIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AHyperbolicQuotientOverAQuadraticInItAsTheSinesIntegralTest.cs new file mode 100644 index 000000000..f49570725 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AHyperbolicQuotientOverAQuadraticInItAsTheSinesIntegralTest.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) 2019-2026 Angouri. +// AngouriMath is licensed under MIT. +// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. +// Website: https://am.angouri.org. +// + +using System; +using System.Threading; +using System.Threading.Tasks; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// A rational function of a hyperbolic sine or cosine over a symbolic quadratic in it, asked under + /// Osborn's rule as one of a sine over a quadratic before the tangent of the half argument: under that + /// tangent Rubi's 6.7.1 sinh(x)^2/(a + b sinh(x) + c sinh(x)^2) is a quartic with symbols in it, + /// and its partial fractions ran past the budget. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class AHyperbolicQuotientOverAQuadraticInItAsTheSinesIntegralTest + { + [Theory] + [InlineData("sinh(x)^2/(a + b*sinh(x) + c*sinh(x)^2)")] + [InlineData("sinh(x)^3/(a + b*sinh(x) + c*sinh(x)^2)")] + [InlineData("cosh(x)^2/(a + b*cosh(x) + c*cosh(x)^2)")] + public void IsAnswered(string integrand) + { + Entity? answer = null; + // A thread of its own, so that the guard is not spent waiting behind other tests' work. + var task = Task.Factory.StartNew(() => answer = integrand.ToEntity().Integrate("x"), + CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); + Assert.True(task.Wait(LimitTermination.Guard), $"{integrand} did not finish within {LimitTermination.Guard}"); + var integral = answer!; + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { -1.7, -0.6, 0.4, 1.3, 2.2 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + Assert.False(got.IsNaN, $"d/dx of the antiderivative of {integrand} has no value at x = {at}, where the integrand is {want}"); + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) + < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + } +}