Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
33 changes: 33 additions & 0 deletions Sources/Tests/UnitTests/Calculus/PolynomialExponentialTrigTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/// <summary>
/// A base with symbols in it, and an exponent whose constant part has them too:
/// <c>F^(c (a + b x))</c> leaves <c>F^(a c)</c> as a constant factor, and that factor
/// differentiates to a <em>conditional</em> zero -- <c>0 provided not F = 0 or a c > 0</c> --
/// 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.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[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}");
}
}
}
}
Loading