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
16 changes: 16 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,22 @@ of `Q`, times `sqrt(Q)`, and the same two integrals that are not algebraic
| `"(1 + 3*x + 4*x^2)/((1 + 2*x)^3*(2 + 3*x^2)^(5/2))".ToEntity().Integrate("x")` | `integral(...)` | linears over `(2 + 3 x^2)^2` and `2 + 3 x^2` and powers of `1/(1 + 2 x)`, times the root, and a logarithm |
| `"(d + k*x + f*x^2)/((g + h*x)^2*(a + c*x^2)^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | the same in the symbols, and a logarithm or an arctangent by the sign of `a h^2 + c g^2` |

### An even root of a quotient with an odd power below the bar keeps its sign below zero

**Wrong answers on the unreleased master, right ones now.** `sqrt((1 + x)/x^3)` is real for `x < -1` as
well as for `x > 0`, and was integrated as `sqrt(1 + x) x^(-3/2)`, which it is for `x > 0` only: below
`-1` the two differ by a sign, and the answer was wrong at every point there. The check that decides
whether an even root of a quotient may be written apart counted a factor below the bar as though it
were above it; it counts against those above it now, and where the root does not come apart, a linear's
odd power below it comes out with its sign in front of the answer, `|1 + x|` being `sgn(1 + x) (1 + x)`.
2.5.0 declined these ([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sqrt((1 + x)/x^3)".ToEntity().Integrate("x")` | `integral(...)` | `sgn(x)` times the antiderivative for `x > 0`, right on both sides |
| `"sqrt(x/(1 + x)^3)".ToEntity().Integrate("x")` | `integral(...)` | the same with `sgn(1 + x)` |
| `"((1 + x)/x^3)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | the same as the first |

### A function comes out of a fractional power of its even power with its sign

**Improvement, not silent.** The entry two above made `(sin(x)^2)^(3/2)` the modulus `|sin(x)|^3`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20779,16 +20779,23 @@ private static bool NotRealAt(Entity expr, Entity.Variable x, double at)
continue;
}
// g^(2m r) is |g|^(2 m r): g itself where that power is even or g is
// positive, and for x the answer for x > 0 extended by parity;
// otherwise the factor stays where it is.
// positive, for x the answer for x > 0 extended by parity, and for a
// linear an odd whole power of it with its sign in front of the answer,
// `|g|` being `sgn(g) g` -- `sqrt(x/(1 + x)^3)` is
// `sgn(1 + x) sqrt(x/(1 + x))/(1 + x)`; only where the root is a factor
// of the integrand, as the sign goes in front of the answer. Otherwise
// the factor stays where it is.
if (factor is Powf(var g, Number.Integer n) && n.EInteger.CompareTo(EInteger.FromInt32(2)) >= 0 && g.ContainsNode(x)
&& n.EInteger.Divide(EInteger.FromInt32(2)).Multiply(EInteger.FromInt32(2)) is var even
&& (Number.Integer.Create(underneath ? even.Negate() : even) * exponent).InnerSimplified is var power
&& (g == x || power is Number.Integer { EInteger.IsEven: true } || IsPositiveForReal(g, x)))
&& (g == x || power is Number.Integer { EInteger.IsEven: true } || IsPositiveForReal(g, x)
|| power is Number.Integer && factors.Contains(node) && TreeAnalyzer.TryGetPolyLinear(g, x, out var slopeOfG, out _) && !TreeAnalyzer.IsZero(slopeOfG)))
{
var evenPower = MathS.Pow(g, Number.Integer.Create(even));
if (g == x && !factors.Contains(node))
everyPowerOfXFromAFactor = false;
if (g != x && power is Number.Integer { EInteger.IsEven: false } && !IsPositiveForReal(g, x))
signs[g] = signs.TryGetValue(g, out var timesTaken) ? timesTaken + 1 : 1;
taken = taken * (g == x ? PowerOfAPositive(evenPower, underneath ? -exponent : exponent)
: power is Number.Integer ? MathS.Pow(g, power) : MathS.Pow(evenPower, underneath ? -exponent : exponent));
if (!n.EInteger.IsEven)
Expand Down Expand Up @@ -20920,12 +20927,20 @@ Entity WithANegativeConstantInsideAFactor(Entity product)
// out below zero -- which Timofeev's `sqrt(tan(x) tan(2x))` found. The constant must
// be positive, since a negative one would take the phase the other way.
// `((x - 1)^3 (x + 2)^5)^(1/4)` splits: above 1 both are positive, below -2 both
// negative with 3 + 5 a multiple of 8, and between the product is negative.
// negative with 3 + 5 a multiple of 8, and between the product is negative. A factor
// below the bar counts against those above it, its power being the negative of the
// exponent: `sqrt((1 + x)/x^3)` below -1 has the phases of `sqrt(1 + x)` and of
// `x^(-3/2)`, pi/2 and -3 pi/2, which add up to -pi, 1 - 3 = -2, and is
// `-sqrt(1 + x) x^(-3/2)` there; counted as one sum, 1 + 3 = 4 split it, right for
// x > 0 and wrong for every x < -1. The sum is still asked as well, so that only a
// split taken before is taken: the difference alone splits `sqrt((1 + x)/x)` below
// -1, rightly, and the rules after it answer `sqrt(1 + x)/sqrt(x)` for x > 0 only,
// where the root whole is answered on both sides.
bool AnEvenRootOfAProductSplits(Entity above, Entity below, EInteger q, out bool onlyWhereTheRootIsReal)
{
onlyWhereTheRootIsReal = false;
var factors = new List<(Entity Polynomial, EInteger Times)>();
foreach (var side in new[] { above, below })
foreach (var (side, underneath) in new[] { (above, false), (below, true) })
foreach (var factor in Mulf.LinearChildren(side))
{
if (!factor.ContainsNode(x))
Expand All @@ -20937,19 +20952,23 @@ bool AnEvenRootOfAProductSplits(Entity above, Entity below, EInteger q, out bool
var (polynomial, times) = factor is Powf(var g, Number.Integer n) && n.EInteger.Sign > 0 ? (g, n.EInteger) : (factor, EInteger.One);
if (!TreeAnalyzer.TryGetPolynomial(polynomial, x, out _))
return false;
factors.Add((polynomial, times));
factors.Add((polynomial, underneath ? times.Negate() : times));
}
if (factors.Count < 2)
return false;
var realBeyondTheRoot = false;
var splits = OnEveryRealIntervalAt(factors.Select(f => f.Polynomial).ToList(), x, (at, signs) =>
{
var negativeTimes = EInteger.Zero;
var negativeTimesUnsigned = EInteger.Zero;
for (var i = 0; i < signs.Count; i++)
if (signs[i] < 0)
{
negativeTimes = negativeTimes.Add(factors[i].Times);
negativeTimesUnsigned = negativeTimesUnsigned.Add(factors[i].Times.Abs());
}
if (negativeTimes.IsEven)
return negativeTimes.Remainder(q.ShiftLeft(1)).IsZero;
return negativeTimes.Remainder(q.ShiftLeft(1)).IsZero && negativeTimesUnsigned.Remainder(q.ShiftLeft(1)).IsZero;
if (!NotRealAt(expr, x, at))
realBeyondTheRoot = true;
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// 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>
/// An even root of a quotient with an odd power below the bar, written apart: below the bar
/// the power's phase counts against those above it, so <c>sqrt((1 + x)/x^3)</c>, real for
/// <c>x &lt; -1</c> as well, is not <c>sqrt(1 + x) x^(-3/2)</c> there, and was integrated as
/// though it were -- right for <c>x &gt; 0</c> and wrong for every <c>x &lt; -1</c>. Each row is
/// checked at the points where the integrand is real, on both sides.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class RootOfAQuotientOnBothSidesIntegralTest
{
[Theory]
[InlineData("sqrt((1 + x)/x^3)")]
[InlineData("((1 + x)/x^3)^(3/2)")]
[InlineData("sqrt(x/(1 + x)^3)")]
[InlineData("sqrt((2 + x)/(1 + x)^3)")]
[InlineData("x^2*sqrt(x/(1 + x)^3)")]
public void RightOnBothSides(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Assert.DoesNotContain("NaN", integral.Stringize());
var derivative = integral.Substitute("C", 0).Differentiate("x");
var original = integrand.ToEntity();
var below = 0;
var above = 0;
foreach (var at in new[] { -2.7, -2.3, -1.9, -1.3, 0.31, 0.83, 1.77 })
{
var want = original.Substitute("x", at).EvalNumerical();
if (want.IsNaN || Math.Abs((double)want.ImaginaryPart) > 1e-12)
continue;
if (at < 0) below++; else above++;
var got = derivative.Substitute("x", at).EvalNumerical();
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart)),
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
Assert.True(below >= 2 && above >= 3, $"only {below} points below zero and {above} above it could be compared for {integrand}");
}
}
}
Loading