#!/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 '''(''' + formatter(f) + ''')''' else: return formatter(f) # returns a plain text string for function def text(f): if f == []: return '''NOFUNCTION''' elif f[0] == E: return '''e''' elif f[0] == PI: return '''pi''' elif f[0] == A: return '''a''' elif f[0] == B: return '''b''' elif f[0] == C: return '''c''' elif f[0] <= CONSTANT: return '''%d''' % f[1][0] # 1-ary functions: elif f[0] == NEGATIVE: return '''-''' + parenthesization(f[1], text, [SUM, DIFFERENCE]) elif f[0] == SINE: return '''sin(''' + text(f[1]) + ''')''' elif f[0] == COSINE: return '''cos(''' + text(f[1]) + ''')''' # 2-ary functions: elif f[0] == POWER: firstString = parenthesization(f[1], text, [NEGATIVE, SUM, DIFFERENCE, PRODUCT, QUOTIENT, POWER]) secondString = parenthesization(f[2], text, [SUM, DIFFERENCE, PRODUCT, QUOTIENT]) return firstString + '''^''' + secondString elif f[0] == LOG: if f[1][0] == E: return '''ln(''' + text(f[2]) + ''')''' else: return '''log_''' + text(f[1]) + '''(''' + text(f[2]) + ''')''' elif f[0] == DIFFERENCE: return text(f[1]) + ''' - ''' + parenthesization(f[2], text, [SUM, DIFFERENCE]) elif f[0] == QUOTIENT: firstString = parenthesization(f[1], text, [SUM, DIFFERENCE, QUOTIENT]) secondString = parenthesization(f[2], text, [SUM, DIFFERENCE, PRODUCT, QUOTIENT]) return firstString + ''' / ''' + secondString # n-ary functions: elif f[0] == SUM: return " + ".join([text(func) for func in f[1:]]) elif f[0] == PRODUCT: return " * ".join([parenthesization(func, text, [SUM, DIFFERENCE, NEGATIVE]) for func in f[1:]]) elif f[0] == X: return '''x''' else: return '''NOFUNCTION''' # returns an HTML string for function def html(f): if f == []: return '''NOFUNCTION''' elif f[0] == E: return '''e''' elif f[0] == PI: return '''π''' elif f[0] == A: return '''a''' elif f[0] == B: return '''b''' elif f[0] == C: return '''c''' elif f[0] <= CONSTANT: return '''%d''' % f[1][0] # 1-ary functions: elif f[0] == NEGATIVE: return '''-''' + parenthesization(f[1], html, [SUM, DIFFERENCE]) elif f[0] == SINE: return '''sin(''' + html(f[1]) + ''')''' elif f[0] == COSINE: return '''cos(''' + html(f[1]) + ''')''' # 2-ary functions: elif f[0] == POWER: firstString = parenthesization(f[1], html, [NEGATIVE, SUM, DIFFERENCE, PRODUCT, QUOTIENT, POWER]) secondString = parenthesization(f[2], html, [SUM, DIFFERENCE, PRODUCT, QUOTIENT]) return firstString + ''' ''' + secondString + '''''' elif f[0] == LOG: if f[1][0] == E: return '''ln(''' + html(f[2]) + ''')''' else: return '''log''' + html(f[1]) + '''(''' + html(f[2]) + ''')''' elif f[0] == DIFFERENCE: return html(f[1]) + ''' - ''' + parenthesization(f[2], html, [SUM, DIFFERENCE]) elif f[0] == QUOTIENT: firstString = parenthesization(f[1], html, [SUM, DIFFERENCE, QUOTIENT]) secondString = parenthesization(f[2], html, [SUM, DIFFERENCE, PRODUCT, QUOTIENT]) return firstString + ''' / ''' + secondString # n-ary functions: elif f[0] == SUM: return ''' + '''.join([html(func) for func in f[1:]]) elif f[0] == PRODUCT: return ''' '''.join([parenthesization(func, html, [SUM, DIFFERENCE, NEGATIVE]) for func in f[1:]]) elif f[0] == X: return '''x''' else: return '''NOFUNCTION''' # returns a table-heavy HTML string for function def htmlTable(f): if f == []: middle = '''NOFUNCTION''' elif f[0] == E: middle = '''e''' elif f[0] == PI: middle = '''π''' elif f[0] == A: middle = '''a''' elif f[0] == B: middle = '''b''' elif f[0] == C: middle = '''c''' elif f[0] <= CONSTANT: middle = '''%d''' % f[1][0] # 1-ary functions: elif f[0] == NEGATIVE: middle = '''-''' + parenthesization(f[1], htmlTable, [SUM, DIFFERENCE]) elif f[0] == SINE: middle = '''sin(''' + htmlTable(f[1]) + ''')''' elif f[0] == COSINE: middle = '''cos(''' + htmlTable(f[1]) + ''')''' # 2-ary functions: elif f[0] == POWER: firstString = parenthesization(f[1], htmlTable, [NEGATIVE, SUM, DIFFERENCE, PRODUCT, QUOTIENT, POWER]) # the exponent cannot be a table; drop back to html: secondString = parenthesization(f[2], html, [SUM, DIFFERENCE, PRODUCT, QUOTIENT]) middle = firstString + '''!''' + secondString + '''''' elif f[0] == LOG: if f[1][0] == E: middle = '''ln(''' + htmlTable(f[2]) + ''')''' else: # the base cannot be a table; drop back to html: middle = '''log''' + html(f[1]) + '''(''' + htmlTable(f[2]) + ''')''' elif f[0] == DIFFERENCE: if f[2][0] == SUM or f[2][0] == DIFFERENCE: middle = htmlTable(f[1]) + '''!!(''' + htmlTable(f[2]) + ''')''' else: middle = htmlTable(f[1]) + '''!!''' + htmlTable(f[2]) elif f[0] == QUOTIENT: # the numerator and denominator cannot be tables; drop back to html: middle = '''''' + html(f[1]) + '''
''' + html(f[2]) + '''''' # n-ary functions: elif f[0] == SUM: middle = '''!+!'''.join([htmlTable(func) for func in f[1:]]) elif f[0] == PRODUCT: middle = '''!'''.join([parenthesization(func, htmlTable, [SUM, DIFFERENCE, NEGATIVE]) for func in f[1:]]) elif f[0] == X: middle = '''x''' else: middle = '''NOFUNCTION''' return '''
''' + middle + '''
''' # prints function to standard output, either as HTML table or as big text def printFunction(f, formatting): if formatting == 1: print '''

\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\nJ. Davis: Differentiation Practice\n\n\n

\n2008 February 22 / E-Mail Me\n

\n

Differentiation Practice

''' # output instructions: if oldFunction == []: print '''

\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\n\n
\n
\nProblem complexity:\n

''' # print complexity radio buttons: if complexity == 1: print ''' f(x)\n
''' else: print ''' f(x)\n
''' if complexity == 2: print ''' f(g(x))\n
''' else: print ''' f(g(x))\n
''' if complexity == 3: print ''' f(g(h(x)))\n
''' else: print ''' f(g(h(x)))\n
''' if complexity == 4: print ''' f(g(h(i(x))))\n
''' else: print ''' f(g(h(i(x))))\n
''' if complexity == 5: print ''' f(g(h(i(j(x)))))''' else: print ''' f(g(h(i(j(x)))))''' # print include powers checkbox: if includePowers == 1: print '''

\n''' else: print '''

\n''' if formatting == 1: print '''include arbitrary powers f(x) g(x)\n''' else: print '''include arbitrary powers f(x)^g(x)\n''' print '''

\nFormula formatting:\n

''' # print formatting radio buttons: if formatting == 1: print '''fancy HTML\n
''' else: print '''pretty HTML\n
''' if formatting == 2: print '''plain text\n''' else: print '''plain text\n''' # print common epilogue: print '''

\nFancy HTML is supposed to look better than plain text, but sometimes it looks worse — especially for huge formulas that are wider than the screen.

\n\n'''