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
16 changes: 16 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,22 @@ polynomial in the symbols is written as the two quadratics first
| `"cot(e + f*x)^3*sqrt(a + b*sec(e + f*x))".ToEntity().Integrate("x")` | `integral(...)`; past a minute on the unreleased master | 36,760 characters, a case for each sign of the roots |
| `"sec(e + f*x)^2/(a*sin(e + f*x) + b*tan(e + f*x))^2".ToEntity().Integrate("x")` | `integral(...)`; past a minute on the unreleased master | 68,854 characters |

### A term with a power of `x` is integrated by parts with the rest of the sum in its remainder

**Answers where there were none.** `sqrt(csc(x)) (x cos(x) - 4 sec(x) tan(x))`, Rubi's 4.7.7, was declined: neither
of its two terms is elementary alone. Where a sum's terms are not integrated one by one, a term `x^k f` whose `f`
has `x` only inside transcendental functions is now integrated by parts, and what parts leaves is asked together
with the other terms. Here that is `-2 sqrt(sin(x)) - 4 sqrt(sin(x))/cos(x)^2`, a power of the sine over a power of
the cosine, which is integrated in closed form
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sqrt(csc(x))*(x*cos(x) - 4*sec(x)*tan(x))".ToEntity().Integrate("x")` | `integral(...)` | `x * -(1 / sin(x)) ^ (-1/2) / (-1/2) + (-4) * sin(x) ^ (3/2) / cos(x)` |
| `"x*sin(x)/sqrt(cos(x)) + 4*sqrt(cos(x))/sin(x)^2".ToEntity().Integrate("x")` | `integral(...)` | `x * -sqrt(cos(x)) / (1/2) + (-4) / sin(x) * cos(x) ^ (3/2)` |
| `"x^2*cos(x)*e^sin(x) + 2*x*e^sin(x)".ToEntity().Integrate("x")` | `integral(...)` | `x ^ 2 * e ^ sin(x)` |
| `"(c + d*x)*erfc(a + b*x)^2".ToEntity().Integrate("x")` | 187 characters, reading `erfc` as a symbol; `integral(...)` on the unreleased master | 1,196 characters |

### An odd power of the tangent beside a function of the secant is integrated in the secant

**Answers where there were none.** `cot(c + d x)^3/(a + b sec(c + d x))^(3/2)` ran past a minute. In `u = sec(y)`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ internal static class IndefiniteIntegralSolver
// corpus for nothing.
if (!Integration.AnsweringTheQuestionAsked)
return null;
if (integrateByParts && ByPartsAgainstThePowersOfX(splitted, x) is { } byPartsOverTheSum)
return byPartsOverTheSum;
// What is polynomial in a term is what has no root of the variable in it: a
// logarithm or an exponential of x is an indeterminate as far as gathering and
// cancelling go. Bronstein's
Expand Down Expand Up @@ -143,6 +145,276 @@ internal static class IndefiniteIntegralSolver
});
}

/// <summary>
/// A sum whose terms are not elementary apart, integrated by parts against the power
/// of <paramref name="x"/> in each term that has one, with what parts leaves and the
/// other terms asked together as one integrand: <c>x^k f + g</c> is
/// <c>x^k F + ∫(g - k x^(k - 1) F)</c> for <c>F</c> the antiderivative of <c>f</c>.
/// </summary>
/// <remarks>
/// <para>
/// <c>sqrt(csc(x)) (x cos(x) - 4 sec(x) tan(x))</c>, Rubi's 4.7.7, is
/// <c>(2 x sqrt(sin(x)) - 4 sin(x)^(3/2)/cos(x))'</c>. Split into its two terms, neither is
/// elementary: parts on the first leaves <c>-2 sqrt(sin(x))</c>, and that and the second
/// term are elementary only together, as <c>sqrt(sin(x)) (-2 - 4/cos(x)^2)</c>. So the
/// remainder is gathered into one quotient with the factors its terms share taken out,
/// which is the spelling the rules read; and where it is zero, as for
/// <c>x cos(x) e^sin(x) + e^sin(x)</c>, the parts are the answer.
/// </para>
/// <para>
/// Only where some term with a power of <paramref name="x"/> beside something else in
/// <paramref name="x"/> has an elementary <c>F</c>, and only at the question asked: the
/// remainder is a new question, and the powers of <paramref name="x"/> of the terms
/// integrated are lower in it, so asking it again descends.
/// </para>
/// </remarks>
private static Entity? ByPartsAgainstThePowersOfX(IReadOnlyList<Entity> terms, Entity.Variable x)
{
// Once: the cofactors and the remainder are asked at the top, where this runs again,
// and `(c + d x) erfc(a + b x)^2` went from a 13-second decline to a hang that way.
if (integratingASumByParts)
{
Integration.DeclinedForItsScope();
return null;
}
integratingASumByParts = true;
try
{
return ByPartsAgainstThePowersOfXOnce(terms, x);
}
finally
{
integratingASumByParts = false;
}
}

/// <summary>
/// Whether a term of <paramref name="antiderivative"/> has a factor that is a polynomial
/// in <paramref name="x"/> of positive degree.
/// </summary>
private static bool HasAPolynomialFactorIn(Entity antiderivative, Entity.Variable x)
=> Sumf.LinearChildren(antiderivative).Any(term => Mulf.LinearChildren(term).Any(factor =>
factor.ContainsNode(x) && TreeAnalyzer.TryGetPolynomial(factor, x, out var powers)
&& powers.Keys.All(power => power.Sign >= 0) && powers.Values.All(coefficient => !coefficient.ContainsNode(x))));

/// <summary>
/// Whether every occurrence of <paramref name="x"/> in <paramref name="expr"/> is inside
/// the argument of a function that is not algebraic: <c>cos(x)/sqrt(sin(x))</c>, not
/// <c>sqrt(x^2 + 3)</c> or <c>atanh(x)/(1 - x^2)</c>.
/// </summary>
private static bool OnlyInsideTranscendentalFunctions(Entity expr, Entity.Variable x)
=> !expr.ContainsNode(x)
|| expr is not (Sumf or Minusf or Mulf or Divf or Powf or Entity.Variable or Number)
|| expr is not Entity.Variable && expr.DirectChildren.All(child => OnlyInsideTranscendentalFunctions(child, x));

/// <summary>Set while <see cref="ByPartsAgainstThePowersOfX"/> asks its questions.</summary>
[System.ThreadStatic] private static bool integratingASumByParts;

private static Entity? ByPartsAgainstThePowersOfXOnce(IReadOnlyList<Entity> terms, Entity.Variable x)
{
Entity parts = Number.Integer.Zero;
var remainder = new List<Entity>();
var any = false;
foreach (var term in terms)
{
var power = EInteger.Zero;
Entity rest = Number.Integer.One;
foreach (var factor in Mulf.LinearChildren(term))
if (factor == x)
power = power.Add(EInteger.One);
else if (factor is Powf(var @base, Number.Integer { EInteger.Sign: > 0 } exponent) && @base == x)
power = power.Add(exponent.EInteger);
else
rest *= factor;
// Only a cofactor with x in it nowhere but inside transcendental functions. A power
// of x beside an algebraic one is what the algebraic reductions answer, and asking
// each such cofactor and the remainder took `(x^2 + 3)^2/(x + sqrt(x^2 + 3))` from a
// half-second decline past two minutes; and beside a rational function of x as
// well, `x^3 atanh(c x^2)/(1 - c^2 x^4)` in what parts leaves of
// `(a + b atanh(c x^2))^2/x^5`, the cofactor is a slow decline that doubled its time.
if (power.IsZero || !rest.ContainsNode(x) || !OnlyInsideTranscendentalFunctions(rest, x))
{
remainder.Add(term);
continue;
}
// A term whose cofactor is not elementary goes into the remainder as it is: in
// `x^2 cos(x) e^sin(x) + 2 x e^sin(x)` the second is what parts on the first leaves.
// And one with a polynomial in x beside something in its antiderivative: what parts
// leaves then has the power it took away, and asking it does not descend --
// `x erfc(a + b x)^2`, whose cofactor's antiderivative has `x erfc(a + b x)^2` in it.
if (Integration.ComputeAsAQuestionOfItsOwn(rest, x, integrateByParts: true) is not { } antiderivative
|| HasAPolynomialFactorIn(antiderivative, x))
{
remainder.Add(term);
continue;
}
var monomial = power.Equals(EInteger.One) ? (Entity)x : MathS.Pow(x, Number.Integer.Create(power));
parts += monomial * antiderivative;
// No `x^0` for a first power: it would bring its `provided not x = 0` along.
var lower = power.Equals(EInteger.One) ? Number.Integer.One : MathS.Pow(x, Number.Integer.Create(power.Subtract(EInteger.One)));
remainder.Add(-Number.Integer.Create(power) * lower * antiderivative);
any = true;
}
if (!any)
return null;
var left = remainder.Aggregate((l, r) => l + r).InnerSimplified;
if (TreeAnalyzer.IsZero(left))
return parts;
var gathered = OverTheLeastPowersOfTheSineAndTheCosine(left, x, out var closed);
if (closed is not null)
return parts + closed;
if (gathered is null)
{
var (above, below) = Functions.SingleQuotient.Of(left);
gathered = WithTheFactorsEveryTermSharesOut(above.InnerSimplified, x) / below;
}
return Integration.ComputeAsAQuestionOfItsOwn(gathered, x, integrateByParts: true) is { } integrated
? parts + integrated
: null;
}

/// <summary>
/// <paramref name="sum"/> with each term read as a number times powers of
/// <c>sin(u)</c> and <c>cos(u)</c> for one argument <c>u</c>, and written as the least
/// power of each times the sum of what is left; <see langword="null"/> where a term
/// is not such a product.
/// </summary>
/// <remarks>
/// What parts leaves is spelled the way the antiderivative came back, and the other
/// terms the way they were written: <c>(1/sin(x))^(-1/2)</c> beside
/// <c>sqrt(csc(x)) sec(x) tan(x)</c> share nothing as written, and are
/// <c>sqrt(sin(x))</c> and <c>sqrt(sin(x))/cos(x)^2</c> as powers. Reading
/// <c>(1/f)^p</c> as <c>f^(-p)</c> holds wherever <c>f</c> is positive or <c>p</c> is
/// whole; where neither, the integrand is not real.
/// <para>
/// And <paramref name="antiderivative"/> where the sum is the derivative of one such
/// product, which the rules otherwise answer in the half angle's tangent at four times
/// the size: <c>(K sin(u)^(a + 1) cos(u)^(b + 1))'</c> is
/// <c>K u' sin(u)^a cos(u)^b ((a + b + 2) cos(u)^2 - (b + 1))</c>, and
/// <c>K u' sin(u)^a cos(u)^b ((a + 1) - (a + b + 2) sin(u)^2)</c> the other way, so
/// <c>sqrt(sin(x)) (-2 - 4/cos(x)^2)</c> is <c>(-4 sin(x)^(3/2)/cos(x))'</c>.
/// </para>
/// </remarks>
private static Entity? OverTheLeastPowersOfTheSineAndTheCosine(Entity sum, Entity.Variable x, out Entity? antiderivative)
{
antiderivative = null;
Entity? argument = null;
var read = new List<(Entity Coefficient, Number.Rational Sine, Number.Rational Cosine)>();
foreach (var term in TreeAnalyzer.GatherLinearChildrenOverSumAndExpand(sum, e => e.ContainsNode(x)) ?? new List<Entity> { sum })
if (Read(term) is { } powers)
read.Add(powers);
else
return null;
if (argument is null || read.Count < 2)
return null;
var leastSine = read.Select(r => r.Sine).Aggregate((l, r) => r < l ? r : l);
var leastCosine = read.Select(r => r.Cosine).Aggregate((l, r) => r < l ? r : l);
Entity Power(Entity @base, Number.Rational power)
=> power == Number.Integer.Zero ? Number.Integer.One : power == Number.Integer.One ? @base : MathS.Pow(@base, power);
var slope = argument.Differentiate(x).InnerSimplified;
var two = Number.Integer.Create(2);
if (!slope.ContainsNode(x))
{
Entity Total(System.Func<(Entity Coefficient, Number.Rational Sine, Number.Rational Cosine), bool> which)
=> read.Where(which).Select(r => r.Coefficient).Aggregate((Entity)Number.Integer.Zero, (l, r) => l + r).InnerSimplified;
var (a, b) = (leastSine, leastCosine);
Entity? k = null;
if (read.All(r => r.Sine == a && (r.Cosine == b || r.Cosine == b + two)) && b + Number.Integer.One != Number.Integer.Zero)
{
var (squared, alone) = (Total(r => r.Cosine != b), Total(r => r.Cosine == b));
var candidate = (-alone / ((b + Number.Integer.One) * slope)).InnerSimplified;
if (TreeAnalyzer.IsZero((squared - candidate * slope * (a + b + two)).InnerSimplified))
k = candidate;
}
else if (read.All(r => r.Cosine == b && (r.Sine == a || r.Sine == a + two)) && a + Number.Integer.One != Number.Integer.Zero)
{
var (squared, alone) = (Total(r => r.Sine != a), Total(r => r.Sine == a));
var candidate = (alone / ((a + Number.Integer.One) * slope)).InnerSimplified;
if (TreeAnalyzer.IsZero((squared + candidate * slope * (a + b + two)).InnerSimplified))
k = candidate;
}
if (k is not null)
{
var (sinePower, cosinePower) = (a + Number.Integer.One, b + Number.Integer.One);
Entity product = k;
if (sinePower < Number.Integer.Zero) product /= Power(MathS.Sin(argument), -sinePower); else product *= Power(MathS.Sin(argument), sinePower);
if (cosinePower < Number.Integer.Zero) product /= Power(MathS.Cos(argument), -cosinePower); else product *= Power(MathS.Cos(argument), cosinePower);
antiderivative = product;
}
}
var left = read.Select(r => r.Coefficient * Power(MathS.Sin(argument), r.Sine - leastSine) * Power(MathS.Cos(argument), r.Cosine - leastCosine))
.Aggregate((l, r) => l + r);
Entity above = left, below = Number.Integer.One;
if (leastSine < Number.Integer.Zero) below *= Power(MathS.Sin(argument), -leastSine); else above = Power(MathS.Sin(argument), leastSine) * above;
if (leastCosine < Number.Integer.Zero) below *= Power(MathS.Cos(argument), -leastCosine); else above = Power(MathS.Cos(argument), leastCosine) * above;
return below == Number.Integer.One ? above : above / below;

(Entity Coefficient, Number.Rational Sine, Number.Rational Cosine)? Read(Entity node)
{
if (!node.ContainsNode(x))
return (node, Number.Integer.Zero, Number.Integer.Zero);
switch (node)
{
case Sinf(var u): return Base(u, Number.Integer.One, Number.Integer.Zero);
case Cosf(var u): return Base(u, Number.Integer.Zero, Number.Integer.One);
case Secantf(var u): return Base(u, Number.Integer.Zero, Number.Integer.MinusOne);
case Cosecantf(var u): return Base(u, Number.Integer.MinusOne, Number.Integer.Zero);
case Tanf(var u): return Base(u, Number.Integer.One, Number.Integer.MinusOne);
case Cotanf(var u): return Base(u, Number.Integer.MinusOne, Number.Integer.One);
case Mulf(var l, var r):
return (Read(l), Read(r)) is ({ } a, { } b) ? ((a.Coefficient * b.Coefficient).InnerSimplified, a.Sine + b.Sine, a.Cosine + b.Cosine) : null;
case Divf(var l, var r):
return (Read(l), Read(r)) is ({ } n, { } d) ? ((n.Coefficient / d.Coefficient).InnerSimplified, n.Sine - d.Sine, n.Cosine - d.Cosine) : null;
case Powf(var @base, Number.Rational power) when Read(@base) is { } inner:
// A whole power of anything read; a fractional one only of a single
// power of the sine or of the cosine with nothing beside it, so that
// no root of a product is split.
if (power is Number.Integer)
return (MathS.Pow(inner.Coefficient, power).InnerSimplified, inner.Sine * power, inner.Cosine * power);
if (inner.Coefficient == Number.Integer.One && (inner.Sine == Number.Integer.Zero || inner.Cosine == Number.Integer.Zero))
return (Number.Integer.One, inner.Sine * power, inner.Cosine * power);
return null;
default:
return null;
}
}

(Entity Coefficient, Number.Rational Sine, Number.Rational Cosine)? Base(Entity u, Number.Rational sine, Number.Rational cosine)
{
if (argument is not null && argument != u)
return null;
argument = u;
return (Number.Integer.One, sine, cosine);
}
}

/// <summary>
/// <paramref name="sum"/> as the factors with <paramref name="x"/> in them that every
/// term has, as written, times the sum of what is left; as written where there are none.
/// </summary>
private static Entity WithTheFactorsEveryTermSharesOut(Entity sum, Entity.Variable x)
{
var terms = Sumf.LinearChildren(sum).Select(term => Mulf.LinearChildren(term).ToList()).ToList();
if (terms.Count < 2)
return sum;
var shared = new List<Entity>();
foreach (var factor in terms[0])
if (factor.ContainsNode(x) && terms.Skip(1).All(other => other.Contains(factor)))
{
shared.Add(factor);
foreach (var other in terms.Skip(1))
other.Remove(factor);
}
if (shared.Count == 0)
return sum;
var firstLeft = new List<Entity>(terms[0]);
foreach (var factor in shared)
firstLeft.Remove(factor);
Entity Product(List<Entity> factors) => factors.Count == 0 ? Number.Integer.One : factors.Aggregate((l, r) => l * r);
var left = new[] { Product(firstLeft) }.Concat(terms.Skip(1).Select(Product)).Aggregate((l, r) => l + r);
return Product(shared) * left;
}

/// <summary>The largest term a sum is split over as written, before the expanding gather.</summary>
private const int LargestTermTakenAsWritten = 60;

Expand Down
Loading
Loading