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
21 changes: 21 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3708,6 +3708,27 @@ Rubi's 1.1.4.2, `(c x)^m (a x^j + b x^n)^p` ([#718](https://github.com/asc-commu
| `"x/sqrt(a*x + b*x^4)".Integrate("x")` | left unevaluated | `K` times `2 ln(2 b x^(3/2) + 2 sqrt(b) sqrt(a + b x^3))/(3 sqrt(b))` for `b > 0`, an arcsine for `b < 0`; real where `x < -(a/b)^(1/3)` as well, where `K` is `-1` |
| `"x^(3/2*(n - 1))/(a*x^(n - 1) + b*x^n + c*x^(n + 1))^(3/2)".Integrate("x")` | left unevaluated | `K (2 b + 4 c x)/((4 a c - b^2) sqrt(a + b x + c x^2))`; `K` is 1 for a positive `x`, and a sign for a negative one, `-1` for `n = 2.3` |

### Half-odd powers of `a ± a sin` beside a fractional power of the cosine are integrated through the sine

**Answers where there were none.** `(e cos(c + d x))^(5/2) (a + a sin(c + d x))^(3/2)`,
`sqrt(e cos(c + d x))/sqrt(a + a sin(c + d x))` and the 22 others of Rubi's 4.1.1.2 with a half-odd power of each were
declined. Under `u = sin(y)` they are `(1 + u)^A (1 - u)^B` with `A` and `B` quarter-odd, which by Chebyshev's theorem
is elementary only where `A + B` is whole. The rule below now takes numeric powers where `A`, `B` or `A + B` is whole
and the rest is a polynomial in `u`, also one level below the question, and declines
`(a + a sin(y)) sqrt(e cos(y))`, which is elliptic, before asking anything. Three of the file the unreleased master
answered in six to eleven seconds, under a condition, now come through it in a twentieth of a second and without one,
and `sqrt(e sin(c + d x))/sqrt(a - a cos(c + d x))`, which it answered by the half angle only for positive `a` and `e`, is
answered for either sign ([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(k*cos(c + d*x))^(5/2)*(a + a*sin(c + d*x))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 1,156 characters |
| `"sqrt(k*cos(c + d*x))/sqrt(a + a*sin(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | 859 characters |
| `"(a + a*sin(c + d*x))^(5/2)/(k*cos(c + d*x))^(13/2)".ToEntity().Integrate("x")` | `integral(...)` | 468 characters |
| `"(k*cos(c + d*x))^(9/2)/(a + a*sin(c + d*x))^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | 1,073 characters |
| `"(a + a*sin(c + d*x))^(3/2)/(k*cos(c + d*x))^(9/2)".ToEntity().Integrate("x")` | `integral(...)`; 456 characters under a condition, in 10.6 s, on the unreleased master | 403 characters, in 61 ms |
| `"sqrt(k*sin(c + d*x))/sqrt(a - a*cos(c + d*x))".ToEntity().Integrate("x")` | `integral(...)`; on the unreleased master, an answer only where `a > 0` and `k > 0` | 825 characters, for either sign |

### Symbolic powers of `a ± a sin` beside a power of the cosine are integrated through the sine

**Answers where there were none.** `(a + a sin(h + f x))^m sqrt(c - c sin(h + f x))` and its kin,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5469,8 +5469,8 @@ Entity Integral(ERational power)
}

/// <summary>
/// Powers of <c>a ± a sin(y)</c> with a symbolic exponent, beside a power of
/// <c>g cos(y)</c> and anything else in the sine alone, under <c>u = sin(y)</c>:
/// Powers of <c>a ± a sin(y)</c> with a symbolic exponent, or beside a fractional power of
/// <c>g cos(y)</c>, and anything else in the sine alone, under <c>u = sin(y)</c>:
/// <c>(1 + sin(y))(1 - sin(y))</c> is <c>cos(y)^2</c>, so each factor is a power of
/// <c>1 + u</c> or of <c>1 - u</c> up to a factor constant on each interval where it is
/// defined, and <c>dy = du/cos(y)</c> is one more pair of half powers. The cosine's the
Expand All @@ -5495,12 +5495,24 @@ Entity Integral(ERational power)
/// <c>(a (1 + u))^m</c> is not <c>a^m (1 + u)^m</c> for every <c>a</c>, and the quotient
/// of the two is constant on each interval either way. At the question asked only, and
/// only where an exponent is symbolic: numeric half powers are the half angle's.
/// </para>
/// <para>
/// But for a fractional power of the cosine, which the half angle does not read: Rubi's
/// <c>(e cos(c + d x))^p (a + a sin(c + d x))^m</c> with half-odd <c>p</c> and <c>m</c>
/// were past the budget, the substitution search spending it on the two radicals. In
/// <c>u</c> they are <c>(1 + u)^A (1 - u)^B</c> with <c>A</c> and <c>B</c> quarter-odd, which
/// by Chebyshev's theorem is elementary only where <c>A + B = m + p - 1</c> is whole, and
/// with every power a number the rule asks only where <c>A</c>, <c>B</c> or <c>A + B</c> is
/// whole and the rest is a polynomial in <c>u</c>: <c>(a + a sin(y)) sqrt(e cos(y))</c>, which
/// is elliptic, is declined before anything is asked. Numeric powers are read one level
/// below the question too, where the polynomial term hands <c>1/(A B)</c> on as
/// <c>(A B)^(-1)</c>, and a whole power of a product is read as the powers of its factors.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </para>
/// </remarks>
internal static Entity? SolveSymbolicPowersOfOnePlusMinusASineThroughTheSine(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!Integration.AnsweringTheQuestionAsked)
if (!Integration.AnsweringTheQuestionAskedOrOneBelow)
return null;
Entity? argument = null;
foreach (var node in expr.Nodes)
Expand All @@ -5519,8 +5531,12 @@ Entity Integral(ERational power)
var cosine = MathS.Cos(argument);
// Each factor as a base and an exponent, a power of a power read as one power: the
// quotient of the two spellings is constant on each interval, and K carries it.
// A whole power of a product is the product of the powers: `1/(A B)` is handed on as
// `(A B)^(-1)` by the polynomial term.
var factors = new List<(Entity Factor, Entity Base, Entity Exponent)>();
foreach (var factor in Mulf.LinearChildren(expr))
foreach (var factor in Mulf.LinearChildren(expr).SelectMany(factor => factor is Powf(var product, Number.Integer power) && product is Mulf or Divf
? Mulf.LinearChildren(product).Select(part => (Entity)MathS.Pow(part, power))
: new[] { factor }))
{
Entity @base = factor, exponent = Number.Integer.One;
while (@base is Powf(var inner, var power))
Expand Down Expand Up @@ -5580,12 +5596,22 @@ Entity Integral(ERational power)
return null;
rest *= inU;
}
if (!symbolic)
// Symbolic powers at the question asked only. Numeric powers are the half angle's, but
// for a fractional power of the complement, which it does not read; and those one level
// down too, where the polynomial term hands on `1/(A B)` as `(A B)^(-1)`.
if (symbolic ? !Integration.AnsweringTheQuestionAsked
: !(ofTheComplement.InnerSimplified is Number.Rational complementPower && !IsWhole(complementPower.ERational)))
return null;
// dy = du/complement takes one more half power of each.
var half = Number.Rational.Create(1, 2);
var a = (plus + ofTheComplement * half - half).Simplify();
var b = (minus + ofTheComplement * half - half).Simplify();
// With every power a number, only where Chebyshev's theorem says the question in u is
// elementary, and beside a polynomial in u: otherwise it is a search past the budget.
if (!symbolic && !(a is Number.Rational ra && b is Number.Rational rb
&& (IsWhole(ra.ERational) || IsWhole(rb.ERational) || IsWhole(ra.ERational.Add(rb.ERational)))
&& IsAPolynomialInU(rest.InnerSimplified)))
return null;
// A zero exponent leaves its factor out: `(1 - u)^0` is one provided `u` is not one, and
// the condition would stand between the question and the rules that answer it.
Entity PowerOf(Entity @base, Entity exponent) => exponent == Number.Integer.Zero ? Number.Integer.One : MathS.Pow(@base, exponent);
Expand Down Expand Up @@ -5619,6 +5645,14 @@ Entity Integral(ERational power)
}
return found;
}

static bool IsWhole(ERational value) => value.IsInteger();

// Free of u, or a polynomial in it with coefficients free of it.
bool IsAPolynomialInU(Entity part)
=> !part.ContainsNode(u)
|| TreeAnalyzer.TryGetPolynomial(part, u, out var terms)
&& terms.Keys.All(power => power.Sign >= 0) && terms.Values.All(coefficient => !coefficient.ContainsNode(u));
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1045,7 +1045,8 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfACosineAndASinePlusTheirAmplitude(expr, x)) is { }) return answer;
// And symbolic powers of `a ± a sin(y)` beside a power of `g cos(y)`, by u = sin(y), where
// `(1 + u)(1 - u)` is the cosine's square: the half angle wants numeric powers, and the
// substitution search spent the budget on these.
// substitution search spent the budget on these. Numeric ones too beside a fractional
// power of `g cos(y)`, which the half angle does not read.
if ((answer = IndefiniteIntegralSolver.SolveSymbolicPowersOfOnePlusMinusASineThroughTheSine(expr, x, integrateByParts)) is { }) return answer;
// And a half-odd power of a +- a sec(y), which is that square over cos(y): by the half-angle
// tangent, in which the whole is rational beside one root.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// 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>
/// Numeric powers of <c>a ± a sin(y)</c> beside a fractional power of <c>k cos(y)</c>, under
/// <c>u = sin(y)</c>: <c>(1 + u)^A (1 - u)^B</c> with <c>A + B</c> whole, which the half angle
/// does not read and the substitution search spent the budget on. Checked where the cosine is
/// positive, and with <c>k</c> negative where it is negative, both real. Rubi's 4.1.1.2.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class FractionalPowersOfACosineBesideOnePlusASineIntegralTest
{
[Theory]
[InlineData("(k*cos(c + d*x))^(5/2)*(a + a*sin(c + d*x))^(3/2)")]
[InlineData("sqrt(k*cos(c + d*x))/sqrt(a + a*sin(c + d*x))")]
[InlineData("(a + a*sin(c + d*x))^(5/2)/(k*cos(c + d*x))^(13/2)")]
[InlineData("(k*cos(c + d*x))^(9/2)/(a + a*sin(c + d*x))^(5/2)")]
[InlineData("1/((k*cos(c + d*x))^(7/2)*(a + a*sin(c + d*x))^(3/2))")]
[InlineData("(k*cos(c + d*x))^(3/2)*sqrt(a - a*sin(c + d*x))")]
[InlineData("sin(c + d*x)*sqrt(k*cos(c + d*x))*(a + a*sin(c + d*x))^(3/2)")]
[InlineData("(k*sin(c + d*x))^(3/2)*(a + a*cos(c + d*x))^(3/2)")]
public void ThroughTheSine(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Assert.True(text.Length < 3000, $"{text.Length} characters of answer for {integrand}");
// y = c + d x stays inside (0, pi/2) at the first pins and inside (pi/2, pi) at the second.
foreach (var (k, c) in new[] { (0.7, 0.4), (-0.7, 2.2) })
{
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("k", k).Substitute("c", c).Substitute("d", 0.3);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { -1.2, -0.7, 0.3, 0.8, 1.3, 2.9 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
if (want.IsNaN)
continue;
compared++;
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}, k = {k}, where the integrand is {want}");
}
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
}
}
}
}
Loading