Simplify turns ln(e^x) into x, which is wrong wherever Im x leaves (-pi, pi]. The rule is
Patterns.Power.cs:158:
Logf(var any1, Powf(var any2, var any3)) => any3 * MathS.Log(any1, any2),
log_b(a^c) = c * log_b(a) needs a to be a positive real, or c to be real together with a branch
choice. Neither is required here.
Measured on master (5397c6f9), .NET 10:
"ln(e^x)".Simplify() => x
"log(2, 2^x)".Simplify() => x
eval(ln(e^(3*pi*i))) => 3.14159...i (pi*i, since e^(3*pi*i) is -1)
eval(3*pi*i) => 9.42477...i
So the two sides differ by 2*pi*i at that point. MathS.Settings.Codomain defaults to
Domain.Complex, which makes this unsound on the library's own default setting.
It is also unsound for a negative real base: log(2, 64) is 6, while 2 * log(2, -8) is
6 + 9.0647...i.
This is the half of #884 that was never fixed
#884 reported five rules of one shape — an
identity that holds on a strip or an interval, written as though it held everywhere. The four
inverse-trigonometric ones were fixed in
#885 and #884 was closed with them; this
logarithm rule was analysed in the same issue and left alone, so it now has no open issue of its own.
Filing it so it is not lost a second time.
The analysis there still holds, including the part that makes this hard to catch: at the shape
log(2, x^2) the rewritten form currently loses the complexity contest and the expression is left
alone. The wrong candidate is still generated, so a neighbouring expression where it happens to be
shorter gets the wrong answer while this one does not — the PolynomialLongDivision failure mode from
#800.
What the guard has to be
ln(a^c) = c * ln(a) exactly when Im(c * ln a) lands in (-pi, pi]. Sufficient and decidable:
a a positive real and c real — then c * ln a is real and the identity is unconditional;
- the reading is real analysis (
MathS.Settings.Codomain.Value is Domain.Real), which is how
Patterns.EqualityInequality.cs already asks this question, paired with IsKnownReal.
A symbolic exponent under the default complex reading is not decidable, so the honest answer there
is to leave the expression as written — the same treatment as the four rules of #884 and as
arctan(x) + arccotan(x) in #887: decide,
or decline.
The coverage cost needs measuring rather than guessing, and it is the reason this is filed rather
than fixed in the same breath. ln(e^x) -> x is common in solver output, so declining it by default
may move casbench, rootcheck and the suite. Worth knowing before choosing between the guard above
and a narrower one. The opposite direction, e^(ln z) = z, is unconditional for z != 0 and is not in
question here.
Related, and deliberately not bundled
Patterns.Power.cs:159, Logf(x, x) => 1 provided x > 0, is the same file disagreeing with itself
about whether these rules need guarding — but its condition is a different question. log_x(x) is
ln x / ln x, which is 1 for every x outside {0, 1} including negative ones, while the attached
x > 0 matches Logf's declared DomainCondition. Whether the declared domain or the evaluation
is authoritative is #721, recorded as
needing a decision under #890. It should be
settled there, not by a guard written here.
How it was found
Written up first in #884 by reading the rules rather than sampling them. work/boundcheck now confirms
it from the other side: composing every unary node with every other and comparing at points where an
assumption fails, ln(e^x) and log(2, 2^x) are two of its four remaining disagreements, both at
x = 3*pi*i. All four sampling harnesses stay green — simpsweep samples real points, where this
rule is correct.
Simplifyturnsln(e^x)intox, which is wrong whereverIm xleaves(-pi, pi]. The rule isPatterns.Power.cs:158:log_b(a^c) = c * log_b(a)needsato be a positive real, orcto be real together with a branchchoice. Neither is required here.
Measured on
master(5397c6f9), .NET 10:So the two sides differ by
2*pi*iat that point.MathS.Settings.Codomaindefaults toDomain.Complex, which makes this unsound on the library's own default setting.It is also unsound for a negative real base:
log(2, 64)is6, while2 * log(2, -8)is6 + 9.0647...i.This is the half of #884 that was never fixed
#884 reported five rules of one shape — an
identity that holds on a strip or an interval, written as though it held everywhere. The four
inverse-trigonometric ones were fixed in
#885 and #884 was closed with them; this
logarithm rule was analysed in the same issue and left alone, so it now has no open issue of its own.
Filing it so it is not lost a second time.
The analysis there still holds, including the part that makes this hard to catch: at the shape
log(2, x^2)the rewritten form currently loses the complexity contest and the expression is leftalone. The wrong candidate is still generated, so a neighbouring expression where it happens to be
shorter gets the wrong answer while this one does not — the
PolynomialLongDivisionfailure mode from#800.
What the guard has to be
ln(a^c) = c * ln(a)exactly whenIm(c * ln a)lands in(-pi, pi]. Sufficient and decidable:aa positive real andcreal — thenc * ln ais real and the identity is unconditional;MathS.Settings.Codomain.Value is Domain.Real), which is howPatterns.EqualityInequality.csalready asks this question, paired withIsKnownReal.A symbolic exponent under the default complex reading is not decidable, so the honest answer there
is to leave the expression as written — the same treatment as the four rules of #884 and as
arctan(x) + arccotan(x)in #887: decide,or decline.
The coverage cost needs measuring rather than guessing, and it is the reason this is filed rather
than fixed in the same breath.
ln(e^x) -> xis common in solver output, so declining it by defaultmay move
casbench,rootcheckand the suite. Worth knowing before choosing between the guard aboveand a narrower one. The opposite direction,
e^(ln z) = z, is unconditional forz != 0and is not inquestion here.
Related, and deliberately not bundled
Patterns.Power.cs:159,Logf(x, x) => 1 provided x > 0, is the same file disagreeing with itselfabout whether these rules need guarding — but its condition is a different question.
log_x(x)isln x / ln x, which is1for everyxoutside{0, 1}including negative ones, while the attachedx > 0matchesLogf's declaredDomainCondition. Whether the declared domain or the evaluationis authoritative is #721, recorded as
needing a decision under #890. It should be
settled there, not by a guard written here.
How it was found
Written up first in #884 by reading the rules rather than sampling them.
work/boundchecknow confirmsit from the other side: composing every unary node with every other and comparing at points where an
assumption fails,
ln(e^x)andlog(2, 2^x)are two of its four remaining disagreements, both atx = 3*pi*i. All four sampling harnesses stay green —simpsweepsamples real points, where thisrule is correct.