Skip to content

Commit 089d615

Browse files
A secant and a tangent in one sum below the bar are integrated in the half angle (#1855)
* The half angle takes the factors a combined denominator's terms share out of the sum Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * Record the secant and tangent half-angle answers, and build on netstandard2.0 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent b3805f8 commit 089d615

3 files changed

Lines changed: 151 additions & 1 deletion

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
505505
| `"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 |
506506
| `"sec(e + f*x)*(a + a*sec(e + f*x))^m*sqrt(c - c*sec(e + f*x))".ToEntity().Integrate("x")` | `integral(...)` | 268 characters |
507507

508+
### A secant and a tangent in one sum below the bar are integrated in the half angle
509+
510+
**Answers where there were none.** `1/(a + c sec(x) + b tan(x))` was declined, and so was
511+
`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
512+
with `1 + t^2` and `1 - t^2` in every term, which was read as a quartic with a symbol in every coefficient. Those
513+
shared factors are now taken out of the sum first, so the numerator cancels them and a quadratic beside `1 + t^2` is
514+
left ([#718](https://github.com/asc-community/AngouriMath/issues/718)).
515+
516+
| Input | Was (2.5.0) | Now |
517+
|---|---|---|
518+
| `"1/(a + c*sec(x) + b*tan(x))".ToEntity().Integrate("x")` | `integral(...)` | 2,211 characters |
519+
| `"1/(a + b*cot(x) + c*csc(x))".ToEntity().Integrate("x")` | `integral(...)` | 2,738 characters |
520+
| `"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 |
521+
508522
### `a cos + i a sin` beside powers of the secant is written over the cosine
509523

510524
**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,

‎Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs‎

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29308,7 +29308,7 @@ static bool IsOneOfTheFour(Entity node, Entity argument)
2930829308
{
2930929309
var (combinedAbove, combinedBelow) = Functions.SingleQuotient.Of(combined.InnerSimplified);
2931029310
integrand = (WithDegenerateBasesLowered(WithWholePowersDistributed(combinedAbove), t)
29311-
/ WithDegenerateBasesLowered(WithWholePowersDistributed(combinedBelow), t)).InnerSimplified;
29311+
/ WithDegenerateBasesLowered(WithTheSharedFactorsOutOfEachSum(WithWholePowersDistributed(combinedBelow), t), t)).InnerSimplified;
2931229312
}
2931329313
else
2931429314
integrand = combined.Simplify();
@@ -29369,6 +29369,96 @@ static bool NoRemainder(Entity rest)
2936929369
: null;
2937029370
}
2937129371

29372+
/// <summary>
29373+
/// <paramref name="product"/> with each factor that is a sum of products written as the
29374+
/// factors every term shares, with <paramref name="t"/> in them, times the sum of what
29375+
/// is left: <c>A (1 + t^2)(1 - t^2) + 2 b t (1 + t^2)(1 - t^2)</c> is
29376+
/// <c>(1 + t^2)(1 - t^2)(A + 2 b t)</c>.
29377+
/// </summary>
29378+
/// <remarks>
29379+
/// <c>1/(a + c sec(x) + b tan(x))</c> combines over <c>cos(x)</c> into a denominator
29380+
/// with both of the half angle's factors in each term, and read as a quartic with a
29381+
/// symbol in every coefficient it was declined after five seconds. Its numerator is
29382+
/// <c>2 (1 - t^2)^2</c>: with the factors out, the division below takes off the
29383+
/// <c>1 + t^2</c>, the partial fractions cancel the <c>1 - t^2</c>, and what is left
29384+
/// is a quadratic beside <c>1 + t^2</c>.
29385+
/// </remarks>
29386+
private static Entity WithTheSharedFactorsOutOfEachSum(Entity product, Entity.Variable t)
29387+
{
29388+
Entity written = Number.Integer.One;
29389+
foreach (var factor in Mulf.LinearChildren(product))
29390+
written *= factor is Sumf ? WithTheSharedFactorsOut(factor, t) : factor;
29391+
return written;
29392+
}
29393+
29394+
/// <summary>
29395+
/// <paramref name="sum"/> as the factors with <paramref name="t"/> in them that every
29396+
/// term has, each to the least power any term has it in, times the sum of what is left;
29397+
/// as written where no term shares one.
29398+
/// </summary>
29399+
private static Entity WithTheSharedFactorsOut(Entity sum, Entity.Variable t)
29400+
{
29401+
static Dictionary<Entity, EInteger> Powers(Entity term, Entity.Variable t)
29402+
{
29403+
var powers = new Dictionary<Entity, EInteger>();
29404+
foreach (var factor in Mulf.LinearChildren(term))
29405+
{
29406+
var (@base, power) = factor is Powf(var raised, Number.Integer { EInteger.Sign: > 0 } exponent)
29407+
? (raised, exponent.EInteger) : (factor, EInteger.One);
29408+
if (@base.ContainsNode(t))
29409+
powers[@base] = powers.TryGetValue(@base, out var before) ? before.Add(power) : power;
29410+
}
29411+
return powers;
29412+
}
29413+
// 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`,
29414+
// which `1/(a + b cot(x) + c csc(x))` combines into, the first term has its
29415+
// `1 + t^2` inside a sum of its own.
29416+
var terms = Sumf.LinearChildren(sum).Select(term => WithTheSharedFactorsOutOfEachSum(term, t)).ToList();
29417+
if (terms.Count < 2)
29418+
return sum;
29419+
var shared = Powers(terms[0], t);
29420+
foreach (var term in terms.Skip(1))
29421+
{
29422+
var powers = Powers(term, t);
29423+
foreach (var @base in shared.Keys.ToList())
29424+
if (powers.TryGetValue(@base, out var power))
29425+
shared[@base] = EInteger.Min(shared[@base], power);
29426+
else
29427+
shared.Remove(@base);
29428+
}
29429+
// A bare `t` shared by every term is a root at zero, which the partial fractions
29430+
// find on their own; only a sum is worth taking out.
29431+
if (!shared.Keys.Any(@base => @base is Sumf or Minusf))
29432+
return terms.Aggregate((left, right) => left + right);
29433+
Entity common = Number.Integer.One;
29434+
foreach (var pair in shared)
29435+
common *= pair.Value.Equals(EInteger.One) ? pair.Key : MathS.Pow(pair.Key, Number.Integer.Create(pair.Value));
29436+
Entity rest = Number.Integer.Zero;
29437+
foreach (var term in terms)
29438+
{
29439+
var left = new Dictionary<Entity, EInteger>(shared);
29440+
Entity kept = Number.Integer.One;
29441+
foreach (var factor in Mulf.LinearChildren(term))
29442+
{
29443+
var (@base, power) = factor is Powf(var raised, Number.Integer { EInteger.Sign: > 0 } exponent)
29444+
? (raised, exponent.EInteger) : (factor, EInteger.One);
29445+
if (left.TryGetValue(@base, out var owed) && owed.Sign > 0)
29446+
{
29447+
var taken = EInteger.Min(owed, power);
29448+
left[@base] = owed.Subtract(taken);
29449+
power = power.Subtract(taken);
29450+
if (power.IsZero)
29451+
continue;
29452+
kept *= power.Equals(EInteger.One) ? @base : MathS.Pow(@base, Number.Integer.Create(power));
29453+
}
29454+
else
29455+
kept *= factor;
29456+
}
29457+
rest += kept;
29458+
}
29459+
return common * rest;
29460+
}
29461+
2937229462
/// <summary>
2937329463
/// <paramref name="product"/> with each whole power of a product written as the product
2937429464
/// of the powers, the rest as written: <c>((1 + t^2)(1 - t^2))^6</c> is
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using AngouriMath.Extensions;
10+
using Xunit;
11+
12+
namespace AngouriMath.Tests.Calculus
13+
{
14+
/// <summary>
15+
/// <c>1/(a + c sec + b tan)</c> and <c>1/(a + b cot + c csc)</c> in the half angle's tangent,
16+
/// with the factors every term of the combined denominator shares taken out. Rubi's 4.7.7.
17+
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
18+
/// </summary>
19+
[Trait("Area", "Calculus")]
20+
public sealed class ASecantAndATangentInOneSumIntegralTest
21+
{
22+
[Theory]
23+
[InlineData("1/(a + c*sec(x) + b*tan(x))", 4000)]
24+
[InlineData("1/(a + b*cot(x) + c*csc(x))", 4000)]
25+
[InlineData("csc(x)/(a + b*cot(x) + c*csc(x))", 4000)]
26+
[InlineData("1/(a + c*sec(2*x + 1) + b*tan(2*x + 1))", 4000)]
27+
public void DifferentiatesBack(string integrand, int atMost)
28+
{
29+
var integral = integrand.ToEntity().Integrate("x");
30+
var text = integral.Stringize();
31+
Assert.DoesNotContain("integral(", text);
32+
Assert.True(text.Length < atMost, $"{text.Length} characters of answer for {integrand}");
33+
Entity Pinned(Entity e) => e.Substitute("a", 2).Substitute("b", 3).Substitute("c", "5/4".ToEntity());
34+
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
35+
var original = Pinned(integrand.ToEntity());
36+
foreach (var at in new[] { "0.35", "0.6", "1.1", "1.4" })
37+
{
38+
var want = original.Substitute("x", at.ToEntity()).EvalNumerical();
39+
var got = derivative.Substitute("x", at.ToEntity()).EvalNumerical();
40+
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
41+
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart)),
42+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
43+
}
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)