[Edu-sig] More calculus in Python

Kirby Urner urnerk at qwest.net
Mon Dec 27 23:35:00 CET 2004


This is part of an exchange with math profs over on math-teach*, but I think
is of relevance here, given all the Python.  KU

* http://www.mathforum.com/epigone/math-teach/ghyrturcrerl

==============

>On Dec 26, 2004, at 4:01 PM, Kirby Urner wrote:
>
>> To recapitulate how we'll do some of the early calculus stuff using 
>> Python, the advantage of top-level functions is you can pass them as 
>> arguments to other functions.  This mirrors the behavior of the 
>> derivative operator, which Spivak likes to symbolize as D.  
>> D(f) -> g, means you take a function, f, operate on it (D), and 
>> spit back another function, g.
>
>Kirby,
>
>You may want to check out so-called "automatic differentiation".  The
>underlying idea is this:  We overload the symbols of ordinary algebra,
>thus extending algebra to the collection of ordered pairs (u, u'), where
>u is a function and u' its derivative:
>
>(u, u') + (v, v') := (u + v, u' + v');
>
>(u, u') * (v, v') := (u * v, u' * v + u * v'),
>
>etc.
>
>We embed the reals in this system:  the real number a --> (a, 0).
>
>The identity function (which we abusively write as "x") is embedded as x
>--> (x, 1).
>
>Of course, this can all be hidden from the user if we wish.
>
>Let's suppose that we have told our computer how to extend addition and
>multiplication as above, and that we've given it the image of the
>identity function too.
>
>Note what happens.  When the machine uses these definitions to calculate
>the squaring function, we get
>
>(x, 1) * (x, 1) = (x * x, 1 * x + x * 1) = (x^2, 2 x).
>
>This happens for other things as well, so we don't need to give most of
>the formal embeddings.  With what we've already done, we're ready to do
>polynomial calculus now.  With a few more rules [sin x --> (sin x, 
>cosx), the quotient rule
>
>(u, u')/(v, v') = (u/v, (u' * v - u * v')/v^2),
>
>etc.] we can do all of elementary calculus.
>

Thanks Lou.

A first take in Python:  write a Pair class to store (u, du) and define the
basic four operations, as you've defined them; write a Function class to
define the same four ops with respect to generic functions.  Even ordinary 
numbers will be considered functions which "return their own value."

class Pair:
    """
    A generic pair (f, f'), both Functions (below), and 
    rules for the four basic ops w/r to Pairs
    """

	def __init__(self, u, du):
		self.u  = u
		self.du = du

	def __add__(self, other):
		return Pair(self.u + other.u, self.du + other.du)

	def __sub__(self, other):
		return Pair(self.u - other.u, self.du - other.du)

	def __mul__(self, other):
		return Pair(self.u * other.u, \
                    self.du * other.u + self.u * other.du)

	def __div__(self, other):
		return Pair(self.u/other.u, \
                    (self.du * other.u - self.u * other.du)/other.u**2)

class Function:
    """
    A generic wrapper for uni-variable functions, allowing them to 
    be composed using the four operations
    """

	def __init__(self, f):
		self.f = lambda x:  f(x)

	def __add__(self, other):
		return Function(lambda x: self.f(x) + other.f(x))

	def __sub__(self,other):
		return Function(lambda x: self.f(x) - other.f(x))

	def __mul__(self,other):
		return Function(lambda x: self.f(x) * other.f(x))

	def __div__(self,other):
		return Function(lambda x: self.f(x) / other.f(x))

	def __call__(self,x):
        """call wrapped function with argument x, return result"""
		return self.f(x)

Now we define the identity function, and unity, per your language game.

 >>> def ident(x):  return x

 >>> id = Function(ident)  # Function-wrapped identity function

 >>> def one(x):  return 1

 >>> unity = Function(one) # Function-wrapped one

Now we define a Pair (ident, unity) corresponding to your (x, 1).

 >>> p1 = Pair(id, unity)

Multiplying p1 * p1 should return a new pair such that newpair.u is a
parabolic or 2nd powering function, and newpair.du is the derivative 
thereof.

 >>> newp = p1 * p1
 >>> [newp.u(i) for i in range(-5,6)]
 [25, 16, 9, 4, 1, 0, 1, 4, 9, 16, 25]

 >>> [newp.du(i) for i in range(-5,6)]
 [-10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10]

So far so good.  Thanks for this brain teaser and opportunity to show off
functional programming in Python.

Kirby




More information about the Edu-sig mailing list