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 @@ -790,6 +790,23 @@ poles of `tan(x/2)`, as every half-angle answer.
| `"1/(a+b*cos(x)+c*cos(x)^2)".Integrate("x")` | unevaluated | the cosine form |
| `"csc(x)^2/(a-a*sin(x)^2)".Integrate("x")` | unevaluated | `-cot(x)/a` and two terms rational in `tan(x/2)` |

### A rational function of the sine and cosine with symbols in it is no longer simplified under the half angle

**Answers where there were none.** Under `t = tan(x/2)` the integrand was simplified before it was
integrated, and with symbols in it the simplifier's search grew past any budget:
`sec(x)^2/(a + b cos(x))^3` spent about forty seconds in it and `sec(x)^2/(a + b cos(x))^4` more than two minutes,
where integrating what it returned takes a tenth of a second. A rational function of `t` with a
symbol in it is now put over one bar with its whole powers of products written as products of
powers, and not simplified further; one without a symbol, or with a root of `t` in it, is
simplified as before
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sec(x)^2/(a + b*cos(x))^3".ToEntity().Integrate("x")` | `integral(...)` | a piecewise antiderivative in `tan(x/2)` |
| `"sec(x)^2/(a + b*cos(x))^4".ToEntity().Integrate("x")` | `integral(...)` | the same |
| `"sec(c + d*x)^2/(a + b*cos(c + d*x))^4".ToEntity().Integrate("x")` | `integral(...)` | the same in `tan((c + d x)/2)` |

### A linear over a quadratic beside the root of another quadratic is integrated

`(g + h x)/(A sqrt(B))`, `A` and `B` two different quadratics, was left unevaluated wherever a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23348,7 +23348,29 @@ static bool IsOneOfTheFour(Entity node, Entity argument)
// 1/(1 - sin(x)) rewrote to 2/((t^2 + 1)(1 + (-2)t/(t^2 + 1))) and stopped there, one
// distribution short of 2/(t^2 - 2t + 1), which is integrated at once.
// https://github.com/asc-community/AngouriMath/issues/1239
var integrand = Functions.SingleQuotient.Combine(inT * 2 / (rate * (1 + tSquared))).Simplify();
// Once combined, a quotient with a symbol in its coefficients is not simplified: the
// simplifier's search grows with the symbols, and `sec(x)^2/(a + b cos(x))^3` spent 43 s
// in it where integrating what it returned took a tenth of a second. Two things the
// simplifier did are done here, since what follows reads them. Whole powers of
// products are written as products of powers: `((1 + t^2)(1 - t^2))^6` keeps the
// `1 + t^2` the division below takes off as a factor out of its sight, and
// `tan(x)^6/(a + b sec(x))` was declined that way. And a base of a lower degree than it
// is written in is written as its polynomial: `a (1 + t^2) + a (1 - t^2)` is `2 a`, and
// read as a quadratic its leading coefficient is zero as a value only. Only for a
// rational function of t: under a root the simplifier's writing of the radicand is
// what the radical rules read, and `1/(b cos(x) + c sin(x) - sqrt(b^2 + c^2))^(3/2)`
// went from a decline in three seconds to a search past two minutes without it.
var combined = Functions.SingleQuotient.Combine(inT * 2 / (rate * (1 + tSquared)));
Entity integrand;
if (combined.Vars.Any(symbol => symbol != t)
&& !combined.Nodes.Any(node => node is Powf(var radicand, var power) && radicand.ContainsNode(t) && power.Evaled is not Number.Integer))
{
var (combinedAbove, combinedBelow) = Functions.SingleQuotient.Of(combined.InnerSimplified);
integrand = (WithDegenerateBasesLowered(WithWholePowersDistributed(combinedAbove), t)
/ WithDegenerateBasesLowered(WithWholePowersDistributed(combinedBelow), t)).InnerSimplified;
}
else
integrand = combined.Simplify();

// Collapsing the nesting attaches a condition saying the denominator it cleared is
// non-zero, and that denominator is 1 + t^2 -- so 1/(1 + cos(x)) comes out as
Expand Down Expand Up @@ -23406,6 +23428,61 @@ static bool NoRemainder(Entity rest)
: null;
}

/// <summary>
/// <paramref name="product"/> with each whole power of a product written as the product
/// of the powers, the rest as written: <c>((1 + t^2)(1 - t^2))^6</c> is
/// <c>(1 + t^2)^6 (1 - t^2)^6</c>.
/// </summary>
private static Entity WithWholePowersDistributed(Entity product)
{
Entity written = Number.Integer.One;
foreach (var factor in Mulf.LinearChildren(product))
{
if (factor is Powf(Mulf inner, Number.Integer { EInteger.Sign: > 0 } power))
foreach (var innerFactor in Mulf.LinearChildren(WithWholePowersDistributed(inner)))
written *= innerFactor is Powf(var @base, Number.Integer exponent)
? MathS.Pow(@base, Number.Integer.Create(exponent.EInteger.Multiply(power.EInteger)))
: MathS.Pow(innerFactor, power);
else
written *= factor;
}
return written;
}

/// <summary>
/// <paramref name="product"/> with each base that is a sum written as its polynomial in
/// <paramref name="t"/> where that is of a lower degree than the base is written in, and
/// as written otherwise: <c>a (1 + t^2) + a (1 - t^2)</c> is <c>2 a</c>.
/// </summary>
private static Entity WithDegenerateBasesLowered(Entity product, Entity.Variable t)
{
Entity written = Number.Integer.One;
foreach (var factor in Mulf.LinearChildren(product))
{
var (@base, power) = factor is Powf(var raised, Number.Integer exponent) ? (raised, (Entity)exponent) : (factor, Number.Integer.One);
if (@base is Sumf or Minusf && @base.ContainsNode(t) && TreeAnalyzer.TryGetPolynomial(@base, t, out var terms)
&& terms.Count > 0 && terms.All(term => term.Key.Sign >= 0 && !term.Value.ContainsNode(t)))
{
var writtenDegree = @base.Nodes.Select(node => node is Powf(var raised, Number.Integer exponent) && raised == t ? exponent.EInteger
: node == t ? EInteger.One : EInteger.Zero).Max()!;
var kept = terms.Where(term => !Functions.PartialFractions.IsZeroAsAValue(term.Value)).ToList();
var degree = kept.Select(term => term.Key).DefaultIfEmpty(EInteger.Zero).Max()!;
if (degree.CompareTo(writtenDegree) < 0)
{
Entity polynomial = Number.Integer.Zero;
foreach (var term in kept.OrderByDescending(term => term.Key))
{
var coefficient = term.Value.InnerSimplified;
polynomial += term.Key.IsZero ? coefficient : coefficient * MathS.Pow(t, Number.Integer.Create(term.Key));
}
@base = polynomial.InnerSimplified;
}
}
written *= power == Number.Integer.One ? @base : MathS.Pow(@base, power);
}
return written;
}

/// <summary>
/// <paramref name="denominator"/> with one power fewer of a factor that is
/// <c>1 + t^2</c>, the rest of the product as written; null where no factor is.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,29 @@ public void ASumTheOrdinaryRulesAnswerBetter(string integrand)
[InlineData("sec(x)^2/(a + b*cos(x))")]
[InlineData("tan(x)^4/(a + a*cos(x))")]
[InlineData("csc(x)^2/(a + a*cos(x))")]
public void ARationalFunctionWithSymbolsForCoefficients(string integrand)
public void ARationalFunctionWithSymbolsForCoefficients(string integrand) => DifferentiatesBackWithSymbols(integrand);

/// <summary>
/// A power of <c>a + b cos(x)</c> past the square beside a power of the secant. The
/// substitution's integrand in <c>t</c> is not simplified where a symbol is in it:
/// simplified, it took 43 s at the cube and more than two minutes at the fourth power,
/// where integrating it takes a tenth of a second.
/// </summary>
[Theory]
[InlineData("sec(x)^2/(a + b*cos(x))^3")]
[InlineData("sec(x)^2/(a + b*cos(x))^4")]
[InlineData("sec(c + d*x)^2/(a + b*cos(c + d*x))^4")]
[InlineData("sec(x)/(a + b*cos(x))^3")]
[InlineData("1/(cos(x)*(a + b*cos(x))^2)")]
public void APowerOfALinearInTheCosineBesideASecant(string integrand) => DifferentiatesBackWithSymbols(integrand);

private static void DifferentiatesBackWithSymbols(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
var derivative = integral.Substitute("C", 0).Differentiate("x").Substitute("a", 1.7).Substitute("b", 0.6);
var original = integrand.ToEntity().Substitute("a", 1.7).Substitute("b", 0.6);
Entity Pin(Entity e) => e.Substitute("a", 1.7).Substitute("b", 0.6).Substitute("c", 0.3).Substitute("d", 1.1);
var derivative = Pin(integral.Substitute("C", 0).Differentiate("x"));
var original = Pin(integrand.ToEntity());
var compared = 0;
foreach (var at in Points)
{
Expand Down
Loading