@@ -40,6 +40,28 @@ export function simplifyLog(x: BoxedExpression): RuleStep | undefined {
4040 return { value : ce . PositiveInfinity , because : 'ln(+inf) -> +inf' } ;
4141 }
4242
43+ // ln(p/q) -> ln(p) - ln(q) for positive rational p/q (not integer)
44+ if ( arg . operator === 'Rational' && arg . isRational === true && arg . isInteger === false ) {
45+ const j = arg . json ;
46+ if ( Array . isArray ( j ) && j [ 0 ] === 'Rational' ) {
47+ const p = j [ 1 ] as number ;
48+ const q = j [ 2 ] as number ;
49+ if ( p > 0 && q > 0 ) {
50+ if ( p === 1 ) {
51+ // ln(1/q) -> -ln(q)
52+ return {
53+ value : ce . _fn ( 'Ln' , [ ce . number ( q ) ] ) . neg ( ) ,
54+ because : 'ln(1/q) -> -ln(q)' ,
55+ } ;
56+ }
57+ return {
58+ value : ce . _fn ( 'Ln' , [ ce . number ( p ) ] ) . sub ( ce . _fn ( 'Ln' , [ ce . number ( q ) ] ) ) ,
59+ because : 'ln(p/q) -> ln(p) - ln(q)' ,
60+ } ;
61+ }
62+ }
63+ }
64+
4365 // ln(x^n) -> n*ln(x) when x >= 0 or n is odd or n is irrational
4466 if ( arg . operator === 'Power' && isBoxedFunction ( arg ) ) {
4567 const base = arg . op1 ;
@@ -275,6 +297,29 @@ export function simplifyLog(x: BoxedExpression): RuleStep | undefined {
275297 because : 'log_c(x^n) -> n*log_c(|x|) when n even' ,
276298 } ;
277299 }
300+ // log_c(x^{p/q}) for non-integer rational p/q
301+ if ( exp . isRational === true && exp . isInteger === false ) {
302+ const j = exp . json ;
303+ if ( Array . isArray ( j ) && j [ 0 ] === 'Rational' ) {
304+ const p = j [ 1 ] as number ;
305+ const q = j [ 2 ] as number ;
306+ // q even: x >= 0 implied (even root), no |x| needed
307+ // q odd, p odd: preserves sign, no |x| needed
308+ // q odd, p even: x^{p/q} is non-negative, need |x|
309+ if ( q % 2 === 0 || p % 2 !== 0 ) {
310+ return {
311+ value : exp . mul ( ce . _fn ( 'Log' , [ powerBase , logBase ] ) ) ,
312+ because : 'log_c(x^{p/q}) -> (p/q)*log_c(x)' ,
313+ } ;
314+ }
315+ return {
316+ value : exp . mul (
317+ ce . _fn ( 'Log' , [ ce . _fn ( 'Abs' , [ powerBase ] ) , logBase ] )
318+ ) ,
319+ because : 'log_c(x^{p/q}) -> (p/q)*log_c(|x|) when p even' ,
320+ } ;
321+ }
322+ }
278323 }
279324 }
280325
@@ -361,6 +406,16 @@ export function simplifyLog(x: BoxedExpression): RuleStep | undefined {
361406 because : 'log_{1/c}(a) -> -log_c(a)' ,
362407 } ;
363408 }
409+ // Same rule for Rational(1, q): log_{1/q}(a) -> -log_q(a)
410+ if ( logBase . operator === 'Rational' ) {
411+ const bj = logBase . json ;
412+ if ( Array . isArray ( bj ) && bj [ 0 ] === 'Rational' && bj [ 1 ] === 1 ) {
413+ return {
414+ value : ce . _fn ( 'Log' , [ arg , ce . number ( bj [ 2 ] as number ) ] ) . neg ( ) ,
415+ because : 'log_{1/c}(a) -> -log_c(a)' ,
416+ } ;
417+ }
418+ }
364419 }
365420
366421 // Handle Power with e and Ln
@@ -757,6 +812,46 @@ export function simplifyLog(x: BoxedExpression): RuleStep | undefined {
757812 because : 'ln(a) / log_c(a) -> ln(c)' ,
758813 } ;
759814 }
815+
816+ // ln(a) / ln(b) -> k when a = b^k for positive integers a, b
817+ if (
818+ num . operator === 'Ln' &&
819+ isBoxedFunction ( num ) &&
820+ denom . operator === 'Ln' &&
821+ isBoxedFunction ( denom )
822+ ) {
823+ const a = num . op1 ;
824+ const b = denom . op1 ;
825+ if (
826+ a &&
827+ b &&
828+ a . isInteger === true &&
829+ b . isInteger === true &&
830+ a . isPositive === true &&
831+ b . isPositive === true
832+ ) {
833+ const aVal = a . re ;
834+ const bVal = b . re ;
835+ if (
836+ Number . isFinite ( aVal ) &&
837+ Number . isFinite ( bVal ) &&
838+ bVal > 1 &&
839+ aVal > 0
840+ ) {
841+ // Check if a = b^k for some integer k
842+ // Use Math.round to handle floating-point imprecision,
843+ // then verify with exact integer exponentiation
844+ const kRaw = Math . log ( aVal ) / Math . log ( bVal ) ;
845+ const k = Math . round ( kRaw ) ;
846+ if ( Math . abs ( kRaw - k ) < 1e-10 && Math . pow ( bVal , k ) === aVal ) {
847+ return {
848+ value : ce . number ( k ) ,
849+ because : 'ln(a)/ln(b) -> k when a = b^k' ,
850+ } ;
851+ }
852+ }
853+ }
854+ }
760855 }
761856 }
762857
0 commit comments