## Wednesday, April 7, 2010

### How to fit exponential decay – An example in Python

Linear least squares can be used to fit an exponent. However, the linear least square problem that is formed, has a structure and behavior that requires some careful consideration to fully understand. Usually, fitting is used because the data is noisy. If only the number of data points equal to the number of free variables in system of equations is used, the estimate of parameters will generally be poor.

For example, a common problem is estimating the parameters or coefficients for cooling. For example, a mass is heated to a steady temperature, then left to cool. Ignoring a lot of detail, a model of this behavior can be described by a simple first order, ordinary differential equation:

In this equation, T is the temperature of the object, T0 is the ambient temperature, and h is a coefficient of hear transfer. When T0 is held constant and T(t=0) is not equal to T0, T(t) is described by an exponential decay function.

An exponential decay function is

For a system whose behavior can be defined by exponential decay, the parameters for the decay function can be found using least-squares. Since the data usually has measurement errors, the measured data from an exponential decay will usually contain an error term.

Ideally, this equation could  be directly set up as a linear least squares problem.   However, minimizing the norm of epsilon, requires solution via methods other than linear least squares. To formulate this problem as a linear least squares minimization, a new error term inside the exponent is introduced, del.

The usual way to set this problem up is to minimize the norm of epsilon.

However, if the problem is set up to minimize the 2-norm of del, then a linear least squared minimization can be formed.

To linearize this problem, the terms in the constraints are rearranged, the natural log of each side is taken, and the properties of logarithms are isolate terms.

The problem statement is simplified by eliminating the epsilon term.

Furthermore, this constrained optimization problem is restated as an unconstrained optimization problem.

Least squares can be used to solve this problem.

The reason for this development is to understand what is really solved by this formulation. When this technique is used to solve for an exponential delay function’s parameters, the measurement errors are not minimized. An artificial term which resembles an error in time is minimized.

The following python code shows how to solve this kind of problem.

from pylab import *
from math import log

def fitExponent(tList,yList,ySS=0):
'''
This function finds a
tList in sec
yList - measurements
ySS - the steady state value of y
returns
amplitude of exponent
tau - the time constant
'''
bList = [log(max(y-ySS,1e-6)) for y in yList]
b = matrix(bList).T
rows = [ [1,t] for t in tList]
A = matrix(rows)
#w = (pinv(A)*b)
(w,residuals,rank,sing_vals) = lstsq(A,b)
tau = -1.0/w[1,0]
amplitude = exp(w[0,0])
return (amplitude,tau)

if __name__=='__main__':
import random

tList = arange(0.0,1.0,0.001)
tSamples = arange(0.0,1.0,0.2)
random.seed(0.0)
tau = 0.3
amplitude = 3
ySS = 3
yList = amplitude*(exp(-tList/tau))+ySS
ySamples = amplitude*(exp(-tSamples/tau))+ySS
yMeasured = [y+random.normalvariate(0,0.05) for y in ySamples]
#print yList
(amplitudeEst,tauEst) = fitExponent(tSamples,yMeasured,ySS)
print ('Amplitude estimate = %f, tau estimate = %f'
% (amplitudeEst,tauEst))

yEst = amplitudeEst*(exp(-tList/tauEst))+ySS

figure(1)
plot(tList,yList,'b')
plot(tSamples,yMeasured,'+r',markersize=12,markeredgewidth=2)
plot(tList,yEst,'--g')
xlabel('seconds')
legend(['True value','Measured values','Estimated value'])
grid(True)
show()