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 @@ -635,6 +635,23 @@ partial fractions divide. It is taken out in front of the integral first
| `"(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 |
| `"(b*d + 2*c*d*x)^8/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute | 65,236 characters |

### A complex unit common to a polynomial's coefficients is taken out before it is expanded

**Answers where there were none.** `1/(e^(5/2 i arctan(a x)) x^3)` was left unevaluated: written algebraically it
puts a power of a polynomial whose coefficients share `i`, `(i a + i a x^4)^3`, below the bar, and the rule for
complex coefficients expanded it past the degree it reads. The common unit is taken out as a power of its own, and a
whole power of a quotient under a root of a quotient is distributed over its two sides
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/(e^(5/2*i*atan(a*x))*x^3)".ToEntity().Integrate("x")` | `integral(...)` | 464 characters |
| `"1/(e^(5/2*i*atan(a*x))*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 523 characters |
| `"1/(x^2*(i+i*x^4)^3)".ToEntity().Integrate("x")` | `integral(...)` | 331 characters |
| `"1/(e^acoth(a*x)*(c-a^2*c*x^2)^2)".ToEntity().Integrate("x")` | `integral(...)` | 486 characters |
| `"1/(e^acoth(a*x)*(c-a^2*c*x^2)^3)".ToEntity().Integrate("x")` | `integral(...)` | 893 characters |
| `"1/(e^acoth(a*x)*(c-a^2*c*x^2)^4)".ToEntity().Integrate("x")` | `integral(...)` | 1157 characters |

### An exponential of a symbolic multiple of i arctan is written as powers of the linears

**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`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2688,6 +2688,32 @@ node is Powf(var radicand, Number.Rational r) && r is not Number.Integer && radi
{
if (!TryReadAsQuotient(expr, out var numerator, out var denominator))
return null;
// A factor whose coefficients are one complex number times real ones gives that number
// up first, factor by factor and before anything is expanded: `(i + i x^4)^3` is
// `i^3 (1 + x^4)^3`, a whole power of a product. `1/(x^2 (i + i x^4)^3)`, what the
// root of `(1 + i x)/(1 - i x)` makes of `1/(e^((5/2) i arctan(x)) x^3)`, expanded to a
// degree past the bound below and was declined, where the real quotient left is
// answered at once (Rubi's 5.3.6:129 and :130).
var tookAUnitOut = false;
Entity WithTheUnitsOut(Entity side)
{
Entity product = Number.Integer.One;
foreach (var factor in Mulf.LinearChildren(side))
{
var (@base, power) = factor is Powf(var b, Number.Integer k) ? (b, (Entity)k) : (factor, Number.Integer.One);
if (ACommonComplexUnit(@base, x) is var (unit, real))
{
tookAUnitOut = true;
product *= power == Number.Integer.One ? unit * real : MathS.Pow(unit, power) * MathS.Pow(real, power);
}
else
product *= factor;
}
return product;
}
var unitsOut = WithTheUnitsOut(numerator) / WithTheUnitsOut(denominator);
if (tookAUnitOut)
return Integration.ComputeAsAQuestionOfItsOwn(unitsOut, x, integrateByParts);
Entity constant = Number.Integer.One;
Number.Complex[]? Read(Entity side, bool below)
{
Expand Down Expand Up @@ -2783,6 +2809,48 @@ Entity Polynomial(IEnumerable<Number.Real> coefficients)
// denominator's degree doubles under the conjugate.
private const int MaximumComplexRationalDegree = 12;

/// <summary>
/// A polynomial in <paramref name="x"/> whose coefficients are each a number times one
/// product of symbols common to them all, the numbers all the same complex number off
/// the real line times real ones, as that number times the symbols and the real
/// polynomial: <c>i a + i a x^4</c> is <c>i a</c> and <c>1 + x^4</c>. Null for anything else.
/// </summary>
private static (Entity Unit, Entity Real)? ACommonComplexUnit(Entity polynomial, Entity.Variable x)
{
if (polynomial is not (Sumf or Minusf) || !polynomial.ContainsNode(x)
|| !TreeAnalyzer.TryGetPolynomial(polynomial, x, out var terms) || terms.Count < 2
|| terms.Keys.Any(power => power.Sign < 0))
return null;
var values = new Dictionary<EInteger, Number.Complex>();
Entity? symbols = null;
foreach (var term in terms)
{
Number.Complex number = Number.Integer.One;
Entity rest = Number.Integer.One;
foreach (var factor in Mulf.LinearChildren(term.Value.InnerSimplified))
if (factor is Number.Complex value)
number *= value;
else
rest = rest == Number.Integer.One ? factor : rest * factor;
if (number.IsZero || symbols is { } && rest != symbols)
return null;
symbols = rest;
values[term.Key] = number;
}
var leading = values[terms.Keys.Max()!];
if (leading.ImaginaryPart.IsZero)
return null;
var unit = symbols is null || symbols == Number.Integer.One ? (Entity)leading : leading * symbols;
Entity real = Number.Integer.Zero;
foreach (var pair in values.OrderBy(pair => pair.Key))
{
if ((pair.Value / leading) is not Number.Real coefficient)
return null;
real += pair.Key.IsZero ? coefficient : coefficient * MathS.Pow(x, Number.Integer.Create(pair.Key));
}
return (unit, real);
}

/// <summary>
/// <paramref name="numerator"/> over <paramref name="denominator"/> with the greatest
/// power of <paramref name="x"/> that divides both taken out of each, the denominator's
Expand Down Expand Up @@ -12322,7 +12390,16 @@ node is Powf(var radical, Number.Rational e) && e is not Number.Integer
// Timofeev's 314 -- the distributed spelling sent the radical rules down a path
// that answered with a sign function, where the undistributed one is answered exactly.
if (!integrand.Nodes.Any(node => node is Powf(var radicand, Number.Rational fractional) && fractional is not Number.Integer && radicand.ContainsNode(u)))
{
integrand = WithWholePowersOfProductsDistributed(integrand);
// And a whole power of a quotient as the quotient of the powers, gathered into
// one again: with a symbol in the linears, `x^(-3)` comes through as
// `(-i (u^4 - 1)/(a (u^4 + 1)))^(-3)`, which the combining leaves a power of a
// quotient and the rational reader does not read (Rubi's 5.3.6:129 and :130).
if (integrand.Nodes.Any(node => node is Powf(Divf, Number.Integer)))
integrand = Functions.SingleQuotient.Combine(WithWholePowersOfProductsDistributed(integrand.Replace(node =>
node is Powf(Divf(var above, var below), Number.Integer power) ? MathS.Pow(above, power) / MathS.Pow(below, power) : node)));
}
// For an even q the principal root is not negative wherever it is real, and a root
// holding a power of u gives that power up: `1/sqrt(t + t^(3/2))` under `u = sqrt(t)`
// is `2u/sqrt(u^2 + u^3)`, a root of a cubic, and is `2/sqrt(1 + u)`. Where the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// 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 complex unit common to a polynomial's coefficients, taken out before the polynomial is expanded,
/// and a whole power of a quotient distributed under a root-of-a-quotient substitution: Rubi's 5.3.6
/// <c>1/(e^(5/2 i arctan(a x)) x^3)</c>, which was left unevaluated, and 7.4.2's
/// <c>1/(e^(arccoth(a x)) (c - a^2 c x^2)^2)</c>, which was answered wrongly.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class AComplexUnitCommonToAPolynomialIntegralTest
{
private static void DifferentiatesBack(string integrand, double[] points, params (string, double)[] pins)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());

var pinned = integral.Substitute("C", 0);
Entity original = integrand.ToEntity();
foreach (var (name, value) in pins)
{
pinned = pinned.Substitute(name, value);
original = original.Substitute(name, value);
}
var derivative = pinned.Differentiate("x");
foreach (var at in points)
{
var got = derivative.Substitute("x", at).EvalNumerical();
var want = original.Substitute("x", at).EvalNumerical();
var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart);
var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart));
Assert.True(difference / scale < 1e-8,
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
}

[Theory]
[InlineData("1/(e^(5/2*i*atan(a*x))*x^3)")]
[InlineData("1/(e^(5/2*i*atan(a*x))*x^4)")]
public void AnExponentialOfAnArctangentOverAPowerIsAnswered(string integrand)
{
DifferentiatesBack(integrand, new[] { -2.3, -0.7, 0.4, 1.9 }, ("a", 1.3));
DifferentiatesBack(integrand, new[] { -2.3, -0.7, 0.4, 1.9 }, ("a", -0.5));
}

[Fact]
public void APolynomialWithACommonUnitIsAnswered()
=> DifferentiatesBack("1/(x^2*(i+i*x^4)^3)", new[] { -2.3, -0.7, 0.4, 1.9 });

[Theory]
[InlineData("1/(e^acoth(a*x)*(c-a^2*c*x^2)^2)")]
[InlineData("1/(e^acoth(a*x)*(c-a^2*c*x^2)^3)")]
public void AnExponentialOfAnArccotangentIsAnsweredRightly(string integrand)
{
DifferentiatesBack(integrand, new[] { -2.3, -1.6, 1.4, 1.9 }, ("a", 1.3), ("c", 0.7));
DifferentiatesBack(integrand, new[] { -4.5, -2.6, 2.4, 3.9 }, ("a", -0.5), ("c", 2.5));
}
}
}
Loading