algebrite
Version:
Computer Algebra System in Coffeescript
567 lines (460 loc) • 13.1 kB
text/coffeescript
### Power function
Input: push Base
push Exponent
Output: Result on stack
###
DEBUG_POWER = false
Eval_power = ->
if DEBUG_POWER then debugger
push(cadr(p1))
Eval()
push(caddr(p1))
Eval()
power()
power = ->
save()
yypower()
restore()
yypower = ->
if DEBUG_POWER then debugger
n = 0
p2 = pop() # exponent
p1 = pop() # base
inputExp = p2
inputBase = p1
#debugger
if DEBUG_POWER
console.log("POWER: " + p1 + " ^ " + p2)
# first, some very basic simplifications right away
# 1 ^ a -> 1
# a ^ 0 -> 1
if (equal(p1, one) || iszero(p2))
if evaluatingAsFloats then push_double(1.0) else push(one)
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
# a ^ 1 -> a
if (equal(p2, one))
push(p1)
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
# -1 ^ -1 -> -1
if (isminusone(p1) and isminusone(p2))
if evaluatingAsFloats then push_double(1.0) else push(one)
negate()
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
# -1 ^ 1/2 -> i
if (isminusone(p1) and (isoneovertwo(p2)))
push(imaginaryunit)
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
# -1 ^ -1/2 -> -i
if (isminusone(p1) and isminusoneovertwo(p2))
push(imaginaryunit)
negate()
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
# -1 ^ rational
if (isminusone(p1) and !isdouble(p1) and isrational(p2) and !isinteger(p2) and ispositivenumber(p2) and !evaluatingAsFloats)
if DEBUG_POWER then console.log(" power: -1 ^ rational")
if DEBUG_POWER then console.log " trick: p2.q.a , p2.q.b " + p2.q.a + " , " + p2.q.b
if p2.q.a < p2.q.b
push_symbol(POWER)
push(p1)
push(p2)
list(3)
else
push_symbol(MULTIPLY)
push(p1)
push_symbol(POWER)
push(p1)
push_rational(p2.q.a.mod(p2.q.b), p2.q.b)
list(3)
list(3)
if DEBUG_POWER then console.log " trick applied : " + stack[tos-1]
# evaluates clock form into
# rectangular form. This seems to give
# slightly better form to some test results.
rect()
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
# both base and exponent are rational numbers?
if (isrational(p1) && isrational(p2))
if DEBUG_POWER then console.log(" power: isrational(p1) && isrational(p2)")
push(p1)
push(p2)
qpow()
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
# both base and exponent are either rational or double?
if (isnum(p1) && isnum(p2))
if DEBUG_POWER then console.log " power: both base and exponent are either rational or double "
if DEBUG_POWER then console.log("POWER - isnum(p1) && isnum(p2)")
push(p1)
push(p2)
dpow()
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
if (istensor(p1))
if DEBUG_POWER then console.log " power: istensor(p1) "
power_tensor()
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
# if we only assume variables to be real, then |a|^2 = a^2
# (if x is complex this doesn't hold e.g. i, which makes 1 and -1
if (car(p1) == symbol(ABS) && iseveninteger(p2) and !iszero(get_binding(symbol(ASSUME_REAL_VARIABLES))))
if DEBUG_POWER then console.log " power: even power of absolute of real value "
push(cadr(p1))
push(p2)
power()
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
# e^log(...)
if (p1 == symbol(E) && car(p2) == symbol(LOG))
push(cadr(p2))
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
# e^some_float
if (p1 == symbol(E) && isdouble(p2))
if DEBUG_POWER then console.log " power: p1 == symbol(E) && isdouble(p2) "
push_double(Math.exp(p2.d))
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
# complex number in exponential form, get it to rectangular
# but only if we are not in the process of calculating a polar form,
# otherwise we'd just undo the work we want to do
if (p1 == symbol(E) && Find(p2, imaginaryunit) != 0 and Find(p2, symbol(PI))!= 0 and !evaluatingPolar)
push_symbol(POWER)
push(p1)
push(p2)
list(3)
if DEBUG_POWER then console.log " power: turning complex exponential to rect: " + stack[tos-1]
rect()
hopefullySimplified = pop(); # put new (hopefully simplified expr) in p2
if Find(hopefullySimplified, symbol(PI)) == 0
if DEBUG_POWER then console.log " power: turned complex exponential to rect: " + hopefullySimplified
push hopefullySimplified
return
# (a * b) ^ c -> (a ^ c) * (b ^ c)
# note that we can't in general do this, for example
# sqrt(x*y) != x^(1/2) y^(1/2) (counterexample" x = -1 and y = -1)
# BUT we can carve-out here some cases where this
# transformation is correct
if (car(p1) == symbol(MULTIPLY) && isinteger(p2))
if DEBUG_POWER then console.log " power: (a * b) ^ c -> (a ^ c) * (b ^ c) "
p1 = cdr(p1)
push(car(p1))
push(p2)
power()
p1 = cdr(p1)
while (iscons(p1))
push(car(p1))
push(p2)
power()
multiply()
p1 = cdr(p1)
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
# (a ^ b) ^ c -> a ^ (b * c)
# note that we can't in general do this, for example
# sqrt(x^y) != x^(1/2 y) (counterexample x = -1)
# BUT we can carve-out here some cases where this
# transformation is correct
# simple numeric check to see if a is a number > 0
is_a_moreThanZero = false
if isnum(cadr(p1))
is_a_moreThanZero = sign(compare_numbers(cadr(p1), zero))
if (car(p1) == symbol(POWER) && (
isinteger(p2) or # when c is an integer
is_a_moreThanZero # when a is >= 0
))
push(cadr(p1))
push(caddr(p1))
push(p2)
multiply()
power()
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
b_isEven_and_c_isItsInverse = false
if iseveninteger(caddr(p1))
push caddr(p1)
push p2
multiply()
isThisOne = pop()
if isone(isThisOne)
b_isEven_and_c_isItsInverse = true
if (car(p1) == symbol(POWER) && b_isEven_and_c_isItsInverse)
if DEBUG_POWER then console.log " power: car(p1) == symbol(POWER) && b_isEven_and_c_isItsInverse "
push(cadr(p1))
abs()
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
# when expanding,
# (a + b) ^ n -> (a + b) * (a + b) ...
if (expanding && isadd(p1) && isnum(p2))
push(p2)
n = pop_integer()
if (n > 1 && !isNaN(n))
if DEBUG_POWER then console.log " power: expanding && isadd(p1) && isnum(p2) "
power_sum(n)
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
# sin(x) ^ 2n -> (1 - cos(x) ^ 2) ^ n
if (trigmode == 1 && car(p1) == symbol(SIN) && iseveninteger(p2))
if DEBUG_POWER then console.log " power: trigmode == 1 && car(p1) == symbol(SIN) && iseveninteger(p2) "
push_integer(1)
push(cadr(p1))
cosine()
push_integer(2)
power()
subtract()
push(p2)
push_rational(1, 2)
multiply()
power()
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
# cos(x) ^ 2n -> (1 - sin(x) ^ 2) ^ n
if (trigmode == 2 && car(p1) == symbol(COS) && iseveninteger(p2))
if DEBUG_POWER then console.log " power: trigmode == 2 && car(p1) == symbol(COS) && iseveninteger(p2) "
push_integer(1)
push(cadr(p1))
sine()
push_integer(2)
power()
subtract()
push(p2)
push_rational(1, 2)
multiply()
power()
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
# complex number? (just number, not expression)
if (iscomplexnumber(p1))
if DEBUG_POWER then console.log " power - handling the case (a + ib) ^ n"
# integer power?
# n will be negative here, positive n already handled
if (isinteger(p2))
# / \ n
# -n | a - ib |
# (a + ib) = | -------- |
# | 2 2 |
# \ a + b /
push(p1)
conjugate()
p3 = pop()
push(p3)
# gets the denominator
push(p3)
push(p1)
multiply()
divide()
if !isone(p2)
push(p2)
negate()
power()
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
# noninteger or floating power?
if (isnum(p2))
push(p1)
abs()
push(p2)
power()
push_integer(-1)
push(p1)
arg()
push(p2)
multiply()
if evaluatingAsFloats or (iscomplexnumberdouble(p1) and isdouble(p2))
# remember that the "double" type is
# toxic, i.e. it propagates, so we do
# need to evaluate PI to its actual double
# value
push_double(Math.PI)
else
#console.log("power pushing PI when p1 is: " + p1 + " and p2 is:" + p2)
push(symbol(PI))
divide()
power()
multiply()
# if we calculate the power making use of arctan:
# * it prevents nested radicals from being simplified
# * results become really hard to manipulate afterwards
# * we can't go back to other forms.
# so leave the power as it is.
if avoidCalculatingPowersIntoArctans
if Find(stack[tos-1], symbol(ARCTAN))
pop()
push_symbol(POWER)
push(p1)
push(p2)
list(3)
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
return
#
#push(p1)
#abs()
#push(p2)
#power()
#push(symbol(E))
#push(p1)
#arg()
#push(p2)
#multiply()
#push(imaginaryunit)
#multiply()
#power()
#multiply()
#
if (simplify_polar())
if DEBUG_POWER then console.log " power: using simplify_polar"
return
if DEBUG_POWER then console.log " power: nothing can be done "
push_symbol(POWER)
push(p1)
push(p2)
list(3)
if DEBUG_POWER then console.log " power of " + inputBase + " ^ " + inputExp + ": " + stack[tos-1]
#-----------------------------------------------------------------------------
#
# Compute the power of a sum
#
# Input: p1 sum
#
# n exponent
#
# Output: Result on stack
#
# Note:
#
# Uses the multinomial series (see Math World)
#
# n n! n1 n2 nk
# (a1 + a2 + ... + ak) = sum (--------------- a1 a2 ... ak )
# n1! n2! ... nk!
#
# The sum is over all n1 ... nk such that n1 + n2 + ... + nk = n.
#
#-----------------------------------------------------------------------------
# first index is the term number 0..k-1, second index is the exponent 0..n
#define A(i, j) frame[(i) * (n + 1) + (j)]
power_sum = (n) ->
a = []
i = 0
j = 0
k = 0
# number of terms in the sum
k = length(p1) - 1
# local frame
push_frame(k * (n + 1))
# array of powers
p1 = cdr(p1)
for i in [0...k]
for j in [0..n]
push(car(p1))
push_integer(j)
power()
stack[frame + (i) * (n + 1) + (j)] = pop()
p1 = cdr(p1)
push_integer(n)
factorial()
p1 = pop()
for i in [0...k]
a[i] = 0
push(zero)
multinomial_sum(k, n, a, 0, n)
pop_frame(k * (n + 1))
#-----------------------------------------------------------------------------
#
# Compute multinomial sum
#
# Input: k number of factors
#
# n overall exponent
#
# a partition array
#
# i partition array index
#
# m partition remainder
#
# p1 n!
#
# A factor array
#
# Output: Result on stack
#
# Note:
#
# Uses recursive descent to fill the partition array.
#
#-----------------------------------------------------------------------------
#int k, int n, int *a, int i, int m
multinomial_sum = (k,n,a,i,m) ->
j = 0
if (i < k - 1)
for j in [0..m]
a[i] = j
multinomial_sum(k, n, a, i + 1, m - j)
return
a[i] = m
# coefficient
push(p1)
for j in [0...k]
push_integer(a[j])
factorial()
divide()
# factors
for j in [0...k]
push(stack[frame + (j) * (n + 1) + a[j]])
multiply()
add()
# exp(n/2 i pi) ?
# p2 is the exponent expression
# clobbers p3
simplify_polar = ->
n = 0
n = isquarterturn(p2)
switch(n)
when 0
doNothing = 1
when 1
push_integer(1)
return 1
when 2
push_integer(-1)
return 1
when 3
push(imaginaryunit)
return 1
when 4
push(imaginaryunit)
negate()
return 1
if (car(p2) == symbol(ADD))
p3 = cdr(p2)
while (iscons(p3))
n = isquarterturn(car(p3))
if (n)
break
p3 = cdr(p3)
switch (n)
when 0
return 0
when 1
push_integer(1)
when 2
push_integer(-1)
when 3
push(imaginaryunit)
when 4
push(imaginaryunit)
negate()
push(p2)
push(car(p3))
subtract()
exponential()
multiply()
return 1
return 0