镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
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
68 changes: 68 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,74 @@ read first.

---

## Unreleased — since 2.1.0

### At a glance

| Silent? | What | Was | Is |
|---|---|---|---|
| **silent** | `log(x, x)` simplified | `1 provided x > 0` — `NaN` at every negative `x`, and `1` at `x = 1` where the logarithm is `NaN` | `1 provided not x = 0 and not x = 1` |
| **silent** | `DomainCondition` of a logarithm, under the default reading | the real condition, so `log(-3, -3)` was declared undefined while evaluating to `1` | `not b = 0 and not b = 1 and not a = 0` |
| **silent** | `ln(x).DomainCondition` | `x > 0` | `not x = 0` |
| **silent** | `"x ^ n".Differentiate("x").Simplify()` | `x ^ n * n / x provided x > 0` — `NaN` at every negative `x` | `x ^ n * n / x provided not x = 0` |

### The logarithm's domain follows the reading, as every other node's already did

`Arcsinf`, `Arccosf`, `Arcsecantf` and `Arccosecantf` each state one condition over the reals and
another over the complex plane, and pick between them by the reading. `Logf` stated the real one as
though it were the only one, which is what
[#890](https://github.com/asc-community/AngouriMath/issues/890) reported as a contradiction: the
library gave an expression a value and declared it undefined at the same time.

```
log(-3, -3) 1 — unchanged
log(-3, -3).DomainCondition False -> True
ln(x).DomainCondition x > 0 -> not x = 0
```

Over the complex plane `log_b(a)` is `ln(a) / ln(b)`, which asks only that both logarithms exist
and that the denominator is not zero — no complex number has `0` as its exponential, so `a` and `b`
must be nonzero, and `ln(b) = 0` is `b = 1`. Negative and non-real arguments are admitted:
`log(2, -8)` is `3 + 4.53i` and was declared undefined.

**The real condition is still there and still reachable.** `expr.WithCodomain(Domain.Real)` gives
`x > 0 and not x = 1` for `log(x, x)` exactly as before; what changed is which of the two is the
default, and the default is now the same complex reading the evaluator has always used.

### `log_b(b) -> 1` carries the condition it has rather than one written into the rule

The rewrite asserted `provided x > 0`, and that was wrong in both directions at once — it withdrew
an answer that exists and kept one that does not:

```
"log(x, x)".Simplify() 1 provided x > 0 -> 1 provided not x = 0 and not x = 1
at x = -3 NaN -> 1 (the logarithm is 1)
at x = 1 1 -> NaN (the logarithm is NaN)
```

`boundcheck` reports one disagreement where it reported two. The survivor,
`ln(x) + ln(x+1) -> ln(x*(1+x))`, needs an assumption travelling with the variable and is
untouched here.

### The derivative of a symbolic power loses a condition it never needed

`d/dx x^n` goes through `ln(x) * 0`, and `anything * 0 = 0` carries the operand's domain condition.
That condition was the logarithm's real one, so the derivative was undefined at every negative `x`:

```
"x ^ n".Differentiate("x").Simplify() x ^ n * n / x provided x > 0
-> x ^ n * n / x provided not x = 0
at n = 3, x = -2.5 NaN -> 75/4 (and 3x^2 at -2.5 is 75/4)
```

The condition does not vanish: `ln(0) * 0` is `-oo * 0`, which is `NaN`, so `not x = 0` is kept.

From [#721](https://github.com/asc-community/AngouriMath/issues/721) and
[#890](https://github.com/asc-community/AngouriMath/issues/890), in PR
[#916](https://github.com/asc-community/AngouriMath/pull/916).

---

## 2.1.0 — since 2.0.0

Everything here landed **after** the 2.0.0 tag, so it is not in the package a reader of that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,29 @@ x.Evaled is Complex c

public partial record Logf
{
// Logarithm is undefined when:
// - base <= 0 or base = 1
// - antilogarithm <= 0
// For complex logarithms, we use the principal branch
private protected override Entity IntrinsicCondition =>
Base > 0 & !Base.EqualTo(1) & Antilogarithm > 0;
// Which of the two conditions below applies is the reading, exactly as it is
// for Arcsinf and its siblings. This node alone stated the real one as though
// it were the only one, so the library declared an expression undefined and
// gave it a value at the same time: log(-3, -3) evaluates to 1 while its
// DomainCondition was False.
//
// Over the reals the base must be positive and not 1, and the antilogarithm
// positive. Over the complex plane log_b(a) is ln(a)/ln(b), which asks only
// that both logarithms exist and that the denominator is not zero: no complex
// number has 0 as its exponential, so a and b must be nonzero, and ln(b) = 0
// is b = 1. Negative and non-real arguments are fine -- log(-3, -3) is 1 and
// log(2, -8) is 3 + 4.53i -- which is the whole of what the real condition was
// wrongly forbidding.
//
// The zero cases are stated rather than read off evaluation, which returns the
// extended-real -oo for ln(0) and so would call it defined. -oo is not a
// complex number, and taking it for one loses conditions that are needed
// downstream: `ln(x) * 0` is 0 only away from x = 0, since -oo * 0 is NaN.
// https://github.com/asc-community/AngouriMath/issues/721
private protected override Entity IntrinsicCondition =>
Codomain < Domain.Complex
? Base > 0 & !Base.EqualTo(1) & Antilogarithm > 0
: !Base.EqualTo(0) & !Base.EqualTo(1) & !Antilogarithm.EqualTo(0);

/// <inheritdoc/>
protected override Entity InnerSimplify(bool isExact) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,15 @@ expr is Powf(var @base, Integer { IsNegative: true } pow)
// https://github.com/asc-community/AngouriMath/issues/902
Logf(var any1, Powf(var any2, var any3))
when IsPositiveReal(any2) && MayBeTakenAsReal(any3) => any3 * MathS.Log(any1, any2),
Logf(var any1, var any1a) when any1 == any1a => new Providedf(1, any1 > 0),
// log_b(b) is 1 wherever it is defined at all, so the condition to carry is the
// node's own and not one written out here. Asserting `any1 > 0` stated the real
// reading inside the rule and was wrong in both directions at once: undefined at
// x = -3, where log(-3, -3) evaluates to 1, and defined at x = 1, where
// log(1, 1) is NaN. Reading the condition instead gets both, and follows the
// reading rather than fixing it.
// https://github.com/asc-community/AngouriMath/issues/721
Logf(var any1, var any1a) logarithm when any1 == any1a
=> new Providedf(1, logarithm.DomainCondition),
Logf(Divf(Integer(1), var any1), Divf(Integer(1), var any2)) => MathS.Log(any1, any2),
Logf(var any1, Divf(Integer(1), var any2)) => -MathS.Log(any1, any2),
Logf(Divf(Integer(1), var any1), var any2) => -MathS.Log(any1, any2),
Expand Down
9 changes: 7 additions & 2 deletions Sources/Tests/UnitTests/Calculus/DerivativeGapsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,14 @@ public void AntiderivativeOfAPowerDifferentiatesBackEverywhere()
}

// A genuinely symbolic exponent must still take the logarithmic rule, condition
// and all.
// and all. The condition is `not x = 0` and was `x > 0` until the logarithm's
// domain stopped being stated over the reals regardless of the reading: it comes
// from `ln(x) * 0`, which is 0 everywhere ln(x) has a value and NaN at x = 0,
// where -oo * 0 is indeterminate. The old condition also cost the answer at every
// negative x -- at n = 3, x = -2.5 the derivative is 75/4 and used to be NaN.
// https://github.com/asc-community/AngouriMath/issues/721
[Theory]
[InlineData("x ^ n", "x ^ n * n / x provided x > 0")]
[InlineData("x ^ n", "x ^ n * n / x provided not x = 0")]
[InlineData("2 ^ x", "ln(2) * 2 ^ x")]
// Compared as printed text: what is under test is that the logarithmic rule and
// its condition are still used, and the tree differs from the parsed expectation
Expand Down
61 changes: 61 additions & 0 deletions Sources/Tests/UnitTests/Common/SimplificationRegressionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1005,5 +1005,66 @@ public void SimplifyAndEvaluationAgreeOffTheRealLine(string input)
Assert.Equal(original.Substitute("x", "i").Evaled,
original.Simplify().Substitute("x", "i").Evaled);
}

// https://github.com/asc-community/AngouriMath/issues/721
// log_b(b) is 1 wherever it is defined, and the rewrite carried `provided x > 0` --
// the real-domain condition, written out inside the rule rather than read from the
// node. It was wrong in both directions at once: at x = -3 the logarithm is 1 and
// the simplification was NaN, and at x = 1 the logarithm is NaN and the
// simplification was 1.
[Theory]
[InlineData("-3")]
[InlineData("-1/2")]
[InlineData("2")]
[InlineData("1")]
[InlineData("0")]
public void ALogarithmOfItsOwnBaseKeepsItsValueOffThePositiveReals(string at)
{
var original = "log(x, x)".ToEntity();
var simplified = original.Simplify();
Assert.Equal(original.Substitute("x", at.ToEntity()).Evaled,
simplified.Substitute("x", at.ToEntity()).Evaled);
}

// The contradiction #890 reported, which is the same defect seen from the domain
// side: the library gave the expression a value and declared it undefined
// everywhere at the same time.
[Theory]
[InlineData("log(-3, -3)")]
[InlineData("log(2, -8)")]
[InlineData("log(-2, 8)")]
public void ALogarithmWithNegativeArgumentsIsNotDeclaredUndefined(string input)
{
var expression = input.ToEntity();
Assert.NotEqual(MathS.NaN, expression.Evaled);
Assert.Equal(Entity.Boolean.True, expression.DomainCondition.Evaled);
}

// Which of the two conditions applies is the reading, as it already was for arcsin
// and its siblings. A negative base is admitted over the complex plane and refused
// over the reals; 0 and 1 are refused under either.
[Theory]
[InlineData("-3", true)]
[InlineData("2", true)]
[InlineData("1", false)]
[InlineData("0", false)]
public void TheLogarithmsDomainOverTheComplexPlaneAdmitsANegativeBase(string at, bool defined)
{
var condition = "log(x, x)".ToEntity().DomainCondition;
Assert.Equal(defined ? Entity.Boolean.True : Entity.Boolean.False,
condition.Substitute("x", at.ToEntity()).Evaled);
}

[Theory]
[InlineData("-3", false)]
[InlineData("2", true)]
[InlineData("1", false)]
[InlineData("0", false)]
public void TheLogarithmsDomainOverTheRealsStillRefusesANegativeBase(string at, bool defined)
{
var condition = "log(x, x)".ToEntity().WithCodomain(Domain.Real).DomainCondition;
Assert.Equal(defined ? Entity.Boolean.True : Entity.Boolean.False,
condition.Substitute("x", at.ToEntity()).Evaled);
}
}
}
Loading