#!/usr/local/bin/python-old # This program automatically differentiates elementary functions of a variable # x constructed by arithmetic, powers, logarithms, sines, and cosines. # Functions are represented by trees of integers. Most of these integers are # codes for functions of 0, 1, or 2 arguments, but there are three special # cases: # [CONSTANT, [n]] is interpreted as the integer n, # [SUM, ...] and [PRODUCT, ...] can take any number n >= 0 of arguments. # Functions can be randomly generated or parsed out of comma-separated strings # of integers in prefix. The functions can be differentiated. The functions can # be printed in plain text, ordinary HTML, or table-heavy HTML. They can also # be encoded into those comma-separated strings; the three special cases are # handled like this: # [CONSTANT, [n]] encodes as "CONSTANT,n" # [SUM, ...] encodes as "SUM,n,..." (and similarly for PRODUCT) # TO DO: # update simplification to handle n-ary SUM, PRODUCT # simplify strings of products and quotients down to a single quotient of products # generate fractional powers to test taking roots sometimes? # propogate NEGATIVE out of products/quotients more # get rid of DIFFERENCE, replacing it with NEGATIVE more often # maybe get rid of QUOTIENT, replacing it with a new INVERSE # replace all opcodes with [opcode, arity] to make SUM/PRODUCT more elegant? # should CONSTANT be treated as 1-ary or 0-ary? # add new hidden datum indicating that independent variable x is really t, u, etc. # randomly generate sums and products that are more than 2-ary? import cgi import random # 0-ary functions (constants): NOFUNCTION = 0 E = 1 PI = 2 A = 3 B = 4 C = 5 # 1-ary functions: CONSTANT = 10 NEGATIVE = 11 SINE = 12 COSINE = 13 # 2-ary functions: POWER = 20 LOG = 21 DIFFERENCE = 22 QUOTIENT = 23 # future n-ary functions: SUM = 90 PRODUCT = 91 # the independent variable: X = 100 # RANDOM PROBLEM GENERATION FUNCTIONS ######################################### # generates a random 1-ary, 2-ary, or n-ary function # this function depends intimately on the codes for the math functions above def randomHead(): # ignoring CONSTANT, there are 9 heads from which to choose: head = random.randint(0, 8) if head < 3: # beginning with NEGATIVE, 3 of the heads are 1-ary: head += NEGATIVE elif head < 3 + 4: # beginning with POWER, 4 of the heads are 2-ary: head += POWER - 3 else: # beginning with SUM, the remaining heads are n-ary: head += SUM - (3 + 4) return head # generates a random constant, either e, pi, a, b, or c, or an integer between 1 and 99 def randomConstant(): if random.randint(0, 1) == 0: return [random.randint(E, C)] else: return [CONSTANT, [random.randint(1, 99)]] # generates a random constant suitable to be a base of a logarithm or power, often e def randomBase(): roll = random.randint(0, 3) if roll == 0: return [random.randint(PI, C)] elif roll == 1: return [CONSTANT, [random.randint(2, 10)]] else: return [E] # generates a function of the given complexity # includes f(x)^g(x) if includePowers != 0 # only 2-ary sums and products are generated def randomFormula(complexity, includePowers): if complexity <= 0: return [X] else: head = randomHead() if head <= COSINE: # a 1-ary function was chosen: return [head, randomFormula(complexity - 1, includePowers)] else: # a 2-ary or n-ary function was chosen: if head == LOG: # make the base a constant: return [head, randomBase(), randomFormula(complexity - 1, includePowers)] elif head == POWER: # determine whether the base, exponent, or both are functions: if includePowers == 0: if random.randint(0, 1) == 0: # f^c: return [head, randomFormula(complexity - 1, includePowers), randomConstant()] else: # c^f: return [head, randomBase(), randomFormula(complexity - 1, includePowers)] else: # f^g: return [head, randomFormula(complexity - 1, includePowers), randomFormula(complexity - 1, includePowers)] else: # one of the four arithmetic operations was chosen: if complexity == 1: # make one subfunction x and the other a constant: if head == PRODUCT: # coefficients should always be written before x: return [head, randomConstant(), [X]] else: # use x before or after the constant: if random.randint(0, 1) == 0: return [head, [X], randomConstant()] else: return [head, randomConstant(), [X]] else: return [head, randomFormula(complexity - 1, includePowers), randomFormula(complexity - 1, includePowers)] # MATHEMATICAL FUNCTIONS ###################################################### # helper function for product rule # given product (f1 ... fn) and 1 <= k <= n, returns product (f1 ... fk' ... fn) def derivativeOfProductTerm(f, k): return f[0:k] + [derivative(f[k])] + f[(k + 1):] # returns the derivative of any function def derivative(f): if f == []: return [] elif f[0] <= CONSTANT: # c' = 0: return [CONSTANT, [0]] elif f[0] == NEGATIVE: # (-f)' = -f': return [NEGATIVE, derivative(f[1])] elif f[0] == SINE: # (sin f)' = (cos f) f': return [PRODUCT, [COSINE, f[1]], derivative(f[1])] elif f[0] == COSINE: # (cos f)' = (-sin f) f': return [PRODUCT, [NEGATIVE, [SINE, f[1]]], derivative(f[1])] elif f[0] == POWER: # optimize for the power rule, since it's not simplified well: if f[2][0] >= E and f[2][0] <= C: # (f^c)' = c f^(c - 1) f': return [PRODUCT, [PRODUCT, f[2], [POWER, f[1], [DIFFERENCE, f[2], [CONSTANT, [1]]]]], derivative(f[1])] elif f[2][0] == CONSTANT: # (f^c)' = c f^(c - 1) f': return [PRODUCT, [PRODUCT, f[2], [POWER, f[1], [CONSTANT, [f[2][1][0] - 1]]]], derivative(f[1])] else: # (f^g)' = f^g (g' ln f + g f' / f): return [PRODUCT, f, [SUM, [PRODUCT, derivative(f[2]), [LOG, [E], f[1]]], [PRODUCT, f[2], [QUOTIENT, derivative(f[1]), f[1]]]]] elif f[0] == LOG: # assuming b is constant, (log_b f)' = f' / (f ln b): return [QUOTIENT, derivative(f[2]), [PRODUCT, f[2], [LOG, [E], f[1]]]] elif f[0] == DIFFERENCE: # (f - g)' = f' - g': return [DIFFERENCE, derivative(f[1]), derivative(f[2])] elif f[0] == QUOTIENT: if f[2][0] <= CONSTANT: # (f / c)' = f' / c; this is not handled well by the simplifier: return [QUOTIENT, derivative(f[1]), f[2]] else: # (f / g)' = (f' g - f g') / g^2: return [QUOTIENT, [DIFFERENCE, [PRODUCT, derivative(f[1]), f[2]], [PRODUCT, f[1], derivative(f[2])]], [POWER, f[2], [CONSTANT, [2]]]] elif f[0] == SUM: # (f1 + ... + fn)' = f1' + ... + fn': return [SUM] + [derivative(func) for func in f[1:]] elif f[0] == PRODUCT: # (f1 ... fn)' = sum_k f1 ... fk' ... fn: return [SUM] + [derivativeOfProductTerm(f, k) for k in range(1, len(f))] elif f[0] == X: return [CONSTANT, [1]] else: return [] # simplifies any function using a canon of rules def simplification(f): if f == []: return [] elif f[0] >= NEGATIVE and f[0] <= COSINE: # f is a 1-ary function; simplify its argument: g = simplification(f[1]) if f[0] == NEGATIVE: if g[0] == NEGATIVE: # --g = g: return g[1] return [f[0], g] elif f[0] >= POWER and f[0] <= QUOTIENT: # f is a 2-ary function; simplify its arguments: g = simplification(f[1]) h = simplification(f[2]) if f[0] == POWER: if h[0] == CONSTANT: if h[1][0] == 0: # g^0 = 1: return [CONSTANT, [1]] elif h[1][0] == 1: # g^1 = g: return g elif f[0] == LOG: if h[0] == CONSTANT and h[1][0] == 1: # log_g 1 = 0: return [CONSTANT, [0]] elif g[0] >= E and g[0] <= C and g[0] == h[0]: # log_g g = 1: return [CONSTANT, [1]] elif f[0] == DIFFERENCE: if g[0] == CONSTANT and g[1][0] == 0: if h[0] == NEGATIVE: # 0 - -h = h: return h[1] else: # 0 - h = -h: return [NEGATIVE, h] if h[0] == CONSTANT: if h[1][0] == 0: # g - 0 = g: return g elif h[1][0] < 0: # g - -h = g + h: return [SUM, g, [CONSTANT, [-h[1][0]]]] if h[0] == NEGATIVE: if g[0] == NEGATIVE: # -g - -h = -(g - h): return [NEGATIVE, [DIFFERENCE, g[1], h[1]]] else: # g - -h = g + h: return [SUM, g, h[1]] elif g[0] == NEGATIVE: # -g - h = -(g + h): return [NEGATIVE, [SUM, g[1], h]] elif f[0] == QUOTIENT: if g[0] == CONSTANT and g[1][0] == 0: # 0 / h = 0: return [CONSTANT, [0]] if h[0] == CONSTANT and h[1][0] == 1: # g / 1 = g: return g if g[0] == QUOTIENT: if h[0] == QUOTIENT: # (g1 / g2) / (h1 / h2) = (g1 h2) / (g2 h1): return [QUOTIENT, simplification([PRODUCT, g[1], h[2]]), simplification([PRODUCT, g[2], h[1]])] else: # (g1 / g2) / h = g1 / (g2 h): return [QUOTIENT, g[1], simplification([PRODUCT, g[2], h])] if h[0] == QUOTIENT: # g / (h1 / h2) = (g h2) / h1: return [QUOTIENT, simplification([PRODUCT, g, h[2]]), h[1]] return [f[0], g, h] elif f[0] >= SUM and f[0] <= PRODUCT: # f is an n-ary function; simplify its arguments: # !!generalize to n-ary g = simplification(f[1]) h = simplification(f[2]) if f[0] == SUM: if g[0] == CONSTANT and g[1][0] == 0: # 0 + h = h: return h elif h[0] == CONSTANT and h[1][0] == 0: # g + 0 = g: return g elif h[0] == NEGATIVE: if g[0] == NEGATIVE: # -g + -h = -(g + h): return [NEGATIVE, [SUM, g[1], h[1]]] else: # g + -h = g - h: return [DIFFERENCE, g, h[1]] elif f[0] == PRODUCT: if g[0] == CONSTANT: if g[1][0] == 0: # 0 * h = 0: return [CONSTANT, [0]] elif g[1][0] == 1: # 1 * h = h: return h elif g[1][0] == -1: # -1 * h = -h: return [NEGATIVE, h] if h[0] == CONSTANT: if h[1][0] == 0: # g * 0 = 0: return [CONSTANT, [0]] elif h[1][0] == 1: # g * 1 = g: return g elif h[1][0] == -1: # g * -1 = -g: return [NEGATIVE, g] if g[0] == QUOTIENT: if h[0] == QUOTIENT: # (g1 / g2) * (h1 / h2) = (g1 h1) / (g2 h2): return [QUOTIENT, simplification([PRODUCT, g[1], h[1]]), simplification([PRODUCT, g[2], h[2]])] else: # (g1 / g2) * h = (g1 h) / g2: return [QUOTIENT, simplification([PRODUCT, g[1], h]), g[2]] if h[0] == QUOTIENT: # g * (h1 / h2) = (g h1) / h2: return [QUOTIENT, simplification([PRODUCT, g, h[1]]), h[2]] return [f[0], g, h] else: return f # OUTPUT FUNCTIONS ############################################################ # returns string representing f formatted by formatter, # enclosed in parentheses if f[0] is among the list of triggers def parenthesization(f, formatter, triggers): if f[0] in triggers: if formatter == text or formatter == html: return '''(''' + formatter(f) + ''')''' else: return '''(
''' + middle + ''' |
\n''' + htmlTable(f) else: print '''
\n''' + text(f) + '''''' # encodes a formula tree into a comma-separated code string, in prefix order def codeString(f): if f == []: return '' elif f[0] < 10: return str(f[0]) elif f[0] == CONSTANT: return str(f[0]) + ''',''' + str(f[1][0]) elif f[0] < 20: # 1-ary function: return str(f[0]) + ''',''' + codeString(f[1]) elif f[0] < 30: # 2-ary function: return str(f[0]) + ''',''' + codeString(f[1]) + ''',''' + codeString(f[2]) elif f[0] < 100: # n-ary function; output code, then number of arguments, then arguments: return str(f[0]) + ''',''' + str(len(f) - 1) + ''',''' + ''','''.join([codeString(func) for func in f[1:]]) else: # the independent variable: return str(f[0]) # INPUT FUNCTIONS ############################################################# # helper function for functionFromCodeString # given a list of numbers, parses first function out # returns output of form [function, numbers] def functionAndRemainder(numbers): if numbers == []: return [[], []] else: head = numbers[0] if head == CONSTANT: # 1-ary function special case: return [[CONSTANT, [numbers[1]]], numbers[2:]] else: if head >= SUM and head <= PRODUCT: # pop the head and arity off the number list: arity = numbers[1] remainder = numbers[2:] else: # pop the head off, and figure out the arity: if head < 10 or head == X: arity = 0 elif head < 20: arity = 1 else: arity = 2 remainder = numbers[1:] function = [head] # pop arity functions off the headless number list: for i in range(0, arity): funcAndRem = functionAndRemainder(remainder) function = function + [funcAndRem[0]] remainder = funcAndRem[1] return [function, remainder] # given string of comma-separated numbers, parses into function def functionFromCodeString(string): # tokenize string into list of numbers: if string == '': numberList = [] else: wordList = string.split(''',''') try: numberList = [int(word) for word in wordList] except ValueError: numberList = [] # parse list of numbers into function; remainder shouldn't exist: return (functionAndRemainder(numberList))[0] # retrieves radio button group value from CGI form, as integer def radioValue(form, name): # radio button values are reported as strings: if form.has_key(name): try: returnValue = int(form[name].value) except ValueError: returnValue = 1 else: returnValue = 1 return returnValue # retrieves checkbox value, either 0 or 1, from CGI form def checkboxValue(form, name): # checkboxes exist iff they're turned on: if form.has_key(name): return 1 else: return 0 # MAIN FUNCTION ############################################################### # input; establish formatting, complexity, includePowers, oldFunction: form = cgi.FieldStorage() if form: formatting = radioValue(form, '''formattingRadio''') complexity = radioValue(form, '''complexityRadio''') includePowers = checkboxValue(form, '''powerCheckbox''') if form.has_key('''oldCodeString'''): oldFunction = functionFromCodeString(form['''oldCodeString'''].value) else: oldFunction = [] else: # set defaults for a first-time visitor: formatting = 1 complexity = 1 includePowers = 0 oldFunction = [] # computation; establish oldDerivative, newFunction: oldDerivative = simplification(derivative(oldFunction)) if oldFunction == []: newFunction = [POWER, [X], [CONSTANT, [3]]] else: newFunction = simplification(randomFormula(complexity, includePowers)) # output prologue: print '''Content-Type: text/html\n''' print '''\n
\n\n2008 February 22 / E-Mail Me\n
\n
\nThis web page randomly generates and automatically solves differentiation problems, using all of the basic rules (sum, constant multiple, product, chain) and the most common functions (constants, powers, exponentials, logarithms, sine and cosine). It can also perform logarithmic differentiation.\n
\nFor starters, compute the derivative of this:\n
''' else: print '''
\nYour old function was...''' printFunction(oldFunction, formatting) print '''
\nIts derivative was...''' printFunction(oldDerivative, formatting) print '''
\nNow what's the derivative of this?''' # output new function and common prologue: printFunction(newFunction, formatting) print '''
\nOnce you've figured it out, click the big button below to check your answer and receive a new problem. You can also adjust the difficulty of the problems and the formatting of the formulas. Having technical difficulties? Found an error? E-mail me at the link above.\n
\n
\n\n'''