UNPKG

algebrite

Version:

Computer Algebra System in Coffeescript

583 lines (459 loc) 16.7 kB
# Factor a polynomial factorpoly = -> if DEBUG then console.log "factorpoly: " + stack[tos-1].toString() + " " + stack[tos-2].toString() save() variable = pop() polynomial = pop() if !Find(polynomial, variable) or !ispolyexpandedform(polynomial, variable) or !issymbol(variable) push(polynomial) else yyfactorpoly(variable, polynomial) restore() #----------------------------------------------------------------------------- # # Input: tos-2 true polynomial # # tos-1 free variable # # Output: factored polynomial on stack # #----------------------------------------------------------------------------- yyfactorpoly = (variable, polynomial) -> if DEBUG firstParam = variable secondParam = polynomial console.log "yyfactorpoly: " + firstParam + " " + secondParam save() h = tos if (isfloating(polynomial)) stop("floating point numbers in polynomial") polycoeff = tos factpoly_expo = coeff(variable, polynomial) - 1 if DEBUG then console.log "yyfactorpoly: " + firstParam + " " + secondParam + " factpoly_expo before rationalize_coefficients: " + factpoly_expo partOfPolynomialFactoredSoFar = rationalize_coefficients(h) if DEBUG then console.log "yyfactorpoly: " + firstParam + " " + secondParam + " factpoly_expo after rationalize_coefficients: " + factpoly_expo # for univariate polynomials we could do factpoly_expo > 1 whichRootsAreWeFinding = "real" remainingPoly = null while (factpoly_expo > 0) if DEBUG then console.log "yyfactorpoly: " + firstParam + " " + secondParam + " factpoly_expo inside while loop: " + factpoly_expo if (isZeroAtomOrTensor(stack[polycoeff+0])) if DEBUG then console.log "yyfactorpoly: " + firstParam + " " + secondParam + " isZeroAtomOrTensor" A = one B = zero else #console.log("trying to find a " + whichRootsAreWeFinding + " root") if whichRootsAreWeFinding == "real" [foundRealRoot,A,B] = get_factor_from_real_root(variable, factpoly_expo, polycoeff) else if whichRootsAreWeFinding == "complex" [foundComplexRoot,A] = get_factor_from_complex_root(remainingPoly, factpoly_expo, polycoeff) if whichRootsAreWeFinding == "real" if foundRealRoot == 0 whichRootsAreWeFinding = "complex" continue else # build the 1-degree polynomial out of the # real solution that was just found. push(A) # A push(variable) # x multiply() push(B) # B add() AxPlusB = pop() if DEBUG then console.log "yyfactorpoly: " + firstParam + " " + secondParam + " success\nFACTOR=" + AxPlusB # factor out negative sign (not req'd because A > 1) #if 0 ### if (isnegativeterm(A)) push(AxPlusB) negate() AxPlusB = pop() push(partOfPolynomialFactoredSoFar) negate_noexpand() partOfPolynomialFactoredSoFar = pop() ### #endif # partOfPolynomialFactoredSoFar is the part of the polynomial that was factored so far, # add the newly found factor to it. Note that we are not actually # multiplying the polynomials fully, we are just leaving them # expressed as (P1)*(P2), we are not expanding the product. push(partOfPolynomialFactoredSoFar) push(AxPlusB) multiply_noexpand() partOfPolynomialFactoredSoFar = pop() # ok now on stack we have the coefficients of the # remaining part of the polynomial still to factor. # Divide it by the newly-found factor so that # the stack then contains the coefficients of the # polynomial part still left to factor. yydivpoly(factpoly_expo, polycoeff, A, B) while (factpoly_expo and isZeroAtomOrTensor(stack[polycoeff+factpoly_expo])) factpoly_expo-- push(zero) for i in [0..factpoly_expo] push(stack[polycoeff+i]) push(variable) # the free variable push_integer(i) power() multiply() add() remainingPoly = pop() #console.log("real branch remainingPoly: " + remainingPoly) else if whichRootsAreWeFinding == "complex" if foundComplexRoot == 0 break else # build the 2-degree polynomial out of the # real solution that was just found. push(A) # A push(variable) # x subtract() #console.log("first factor: " + stack[tos-1].toString()) push(A) # A conjugate() push(variable) # x subtract() #console.log("second factor: " + stack[tos-1].toString()) multiply() #if (factpoly_expo > 0 && isnegativeterm(stack[polycoeff+factpoly_expo])) # negate() # negate_noexpand() secondDegreePloly = pop() if DEBUG then console.log "yyfactorpoly: " + firstParam + " " + secondParam + " success\nFACTOR=" + secondDegreePloly # factor out negative sign (not req'd because A > 1) #if 0 ### if (isnegativeterm(A)) push(secondDegreePloly) negate() secondDegreePloly = pop() push(partOfPolynomialFactoredSoFar) negate_noexpand() partOfPolynomialFactoredSoFar = pop() ### #endif # partOfPolynomialFactoredSoFar is the part of the polynomial that was factored so far, # add the newly found factor to it. Note that we are not actually # multiplying the polynomials fully, we are just leaving them # expressed as (P1)*(P2), we are not expanding the product. push(partOfPolynomialFactoredSoFar) previousFactorisation = pop() #console.log("previousFactorisation: " + previousFactorisation) push(partOfPolynomialFactoredSoFar) push(secondDegreePloly) multiply_noexpand() partOfPolynomialFactoredSoFar = pop() #console.log("new prospective factorisation: " + partOfPolynomialFactoredSoFar) # build the polynomial of the unfactored part #console.log("build the polynomial of the unfactored part factpoly_expo: " + factpoly_expo) if !remainingPoly? push(zero) for i in [0..factpoly_expo] push(stack[polycoeff+i]) push(variable) # the free variable push_integer(i) power() multiply() add() remainingPoly = pop() #console.log("original polynomial (dividend): " + remainingPoly) dividend = remainingPoly #push(dividend) #degree() #startingDegree = pop() push(dividend) #console.log("dividing " + stack[tos-1].toString() + " by " + secondDegreePloly) push(secondDegreePloly) # divisor push(variable) # X divpoly() remainingPoly = pop() push(remainingPoly) push(secondDegreePloly) # divisor multiply() checkingTheDivision = pop() if !equal(checkingTheDivision, dividend) #push(dividend) #gcd_sum() #console.log("gcd top of stack: " + stack[tos-1].toString()) if DEBUG then console.log("we found a polynomial based on complex root and its conj but it doesn't divide the poly, quitting") if DEBUG then console.log("so just returning previousFactorisation times dividend: " + previousFactorisation + " * " + dividend) push(previousFactorisation) push(dividend) prev_expanding = expanding expanding = 0 yycondense() expanding = prev_expanding multiply_noexpand() partOfPolynomialFactoredSoFar = pop() stack[h] = partOfPolynomialFactoredSoFar moveTos h + 1 restore() return #console.log("result: (still to be factored) " + remainingPoly) #push(remainingPoly) #degree() #remainingDegree = pop() ### if compare_numbers(startingDegree, remainingDegree) # ok even if we found a complex root that # together with the conjugate generates a poly in Z, # that doesn't mean that the division would end up in Z. # Example: 1+x^2+x^4+x^6 has +i and -i as one of its roots # so a factor is 1+x^2 ( = (x+i)*(x-i)) # BUT ### for i in [0..factpoly_expo] pop() coeff(variable, remainingPoly) factpoly_expo -= 2 #console.log("factpoly_expo: " + factpoly_expo) if DEBUG then console.log "yyfactorpoly: " + firstParam + " " + secondParam + " building the remaining unfactored part of the polynomial" push(zero) for i in [0..factpoly_expo] push(stack[polycoeff+i]) push(variable) # the free variable push_integer(i) power() multiply() add() polynomial = pop() if DEBUG then console.log "yyfactorpoly: " + firstParam + " " + secondParam + " remaining unfactored part of the polynomial: " + polynomial.toString() push(polynomial) prev_expanding = expanding expanding = 0 yycondense() expanding = prev_expanding polynomial = pop() if DEBUG then console.log "yyfactorpoly: " + firstParam + " " + secondParam + " new poly with extracted common factor: " + polynomial.toString() #debugger # factor out negative sign if (factpoly_expo > 0 && isnegativeterm(stack[polycoeff+factpoly_expo])) push(polynomial) #prev_expanding = expanding #expanding = 1 negate() #expanding = prev_expanding polynomial = pop() push(partOfPolynomialFactoredSoFar) negate_noexpand() partOfPolynomialFactoredSoFar = pop() push(partOfPolynomialFactoredSoFar) push(polynomial) multiply_noexpand() partOfPolynomialFactoredSoFar = pop() if DEBUG then console.log "yyfactorpoly: " + firstParam + " " + secondParam + " result: " + partOfPolynomialFactoredSoFar stack[h] = partOfPolynomialFactoredSoFar moveTos h + 1 restore() rationalize_coefficients = (h) -> # LCM of all polynomial coefficients ratio = one for i in [h...tos] push(stack[i]) denominator() push(ratio) lcm() ratio = pop() # multiply each coefficient by RESULT for i in [h...tos] push(ratio) push(stack[i]) multiply() stack[i] = pop() # reciprocate RESULT push(ratio) reciprocate() ratioInverse = pop() if DEBUG then console.log "rationalize_coefficients result: " + ratioInverse.toString() return ratioInverse get_factor_from_real_root = (variable, factpoly_expo, polycoeff)-> if DEBUG then console.log "get_factor_from_real_root" i = 0 j = 0 h = 0 a0 = 0 an = 0 na0 = 0 nan = 0 if DEBUG push(zero) for i in [0..factpoly_expo] push(stack[polycoeff+i]) push(variable) push_integer(i) power() multiply() add() polynomial = pop() console.log("POLY=" + polynomial) h = tos an = tos push(stack[polycoeff+factpoly_expo]) divisors_onstack() nan = tos - an a0 = tos push(stack[polycoeff+0]) divisors_onstack() na0 = tos - a0 if DEBUG console.log("divisors of base term") for i in [0...na0] console.log(", " + stack[a0 + i]) console.log("divisors of leading term") for i in [0...nan] console.log(", " + stack[an + i]) # try roots for rootsTries_i in [0...nan] for rootsTries_j in [0...na0] #if DEBUG then console.log "nan: " + nan + " na0: " + na0 + " i: " + rootsTries_i + " j: " + rootsTries_j testNumerator = stack[an + rootsTries_i] testDenominator = stack[a0 + rootsTries_j] push(testDenominator) push(testNumerator) divide() negate() testValue = pop() evalPolyResult = Evalpoly(factpoly_expo, polycoeff, testValue) if DEBUG console.log("try A=" + testNumerator) console.log(", B=" + testDenominator) console.log(", root " + variable) console.log("=-B/A=" + testValue) console.log(", POLY(" + testValue) console.log(")=" + evalPolyResult) if (isZeroAtomOrTensor(evalPolyResult)) moveTos h if DEBUG then console.log "get_factor_from_real_root returning 1" return [1, testNumerator, testDenominator] push(testDenominator) negate() testDenominator = pop() push(testValue) negate() testValue = pop() evalPolyResult = Evalpoly(factpoly_expo, polycoeff, testValue) if DEBUG console.log("try A=" + testNumerator) console.log(", B=" + testDenominator) console.log(", root " + variable) console.log("=-B/A=" + testValue) console.log(", POLY(" + testValue) console.log(")=" + evalPolyResult) if (isZeroAtomOrTensor(evalPolyResult)) moveTos h if DEBUG then console.log "get_factor_from_real_root returning 1" return [1, testNumerator, testDenominator] moveTos h if DEBUG then console.log "get_factor_from_real_root returning" return [0, null, null] get_factor_from_complex_root = (remainingPoly, factpoly_expo, polycoeff) -> i = 0 j = 0 h = 0 a0 = 0 an = 0 na0 = 0 nan = 0 if factpoly_expo <= 2 if DEBUG then console.log("no more factoring via complex roots to be found in polynomial of degree <= 2") return [0,null] if DEBUG then console.log("complex root finding for POLY=" + remainingPoly) h = tos an = tos # trying -1^(2/3) which generates a polynomial in Z # generates x^2 + 2x + 1 push_integer(-1) push_rational(2,3) power() rect() testValue = pop() if DEBUG then console.log("complex root finding: trying with " + testValue) push(testValue) evalPolyResult = Evalpoly(factpoly_expo, polycoeff, testValue) if DEBUG then console.log("complex root finding result: " + evalPolyResult) if (isZeroAtomOrTensor(evalPolyResult)) moveTos h if DEBUG then console.log "get_factor_from_complex_root returning 1" return [1,testValue] # trying 1^(2/3) which generates a polynomial in Z # http://www.wolframalpha.com/input/?i=(1)%5E(2%2F3) # generates x^2 - 2x + 1 push_integer(1) push_rational(2,3) power() rect() testValue = pop() if DEBUG then console.log("complex root finding: trying with " + testValue) push(testValue) evalPolyResult = Evalpoly(factpoly_expo, polycoeff, testValue) if DEBUG then console.log("complex root finding result: " + evalPolyResult) if (isZeroAtomOrTensor(evalPolyResult)) moveTos h if DEBUG then console.log "get_factor_from_complex_root returning 1" return [1,testValue] # trying some simple complex numbers. All of these # generate polynomials in Z for rootsTries_i in [-10..10] for rootsTries_j in [1..5] push_integer(rootsTries_i) push_integer(rootsTries_j) push(imaginaryunit) multiply() add() rect() testValue = pop() if DEBUG then console.log("complex root finding: trying simple complex combination " + testValue) push(testValue) evalPolyResult = Evalpoly(factpoly_expo, polycoeff, testValue) #console.log("complex root finding result: " + evalPolyResult) if (isZeroAtomOrTensor(evalPolyResult)) moveTos h if DEBUG then console.log "found complex root: " + evalPolyResult return [1,testValue] moveTos h if DEBUG then console.log "get_factor_from_complex_root returning 0" return [0,null] #----------------------------------------------------------------------------- # # Divide a polynomial by Ax+B # # Input: on stack: polycoeff Dividend coefficients # # factpoly_expo as parameter # # A as parameter # # B as parameter # # Output: on stack: polycoeff Contains quotient coefficients # #----------------------------------------------------------------------------- yydivpoly = (factpoly_expo, polycoeff, A, B) -> Q = zero for i in [factpoly_expo...0] push(stack[polycoeff+i]) stack[polycoeff+i] = Q push(A) divide() Q = pop() push(stack[polycoeff+i - 1]) push(Q) push(B) multiply() subtract() stack[polycoeff+i - 1] = pop() stack[polycoeff+0] = Q if DEBUG then console.log "yydivpoly Q: " + Q.toString() Evalpoly = (factpoly_expo, polycoeff, evaluateAt) -> push(zero) for i in [factpoly_expo..0] push(evaluateAt) multiply() push(stack[polycoeff+i]) #if DEBUG # console.log("Evalpoly top of stack:") # console.log stack[tos-i].toString() add() return pop()