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 @@ -2155,6 +2155,21 @@ secant too now, and the integrand is `sgn(cos(x)) (1 + u^2)^(3/2)/(a + b u)^2` i
| `"sec(x)^7/(a + b*tan(x))^3".ToEntity().Integrate("x")` | `integral(...)` | 1,253 characters |
| `"sec(x)/(a + b*cot(x))".ToEntity().Integrate("x")` | `integral(...)` | 571 characters |

### A quotient by a radical sum with a constant conjugate product is written over the conjugate

**Answers where there were none.** `x^4/e^asech(a x)` was declined, and so were the powers of `x` below it, Rubi's
7.5.2. `e^asech(a x)` arrives as `1/(a x) + sqrt(1/(a x)^2 - 1)`, whose product with its conjugate is 1, so the
quotient is `x^m (1/(a x) - sqrt(1/(a x)^2 - 1))`, a sum with no quotient left. `(A + B sqrt(R))(A - B sqrt(R)) =
A^2 - B^2 R` holds on every branch, so the rewrite holds wherever the quotient is defined. Only for a constant
product, and only for the question asked: by parts against `asech` or `acsch` asks a quotient by the same sum, which
the rules below answer as written ([#233](https://github.com/asc-community/AngouriMath/issues/233)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"x^4/e^asech(a*x)".ToEntity().Integrate("x")` | `integral(...)` | 284 characters |
| `"x/e^asech(a*x)".ToEntity().Integrate("x")` | `integral(...)` | 268 characters |
| `"1/(e^asech(a*x)*x)".ToEntity().Integrate("x")` | `integral(...)` | 366 characters |

### A sum with the root of a quadratic below the bar is cleared by its conjugate

**Answers where there were none.** `1/(a c + b c x^2 + d sqrt(a + b x^2))` was declined: Euler's substitution
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,74 @@ private static bool IsADerivativeOfAFactorBelowTheBar(Entity expr, Entity.Variab
}
}

/// <summary>
/// A quotient by <c>A + B sqrt(R)</c> whose conjugate product <c>A^2 - B^2 R</c> is a constant
/// <c>c</c>, written over the conjugate: <c>N/(A + B sqrt(R)) = N (A - B sqrt(R))/c</c>, a sum
/// of terms without the quotient. <c>e^asech(a x)</c> arrives as
/// <c>1/(a x) + sqrt(1/(a x)^2 - 1)</c>, whose conjugate product is 1, so <c>x^m/e^asech(a x)</c>
/// is <c>x^m (1/(a x) - sqrt(1/(a x)^2 - 1))</c>. Rubi's 7.5.2.
/// https://github.com/asc-community/AngouriMath/issues/233
/// </summary>
/// <remarks>
/// The search took the quotient as written, a root below the bar beside a pole, and ran past
/// the budget. The rewrite is the identity <c>(A + B sqrt(R))(A - B sqrt(R)) = A^2 - B^2 R</c>,
/// with <c>sqrt(R)^2 = R</c> on every branch, so it holds wherever the quotient is defined.
/// Only for a constant product: otherwise the quotient by it is the harder question.
/// </remarks>
internal static Entity? SolveByTheConjugateOfARadicalSumBelowTheBar(Entity expr, Entity.Variable x, bool integrateByParts)
{
// Only the question asked: by parts against asech or acsch asks a quotient by the same sum,
// which the routes below answer as written and not as the sum over the conjugate.
if (!Integration.AnsweringTheQuestionAsked || expr is not Divf)
return null;
var factors = FactorsOfTheIntegrand(expr);
for (var chosen = 0; chosen < factors.Count; chosen++)
{
var (factor, underneath) = factors[chosen];
if (!underneath || factor is not (Sumf or Minusf) || !factor.ContainsNode(x))
continue;
// A + B sqrt(R), the root's terms gathered.
Entity a = Number.Integer.Zero, b = Number.Integer.Zero;
Entity? radicand = null;
var readable = true;
foreach (var term in Sumf.LinearChildren(factor))
{
Entity? root = null;
Entity rest = Number.Integer.One;
foreach (var part in Mulf.LinearChildren(term))
{
if (root is null && part is Powf(var q, Number.Rational half) && half == Number.Rational.Create(1, 2) && q.ContainsNode(x))
root = q;
else
rest *= part;
}
if (root is null)
a += term;
else if (radicand is null || radicand == root)
{
radicand = root;
b += rest;
}
else
readable = false;
}
if (!readable || radicand is null || a == Number.Integer.Zero || b.ContainsNode(x) && b.Nodes.Any(node => node is Powf(_, Number.Rational { ERational.Denominator: var den }) && !den.Equals(EInteger.One)))
continue;
var product = a * a - b * b * radicand;
// A constant: the same at a point and identically so.
var atAPoint = Functions.PartialFractions.Bare(product.Substitute(x, Number.Rational.Create(37, 23)).Simplify());
if (atAPoint.ContainsNode(x) || VanishesIdentically(atAPoint) || !VanishesIdentically(product - atAPoint))
continue;
Entity rest2 = Number.Integer.One;
for (var other = 0; other < factors.Count; other++)
if (other != chosen)
rest2 = factors[other].Underneath ? rest2 / factors[other].Factor : rest2 * factors[other].Factor;
var rewritten = rest2 * (a - b * MathS.Sqrt(radicand)) / atAPoint;
return Integration.ComputeAsAQuestionOfItsOwn(rewritten, x, integrateByParts);
}
return null;
}

/// <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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,8 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
if ((answer = IndefiniteIntegralSolver.SolveAFunctionOfTheHyperbolicTangent(expr, x, integrateByParts)) is { }) return answer;
// A power of a quotient of linears over a half-odd power of their product, by Liouville's ansatz.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfAQuotientOfLinearsOverAHalfOddPowerOfTheirProduct(expr, x)) is { }) return answer;
// A quotient by a radical sum whose conjugate product is a constant, over the conjugate.
if ((answer = IndefiniteIntegralSolver.SolveByTheConjugateOfARadicalSumBelowTheBar(expr, x, integrateByParts)) is { }) return answer;
// A power of an exponential with a positive base is the exponential of the product,
// exactly, and only that spelling is one the exponential rules read.
if ((answer = IndefiniteIntegralSolver.SolveByFlatteningAPowerOfAnExponential(expr, x, integrateByParts)) is { }) return answer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// 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 <c>A + B sqrt(R)</c> whose conjugate product is a constant, over the conjugate:
/// <c>e^asech(a x)</c> arrives as <c>1/(a x) + sqrt(1/(a x)^2 - 1)</c>, whose conjugate product
/// is 1, and <c>x^m/e^asech(a x)</c> timed out. Rubi's 7.5.2.
/// <a href="https://github.com/asc-community/AngouriMath/issues/233">#233</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class AQuotientByARadicalSumWithAConstantConjugateProductIntegralTest
{
[Theory]
[InlineData("x/e^asech(a*x)")]
[InlineData("x^3/e^asech(a*x)")]
[InlineData("1/(e^asech(a*x)*x)")]
public void OverTheConjugate(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
var text = integral.Stringize();
Assert.DoesNotContain("integral(", text);
Entity Pinned(Entity e) => e.Substitute("a", 1.3);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
// Where asech(1.3 x) is real: 0 < x < 1/1.3.
foreach (var at in new[] { 0.05, 0.1, 0.2, 0.35, 0.5, 0.7 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
if (want.IsNaN || got.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}, where the integrand is {want}");
}
Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}");
}

// By parts against asech or acsch asks a quotient by the same sum; written over the conjugate
// below the top, these ran past the budget where they are answered at once as written.
[Theory]
[InlineData("x^3*asech(a*x)^2")]
[InlineData("x*(a+b*asech(c*x))^2")]
[InlineData("x^3*(a+b*acsch(c*x))^2")]
public void LeavesTheQuotientsByPartsAsksAsWritten(string integrand)
=> Assert.DoesNotContain("integral(", integrand.ToEntity().Integrate("x").Stringize());
}
}
Loading