import numpy as np
from scipy.stats import linregress
import matplotlib.pyplot as plt
# Data
= 0.5 # mol/dm^3
C_B0
= np.array([0, 50, 100, 150, 200, 250, 300])
t = np.array([0.05, 0.038, 0.0306, 0.0256, 0.0222, 0.0195, 0.0174])
C_A
= C_A[0]
C_A0
# fit polynomial to the data
= np.polyfit(t, C_A, deg=4)
coefficients = np.poly1d(coefficients)
polynomial
= np.linspace(t.min(), t.max(), 100)
t_fit = polynomial(t_fit)
ca_fit
'o', label='Experimental data')
plt.plot(t, C_A, '-', label='Polynomial fit')
plt.plot(t_fit, ca_fit , 'time (min)')
plt.xlabel('Concentration')
plt.ylabel('Raw data')
plt.title(
plt.legend()True)
plt.grid(0,300)
plt.xlim(
plt.show()
In class activity: Collection and analysis of rate data
Lecture notes for chemical reaction engineering
Determining the Rate Law
The liquid-phase reaction of triphenyl methyl chloride (trityl) (A) and methanol (B)
was carried out in a batch reactor at 25°C in a solution of benzene and pyridine in an excess of methanol . (We need to point out that this batch reactor was purchased at the Sunday market in Rijca, Jofostan.) Pyridine reacts with HCl, which then precipitates as pyridine hydro-chloride thereby making the reaction irreversible. The reaction is first order in methanol. The concentration of triphenyl methyl chloride (A) was measured as a function of time and is shown below (Table 1)
Determine the reaction order with respect to triphenyl methyl chloride.
In a separate set of experiments, the reaction order wrt methanol was found to be first order. Determine the specific reaction-rate constant.
Part (1) Find the reaction order with respect to trityl.
Step 1 Postulate a rate law.
Step 2 Process your data in terms of the measured variable, which in this case is .
Step 3 Look for simplifications. Because the concentration of methanol is 10 times the initial concentration of triphenyl methyl chloride, its concentration is essentially constant
Substituting for in Equation 1
Step 4 Apply the CRE algorithm.
Mole Balance
Rate Law:
Stoichiometry: Liquid
Combine: Mole balance, rate law, and stoichiometry
Evaluate: Taking the natural log of both sides of Equation 2
The slope of a plot of versus will yield the reaction order α with respect to triphenyl methyl chloride (A)
# calculate the derivative of the polynomial
= np.polyder(polynomial)
dca_dt
= np.log(C_A)
ln_ca = np.log(-dca_dt(t))
ln_dca_dt
# fit line
= linregress(ln_ca, ln_dca_dt)
res = res.slope * ln_ca + res.intercept
line
'o', label='Differential analysis')
plt.plot(ln_ca, ln_dca_dt, '-', label='fitted line')
plt.plot(ln_ca, line,
f'Slope = {res.slope:.2e}\nIntercept = {res.intercept:.2f}\n$R^2$ = {res.rvalue**2:.3f}',
plt.annotate(=(0.5, 0.15), xycoords='axes fraction', fontsize=12)
xy
'$\ln C_A$')
plt.xlabel('$\ln(\\frac{-dC_A}{dt})$')
plt.ylabel(
plt.legend()True)
plt.grid(
plt.show()
# pick the closest round order
= round(res.slope)
order = np.exp(res.intercept)
a = a /C_B0 k
<>:17: SyntaxWarning: invalid escape sequence '\l'
<>:18: SyntaxWarning: invalid escape sequence '\l'
<>:17: SyntaxWarning: invalid escape sequence '\l'
<>:18: SyntaxWarning: invalid escape sequence '\l'
C:\Users\Ranjeet\AppData\Local\Temp\ipykernel_16332\3516368603.py:17: SyntaxWarning: invalid escape sequence '\l'
plt.xlabel('$\ln C_A$')
C:\Users\Ranjeet\AppData\Local\Temp\ipykernel_16332\3516368603.py:18: SyntaxWarning: invalid escape sequence '\l'
plt.ylabel('$\ln(\\frac{-dC_A}{dt})$')
Reaction order is = 2, and is 0.292 .
Rate law is 0.292
Integral analysis
Use the integral method to confirm that the reaction is second order with regard to triphenyl methyl chloride
import numpy as np
from scipy.stats import linregress
import matplotlib.pyplot as plt
# Data
= 0.5 # mol/dm^3
C_B0
= np.array([0, 50, 100, 150, 200, 250, 300])
t = np.array([0.05, 0.038, 0.0306, 0.0256, 0.0222, 0.0195, 0.0174])
C_A
= C_A[0]
C_A0
= np.log(C_A0/C_A)
ln_ca0_by_ca = 1/C_A
one_by_ca
# Plotting
# C_A vs t
'bo-')
plt.plot(t, C_A, '0th order test: Concentration of A vs. Time')
plt.title('Time (min)')
plt.xlabel('Concentration of A ($mol/dm^3$)')
plt.ylabel(
0,300)
plt.xlim(
plt.show()
# ln(C_A0/C_A) vs t
'ro-')
plt.plot(t, ln_ca0_by_ca, '1st order test: $\ln(C_A0/C_A)$ vs. Time')
plt.title('Time (min)')
plt.xlabel('$\ln(C_A0/C_A)$')
plt.ylabel(
0,300)
plt.xlim(
plt.show()
# 1/C_A vs t
'go-')
plt.plot(t, one_by_ca, '2nd order test: $1/C_A$ vs. Time')
plt.title('Time (min)')
plt.xlabel('$1/C_A (dm^3/mol)$')
plt.ylabel(
0,300)
plt.xlim( plt.show()
<>:29: SyntaxWarning: invalid escape sequence '\l'
<>:31: SyntaxWarning: invalid escape sequence '\l'
<>:29: SyntaxWarning: invalid escape sequence '\l'
<>:31: SyntaxWarning: invalid escape sequence '\l'
C:\Users\Ranjeet\AppData\Local\Temp\ipykernel_16332\965087012.py:29: SyntaxWarning: invalid escape sequence '\l'
plt.title('1st order test: $\ln(C_A0/C_A)$ vs. Time')
C:\Users\Ranjeet\AppData\Local\Temp\ipykernel_16332\965087012.py:31: SyntaxWarning: invalid escape sequence '\l'
plt.ylabel('$\ln(C_A0/C_A)$')
As the plot of vs. t is linear, the reaction is second order with respect to A.
Use of Regression to Find the Rate-Law Parameters
Use polynomial regression to estimate rate equation. Assume the reaction order is not 1.
For constant volume batch reactor,
and integrating with the initial condition when and for gives us:
We want to minimize to give and .
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize
# Data
= 0.5 # mol/dm^3
C_B0
= np.array([0, 50, 100, 150, 200, 250, 300])
t = np.array([0.05, 0.038, 0.0306, 0.0256, 0.0222, 0.0195, 0.0174])
C_A
= C_A[0]
C_A0
# Initial guesses of k and n
# here k is the clubbed constant k * C_B0
= 1
k = 0
n
# Objective function to minimize: the difference between t (experimental) and t (model)
def objective(params):
= params
k, n
# Calculate t model
= (1/k) * (C_A0**(1-n) - C_A**(1-n))/ (1-n)
t_model = np.sum((t - t_model)**2) # Sum of squared residuals
ssr return ssr
# Minimize the objective function
= minimize(objective, [k,n], bounds=[(1e-4, 1e4), (0, 5)])
result
# Extract the results
= result.x
k_opt, n_opt = result.success
success
# Check if the solution was successful
if not success:
print("Optimization was not successful. Try different initial guesses or methods.")
# final evaluation
= (1/k_opt) * (C_A0**(1-n_opt) - C_A**(1-n_opt))/ (1-n_opt)
t_model
# plot the data
'o', label='Experiment')
plt.plot(t, C_A, '-', label='model')
plt.plot(t_model, C_A, '$t (min)$')
plt.xlabel('$C_A (mol/dm^3)$')
plt.ylabel(
plt.legend()True)
plt.grid(min(t),max(t))
plt.xlim(
plt.show()
Initial guess for Reaction order is = 0.00, and is 1.000e+00.
Optimized value of Reaction order is = 2.04, and is 2.934e-01.
Method of initial rates
The dissolution of dolomite using hydrochloric acid:
Concentration of HCl at various times was determined from atomic absorption spectrophotometer measurements of the and ions (Table 2). Determine the rate constant and order of reaction.
The mole balance for constant V batch reactor at t = 0:
Taking log
import numpy as np
from scipy.stats import linregress
import matplotlib.pyplot as plt
= np.array([1, 4, 2, 0.1, 0.5])
ca0 = np.array([1.2, 2.0, 1.36, 0.36, 0.74]) * 1e-7
ra0
= np.log(ca0)
ln_ca0 = np.log(ra0)
ln_ra0
# fit line
= linregress(ln_ca0, ln_ra0)
res = res.slope * ln_ca0 + res.intercept
line
'o', label='initial rates analysis')
plt.plot(ln_ca0, ln_ra0, '-', label='fitted line')
plt.plot(ln_ca0, line,
f'Slope = {res.slope:.2f}\nIntercept = {res.intercept:.2f}\n$R^2$ = {res.rvalue**2:.3f}',
plt.annotate(=(0.5, 0.15), xycoords='axes fraction', fontsize=12)
xy
'$\ln C_A$')
plt.xlabel('$\ln(-r_{A0})$')
plt.ylabel(
plt.legend()True)
plt.grid(
plt.show()
= res.slope
order = np.exp(res.intercept) k
<>:21: SyntaxWarning: invalid escape sequence '\l'
<>:22: SyntaxWarning: invalid escape sequence '\l'
<>:21: SyntaxWarning: invalid escape sequence '\l'
<>:22: SyntaxWarning: invalid escape sequence '\l'
C:\Users\Ranjeet\AppData\Local\Temp\ipykernel_16332\399930876.py:21: SyntaxWarning: invalid escape sequence '\l'
plt.xlabel('$\ln C_A$')
C:\Users\Ranjeet\AppData\Local\Temp\ipykernel_16332\399930876.py:22: SyntaxWarning: invalid escape sequence '\l'
plt.ylabel('$\ln(-r_{A0})$')
Reaction order is = 0.46, and is 1.058e-07.
Using a Differential Reactor to Obtain Catalytic Rate Data
The formation of methane from carbon monoxide and hydrogen using a nickel catalyst was studied by Pursley. The reaction
was carried out at 500 °F in a differential reactor where the effluent concentration of methane was measured. The raw data is shown in Table 3
Run | (atm) | (atm) | |
---|---|---|---|
1 | 1 | 1.0 | 1.73 |
2 | 1.8 | 1.0 | 4.40 |
3 | 4.08 | 1.0 | 10.0 |
4 | 1.0 | 0.1 | 1.65 |
5 | 1.0 | 0.5 | 2.47 |
6 | 1.0 | 4.0 | 1.75 |
The exit volumetric flow rate from a differential packed bed containing 10 g of catalyst was maintained at 300 for each run. The partial pressures of and were determined at the entrance to the reactor, and the methane concentration was measured at the reactor exit. Determine the rate law and rate law parameters.
- Reaction temperature: 500°F (isothermal reaction)
- Weight of catalyst : ΔW = 10 g
- Exit volumetric flow rate v = 300
The reaction-rate law is assumed to be the product of a function of the partial pressure of and a function of the partial pressure of ,
For the first 3 experiments, is constant. We use this data to determine the dependence on .
For the experiments 1, 4, 5, 6, is constant. We use this data to determine the dependence on
The rate in a differential reactor is given by
For constant partial pressure,
Taking log
For constant partial pressure,
From the data:
At low partial pressures, where increases as increases, the rate law may be of the form
At high partial pressures,where decreases as increases, the rate law may be of the form
Combining Equation 3, and Equation 4 we can write
And the overall rate equation becomes:
We can use regression to calculate estimate , , and constants a, and b.
import numpy as np
from numpy.lib import recfunctions as rfn
from scipy.stats import linregress
import matplotlib.pyplot as plt
= 500 # deg. F
Temperature = 10 # g
DeltaW = 300 # dm^3/min
V_0
= [('Run', int), ('P_CO', float), ('P_H2', float), ('C_CH4', float)]
dtype = np.array([
data 1, 1.0, 1.0, 1.73e-4),
(2, 1.8, 1.0, 4.40e-4),
(3, 4.08, 1.0, 10.0e-4),
(4, 1.0, 0.1, 1.65e-4),
(5, 1.0, 0.5, 2.47e-4),
(6, 1.0, 4.0, 1.75e-4)
(=dtype)
], dtype
= data["P_CO"]
pco = data["P_H2"]
ph2 = data["C_CH4"]
cch4
= V_0 * cch4 / DeltaW
rate
= rfn.append_fields(data, 'Rate', rate, usemask=False)
data
# Use first three points to estimate alpha
= pco[:3]
pco_a = rate[:3]
rate_a
= np.log(pco_a)
ln_pco_a = np.log(rate_a)
ln_rate_a
# fit line
= linregress(ln_pco_a, ln_rate_a)
res = res.slope * ln_pco_a + res.intercept
line
= res.slope
alpha
'bo', label='Experimental Rate') # Original data on log-log scale
plt.loglog(pco_a, rate_a, 'r-', label='Fitted Line') # Convert the log of the fitted line back with exp
plt.loglog(pco_a, np.exp(line),
'$P_{CO}$')
plt.xlabel('$r\'_{CH_4}$')
plt.ylabel('Log-Log Plot of Rate vs $P_{CO}$')
plt.title(
plt.legend()True)
plt.grid(0.1, 10)
plt.xlim(
f'Order = {alpha:.2f}', xy=(0.05, 0.8), xycoords='axes fraction', fontsize=12)
plt.annotate(
plt.show()
Reaction order with respect to is 1.23.
Had we included more points, we would have found that the reaction is essentially first order.
We now use data from all the runs to estimate other parameters of the rate expression
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import least_squares
# Objective function to minimize: the difference between rate (experimental) and rate (calculated)
def objective(params, *args):
= params
a, b, beta_1, beta_2 = args
pco, ph2,rate_obs
# calculate rate
= (a * pco * ph2**beta_1)/ (1 + b * ph2**beta_2)
rate_c return rate_obs - rate_c
# Initial guesses
= 1
a = 1
b = 1
beta_1 = 1
beta_2
= np.array([a,b,beta_1,beta_2])
guess = (
bounds 1e-3, 1e-3, 0, 0], # lower bound
[1e3, 1e3, 3, 3] # upper bound
[
)= (pco, ph2, rate)
args
# Minimize the objective function
= least_squares(objective, guess, args=args, bounds=bounds)
result
# Extract the results
# Results from Fogler 5e
# a_opt = 0.0252715
# b_opt = 2.4872569
# beta_1_opt = 0.616542
# beta_2_opt = 1.0262047
= result.x
a_opt, b_opt, beta_1_opt, beta_2_opt = (a_opt * pco * ph2**beta_1_opt)/ (1 + b_opt * ph2**beta_2_opt)
rate_c
# plot the data
"Run"], rate, 'o', label='Experimental rate')
plt.plot(data["Run"], rate_c, 'x', label='Fitted rate')
plt.plot(data[
plt.annotate(f'a= {a_opt:.4f}\n'\
f'b = {b_opt:.4f}\n'\
f'$\\beta_1$ = {beta_1_opt:.2f}\n'\
f'$\\beta_2$ = {beta_2_opt:.2f}',
=(0.7, 0.5),
xy='axes fraction',
xycoords=12
fontsize
)
'Run')
plt.xlabel('Rate')
plt.ylabel(
plt.legend()
plt.show()
The final constants are:
a = 0.0246
b = 2.3981
= 0.61
= 1.02
If we assume hydrogen undergoes dissociative adsorption on the catalyst surface, we would expect a dependence on the partial pressure of hydrogen to be to the power. Because 0.61 is close to 0.5, we are going to regress the data again, setting and = 1.0.
# Objective function to minimize: the difference between rate (experimental) and rate (calculated)
def objective2(params, *args):
= params
a, b = args
pco, ph2,rate_obs
# calculate rate
= (a * pco * ph2**0.5)/ (1 + b * ph2)
rate_c return rate_obs - rate_c
# Initial guesses
= 1
a = 1
b
= np.array([a,b])
guess = (
bounds 1e-3, 1e-3], # lower bound
[1e3, 1e3] # upper bound
[
)= (pco, ph2, rate)
args
# Minimize the objective function
= least_squares(objective2, guess, args=args, bounds=bounds)
result
# Extract the results
# Results from Fogler 5e
# a_opt = 0.018
# b_opt = 1.49
= result.x
a_opt, b_opt = (a_opt * pco * ph2**0.5)/ (1 + b_opt * ph2)
rate_c
= pco*ph2**0.5/rate
lin_e = pco*ph2**0.5/rate_c
lin_c
# plot the data
'o', label='Experimental rate')
plt.plot(ph2, lin_e, '-', label='Fitted rate')
plt.plot(ph2, lin_c,
plt.annotate(f'a= {a_opt:.4f}\n'\
f'b = {b_opt:.4f}',
=(0.7, 0.5),
xy='axes fraction',
xycoords=12
fontsize
)
'$P_{H_2}$ (atm)')
plt.xlabel('$\\frac{P_{CO} P_{H_2}^0.5}{r\'_{CH_4}}$')
plt.ylabel(
plt.legend()
plt.show()
The final constants are:
a = 0.0180
b = 1.4880
Citation
@online{utikar2024,
author = {Utikar, Ranjeet},
title = {In Class Activity: {Collection} and Analysis of Rate Data},
date = {2024-03-23},
url = {https://cre.smilelab.dev/content/notes/05-collection-and-analysis-of-rate-data/in-class-activities.html},
langid = {en}
}