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
14 changes: 14 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,20 @@ two more of Rubi's 4.5.2.3. The two sums multiply to `-a c tan^2`, so in `u = a
| `"sec(e + f*x)*(a + a*sec(e + f*x))^m*(c - c*sec(e + f*x))^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | 469 characters |
| `"sec(e + f*x)*(a + a*sec(e + f*x))^m*sqrt(c - c*sec(e + f*x))".ToEntity().Integrate("x")` | `integral(...)` | 268 characters |

### A secant and a tangent in one sum below the bar are integrated in the half angle

**Answers where there were none.** `1/(a + c sec(x) + b tan(x))` was declined, and so was
`1/(a + b cot(x) + c csc(x))`, Rubi's 4.7.7. Combined over the cosine, the half-angle tangent `t` gives a denominator
with `1 + t^2` and `1 - t^2` in every term, which was read as a quartic with a symbol in every coefficient. Those
shared factors are now taken out of the sum first, so the numerator cancels them and a quadratic beside `1 + t^2` is
left ([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/(a + c*sec(x) + b*tan(x))".ToEntity().Integrate("x")` | `integral(...)` | 2,211 characters |
| `"1/(a + b*cot(x) + c*csc(x))".ToEntity().Integrate("x")` | `integral(...)` | 2,738 characters |
| `"1/(a + c*sec(2*x + 1) + b*tan(2*x + 1))".ToEntity().Integrate("x")` | `integral(...)`; past 290 s on the unreleased master | 2,890 characters |

### `a cos + i a sin` beside powers of the secant is written over the cosine

**Answers where there were none, and shorter ones.** `sec(c + d x)^5/(a cos(c + d x) + i a sin(c + d x))^2` was declined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29308,7 +29308,7 @@ static bool IsOneOfTheFour(Entity node, Entity argument)
{
var (combinedAbove, combinedBelow) = Functions.SingleQuotient.Of(combined.InnerSimplified);
integrand = (WithDegenerateBasesLowered(WithWholePowersDistributed(combinedAbove), t)
/ WithDegenerateBasesLowered(WithWholePowersDistributed(combinedBelow), t)).InnerSimplified;
/ WithDegenerateBasesLowered(WithTheSharedFactorsOutOfEachSum(WithWholePowersDistributed(combinedBelow), t), t)).InnerSimplified;
}
else
integrand = combined.Simplify();
Expand Down Expand Up @@ -29369,6 +29369,96 @@ static bool NoRemainder(Entity rest)
: null;
}

/// <summary>
/// <paramref name="product"/> with each factor that is a sum of products written as the
/// factors every term shares, with <paramref name="t"/> in them, times the sum of what
/// is left: <c>A (1 + t^2)(1 - t^2) + 2 b t (1 + t^2)(1 - t^2)</c> is
/// <c>(1 + t^2)(1 - t^2)(A + 2 b t)</c>.
/// </summary>
/// <remarks>
/// <c>1/(a + c sec(x) + b tan(x))</c> combines over <c>cos(x)</c> into a denominator
/// with both of the half angle's factors in each term, and read as a quartic with a
/// symbol in every coefficient it was declined after five seconds. Its numerator is
/// <c>2 (1 - t^2)^2</c>: with the factors out, the division below takes off the
/// <c>1 + t^2</c>, the partial fractions cancel the <c>1 - t^2</c>, and what is left
/// is a quadratic beside <c>1 + t^2</c>.
/// </remarks>
private static Entity WithTheSharedFactorsOutOfEachSum(Entity product, Entity.Variable t)
{
Entity written = Number.Integer.One;
foreach (var factor in Mulf.LinearChildren(product))
written *= factor is Sumf ? WithTheSharedFactorsOut(factor, t) : factor;
return written;
}

/// <summary>
/// <paramref name="sum"/> as the factors with <paramref name="t"/> in them that every
/// term has, each to the least power any term has it in, times the sum of what is left;
/// as written where no term shares one.
/// </summary>
private static Entity WithTheSharedFactorsOut(Entity sum, Entity.Variable t)
{
static Dictionary<Entity, EInteger> Powers(Entity term, Entity.Variable t)
{
var powers = new Dictionary<Entity, EInteger>();
foreach (var factor in Mulf.LinearChildren(term))
{
var (@base, power) = factor is Powf(var raised, Number.Integer { EInteger.Sign: > 0 } exponent)
? (raised, exponent.EInteger) : (factor, EInteger.One);
if (@base.ContainsNode(t))
powers[@base] = powers.TryGetValue(@base, out var before) ? before.Add(power) : power;
}
return powers;
}
// Inner sums first: in `(a (1 + t^2) 2 t + b (1 - t^2)(1 + t^2)) 2 t + c (1 + t^2)^2 2 t`,
// which `1/(a + b cot(x) + c csc(x))` combines into, the first term has its
// `1 + t^2` inside a sum of its own.
var terms = Sumf.LinearChildren(sum).Select(term => WithTheSharedFactorsOutOfEachSum(term, t)).ToList();
if (terms.Count < 2)
return sum;
var shared = Powers(terms[0], t);
foreach (var term in terms.Skip(1))
{
var powers = Powers(term, t);
foreach (var @base in shared.Keys.ToList())
if (powers.TryGetValue(@base, out var power))
shared[@base] = EInteger.Min(shared[@base], power);
else
shared.Remove(@base);
}
// A bare `t` shared by every term is a root at zero, which the partial fractions
// find on their own; only a sum is worth taking out.
if (!shared.Keys.Any(@base => @base is Sumf or Minusf))
return terms.Aggregate((left, right) => left + right);
Entity common = Number.Integer.One;
foreach (var pair in shared)
common *= pair.Value.Equals(EInteger.One) ? pair.Key : MathS.Pow(pair.Key, Number.Integer.Create(pair.Value));
Entity rest = Number.Integer.Zero;
foreach (var term in terms)
{
var left = new Dictionary<Entity, EInteger>(shared);
Entity kept = Number.Integer.One;
foreach (var factor in Mulf.LinearChildren(term))
{
var (@base, power) = factor is Powf(var raised, Number.Integer { EInteger.Sign: > 0 } exponent)
? (raised, exponent.EInteger) : (factor, EInteger.One);
if (left.TryGetValue(@base, out var owed) && owed.Sign > 0)
{
var taken = EInteger.Min(owed, power);
left[@base] = owed.Subtract(taken);
power = power.Subtract(taken);
if (power.IsZero)
continue;
kept *= power.Equals(EInteger.One) ? @base : MathS.Pow(@base, Number.Integer.Create(power));
}
else
kept *= factor;
}
rest += kept;
}
return common * rest;
}

/// <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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// 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>1/(a + c sec + b tan)</c> and <c>1/(a + b cot + c csc)</c> in the half angle's tangent,
/// with the factors every term of the combined denominator shares taken out. Rubi's 4.7.7.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ASecantAndATangentInOneSumIntegralTest
{
[Theory]
[InlineData("1/(a + c*sec(x) + b*tan(x))", 4000)]
[InlineData("1/(a + b*cot(x) + c*csc(x))", 4000)]
[InlineData("csc(x)/(a + b*cot(x) + c*csc(x))", 4000)]
[InlineData("1/(a + c*sec(2*x + 1) + b*tan(2*x + 1))", 4000)]
public void DifferentiatesBack(string integrand, int atMost)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Assert.True(text.Length < atMost, $"{text.Length} characters of answer for {integrand}");
Entity Pinned(Entity e) => e.Substitute("a", 2).Substitute("b", 3).Substitute("c", "5/4".ToEntity());
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { "0.35", "0.6", "1.1", "1.4" })
{
var want = original.Substitute("x", at.ToEntity()).EvalNumerical();
var got = derivative.Substitute("x", at.ToEntity()).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)),
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
}
}
}
Loading