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 @@ -3100,6 +3100,24 @@ leaves are read as quotients of the sine and the cosine
| `"1/(a+b*asin(c*x))".Integrate("x")` | `integral(1 / (a + b * arcsin(c * x)), x)` | `Si` and `Ci` of `(a + b arcsin(c x))/b` |
| `"x/((c+a^2*c*x^2)^2*atan(a*x))".Integrate("x")` | `integral(x / ((c + a ^ 2 * c * x ^ 2) ^ 2 * arctan(a * x)), x)` | `1/2 * Si(2 * arctan(a * x)) / (a ^ 2 * c ^ 2) + C` |

### The arcsecant beside a root of `x^2 - 1` is integrated right below `-1` too

**Answers where there were none.** Under `x = sec(u)`, the substitution that undoes the arcsecant,
`sqrt(x^2 - 1)` is `|tan(u)|`, and on the arcsecant's range the tangent has the sign of `x`. It was
written `tan(u)`, so that on the unreleased master `x arcsec(x)/sqrt(x^2 - 1)` and its kin were
answered right for `x > 1` and with the integrand's negative for a derivative at every `x < -1`,
where they are as real; 2.5.0 declined them. The root is `tan(u) sgn(x)` now, as the cosecant's and
the cotangent's carry their signs, and a first power of the arcsecant beside it is by parts. The sign
written back is the argument's, which is that of `x` only for a positive slope and no constant:
`arcsec(x + 3)` has the sign of `x + 3` ([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"x*asec(x)/sqrt(-1 + x^2)".ToEntity().Integrate("x")` | `integral(...)` | right for `x > 1` and for `x < -1` |
| `"asec(x)/(x^2*sqrt(-1 + x^2))".ToEntity().Integrate("x")` | `integral(...)` | the same |
| `"asec(x)^2/(x*sqrt(x^2 - 1))".ToEntity().Integrate("x")` | `integral(...)` | the same |
| `"asec(x + 3)^2/((x + 3)*sqrt((x + 3)^2 - 1))".ToEntity().Integrate("x")` | `integral(...)` | right for `x > -2` and for `x < -4` |

### A perfect square in a power of the variable is read as one

`sqrt(a^2 + 2 a b x^2 + b^2 x^4) sqrt(c + e x + d x^2)` was left unevaluated. The radicand is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7157,10 +7157,12 @@ private static bool TheInverseIsBelowTheBar(Entity expr, Entity inverse, Entity.
/// and the cosine is not negative; substituting and simplifying instead would leave
/// <c>sqrt(1 - sin(u)^2)</c>, which the simplifier is right not to call <c>cos(u)</c>.
/// So <c>(1 - x^2)^(k/2)</c> becomes <c>cos(u)^k</c> directly, and likewise
/// <c>(1 + x^2)^(k/2)</c> into <c>sec(u)^k</c> for the tangent and <c>(x^2 - 1)^(k/2)</c>
/// into <c>tan(u)^k</c> for the secant, each on its principal branch. That branch is the
/// one the inverse function is defined on, so the answer holds wherever the integrand
/// is read through it.
/// <c>(1 + x^2)^(k/2)</c> into <c>sec(u)^k</c> for the tangent, each on its principal
/// branch. That branch is the one the inverse function is defined on, so the answer holds
/// wherever the integrand is read through it. For the secant <c>(x^2 - 1)^(k/2)</c> is
/// <c>|tan(u)|^k</c>, and on the arcsecant's range the tangent has the sign of the
/// argument, so it becomes <c>tan(u)^k</c> times that sign to the <c>k</c>, as the
/// cosecant's and the cotangent's roots carry theirs.
/// </para>
/// <para>
/// Exactly one inverse function, of the variable or of a linear <c>c x + d</c> in it,
Expand Down Expand Up @@ -7213,9 +7215,10 @@ private static bool TheInverseIsBelowTheBar(Entity expr, Entity inverse, Entity.
}

var u = Variable.CreateUnique(expr, "u_inv");
// The sign of u, where the root's sign is that: a constant on each half of the
// range, carried through the integration as a symbol and written back as the sign
// of x, which it is for the cosecant and the cotangent.
// The sign the root has, where it has one: a constant on each half of the range,
// carried through the integration as a symbol and written back as the sign of the
// argument, which it is -- the sign of u for the cosecant and the cotangent, and of
// tan(u) for the secant.
var signOfU = Variable.CreateUnique(expr, "s_inv");
// x in terms of u, dx/du, the quadratic whose root goes by construction and what it
// becomes, and the way back.
Expand Down Expand Up @@ -7245,7 +7248,11 @@ private static bool TheInverseIsBelowTheBar(Entity expr, Entity inverse, Entity.
(xInU, dxdu, radicandBase, root) = (MathS.Cosec(u), -MathS.Cosec(u) * MathS.Cotan(u), MathS.Sqr(argument) - 1, MathS.Cotan(u) * signOfU);
break;
default:
(xInU, dxdu, radicandBase, root) = (MathS.Sec(u), MathS.Sec(u) * MathS.Tan(u), MathS.Sqr(argument) - 1, MathS.Tan(u));
// arcsec has the range [0, pi/2) ∪ (pi/2, pi], where the tangent has the sign
// of the argument: `sqrt(x^2 - 1)` is `tan(u) sgn(x)`. Taken as `tan(u)`, the
// answer was right for x > 1 and its derivative the integrand's negative for
// every x < -1, where the integrand is as real: `x arcsec(x)/sqrt(x^2 - 1)`.
(xInU, dxdu, radicandBase, root) = (MathS.Sec(u), MathS.Sec(u) * MathS.Tan(u), MathS.Sqr(argument) - 1, MathS.Tan(u) * signOfU);
break;
}
if (linear)
Expand Down Expand Up @@ -7318,10 +7325,10 @@ node is Powf(var @base, Number.Integer power) && power.EInteger.CompareTo(EInteg
{
if (radicalsRemoved == 0 && !exponentialOfTheInverse && !powerOfTheInverse)
return null;
// The cosecant and the cotangent carry the sign of u into the root, and a first
// power of either beside a root is parts' -- `arccot(x)/(1 + x^2)^(3/2)` is
// `x arccot(x)/sqrt(1 + x^2) + 1/sqrt(1 + x^2)` there, with no sign in it.
if (inverse is Arccosecantf or Arccotanf && !exponentialOfTheInverse && !powerOfTheInverse)
// The cosecant, the cotangent and the secant carry a sign into the root, and a
// first power of any of them beside a root is parts' -- `arccot(x)/(1 + x^2)^(3/2)`
// is `x arccot(x)/sqrt(1 + x^2) + 1/sqrt(1 + x^2)` there, with no sign in it.
if (inverse is Arccosecantf or Arccotanf or Arcsecantf && !exponentialOfTheInverse && !powerOfTheInverse)
return null;
}
// And nothing else of `x` under a root, which the construction did not reach:
Expand Down Expand Up @@ -7374,10 +7381,11 @@ node is Powf(var @base, Number.Rational power) && power is not Number.Integer &&
if (result is null)
return null;
if (result.ContainsNode(signOfU))
// The sign squared is one, and the sign itself is the sign of x.
// The sign squared is one, and the sign itself is the argument's: `arccsc(c x + d)`
// has the sign of `c x + d`, which is the sign of x only for a positive c and no d.
result = result
.Replace(node => node is Powf(var b, Number.Integer k) && b == signOfU ? (k.EInteger.IsEven ? Number.Integer.One : signOfU) : node)
.Substitute(signOfU, MathS.Signum(x));
.Substitute(signOfU, MathS.Signum(argument));
return TrigonometryOfTheInverseInX(result.Substitute(u, inverse), inverse, argument);
}

Expand Down
20 changes: 20 additions & 0 deletions Sources/Tests/UnitTests/Calculus/InverseSecantIntegralTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,26 @@ public void TheFourThatWereAlreadyThere(string integrand, double[] points)
[InlineData("x^3*asec(x)/sqrt(x^4 - 1)", new[] { 1.4, 2.6, 5.1, -1.9, -3.7 })]
public void ByPartsAgainstARootOfAQuartic(string integrand, double[] points) => DifferentiatesBack(integrand, points);

/// <summary>
/// The arcsecant beside a root of <c>x^2 - 1</c>, under <c>x = sec(u)</c>: the root is
/// <c>tan(u) sgn(x)</c> on the arcsecant's range, and taken as <c>tan(u)</c> the answers
/// were right for x &gt; 1 and their derivatives the integrands' negatives for every
/// x &lt; -1, where the integrands are as real. Timofeev's and Charlwood's. With a linear
/// argument the sign is the argument's: at <c>x = -1.5</c> the sign of <c>x + 3</c> is
/// not the sign of <c>x</c>.
/// </summary>
[Theory]
[InlineData("x*asec(x)/sqrt(-1 + x^2)", new[] { 1.3, 2.6, -1.3, -1.9, -2.7 })]
[InlineData("asec(x)*sqrt(-1 + x^2)/x^4", new[] { 1.3, 2.6, -1.3, -1.9, -2.7 })]
[InlineData("asec(x)/(-1 + x^2)^(5/2)", new[] { 1.3, 2.6, -1.3, -1.9, -2.7 })]
[InlineData("x^3*asec(x)/(-1 + x^2)^(5/2)", new[] { 1.3, 2.6, -1.3, -1.9, -2.7 })]
[InlineData("asec(x)/(x^2*sqrt(-1 + x^2))", new[] { 1.3, 2.6, -1.3, -1.9, -2.7 })]
[InlineData("(-1 + x^2)^(3/2)*asec(x)^2/x^5", new[] { 1.3, 2.6, -1.3, -1.9, -2.7 })]
[InlineData("asec(x)^3*sqrt(-1 + x^2)/x^4", new[] { 1.3, 2.6, -1.3, -1.9, -2.7 })]
[InlineData("asec(x)^2/(x*sqrt(x^2 - 1))", new[] { 1.3, 2.6, -1.3, -1.9, -2.7 })]
[InlineData("asec(x + 3)^2/((x + 3)*sqrt((x + 3)^2 - 1))", new[] { 0.5, -1.5, -0.5, -4.5, -6.0 })]
public void BesideARootOnBothIntervals(string integrand, double[] points) => DifferentiatesBack(integrand, points);

/// <summary>
/// A non-linear argument is not this row's: <c>arcsec(x^2)</c> would want the chain rule
/// undone first, and the table reads a linear argument only.
Expand Down
Loading