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
Note that instead of wrapping my little functions with Function, per previous post, as of Python 2.4 I have the new decorator syntax with which to accomplish the same thing: #--- calc.py class Function: 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) class Pair: 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) @Function def ident(x): return x @Function def one(x): return 1 p1 = Pair(ident, one) newp = p1 * p1 * p1 print [newp.u(i) for i in range(-5,6)] print [newp.du(i) for i in range(-5,6)] #--- Testing:
import calc [-125, -64, -27, -8, -1, 0, 1, 8, 27, 64, 125] [75, 48, 27, 12, 3, 0, 3, 12, 27, 48, 75]
i.e. I get back the functions u(x) = x**3 and du(x) = 3*x**2, per differential calculus. Kirby
Kirby writes -
#--- calc.py
snip
p1 = Pair(ident, one)
newp = p1 * p1 * p1
print [newp.u(i) for i in range(-5,6)] print [newp.du(i) for i in range(-5,6)]
#---
Testing:
import calc [-125, -64, -27, -8, -1, 0, 1, 8, 27, 64, 125] [75, 48, 27, 12, 3, 0, 3, 12, 27, 48, 75]
i.e. I get back the functions u(x) = x**3 and du(x) = 3*x**2, per differential calculus.
Finally had a chance to focus on this. Kinda nice. Just set it to music - i.e. graph it - and there lot's to see as well, for those of us who find that added visual element essential for grasping what is happening, and preventing glazing ove. Are you going to take it that far, or shall I. Art
participants (2)
-
Arthur
-
Kirby Urner