diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index a24ce827b..4ccb886a4 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -1158,6 +1158,21 @@ rule answers, where the imaginary unit in the coefficient is read by nothing els | `"x/(2+2*i*tan(x))".Integrate("x")` | left unevaluated | the antiderivative | | `"(c+d*x)/(a+i*a*tan(pe+f*x))".Integrate("x")` | left unevaluated | the antiderivative | +### A symbolic constant factor no longer stops the rounds of parts + +`F^(c (a + b x)) sin(d + pe x)^3` was left as written. The exponent's constant part becomes a +factor -- `F^(a c)` -- and the by-parts loop stops when the factor it carries differentiates to +nothing; but `F^(a c)` differentiates to a *conditional* zero, `0 provided not F = 0 or a c > 0`, +and the loop compared against the number, so it carried on until it ran out of steps and +declined. The derivative is read bare now: the conditions are the constant's own and travel with +it in the answer, and what the loop asks is whether the degree has fallen to nothing. Rubi's +4.7.6 and 6.1.5 ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"F^(c*(a+b*x))*sin(d+pe*x)^3".Integrate("x")` | left unevaluated | the antiderivative | +| `"F^(c*(a+b*x))*sin(d+pe*x)*cos(d+pe*x)".Integrate("x")` | left unevaluated | the antiderivative | + ### `binomial(n, k)` is a function **Addition, not silent.** The binomial coefficient is a node, `Entity.Binomialf`, spelled diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 9dce20aa0..599e36c75 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -3403,8 +3403,13 @@ void Add(int k, bool isSine, Entity coefficient) var antiderivative = exponential * (nextC * cosine + nextD * sine); total += carried * antiderivative; - var derivative = carried.Differentiate(x).InnerSimplified; - if (derivative == 0) + // Bare: a constant factor with symbols in it differentiates to a *conditional* + // zero -- `F^(a c)` gives `0 provided not F = 0 or a c > 0` -- and the loop, + // comparing against the number, carried on until it ran out of steps and + // declined. The conditions are the constant's own and travel with it in the + // answer; what the loop asks is whether the degree has fallen to nothing. + var derivative = Functions.PartialFractions.Bare(carried.Differentiate(x).InnerSimplified); + if (derivative == 0 || derivative.Evaled is Number.Complex { IsZero: true }) return total.InnerSimplified; // `- int P'(x) * (that) dx`, which is this loop again with the sign folded in. carried = (-derivative).InnerSimplified; diff --git a/Sources/Tests/UnitTests/Calculus/PolynomialExponentialTrigTest.cs b/Sources/Tests/UnitTests/Calculus/PolynomialExponentialTrigTest.cs index e4cd459c3..234ae44c2 100644 --- a/Sources/Tests/UnitTests/Calculus/PolynomialExponentialTrigTest.cs +++ b/Sources/Tests/UnitTests/Calculus/PolynomialExponentialTrigTest.cs @@ -262,5 +262,38 @@ public void TheResonanceIsSeenSymbolically(string integrand, string? symbol) [InlineData("x^2/(3 - 3*i*cot(x))")] [InlineData("(1 + 2*x)/(3 + 3*i*tan(1/2 + x))^2")] public void AnImaginaryTangentBelowTheBarIsAnExponential(string integrand) => DifferentiatesBack(integrand); + + /// + /// A base with symbols in it, and an exponent whose constant part has them too: + /// F^(c (a + b x)) leaves F^(a c) as a constant factor, and that factor + /// differentiates to a conditional zero -- 0 provided not F = 0 or a c > 0 -- + /// where the by-parts loop compared against the number and carried on until it ran out of + /// steps. Rubi's 4.7.6 and 6.1.5. + /// #718 + /// + [Theory] + [InlineData("F^(c*(a + b*x))*sin(d + pe*x)^3")] + [InlineData("F^(c*(a + b*x))*cos(d + pe*x)^2")] + [InlineData("F^(c*(a + b*x))*sin(d + pe*x)*cos(d + pe*x)")] + public void ASymbolicConstantFactorDoesNotStopTheParts(string integrand) + { + var integral = integrand.ToEntity().Integrate("x").Substitute("C", 0); + Assert.DoesNotContain("integral(", integral.Stringize()); + Assert.DoesNotContain("NaN", integral.Stringize()); + Entity Pin(Entity e) => e.Substitute("F", 2.3).Substitute("a", 0.4).Substitute("b", 1.1) + .Substitute("c", 0.7).Substitute("d", 0.9).Substitute("pe", 1.3); + var derivative = Pin(integral).Differentiate("x"); + var original = Pin(integrand.ToEntity()); + foreach (var at in Points) + { + var got = derivative.Substitute("x", at).EvalNumerical(); + var want = original.Substitute("x", at).EvalNumerical(); + Assert.False(got.IsNaN, $"the antiderivative of {integrand} differentiates to NaN at x = {at}"); + var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart); + var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)); + Assert.True(difference / scale < 1e-9, + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } } }