# [Edu-sig] More Pythonic Mathematics

Kirby Urner urnerk at qwest.net
Thu Jul 28 20:06:50 CEST 2005

```This morning I posted to the Math Forum about how an algebra student, armed
with Python, might explore 'composition of functions' using operator

Wrap ordinary function definitions in the Function class below, and you
still have __call__ (to get range outputs from domain inputs), plus you have
the ability to write h = f * g where f and g and Function objects, and h is
a new function object such that h(x) == f(g(x)) for all x.

An important aspect of algebra is polymorphism around * and +
(multiplication and addition).  These become known in terms of their field,
ring and group properties, somewhat obscuring their origins in basic
arithmetic.  We multiply matrices and polynomials, quaternions and
going on:  we're putting the definition of __mul__ under the
programmer-mathematician's control

One of the most primitive of all functions is the mapping of letters to
letters, e.g. A->R, B->Q, C->C, D->Z... according to whatever random
shuffle.  The composition of P1 and P2 then becomes the result of mapping A
to R per P2, then R to whatever per P1 (say Z), such that A->Z is the result
of P1 * P2.  The notions of inverse and identity mapping are well defined.
Group properties may be explored (the beginning of abstract algebra).

Notice that my algebraic functions below (a quadratic and a linear) need to
have their inverses specified by the programmer, should it be that negative
powering is likely to happen (for example).  Some languages attempt to
provide this automatically in simple cases, leaving it to the user to supply
it otherwise (I'm thinking of J in particular).  I'm not trying to be that
fancy.

Kirby

Relevant post to math-teach:
http://mathforum.org/kb/plaintext.jspa?messageID=3863728

#=====================================

>>> def f(x): return x*x

>>> def g(x): return 2*x + 3

>>> class Function(object):

def __init__(self, f, invf = None):
self.f = f
self.invf = invf

def __call__(self,x):
return self.f(x)

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

def __pow__(self, exp):
if exp==1: return self
newself = Function(self.f)
if exp < 1:
newself = Function(self.invf)
for i in range(exp-1):
newself *= self
return newself

#--- composition of function objects using __mul__

>>> of = Function(f)
>>> og = Function(g)
>>> oh = of * og
>>> oh(10)
529
>>> oj = og * of
>>> oj(10)
203

>>> import math
>>> def invf(x):  return math.sqrt(x)

#--- expanding to integer powers (including negative powers).

>>> of = Function(f, invf)
>>> newf = of**(-1)
>>> h = newf * of
>>> h(10)
10.0
>>> h(20)
20.0

```