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 @@ -854,6 +854,23 @@ left as it was, since the sum is real on both sides of 0 and `x^(j p)` is not. R
| `"1/(x*sqrt(b*x^(2/3) + a*x))".ToEntity().Integrate("x")` | `integral(...)` | `K` times an antiderivative in `sqrt(b + a x^(1/3))` |
| `"x/sqrt(1 + 1/(c*x)^2)".ToEntity().Integrate("x")` | `integral(...)` | `sgn(x) (x sqrt(x^2 + 1/c^2)/2 - ln(2 x + 2 sqrt(x^2 + 1/c^2))/(2 c^2))`, for any `c` but 0 |

### A quadratic sharing a root off the real line with a linear beside it is written over that root

**Answers where there were none.** `1/((a + i a tan(x)) (c + d tan(x)))`, Rubi's 4.3.2.1, is under the
tangent substitution a rational function over `(1 + i u)(c + d u)(1 + u^2)`, and `1 + u^2` is
`(1 + i u)(1 - i u)`: the written factors share a linear. The splits read written factors as coprime,
and the divisors that take shared factors apart are taken over the rationals, which the imaginary unit
is not; it was declined, and with a square of `c + d tan(x)` it ran past a minute. With a symbol in the
denominator, a quadratic that has a root off the real line in common with a linear beside it is now
written as its leading coefficient times the linears of its two roots, and the shared linear taken as
one power ([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/((a + i*a*tan(x))*(c + d*tan(x)))".ToEntity().Integrate("x")` | `integral(...)` | a quotient by `tan(x) - i` and logarithms |
| `"1/((a + i*a*tan(x))^2*(c + d*tan(x)))".ToEntity().Integrate("x")` | `integral(...)` | the same |
| `"1/((a + i*a*tan(x))*(c + d*tan(x))^2)".ToEntity().Integrate("x")` | `integral(...)` | the same |

### A rational function of a sine over a symbolic quadratic in it is integrated over the two roots

**Improvement, not silent.** `sin(x)/(a + b sin(x) + c sin(x)^2)` and everything rational in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,17 @@ is var (multiple, leftover)
?? Integration.ComputeIndefiniteIntegral(above / below, x, integrateByParts)) is { } overWhatTheyShare)
return overWhatTheyShare;

// And a linear with a root off the real line that a quadratic beside it shares, which
// the divisors over the rationals above cannot see: `1 + i u` beside `1 + u^2`, which is
// `(1 + i u)(1 - i u)`, what the tangent substitution makes of Rubi's 4.3.2.1
// `1/((a + i a tan(x)) (c + d tan(x)))`. The quadratic is written over the linears of
// its roots and the shared one taken as one power, coprime and squarefree for the
// splits below.
if (OverAComplexRootSharedWithAQuadratic(denominator, x) is { } overTheSharedRoot
&& (SolveByPartialFractions(numerator / overTheSharedRoot, x, integrateByParts)
?? Integration.ComputeIndefiniteIntegral(numerator / overTheSharedRoot, x, integrateByParts)) is { } overTheComplexRoot)
return overTheComplexRoot;

// Written linear factors with symbols in their coefficients, two or more, one of
// them to a power: decomposed over the written factors, the coefficients read
// off derivatives at the roots and each a line. First, before the respellings
Expand Down Expand Up @@ -12779,6 +12790,72 @@ private static bool IsASumOfMonomials(Entity expr, Entity.Variable x)
return changed ? product : null;
}

/// <summary>
/// <paramref name="denominator"/> with every quadratic factor that has a root off the real line
/// in common with a linear factor beside it written as its leading coefficient times the
/// linears of its two roots, each linear with such a root written as its slope times
/// <c>x - r</c>, and the powers of one linear gathered into one; <see langword="null"/>
/// where no quadratic shares such a root. <c>(1 + i x)(1 + x^2)</c> is
/// <c>i (x - i)^2 (x + i)</c>.
/// </summary>
private static Entity? OverAComplexRootSharedWithAQuadratic(Entity denominator, Entity.Variable x)
{
// With numbers only, the split over the rationals and the imaginary unit answers it as
// it is written.
if (!denominator.Vars.Any(symbol => symbol != x))
return null;
var written = new List<(Entity Base, EInteger Power)>();
Entity constant = Number.Integer.One;
foreach (var factor in Mulf.LinearChildren(denominator))
{
if (!factor.ContainsNode(x))
{
constant = constant * factor;
continue;
}
written.Add(factor is Powf(var @base, Number.Integer n) && n.EInteger.Sign > 0 ? (@base, n.EInteger) : (factor, EInteger.One));
}
// The roots of the linears that are not real, each with the linear's slope.
var roots = new Dictionary<Entity, (Number.Complex Root, Entity Slope)>();
foreach (var (@base, _) in written)
if (TreeAnalyzer.TryGetPolyLinear(@base, x, out var slope, out var offset) && !TreeAnalyzer.IsZero(slope)
&& Functions.PartialFractions.Bare((-offset / slope).Simplify()).Evaled is Number.Complex root && root is not Number.Real)
roots[@base] = (root, slope);
if (roots.Count == 0)
return null;
var gathered = new Dictionary<Entity, EInteger>();
void Add(Entity @base, EInteger power) => gathered[@base] = gathered.TryGetValue(@base, out var before) ? before.Add(power) : power;
var shared = false;
foreach (var (@base, power) in written)
{
if (roots.TryGetValue(@base, out var linear))
{
constant = constant * MathS.Pow(linear.Slope, Number.Integer.Create(power));
Add((x - linear.Root).InnerSimplified, power);
continue;
}
if (TreeAnalyzer.TryGetPolyQuadratic(@base, x, out var a, out var b, out var c) && !TreeAnalyzer.IsZero(a)
&& a.Evaled is Number.Complex && b.Evaled is Number.Complex && c.Evaled is Number.Complex
&& roots.Values.FirstOrDefault(candidate => (a * candidate.Root * candidate.Root + b * candidate.Root + c).Evaled is Number.Complex { IsZero: true }) is var (root, _)
&& root is not null)
{
shared = true;
var other = (-b / a - root).Evaled;
constant = constant * MathS.Pow(a, Number.Integer.Create(power));
Add((x - root).InnerSimplified, power);
Add((x - other).InnerSimplified, power);
continue;
}
Add(@base, power);
}
if (!shared)
return null;
Entity product = constant;
foreach (var pair in gathered)
product = product * (pair.Value.Equals(EInteger.One) ? pair.Key : MathS.Pow(pair.Key, Number.Integer.Create(pair.Value)));
return product;
}

/// <summary>
/// <paramref name="numerator"/> over <paramref name="denominator"/>, the denominator's
/// written factors taken apart over their greatest common divisors -- with one another,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// 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>
/// A linear with a root off the real line that a quadratic beside it shares, with symbols in
/// the rest: <c>1/((1 + i u)(c + d u)(1 + u^2))</c>, where <c>1 + u^2</c> is
/// <c>(1 + i u)(1 - i u)</c>, the tangent substitution's form of Rubi's 4.3.2.1
/// <c>1/((a + i a tan(x)) (c + d tan(x)))</c>, declined or past a minute. The integrands are
/// complex for a real <c>x</c>, and compared as complex numbers.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ComplexRootSharedWithAQuadraticIntegralTest
{
[Theory]
[InlineData("1/((1 + i*x)*(c + d*x)*(1 + x^2))")]
[InlineData("1/((a + i*a*tan(x))*(c + d*tan(x)))")]
[InlineData("1/((a + i*a*tan(x))^2*(c + d*tan(x)))")]
[InlineData("1/((a + i*a*tan(x))*(c + d*tan(x))^2)")]
public void OverTheSharedRoot(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Assert.DoesNotContain("NaN", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("c", 1.1).Substitute("d", 0.6);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -1.2, -0.7, 0.3, 0.8, 1.3, 2.9 })
{
var want = original.Substitute("x", at).EvalNumerical();
if (want.IsNaN)
continue;
compared++;
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}");
}
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
}
}
}
Loading