Skip to content

Commit 20a4b39

Browse files
Keep a quotient written apart true beyond the restriction its sub-answer holds under (#1993)
x^3 (a + b arcsec(c x))/sqrt(1 - c^4 x^4) is integrated by parts, and the remainder reaches SolveByWritingAPowerOfAQuotientApart, which writes sqrt(1 - 1/u^2) as sqrt(u^2 - 1)/|u| and asks sqrt(1 - u^4)/(u sqrt(u^2 - 1)). That came back provided u^2 - 1 >= 0, and the rule's Bare dropped the condition: for |c x| < 1 the answer was the negative of the integral there. Where a power of x was written apart and the sub-answer is restricted to an inequality in x, the rule now checks the bare answer at two points in every interval between the roots of the restriction and of x; returns it where it holds on every one; else returns sgn(P) times it, Rubi's own sign, where the integrand has no value at P's roots and that holds everywhere; and else keeps the condition. Rubi's 5.5.1:227-229 and 5.6.1:227-229 hold on both sides of |c x| = 1, checked by complex values at c = 0.7, -0.7 and 2.1, 24 characters longer each; across 934 rows of 5.5, 5.6, 7.5, 7.6, 5.1.4 and 5.2.4 only these six reach the new path, and 111 solved neighbours answer as before, character for character. Part of #233. Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 48404e4 commit 20a4b39

3 files changed

Lines changed: 234 additions & 0 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6998,6 +6998,27 @@ written back is the argument's, which is that of `x` only for a positive slope a
69986998
| `"asec(x)^2/(x*sqrt(x^2 - 1))".ToEntity().Integrate("x")` | `integral(...)` | the same |
69996999
| `"asec(x + 3)^2/((x + 3)*sqrt((x + 3)^2 - 1))".ToEntity().Integrate("x")` | `integral(...)` | right for `x > -2` and for `x < -4` |
70007000

7001+
### `x^(4k - 1) (a + b arcsec(c x))/sqrt(1 - c^4 x^4)` and the arccosecant's are integrated on both sides of `|c x| = 1`
7002+
7003+
**Answers where there were none.** Rubi's 5.5.1 and 5.6.1 `x^(4k - 1) (a + b arcsec(c x))/sqrt(1 - c^4 x^4)`
7004+
and the arccosecant's were declined by 2.5.0, and on the unreleased master answered for `|c x| > 1` only. By
7005+
parts leaves a root of `1 - 1/(c x)^2`, which is written apart as `sqrt((c x)^2 - 1)/|c x|`, and the antiderivative
7006+
of what that leaves came back `provided (c x)^2 - 1 >= 0`, a condition the step dropped: for `|c x| < 1`, where the
7007+
integrand is complex, the answer's derivative was the integrand's negative. `sqrt(1 - u^2)/sqrt(u - 1)` is
7008+
`sqrt(-(1 + u))` for `u > 1` and its negative for `-1 < u < 1`, so with `sgn((c x)^2 - 1)` in front the antiderivative
7009+
holds on both sides, as Rubi's does with `sqrt(1 - c^2 x^2)/(c x sqrt(1 - 1/(c^2 x^2)))`. The sign is checked at two
7010+
points of every interval between its roots, and goes in front only where the integrand has no value at them, here
7011+
`0/0`; where no sign is found the condition is kept, and the answer says where it holds. The same holds of
7012+
`x^3 (a + b arcsec(c x)) sqrt(1 - c^4 x^4)` and its kin, also declined by 2.5.0
7013+
([#233](https://github.com/asc-community/AngouriMath/issues/233)).
7014+
7015+
| Input | Was (2.5.0) | Now |
7016+
|---|---|---|
7017+
| `"x^3*(a + b*arcsec(c*x))/sqrt(1 - c^4*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 322 characters, for `c^2 x^2 < 1` and `> 1`; 298 for `c^2 x^2 > 1` only on the unreleased master |
7018+
| `"x^7*(a + b*arcsec(c*x))/sqrt(1 - c^4*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 678 characters, the same; 654 on the unreleased master |
7019+
| `"x^11*(a + b*arcsec(c*x))/sqrt(1 - c^4*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 1,211 characters, the same; 1,187 on the unreleased master |
7020+
| `"x^3*(a + b*arccsc(c*x))/sqrt(1 - c^4*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 321 characters, the same; 297 on the unreleased master |
7021+
70017022
### A perfect square in a power of the variable is read as one
70027023

70037024
`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

‎Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs‎

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32323,6 +32323,19 @@ private static bool NotRealAt(Entity expr, Entity.Variable x, double at)
3232332323
foreach (var pair in signs)
3232432324
if (pair.Value % 2 == 1 && extended is { })
3232532325
extended = MathS.Signum(pair.Key) * extended;
32326+
// Bare drops the conditions the simplification attaches, a denominator nonzero, and
32327+
// with them one of another kind, that the antiderivative holds on part of the line
32328+
// only: the root of `1 - 1/x^2` written apart is `sqrt(x^2 - 1)/|x|`, and the
32329+
// remainder parts leaves from Rubi's `x^3 arcsec(x)/sqrt(1 - x^4)` written so comes
32330+
// back `provided x^2 - 1 >= 0`. Returned bare it was taken for an antiderivative on
32331+
// `|x| < 1` as well, where its derivative is the integrand's negative.
32332+
if (tookAPowerOfX && extended is { } && TheRestrictionsOnTheVariable(forPositive, x) is { Count: > 0 } restrictions)
32333+
{
32334+
var (offIt, stillUnder) = OffItsRestrictions(expr, extended, restrictions, byParity: !(wholePowersOfX && everyPowerOfXFromAFactor), x);
32335+
extended = offIt;
32336+
if (stillUnder is { })
32337+
assumed = assumed == Entity.Boolean.True ? stillUnder : assumed & stillUnder;
32338+
}
3232632339
return extended?.Provided(assumed);
3232732340

3232832341
// What is set free under the root is a polynomial of degree four at most: the
@@ -32480,6 +32493,120 @@ Entity PowerOfAPositive(Entity positive, Number.Rational exponent)
3248032493
}
3248132494
}
3248232495

32496+
/// <summary>
32497+
/// The conjuncts of the condition <paramref name="answer"/> is given under that confine
32498+
/// <paramref name="x"/> to part of the real line, its inequalities in <paramref name="x"/>:
32499+
/// not those the simplification attaches, a denominator nonzero, which leave out points.
32500+
/// </summary>
32501+
private static List<Entity> TheRestrictionsOnTheVariable(Entity answer, Entity.Variable x)
32502+
=> answer.InnerSimplified is Providedf(_, var condition)
32503+
? Andf.LinearChildren(condition).Where(conjunct => conjunct is Greaterf or GreaterOrEqualf or Lessf or LessOrEqualf && conjunct.ContainsNode(x)).Distinct().ToList()
32504+
: new List<Entity>();
32505+
32506+
/// <summary>
32507+
/// <paramref name="answer"/>, an antiderivative of <paramref name="integrand"/> where
32508+
/// <paramref name="restrictions"/> hold, as one wherever the integrand has a value, with the
32509+
/// condition it still holds under, <see langword="null"/> where it needs none: as it is
32510+
/// where it holds on every interval between the roots of the restrictions, with the sign
32511+
/// of the one restriction in front where that is what it is off it, and under the
32512+
/// restrictions otherwise. <paramref name="byParity"/> where <paramref name="answer"/> is
32513+
/// the antiderivative for positive x written in |x|.
32514+
/// </summary>
32515+
/// <remarks>
32516+
/// <para>
32517+
/// <c>sqrt(1 - u^2)/sqrt(u - 1)</c> is <c>sqrt(-(1 + u))</c> for <c>u &gt; 1</c> and its
32518+
/// negative for <c>-1 &lt; u &lt; 1</c>, where the root below the bar is imaginary, so an
32519+
/// antiderivative found through the one is the negative of one off it, and
32520+
/// <c>sgn(u - 1) F</c> holds on both sides. Rubi writes that sign as
32521+
/// <c>sqrt(1 - c^2 x^2)/(c x sqrt(1 - 1/(c^2 x^2)))</c>, which is <c>i sgn(c x) sgn(c^2 x^2 - 1)</c>.
32522+
/// Only where the integrand has no value at the restriction's roots, which then part its
32523+
/// domain: where it has one, an antiderivative is continuous there and <c>sgn(P) F</c> is not.
32524+
/// </para>
32525+
/// <para>
32526+
/// Decided at two points of every interval between the real roots of the restrictions and
32527+
/// of x, comparing complex values, every other symbol pinned to an exact rational so that
32528+
/// the roots are exact. An interval where the two cannot be compared does not hold. The
32529+
/// derivative is compared with each <c>(P^(1/n))^n</c> in the answer written <c>P</c>, which
32530+
/// is the same number on every branch: <c>(sqrt(x^2 - 1))^2</c> under a root, inside
32531+
/// <c>(-1, 1)</c>, is an interval about the negative axis that the evaluation cannot place
32532+
/// on either side of the cut.
32533+
/// https://github.com/asc-community/AngouriMath/issues/233
32534+
/// </para>
32535+
/// </remarks>
32536+
private static (Entity Answer, Entity? Condition) OffItsRestrictions(Entity integrand, Entity answer, List<Entity> restrictions, bool byParity, Entity.Variable x)
32537+
{
32538+
Entity Written(Entity inX) => byParity ? inX.Substitute(x, MathS.Abs(x)) : inX;
32539+
var condition = restrictions.Select(Written).Aggregate((left, right) => left & right);
32540+
// What each restriction says is positive, or not negative.
32541+
var positives = restrictions.Select(restriction => restriction switch
32542+
{
32543+
Greaterf(var left, var right) => left - right,
32544+
GreaterOrEqualf(var left, var right) => left - right,
32545+
Lessf(var left, var right) => right - left,
32546+
LessOrEqualf(var left, var right) => right - left,
32547+
_ => restriction
32548+
}).Select(positive => positive.Expand().InnerSimplified).Distinct().ToList();
32549+
var parameters = integrand.Vars.Concat(answer.Vars).Concat(positives.SelectMany(positive => positive.Vars))
32550+
.Where(v => v != x).Distinct().ToList();
32551+
Entity Pinned(Entity e)
32552+
{
32553+
for (var i = 0; i < parameters.Count; i++)
32554+
e = e.Substitute(parameters[i], Number.Rational.Create((i % 3) switch { 0 => 137, 1 => 271, _ => 83 } + 100 * i, 100));
32555+
return e;
32556+
}
32557+
// The real roots of x and of each restriction, exact.
32558+
var bases = new List<Entity> { x };
32559+
foreach (var positive in positives)
32560+
{
32561+
var pinned = Pinned(positive).InnerSimplified;
32562+
bases.Add(pinned);
32563+
if (byParity)
32564+
bases.Add(pinned.Substitute(x, -x).InnerSimplified);
32565+
}
32566+
var realRoots = new List<(Entity Root, double At, bool OfARestriction)>();
32567+
for (var index = 0; index < bases.Count; index++)
32568+
{
32569+
var polynomial = bases[index];
32570+
if (!TreeAnalyzer.TryGetPolynomial(polynomial, x, out _) || MathS.SolveEquation(polynomial, x) is not Set.FiniteSet solutions)
32571+
return (answer, condition);
32572+
foreach (var solution in solutions.Elements)
32573+
{
32574+
if (solution.Evaled is not Number.Complex { IsFinite: true } value)
32575+
return (answer, condition);
32576+
if (value.ImaginaryPart.EDecimal.IsZero)
32577+
realRoots.Add((solution, (double)value.RealPart, index > 0));
32578+
}
32579+
}
32580+
var at = realRoots.Select(root => root.At).Distinct().OrderBy(root => root).ToList();
32581+
var intervals = new List<double[]> { new[] { at[0] - 1, at[0] - 2 }, new[] { at[at.Count - 1] + 1, at[at.Count - 1] + 2 } };
32582+
for (var i = 0; i + 1 < at.Count; i++)
32583+
if (at[i + 1] - at[i] > 1e-9)
32584+
intervals.Add(new[] { at[i] + (at[i + 1] - at[i]) / 3, at[i] + 2 * (at[i + 1] - at[i]) / 3 });
32585+
var points = intervals.Select(pair => pair.Select(point => point.ToString("0.0###########", System.Globalization.CultureInfo.InvariantCulture)).ToArray()).ToList();
32586+
var pinnedIntegrand = Pinned(integrand);
32587+
bool Holds(Entity candidate)
32588+
{
32589+
using var _ = MathS.Settings.DowncastingEnabled.Set(false);
32590+
var derivative = Functions.PartialFractions.Bare(Pinned(candidate))
32591+
.Replace(node => node is Powf(Powf(var radicand, Number.Rational root), Number.Integer power)
32592+
&& root.ERational.Numerator.Equals(EInteger.One) && root.ERational.Denominator.Equals(power.EInteger) ? radicand : node)
32593+
.Differentiate(x);
32594+
return points.All(pair => Functions.PartialFractions.HoldsAtSampledPoints(derivative, pinnedIntegrand, x, pair));
32595+
}
32596+
if (Holds(answer))
32597+
return (answer, null);
32598+
if (positives.Count == 1)
32599+
{
32600+
var signed = MathS.Signum(Written(positives[0])) * answer;
32601+
// The integrand without a value at each real root of the restriction: 0/0 there.
32602+
var partsTheDomain = realRoots.Where(root => root.OfARestriction)
32603+
.All(root => pinnedIntegrand.Substitute(x, root.Root).Evaled is Number.Complex { IsFinite: false });
32604+
if (partsTheDomain && Holds(signed))
32605+
return (signed, null);
32606+
}
32607+
return (answer, condition);
32608+
}
32609+
3248332610
/// <summary>
3248432611
/// Whether the polynomial <paramref name="expr"/> in <paramref name="x"/> is positive at
3248532612
/// every real <paramref name="x"/> but possibly zero once its parameters are real: every
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using System.Linq;
10+
using AngouriMath.Extensions;
11+
using Xunit;
12+
13+
namespace AngouriMath.Tests.Calculus
14+
{
15+
/// <summary>
16+
/// <c>x^(4k - 1) (a + b arcsec(c x))/sqrt(1 - c^4 x^4)</c> and the arccosecant's, by parts: the
17+
/// remainder holds a root of <c>1 - 1/(c x)^2</c>, which is written apart as
18+
/// <c>sqrt((c x)^2 - 1)/|c x|</c>, and what the chain answers for it holds for
19+
/// <c>|c x| &gt;= 1</c> only. Returned without that condition, the answer was wrong for
20+
/// <c>|c x| &lt; 1</c>, where the integrand is complex; off it, it is the negative of one, and
21+
/// <c>sgn((c x)^2 - 1)</c> in front makes it one on both sides. Rubi's 5.5.1 and 5.6.1.
22+
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
23+
/// </summary>
24+
[Trait("Area", "Calculus")]
25+
public sealed class AQuotientWrittenApartBeyondItsRestrictionIntegralTest
26+
{
27+
[Theory]
28+
[InlineData("x^3*(a + b*arcsec(c*x))/sqrt(1 - c^4*x^4)")]
29+
[InlineData("x^7*(a + b*arcsec(c*x))/sqrt(1 - c^4*x^4)")]
30+
[InlineData("x^11*(a + b*arcsec(c*x))/sqrt(1 - c^4*x^4)")]
31+
[InlineData("x^3*(a + b*arccsc(c*x))/sqrt(1 - c^4*x^4)")]
32+
[InlineData("x^7*(a + b*arccsc(c*x))/sqrt(1 - c^4*x^4)")]
33+
[InlineData("x^11*(a + b*arccsc(c*x))/sqrt(1 - c^4*x^4)")]
34+
// And with the root above the bar, which parts leaves the same root of 1 - 1/(c x)^2 beside.
35+
[InlineData("x^3*(a + b*arcsec(c*x))*sqrt(1 - c^4*x^4)")]
36+
[InlineData("x^3*(a + b*arccsc(c*x))*sqrt(1 - c^4*x^4)")]
37+
public void HoldsOnBothSidesOfTheUnitModulus(string integrand)
38+
{
39+
var integral = integrand.ToEntity().Integrate("x");
40+
var text = integral.Stringize();
41+
Assert.DoesNotContain("integral(", text);
42+
Assert.DoesNotContain("provided", text);
43+
// c = 1/2 puts |c x| = 1 at x = 2, and c = -13/10 at |x| = 10/13, a negative c.
44+
Compare(integrand, integral, c: 0.5, inside: new[] { -1.7, -0.9, -0.3, 0.4, 1.1, 1.9 }, outside: new[] { -3.3, -2.2, 2.3, 4.1 });
45+
Compare(integrand, integral, c: -1.3, inside: new[] { -0.7, -0.35, 0.2, 0.6 }, outside: new[] { -2.1, -0.9, 0.85, 1.7 });
46+
}
47+
48+
[Theory]
49+
[InlineData("x^3*(a + b*arcsec(c*x))/sqrt(1 - c^4*x^4)")]
50+
[InlineData("x^3*(a + b*arccsc(c*x))/sqrt(1 - c^4*x^4)")]
51+
public void TheIntegrandHasNoValueWhereTheSignTurns(string integrand)
52+
{
53+
// At |c x| = 1 the integrand is 0/0, so the domain parts there and a sign that turns
54+
// there leaves an antiderivative on each side: Rubi's has the same factor,
55+
// sqrt(1 - c^2 x^2)/(c x sqrt(1 - 1/(c^2 x^2))).
56+
var original = integrand.ToEntity().Substitute("a", 1.3).Substitute("b", 0.6).Substitute("c", 0.5);
57+
Assert.True(original.Substitute("x", 2).EvalNumerical().IsNaN);
58+
Assert.True(original.Substitute("x", -2).EvalNumerical().IsNaN);
59+
}
60+
61+
private static void Compare(string integrand, Entity integral, double c, double[] inside, double[] outside)
62+
{
63+
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.6).Substitute("c", c);
64+
// Pinned before the differentiation, so that sgn((c x)^2 - 1)' is zero off its roots.
65+
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
66+
var original = Pinned(integrand.ToEntity());
67+
foreach (var at in inside)
68+
Assert.True(Math.Abs(c * at) < 1, $"{at} is not inside for c = {c}");
69+
foreach (var at in outside)
70+
Assert.True(Math.Abs(c * at) > 1, $"{at} is not outside for c = {c}");
71+
// In decimals, as the integrator's own checks compare: downcast to rationals, the
72+
// integrand at c = -1.3, x = 0.2 has no value in the legacy evaluation.
73+
using var _ = MathS.Settings.DowncastingEnabled.Set(false);
74+
foreach (var at in inside.Concat(outside))
75+
{
76+
var want = original.Substitute("x", at).EvalNumerical();
77+
var got = derivative.Substitute("x", at).EvalNumerical();
78+
// The integrand is complex on both sides: compared as complex values.
79+
Assert.False(want.IsNaN, $"the integrand {integrand} has no value at x = {at}, c = {c}");
80+
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
81+
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
82+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, c = {c}, where the integrand is {want}");
83+
}
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)