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
18 changes: 18 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,24 @@ rule that does the same for a polynomial under a square root, whose answers are
| `"1/sqrt(a*cot(x)^2)".Integrate("x")` | `-sgn(tan(x)) ln(1/sqrt(1 + abs(tan(x))^2))/sqrt(a)` | `sgn(cot(x)) ln(1 + tan(x)^2)/(2 sqrt(a))` |
| `"(b*tan(x)^2)^(5/2)".Integrate("x")` | `sgn(tan(x)) b^(5/2) ((abs(tan(x))^2)^2/2 - abs(tan(x))^2 + ln(abs(tan(x))^2 + 1))/2` | the same with `tan(x)` for `abs(tan(x))` |

### A shared symbol among a denominator sum's coefficients comes out in front of it

**Improvement, not silent.** `tan(x)/(a + a csc(x))` came back unevaluated while
`tan(x)/(1 + csc(x))` was answered: under `u = sin(x)` the denominator is `(a u + a)(1 - u^2)`,
a symbolic linear beside a rational quadratic that shares its root, which the symbolic split
declines, and under the half-angle it is a sextic with `a` in every coefficient, which nothing
factors. A written sum below the bar whose coefficients share a symbol is now that symbol times
a polynomial over the rationals — `(a u + a)` is `a (u + 1)` — before the denominator is
refactored, so the repeated factor is found and the rational machinery answers; and the lowest
power of the variable every term holds comes out the same way.

| | Was | Is |
|---|---|---|
| `"tan(x)/(a + a*csc(x))".Integrate("x")` | unevaluated | `(-1/(2 (sin(x) + 1)) + 3/4 ln(sin(x) + 1) + 1/4 ln(sin(x) - 1))/(-a)` |
| `"sec(x)^2/(a + a*csc(x))".Integrate("x")` | unevaluated | in `tan(x/2)`, over `a` |
| `"x^2/((a*x + a)*(1 - x^2))".Integrate("x")` | unevaluated | the partial fractions over `(x + 1)^2 (1 - x)`, over `a` |
| `"cot(x)^3/(a + b*csc(x))".Integrate("x")` | unevaluated | `-1/(b sin(x)) - a ln(sin(x))/b^2 + (b^2 - a^2) ln(a sin(x) + b)/(a b^2)`, once `a u^4 + b u^3` is read as `u^3 (a u + b)` |

### A condition the answer states on its own is no longer repeated beside it

**Improvement, not silent.** `Simplify` returned `(1 - ln(x))/x^2 provided not x = 0` for the
Expand Down
3 changes: 3 additions & 0 deletions Sources/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed
[Tests/UnitTests/Calculus/CombinedRadicalsTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

[Tests/UnitTests/Calculus/DenominatorContentTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

[Tests/UnitTests/Calculus/SineOverASymbolicQuadraticTest.cs]
file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,16 @@ is var (multiple, leftover)
// under a second this way, with the chain behind it for a spelling this rule does
// not answer on its own -- one irreducible factor to the first power is the table's.
// Once: the spelling this produces refactors to itself.
// A written sum with a symbol among its coefficients that they all share is that
// symbol times a polynomial over the rationals, and is written so first: `(a u + a)`
// beside `1 - u^2` shares a root with it, which the symbolic split declines, and a
// symbolic sextic is nothing the refactoring below reads, where `a` times a rational
// one is. Once: a primitive factor has no content to take out.
if (WithTheContentOutOfEachSumFactor(denominator, x) is { } primitive
&& (SolveByPartialFractions(numerator / primitive, x, integrateByParts)
?? Integration.ComputeIndefiniteIntegral(numerator / primitive, x, integrateByParts)) is { } overPrimitives)
return overPrimitives;

if (TryWriteInIrreducibleFactors(denominator, x) is { } refactored
&& (SolveByPartialFractions(numerator / refactored, x, integrateByParts)
?? Integration.ComputeIndefiniteIntegral(numerator / refactored, x, integrateByParts)) is { } overIrreducibles)
Expand Down Expand Up @@ -7535,6 +7545,79 @@ private static bool IsASumOfMonomials(Entity expr, Entity.Variable x)
return true;
}

/// <summary>
/// <paramref name="denominator"/> with the content of every written sum among its
/// factors taken out in front of it: <c>(a u + a)(1 - u^2)</c> is <c>a (u + 1)(1 - u^2)</c>,
/// and <c>-a t^6 - 2a t^5 - a t^4 + a t^2 + 2a t + a</c> is <c>a</c> times the
/// polynomial over the rationals. Null where no factor has one.
/// </summary>
/// <remarks>
/// The content is the gcd of the coefficients as polynomials in the symbols, and a
/// factor whose coefficients share one is a constant times a smaller polynomial: with
/// the constant written inside, <c>(a u + a)</c> was a symbolic linear whose root the
/// factor <c>1 - u^2</c> beside it shares, which the symbolic split declines, and the
/// sextic the half-angle makes of <c>tan(x)/(a + a csc(x))</c> was a polynomial with a
/// symbol in every coefficient, which nothing factors -- where over the rationals it is
/// <c>(t - 1)(t + 1)^3 (t^2 + 1)</c>. Rubi's <c>a + a csc(x)</c> and the like are what
/// this is for.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
private static Entity? WithTheContentOutOfEachSumFactor(Entity denominator, Entity.Variable x)
{
var changed = false;
Entity product = Number.Integer.One;
foreach (var factor in Mulf.LinearChildren(denominator))
{
// Whole powers only: `(c a + d a x)^(1/3)` is `a^(1/3) (c + d x)^(1/3)` for a
// positive `a` and not for a negative one, and a symbol is neither.
var (@base, power) = factor is Powf(var b, Number.Integer p) ? (b, p) : (factor, Number.Integer.One);
if (@base is not (Sumf or Minusf) || !@base.ContainsNode(x) || !@base.Vars.Any(v => v != x)
|| !TreeAnalyzer.TryGetPolynomial(@base, x, out var read) || read.Count < 2
|| read.Keys.Any(degree => degree.Sign < 0) || read.Values.Any(coefficient => coefficient.ContainsNode(x)))
{
product *= factor;
continue;
}
var variables = @base.Vars.OrderBy(v => v.Name, System.StringComparer.Ordinal).ToList();
if (variables.Count > MultivariatePolynomial.MaxVariables)
{
product *= factor;
continue;
}
var indices = new Dictionary<Variable, int>();
for (var i = 0; i < variables.Count; i++)
indices[variables[i]] = i;
var rest = Enumerable.Range(0, variables.Count).Where(i => variables[i] != x).ToList();
// The lowest power of x every term holds comes out too: `a u^4 + b u^3` is
// `u^3 (a u + b)`, which is what the sine substitution makes of `cot^3/(a + b csc)`,
// and neither the refactoring over the rationals nor the symbolic split reads
// a symbolic polynomial for the monomial it is a multiple of.
var lowest = read.Keys.Min()!;
if (MultivariatePolynomial.TryParse(@base, indices) is not { } polynomial
|| Functions.PolynomialGcd.ContentIn(polynomial, indices[x], rest, 0) is not { } content
|| content.IsConstant && lowest.IsZero
|| polynomial.DivideExact(content) is not { } primitive)
{
product *= factor;
continue;
}
var monomial = lowest.IsZero ? Number.Integer.One : lowest.Equals(EInteger.One) ? x : MathS.Pow(x, Number.Integer.Create(lowest));
var primitivePart = primitive.ToEntity(variables);
if (!lowest.IsZero && TreeAnalyzer.PolynomialLongDivision(primitivePart, monomial, genericCase: true, inTermsOf: x) is var (divided, remainder)
&& (remainder is Divf(var top, _) ? top : remainder).Evaled is Number.Complex { IsZero: true })
primitivePart = divided.InnerSimplified;
else if (!lowest.IsZero)
{
product *= factor;
continue;
}
Entity constantPart = content.IsConstant ? monomial : lowest.IsZero ? content.ToEntity(variables) : content.ToEntity(variables) * monomial;
product *= power == Number.Integer.One ? constantPart * primitivePart : MathS.Pow(constantPart, power) * MathS.Pow(primitivePart, power);
changed = true;
}
return changed ? product : null;
}

private static Entity? TryWriteInIrreducibleFactors(Entity denominator, Entity.Variable x)
{
// Only where the written bases, each taken once, share a factor among them or
Expand Down
64 changes: 64 additions & 0 deletions Sources/Tests/UnitTests/Calculus/DenominatorContentTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// 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 written sum below the bar whose coefficients share a symbol is that symbol times a
/// polynomial over the rationals, and is read so: <c>(a u + a)(1 - u^2)</c> is
/// <c>a (u + 1)(1 - u^2)</c>, whose repeated factor the refactoring over the rationals then
/// finds; and the lowest power of the variable every term holds comes out the same way,
/// <c>a u^4 + 2 u^3</c> being <c>u^3 (a u + 2)</c>. Rubi's <c>tan(x)/(a + a csc(x))</c> and
/// <c>cot(x)^3/(a + b csc(x))</c>, declined for the shared root under <c>u = sin(x)</c>, for
/// a symbolic sextic under the half-angle, and for a symbolic quartic that is a multiple of
/// <c>u^3</c>.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class DenominatorContentTest
{
private static readonly double[] Points = { 0.4, 1.1, 2.3, 3.6, 5.2 };

private static void DifferentiatesBack(string integrand, double constant)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Assert.DoesNotContain("NaN", integral.Stringize());

var derivative = integral.Substitute("C", 0).Substitute("a", constant).Differentiate("x");
var original = integrand.ToEntity().Substitute("a", constant);
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} at a = {constant} is {got} at x = {at}, "
+ $"where the integrand is {want}");
}
}

[Theory]
[InlineData("tan(x)/(a + a*csc(x))")]
[InlineData("sec(x)^2/(a + a*csc(x))")]
[InlineData("x^2/((a*x + a)*(1 - x^2))")]
[InlineData("1/((2*a*x + 4*a)*(x + 2)^2)")]
[InlineData("cot(x)^3/(2 + a*csc(x))")]
[InlineData("(1 - x^2)/(a*x^4 + 2*x^3)")]
public void TheContentComesOutOfTheSum(string integrand)
{
DifferentiatesBack(integrand, 2);
DifferentiatesBack(integrand, -3);
}
}
}
Loading