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 @@ -5858,6 +5858,22 @@ below are one indeterminate. Rubi's 7.5.1 and 7.6.1
| `"x^2 * asech(c * x)".ToEntity().Integrate("x")` | `integral(...)`, the same | an antiderivative |
| `"x / sqrt(1 + 1/(c*x)^2)".ToEntity().Integrate("x")` | `integral(x / sqrt(1 + 1 / (c * x) ^ 2), x)` | an antiderivative, `provided c^2 > 0` |

### The sign and the modulus of an expression with a logarithm in it are differentiated where the logarithm is real

**Answers where there were none.** The derivative of `sgn(f)` and of `abs(f)` was left unevaluated where `f`
holds a logarithm, which is not real on the whole line. A logarithm to a positive constant base is real where
its argument is positive, and is read now as a root's base is (see the entry below): the real-line rule, `0`
and `sgn(f) f'`, where every such argument and base is positive, and the derivative left as written elsewhere.
The antiderivative of Rubi's 3.5 `sqrt(ln(a x^n)^2)/x`, `sgn(ln(a x^n)) ln(a x^n)^2/(2 n)`, which is unchanged,
could not be differentiated back anywhere
([#1186](https://github.com/asc-community/AngouriMath/issues/1186)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sgn(ln(x))".ToEntity().Differentiate("x")` | `derivative(sgn(ln(x)), x)` | `piecewise(0 provided (x > 0 and not ln(x) = 0), (derivative(sgn(ln(x)), x)) provided True)` |
| `"abs(ln(x))".ToEntity().Differentiate("x")` | `derivative(abs(ln(x)), x)` | `sgn(ln(x)) * 1 / x` where `x > 0` and `ln(x)` is not `0`, the derivative as written elsewhere |
| `"abs(ln(x) - 1)".ToEntity().Differentiate("x")` | `derivative(abs(ln(x) - 1), x)` | `sgn(ln(x) - 1) * 1 / x` where `x > 0` and `ln(x) - 1` is not `0`, the derivative as written elsewhere |

### The sign and the modulus of an expression in roots are differentiated where the roots are real

**Answers where there were none.** The derivative of `sgn(f)` and of `abs(f)` was left unevaluated
Expand Down
22 changes: 17 additions & 5 deletions Sources/AngouriMath/Functions/TreeAnalyzer/RealValued.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,14 @@ internal static bool IsRealValued(Entity expr, Variable x)
/// <summary>
/// Where an expression that is not real everywhere is real on an open set: the
/// conjunction of <c>b &gt; 0</c> over the bases <c>b</c> of its powers to a real
/// exponent that is not whole, or <see langword="null"/> where it has no such power or
/// exponent that is not whole and the arguments <c>b</c> of its logarithms to a positive
/// constant base, or <see langword="null"/> where it has no such power or logarithm or
/// a shape <see cref="IsRealValued"/> does not list. <c>sqrt(1 + x) + 1/2</c> is real
/// where <c>1 + x &gt; 0</c>, and <c>x^(1/3) - 1</c> where <c>x &gt; 0</c>, which is all
/// that is claimed: with the downcasting off a cube root of a negative is the principal
/// one. Strict for the reason <see cref="IsRealValued"/> reads only a strict bound: a
/// derivative needs the expression real on an interval around the point.
/// where <c>1 + x &gt; 0</c>, <c>x^(1/3) - 1</c> where <c>x &gt; 0</c>, and
/// <c>ln(2 x^3)</c> where <c>2 x^3 &gt; 0</c>, which is all that is claimed: with the
/// downcasting off a cube root of a negative is the principal one. Strict for the reason
/// <see cref="IsRealValued"/> reads only a strict bound: a derivative needs the
/// expression real on an interval around the point.
/// </summary>
/// <remarks>
/// Asked by differentiation where the sign or the modulus of such an expression is
Expand Down Expand Up @@ -134,6 +136,16 @@ private static bool GatherTheBasesOfRoots(Entity expr, Variable x, List<Entity>
if (!bases.Contains(@base))
bases.Add(@base);
return true;
// A logarithm to a positive constant base is real where its argument is positive,
// and so is gathered as a root's base is: sgn(ln(a x^n)), which the antiderivative
// of sqrt(ln(a x^n)^2)/x carries, is flat where a x^n > 0.
case Logf(var logBase, var antilogarithm) when !logBase.ContainsNode(x)
&& IsARealNumber(logBase.Evaled) && ((Complex)logBase.Evaled).RealPart.IsPositive:
if (!GatherTheBasesOfRoots(antilogarithm, x, bases))
return false;
if (!bases.Contains(antilogarithm))
bases.Add(antilogarithm);
return true;
case Sinf or Cosf or Tanf or Cotanf or Secantf or Cosecantf
or Arctanf or Arccotanf or Signumf or Erff or Erfcf or Erfif or Eif or Sif or Shif:
return GatherTheBasesOfRoots(expr.DirectChildren[0], x, bases);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//
// 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;
using static AngouriMath.Entity;

namespace AngouriMath.Tests.Calculus
{
/// <summary>
/// The sign and the modulus of an expression with a logarithm in it are differentiated as on
/// the real line where the logarithm's argument is positive, as they are where the bases of
/// its roots are, and left unevaluated elsewhere. The antiderivative of Rubi's
/// <c>sqrt(ln(a x^n)^2)/x</c> carries <c>sgn(ln(a x^n))</c>, and could not be checked by
/// differentiating it back at any point.
/// <a href="https://github.com/asc-community/AngouriMath/issues/1186">#1186</a>
/// </summary>
[Trait("Area", "Calculus")]
public sealed class ASignOfALogarithmIsFlatWhereTheLogarithmIsRealTest
{
[Theory]
[InlineData("sgn(ln(x))", "3", "0")]
[InlineData("sgn(ln(x))", "1/2", "0")]
[InlineData("sgn(ln(2 * x^3))", "1/4", "0")]
[InlineData("sgn(ln(x) - 1/2)", "5", "0")]
[InlineData("abs(ln(x))", "3", "1/3")]
[InlineData("abs(ln(x))", "1/2", "-2")]
[InlineData("abs(ln(sqrt(x) + 1))", "4", "1/12")]
public void WhereTheArgumentIsPositiveTheRealLineRuleHolds(string expression, string at, string derivative)
=> Assert.Equal(derivative.ToEntity().Evaled,
expression.ToEntity().Differentiate("x").Substitute("x", at.ToEntity()).Evaled);

// To another positive base as well: |log_2(x)|' is -1/(x ln 2) below 1.
[Fact]
public void ToAnotherPositiveBase()
{
var got = (double)((Number.Complex)"abs(log(2, x))".ToEntity().Differentiate("x").Substitute("x", "1/4".ToEntity()).EvalNumerical()).RealPart;
Assert.Equal(-4 / Math.Log(2), got, 12);
}

// Where the argument is negative the logarithm is off the real line, and sgn(z) is
// z / |z|, which turns with z: nothing is claimed there.
[Theory]
[InlineData("sgn(ln(x))", "-2")]
[InlineData("abs(ln(x - 1))", "1/2")]
[InlineData("sgn(ln(sqrt(x) - 1))", "1/4")]
public void WhereTheArgumentIsNegativeNothingIsClaimed(string expression, string at)
{
var derivative = expression.ToEntity().Differentiate("x").Substitute("x", at.ToEntity()).Evaled;
Assert.IsNotType<Number.Integer>(derivative);
Assert.Contains("derivative(", derivative.Stringize());
}

// A symbol is complex, and so is a base off the real line or one in x: those are left
// as they were.
[Theory]
[InlineData("sgn(ln(a * x))")]
[InlineData("abs(ln(x) * a)")]
[InlineData("sgn(log(i, x))")]
[InlineData("sgn(log(x, 2))")]
public void WhatIsNotRealOnAnIntervalIsLeftUnevaluated(string expression)
=> Assert.IsType<Derivativef>(expression.ToEntity().Differentiate("x"));

[Theory(Timeout = 30000)]
[InlineData("derivative(sgn(ln(x)), x)")]
[InlineData("derivative(abs(ln(x)), x)")]
[InlineData("derivative(sgn(ln(x)) * x, x, 2)")]
public void TheDerivativeNodeOfSuchASignTerminates(string expression)
{
var entity = expression.ToEntity();
Assert.NotNull(entity.Simplify());
Assert.NotNull(entity.Evaled);
}

[Theory]
[InlineData("sqrt(ln(a*x^n)^2)/x")]
[InlineData("sqrt(ln(x)^2)/x")]
[InlineData("abs(ln(a*x))/x")]
public void AnAntiderivativeCarryingSuchASignDifferentiatesBack(string integrand)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("n", 2.3);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
var compared = 0;
foreach (var at in new[] { 0.11, 0.31, 0.57, 0.83, 1.29, 1.77, 2.41, 5.7 })
{
var want = original.Substitute("x", at).EvalNumerical();
var got = derivative.Substitute("x", at).EvalNumerical();
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)),
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
Assert.Equal(8, compared);
}
}
}
Loading