Conceptual Questions (3 Points)

  1. Is Error Propogation using partial derivatives exact or an approximation? If it is an approximation, when and why is it valid?

  2. If you have a constant error in your independent variable, you almost always require weighted least-squares instead of ordinary least-squares for non-linear regression. Why?

  3. Is attenuation possible in non-linear weighted least-squares?

Answers to Ex1

  1. It is an approximation. It is valid when the error is small relative to the partial derivatives. This is valid, because the error is smaller than the curvature of the function, making it approximately linear.
  2. Ordinary least squares are not good for non-linear behavior of varaibles, whereas weighted leas squares work both for linear anc non-linear functions.
  3. No, the weighted least-squares takes care of the attenuation by not mentioning the sample size in the formula for the error.????

Exercises (8 Points)

  1. If you have a measurement error of $\sigma_{\lambda} = 2.0$ and you are using the equation:

    $$ y = \lambda e^{-\lambda x}$$

    what is your uncertainty in $y$? Assume $x$ is exact.

  2. What is the equation for standard error in residuals with constant uncertainty in the independent variable?

  3. What is the equation for standard error in residuals with variable uncertainty in the dependent variable?

  4. Give two examples of when to use weighted least-squares

Answers to EX2

2.1 $$\sigma_{y}=\frac{\delta y}{\delta\lambda}\sigma_{\lambda}=\sqrt{4(e^{-\lambda x}-\lambda xe^{-\lambda x})^{2}}$$

2.2 $$ S^2_{\epsilon} = \frac{SSR}{N} + \hat{\beta}^2\sigma_{\eta}^2 $$

2.3 $$ S^2_{\epsilon} =\frac{SSR + \sum_i\sigma_{x_i}^2\beta^2}{N} $$

2.4

Arrhenius Equation Problem (20 Points)

The Arrhenius Equation allows you to find a reaction rate at a temperature. It is:

$$ k = A e^{-\frac{E_a}{RT}} $$

where $k$ is the reaction rate, $A$ is an empirical constant, $E_a$ is the activation energy, $R$ is the Universal gas constant and $T$ is temperature. In this homework problem, you will determine $A$ and $E_a$ and their confidence intervals.

The chemical reaction that you're observing is the unimolecular dissolution of a 125 Dalton compound in water. This reaction occurs over the course of days inside a large tank which has a controlled temperature. You take an approximately 250mL sample every four hours from the tank and can compute a concentration of the compound by drying the water off and weighing the dry mass. The measurement process takes about 10 minutes and so you may estimate your measurement error in time as 5 minutes, since the reaction continues during the drying process and taking the sample requires some time. Your volume measurements have a measurement error of 0.25 mL and your balance has a measurement error of 0.5 g. You know the temperature exactly.

Knowing the information above, you may fit your concentration data vs time to an exponential decay:

$$C(t) = C_0 e^{-kt}$$

and thus compute a reaction rate. Using reaction rates at four temperatures, you may estimate the activation energy and pre-exponential empricial factor $A$ from the Arrhenius equation. Carefully propogate error at all steps and ensure your final confidence interval includes error from the measurements and the regression. Report the reaction rate in mol / s$\cdot$L and activation energy in kJ. Also, plot concentration vs time for one experiment with error bars on the data and your best fit line. Also plot the best-fit Arrhenius equation and your data with error bars. As a hint, here's the basic workflow of this problem:

  1. Compute concentration and error in concentration at each temperature
  2. Fit the exponential equation above to the concentration vs time for each temperature to get a rate
  3. Compute the error in that rate for each tempeture
  4. Put the rates and temperatures onto one plot
  5. Fit the Arrhenius equation to that plot
  6. Compute the error in the Arrhenius plot terms

Hints: you'll need to use basinhopping to do the minimization. Also, you should have variable measurement error for concentration due to error propogation.

$T = 25^\circ$C

  • volume in mL
  • mass in g
  • time in minutes

In [2]:
%matplotlib inline
import numpy as np
import scipy
import matplotlib.pyplot as plt

import numpy as np

volume_25 = np.array([276.1, 240.5,  277.6,  260.3,  296.0,  237.1,  247.5,  263.2,  232.2,  264.1,  236.0,  262.2,  293.4,  268.6,  222.7,  280.5,  239.3,  267.6,  242.6,  225.5,  278.0])
mass_25 = np.array([ 13.8,  10.1,   12.8,   12.3,   12.8,    9.7,    9.2,   10.3,    8.2,    8.1,    7.8,    8.1,    8.4,    7.6,    5.3,    7.4,    5.2,    5.4,    5.2,    4.2,    4.9])
time = np.array([0, 480,  960,  1440,  1920,  2400,  2880,  3360,  3840,  4320,  4800,  5280,  5760,  6240,  6720,  7200,  7680,  8160,  8640,  9120,  9600])

Let's first compute the measurement error in concentration due to our error in volume and mass:

$$c = f(m,v) = \frac{m}{MW v}$$

Notice we do not need to consider molecular weight, since it is known exactly.

$$\sigma_{c}^2 = \left(\frac{\partial f(m, v)}{\partial v}\right)^2 \sigma_v^2 + \left(\frac{\partial f(m, v)}{\partial m}\right)^2 \sigma_m^2$$$$\sigma_c^2 = \frac{m^2}{MW^2 v^4}\sigma_v^2 + \frac{1}{MW^2 v^2}\sigma_m^2$$

In [3]:
#Setup constants and divens
MW = 125.0
terr = 5.0
merr = 0.5
verr = 0.25
#compute concentration and error in concentration 
c_25 = mass_25 / volume_25 * (1000 / MW)
c_25_err = np.sqrt( 1.0 / (volume_25)**2 * merr**2 + mass_25**2 / volume_25**4 * verr**2 ) * 1000.0 / MW
print c_25_err, c_25
#plot em
plt.errorbar(time, c_25, xerr=terr, yerr=c_25_err, capthick=3)
plt.ylabel('Concentration [mol/L]')
plt.xlabel('Time [minutes]')
plt.show()


[ 0.01449203  0.01663568  0.01441305  0.01537117  0.01351667  0.01687405
  0.01616441  0.01520048  0.01722921  0.01514756  0.01695147  0.01525735
  0.01363466  0.01489352  0.01796265  0.01426149  0.01671641  0.01494844
  0.01648899  0.01773913  0.01438905] [ 0.39985512  0.33596674  0.36887608  0.37802536  0.34594595  0.32728806
  0.29737374  0.31306991  0.28251507  0.24536161  0.26440678  0.24713959
  0.22903885  0.2263589   0.19039066  0.21105169  0.17384037  0.16143498
  0.17147568  0.14900222  0.14100719]

For the analysis, we have two adjustable parameters: $C_0$ and $k$. This requires weighted-least squares in the non-linear regression setting. First, let's compute our weights. The equation is:

$$W_i = \frac{1}{\sigma^2_{c_i} + \sigma_{f(t_i)}^2}$$$$\sigma_{f(x_i)}^2 = \left(\frac{\partial C_0e^{-kt}}{\partial t}\right)^2 \sigma_{t}^2 = C_0^2k^2e^{-2kt} \sigma_t^2$$

We'll just use $\hat{C}_0$ as our first concentration measurement. However, we cannot forget it is an adjustable parameter in our error analysis.


In [4]:
import scipy.stats
#This our weighted SSR following the derivation above.
def WSSR(k, t, c, terr, cerr):
    w = 1.0 / (cerr**2 + terr**2 * (k * np.exp(-k * t))**2)
    chat = c[0] * np.exp(-k * t)
    return np.sum(w*(chat - c)**2)


#WE need to bound this and use basin_hopping because it is a non-linear problem
mini_args = {'bounds': [(10**-10, 10)]}
result = scipy.optimize.basinhopping(lambda s: WSSR(s, time, c_25, terr, c_25_err), x0=0.1, minimizer_kwargs=mini_args, niter=1000)
print result

#now get the predicted chats and compare 
chat = c_25[0] * np.exp(-result.x * time)
plt.errorbar(time, c_25, xerr=terr, yerr=c_25_err, capthick=3)
plt.plot(time, chat)
plt.show()


                  nfev: 47238
 minimization_failures: 2
                   fun: 24.759824922716565
                     x: array([  9.80721733e-05])
               message: ['requested number of basinhopping iterations completed successfully']
                   nit: 1000
/usr/lib/python2.7/dist-packages/scipy/optimize/_basinhopping.py:290: RuntimeWarning: overflow encountered in exp
  w = min(1.0, np.exp(-(energy_new - energy_old) * self.beta))

We need to find the standard error in the fit rate. We start by building our $\mathbf{F}$ matrix:

$$F_{ij} = \left. \frac{\partial f(\beta, x)}{\partial \beta_j}\right|_{x=x_i}$$$$F_{i1} = \left. \frac{\partial (C_0e^{-kt})}{\partial k}\right|_{t=t_i} = -t_iC_0e^{-kt_i}$$$$F_{i2} = \left. \frac{\partial (C_0e^{-kt})}{\partial C_0}\right|_{t=t_i} = e^{-kt_i}$$

Recall, that even though we just used our first concentration point as our estimate for $\hat{C}_0$, we have to propogate error in that measurement. Next we need the standard error in the residuals:

$$S_{\epsilon}^2 = \frac{\sigma_{\epsilon}^2 + \frac{1}{N}\sum_i^N \sigma_{f(x_i)}^2 + \frac{1}{N} \sum_i^N \sigma_c^2}{N - 2}$$

where $\sigma_\epsilon^2 = \frac{1}{N}\sum_i^N (\hat{y} - y)^2$

The $-2$ is OK to omit, but accounts for our degrees of freedom. We need to find the variance in $f(x_i)$ term, which we already found for the weights. It is:

$$\sigma_{f(x_i)}^2 = \left(\frac{\partial C_0e^{-kt}}{\partial t}\right)^2 \sigma_{t}^2 = C_0^2k^2e^{-2kt} \sigma_t^2$$

Now, combining the $\mathbf{F}$ matrix with the standard error in the residuals:

$$S_{\beta}^2 = S_{\epsilon}^2(\mathbf{F}^T\mathbf{F})^{-1}$$

For grading purposes, it's ok to only do error in k but notice that will slightly change the uncertainty values


In [5]:
import numpy.linalg as linalg

#Get the best fit
khat = result.x
#Now we computer error in residuals
s2_e = (np.sum((chat - c_25)**2) + np.sum(c_25_err**2) + np.sum((c_25[0] * khat * np.exp(-khat * time) * terr)**2)) / (len(time) - 2)
print s2_e
#Stack our f-matrix.
F_mat = np.column_stack( (-time * c_25[0] * np.exp(-khat * time), np.exp(-khat * time) ) )
#and now compute uncertainty
s2_beta = s2_e * linalg.inv(F_mat.transpose().dot(F_mat))
print khat, np.sqrt(s2_beta[0, 0])
khat_25 = khat
kerr_25 = np.sqrt(s2_beta[0,0])


0.000598825916206
[  9.80721733e-05] 7.46021701307e-06

Now, let's turn this into a function to make it easier


In [7]:
#Givens

MW = 125.0
terr = 5.0
merr = 0.5
verr = 0.25

#The SSR function to minimize
def WSSR(k, t, c, terr, cerr):
    w = 1.0 / (cerr**2 + terr**2 * (k * np.exp(-k * t))**2)
    chat = c[0] * np.exp(-k * t)
    return np.sum(w*(chat - c)**2)


def regress_data(time, volume, mass):
    
    #compute c and error
    c = mass / volume * (1000 / MW)
    c_err = np.sqrt( 1.0 / (volume)**2 * merr**2 + mass**2 / volume**4 * verr**2 ) * 1000.0 / MW
    
    #compute the best-fit
    mini_args = {'bounds': [(10**-10, 10)]}
    result = scipy.optimize.basinhopping(lambda s: WSSR(s, time, c, terr, c_err), x0=0.1, minimizer_kwargs=mini_args, niter=1000)
    
    #plot it and compute chats    
    chat = c[0] * np.exp(-result.x * time)
    plt.plot(time, chat)
    plt.errorbar(time, c, xerr=terr, yerr=c_err, capthick=3)
    plt.ylabel('Concentration [mol/L]')
    plt.xlabel('Time [minutes]')
    plt.show()
    
    #uncertainty analysis
    khat = result.x
    s2_e = (np.sum((chat - c)**2) + np.sum(c_err**2) + np.sum((c[0] * khat * np.exp(-khat * time) * terr)**2)) / (len(time) - 2)
    F_mat = np.column_stack( (-time * c[0] * np.exp(-khat * time), np.exp(-khat * time) ) )
    #print F_mat
    s2_beta = s2_e * linalg.inv(F_mat.transpose().dot(F_mat))
    return khat, np.sqrt(s2_beta[0, 0])

$T = 30^\circ$C

  • volume in mL
  • mass in g
  • time in minutes

In [8]:
volume_30 = np.array([243.7, 262.7,  247.4,  224.4,  225.0,  298.5,  233.0,  285.7,  227.5,  243.3,  240.3,  276.1,  276.4,  228.0,  245.2,  236.0,  261.1,  243.3,  275.9,  239.5,  208.0])
mass_30 = np.array([ 12.1,  12.4,    9.0,    8.5,    7.3,    8.3,    4.8,    5.3,    3.3,    3.6,    2.6,    2.6,    2.4,    1.5,    2.4,    1.2,    2.3,    2.0,    2.2,    0.9,    0.4])

In [9]:
khat_30, kerr_30 = regress_data(time, volume_30, mass_30)


$T = -10^\circ$C

  • volume in mL
  • mass in g
  • time in minutes

In [10]:
volume_10 = np.array([254.1, 280.5,  265.7,  233.8,  258.8,  239.6,  279.0,  194.8,  285.9,  278.6,  281.0,  302.7,  265.3,  209.9,  278.0,  213.2,  243.3,  274.4,  238.9,  259.3,  251.6])
mass_10 = np.array([ 12.8,  13.6,   12.5,   11.4,   13.4,   12.5,   12.7,   10.2,   14.0,   13.7,   13.1,   14.2,   12.8,    9.2,   13.0,    8.9,   11.7,   12.8,   10.6,   12.2,   11.1])

In [11]:
khat_m10, kerr_m10 = regress_data(time, volume_10, mass_10)


$T = 8^\circ$C

  • volume in mL
  • mass in g
  • time in minutes

In [12]:
volume_8 = np.array([249.3, 251.0,  202.6,  291.9,  251.6,  214.1,  272.5,  257.8,  274.7,  261.7,  227.6,  260.9,  295.4,  277.4,  237.2,  260.8,  243.0,  249.6,  258.0,  260.6,  272.4])
mass_8 = np.array([ 12.8,  12.3,    9.6,   14.2,   11.8,   10.1,   12.0,   11.0,   11.4,   11.1,    9.2,   11.0,   11.4,   11.2,    9.6,   10.1,   10.2,    9.2,    9.2,    8.8,   10.5])

In [13]:
khat_8, kerr_8 = regress_data(time, volume_8, mass_8)


Now combine the results. Remember, that the temperature must be in an absolute scale


In [16]:
#Put together our data
k = np.array([khat_m10, khat_8, khat_25, khat_30])
k = np.reshape(k, (4,))
kerr = np.array([kerr_m10, kerr_8, kerr_25, kerr_30])
temp = np.array([-10, 8, 25, 30]) + 273.15

#Convert to kJ/ mol
R = 8.314 / 1000.0

#our new WSSR. Notice our weights do not have partial derivatives, since we have no dependent variable uncertainty.
def WSSR(A, E_a, temp, k, kerr):
    w = 1.0  / kerr**2
    khat = A * np.exp(-E_a / (R * temp) )
    ssr = np.sum( w * (khat - k)**2)
    return ssr

#Again, ust use bounded optimization
mini_args = {'bounds': [(10**-10, 10**10), (10, 1000000)]}
result = scipy.optimize.basinhopping(lambda x: WSSR(x[0], x[1], temp, k, kerr), x0=[1,1], minimizer_kwargs=mini_args, niter=10000)
print result


#Get estimated values for the fit
A = result.x[0]
E_a = result.x[1]
khat = A * np.exp(-E_a / (R * temp) )

#plot it

plt.errorbar(temp, k, fmt='o',  yerr=kerr)
plt.plot(temp, khat)

plt.show()


                  nfev: 243248
 minimization_failures: 9
                   fun: 41.681447726782316
                     x: array([  1.60981213e+05,   5.20174199e+01])
               message: ['requested number of basinhopping iterations completed successfully']
                   nit: 10000

In [17]:
#The math is not shown here, but it's very similar to above (same equation).

#Nootice we don't have partials, just the independent variable uncertainty.
s2_e = (np.sum((khat - k)**2) + np.sum(kerr**2)) / (len(k) - 2)#subtract 2 because we have two adjustable parameters

#stuck up the F-matrix
F_overall_mat = np.column_stack( (np.exp(-E_a / (temp * R)),  -A / (temp * R) * np.exp(-E_a / (temp * R)) ) )
print F_overall_mat
print s2_e
s2_beta = s2_e * linalg.inv(F_overall_mat.transpose().dot(F_overall_mat))

#print results
print A, np.sqrt(s2_beta[0,0]) * scipy.stats.t.ppf(0.975, len(k) - 2)
print E_a, np.sqrt(s2_beta[1,1]) * scipy.stats.t.ppf(0.975, len(k) - 2)


[[  4.72383536e-11  -3.47581420e-06]
 [  2.16458647e-10  -1.49074042e-05]
 [  7.69901616e-10  -4.99994920e-05]
 [  1.08830262e-09  -6.95115988e-05]]
5.53353513672e-09
160981.212783 16154348.9907
52.017419883 250.97548581

So our final answer is $$E_a = 52 \pm 251 \textrm{kJ/mol}$$

$$A = 200,000 \pm 10,000,000 \frac{1}{\textrm{minutes}}$$

For grading, the $E_a$ is all that matters. The uncertainty value is very sensitive to the value of $E_a$ and $A$, so be easy on them. the most important part is it's done correctly, the error bars are correct on the plot, and that they have near the correct $E_a$ value


In [ ]: