Sequences is a central topic in mathematics:
x0, x1, x2, …, xn,…,
Example: all odd numbers
1,3,5,7,…,2n+1,…
For this sequence we have a formula for the n-th term:
xn=2n+1
and we can write the sequence more compactly as
(xn)∞n=0,xn=2n+1
1, 4, 9, 16, 25, …(xn)∞n=0, xn=n2
1, 12, 13, 14, …(xn)∞n=0, xn=1n+1
1, 1, 2, 6, 24, …(xn)∞n=0, xn=n!
1, 1+x, 1+x+12x2, 1+x+12x2+16x3, …(xn)∞n=0, xn=n∑j=0xjj!
Put x0 money in a bank at year 0. What is the value after N years if the interest rate is p percent per year?
The fundamental information relates the value at year n, xn, to the value of the previous year, xn−1:
xn=xn−1+p100xn−1
How to solve for xn? Start with x0, compute x1, x2, ...
Solve math equations by repeating a simple procedure (relation) many times (boring, but well suited for a computer!)
from scitools.std import *
x0 = 100 # initial amount
p = 5 # interest rate
N = 4 # number of years
index_set = range(N+1)
x = zeros(len(index_set))
# Solution:
x[0] = x0
for n in index_set[1:]:
x[n] = x[n-1] + (p/100.0)*x[n-1]
print x
plot(index_set, x, 'ro', xlabel='years', ylabel='amount')
Thus, we could only store the two last values in memory:
x_old = x0
for n in index_set[1:]:
x_new = x_old + (p/100.)*x_old
x_old = x_new # x_new becomes x_old at next step
However, programming with an array x[n]
is simpler, safer, and enables plotting the sequence, so we will continue to use arrays in the examples
Just a minor change in the model:
xn=xn−1+r100xn−1
How can we find the number of days between two dates?
>>> import datetime
>>> date1 = datetime.date(2007, 8, 3) # Aug 3, 2007
>>> date2 = datetime.date(2008, 8, 4) # Aug 4, 2008
>>> diff = date2 - date1
>>> print diff.days
367
from scitools.std import *
x0 = 100 # initial amount
p = 5 # annual interest rate
r = p/360.0 # daily interest rate
import datetime
date1 = datetime.date(2007, 8, 3)
date2 = datetime.date(2011, 8, 3)
diff = date2 - date1
N = diff.days
index_set = range(N+1)
x = zeros(len(index_set))
# Solution:
x[0] = x0
for n in index_set[1:]:
x[n] = x[n-1] + (r/100.0)*x[n-1]
print x
plot(index_set, x, 'ro', xlabel='days', ylabel='amount')
p
with correct interest rate for day n
Modified program:
p = zeros(len(index_set))
# fill p[n] for n in index_set (might be non-trivial...)
r = p/360.0 # daily interest rate
x = zeros(len(index_set))
x[0] = x0
for n in index_set[1:]:
x[n] = x[n-1] + (r[n-1]/100.0)*x[n-1]
The fundamental relation from one month to the text:
xn=xn−1+p12⋅100xn−1−(p12⋅100xn−1+LN)
which simplifies to
xn=xn−1−LN
(L/N makes the equation nonhomogeneous)
A fundamental relation from one year to the other is
xn=xn−1+p100xn−1−cn
Simplest possibility: keep cn constant, but inflation demands cn to increase...
xn develops with rate p but with a loss cn every year:
xn=xn−1+p100xn−1−cn−1,x0=F, c0=pq104Fcn=cn−1+I100cn−1
This is a coupled system of two difference equations, but the programming
is still simple: we update two arrays, first x[n]
, then c[n]
, inside the loop
(good exercise!)
No programming or math course is complete without an example on Fibonacci numbers:
xn=xn−1+xn−2,x0=1, x1=1
This is a homogeneous difference equation of second order (second order means three levels: n, n−1, n−2). This classification is important for mathematical solution technique, but not for simulation in a program.
Fibonacci derived the sequence by modeling rat populations, but the sequence of numbers has a range of peculiar mathematical properties and has therefore attracted much attention from mathematicians.
N = int(sys.argv[1])
from numpy import zeros
x = zeros(N+1, int)
x[0] = 1
x[1] = 1
for n in range(2, N+1):
x[n] = x[n-1] + x[n-2]
print n, x[n]
Run the program with N=50:
2 2
3 3
4 5
5 8
6 13
...
45 1836311903
Warning: overflow encountered in long_scalars
46 -1323752223
Note:
int
to long
or int64
for array elements allows N≤91float96
(though xn is integer): N≤23600
int
- these automatically changes to long
when overflow in int
long
type in Python has arbitrarily many digits (as many as required in a computation!)long
for arrays is 64-bit integer (int64
), while scalar long
in Python is an integer with as "infinitely" many digits
The program now avoids arrays and makes use of three int
objects (which automatically changes to long
when needed):
import sys
N = int(sys.argv[1])
xnm1 = 1 # "x_n minus 1"
xnm2 = 1 # "x_n minus 2"
n = 2
while n <= N:
xn = xnm1 + xnm2
print 'x_%d = %d' % (n, xn)
xnm2 = xnm1
xnm1 = xn
n += 1
Run with N=200:
x_2 = 2
x_3 = 3
...
x_198 = 173402521172797813159685037284371942044301
x_199 = 280571172992510140037611932413038677189525
x_200 = 453973694165307953197296969697410619233826
Limition: your computer's memory
The model for growth of money in a bank has a solution of the type
xn=x0Cn(=x0enlnC)
Note:
Initially, when there are enough resources, the growth is exponential:
xn=xn−1+r100xn−1
The growth rate r must decay to zero as xn approaches M. The simplest variation of r(n) is a linear:
r(n)=ϱ(1−xnM)
Observe: r(n)≈ϱ for small n when xn≪M, and r(n)→0 as xn→M and n is big
xn=xn−1+ϱ100xn−1(1−xn−1M)
(This is a nonlinear difference equation)
In a program it is easy to introduce logistic instead of exponential growth, just replace
x[n] = x[n-1] + p/100.0)*x[n-1]
by
x[n] = x[n-1] + (rho/100.0)*x[n-1]*(1 - x[n-1]/float(M))
The factorial n! is defined as
n(n−1)(n−2)⋯1,0!=1
The following difference equation has xn=n! as solution and can be used to compute the factorial:
xn=nxn−1,x0=1
x[0] = x0
in the program, you get x0=0
(because x = zeroes(N+1)
),
which (usually) gives unintended results!
sinx=∞∑k=0(−1)kx2k+1(2k+1)!
f(x)=∞∑k=01k!(dkdxkf(0))xk
For "any" f(x), if we can differentiate, add, and multiply xk,
we can evaluate f at any x (!!!)
f(x)≈N∑k=01k!(dkdxkf(0))xk
N=1 is very popular and has been essential in developing physics and technology
ex=∞∑k=0xkk!≈1+x+12x2+16x3≈1+x
The previous Taylor polynomials are most accurate around x=0. Can make the polynomials accurate around any point x=a:
f(x)≈N∑k=01k!(dkdxkf(a))(x−a)k
The Taylor series for ex around x=0 reads
ex=∞∑n=0xnn!
Define
en=n−1∑k=0xkk!=n−2∑k=0xkk!+xn−1(n−1)!
We can formulate the sum in en as the following difference equation:
en=en−1+xn−1(n−1)!,e0=0
Observe:
xnn!=xn−1(n−1)!⋅xn
Let an=xn/n!. Then we can efficiently compute an via
an=an−1xn,a0=1
Now we can update each term via the an equation and sum the terms via the en equation:
en=en−1+an−1,e0=0, a0=1an=xnan−1
See the book for more details
f(x)=0
ax+b=0 ax2+bx+c=0 sinx+cosx=1
Simpson (1740) came up with the following general method for solving f(x)=0 (based on ideas by Newton):
xn=xn−1−f(xn−1)f′(xn−1),x0 given
Note:
def Newton(f, x, dfdx, epsilon=1.0E-7, max_n=100):
n = 0
while abs(f(x)) > epsilon and n <= max_n:
x = x - f(x)/dfdx(x)
n += 1
return x, n, f(x)
Note:
f(x)/dfdx(x)
can give integer divisionx
and f(x)
values in each iteration (for plotting or printing a convergence table)
Only one f(x) call in each iteration, optional storage of (x,f(x)) values during the iterations, and ensured float division:
def Newton(f, x, dfdx, epsilon=1.0E-7, max_n=100,
store=False):
f_value = f(x)
n = 0
if store: info = [(x, f_value)]
while abs(f_value) > epsilon and n <= max_n:
x = x - float(f_value)/dfdx(x)
n += 1
f_value = f(x)
if store: info.append((x, f_value))
if store:
return x, info
else:
return x, n, f_value
from math import sin, cos, exp, pi
import sys
def g(x):
return exp(-0.1*x**2)*sin(pi/2*x)
def dg(x):
return -2*0.1*x*exp(-0.1*x**2)*sin(pi/2*x) + \
pi/2*exp(-0.1*x**2)*cos(pi/2*x)
x0 = float(sys.argv[1])
x, info = Newton(g, x0, dg, store=True)
print 'Computed zero:', x
# Print the evolution of the difference equation
# (i.e., the search for the root)
for i in range(len(info)):
print 'Iteration %3d: f(%g)=%g' % (i, info[i][0], info[i][1])
x0=1.7 gives quick convergence towards the closest root x=0:
zero: 1.999999999768449
Iteration 0: f(1.7)=0.340044
Iteration 1: f(1.99215)=0.00828786
Iteration 2: f(1.99998)=2.53347e-05
Iteration 3: f(2)=2.43808e-10
Start value x0=3 (closest root x=2 or x=4):
zero: 42.49723316011362
Iteration 0: f(3)=-0.40657
Iteration 1: f(4.66667)=0.0981146
Iteration 2: f(42.4972)=-2.59037e-79
Try the demo program src/diffeq/Newton_movie.py
with x0=3,
x∈[−2,50] for plotting and numerical approximation of f′(x):
Terminal> python Newton_movie.py "exp(-0.1*x**2)*sin(pi/2*x)" \
numeric 3 -2 50
Newton's method may work fine or give wrong results! You need to understand the method to interpret the results!
A tone A (440 Hz) is a sine wave with frequency 440 Hz:
s(t)=Asin(2πft),f=440
On a computer we represent s(t) by a discrete set of points on the function curve (exactly as we do when we plot s(t)). CD quality needs 44100 samples per second.
Sampled sine function for this tone:
sn=Asin(2πfnr),n=0,1,…,m⋅r
Code (we use descriptive names: frequency
f, length
m, amplitude
A, sample_rate
r):
import numpy
def note(frequency, length, amplitude=1,
sample_rate=44100):
time_points = numpy.linspace(0, length,
length*sample_rate)
data = numpy.sin(2*numpy.pi*frequency*time_points)
data = amplitude*data
return data
data
as an array with float
and unit amplitudeint16
) as data elements and amplitudes up to 215−1 (max value for int16
data)
data = note(440, 2)
data = data.astype(numpy.int16)
max_amplitude = 2**15 - 1
data = max_amplitude*data
import scitools.sound
scitools.sound.write(data, 'Atone.wav')
scitools.sound.play('Atone.wav')
s[n]
# echo: e[n] = beta*s[n] + (1-beta)*s[n-b]
def add_echo(data, beta=0.8, delay=0.002,
sample_rate=44100):
newdata = data.copy()
shift = int(delay*sample_rate) # b (math symbol)
for i in xrange(shift, len(data)):
newdata[i] = beta*data[i] + (1-beta)*data[i-shift]
return newdata
Load data, add echo and play:
data = scitools.sound.read(filename)
data = data.astype(float)
data = add_echo(data, beta=0.6)
data = data.astype(int16)
scitools.sound.play(data)
data1
, data2
, ...:
# put data1, data2, ... after each other in a new array:
data = numpy.concatenate((data1, data2, data3, ...))
The start of "Nothing Else Matters" (Metallica):
E1 = note(164.81, .5)
G = note(392, .5)
B = note(493.88, .5)
E2 = note(659.26, .5)
intro = numpy.concatenate((E1, G, B, E2, B, G))
...
song = numpy.concatenate((intro, intro, ...))
scitools.sound.play(song)
scitools.sound.write(song, 'tmp.wav')
Solution of difference equations by simulation:
index_set = <array of n-values: 0, 1, ..., N>
x = zeros(N+1)
x[0] = x0
for n in index_set[1:]:
x[n] = <formula involving x[n-1]>
Can have (simple) systems of difference equations:
for n in index_set[1:]:
x[n] = <formula involving x[n-1]>
y[n] = <formula involving y[n-1] and x[n]>
Taylor series and numerical methods such as Newton's method can be formulated as difference equations, often resulting in a good way of programming the formulas
scitools.sound
to generate tonesWe will study two sequences:
xn=e−4n/Nsin(8πn/N)
and
xn=xn−1+qxn−1(1−xn−1),x=x0
The first has values in [−1,1], the other from x0=0.01 up to around 1
Transformation from "unit" xn to frequencies:
yn=440+200xn
(first sequence then gives tones between 240 Hz and 640 Hz)
files/soundeq.py
for complete codeTry it out in these examples:
Terminal> python soundseq.py oscillations 40
Terminal> python soundseq.py logistic 100
Try to change the frequency range from 200 to 400.