diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 5f7c251b9..2f66af2a3 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -117,6 +117,21 @@ coefficients are the roots of that quadratic, and the polynomial over it is spli | `"1/(4*a*c+4*c^2*x^2+4*c*d*x^3+d^2*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 3,920 characters | | `"x/(4*a*c+4*c^2*x^2+4*c*d*x^3+d^2*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 2,315 characters | +### An even power of the sine beside the secant is written in the cosine + +**Answers where there were none, shorter than master's.** Rubi's 4.5.1.3 `sin(c + d x)^2/(a + b sec(c + d x))` ran +past the corpus's budget. The half-angle substitution wrote it in 117,751 characters, and `sin(x)^4/(a + b sec(x))` in +160,657. A rational function of the secant was written in the cosine only beside the cosine itself. The sine beside it +stands to even powers only, and `sin(z)^(2k)` is `(1 - cos(z)^2)^k`, so the integrand is now written in the cosine, as a +polynomial over a linear in it. The cosine beside the cosecant is written in the sine the same way +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sin(c+d*x)^2/(a+b*sec(c+d*x))".ToEntity().Integrate("x")` | `integral(...)` | 1,187 characters, where before this change master gave 117,751 | +| `"sin(x)^4/(a+b*sec(x))".ToEntity().Integrate("x")` | `integral(...)` | 1,729 characters, where before this change master gave 160,657 | +| `"cos(x)^2/(a+b*csc(x))".ToEntity().Integrate("x")` | `integral(...)` | 994 characters, where before this change master gave 2,271 | + ### An exponential of a quadratic over a power of the linear through its vertex is a moment about it **Answers where there were none.** `f^(a + b x + c x^2)/(b + 2 c x)` was declined, and so were five more of Rubi's 2.3, diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 50e63673b..b4e62a2a7 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -46958,8 +46958,14 @@ bool IsAPolynomialInBoth(Entity polynomial) var argument = reciprocal.DirectChildren.First(); var function = reciprocal is Secantf ? MathS.Cos(argument) : MathS.Sin(argument); // The cosine of the same argument beside the secant, or the sine beside the cosecant, is - // what the rewritten integrand is in already; any other is a second function. - if (partners.Any(partner => partner != function)) + // what the rewritten integrand is in already, and the other of the two is too where it + // stands only to even powers, `sin(z)^(2k)` being `(1 - cos(z)^2)^k`: the half-angle + // substitution wrote `sin(x)^2/(a + b sec(x))` in 117,517 characters, where + // `(1 - cos(x)^2) cos(x)/(a cos(x) + b)` is 969. Any other is a second function. + var complement = reciprocal is Secantf ? MathS.Sin(argument) : MathS.Cos(argument); + static bool AnEvenPower(Entity node, Entity of) => node is Powf(var @base, Number.Integer { EInteger: var power }) && @base == of && power.IsEven && power.Sign > 0; + var inEvenPowers = expr.Nodes.Count(node => node == complement) == expr.Nodes.Count(node => AnEvenPower(node, complement)); + if (partners.Any(partner => partner != function && (partner != complement || !inEvenPowers))) return null; // Only two sums in the secant or more, all below the bar: one, or one above, the rules for // the secant answer as written, and in the cosine `sec(x)^5/(a + b sec(x))^3` ran past five @@ -46978,7 +46984,11 @@ bool IsAPolynomialInBoth(Entity polynomial) if (partners.Count > 0 && (!below.Nodes.Any(ASum) || Mulf.LinearChildren(below).Any(factor => factor is Powf(var @base, _) && ASum(@base)))) return null; - var rewritten = Functions.SingleQuotient.Combine(expr.Replace(node => node == reciprocal ? 1 / function : node)); + var rewritten = Functions.SingleQuotient.Combine(expr.Replace(node => + node == reciprocal ? 1 / function + : AnEvenPower(node, complement) && node is Powf(_, Number.Integer { EInteger: var power }) + ? MathS.Pow(1 - MathS.Pow(function, 2), Number.Integer.Create(power / 2)) + : node)); return Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts); } diff --git a/Sources/Tests/UnitTests/Calculus/AnEvenPowerOfTheSineBesideASecantIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnEvenPowerOfTheSineBesideASecantIntegralTest.cs new file mode 100644 index 000000000..9cc6e15c7 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AnEvenPowerOfTheSineBesideASecantIntegralTest.cs @@ -0,0 +1,49 @@ +// +// 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 AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// An even power of the sine beside a rational function of the secant, written in the cosine, + /// sin(z)^(2k) as (1 - cos(z)^2)^k: the half-angle substitution wrote Rubi's 4.5.1.3 + /// sin(c + d x)^2/(a + b sec(c + d x)) in 117,751 characters and past the corpus's budget, + /// where in the cosine it is a polynomial over a linear. The cosine beside the cosecant the same way. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class AnEvenPowerOfTheSineBesideASecantIntegralTest + { + [Theory] + // Rubi 4.5.1.3:238. + [InlineData("sin(c + d*x)^2/(a + b*sec(c + d*x))")] + [InlineData("sin(x)^4/(a + b*sec(x))")] + [InlineData("cos(x)^2/(a + b*csc(x))")] + public void IsAnsweredInTheCosine(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < 3000, $"{integrand} came back in {text.Length} characters"); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.9); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { -2.2, -0.9, 0.3, 1.4, 2.1 }) + { + 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}"); + } + } + } +}