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
17 changes: 17 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,23 @@ rule that does the same for a polynomial under a square root, whose answers are
| `"1/sqrt(a*cot(x)^2)".Integrate("x")` | `-sgn(tan(x)) ln(1/sqrt(1 + abs(tan(x))^2))/sqrt(a)` | `sgn(cot(x)) ln(1 + tan(x)^2)/(2 sqrt(a))` |
| `"(b*tan(x)^2)^(5/2)".Integrate("x")` | `sgn(tan(x)) b^(5/2) ((abs(tan(x))^2)^2/2 - abs(tan(x))^2 + ln(abs(tan(x))^2 + 1))/2` | the same with `tan(x)` for `abs(tan(x))` |

### A power of the cotangent that is not whole below the bar is integrated beside the tangent

**Answers where there were none.** `1/(cot(x)^(7/2) (a + b tan(x))^(3/2))` was declined while
`tan(x)^(7/2)/(a + b tan(x))^(3/2)` was answered: the substitution `u = tan(x)` wrote the cotangent
as `1/u`, and nothing read a power of `1/u` that is not whole below the bar. Such a power is written
as the tangent's above it now, `1/cot(x)^p = tan(x)^p/K`, with `K = cot(x)^p tan(x)^p` dividing the
answer: `K` is 1 where the tangent is positive and a constant on every interval where it keeps its
sign, so the answer holds where the tangent is negative too. Only below the bar and beside the
tangent; a power of the cotangent above it, or alone, keeps its answer. Rubi's 4.3.2.1 and 4.3.3.1
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/(cot(x)^(7/2)*(a + b*tan(x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative in `sqrt(tan(x))` and `sqrt(a + b tan(x))`, over `K` |
| `"1/(cot(x)^(5/2)*(a + i*a*tan(x))^(5/2))".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative in `sqrt(tan(x))/sqrt(1 + i tan(x))`, over `K` |
| `"(A + B*tan(x))/(cot(x)^(3/2)*(a + i*a*tan(x)))".ToEntity().Integrate("x")` | `integral(...)` | an antiderivative in `sqrt(tan(x))`, over `K` |

### A fractional power of a product of trigonometric functions keeps its sign

**Wrong answers, silent.** The `sin^p cos^q` reader took a power of a product with an even
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8795,6 +8795,51 @@ private static bool TryReadAHomogeneousTrigonometricPolynomial(
return true;
}

/// <summary>
/// A power of the cotangent that is not whole below the bar of an integrand with the
/// tangent of the same argument in it, written as a power of the tangent above it:
/// <c>1/cot(z)^p = K tan(z)^p</c> with <c>K = 1/(cot(z)^p tan(z)^p)</c>, which is 1
/// wherever the tangent is positive and a constant on every interval where it keeps its
/// sign, so it stands in front of the answer, the way Rubi writes it.
/// </summary>
/// <remarks>
/// <c>1/(cot(x)^(7/2) (a + b tan(x))^(3/2))</c> was declined while
/// <c>tan(x)^(7/2)/(a + b tan(x))^(3/2)</c> was answered: the tangent substitution
/// writes the cotangent as <c>1/tan</c>, and a power of <c>1/u</c> that is not whole below
/// the bar is read by no rule. Only below the bar: above it the substitution's
/// <c>(1/u)^p</c> is read, and <c>cot(x)^(3/2)/(a + b tan(x))</c>, answered in four
/// seconds so, ran past a minute as <c>tan(x)^(-3/2)</c>, while
/// <c>cot(x)^(7/2) sqrt(a + b tan(x))</c>, declined in a second, took a hundred to be.
/// And only beside the tangent; a power of the cotangent alone keeps the answer it had.
/// Rubi's 4.3.2.1 and 4.3.3.1.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveByWritingAPowerOfTheCotangentInTheTangent(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!expr.Nodes.Any(node => node is Cotanf))
return null;
Entity? power = null;
Entity? argument = null;
foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr))
if (underneath && factor is Powf(Cotanf(var cotangentArgument), var exponent) && cotangentArgument.ContainsNode(x)
&& exponent.Evaled is Number.Rational fraction && fraction is not Number.Integer)
{
if (power is not null)
return null;
(power, argument) = (factor, cotangentArgument);
}
if (power is not Powf(var cotangent, var p) || argument is null)
return null;
// The cotangent nowhere else, and the tangent somewhere: the power is then all of
// the cotangent there is, and the integrand is the tangent substitution's.
var tangent = MathS.Tan(argument);
if (expr.Nodes.Count(node => node == cotangent) != 1 || !expr.ContainsNode(tangent))
return null;
var rewritten = expr.Replace(node => node == power ? MathS.Pow(tangent, (-p).InnerSimplified) : node);
var constant = power * MathS.Pow(tangent, p);
return Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts)?.Pipe(answer => answer / constant);
}

/// <summary>
/// An integrand that is a function of <c>tan(x)</c> and of nothing else, integrated by
/// the substitution <c>u = tan(x)</c>, under which <c>dx</c> is <c>du/(1 + u^2)</c>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// the pair of exponents they are. After the reduction, because a power of the secant
// alone is both rules' and the reduction's answer for it is shorter.
if ((answer = IndefiniteIntegralSolver.SolveByTrigonometricPowerSubstitution(expr, x)) is { }) return answer;
// A power of the cotangent that is not whole beside the tangent, written as the
// tangent's with the constant that takes in front, which the substitution below reads.
if ((answer = IndefiniteIntegralSolver.SolveByWritingAPowerOfTheCotangentInTheTangent(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveByTangentSubstitution(expr, x, integrateByParts)) is { }) return answer;
// A root of a quadratic in the tangent with a linear term, rotated until it has
// none: `1/sqrt(a + b tan(x) + c tan(x)^2)` is `1/sqrt(A + C tan(y)^2)` under
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,37 @@ public void SymbolsArePinnedBeforeTheParityIsDecided()
[InlineData("tan(x) ^ 3")]
public void APowerOfTheTangent(string integrand) => DifferentiatesBack(integrand);

/// <summary>
/// A power of the cotangent that is not whole below the bar beside the tangent, which is
/// the tangent's power above it over <c>cot(x)^p tan(x)^p</c>, a constant on every
/// interval where the tangent keeps its sign. Compared where the tangent is positive and
/// the integrand real, and where it is negative and the integrand imaginary, which is
/// where the constant is what keeps the answer right: it is -1 there for an odd number of
/// halves and <c>i</c> for a quarter. Rubi's 4.3.2.1 and 4.3.3.1.
/// </summary>
[Theory]
[InlineData("1/(cot(x)^(7/2)*(a + b*tan(x))^(3/2))")]
[InlineData("1/(cot(x)^(5/2)*(a + i*a*tan(x))^(5/2))")]
[InlineData("(A + B*tan(x))/(cot(x)^(3/2)*(a + i*a*tan(x)))")]
[InlineData("1/(cot(x)^(1/4)*(a + b*tan(x)))")]
public void APowerOfTheCotangentBelowTheBarBesideTheTangent(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pin(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("A", 0.9).Substitute("B", 1.4);
var derivative = Pin(integral.Substitute("C", 0).Differentiate("x"));
var original = Pin(integrand.ToEntity());
foreach (var at in new[] { -1.1, -0.4, 0.3, 0.8, 1.2 })
{
var got = derivative.Substitute("x", at).EvalNumerical();
var want = original.Substitute("x", at).EvalNumerical();
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}");
}
}

/// <summary>
/// What the neighbours still answer, and must go on answering the same way: the tangent
/// itself has a rule, which is reached before this and gives the shorter form.
Expand Down
Loading