Skip to content

Commit 97ef01e

Browse files
Take a complex unit common to a polynomial out before expanding it (#1937)
* Take a complex unit common to a polynomial's coefficients out before expanding A factor whose coefficients are one complex number times real ones, with any product of symbols common to them all, gives that number up before the complex-coefficient rule expands it: (i a + i a x^4)^3 is (i a)^3 (1 + x^4)^3. 1/(x^2 (i + i x^4)^3) expanded past the degree bound and was declined. And the root of a quotient of linears distributes a whole power of a quotient in the integrand it builds, which with a symbol in the linears came through as (-i (u^4 - 1)/(a (u^4 + 1)))^(-3) and was not read. 1/(e^((5/2) i arctan(a x)) x^3) and x^4 were left unevaluated (Rubi's 5.3.6:129 and :130). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * Test a complex unit common to a polynomial, and an exponential of an arccotangent Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * Record the complex unit taken out of a polynomial in BREAKING-CHANGES 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 0f84066 commit 97ef01e

3 files changed

Lines changed: 164 additions & 0 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,23 @@ partial fractions divide. It is taken out in front of the integral first
635635
| `"(b*d + 2*c*d*x)^9/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute | 76,249 characters, the length the content-free one already had |
636636
| `"(b*d + 2*c*d*x)^8/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute | 65,236 characters |
637637

638+
### A complex unit common to a polynomial's coefficients is taken out before it is expanded
639+
640+
**Answers where there were none.** `1/(e^(5/2 i arctan(a x)) x^3)` was left unevaluated: written algebraically it
641+
puts a power of a polynomial whose coefficients share `i`, `(i a + i a x^4)^3`, below the bar, and the rule for
642+
complex coefficients expanded it past the degree it reads. The common unit is taken out as a power of its own, and a
643+
whole power of a quotient under a root of a quotient is distributed over its two sides
644+
([#718](https://github.com/asc-community/AngouriMath/issues/718)).
645+
646+
| Input | Was (2.5.0) | Now |
647+
|---|---|---|
648+
| `"1/(e^(5/2*i*atan(a*x))*x^3)".ToEntity().Integrate("x")` | `integral(...)` | 464 characters |
649+
| `"1/(e^(5/2*i*atan(a*x))*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 523 characters |
650+
| `"1/(x^2*(i+i*x^4)^3)".ToEntity().Integrate("x")` | `integral(...)` | 331 characters |
651+
| `"1/(e^acoth(a*x)*(c-a^2*c*x^2)^2)".ToEntity().Integrate("x")` | `integral(...)` | 486 characters |
652+
| `"1/(e^acoth(a*x)*(c-a^2*c*x^2)^3)".ToEntity().Integrate("x")` | `integral(...)` | 893 characters |
653+
| `"1/(e^acoth(a*x)*(c-a^2*c*x^2)^4)".ToEntity().Integrate("x")` | `integral(...)` | 1157 characters |
654+
638655
### An exponential of a symbolic multiple of i arctan is written as powers of the linears
639656

640657
**Answers where there were none.** `(c + a^2 c x^2)^p/e^(2 i p arctan(a x))` was left unevaluated. For a real `L`,

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

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2688,6 +2688,32 @@ node is Powf(var radicand, Number.Rational r) && r is not Number.Integer && radi
26882688
{
26892689
if (!TryReadAsQuotient(expr, out var numerator, out var denominator))
26902690
return null;
2691+
// A factor whose coefficients are one complex number times real ones gives that number
2692+
// up first, factor by factor and before anything is expanded: `(i + i x^4)^3` is
2693+
// `i^3 (1 + x^4)^3`, a whole power of a product. `1/(x^2 (i + i x^4)^3)`, what the
2694+
// root of `(1 + i x)/(1 - i x)` makes of `1/(e^((5/2) i arctan(x)) x^3)`, expanded to a
2695+
// degree past the bound below and was declined, where the real quotient left is
2696+
// answered at once (Rubi's 5.3.6:129 and :130).
2697+
var tookAUnitOut = false;
2698+
Entity WithTheUnitsOut(Entity side)
2699+
{
2700+
Entity product = Number.Integer.One;
2701+
foreach (var factor in Mulf.LinearChildren(side))
2702+
{
2703+
var (@base, power) = factor is Powf(var b, Number.Integer k) ? (b, (Entity)k) : (factor, Number.Integer.One);
2704+
if (ACommonComplexUnit(@base, x) is var (unit, real))
2705+
{
2706+
tookAUnitOut = true;
2707+
product *= power == Number.Integer.One ? unit * real : MathS.Pow(unit, power) * MathS.Pow(real, power);
2708+
}
2709+
else
2710+
product *= factor;
2711+
}
2712+
return product;
2713+
}
2714+
var unitsOut = WithTheUnitsOut(numerator) / WithTheUnitsOut(denominator);
2715+
if (tookAUnitOut)
2716+
return Integration.ComputeAsAQuestionOfItsOwn(unitsOut, x, integrateByParts);
26912717
Entity constant = Number.Integer.One;
26922718
Number.Complex[]? Read(Entity side, bool below)
26932719
{
@@ -2783,6 +2809,48 @@ Entity Polynomial(IEnumerable<Number.Real> coefficients)
27832809
// denominator's degree doubles under the conjugate.
27842810
private const int MaximumComplexRationalDegree = 12;
27852811

2812+
/// <summary>
2813+
/// A polynomial in <paramref name="x"/> whose coefficients are each a number times one
2814+
/// product of symbols common to them all, the numbers all the same complex number off
2815+
/// the real line times real ones, as that number times the symbols and the real
2816+
/// polynomial: <c>i a + i a x^4</c> is <c>i a</c> and <c>1 + x^4</c>. Null for anything else.
2817+
/// </summary>
2818+
private static (Entity Unit, Entity Real)? ACommonComplexUnit(Entity polynomial, Entity.Variable x)
2819+
{
2820+
if (polynomial is not (Sumf or Minusf) || !polynomial.ContainsNode(x)
2821+
|| !TreeAnalyzer.TryGetPolynomial(polynomial, x, out var terms) || terms.Count < 2
2822+
|| terms.Keys.Any(power => power.Sign < 0))
2823+
return null;
2824+
var values = new Dictionary<EInteger, Number.Complex>();
2825+
Entity? symbols = null;
2826+
foreach (var term in terms)
2827+
{
2828+
Number.Complex number = Number.Integer.One;
2829+
Entity rest = Number.Integer.One;
2830+
foreach (var factor in Mulf.LinearChildren(term.Value.InnerSimplified))
2831+
if (factor is Number.Complex value)
2832+
number *= value;
2833+
else
2834+
rest = rest == Number.Integer.One ? factor : rest * factor;
2835+
if (number.IsZero || symbols is { } && rest != symbols)
2836+
return null;
2837+
symbols = rest;
2838+
values[term.Key] = number;
2839+
}
2840+
var leading = values[terms.Keys.Max()!];
2841+
if (leading.ImaginaryPart.IsZero)
2842+
return null;
2843+
var unit = symbols is null || symbols == Number.Integer.One ? (Entity)leading : leading * symbols;
2844+
Entity real = Number.Integer.Zero;
2845+
foreach (var pair in values.OrderBy(pair => pair.Key))
2846+
{
2847+
if ((pair.Value / leading) is not Number.Real coefficient)
2848+
return null;
2849+
real += pair.Key.IsZero ? coefficient : coefficient * MathS.Pow(x, Number.Integer.Create(pair.Key));
2850+
}
2851+
return (unit, real);
2852+
}
2853+
27862854
/// <summary>
27872855
/// <paramref name="numerator"/> over <paramref name="denominator"/> with the greatest
27882856
/// power of <paramref name="x"/> that divides both taken out of each, the denominator's
@@ -12322,7 +12390,16 @@ node is Powf(var radical, Number.Rational e) && e is not Number.Integer
1232212390
// Timofeev's 314 -- the distributed spelling sent the radical rules down a path
1232312391
// that answered with a sign function, where the undistributed one is answered exactly.
1232412392
if (!integrand.Nodes.Any(node => node is Powf(var radicand, Number.Rational fractional) && fractional is not Number.Integer && radicand.ContainsNode(u)))
12393+
{
1232512394
integrand = WithWholePowersOfProductsDistributed(integrand);
12395+
// And a whole power of a quotient as the quotient of the powers, gathered into
12396+
// one again: with a symbol in the linears, `x^(-3)` comes through as
12397+
// `(-i (u^4 - 1)/(a (u^4 + 1)))^(-3)`, which the combining leaves a power of a
12398+
// quotient and the rational reader does not read (Rubi's 5.3.6:129 and :130).
12399+
if (integrand.Nodes.Any(node => node is Powf(Divf, Number.Integer)))
12400+
integrand = Functions.SingleQuotient.Combine(WithWholePowersOfProductsDistributed(integrand.Replace(node =>
12401+
node is Powf(Divf(var above, var below), Number.Integer power) ? MathS.Pow(above, power) / MathS.Pow(below, power) : node)));
12402+
}
1232612403
// For an even q the principal root is not negative wherever it is real, and a root
1232712404
// holding a power of u gives that power up: `1/sqrt(t + t^(3/2))` under `u = sqrt(t)`
1232812405
// is `2u/sqrt(u^2 + u^3)`, a root of a cubic, and is `2/sqrt(1 + u)`. Where the
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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+
/// A complex unit common to a polynomial's coefficients, taken out before the polynomial is expanded,
16+
/// and a whole power of a quotient distributed under a root-of-a-quotient substitution: Rubi's 5.3.6
17+
/// <c>1/(e^(5/2 i arctan(a x)) x^3)</c>, which was left unevaluated, and 7.4.2's
18+
/// <c>1/(e^(arccoth(a x)) (c - a^2 c x^2)^2)</c>, which was answered wrongly.
19+
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
20+
/// </summary>
21+
[Trait("Area", "Calculus")]
22+
public sealed class AComplexUnitCommonToAPolynomialIntegralTest
23+
{
24+
private static void DifferentiatesBack(string integrand, double[] points, params (string, double)[] pins)
25+
{
26+
var integral = integrand.ToEntity().Integrate("x");
27+
Assert.DoesNotContain("integral(", integral.Stringize());
28+
29+
var pinned = integral.Substitute("C", 0);
30+
Entity original = integrand.ToEntity();
31+
foreach (var (name, value) in pins)
32+
{
33+
pinned = pinned.Substitute(name, value);
34+
original = original.Substitute(name, value);
35+
}
36+
var derivative = pinned.Differentiate("x");
37+
foreach (var at in points)
38+
{
39+
var got = derivative.Substitute("x", at).EvalNumerical();
40+
var want = original.Substitute("x", at).EvalNumerical();
41+
var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart);
42+
var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart));
43+
Assert.True(difference / scale < 1e-8,
44+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
45+
}
46+
}
47+
48+
[Theory]
49+
[InlineData("1/(e^(5/2*i*atan(a*x))*x^3)")]
50+
[InlineData("1/(e^(5/2*i*atan(a*x))*x^4)")]
51+
public void AnExponentialOfAnArctangentOverAPowerIsAnswered(string integrand)
52+
{
53+
DifferentiatesBack(integrand, new[] { -2.3, -0.7, 0.4, 1.9 }, ("a", 1.3));
54+
DifferentiatesBack(integrand, new[] { -2.3, -0.7, 0.4, 1.9 }, ("a", -0.5));
55+
}
56+
57+
[Fact]
58+
public void APolynomialWithACommonUnitIsAnswered()
59+
=> DifferentiatesBack("1/(x^2*(i+i*x^4)^3)", new[] { -2.3, -0.7, 0.4, 1.9 });
60+
61+
[Theory]
62+
[InlineData("1/(e^acoth(a*x)*(c-a^2*c*x^2)^2)")]
63+
[InlineData("1/(e^acoth(a*x)*(c-a^2*c*x^2)^3)")]
64+
public void AnExponentialOfAnArccotangentIsAnsweredRightly(string integrand)
65+
{
66+
DifferentiatesBack(integrand, new[] { -2.3, -1.6, 1.4, 1.9 }, ("a", 1.3), ("c", 0.7));
67+
DifferentiatesBack(integrand, new[] { -4.5, -2.6, 2.4, 3.9 }, ("a", -0.5), ("c", 2.5));
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)