From 67c4c5f8fcb93194b10a6ed2dec64a52dc33f11c Mon Sep 17 00:00:00 2001 From: GiAnG Date: Mon, 7 Sep 2026 01:12:11 +0800 Subject: [PATCH] fix: normalize Log(x, e) to Ln(x) during simplify --- .../boxed-expression/simplify.ts | 8 ++- src/compute-engine/symbolic/simplify-log.ts | 52 +++++++++++++------ test/compute-engine/simplify.test.ts | 18 +++++++ 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/src/compute-engine/boxed-expression/simplify.ts b/src/compute-engine/boxed-expression/simplify.ts index d2be27cb..eb8a0bca 100644 --- a/src/compute-engine/boxed-expression/simplify.ts +++ b/src/compute-engine/boxed-expression/simplify.ts @@ -801,8 +801,12 @@ function simplifyOperands( simplifiedOps.push(full(x, i)); // Simplify Ln/Log operands within Add/Multiply to enable term cancellation // (e.g., ln(x^3) -> 3*ln(x) so that ln(x^3) - 3*ln(x) = 0) - // Only simplify Ln (natural log), not Log (which may lose base info) - else if (x.operator === 'Ln') simplifiedOps.push(full(x, i)); + // Log with base e is safe to simplify: log_e(x) -> ln(x) + else if ( + x.operator === 'Ln' || + (isFunction(x, 'Log') && x.op2 && isSymbol(x.op2, 'ExponentialE')) + ) + simplifiedOps.push(full(x, i)); // Simplify Abs operands to enable cancellation // (e.g., |xy| -> |x||y| so that |xy| - |x||y| = 0) // Also handle Negate(Abs(...)) which appears in subtraction expressions diff --git a/src/compute-engine/symbolic/simplify-log.ts b/src/compute-engine/symbolic/simplify-log.ts index ae23b3a2..985568e8 100644 --- a/src/compute-engine/symbolic/simplify-log.ts +++ b/src/compute-engine/symbolic/simplify-log.ts @@ -7,6 +7,11 @@ import { import { toBigint } from '../boxed-expression/numerics.js'; import { logarithmAtExceptionalPoint } from '../boxed-expression/logarithm.js'; +/** Whether `logBase` is Euler's number — `Log(x, e)` is `Ln(x)`. */ +function isNaturalLogBase(base: Expression): boolean { + return sym(base) === 'ExponentialE'; +} + /** * Logarithm simplification rules consolidated from simplify-rules.ts. * Handles ~30 patterns for simplifying Ln and Log expressions. @@ -242,6 +247,11 @@ function simplifyLogCore(x: Expression): RuleStep | undefined { return { value: special, because: 'log at an exceptional point' }; } + // log_e(x) -> ln(x): base-e logarithm is the natural logarithm + if (isNaturalLogBase(logBase)) { + return { value: ce._fn('Ln', [arg]), because: 'log_e(x) -> ln(x)' }; + } + // log_c(c) -> 1 (an infinite base was answered above: `∞/∞` is NaN) if (arg.isSame(logBase) && logBase.isInfinity !== true) { return { value: ce.One, because: 'log_c(c) -> 1' }; @@ -659,27 +669,39 @@ function simplifyLogCore(x: Expression): RuleStep | undefined { innerTerm.op1 && innerTerm.op2 ) { - const baseKey = JSON.stringify(innerTerm.op2.json); - if (!logTerms.has(baseKey)) { - logTerms.set(baseKey, []); + if (isNaturalLogBase(innerTerm.op2)) { + lnTerms.push({ + index: i, + arg: innerTerm.op1, + positive: false, + }); + } else { + const baseKey = JSON.stringify(innerTerm.op2.json); + if (!logTerms.has(baseKey)) { + logTerms.set(baseKey, []); + } + logTerms.get(baseKey)!.push({ + index: i, + arg: innerTerm.op1, + base: innerTerm.op2, + positive: false, + }); } - logTerms.get(baseKey)!.push({ - index: i, - arg: innerTerm.op1, - base: innerTerm.op2, - positive: false, - }); } } // Direct Log term else if (isFunction(term, 'Log') && term.op1 && term.op2) { - const baseKey = JSON.stringify(term.op2.json); - if (!logTerms.has(baseKey)) { - logTerms.set(baseKey, []); + if (isNaturalLogBase(term.op2)) { + lnTerms.push({ index: i, arg: term.op1, positive: true }); + } else { + const baseKey = JSON.stringify(term.op2.json); + if (!logTerms.has(baseKey)) { + logTerms.set(baseKey, []); + } + logTerms + .get(baseKey)! + .push({ index: i, arg: term.op1, base: term.op2, positive: true }); } - logTerms - .get(baseKey)! - .push({ index: i, arg: term.op1, base: term.op2, positive: true }); } } diff --git a/test/compute-engine/simplify.test.ts b/test/compute-engine/simplify.test.ts index 389a1736..4f17c667 100755 --- a/test/compute-engine/simplify.test.ts +++ b/test/compute-engine/simplify.test.ts @@ -1516,6 +1516,24 @@ describe('LOGARITHM COMBINATION RULES', () => { expect(simplify('\\ln(x) + \\ln(y) + z')).toMatchInlineSnapshot( `["Add", "z", ["Ln", ["Multiply", "x", "y"]]]` )); + + test('log_e(x) -> ln(x)', () => + expect(simplify('\\log_e(x)')).toMatchInlineSnapshot(`["Ln", "x"]`)); + + test('log_e(x) + log_e(x) = 2*ln(x)', () => + expect(simplify('\\log_e(x) + \\log_e(x)')).toMatchInlineSnapshot( + `["Multiply", 2, ["Ln", "x"]]` + )); + + test('log_e(x) + log_e(y) = ln(xy)', () => + expect(simplify('\\log_e(x) + \\log_e(y)')).toMatchInlineSnapshot( + `["Ln", ["Multiply", "x", "y"]]` + )); + + test('ln(x) + log_e(y) = ln(xy)', () => + expect(simplify('\\ln(x) + \\log_e(y)')).toMatchInlineSnapshot( + `["Ln", ["Multiply", "x", "y"]]` + )); }); describe('INDETERMINATE FORMS', () => {