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
18 changes: 18 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,24 @@ power up each step, and what reaches the first power is integrated once over the
| `"1/(a + b*x^2 + c*x^4)^3".ToEntity().Integrate("x")` | `integral(...)` | the same |
| `"x/(a + b*x^3 + c*x^6)^2".ToEntity().Integrate("x")` | `integral(...)` | a polynomial over `a + b x^3 + c x^6`, and the integral of a polynomial over it |

### `a + i a tan(z)` below the bar is integrated beside a root of the tangent, and beside `q - i q tan(z)` above it

**Answers where there were none.** `sqrt(tan(c + d x)) (A + B tan(c + d x))/(a + i a tan(c + d x))`
ran past the budget, with the rest of Rubi's 4.3.3.1 beside a root of the tangent or the
cotangent, and so did `(a + i a tan(z))^2 (A + B tan(z))/(q - i q tan(z))^4` and the other whole
powers of the two sums on the two sides of the bar. `a + i a tan(z)` below the bar is
`a e^(i z)/cos(z)`, and the integrand was written so wherever the sum was found. Beside a root of
the tangent that spelling is read by nothing, and the integrand is left to the substitution
`t = tan(z)`, which answers it as written. Above the bar, `q - i q tan(z)` was left in sines and
cosines, a sum of exponentials that nothing gathered; it is `q e^(-i z)/cos(z)` the same way now,
and the two together are a polynomial in `e^(i z)` and its reciprocal, multiplied out
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sqrt(tan(c + d*x))*(A + B*tan(c + d*x))/(a + i*a*tan(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | arctangents and logarithms of `sqrt(tan(c + d x))` |
| `"(a + i*a*tan(c + d*x))^2*(A + B*tan(c + d*x))/(q - i*q*tan(c + d*x))^4".ToEntity().Integrate("x")` | `integral(...)` | a sum of multiples of `e^(k i (c + d x))` |

### A polynomial over a power of one linear with a symbol in it is written in powers of the linear

**Answers where there were none.** `t^9/(a + b t)^8` is a polynomial and eight powers of `1/(a + b t)`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25121,7 +25121,7 @@ static bool IsAWholeNumberOfSteps(ERational difference)
if (!below.ContainsNode(x))
return null;
var arguments = new List<Entity>();
var rewritten = below.Replace(node =>
Entity AsAnExponential(Entity node)
{
if (node is not Sumf and not Minusf || !node.ContainsNode(x))
return node;
Expand Down Expand Up @@ -25175,14 +25175,32 @@ static bool IsAWholeNumberOfSteps(ERational difference)
return isTangent
? constant * exponential / MathS.Cos(argument)
: (plus ? MathS.i : -MathS.i) * constant * exponential / MathS.Sin(argument);
});
}
var rewritten = below.Replace(AsAnExponential);
if (rewritten == below)
return null;
// A tangent or a cotangent of the same argument above the bar in sines and cosines, so that
// the cosine or sine the identity puts below cancels it: `tan(z)/(A + i A tan(z))` is
// `sin(z) e^(-i z)/A`, an exponential times a sine, where `tan(z) cos(z) e^(-i z)/A` was
// a search past the budget with `z = c + d x`.
var inSinesAndCosines = above.Replace(node => node switch
// A root of the tangent or the cotangent of that argument, or of a constant times one, is
// read as written by the substitution `t = tan(z)`, where it is a root of `t`, and by
// nothing in the exponential's spelling: `sqrt(tan(c + d x))/(a + i a tan(c + d x))`
// rewritten was seven seconds of search to be declined, and is a third of one in `t`.
bool ARootOfTheTangent(Entity node)
{
if (node is not Powf(var radicand, Number.Rational power) || power is Number.Integer)
return false;
var varying = Mulf.LinearChildren(radicand).Where(factor => factor.ContainsNode(x)).ToList();
return varying is [Tanf(var tangentOf)] && arguments.Contains(tangentOf)
|| varying is [Cotanf(var cotangentOf)] && arguments.Contains(cotangentOf);
}
if (expr.Nodes.Any(ARootOfTheTangent))
return null;
// The sums above the bar as exponentials too, now that one below has called for them:
// `(a + i a tan(z))^2/(c - i c tan(z))^4` is `a^2 e^(6 i z) cos(z)^2/c^4`, where the square
// above in sines and cosines was a sum of exponentials that nothing gathered, and a search
// past the budget. And a tangent or a cotangent of the same argument above the bar in sines
// and cosines, so that the cosine or sine the identity puts below cancels it:
// `tan(z)/(A + i A tan(z))` is `sin(z) e^(-i z)/A`, an exponential times a sine, where
// `tan(z) cos(z) e^(-i z)/A` was a search past the budget with `z = c + d x`.
var inSinesAndCosines = above.Replace(AsAnExponential).Replace(node => node switch
{
Tanf(var inner) when arguments.Contains(inner) => MathS.Sin(inner) / MathS.Cos(inner),
Cotanf(var inner) when arguments.Contains(inner) => MathS.Cos(inner) / MathS.Sin(inner),
Expand All @@ -25208,15 +25226,49 @@ static bool IsAWholeNumberOfSteps(ERational difference)
return Integration.ComputeAsTheSameQuestion((above / rewritten).InnerSimplified, x, integrateByParts);
// And what sines and cosines of it are left, as the exponentials they are, so that the whole is
// a rational function of `e^(i z)`: `tan(z)^2/(A + i A tan(z))` leaves `sin(z)^2/(A cos(z) e^(i z))`.
var inExponentials = (top / bottom).Replace(node => node switch
Entity InExponentials(Entity written) => written.Replace(node => node switch
{
Sinf(var inner) when arguments.Contains(inner)
=> (MathS.Pow(MathS.e, MathS.i * inner) - MathS.Pow(MathS.e, -MathS.i * inner)) / (2 * MathS.i),
Cosf(var inner) when arguments.Contains(inner)
=> (MathS.Pow(MathS.e, MathS.i * inner) + MathS.Pow(MathS.e, -MathS.i * inner)) / 2,
_ => node,
});
return Integration.ComputeAsTheSameQuestion(inExponentials.InnerSimplified, x, integrateByParts);
// Multiplied out where it is a polynomial in `e^(i z)` and its reciprocal with nothing else of x
// in it, the exponentials below the bar taken above as their reciprocals: a sum of exponentials,
// each term one the closed rule answers. `(a + i a tan(z))^2 (A + B tan(z))/(c - i c tan(z))^6`
// is `a^2 e^(8 i z) cos(z)^4 (A + B tan(z))/c^6`, fourteen seconds through the hyperbolic
// tangent's substitution as written and forty milliseconds multiplied out. Not beside a
// polynomial in x, which would be multiplied out too, and its answer with it.
Entity? Reciprocal(Entity below) => below switch
{
_ when !below.ContainsNode(x) => 1 / below,
Mulf(var left, var right) => Reciprocal(left) is { } ofLeft && Reciprocal(right) is { } ofRight ? ofLeft * ofRight : null,
Powf(var @base, var exponent) when @base == MathS.e => MathS.Pow(MathS.e, -exponent),
Powf(var inner, Number.Integer power) => Reciprocal(inner) is { } ofInner ? MathS.Pow(ofInner, power) : null,
_ => null,
};
if (Reciprocal(bottom) is not { } belowTakenAbove
|| top.Replace(node => node is Sinf or Cosf || TheExponentOf(node) is not null ? Number.Integer.One : node).ContainsNode(x))
return Integration.ComputeAsTheSameQuestion(InExponentials(top / bottom).InnerSimplified, x, integrateByParts);
var overTheBar = top * belowTakenAbove;
// The exponentials of each term multiplied out gathered into one, whose exponent, a sum of
// theirs, `i x (-1) + i x`, is gathered to its slope and offset, so that a term whose
// exponents cancel is a constant.
var multipliedOut = Patterns.GatherPowersOfOneBase(InExponentials(overTheBar).Expand().InnerSimplified).Replace(node =>
TheExponentOf(node) is { } exponent && exponent.ContainsNode(x)
&& TreeAnalyzer.TryGetPolyLinear(exponent, x, out var slope, out var offset)
? MathS.Pow(MathS.e, slope.InnerSimplified * x + offset.InnerSimplified)
: node);
return Integration.ComputeAsTheSameQuestion(multipliedOut.InnerSimplified, x, integrateByParts);

// The exponent of an exponential, or of a whole power of one, as one exponential.
static Entity? TheExponentOf(Entity node) => node switch
{
Powf(var @base, var exponent) when @base == MathS.e => exponent,
Powf(Powf(var @base, var exponent), Number.Integer times) when @base == MathS.e => exponent * times,
_ => null,
};
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// 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
{
/// <summary>
/// <c>a + i a tan(z)</c> below the bar, which is <c>a e^(i z)/cos(z)</c>: beside a root of the
/// tangent the integrand is left to the substitution <c>t = tan(z)</c>, where the root is a root
/// of <c>t</c>; and beside <c>c - i c tan(z)</c> above the bar, <c>c e^(-i z)/cos(z)</c>, the
/// two are a polynomial in <c>e^(i z)</c> and its reciprocal, multiplied out. Rubi's 4.3.3.1.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
/// <remarks>Compared as complex numbers, the integrands being complex.</remarks>
[Trait("Area", "Calculus")]
public sealed class ImaginaryTangentSumAsAnExponentialIntegralTest
{
[Theory]
[InlineData("sqrt(tan(c + d*x))/(a + i*a*tan(c + d*x))")]
[InlineData("sqrt(tan(c + d*x))*(A + B*tan(c + d*x))/(a + i*a*tan(c + d*x))")]
[InlineData("tan(c + d*x)^(3/2)*(A + B*tan(c + d*x))/(a + i*a*tan(c + d*x))^2")]
[InlineData("(A + B*tan(c + d*x))/(sqrt(cot(c + d*x))*(a + i*a*tan(c + d*x)))")]
[InlineData("(a + i*a*tan(c + d*x))^2*(A + B*tan(c + d*x))/(q - i*q*tan(c + d*x))^4")]
[InlineData("(a + i*a*tan(c + d*x))^3*(A + B*tan(c + d*x))/(q - i*q*tan(c + d*x))^5")]
public void IsIntegrated(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("A", 1.1).Substitute("B", 0.6)
.Substitute("c", 0.4).Substitute("d", 1.1).Substitute("q", 0.7);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -1.1, -0.6, 0.3, 0.9 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
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}");
}
}
}
}
Loading