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
19 changes: 19 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,25 @@ written in `u = c + d x` first. Rubi's 1.1.3.2, 1.2.3.2 and 1.3.1 write whole se
| `"(c*m + d*m*x)^3/(a + b*(c + d*x)^3)^2".ToEntity().Integrate("x")` | `integral(...)` | `m^3` times a rational function of `c + d x`, the logarithms and the arctangent |
| `"(c + d*x)^4/(a + b*(c + d*x)^2 + k*(c + d*x)^4)^2".ToEntity().Integrate("x")` | `integral(...)` | a rational function of `c + d x` and arctangents |

### A root of a square inside a sum is integrated on each side of the square's zero

**Answers where there were none.** `1/(1 + (x^2)^(3/2))` is `1/(1 + x^3)` for a positive `x` and
`1/(1 - x^3)` for a negative one; 2.5.0 declined it. The unreleased master wrote the root as
`sgn(x) x^3` and took the sign out in front of the integral, which holds for a factor of the
integrand and not inside a sum, and its answer was the first one's on both sides of zero. A sign
goes in front only where every occurrence of the root is a factor now; otherwise the integrand is
integrated with the sign 1 and with it -1, and the answer is the piecewise of the two on the sign of
the root's linear. The same where a square factor is taken out of a root: `x/(x + sqrt(x^6))` is
`1/(1 + x^2)` for a positive `x` and `1/(1 - x^2)` for a negative one, and was `sgn(x) arctan(x)`
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/(1+(x^2)^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | the integral of `1/(1 + x^3)` where `x > 0`, of `1/(1 - x^3)` otherwise |
| `"1/(1+sqrt(x^2))".ToEntity().Integrate("x")` | `integral(...)` | `ln(x + 1)` where `x > 0`, `-ln(1 - x)` otherwise |
| `"1/(2+sqrt(x^2+2*x+1))".ToEntity().Integrate("x")` | `integral(...)` | `ln(x + 3)` where `x + 1 > 0`, `-ln(1 - x)` otherwise |
| `"x/(x+sqrt(x^6))".ToEntity().Integrate("x")` | `integral(...)` | `arctan(x)` where `x > 0`, a logarithm of `(1 + x)/(1 - x)` otherwise |

### A root of an even power is the modulus, and is no longer read as the power

**Wrong answers, silent.** `(u^2)^(3/2)` is `|u|^3`, and two readers took it for `u^3`: the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16189,6 +16189,16 @@ bool MayBeARootOfASquare(Entity node, Entity.Variable x)
var changed = false;
Entity assumed = Entity.Boolean.True;
Entity signs = Number.Integer.One;
// A sign goes in front of the integral only from a factor of the integrand. Inside a
// sum it is a factor of nothing: `1/(1 + (x^2)^(3/2))` is `1/(1 + x^3)` for a positive
// x and `1/(1 - x^3)` for a negative one, and with the sign taken out in front the
// answer was the first's for both. There the root is written with a sign of its own,
// and the integrand is integrated with it 1 and with it -1, each on its side of the
// linear's zero. One such linear only.
var factors = FactorsOfTheIntegrand(expr).Select(pair => pair.Factor).ToList();
var sign = Variable.CreateUnique(expr, "s_sgn");
Entity? signedLinear = null;
var signsOfMoreThanOneLinear = false;
var written = expr.Replace(node =>
{
if (!MayBeARootOfASquare(node, x))
Expand Down Expand Up @@ -16260,14 +16270,45 @@ bool MayBeARootOfASquare(Entity node, Entity.Variable x)
// `sqrt(a^2 + 2abx + b^2x^2) sqrt(c + ex + dx^2)` threw with it left in place.
// None where it is plainly one: an even power of x plus a positive number, or a
// root of x of an even order plus one, `sqrt(x) + 1`.
var modulus = leading == Number.Integer.One ? power : MathS.Pow(leading, r) * power;
if (!whole && !((half.IsEven || wBase is Powf(_, Number.Rational { ERational.Denominator.IsEven: true }))
&& h.Evaled is Number.Real { IsPositive: true }))
{
// Each occurrence a factor, or the sign of none of them goes in front: the
// two cases are exact for a factor as well, and `(x^2)^(3/2)/(1 + (x^2)^(3/2))`
// holds the one root as a factor and inside the sum both.
if (factors.Count(factor => factor == node) < expr.Nodes.Count(inner => inner == node))
{
if (signedLinear is not null && signedLinear != linear)
{
signsOfMoreThanOneLinear = true;
return node;
}
signedLinear = linear;
changed = true;
return sign * modulus;
}
signs = signs * MathS.Signum(linear);
}
changed = true;
return leading == Number.Integer.One ? power : MathS.Pow(leading, r) * power;
return modulus;
});
if (!changed || Integration.ComputeAsTheSameQuestion(written, x, integrateByParts) is not { } answer)
if (!changed || signsOfMoreThanOneLinear)
return null;
Entity answer;
if (signedLinear is null)
{
if (Integration.ComputeAsTheSameQuestion(written, x, integrateByParts) is not { } unsigned)
return null;
answer = unsigned;
}
else
{
if (Integration.ComputeAsTheSameQuestion(written.Substitute(sign, Number.Integer.One).InnerSimplified, x, integrateByParts) is not { } above
|| Integration.ComputeAsTheSameQuestion(written.Substitute(sign, Number.Integer.MinusOne).InnerSimplified, x, integrateByParts) is not { } below)
return null;
answer = MathS.Piecewise(new[] { new Providedf(above, signedLinear > Number.Integer.Zero) }, below);
}
if (signs != Number.Integer.One)
answer = signs * answer;
return assumed == Entity.Boolean.True ? answer : answer.Provided(assumed);
Expand Down Expand Up @@ -18894,13 +18935,24 @@ static bool IsASumOfPowersOfX(Entity sum, Entity.Variable x)
var atTheTop = Integration.AnsweringTheQuestionAsked;
// The signs are constants, one on each side of a factor's root, so they come out
// in front of the integral: the integrand is asked without them, and the answer
// is the product of the signs to their powers, an even power being one.
// is the product of the signs to their powers, an even power being one. From a root
// that is a factor of the integrand only: inside a sum the sign is a factor of
// nothing, `x/(x + sqrt(x^6))` being `1/(1 + x^2)` for a positive x and `1/(1 - x^2)`
// for a negative one. There the root is written with a sign of its own, and the
// integrand is integrated with it 1 and with it -1, each on its side of the
// factor's root; one such factor only.
Entity signs = Number.Integer.One;
var factorsOfTheIntegrand = FactorsOfTheIntegrand(expr).Select(pair => pair.Factor).ToList();
var sign = Variable.CreateUnique(expr, "s_sgn");
Entity? signedFactor = null;
var signsOfMoreThanOneFactor = false;
var rewritten = expr.Replace(node =>
{
if (node is not Powf(var radicand, Number.Rational exponent) || exponent is Number.Integer
|| !exponent.ERational.Denominator.Equals(EInteger.FromInt32(2)) || !radicand.ContainsNode(x))
return node;
var asAFactor = factorsOfTheIntegrand.Count(factor => factor == node) == expr.Nodes.Count(inner => inner == node);
var signsHere = new List<Entity>();
if (Functions.PolynomialFactorization.FactorComplete(radicand, x) is not { } factorization
|| factorization.Parts.All(part => part.Multiplicity < 2))
return node;
Expand Down Expand Up @@ -18928,19 +18980,46 @@ static bool IsASumOfPowersOfX(Entity sum, Entity.Variable x)
}
outside = outside * (half == 1 ? factor : MathS.Pow(factor, half));
if (withASign)
signs = signs * MathS.Signum(factor);
signsHere.Add(factor);
}
if (part.Multiplicity % 2 == 1)
inside = inside * factor;
}
if (!outside.ContainsNode(x))
return node;
return MathS.Pow(outside, Number.Integer.Create(numerator)) * MathS.Pow(inside, exponent);
var taken = MathS.Pow(outside, Number.Integer.Create(numerator)) * MathS.Pow(inside, exponent);
if (signsHere.Count == 0)
return taken;
if (asAFactor)
{
foreach (var signed in signsHere)
signs = signs * MathS.Signum(signed);
return taken;
}
if (signsHere.Count > 1 || signedFactor is not null && signedFactor != signsHere[0])
{
signsOfMoreThanOneFactor = true;
return node;
}
signedFactor = signsHere[0];
return sign * taken;
});
if (rewritten == expr)
return null;
if (Integration.ComputeAsAQuestionOfItsOwn(rewritten, x, integrateByParts) is not { } result)
if (rewritten == expr || signsOfMoreThanOneFactor)
return null;
Entity result;
if (signedFactor is null)
{
if (Integration.ComputeAsAQuestionOfItsOwn(rewritten, x, integrateByParts) is not { } unsigned)
return null;
result = unsigned;
}
else
{
if (Integration.ComputeAsAQuestionOfItsOwn(rewritten.Substitute(sign, Number.Integer.One).InnerSimplified, x, integrateByParts) is not { } above
|| Integration.ComputeAsAQuestionOfItsOwn(rewritten.Substitute(sign, Number.Integer.MinusOne).InnerSimplified, x, integrateByParts) is not { } below)
return null;
result = MathS.Piecewise(new[] { new Providedf(above, signedFactor > Number.Integer.Zero) }, below);
}
var answer = signs == Number.Integer.One ? result : signs * result;
return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,25 @@ private static void DifferentiatesBack(string integrand)
[InlineData("sqrt(sinh(x)^2)")]
public void ARootOfAPerfectSquareIsTheModulus(string integrand) => DifferentiatesBack(integrand);

/// <summary>
/// A root of a square inside a sum, where its sign is a factor of nothing:
/// <c>1/(1 + (x^2)^(3/2))</c> is <c>1/(1 + x^3)</c> for a positive <c>x</c> and
/// <c>1/(1 - x^3)</c> for a negative one, each integrated on its own side of zero. With the
/// sign taken out in front, the answer was the first's on both sides. The same with a
/// square factor taken out of a root: <c>x/(x + sqrt(x^6))</c> is <c>1/(1 + x^2)</c> and
/// <c>1/(1 - x^2)</c>. Rubi's 1.1.3.2 and 1.3.2.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Theory]
[InlineData("1/(1+(x^2)^(3/2))")]
[InlineData("1/(1+sqrt(x^2))")]
[InlineData("x/(1+(x^2)^(3/2))")]
[InlineData("(x^2)^(3/2)/(1+(x^2)^(3/2))")]
[InlineData("1/(2+sqrt(x^2+2*x+1))")]
[InlineData("x/(x+sqrt(x^6))")]
[InlineData("(x-sqrt(x^6))/(x*(1-x^4))")]
public void ARootOfASquareInsideASumIsIntegratedOnEachSide(string integrand) => DifferentiatesBack(integrand);

/// <summary>
/// A square written with symbols: <c>a^2 + 2 a b x + b^2 x^2</c> is <c>(a + b x)^2</c>,
/// whose discriminant <c>4a^2b^2 - 4a^2b^2</c> <see cref="Entity.InnerSimplified"/> does
Expand Down
Loading