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
16 changes: 16 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,22 @@ the working digits, or `MathS.Settings.PrecisionErrorZeroRange` where a caller h
still that integer, so `e^(-123.456)` is 0 as it was.
[#1338](https://github.com/asc-community/AngouriMath/issues/1338).

### A logarithm of nested powers of one linear is named as a multiple of the linear's logarithm

**Answers where there were none, and shorter ones.** Rubi's 3.3 `(g + h x)^2 (a + b ln(c (d (e + f x)^p)^q))^3` was declined
after eleven seconds and twelve gigabytes. The logarithm's derivative is `p q f/(e + f x)` on any branch, so it is
`p q ln(e + f x) + K`, with `K` constant wherever it is continuous. Named so, the integrand is a polynomial times a power
of `ln(e + f x)`, and the answer writes the logarithm back as it was. The square and the cube beside a linear come out
shorter. The first power beside a quadratic comes out a little longer
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(g+h*x)^2*(a+b*ln(c*(d*(k+f*x)^p)^q))^3".ToEntity().Integrate("x")` | `integral(...)` | 1,660 characters, where before this change master declined it after 11 s and 12 GB |
| `"(g+h*x)^2*(a+b*ln(c*(d*(k+f*x)^p)^q))^2".ToEntity().Integrate("x")` | `integral(...)` | 1,051 characters, where before this change master gave 2,048 |
| `"(g+h*x)*(a+b*ln(c*(d*(k+f*x)^p)^q))^3".ToEntity().Integrate("x")` | `integral(...)` | 702 characters, where before this change master gave 2,569 |
| `"(g+h*x)^2*(a+b*ln(c*(d*(k+f*x)^p)^q))".ToEntity().Integrate("x")` | `integral(...)` | 631 characters, where before this change master gave 527 |

### A power of a power of a logarithm over x is integrated below the question asked too

**Answers where there were none.** Rubi's 3.5 `(ln(a x^n)^2)^p/x` was declined. Under `u = ln(a x^n)` it is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30090,6 +30090,69 @@ Entity Named(Entity value)
return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null;
}

/// <summary>
/// A logarithm of constants and nested powers of one linear <c>L</c>, <c>ln(c (d L^p)^q)</c>, is
/// <c>p q ln(L) + K</c> with <c>K</c> constant wherever it is continuous, since its derivative is
/// <c>p q L'/L</c> by the chain rule on any branch: named so, Rubi's 3.3
/// <c>(g + h x)^2 (a + b ln(c (d (e + f x)^p)^q))^3</c> is a polynomial times a power of
/// <c>ln(e + f x)</c>, answered in a quarter of a second, where as written it was declined after
/// eleven and twelve gigabytes. The answer is the generic case's, as
/// <see cref="SolveByNamingALogarithmLinearInTheVariable"/>'s is. Only a nested power, which the
/// rules for <c>ln(c L^n)</c> do not read. Asked as a question of its own.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </summary>
internal static Entity? SolveByNamingALogarithmOfNestedPowersOfALinear(Entity expr, Entity.Variable x, bool integrateByParts)
{
Logf? named = null;
Entity? linear = null;
Entity? times = null;
foreach (var node in expr.Nodes)
{
if (node is not Logf(var logBase, var argument) log || logBase != MathS.e || !argument.ContainsNode(x)
|| !argument.Nodes.Any(inner => inner is Powf(var raised, _) && raised.ContainsNode(x) && raised.Nodes.Any(deeper => deeper != raised && deeper is Powf(var b, _) && b.ContainsNode(x))))
continue;
Entity? found = null;
Entity? Exponent(Entity part)
{
if (!part.ContainsNode(x))
return Number.Integer.Zero;
if (TreeAnalyzer.TryGetPolyLinear(part, x, out var slope, out _) && !TreeAnalyzer.IsZero(slope))
{
if (found is null)
found = part;
return found == part ? Number.Integer.One : null;
}
switch (part)
{
case Mulf(var left, var right):
return Exponent(left) is { } l && Exponent(right) is { } r ? l + r : null;
case Divf(var above, var below):
return Exponent(above) is { } a && Exponent(below) is { } b ? a - b : null;
case Powf(var @base, var power) when !power.ContainsNode(x):
return Exponent(@base) is { } e ? power * e : null;
default:
return null;
}
}
if (Exponent(argument) is not { } exponent || found is null)
continue;
(named, linear, times) = (log, found, exponent.InnerSimplified);
break;
}
if (named is null || linear is null || times is null || TreeAnalyzer.IsZero(times))
return null;
var constant = Variable.CreateUnique(expr, "K");
var inTheLinear = times * MathS.Ln(linear) + constant;
var rewritten = expr.Substitute(named, inTheLinear);
if (rewritten == expr || rewritten.ContainsNode(named))
return null;
if (Integration.ComputeAsAQuestionOfItsOwn(rewritten, x, integrateByParts) is not { } answer)
return null;
// The sum as it was written stands for the logarithm again, where term by term it would be
// `p q ln(L) + (ln(c (d L^p)^q) - p q ln(L))`; K anywhere else is the difference.
return answer.Replace(node => node == inTheLinear ? named : node).Substitute(constant, named - times * MathS.Ln(linear));
}

/// <summary>
/// The sign of a real-valued function of <paramref name="x"/> among the factors of the
/// integrand is constant on every interval between its zeros, and goes in front of the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,9 @@ internal static bool ASumOverRootsWouldAnswer
// And a squared sine beside the squared cosine of its argument, the one they add up to:
// `e^(cos(x)^2 + sin(x)^2)` was declined, written with an x its value does not hold.
if ((answer = IndefiniteIntegralSolver.SolveByFoldingAPythagoreanPair(expr, x, integrateByParts)) is { }) return answer;
// A logarithm of nested powers of one linear, `ln(c (d (e + f x)^p)^q)`, named as `p q ln(e + f x)`
// plus a constant, before the rules that declined its cube beside a quadratic after twelve gigabytes.
if ((answer = IndefiniteIntegralSolver.SolveByNamingALogarithmOfNestedPowersOfALinear(expr, x, integrateByParts)) is { }) return answer;
// An arctangent over a power of d (1 +- i c x), under x = tan(u)/c, where 1 + i tan(u) is
// e^(i u)/cos(u): the rules below ran out of time on `(a + b atan(c x))^2/(d + i c d x)^2`.
if ((answer = IndefiniteIntegralSolver.SolveAnArctangentOverAPowerOfOnePlusICXUnderTheTangent(expr, x, integrateByParts)) is { }) return answer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// 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 System.Threading;
using System.Threading.Tasks;
using AngouriMath.Extensions;
using Xunit;

namespace AngouriMath.Tests.Calculus
{
/// <summary>
/// A logarithm of nested powers of one linear, <c>ln(c (d (e + f x)^p)^q)</c>, named as
/// <c>p q ln(e + f x) + K</c>, K constant where it is continuous: Rubi's 3.3
/// <c>(g + h x)^2 (a + b ln(c (d (e + f x)^p)^q))^3</c> was declined after eleven seconds and twelve
/// gigabytes, where in the linear's logarithm it is a polynomial times a power of it.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ALogarithmOfNestedPowersOfALinearIntegralTest
{
[Theory]
// Rubi 3.3:620, with k for Rubi's e.
[InlineData("(g + h*x)^2*(a + b*ln(c*(d*(k + f*x)^p)^q))^3")]
[InlineData("(g + h*x)*(a + b*ln(c*(d*(k + f*x)^p)^q))^3")]
[InlineData("(g + h*x)^2*(a + b*ln(c*(d*(k + f*x)^p)^q))^2")]
public void IsAnsweredWithTheLogarithmAsWritten(string integrand)
{
Entity? integral = null;
// A thread of its own, so that the guard is not spent waiting behind other tests' work.
var task = Task.Factory.StartNew(() => integral = integrand.ToEntity().Integrate("x"),
CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
Assert.True(task.Wait(LimitTermination.Guard), $"{integrand} did not finish within {LimitTermination.Guard}");
var text = integral!.Stringize();
Assert.DoesNotContain("integral(", text);
Assert.Contains("ln(c * (d * (k + f * x) ^ p) ^ q)", text);
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4).Substitute("d", 0.9)
.Substitute("f", 0.8).Substitute("g", 1.7).Substitute("h", 0.6).Substitute("k", 0.2).Substitute("p", 1.5).Substitute("q", 0.75);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { 0.3, 0.9, 1.4, 2.1, 3.3 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
Assert.False(got.IsNaN, $"d/dx of the antiderivative of {integrand} has no value at x = {at}, where the integrand is {want}");
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}");
}
}
}
}
Loading