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
15 changes: 15 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,21 @@ but `sqrt(q)^2 = q` ([#1598](https://github.com/asc-community/AngouriMath/issues
| `"1/sqrt(-x^2 + i)".Integrate("x")` | `-arcsin(-2x/sqrt(4i)) + C` | `ln(-2x + 2i sqrt(-x^2 + i))/i + C`, an antiderivative as before, in the logarithm |
| `"1/(sqrt(x)*sqrt(1 + i*x))".Integrate("x")`, and Rubi 4.3.2.1's `sqrt(tan(c + d x))/(a + i a tan(c + d x))^(5/2)` | `integral(...)` | the antiderivative |

### A quotient by the square of a base is integrated by parts against the base's reciprocal

**Answers where there were none.** `x^2/(a x cos(a x) - sin(a x))^2` was declined, with the rest of
Rubi's 4.7.7 over the square of `a x cos(a x) - sin(a x)` or `cos(a x) + a x sin(a x)`. A quotient
`N/(M v^2)` by the square of a base with a function of `x` in it is `g v'/v^2` with `g = N/(M v')`, and
`v'/v^2` is the derivative of `-1/v`: its integral is `-g/v` plus the integral of `g'/v`, taken where
the base's derivative is one term and `v` cancels from `g'/v`
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"x^2/(a*x*cos(a*x) - sin(a*x))^2".ToEntity().Integrate("x")` | `integral(...)` | `x/(a^2 sin(a x) (a x cos(a x) - sin(a x))) - cot(a x)/a^3` |
| `"sin(a*x)^2/(a*x*cos(a*x) - sin(a*x))^2".ToEntity().Integrate("x")` | `integral(...)` | a quotient by `a x cos(a x) - sin(a x)` and `1/(a^2 x)` |
| `"sin(a*x)^3/(x*(a*x*cos(a*x) - sin(a*x))^2)".ToEntity().Integrate("x")` | `integral(...)` | the same with the sine integral `Si(a x)` |

### Trigonometric functions of multiples of one linear argument are written in it

`csc(a + b x) csc(2a + 2b x)^2` was left unevaluated, and `csc(1 + x) csc(2 + 2x)^2` ran twenty
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,81 @@ private static bool IsADerivativeOfAFactorBelowTheBar(Entity expr, Entity.Variab
return Integration.ComputeAsTheSameQuestion(written, x, integrateByParts);
}

/// <summary>
/// A quotient by the square of a base, by parts against the base's reciprocal, where the
/// base's derivative makes the rest exact: <c>N/(M v^2)</c> is <c>g v'/v^2</c> with
/// <c>g = N/(M v')</c>, and <c>v'/v^2</c> is the derivative of <c>-1/v</c>, so the integral
/// is <c>-g/v</c> plus the integral of <c>g'/v</c>; taken only where <c>v</c> cancels from
/// that, with the functions of <paramref name="x"/> in it taken for indeterminates.
/// </summary>
/// <remarks>
/// <c>x^2/(a x cos(a x) - sin(a x))^2</c> was declined, with the rest of Rubi's 4.7.7 by the
/// square of <c>a x cos(a x) - sin(a x)</c> or <c>cos(a x) + a x sin(a x)</c>: the base's
/// derivative is <c>-a^2 x sin(a x)</c>, so <c>g</c> is <c>-x csc(a x)/a^2</c>, and
/// <c>g'</c> is <c>a^2 v/(a^2 sin(a x))^2</c>, whose integral is <c>-cot(a x)/a^3</c>. Only
/// for a base with a function of <paramref name="x"/> in it that is not a polynomial, since
/// a quotient of polynomials is the partial fractions', and only where <c>v</c> cancels,
/// since otherwise the remainder is the harder question.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveByPartsAgainstTheReciprocalOfASquaredBase(Entity expr, Entity.Variable x)
{
if (!TryReadAsQuotient(expr, out var numerator, out var denominator) || !denominator.ContainsNode(x))
return null;
Entity? squared = null;
Entity rest = Number.Integer.One;
foreach (var factor in Mulf.LinearChildren(denominator))
if (squared is null && factor is Powf(var @base, Number.Integer { EInteger: var power }) && power.Equals(EInteger.FromInt32(2))
&& @base is Sumf or Minusf && @base.ContainsNode(x)
&& @base.Nodes.Any(node => node.ContainsNode(x) && IsAFunctionOfTheVariable(node))
// The variable outside the functions as well, as in `a x cos(a x) - sin(a x)`: a
// base that is a function of `sin(x)` alone, `(a + b sin(x))^2`, is the
// substitutions' and the reductions', and was answered in a second where this
// ran past the budget.
&& @base.Replace(node => IsAFunctionOfTheVariable(node) ? Number.Integer.One : node).ContainsNode(x))
squared = @base;
else
rest = rest * factor;
if (squared is null)
return null;
// A power of x every term of the base has goes to the rest: `x^2 v^2` arrives as
// `(a x^2 cos(a x) - x sin(a x))^2`, the square of `x v`.
var lowest = Sumf.LinearChildren(squared).Select(term => Mulf.LinearChildren(term)
.Select(factor => factor == x ? 1 : factor is Powf(var b, Number.Integer { EInteger: var n }) && b == x && n.CanFitInInt32() ? n.ToInt32Unchecked() : 0)
.Sum()).Min();
if (lowest > 0)
{
squared = (squared / MathS.Pow(x, lowest)).Expand().InnerSimplified;
rest = rest * MathS.Pow(x, 2 * lowest);
}
// Expanded, since the derivative of `a x cos(a x)` and of `sin(a x)` each have an
// `a cos(a x)`, which cancel only once multiplied out.
var derivative = squared.Differentiate(x).Expand().InnerSimplified;
// A derivative of one term, so that what it leaves below the bar of the remainder is
// no new square of a sum to take apart the same way.
if (TreeAnalyzer.IsZero(derivative) || derivative is Sumf or Minusf)
return null;
// The ring's answer first, where it has one: `x^2/(x cos(x) - sin(x))^2` is
// `(x sin(x) + cos(x))/(x cos(x) - sin(x))` there, continuous across the zeros of the
// base's derivative, where `-g/v` and the remainder each have a pole that cancels.
if (SolveByTrigonometricTowerAnsatz(expr, x) is { } fromTheRing)
return fromTheRing;
var (gTop, gBottom) = CancelledWithFunctionsAsIndeterminates(numerator, rest * derivative, x, expr);
// g' over v, cancelled: (A' B - A B')/(B^2 v) for g = A/B.
var above = (gTop.Differentiate(x) * gBottom - gTop * gBottom.Differentiate(x)).InnerSimplified;
var (rTop, rBottom) = CancelledWithFunctionsAsIndeterminates(above, MathS.Pow(gBottom, 2) * squared, x, expr);
if (rBottom.Nodes.Any(node => node == squared) || !rTop.ContainsNode(x) && !rBottom.ContainsNode(x) && TreeAnalyzer.IsZero(rTop))
return null;
var remainder = Functions.PartialFractions.Bare(rTop / rBottom);
if (Integration.ComputeIndefiniteIntegral(remainder, x) is not { } integral || integral.Nodes.Any(node => node is Integralf))
return null;
return -gTop / (gBottom * squared) + integral;
}

/// <summary>Whether <paramref name="node"/> is a trigonometric function, a logarithm or an exponential.</summary>
private static bool IsAFunctionOfTheVariable(Entity node)
=> node is Sinf or Cosf or Tanf or Cotanf or Secantf or Cosecantf or Logf or Powf(Number, _);

/// <summary>Whether <paramref name="expr"/> holds a fractional power of something in <paramref name="x"/>.</summary>
private static bool HasARadicalOf(Entity expr, Entity.Variable x)
=> expr.Nodes.Any(node => node is Powf(var @base, Number.Rational power) && power is not Number.Integer && @base.ContainsNode(x));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// is `x^2/(L^2 sqrt(q))` cancelled, and was five terms over `L^5 (1 + L sqrt(q))`
// each searched for seconds when the reduction reached it first.
if ((answer = IndefiniteIntegralSolver.SolveByCancellingWithFunctionsAsIndeterminates(expr, x, integrateByParts)) is { }) return answer;
// A quotient by the square of a base with a function in it, by parts against the base's
// reciprocal where the base's derivative makes the rest exact.
if ((answer = IndefiniteIntegralSolver.SolveByPartsAgainstTheReciprocalOfASquaredBase(expr, x)) is { }) return answer;
// A polynomial over a power of a linear and something that is not a polynomial,
// the polynomial written in powers of the linear at its root: the part the power
// divides goes over the rest alone. Before the split, so that the whole polynomial
Expand Down
52 changes: 52 additions & 0 deletions Sources/Tests/UnitTests/Calculus/SquaredBaseByPartsIntegralTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// 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>
/// A quotient by the square of a base with a function in it, by parts against the base's
/// reciprocal: <c>N/(M v^2)</c> is <c>g v'/v^2</c> with <c>g = N/(M v')</c>, whose integral is
/// <c>-g/v</c> plus that of <c>g'/v</c>, which the base cancels from where its derivative is
/// one term. <c>x^2/(a x cos(a x) - sin(a x))^2</c> was declined; Rubi's 4.7.7.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
/// <remarks>
/// Checked by differentiating back with <c>a = 1.3</c> on both sides of zero.
/// </remarks>
[Trait("Area", "Calculus")]
public sealed class SquaredBaseByPartsIntegralTest
{
[Theory]
[InlineData("x^2/(a*x*cos(a*x) - sin(a*x))^2")]
[InlineData("sin(a*x)^2/(a*x*cos(a*x) - sin(a*x))^2")]
[InlineData("sin(a*x)^3/(x*(a*x*cos(a*x) - sin(a*x))^2)")]
[InlineData("sin(a*x)^4/(x^2*(a*x*cos(a*x) - sin(a*x))^2)")]
[InlineData("x^2/(cos(a*x) + a*x*sin(a*x))^2")]
[InlineData("cos(a*x)^2/(cos(a*x) + a*x*sin(a*x))^2")]
[InlineData("cos(a*x)^3/(x*(cos(a*x) + a*x*sin(a*x))^2)")]
public void IsIntegratedAgainstTheReciprocalOfTheBase(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 1.3);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -2.3, -1.1, -0.4, 0.4, 1.1, 2.3 })
{
var want = original.Substitute("x", at).EvalNumerical();
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}");
}
}
}
}
Loading