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 @@ -607,6 +607,23 @@ left ([#718](https://github.com/asc-community/AngouriMath/issues/718)).
| `"1/(a + b*cot(x) + c*csc(x))".ToEntity().Integrate("x")` | `integral(...)` | 2,738 characters |
| `"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 |

### A root of a product beside a root of one of its factors is split

**Answers where there were none.** `(a + b x^2)^(5/2)/sqrt(a^2 - b^2 x^4)` was declined: nothing read a radicand as
another radicand times a cofactor. `(P Q)^r` is `K P^r Q^r` with `K = (P Q)^r/(P^r Q^r)`, constant wherever it is
defined, and the powers of `P` then gather into `K (a + b x^2)^2/sqrt(a - b x^2)`
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

The answers are as large as the split integrands' answers, which are piecewise in the signs of `a` and `b`.

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(a + b*x^2)^(5/2)/sqrt(a^2 - b^2*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 2,937 characters |
| `"(a + b*x^2)^(1/2)/sqrt(a^2 - b^2*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 562 characters |
| `"1/((a + b*x^2)^(3/2)*sqrt(a^2 - b^2*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 4,191 characters |
| `"(a - b*x^2)^(3/2)/sqrt(a^2 - b^2*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 6,543 characters |
| `"1/((a - b*x^2)^(1/2)*sqrt(a^2 - b^2*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 877 characters |

### A secant or a cosecant beside a power of a sum of them is read as a power of the cosine or the sine

**Answers where there were none.** `sec(x) sqrt(sec(x) + tan(x))` was declined, with the rest of Rubi's 4.7.7
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19120,6 +19120,76 @@ Entity TakeOut(Entity node)
/// <summary>Set while <see cref="SolveByTakingTheWholePowersOutOfAPowerOfAProduct"/> asks its question.</summary>
[System.ThreadStatic] private static bool takingTheWholePowersOut;

/// <summary>
/// A root of a product beside a root of one of its factors, the product split:
/// <c>(P Q)^r</c> is <c>K P^r Q^r</c>, where <c>K = (P Q)^r/(P^r Q^r)</c> is constant wherever
/// it is defined, for the same reason as in
/// <see cref="SolveByTakingTheWholePowersOutOfAPowerOfAProduct"/>. The powers of <c>P</c>
/// then gather: <c>(a + b x^2)^(5/2)/sqrt(a^2 - b^2 x^4)</c> is
/// <c>K (a + b x^2)^2/sqrt(a - b x^2)</c>.
/// </summary>
/// <remarks>
/// Rubi's 1.2.2.3:282-293 were declined: nothing reads a radicand as a product of another
/// radicand and a cofactor. Only factors of the integrand are read, and at the question
/// asked only. https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveBySplittingARootOfAProductBesideARootOfAFactor(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!Integration.AnsweringTheQuestionAsked || splittingARootOfAProduct)
return null;
var roots = new List<(Entity Node, Entity Base, Number.Rational Exponent)>();
Gather(expr);
if (roots.Count < 2)
return null;
foreach (var product in roots)
foreach (var factor in roots)
{
if (ReferenceEquals(product.Node, factor.Node) || product.Base == factor.Base
|| ExactlyDivided(product.Base, factor.Base, x) is not { } cofactor || !cofactor.ContainsNode(x))
continue;
var multiple = Variable.CreateUnique(expr, "k_split");
var written = MathS.Pow(factor.Base, product.Exponent) * MathS.Pow(cofactor, product.Exponent);
var constant = product.Node / written;
var rewritten = expr.Replace(node => ReferenceEquals(node, product.Node) ? multiple * written : node).InnerSimplified;
Entity? answer;
splittingARootOfAProduct = true;
try
{
answer = Integration.ComputeAsTheSameQuestion(rewritten, x, integrateByParts);
}
finally
{
splittingARootOfAProduct = false;
}
if (answer is null)
return null;
answer = answer.Substitute(multiple, constant);
return answer.ContainsNode(multiple) ? null : answer;
}
return null;

void Gather(Entity node)
{
switch (node)
{
case Mulf(var left, var right):
Gather(left);
Gather(right);
break;
case Divf(var above, var below):
Gather(above);
Gather(below);
break;
case Powf(var @base, Number.Rational exponent) when exponent is not Number.Integer && @base.ContainsNode(x):
roots.Add((node, @base, exponent));
break;
}
}
}

/// <summary>Set while <see cref="SolveBySplittingARootOfAProductBesideARootOfAFactor"/> asks its question.</summary>
[System.ThreadStatic] private static bool splittingARootOfAProduct;

/// <summary>
/// A rational function of <c>x</c> and one square root of a quadratic in <c>x</c>,
/// rationalised by an Euler substitution and handed to the rational integrator.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// And a power of a product with a factor under a whole power, that power taken out in
// front of a constant: `(c sin(a + b x)^3)^(1/3)` is `K sin(a + b x)`.
if ((answer = IndefiniteIntegralSolver.SolveByTakingTheWholePowersOutOfAPowerOfAProduct(expr, x, integrateByParts)) is { }) return answer;
// And a root of a product beside a root of one of its factors, the product split:
// `(a + b x^2)^(5/2)/sqrt(a^2 - b^2 x^4)` is `K (a + b x^2)^2/sqrt(a - b x^2)`.
if ((answer = IndefiniteIntegralSolver.SolveBySplittingARootOfAProductBesideARootOfAFactor(expr, x, integrateByParts)) is { }) return answer;
// Product-to-sum among the rewrites rather than before them, because a product of
// trigonometric functions of *equal* arguments is a power and wants a different tool;
// this only fires where the arguments differ, which is exactly what every substitution
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//
// 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 root of a product beside a root of one of its factors, the product split in front of a
/// constant: <c>(a + b x^2)^(5/2)/sqrt(a^2 - b^2 x^4)</c> is <c>K (a + b x^2)^2/sqrt(a - b x^2)</c>.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
/// <remarks>
/// Rubi's 1.2.2.3:282-293 were declined. Each answer is differentiated back with <c>b</c> of
/// either sign, so that each factor of the radicand is negative somewhere it is sampled.
/// </remarks>
[Trait("Area", "Calculus")]
public sealed class ARootOfAProductSplitBesideARootOfAFactorIntegralTest
{
private static void DifferentiatesBack(string integrand, double[] points, params (string, double)[] pins)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Assert.DoesNotContain("NaN", integral.Stringize());

var derivative = integral.Substitute("C", 0).Differentiate("x");
Entity original = integrand.ToEntity();
foreach (var (name, value) in pins)
{
derivative = derivative.Substitute(name, value);
original = original.Substitute(name, value);
}

var compared = 0;
foreach (var at in points)
{
var got = derivative.Substitute("x", at).EvalNumerical();
var want = original.Substitute("x", at).EvalNumerical();
if (got.IsNaN || want.IsNaN)
continue;
compared++;
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}");
}
Assert.True(compared >= 3,
$"only {compared} of {points.Length} points were comparable for {integrand}");
}

[Theory]
[InlineData("(a+b*x^2)^(5/2)/sqrt(a^2-b^2*x^4)")]
[InlineData("(a+b*x^2)^(1/2)/sqrt(a^2-b^2*x^4)")]
[InlineData("1/((a+b*x^2)^(3/2)*sqrt(a^2-b^2*x^4))")]
[InlineData("(a-b*x^2)^(3/2)/sqrt(a^2-b^2*x^4)")]
[InlineData("1/((a-b*x^2)^(5/2)*sqrt(a^2-b^2*x^4))")]
public void ARootOfADifferenceOfSquaresBesideARootOfAFactor(string integrand)
{
var points = new[] { -1.3, -0.4, 0.3, 0.7, 1.6 };
DifferentiatesBack(integrand, points, ("a", 2), ("b", 3));
DifferentiatesBack(integrand, points, ("a", 2), ("b", -3));
}
}
}
Loading