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 @@ -335,6 +335,24 @@ and the complex arcsine's real part moved two units away. A caller comparing a h
result to a pinned string will see the last digits differ; a caller comparing to a tolerance
will not. [#1338](https://github.com/asc-community/AngouriMath/issues/1338).

### Whole powers of a root are written as powers of its radicand, and hold on both sides of 0

**Answers where there were none.** Rubi's 1.1.3.2 `1/(x^(7/2) sqrt(a + b x^5))` and three like it, and 1.3.2's
`sqrt(a x^23)/sqrt(1 + x^5)` and `sqrt(a x^13)/sqrt(1 + x^5)`, and 1.1.1.2's `1/(sqrt(x) sqrt(2 x - 3))`, were answered
`provided x >= 0`. The integrand is real
beyond `-(a/b)^(1/5)` as a product of two imaginary roots, and the answer holds there. But it spelled `x^5` as
`((x^(1/2))^5)^2`, whose value at a negative x has a zero imaginary part, and the measurement of where the answer holds
could not evaluate the outer root on its cut. Under `u = sqrt(x)` a whole power of `u` that is a multiple of the
root's order is now a power of the radicand, which it is on every branch, and the condition is gone
([#233](https://github.com/asc-community/AngouriMath/issues/233)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/(x^(7/2)*sqrt(a+b*x^5))".ToEntity().Integrate("x")` | `integral(...)` | 60 characters, where before this change master gave 92 for a positive x only |
| `"1/(x^(7/2)*sqrt(1+x^5))".ToEntity().Integrate("x")` | `integral(...)` | 50 characters |
| `"sqrt(a*x^23)/sqrt(1+x^5)".ToEntity().Integrate("x")` | `integral(...)` | 426 characters |
| `"1/(sqrt(x)*sqrt(-3+2*x))".ToEntity().Integrate("x")` | `integral(...)` | 141 characters, where before this change master gave an answer for a positive x only |

### A function of an inverse sine of `sqrt(1 + b x^2)` beside its derivative keeps the sign of x

**A wrong answer, and answers where there were none.** `1/(asin(sqrt(1 + b x^2)) sqrt(1 + b x^2))` was
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15817,7 +15817,12 @@ node is Powf(var radical, Number.Rational e) && e is not Number.Integer

if (Integration.ComputeIndefiniteIntegral(integrand, u, integrateByParts) is not { } result)
return null;
var back = result.Substitute(u, MathS.Pow(radicalBase, Number.Rational.Create(1, q)));
// A whole power of u that is a multiple of q is a whole power of the radicand on every
// branch, `(B^(1/q))^(k q) = B^k`, and is written so: as `((x^(1/2))^5)^2`, the `x^5` in Rubi's
// `1/(x^(7/2) sqrt(a + b x^5))` was real at a negative x with a zero imaginary part, the outer
// root on its branch cut there, and the measurement below could not read the answer where
// the integrand is real, and kept `provided x >= 0` on an answer that holds on both sides.
var back = WithWholePowersOfARootAsPowersOfItsRadicand(result.Substitute(u, MathS.Pow(radicalBase, Number.Rational.Create(1, q))), radicalBase);
// Beside a second root the integrand can be real where this radicand is negative, and
// the condition was the whole of its domain there: the remainder parts leaves from
// Timofeev's `arcsec(x) sqrt(x^2 - 1)/x^4`, in `w = x^2`, is
Expand Down Expand Up @@ -48128,6 +48133,7 @@ private static bool HoldsAnEvenRootBesides(Entity expr, Entity radicand, Entity.
/// </remarks>
private static Entity ConditionedOnTheRadicand(Entity answer, Entity expr, Entity radicand, Entity.Variable x)
{
answer = WithWholePowersOfARootAsPowersOfItsRadicand(answer, radicand);
var condition = radicand >= Number.Integer.Zero;
if (!HoldsAnEvenRootBesides(expr, radicand, x))
return answer.Provided(condition);
Expand All @@ -48143,6 +48149,29 @@ when TheOneOtherRadicandOfAHalfOddPower(expr, radicand, x) is { } other:
}
}

/// <summary>
/// <paramref name="answer"/> with every whole power of a root of <paramref name="radicand"/> that
/// is a multiple of the root's order written as a power of the radicand, which it is on every
/// branch: <c>(B^(1/q))^(k q) = B^k</c>, nested whole powers flattened first. Spelled
/// <c>((x^(1/2))^5)^2</c>, the <c>x^5</c> in the answer to Rubi's 1.1.3.2
/// <c>1/(x^(7/2) sqrt(a + b x^5))</c> was real at a negative x with a zero imaginary part, the
/// outer root on its cut there, which the interval and decimal evaluations rightly refuse, and
/// the measurement of where the answer holds kept <c>provided x &gt;= 0</c> on an answer that holds
/// on both sides; so did <c>(x^(1/2))^2</c> in 1.1.1.2's <c>1/(sqrt(x) sqrt(2 x - 3))</c>.
/// </summary>
private static Entity WithWholePowersOfARootAsPowersOfItsRadicand(Entity answer, Entity radicand)
=> answer
.Replace(node => node is Powf(Powf(var inner, Number.Integer k), Number.Integer m) && IsARootOf(inner, radicand)
? MathS.Pow(inner, Number.Integer.Create(k.EInteger * m.EInteger)) : node)
.Replace(node => node is Powf(var inner, Number.Integer { EInteger: var n }) && IsARootOf(inner, radicand)
&& ((Powf)inner).Exponent is Number.Rational { ERational: var root } && n.Remainder(root.Denominator).IsZero
? MathS.Pow(radicand, Number.Integer.Create(n.Divide(root.Denominator))) : node);

/// <summary>Whether <paramref name="node"/> is <c>radicand^(1/q)</c> for a whole q past 1.</summary>
private static bool IsARootOf(Entity node, Entity radicand)
=> node is Powf(var @base, Number.Rational { ERational: var root }) && @base == radicand
&& root.Numerator.Equals(EInteger.One) && root.Denominator.CompareTo(EInteger.One) > 0;

/// <summary>
/// The one base other than <paramref name="radicand"/> under a power of
/// <paramref name="expr"/> that is not whole, where every such power is half-odd; else null.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// 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>
/// Under <c>u = sqrt(x)</c>, a whole power of u that is a multiple of the root's order written as a
/// power of the radicand, <c>(x^(1/2))^10 = x^5</c>: spelled <c>((x^(1/2))^5)^2</c>, the answer to Rubi's
/// 1.1.3.2 <c>1/(x^(7/2) sqrt(a + b x^5))</c> could not be read at a negative x, its root on the cut,
/// and kept <c>provided x &gt;= 0</c>, where the integrand is real beyond <c>-(a/b)^(1/5)</c> and the
/// answer holds there.
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class WholePowersOfTheRootAsPowersOfTheRadicandIntegralTest
{
[Theory]
[InlineData("1/(x^(7/2)*sqrt(a + b*x^5))")]
[InlineData("1/(x^(17/2)*sqrt(1 + x^5))")]
[InlineData("sqrt(a*x^13)/sqrt(1 + x^5)")]
[InlineData("1/(sqrt(x)*sqrt(-3 + 2*x))")]
public void HoldsOnBothSides(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
// Beyond -(a/b)^(1/5), about -1.13, the integrand is real as a product of two imaginary roots,
// and `1/(sqrt(x) sqrt(2 x - 3))` is real for every negative x the same way.
foreach (var at in new[] { -2.3, -1.4, 0.4, 1.3, 2.2 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
Assert.False(got.IsNaN, $"d/dx of the antiderivative of {integrand} has no value at x = {at}, where the integrand is {want}");
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}");
}
}
}
}
Loading