1 CS 425 / CS 625 Software Engineering Fall 2008 Course Syllabus August 25, 2008.
06 625 Course Notes
-
Upload
rniyatee90 -
Category
Documents
-
view
215 -
download
0
Transcript of 06 625 Course Notes
-
7/29/2019 06 625 Course Notes
1/97
Chemical and ReactiveSystems
John Kitchin
August 26, 2013
1 Introduction
1.1 What is Chemical Reaction En-gineering?
The design of processes that transform lower valuefeedstocks to higher value products through chem-ical reactions.
We answer questions such as:
1. Can we make a product at an economical rate?
2. How big should a reactor be to make a product at
some rate?
3. How much heat should I remove from a reactor tomaintain a safe temperature?
1
-
7/29/2019 06 625 Course Notes
2/97
4. What kind of reactor gives the highest yield?
5. etc...
1.2 We are engineers
We get paid to answer those questions
quantitatively
with uncertainty and risk analysis
even when the problems are very hard
1.3 Role of computational tools
Most problems are too hard to solve by hand andmust be solved numerically.
We will extensively use Python to numerically solveproblems in this course.
Why?
Python is free
You can use this anywhere you go
Python does everything we need
Almost every class will use and show examples ofpython
2
-
7/29/2019 06 625 Course Notes
3/97
These notes will be available to you
You should make sure you can run the examples,and that you get the same results
Ask questions when you do not under-stand
1.4 Quick python examples
1.4.1 Super simple stuff
1 a = 4 # define a variable
2 print(4*a)
3 print a**2
16
16
1.4.2 A simple plot
Here we import functionality from python modules.
1 import numpy as np
2 import matplotlib.pyplot as plt
3
4 x = np.linspace(0, np.pi)
5 plt.plot(x, np.sin(x))
6 plt.xlabel(x)
7 plt.ylabel(sin(x))
8 plt.savefig(images/sin.png)
9 plt.show()
3
-
7/29/2019 06 625 Course Notes
4/97
1.5 Python setup
Get and install Canopy at https://www.enthought.com/products/canopy/
You should request an academic license
It has most of what we will need this semester
We will install new packages as needed
1.6 Additional python packages toinstall
1. Open Canopy.
2. In the Ipython console type:
!easy_install pip
!pip install pycse
1. Restart Canopy
2. in the canopy IPython console type:
pycse_test
You should see: "Your installation of pycse looks ok."
Later to update pycse type this:
!pip install --upgrade pycse
4
https://www.enthought.com/products/canopy/https://www.enthought.com/products/canopy/https://www.enthought.com/products/canopy/https://www.enthought.com/products/canopy/ -
7/29/2019 06 625 Course Notes
5/97
1.7 LATEX
Get and install MikTeX at http://miktex.org/download
You will need this to convert your homework as-signments to pdf
The first homework (hello-world.py) is due at theend of the week.
1.8 Assignments
All of your assignments must be completed inpython
Each assignment will be completed by writing apython script
You may use any editor you want. I suggest theCanopy editor if you do not have a preference.
The script will be converted to a pdf using a com-mand in pycse
You will upload that pdf to your folder in Box.comfor grading
5
http://miktex.org/downloadhttp://miktex.org/downloadhttp://miktex.org/downloadhttp://miktex.org/download -
7/29/2019 06 625 Course Notes
6/97
1.9 More assignment information
Each assignment will have a label
Each script you submit must have this informationat the top:
#+COURSE: 06-625
#+ASSIGNMENT:
#+ANDREWID:
#+NAME:
The publish.py script from pycse will generate apdf for you to upload to Box.com.
The pdf will be automatically named. Do not re-name it.
If your information is not correct, your homeworkwill not be graded. ;(
1.10 Example assignment
Suppose assignment 1a is to write a python script toprint "Hello world" and plot the function y = x2 for x= [1,2,3,5].
This is what your script should look like:
1 #+COURSE: 06-625
2 #+ASSIGNMENT: hello-world
6
-
7/29/2019 06 625 Course Notes
7/97
3 #+ANDREWID: jkitchin
4 #+NAME: John Kitchin
5
6 print Hello world
7
8 import matplotlib.pyplot as plt
9 import numpy as np
10
11 x = np.array([1,2,3,5])
12 y = x**2
13
14 plt.plot(x, y)
15 plt.xlabel(x)
16 plt.ylabel(y)17
18 plt.show()
Hello world
To publish your script, make sure the IPython promptis in the same directory as your script and type
1 publish script.py
A new file is created and opened. If you are satisfied,upload it to your folder on Box.com.
We will not grade files that are renamedor that are incorrectly named.
It is not that we do not want to grade them, butthe assignments will be collected by a computer,
which will not recognize renamed files
After we grade the assignments you can see yourgrade on the assignment in your Box.com folder.
7
-
7/29/2019 06 625 Course Notes
8/97
1.11 Getting help
I am expecting you will need help. Python is prob-ably new for you.
You may find these resources helpful:
Class
Come to class everyday.
Watch me use Python
Ask questions about things you do not under-stand
Learning python
http://learnpythonthehardway.org/book/
http://interactivepython.org/coursel ib/static/thinkcspy/index.html
Python documentation
Builtin modules - http://docs.python.org/2/library/index.html
Python and scientific computing
http://jkitchin.github.io/pycse/ (thereis also a pdf version)
8
http://learnpythonthehardway.org/book/http://interactivepython.org/courselib/static/thinkcspy/index.htmlhttp://interactivepython.org/courselib/static/thinkcspy/index.htmlhttp://docs.python.org/2/library/index.htmlhttp://docs.python.org/2/library/index.htmlhttp://jkitchin.github.io/pycse/http://jkitchin.github.io/pycse/http://docs.python.org/2/library/index.htmlhttp://docs.python.org/2/library/index.htmlhttp://interactivepython.org/courselib/static/thinkcspy/index.htmlhttp://interactivepython.org/courselib/static/thinkcspy/index.htmlhttp://learnpythonthehardway.org/book/ -
7/29/2019 06 625 Course Notes
9/97
Numerical python - http://docs.scipy.org/
doc/numpy/reference/ Scientific python - http://docs.scipy.org/
doc/scipy/reference/
I will be posting the notes I make in class on thecourse website.
1.12 First assigment: due next class Install Canopy, pycse and Latex
We will use these next class
First assignment to turn will be due [2013-08-30Fri]
hello-world.py (see Box.com/assignments)
2 The Basics - isothermal re-actor design with single re-actions
2.1 Chemical reactions
Chemical reaction transform reactants to products.
Consider a reaction aA+ bB+ qQ+sS+
9
http://docs.scipy.org/doc/numpy/reference/http://docs.scipy.org/doc/numpy/reference/http://docs.scipy.org/doc/scipy/reference/http://docs.scipy.org/doc/scipy/reference/http://docs.scipy.org/doc/scipy/reference/http://docs.scipy.org/doc/scipy/reference/http://docs.scipy.org/doc/numpy/reference/http://docs.scipy.org/doc/numpy/reference/ -
7/29/2019 06 625 Course Notes
10/97
symbols on the left are reactants
symbols on the right are products
The lower case letters are stoichiometric coefficients
Stoichiometric coefficients relate the amountsof each reactant that react to the amounts ofproducts produced
The upper case letters are symbols for reactant andproduct species
For specificity let us consider
aA + bB cC+ dD
We will express this reaction as:
0 = 3A3 + 4A4 1A1 2A2where we have substituted A = A1 and a = 1,
C= A3 and c = 3, etc. . .
In the most compact form we might write this asa sum over all N species for a reaction:
Ni=0 iAi = 0
where i is the stoichiometric coefficient (negativefor reactants, positive for products), or more preferrablyin matrix equation form:
10
-
7/29/2019 06 625 Course Notes
11/97
A = 0
where is the vector of stoichiometric coefficients,and A is the vector of chemical species.
It is conventional that the stoichiometric coeffi-cients of reactants are negative and for productsthe stoichiometric coefficients are positive.
Atoms cannot be destroyed in non-nuclear chemi-
cal reactions, hence it follows that the same num-ber of atoms entering a reactor must also leave thereactor. The atoms may leave the reactor in a dif-ferent molecular configuration due to the reaction,but the total mass leaving the reactor must be thesame.
We consider the water gas shift reaction:
CO + H2O H2 + CO2.
The total mass will be MCO+MH2O+MH2+MCO2.
These are related to the number of moles ofeach species through the species molecular weights.
Let N be a vector that is the number of moles of
each species. Then, the total mass is: N MW. Stoichiometry constrains the relationship between
the moles of each species during reaction.
11
-
7/29/2019 06 625 Course Notes
12/97
Suppose we start with this initial number of moles
of each species: [N0, N0, 0, 0].
Now, ifn moles of A1 reacts, we know that nmoles of A2 react, and n moles of A3 and A4are produced.
In otherwords, the new number of moles of eachspecies is: N0 + n. And the new mass iscorrespondingly: ( N0 + n)
MW or: M0 +
n MW. In a properly balanced chemical reaction, there are
the same number of each type of atom on eachside of the reaction, hence the sum of molecularweights of reactants must be the same as products,hence M= 0. Therefore, the total mass does notchange for any n!
We can illustrate the conservation of mass withthe following equation: MW = 0. Where isthe stoichiometric coefficient vector and MW is acolumn vector of molecular weights.
For simplicity, we use pure isotope molecular weights,and not the isotope-weighted molecular weights.
This equation simply examines the mass on theright side of the equation and the mass on left sideof the equation.
12
-
7/29/2019 06 625 Course Notes
13/97
1 import numpy as np
2
3 alpha = [-1, -1, 1, 1]; # stoichiometric vector for CO + H2O -> H2 + CO2
4 MW = [28, 18, 2, 44] # Molecular weights gm/mol
5 print np.dot(alpha, MW)
6
7 # Here is some old-fashioned code. do not do this. even though it works:
8 total = 0
9 for i in range(4):
10 total = total + alpha[i] * MW[i]
11 print total
12
13
# Kudos if you thought of this:14 import operator
15 print sum(map(operator.mul, alpha, MW))
16 # just kidding, I would never do that! This is called functional programming
0
0
0
Stoichiometry also determines if the total number ofmoles in a reaction change. Even though the total massis constant, the total number of moles may change.Here are three examples showing how this is possible.
1. CO + H2O H2 + CO2 (no total mole change)2. H2O H2 + 1/2 O2 (Total moles increase by 0.5
mol per mol water reacted)
3. N2 + 3H2 2 NH3 (Total moles decrease by twomoles for every mole of N2 reacted)
13
-
7/29/2019 06 625 Course Notes
14/97
The change in number of moles is given by =Ni=0 i or = .
1 # WGS
2 alpha = [-1, -1, 1, 1]; # stoichiometric vector for CO + H2O -> H2 + CO2
3 print Change in moles for the WGS = {0} moles.format(sum(alpha))
4
5 alpha = [-1, 1, 0.5] # H2O -> H2 + 1/2 O2
6 print Change in moles for water splitting = {0} moles.format(sum(alpha))
7
8 alpha = [-1, -3, 2] # N2 + 3H2 -> 2 NH3
9 print Change in moles for the ammonia synthesis = {0} moles.format(sum(alpha)
Change in moles for the WGS = 0 moles
Change in moles for water splitting = 0.5 moles
Change in moles for the ammonia synthesis = -2 moles
Changing the total number of moles in a reactionwill have a big effect in gas phase reactions because
it results in changing volumetric flow rates. We willcome back to this later.
2.2 Reaction extent
We now consider formalizing the change in molesof each species when reactions occur. Consider:
2H2 + O2 2H2Owhich we remember is:0 = 2A3 2A1 A2
14
-
7/29/2019 06 625 Course Notes
15/97
If we start with NA1,0 moles at some time, and later
have NA1 moles later, then stoichiometry dictatesthat:
NA1NA1,02 =
NA2NA2,01 =
NA3NA3,01 = X
We call X the extent of reaction, and it has unitsof moles. We can show generally that:
NJ = NJ,0 + JXor for a flow system:FJ = FJ,0 + JX
Note that the extent of reaction as written is ex-tensive, and depends on how the reaction is writtenthrough the stoichiometric coefficients. It does not,however, depend on a particular species.
If we have a constant volume reactor and a con-stant volumetric flow, we can use an intensive re-action extent:
CJ = CJ,0 + J. is now an intensive reaction extent X/V, with
units of mol / L.
Note that there are limits on the maximum value ofbecause we cannot have negative concentrations.If we set CJ to zero, we derive
15
-
7/29/2019 06 625 Course Notes
16/97
max =
CJ,0J
If there are multiple reactants present, then youmust pick the smallest positive (non-zero) max toavoid getting negative concentrations of one species.
Consider this reaction:
H2 + 0.5 O2 H2OIf you start with 0.55 mole of H2, and 0.2 mol of O2.What is max?
1 import numpy as np
2
3 M0 = np.array([0.55, 0.2])
4 alpha = np.array([-1.0, -0.5])
5
6 print - M0 / alpha
7 print The maximum extent is {0} moles..format(min(- M0 / alpha))
[ 0.55 0.4 ]
The maximum extent is 0.4 moles.
Now for that extent, what is the reaction compos-tion?1
1 import numpy as np
2
1Note that if you put the product in here, this will tell
you that the maximum extent is zero. You should of course
take the smallest positive number.
16
-
7/29/2019 06 625 Course Notes
17/97
3 M0 = np.array([0.55, 0.2, 0.0])
4 alpha = np.array([-1.0, -0.5, 1.0])
5
6 xi = 0.4
7
8 M = M0 + alpha * xi
9 print M
[ 0.15 0. 0.4 ]
You can see that at that extent we have consumed
all of the oxygen. We would call that the limit-ing reagent, because the reaction cannot proceedfurther since one of the reactants is gone.
Rather than work in terms of reaction extents, youmay choose to define a fractional extent:
= /max
which leads upon substitution to:CJ = CJ,0(1 )
This new quantity is sometimes referred to asconversion. Conversion has the nice property of be-ing dimensionless, and bounded between 0 and 1.While convenient for a single reaction, it is imprac-tical for multiple reactions and we will not consider
it further.
17
-
7/29/2019 06 625 Course Notes
18/97
2.3 Reaction rates and rate laws
2.3.1 The rate of a reaction
We are now in a position to define the rate of areaction as:
R = dXdt
which is the rate of the change of the extensive
reaction extent. Note that this is:
Independent of any particular species
Dependent on how the reaction is written
The second point is a result of how we defined X.
For the reaction 2 H2 + O2
2 H2O we defined:
NA1NA1,02 =
NA2NA2,01 =
NA3NA3,02 = X
For the reaction H2 + 1/2 O2 H2O we define:NA1NA1,0
1 =NA2NA2,0
0.5 =NA3NA3,0
1 = X
You can see that Xdepends on the stoichiometric
coefficients, so we have to know the reaction andhow it was written when we discuss reaction rates.
18
-
7/29/2019 06 625 Course Notes
19/97
2.3.2 The rate of disappearance of a reac-
tant
Now, recalling that X=NAjNAj,0
j, we arrive at:
dXdt =
1j
dNAJdt
or:dNAJdt = jR
Thus, the change in composition of species J dueto the reaction is just the stoichiometric coeffi-cient for that species times the reaction rate. Ifthe volume is constant, we have:
r = R/V = ddtanddCAJdt = jr
We define the species rate of production as:
rj = jr
This is NOT the rate law!
One of our goals is to find the function thatdescribes the rate of the reaction and its de-pendence on concentration and temperature
19
-
7/29/2019 06 625 Course Notes
20/97
2.3.3 Rates of disappearance and appear-
ance of other species
The stoichiometric coefficients define the rates ofappearance and disappearance of other species.
r =rA11
=rA22
=rA33
Remember that stoichiometric coefficients of reac-
tants are negative, and products are positive
We call r the rate of the reaction with units (typ-ically) mol / vol / time
The rate of disappearance of species A1 is rA1 =1r
The rate generally depends on the concentrationof reactants (and sometimes products), as well as
the temperature of the reactor.
2.3.4 Rate laws
The rate law is an algebraic equation that relatesthe rate of reaction to the concentrations of reac-tants, products and temperature.
Law of mass action for elementary steps:
Reaction rate is proportional to the concentra-tion of each reactant raised to its stoichiometriccoefficient
20
-
7/29/2019 06 625 Course Notes
21/97
For example: A + B C r = kCACB
2A B r = kC2A
Many other more complex rate laws exist for non-elementary reactions
r = k1CA1+k2CA e.g. for surface reactions or enzyme
reactions r = kC
3/2A for complex mechanisms
Rate laws are ultimately determined from experi-ments
We use these rate laws in conjunction with stoi-chiometry and mole balances to design reactors.
2.4 Simple mole balances
2.4.1 Review of the mole balance
Mole balances are performed for a species in acontrol volume
21
-
7/29/2019 06 625 Course Notes
22/97
Accumulation = In Out + Generation (1)dNJdt
= FJ0 FJ + V rJ (2)
Here we use the convention that Nj refers to thetotal number of moles of species J in the volume,FJ is a molar flow ofJ, and rJ is the intensive rateof production ofJ, and it has a negative magnitude
if species J is in fact being consumed.
2.4.2 A continuously stirred tank reactor
We assume the tank is well-mixed because it iswell-stirred
The concentration at the exit is the same as every-
where in the tank The mole balance at steady state (dNAdt = 0) is:
0 = FA0 FA + V rA22
-
7/29/2019 06 625 Course Notes
23/97
2.4.3 A continuously stirred tank reactor
problem
We have a 10L stirred tank reactor
A flows in at a molar flow rate of 1 mol/hr andvolumetric flowrate of 2.5 L/hr
rA = kCA, k = 0.23 1/hr
What is the steady-state exit concentration ofA?
The equations
dNAdt
= 0 = FA0 FA + V rA (3)0 = FA0 FA V kCA,exit (4)0 = FA0 v0CA,exit V kCA,exit (5)
CA,exit =FA0
v0+V k
Only for constant volume
Assumes well-mixed, i.e. uniform concentration
2.4.4 Solving the problem with algebra (CSTR)
Simple algebra
23
-
7/29/2019 06 625 Course Notes
24/97
1 k = 0.23 # 1/hr
2 Fa0 = 1.0 # mol /hr3 v0 = 2.5 # L /hr
4 V = 10 # L
5
6 Ca_exit = (Fa0 / (v0 + V * k))
7 print Ca_exit = {0:1.3f} mol / L.format(Ca_exit)
Ca_exit = 0.208 mol / L
This was an easy problem, but the algebraic ma-nipulations are all possible places where errors canbe made.
2.4.5 Solving the problem numerically us-ing a solver (CSTR)
We have to create a function that is equal to zeroat the solution.
We have that from the mole balance:
0 = FA0 FA V rA We just have to make sure to use the correct
variables.
We use a nonlinear solver, so we also have to pro-
vide an initial guess.
1 from scipy.optimize import fsolve
2
3 k = 0.23 # 1/hr
24
-
7/29/2019 06 625 Course Notes
25/97
4 Fa0 = 1.0 # mol /hr
5 v0 = 2.5 # L /hr
6 V = 10 # L
7
8 def func(Ca):
9 return Fa0 - v0 * Ca - V * k * Ca
10
11 guess = 1.0 # m o l / L
12 ans, = fsolve(func, guess)
13 print Ca_exit = {0:1.3f} mol/L.format(ans)
Ca_exit = 0.208 mol/L
This had less manipulation, and fewer opportuni-ties for mistakes
On the other hand, we ended up using a solver thatrequired an initial guess to solve a linear problem.
This was a simple problem, but other problems will
not be linear, and will be much more difficult.
Remember what the units are? Were they consis-tent?
2.4.6 Solving the problem with units (CSTR)
Units are not built-in to python
We have to install the quantities package
Then import the package so we can use it
25
-
7/29/2019 06 625 Course Notes
26/97
1 import quantities as u
2 k = 0.23 * 1/u.hr3 Fa0 = 1.0 * u.mol / u.hr
4 v0 = 2.5 * u.L / u.hr
5 V = 1 0 * u.L
6
7 Cae = Fa0 / (v0 + V * k)
8 print(Ca,exit = {0}.format(Cae))
Ca,exit = 0.208333333333 mol/L
This has printed with too many significant figures
quantities is great for simple problems
There are limitations we will see later
2.4.7 Solving the problem with uncertainty(CSTR)
Uncertainty analysis is not built in to python
We have to install the uncertainties packageand import it
Let us assume there is some uncertainty in the rateconstant, say it is k = 0.23 0.1 1/hr. We can usethe uncertainties package to propagate that error
automatically.
1 import uncertainties as u
2
26
-
7/29/2019 06 625 Course Notes
27/97
3 k = u.ufloat(0.23, 0.1) # rate constant 1/hr
4 Fa0 = 1.0 # inlet molar flow mol/hr
5 v0 = 2.5 # volumetric flow L/hr
6 V = 10 # reactor volume L
7 Cae = Fa0 / (v0 + V * k)
8 print(Ca,exit = {0}.format(Cae))
Ca,exit = 0.21+/-0.04
uncertainties is also great for simple problems
We have to do some work to make it work in othersituations
2.4.8 Mole balance on a batch reactor
The next more complex (mathematically) mole bal-ance is the batch reactor. The batch reactor does notoperate at steady state, and therefore we have an or-
dinary differential equation that describes the numberof moles in the reactor as a function of time.
27
-
7/29/2019 06 625 Course Notes
28/97
Constant volume
No flow in or outdNAdt = V rA
2.4.9 Simple application of a mole balanceto a constant volume batch reactor
At t = 0 we have an initial concentration of 2mol/L
rA = kCA with k = 0.23 1/hr How much A is left after 1 hour?
28
-
7/29/2019 06 625 Course Notes
29/97
Equations
NA = CAV (6)
dNAdt
= VdCAdt
(7)
dCAdt
= rA = kCA (8)
CA(t = 0) = CA0 (9)
Only for constant volume
Assumes well-mixed, i.e. uniform concentration
Initial condition, ordinary differential equation
2.4.10 Solving the problem (constant vol-ume batch reactor)
1 import numpy as np
2 from scipy.integrate import odeint
3
4 k = 0.23 # 1/hr
5 Ca0 = 2.0 # m o l / L
6
7 def ode(Ca, t):
8 dCadt = -k * Ca
9
return dCadt10
11 tspan = np.linspace(0, 1) # hours
12 sol = odeint(ode, Ca0, tspan)
13 print C_A at t = 1 hour = {0} mol/L.format(sol[-1][0])
29
-
7/29/2019 06 625 Course Notes
30/97
C_A at t = 1 hour = 1.58906722836 mol/L
Remember what the units are?
2.4.11 Plotting CA vs. time in a batch re-actor
Now let us solve the ODE at many times, and plotthe solution.
1 import numpy as np
2 from scipy.integrate import odeint
3 import matplotlib.pyplot as plt
4
5 k = 0.23 # 1 / h r
6 Ca0 = 2.0 # m o l / L
7
8 def ode(Ca, t):
9 dCadt = -k * Ca
10 return dCadt
11
12 tspan = np.linspace(0, 1)
13 sol = odeint(ode, Ca0, tspan)
14 plt.plot(tspan, sol)
15 plt.xlabel(Time (hours))
16 plt.ylabel($C_A$ (mol / L))
17 plt.savefig(images/batch-time.png)
30
-
7/29/2019 06 625 Course Notes
31/97
CA decreases with time (it is consumed)
It is not apparent from this graph because of theshort time, but the concentration decreases expo-
nentially with time
2.4.12 Mole balance in a plug flow reactor
In the plug flow reactor, reactants enter the frontof the reactor and disappear as they flow throughthe reactor
31
-
7/29/2019 06 625 Course Notes
32/97
We assume our differential element is well-mixed The mole balance on the differential element leads
to
dNAdt = FA|V FA|V+V + V rA
At steady state, in the limit of V 0 we get:dFA
dV = rA
This is an ordinary differential equation, and tosolve it we need an initial condition on the molarflow at V = 0.
2.4.13 A worked PFR example
Given a 100 L reactor with A flowing in at a con-
centration of 3 mol/L and a rate of 10 L/min
The reaction A B occurs at a rate law ofr =kCA with k = 0.23 1/min
32
-
7/29/2019 06 625 Course Notes
33/97
What is the exit concentration ofA?
We have dFAdV = rA
We have rA = r (stoichiometry) FA(V = 0) = CA0v0
1 from scipy.integrate import odeint
2
3 Ca0 = 3.0 # m o l / L
4 v0 = 10.0 # volumetric flowrate L/min5 k = 0.23 # rate constant 1/min
6
7 def ode(Fa, V):
8 Ca = Fa / v0
9 return -k * Ca
10
11 Vspan = [0, 100] # reactor volume
12
13 sol = odeint(ode, Ca0 * v0, Vspan)
14 Fa_exit = sol[-1, 0]
15
16 print(Exit concentration = {0:1.4f} mol/L.format(Fa_exit / v0))
Exit concentration = 0.3008 mol/L
Our solution only has two points in it: 0 and 100L
We cannot visualize the concentration profile
2.4.14 A harder PFR example
The reaction A B occurs at a rate law ofr =kCA with k = 0.23 1/min
33
-
7/29/2019 06 625 Course Notes
34/97
IfA is flowing in at a concentration of 3 mol/L and
a rate of 10 L/min
How large should the reactor be to reduce the con-centration ofA to 0.3 mol/L?
There are many ways to approach this.
You could integrate dFAdV = rA and graphically de-termine where the solution is.
You could setup a numerical approach to solvingthe equation
First we graph the solution. the code is almost thesame as before, but we integrate over more pointsand a a larger range.
1 import numpy as np2 from scipy.integrate import odeint
3 import matplotlib.pyplot as plt
4
5 Ca0 = 3.0 # m o l / L
6 v0 = 10.0 # L / m i n
7 k = 0.23 # 1/min
8
9 def ode(Fa, V):
10 Ca = Fa / v0
11 return -k * Ca
12
13 Vspan = np.linspace(0, 200) # volumes to integrate over
14
15 sol = odeint(ode, Ca0 * v0, Vspan)
16
17 plt.plot(Vspan, sol)
34
-
7/29/2019 06 625 Course Notes
35/97
18 plt.xlabel(Volume (L))
19 plt.ylabel($F_A$ (mol/L))
20 plt.savefig(images/pfr-volume.png)
at CA = 0.3 mol/L, FA = 3 mol/min.
We know the answer before: It is about 100 L.
It is hard to be very accurate this way, althoughinteractive graphics help
2.4.15 Numerical solution
To numerically solve this we must solve a functionf(V) = 0.
Here is one approach
35
-
7/29/2019 06 625 Course Notes
36/97
Starting from dFAdV =
kFA/we derive:
f(V) =FAFA0
dFAFA
VV=0 k dVwhere everything is known but V. We use numerical
quadrature to evaluate the integrals.
1 from scipy.integrate import quad
2 from scipy.optimize import fsolve
3
4 k = 0.23 # 1/min
5
nu = 10 # L/min6 Ca0 = 3.0 # m o l / L
7 Fa0 = Ca0 * nu
8 Fa = 0.30 * nu
9
10 def integrand1(Fa):
11 return 1.0 / Fa
12
13 def integrand2(V):
14 return -k / nu
15
16 def func(V):
17 I1, e1 = quad(integrand1, Fa0, Fa)18 I2, e2 = quad(integrand2, 0, V)
19 return I1 - I2
20
21 guess = 120 # Liters
22 sol, = fsolve(func, guess)
23 print Volume = {0:1.2f}.format(sol)
Volume = 100.11
This also leaves something to be desired in com-plexity
Many opportunities for mistakes in the derivation
36
-
7/29/2019 06 625 Course Notes
37/97
Requires sophisticated thinking about the problem
Other approaches require similar or more sophisti-cation!
2.4.16 Solution by interpolation
Solve the problem
Create interpolation function for the solution
Solve the inverse problem V(Fa)
1 import numpy as np
2 from scipy.integrate import odeint
3 from scipy.interpolate import interp1d
4
5 Ca0 = 3.0 # m o l / L
6 v0 = 10.0 # L / m i n
7 k = 0.23 # 1 / m i n
8
9 def ode(Fa, V):
10 Ca = Fa / v0
11 return -k * Ca
12
13 Vspan = np.linspace(0, 200) # L
14
15 sol = odeint(ode, Ca0 * v0, Vspan)
16
17 interp_func = interp1d(sol[:, 0][::-1],
18 Vspan[::-1],
19 cubic)
20
21 Ca_exit = 0.3 # m o l / L
22 Fa_exit = Ca_exit * v0
23 V_sol = interp_func(Fa_exit)
24 print Solution is at {0} L.format(V_sol)
37
-
7/29/2019 06 625 Course Notes
38/97
Solution is at 100.112342835 L
this only works because V(FA) is monotonic. Thatis a restriction on the interp1d function
2.4.17 Using events to stop integration
An alternative to the methods above is to use anODE solver that is aware of events to stop the
integration where you want it. pycse provides a function like this called odelay.
You define an event function that equals zero atthe event. You specify if the event is terminal,and whether to the zero must be approached fromabove or below, or if all zeros count.
Here is an example.
1 import numpy as np
2 from pycse import odelay
3
4 Ca0 = 3.0 # m o l / L
5 v0 = 10.0 # L / m i n
6 k = 0.23 # 1 / m i n
7
8 Fa_Exit = 0.3 * v0
9
10 def ode(Fa, V):
11 Ca = Fa / v0
12 return -k * Ca
13
14 def event1(Fa, V):
38
-
7/29/2019 06 625 Course Notes
39/97
15 isterminal = True
16 direction = 0
17 value = Fa - Fa_Exit
18 return value, isterminal, direction
19
20 Vspan = np.linspace(0, 200) # L
21
22 V, F, TE, YE, IE = odelay(ode, Ca0 * v0, Vspan, events=[event1])
23
24 print Solution is at {0} L.format(V[-1])
25 import matplotlib.pyplot as plt
26 plt.plot(V, F)
27 plt.show()
Solution is at 100.112421732 L
As you can see, there are many ways to solve thisproblem
It is not necessary to know every single way to doit, but knowing multiple ways increases your ability
to solve other problems in the future
2.5 Mole balances with changing num-ber of moles
The reason it is important to know whether thetotal number of moles is changing in a reactor isbecause we define the concentration of a species ina flowing system as
Cj =Fj
39
-
7/29/2019 06 625 Course Notes
40/97
where is the total volumetric flowrate, and we need
these concentrations to evaluate the rate laws.
For reactions with no change in total moles, nopressure drops, and isothermal conditions, is aconstant.
In all other cases, changes, and we have to com-pute to compute the concentrations for use in
the rate laws. We know at the entrance of the reactor that
P00 = FT0RT0Z0
Here we have the initial pressure, volumetric flowrate,temperature and compressibility factor. At somelater point in the reactor or in time, we also have:
P= FTRTZ
Now, even if we are isothermal, so that T = T0,isobaric, so that P = P0, and there is no changein compressibility, i.e. Z = Z0, if the total molarflow rate has changed, there will be a change in thevolumetric flow.
The ratio of these two equations leads to this ex-pression for the volumetric flow rate in a reactorwhere the total number of moles is changing:
40
-
7/29/2019 06 625 Course Notes
41/97
= 0FTFT0
P0P
TT0
ZZ0
Here, the total molar flow is simply the sum of themolar flows of each species (including inert species)in the reactor, FT =
Ni=0 Fj.
That means we now need to calculate the molarflow of each species, which in general means wewill have a species mole balance for each species.
For single reactions, it is often possible to relatethese molar flows by stoichiometry.
Consider this reaction, e.g. ethane cracking toethylene and hydrogen.
A B + C
We want to design a plug flow reactor to consume
80% of the ethane that enters the reactor. It isgiven that rA = kCA with k = 0.072 1/s at1000K.
Let the initial molar flowrate be 0.425 mol / sof pure A into the reactor at a total pressure of6 atm. The initial concentration of A is simplyCA0 = PA0/(RT). From this, we calculate the
initial volumetric flowrate: 0 = FA0/CA0
first we use the quantities package to do some unitconversions to get consistent SI units
41
-
7/29/2019 06 625 Course Notes
42/97
1 import quantities as u
2
3 PA0 = 6 * u.atm
4 T = 1000 * u.K
5 R = 8.314 * u.J / u.mol / u.K
6
7 CA0 = PA0 /( R * T)
8 print C_{{A0}} = {0}.format(CA0.simplified)
9
10 FA0 = 0.425 * u.mol / u.s
11 v0 = FA0 / CA0
12
13
print \\nu_0 = {0}.format(v0.simplified)
C_{A0} = 73.1236468607 mol/m**3
\nu_0 = 0.0058120733613 m**3/s
If we assume there is no pressure drop, and that80% of A has reacted at the exit, with pure A atthe entrance, then we know that there will be 0.20
FA0 + 0.8 FA0 + 0.8 FA0 moles at the exit. Thus,
1 import quantities as u
2 FA0 = 0.425 * u.mol / u.s
3 PA0 = 6 * u.atm
4 T = 1000 * u.K
5 R = 8.314 * u.J / u.mol / u.K
6
7 CA0 = PA0 /( R * T)
8 v0 = FA0 / CA0
9
10 F_exit = 0.2 * FA0 + 0.8 * FA0 + 0.8 * FA0
11
12 v = v0 * F_exit / FA0
13 print Exit volumetric flowrate is {0}..format(v.simplified)
14
42
-
7/29/2019 06 625 Course Notes
43/97
15 CA_exit = 0.2 * FA0 / v
16
17 print Exit concentration of A is {0}
18 which is not 0.2 * CA0 ({1}).format(CA_exit.simplified,
19 (0.2 * CA0).simplified)
Exit volumetric flowrate is 0.0104617320503 m**3/s.
Exit concentration of A is 8.12484965119 mol/m**3
which is not 0.2 * CA0 (14.6247293721 mol/m**3)
You can see just by this algebra that the concen-tration changes because of the chemical reactionthat consumes A, but also because the number ofmoles is changing, and the volumetric flowrate isincreasing.
We have to account for that in our mole balances.The normal mole balance for a plug flow reactor
looks like:dFAdV = rA. Here, it is convenient to invert the equa-
tion:dVdFA
= 1rAbecause then we can integrate over the molar flow
to directly compute the volume.
We compute the molar flows of B and C using the
reaction extent: Fj = Fj0 + j.
1 import numpy as np
2 from scipy.integrate import odeint
43
-
7/29/2019 06 625 Course Notes
44/97
3
4 Fa0 = 0.425 # m o l / s
5 Fa_exit = 0.2 * Fa0
6
7 v0 = 0.0058120733613 # m ^ 3 / s
8 k = 0.072 # 1 / s
9
10 def dVdFa(V, Fa):
11 xi = (Fa - Fa0) / (-1) # compute reaction extent
12 Fb = xi * 1
13 Fc = xi * 1
14 Ft = Fa + Fb + Fc # total molar flow
15 v = v0 * Ft / Fa0 # volumetric flow
16
17 Ca = Fa / v
18 ra = -k * Ca
19 return 1.0 / ra
20
21 Fspan = np.linspace(Fa0, Fa_exit)
22
23 V0 = 0
24 sol = odeint(dVdFa, V0, Fspan)
25
26 print At a volume of {0:1.2f} m^3 we achieve 80% conversion of A.format(sol[-
27
28 import matplotlib.pyplot as plt
29 plt.plot(Fspan, sol)
30 plt.xlabel(F$_A$ (mol/s))
31 plt.ylabel(Volume (m$^3$))
32 plt.savefig(images/changing-moles-pfr.png)
At a volume of 0.20 m^3 we achieve 80% conversion of A
44
-
7/29/2019 06 625 Course Notes
45/97
An alternative approach, and one that is neededfor multiple reactions, is to use a mole balance foreach species:
dFAdV
= rA (10)
dFBdV
= rB (11)
dFCdV
= rC (12)
and to relate the rates of each species reaction rateto each other via stoichimetry:
r = rA1 =rB1 =
rC1
45
-
7/29/2019 06 625 Course Notes
46/97
Here we cannot invert the ODE, because we have
coupled odes.
1 import numpy as np
2 from scipy.integrate import odeint
3
4 Fa0 = 0.425 # m o l / s
5 Fa_exit = 0.2 * Fa0
6
7 v0 = 0.0058120733613 # m ^ 3 / s
8 k = 0.072 # 1 / s
9
10 def dFdV(F, V):
11
12 Fa = F[0] # we only need Fa for the rate law
13 Ft = sum(F) # total flow rate
14 v = v0 * Ft / Fa0
15
16 Ca = Fa / v
17 ra = -k * Ca
18
19 dFadV = ra
20 dFbdV = -ra
21 dFcdV = -ra22 return [dFadV, dFbdV, dFcdV]
23
24 Vspan = np.linspace(0, 1) # m**3
25
26 F0 = [Fa0, 0, 0] # entrance conditions
27 sol = odeint(dFdV, F0, Vspan)
28
29 Fa = sol[:,0]
30
31 from scipy.interpolate import interp1d
32 f = interp1d(Vspan, Fa)
33
34 from scipy.optimize import fsolve
35 Vsol, = fsolve(lambda x: f(x) - 0.2 * Fa0, 0.2)
36 print At a volume of {0:1.2f} m^3 we achieve 80% conversion of A.format(Vsol)
37
46
-
7/29/2019 06 625 Course Notes
47/97
38
39 import matplotlib.pyplot as plt
40 plt.plot(Vspan, sol)
41
42 plt.xlabel(Volume (m$^3$))
43 plt.ylabel(F$_A$ (mol/s))
44
45 plt.legend([A, B, C])
46 plt.savefig(images/changing-moles-pfr-2.png)
47 plt.show()
At a volume of 0.20 m^3 we achieve 80% conversion of A
This approach is more involved, but when there aremultiple reactions, and net rates of reaction mustbe considered, this is the only way to proceed withreactor design.
47
-
7/29/2019 06 625 Course Notes
48/97
2.5.1 Summary
It is important to keep track of when the numberof moles in a reaction change because we define theconcentration of a species as
Cj = Fj
and depends on the total number of moles in thesystem.
= 0FTFT0
P0P
TT0
ZZ0
We will see in a later section that pressure dropsaffect reactor design because of the change it causesin volumetric flow also.
2.6 Mole balances with pressure drops We have previously seen that we must account for
changing volumetric flowrates in reactor design be-cause the concentrations of species used in comput-ing reaction rates are dependent on the volumetricflowrate.
This can be important even when the total molar
flow is constant, if there is a pressure drop in thereactor, i.e. if the pressure at the entrance is notthe same as the pressure at the exit of the reactor:
48
-
7/29/2019 06 625 Course Notes
49/97
= 0FTFT0
P0P
TT0
ZZ0
Since the pressure drops through a reactor, if noth-ing else changes, the volumetric flow will increase(this is a consequence of conservation of mass).The consequence of this is the following:
CA =FA =
FA0
FT0FT
PP0
This is especially important for packed bed reac-tors, which are often filled with catalyst beads thatcan impede the flow.
Since we apply this specifically to a packed bedreactor, it is convenient to work in terms of catalystweight, rather than reactor volume.
The two quantities are related by W = bV =
c(1 )V where W is the weight, b is the bulkcatalyst density, c is the density of solid catalyst,and is the porosity of the catalyst.
We know how to develop mole balances for FA,but these will lead to equations of the form dFjdW =f(F, P), which has an additional variable P in it.
Now we need to have a quantitative expression forthe pressure at some point in a reactor, as a func-tion of the molar flows of each species.
49
-
7/29/2019 06 625 Course Notes
50/97
The pressure drop through a packed bed can be
modeled with the Ergun equation2
. This is one ofthe more common approaches to considering pres-sure drops.
The most important result is that
dPdW = 0Ac(1)c P0P TT0 FTFT0where:
0 =
G(1
)
0gcDp3150(1
)
Dp + 1.75G
0 is a constant that depends only on the proper-ties of the packed bed, and the entrance gas con-ditions:
Ac bed cross-sectional areac solid catalyst density catalyst porosity gas viscosityG superficial mass velocity ( u)u superficial velocity (volumetric flow / Ac)Dp catalyst bead diametergc 32.174 lbm ft/s
2/lbf (in metric gc=1)0 inlet gas density
Clearly, we need additional data, but the data areall constants. In fact, it is customary to lump ad-ditional constants, and to define:
2See Fogler, section 4.5
50
-
7/29/2019 06 625 Course Notes
51/97
= 20Acc(1)P0
and to define y = P/P0 so that we
can reexpress the differential equation as:dydW = 2y TT0 FTFT0 This equation has an analytical solution when there
is no change in the total number of moles. Fromhere you can see that y will decrease in an isother-mal, isomolar reaction due to the negative sign.
This equation depends on FT, so it is coupled tothe mole balances. So, we will typically have equa-tions such as:
dFAdW
= rA (13)
dFBdW
= rB (14)
... (15)
dy
dW=
2y
T
T0
FTFT0
(16)
which must be numerically integrated with appro-priate initial conditions.
2.6.1 A worked example with a pressure
drop and inerts
We consider the partial oxidation of ethylene toethylene oxide:
51
-
7/29/2019 06 625 Course Notes
52/97
C2H4 + 0.5 O2 C2H4O
Oxygen is fed in a stoichiometric amount in theform of air.
The rate law is given as rA = kC1/3A C2/3B . A is fed at a rate of Fa0 = 1.08 lbmol / h
B is fed at a rate of0.5Fa0
FN2 = FB0.8/0.2
for the conditions and bed are provided as 0.01661 / (lbm cat).
Let us estimate the catalyst weight required toachieve 60% conversion of A.
1 import numpy as np
2 from scipy.integrate import odeint
3
4 Fa0 = 1.08 # lbmol / h
5 Fb0 = 0.5 * Fa0
6 FI0 = Fb0 * 0.8 / 0.2 # flow rate of N2
7 Fc0 = 0.0
8
9 Ft0 = Fa0 + Fb0 + FI0 + Fc0
10 P0 = 10 # atm
11
12 alpha = 0.0166 # 1 / lb_m cat13 k = 0.0141 # lb-mol / (atm * lb_m cat * h)
14
15 def ode(F, W):
16 Fa, Fb, Fc, y = F
52
-
7/29/2019 06 625 Course Notes
53/97
17 P = y * P0
18
19 Ft = sum(F) + FI0 # do not forget the inerts!
20
21 Pa = Fa / Ft * P
22 Pb = Fb / Ft * P
23
24 ra = -k * Pa**(1.0/3.0) * Pb**(2.0 / 3.0)
25
26 dFadW = ra
27 dFbdW = 0.5 * ra
28 dFcdW = -ra
29 dydW = -alpha /(2 * y) * Ft / Ft0
30
31 return [dFadW, dFbdW, dFcdW, dydW]
32
33 y0 = 1.0 # P0/P0
34 F0 = [Fa0, Fb0, Fc0, y0]
35
36 Wspan = np.linspace(0, 50) # lb_m cat
37
38 sol = odeint(ode, F0, Wspan)
39
40 import matplotlib.pyplot as plt
41
42 plt.plot(Wspan, sol[:,0:3])
43 plt.legend([A,B,C],loc=8)
44 plt.xlabel(Catalyst weight ($lb_m$))
45 plt.ylabel(Molar flow (mol/min))
46
47 ax1 = plt.gca()
48 ax2 = ax1.twinx()
49 plt.plot(Wspan, sol[:,3], k--)
50 plt.ylabel($P/P_0$)
51 plt.legend([$P/P_0$],loc=NorthEast)
52 plt.savefig(images/pressure-drop-pfr.png)
53 plt.show()
53
-
7/29/2019 06 625 Course Notes
54/97
2.7 Transient CSTR
We can model the startup of a CSTR as an ordi-nary differential equation. We start with the usualmole balance:
dNAdt = FA0 FA + V rA
and an initial condition on the concentration ofAin the reactor.
Suppose that the reactor starts out full of solvent,
with no A present CA(t = 0) = 0. The reactor isat constant volume, so we rewrite the mole balanceas:
54
-
7/29/2019 06 625 Course Notes
55/97
dCAdt = FA0/V
FA/V + rA
We will presume a first order reaction, rA = kCAwith k = 0.11 1/min. A flows into the reactor at aconcentration of 0.5 mol/L at a rate of 1.5 L/min.The reactor is 2 L in volume.
Let us plot the exit concentration as a function oftime.
1 import numpy as np
2 from scipy.integrate import odeint
3
4 CAin = 0.5 # mol/L
5 v0 = 1.5 # L/min
6 V = 2.0 # reactor volume (L)
7
8 FA0 = CAin * v0
9
10 k = 0.11 # rate constant (1/min)
11 def dCadt(Ca, t):12 rA = -k * Ca
13 return FA0 / V - v0 * Ca / V + rA
14
15 tspan = np.linspace(0.0, 20.0)
16 Ca0 = 0.0 # initial condition in the tank
17 sol = odeint(dCadt, Ca0, tspan)
18
19 import matplotlib.pyplot as plt
20 plt.plot(tspan, sol)
21 plt.xlabel(Time (min))
22 plt.ylabel($C_A$ (mol/L))
23 plt.savefig(images/transient-cstr.png)
24 plt.show()
55
-
7/29/2019 06 625 Course Notes
56/97
You can see that the concentration initially in-creases
That is because the tank is initially empty and
fills up
Eventually a steady state concentration occurs
In this case, the conversion is low because the re-action rate is slow
Note that unlike solving for the steady state solu-tion using fsolve, here we do not need an initialguess.
Instead, we start with an initial condition
56
-
7/29/2019 06 625 Course Notes
57/97
There are scenarios where there are multiple steady
satte solutions.
In those cases the solution you get depends onthe initial conditions
This is analogous to the solution depending onthe initial guess in a non-linear algebra problem
2.8 SummaryYou should have learned:
1. How stoichiometry determines changes in the molesof species in a reaction
2. How the relative rates of species production arerelated by stoichiometry
3. Mole balances for a batch reactor, continuouslystirred tank reactor, and plug flow reactor
4. Mole balances for reactors with pressure drops andfor reactions that change the total number of moles
You have seen examples of:
1. solving nonlinear equations
2. integrating ordinary differential equations
57
-
7/29/2019 06 625 Course Notes
58/97
3 Multiple reactions
3.1 Stoichiometry in multiple reac-tions
When we have multiple reactions, e.g.
A 2B B
C
We have a scenario where a species maybe con-sumed and/or generated by multiple reactions.
We can define a reaction extent j for each reaction.
then for each species we can determine the changein moles from all the reactions as:
ni = ni0 +j ijj
where ij is the stoichiometric coefficient of speciesi in reaction j.
3.2 Rates for multiple reactions
When we have multiple reactions, e.g.
A 2B B C
58
-
7/29/2019 06 625 Course Notes
59/97
we have a scenario where a species maybe consumed
and/or generated by multiple reactions.
Each reaction will have its own reaction rate. Wedenote the rate of reaction i as ri.
In this example, we might have r1 = k1CA, andr2 = k2CB.
Then we have from reaction 1 that the rate of pro-duction of B is r1,B = 2r1 and the rate of con-sumption in reaction two is r2,B = r2.
The net rate of production of species B is the sumof these two species specific rates.
rB = 2r1 r2 = 2k1CA k2CB
This is the expression we would use in a speciesmole balance. For example, in a constant volumebatch reactor we would have:
dNBdt = V rB = V(2k1CA k2CB)
In this example you also need another mole balanceon species A.
Critical points
We need rate laws for each reaction
59
-
7/29/2019 06 625 Course Notes
60/97
You derive the species rates using stoichiometry
for each reaction You add all the species rates together to get the
net rate of reaction for the species
Let us work out this example completely. Let usconsider a constant volume batch reactor.
1 import numpy as np
2 from scipy.integrate import odeint3
4 k1 = 0.09 # 1/min
5 k2 = 0.2 # 1/min
6
7 CA0 = 2.5 # mol/L
8
9 def batch(C, t):
10 Ca, Cb = C
11 r1 = k1 * Ca
12 r2 = k2 * Cb
13
14 ra = -r1
15 rb = 2.0 * r1 - r2
16
17 dCadt = ra
18 dCbdt = rb
19
20 return [dCadt, dCbdt]
21
22 init = [CA0, 0.0] # initial conditions
23 tspan = np.linspace(0, 30) # min
24 sol = odeint(batch, init, tspan)
25
26 import matplotlib.pyplot as plt
27 plt.plot(tspan, sol)
28 plt.xlabel(Time (min))
29 plt.ylabel(Conc (mol/L))
60
-
7/29/2019 06 625 Course Notes
61/97
30 plt.legend([A,B])
31
32 plt.savefig(images/batch-multiple.png)
33 plt.show()
You can see here that A continuously disappears.A is only consumed in the first reaction.
Initially, B increases as it is produced by the firstreaction. However, it begins to be consumed by re-action two, and eventually is completely consumed.
If B was the desired product, you could maximize
the yield by stopping the reaction after a shorttime.
61
-
7/29/2019 06 625 Course Notes
62/97
3.3 Reversible chemical reactions
Reversible reactions are a special case of multiplereactions. We consider a forward and reverse reac-tion.
For example, in the water gas shift reaction wecould write the forward reaction as:
CO + H2O
H2 + CO2
and the reverse reaction as:H2 + CO2 CO + H2OWe write this as CO + H2O H2 + CO2.
All reactions are to some extent reversible
A + bB + qQ + sS+
Thermodynamics defines the equilibrium distribu-tion of reactants and products
aqQasS
aAabB
= Keq = eG/RT
where G is the reaction Gibbs free energy definedby G =
j jGj
aA is the activity of speciesA raised to the power.
It is common to use concentration instead of activ-ity, but you must remember this implies ideal ac-tivity, and that the equilibrium constant may endup having units.
62
-
7/29/2019 06 625 Course Notes
63/97
Recall that activity is dimensionless
We sometimes express activity in terms of concen-tration using activity coefficients
aj = jCj
For ideal solutions, j = 1
3.3.1 A brief worked example.
The water gas shift reaction H2O+CO CO2 +H2 has a Gibbs reaction energy of -730 cal/mol at1000K.
If you start with equimolar amounts of water andcarbon monoxide at a total pressure of 10 atm,what is the equilibrium composition of gases inmol/L?
There is no change in the number of moles for thereaction, the temperature is constant, and thus thevolume is constant.
We essentially only need to find the equilibriumextent of reaction, and the problem is solved. Weknow in this case that Cj = Cj,0 + j. At equi-
librium, we will have:
K= CC,eqCD,eqCA,eqCB,eq =(CC,0+Ceq)(CD,0+Deq)(CA,0+Aeq)(CB,0+Beq)
63
-
7/29/2019 06 625 Course Notes
64/97
After simplification, we have:
K= 2eq
(CA,0eq)2
So, we simply evaluate K for the conditions, andthen solve for eq.
Let CA0 = the concentration of A and B initiallyin the reactor. CA0 = PA0/(RT).
1 import numpy as np
2 R = 1.987 # c a l / m o l / K 3 dG = -730 # cal / mol
4 T = 1000.0 # K
5
6 K = np.exp(-dG / R / T)
7 print(K = {0}.format(K))
8
9 Pa0 = 5 # atm
10 R = 0.082057 # L atm / (mol K)
11 Ca0 = Pa0 / (R * T)
12
13
def func(xi):14 return K - (xi**2) / (Ca0 - xi)**2
15
16 from scipy.optimize import fsolve
17 guess = 0.05
18 xi_eq, = fsolve(func, guess)
19 print xi_eq = ,xi_eq
20
21 print C_A = {0:1.4f} mol / L.format(Ca0 - xi_eq)
22 print C_C = {0:1.4f} mol / L.format(xi_eq)
K = 1.44395809814xi_eq = 0.0332570531518
C_A = 0.0277 mol / L
C_C = 0.0333 mol / L
64
-
7/29/2019 06 625 Course Notes
65/97
An alternative formulation that uses the linear al-
gebra notation follows. The advantage of this ap-proach is that we do not need to derive the equa-tion to solve, it is simply the definition of the equi-librium constant. The code, on the other hand, isa little more verbose, while simultaneously beingmore explicit.
1 import numpy as np
2 R = 1.987 # cal / mol / K
3 dG = -730 # cal / mol
4 T = 1000.0 # K
5
6 K = np.exp(-dG / R / T)
7 print(K = {0}.format(K))
8
9 Pa0 = 5 # atm
10 R = 0.082057 # L atm / (mol K)
11 Ca0 = Pa0 / (R * T)
12
13 def func(xi):
14 nu = np.array([-1, -1, 1, 1]) # stoichiometric coefficients15 C0 = np.array([Ca0, Ca0, 0.0, 0.0]) # initial concentrations
16 C = C0 + nu * xi
17 return K - np.prod(C**nu)
18
19 from scipy.optimize import fsolve
20 guess = 0.05
21 xi_eq, = fsolve(func, guess)
22
23 print C_A = {0:1.4f} mol / L.format(Ca0 - xi_eq)
24 print C_C = {0:1.4f} mol / L.format(xi_eq)
K = 1.44395809814
C_A = 0.0277 mol / L
C_C = 0.0333 mol / L
65
-
7/29/2019 06 625 Course Notes
66/97
We get the same result as before.
3.3.2 Temperature dependent equilibriumconstants
We defined the equilibrium constant as K= eG/(RT).
Thus, the equilibrium constant is temperature de-pendent because of the T in the denominator ofthe exponential.
It is also temperature dependent because G istemperature dependent.
To incorporate the temperature dependence of theGibbs free energy of a reaction, we need to computethe temperature dependence.
The NIST Webbook provides data about a largenumber of compounds which can be used to com-pute reaction energies.
Let us consider CO.
At http://webbook.nist.gov/cgi/cbook.cgi ?ID=C630080&Units=SI&Mask=1#Thermo-Gas youwill find the data needed to compute the Gibbs freeenergy of CO at arbitrary temperature and stan-dard pressure.
66
http://webbook.nist.gov/chemistry/http://webbook.nist.gov/cgi/cbook.cgi?ID=C630080&Units=SI&Mask=1#Thermo-Gashttp://webbook.nist.gov/cgi/cbook.cgi?ID=C630080&Units=SI&Mask=1#Thermo-Gashttp://webbook.nist.gov/cgi/cbook.cgi?ID=C630080&Units=SI&Mask=1#Thermo-Gashttp://webbook.nist.gov/cgi/cbook.cgi?ID=C630080&Units=SI&Mask=1#Thermo-Gashttp://webbook.nist.gov/chemistry/ -
7/29/2019 06 625 Course Notes
67/97
You will find the standard heat of formation
and entropy, coefficients of the Shomate polynomials which
are used to calculate the enthalpy and entropyat non-standard temperatures.
The Shomate polynomials are polynomials in t =T/1000.
H = HF,298.15 + At + Bt2/2 + Ct3/3 + Dt4/4 E/t + FHS= A ln(t) + Bt + Ct2/2 + Dt3/3 E/(2t2) + G
With this information, we can calculate G for COat any temperature: G = H TS. If we havethis information for all of the species, then we cancompute the reaction energy at any temperature.
Grxn(T) = GJ(T)
1 import numpy as np
2
3 R = 8.314e-3 # kJ/mol/K
4
5 P = 10.0 # atm, this is the total pressure in the reactor
6 Po = 1.0 # atm, this is the standard state pressure
7
8 species = [CO, H2O, CO2, H2]9
10 # Heats of formation at 298.15 K
11
12 Hf298 = [-110.53, # CO
67
-
7/29/2019 06 625 Course Notes
68/97
13 -241.826, # H2O
14 -393.51, # CO2
15 0.0] # H2
16
17 # Shomate parameters for each species
18 # CO H2O CO2 H2
19 WB = [[25.56759, 30.092, 24.99735, 33.066178], # A
20 [6.09613, 6.832514, 55.18696, -11.363417], # B
21 [4.054656, 6.793435, -33.69137, 11.432816], # C
22 [-2.671301, -2.53448, 7.948387, -2.772874], # D
23 [0.131021, 0.082139, -0.136638, -0.158558], # E
24 [-118.0089, -250.881, -403.6075, -9.980797],# F
25 [227.3665, 223.3967, 228.2431, 172.707974], # G
26 [-110.5271, -241.8264, -393.5224, 0.0]] # H27
28 WB = np.array(WB).T
29
30 def G_rxn(T):
31 # Shomate equations
32 t = T/1000
33 T_H = np.array([t, t**2 / 2.0, t**3 / 3.0,
34 t**4 / 4.0, -1.0 / t, 1.0, 0.0, -1.0])
35 T_S = np.array([np.log(t), t, t**2 / 2.0, t**3 / 3.0,
36 -1.0 / (2.0 * t**2), 0.0, 1.0, 0.0])
37
38 H = np.dot(WB, T_H) # (H - H_298.15) kJ/mol
39 S = np.dot(WB, T_S / 1000.0) # absolute entropy kJ/mol/K
40
41 Gjo = Hf298 + H - T * S # Gibbs energy of each component at 1000 K
42
43 nu = np.array([-1, -1, 1, 1])
44 Grxn = np.dot(nu, Gjo)
45 return Grxn
46
47 print Reaction energy at 1000K = {0} kJ/mol.format(G_rxn(1000))
48
49 # print energy in different units50 import quantities as u
51
52 e500 = (G_rxn(500.0) * 1000 * u.J / u.mol ).rescale(u.cal / u.mol)
53 e1000 = (G_rxn(1000.0) * 1000 * u.J / u.mol ).rescale(u.cal / u.mol)
68
-
7/29/2019 06 625 Course Notes
69/97
54
55 print At 1000 K the reaction energy is {0}.format(e1000)
56 print At 500 K the reaction energy is {0}.format(e500)
Reaction energy at 1000K = -3.00803816667 kJ/mol
At 1000 K the reaction energy is -718.938376354 cal/mo
At 500 K the reaction energy is -4890.09976584 cal/mol
You can see here that lower temperatures make thereaction much more exothermic.
The equilibrium constant would be considerablylarger, and the products more favored at the lowertemperature.
3.3.3 Another view of chemical equilibrium
The composition at chemical equilibrium is the one
that minimizes the Gibbs free energy of the mix-ture.
We can use the data for computing the Gibbs freeenergy of pure components to illustrate this.
We have to compute the Gibbs free energy of aspecies in the mixture, which is easy if we can as-
sume an ideal mixture. Then we have
Gj = Gj0 + RT log(xjP/P0).
69
-
7/29/2019 06 625 Course Notes
70/97
Here we will show that there is a reaction extent
that minimizes the Gibbs free energy of the mix-ture.
1 import numpy as np
2 T = 1000 # K
3 R = 8.314e-3 # kJ/mol/K
4 R_ = 0.082057 # L * a t m / m o l / K
5
6 P = 10.0 # atm, this is the total pressure in the reactor
7 Po = 1.0 # atm, this is the standard state pressure
8
9 species = [CO, H2O, CO2, H2]
10 nu = np.array([-1, -1, 1, 1]) # stoichiometric coefficients
11
12 # Heats of formation at 298.15 K
13 Hf298 = [
14 -110.53, # CO
15 -241.826, # H2O
16 -393.51, # CO2
17 0.0] # H2
18
19 # Shomate parameters for each species
20 # CO H2O CO2 H2 21 WB = [[25.56759, 30.092, 24.99735, 33.066178], # A
22 [6.09613, 6.832514, 55.18696, -11.363417], # B
23 [4.054656, 6.793435, -33.69137, 11.432816], # C
24 [-2.671301, -2.53448, 7.948387, -2.772874], # D
25 [0.131021, 0.082139, -0.136638, -0.158558], # E
26 [-118.0089, -250.881, -403.6075, -9.980797],# F
27 [227.3665, 223.3967, 228.2431, 172.707974], # G
28 [-110.5271, -241.8264, -393.5224, 0.0]] # H
29
30 WB = np.array(WB).T
31
32 # Shomate equations
33 t = T/1000
34 T_H = np.array([t, t**2 / 2.0, t**3 / 3.0, t**4 / 4.0,
35 -1.0 / t, 1.0, 0.0, -1.0])
36 T_S = np.array([np.log(t), t, t**2 / 2.0, t**3 / 3.0,
70
-
7/29/2019 06 625 Course Notes
71/97
37 -1.0 / (2.0 * t**2), 0.0, 1.0, 0.0])
38
39 H = np.dot(WB, T_H) # (H - H_298.15) kJ/mol
40 S = np.dot(WB, T_S / 1000.0) # absolute entropy kJ/mol/K
41
42 Gjo = Hf298 + H - T * S # Gibbs energy of each component at 1000 K
43
44 C0 = np.array([5.0, 5.0, 0.0, 0.0]) / R_ / T # initial concentrations
45
46 @np.vectorize
47 def G_tot(xi):
48
49 C = C0 + xi * nu # change in moles from reaction extent
50
51 x = C / C.sum() # mole fractions
52
53 # Species gibbs energies in mixture
54 G = Gjo + R * T * np.log(x * P / Po)
55 return np.dot(C, G)
56
57
58 XI = np.linspace(0, max(C0))
59
60 import matplotlib.pyplot as plt
61 plt.plot(XI, G_tot(XI))
62 plt.xlabel($\\xi$ (mol))
63 plt.ylabel($G$ (kJ/mol))
64 plt.savefig(images/equilibrium-G.png)
65 plt.show()
71
-
7/29/2019 06 625 Course Notes
72/97
You have to be careful not to exceed the maximum. You can see there is a minimum in the Gibbs energyof the mixture, and it corresponds to the value we saw
previously.
We could find the minimum numerically using opti-mization algorithms. For completeness we show thathere:
1 import numpy as np
2 T = 1000 # K
3 R = 8.314e-3 # kJ/mol/K
4 R_ = 0.082057 # L * a t m / m o l / K
5
6 P = 10.0 # atm, this is the total pressure in the reactor
7 Po = 1.0 # atm, this is the standard state pressure8
9 species = [CO, H2O, CO2, H2]
10 nu = np.array([-1, -1, 1, 1]) # stoichiometric coefficients
11
72
-
7/29/2019 06 625 Course Notes
73/97
12 # Heats of formation at 298.15 K
13 Hf298 = [
14 -110.53, # CO
15 -241.826, # H2O
16 -393.51, # CO2
17 0.0] # H2
18
19 # Shomate parameters for each species
20 # CO H2O CO2 H2
21 WB = [[25.56759, 30.092, 24.99735, 33.066178], # A
22 [6.09613, 6.832514, 55.18696, -11.363417], # B
23 [4.054656, 6.793435, -33.69137, 11.432816], # C
24 [-2.671301, -2.53448, 7.948387, -2.772874], # D
25 [0.131021, 0.082139, -0.136638, -0.158558], # E26 [-118.0089, -250.881, -403.6075, -9.980797],# F
27 [227.3665, 223.3967, 228.2431, 172.707974], # G
28 [-110.5271, -241.8264, -393.5224, 0.0]] # H
29
30 WB = np.array(WB).T
31
32 # Shomate equations
33 t = T/1000
34 T_H = np.array([t, t**2 / 2.0, t**3 / 3.0,
35 t**4 / 4.0, -1.0 / t, 1.0, 0.0, -1.0])
36 T_S = np.array([np.log(t), t, t**2 / 2.0,
37 t**3 / 3.0, -1.0 / (2.0 * t**2),
38 0.0, 1.0, 0.0])
39
40 H = np.dot(WB, T_H) # (H - H_298.15) kJ/mol
41 S = np.dot(WB, T_S / 1000.0) # absolute entropy kJ/mol/K
42
43 Gjo = Hf298 + H - T * S # Gibbs energy of each component at 1000 K
44
45 C0 = np.array([5.0, 5.0, 0.0, 0.0]) / R_ / T # initial concentrations
46
47 @np.vectorize
48 def G_tot(xi):49
50 C = C0 + xi * nu # change in moles from reaction extent
51
52 x = C / C.sum() # mole fractions
73
-
7/29/2019 06 625 Course Notes
74/97
53
54 # Species gibbs energies in mixture
55 G = Gjo + R * T * np.log(x * P / Po)
56 return np.dot(C, G)
57
58 from scipy.optimize import fmin
59
60 xi_guess = 0.03
61 sol, = fmin(G_tot, xi_guess)
62 print The Gibbs free energy is minimized at xi = {0} mol/L.format(sol)
Optimization terminated successfully.
Current function value: -46.204283Iterations: 6
Function evaluations: 12
The Gibbs free energy is minimized at xi = 0.0331875 m
3.3.4 Gibbs energy constrained minimiza-tion and the NIST webbook
We used the NIST webbook to compute a tem-perature dependent Gibbs energy of reaction, andthen used a reaction extent variable to computethe equilibrium concentrations of each species forthe water gas shift reaction.
Here we look at the direct minimization of theGibbs free energy of the species, with no assump-
tions about stoichiometry of reactions. We onlyapply the constraint of conservation of atoms. Weuse the NIST Webbook to provide the data for theGibbs energy of each species.
74
-
7/29/2019 06 625 Course Notes
75/97
As a reminder we consider equilibrium between the
species CO, H2O, CO2 and H2, at 1000K, and 10atm total pressure with an initial equimolar molarflow rate ofCO and H2O.
1 import numpy as np
2
3 T = 1000 # K
4 R = 8.314e-3 # kJ/mol/K
5
6 P = 10.0 # atm, this is the total pressure in the reactor
7 Po = 1.0 # atm, this is the standard state pressure
We are going to store all the data and calculationsin vectors, so we need to assign each position in thevector to a species. Here are the definitions we use inthis work.
1 CO
2 H2O
3 CO2
4 H2
1 species = [CO, H2O, CO2, H2]
2
3 # Heats of formation at 298.15 K
4
5 Hf298 = [
6 -110.53, # CO
7 -241.826, # H2O
8 -393.51, # CO2
9 0.0] # H2
10
75
-
7/29/2019 06 625 Course Notes
76/97
11
12 # Shomate parameters for each species
13 # CO H2O CO2 H2
14 WB = [[25.56759, 30.092, 24.99735, 33.066178], # A
15 [6.09613, 6.832514, 55.18696, -11.363417], # B
16 [4.054656, 6.793435, -33.69137, 11.432816], # C
17 [-2.671301, -2.53448, 7.948387, -2.772874], # D
18 [0.131021, 0.082139, -0.136638, -0.158558], # E
19 [-118.0089, -250.881, -403.6075, -9.980797],# F
20 [227.3665, 223.3967, 228.2431, 172.707974], # G
21 [-110.5271, -241.8264, -393.5224, 0.0]] # H
22
23 WB = np.array(WB).T
24
25 # Shomate equations
26 t = T/1000
27 T_H = np.array([t, t**2 / 2.0, t**3 / 3.0, t**4 / 4.0,
28 -1.0 / t, 1.0, 0.0, -1.0])
29 T_S = np.array([np.log(t), t, t**2 / 2.0, t**3 / 3.0,
30 -1.0 / (2.0 * t**2), 0.0, 1.0, 0.0])
31
32 H = np.dot(WB, T_H) # (H - H_298.15) kJ/mol
33 S = np.dot(WB, T_S/1000.0) # absolute entropy kJ/mol/K
34
35 Gjo = Hf298 + H - T*S # Gibbs energy of each component at 1000 K
Now, construct the Gibbs free energy function, ac-counting for the change in activity due to concen-tration changes (ideal mixing).
1 def func(nj):
2 nj = np.array(nj)
3 Enj = np.sum(nj);
4 Gj = Gjo / (R * T) + np.log(nj / Enj * P / Po)
5 return np.dot(nj, Gj)
We impose the constraint that all atoms are con-served from the initial conditions to the equilib-
76
-
7/29/2019 06 625 Course Notes
77/97
rium distribution of species. These constraints are
in the form ofAeqn = beq, where n is the vector ofmole numbers for each species.
1 Aeq = np.array([[ 1, 0, 1, 0], # C balance
2 [ 1, 1, 2, 0], # O balance
3 [ 0, 2, 0, 2]]) # H balance
4
5 # equimolar feed of 1 mol H2O and 1 mol CO
6 beq = np.array([1, # mol C fed
7 2, # mol O fed
8 2]) # mol H fed9
10 def ec1(nj):
11 conservation of atoms constraint
12 return np.dot(Aeq, nj) - beq
Now we are ready to solve the problem.
1 from scipy.optimize import fmin_slsqp
2
3 n0 = [0.5, 0.5, 0.5, 0.5] # initial guesses
4 N = fmin_slsqp(func, n0, f_eqcons=ec1)
5 print N
>>> >>> Optimization terminated successfully. (Exit
Current function value: -91.204832308
Iterations: 2
Function evaluations: 13Gradient evaluations: 2
[ 0.45502309 0.45502309 0.54497691 0.54497691]
77
-
7/29/2019 06 625 Course Notes
78/97
1. Compute mole fractions and partial pressures
The pressures here are in good agreement withthe pressures found by other methods. The mi-nor disagreement (in the third or fourth decimalplace) is likely due to convergence tolerances inthe different algorithms used.
1 yj = N / np.sum(N)
2
Pj = yj * P3
4 for s, y, p in zip(species, yj, Pj):
5 print {0:10s}: {1:1.2f} {2:1.2f}.format(s, y, p)
>>> >>> ... ... CO : 0.23 2.28
H2O : 0.23 2.28
CO2 : 0.27 2.72
H2 : 0.27 2.72
2. Computing equilibrium constants
We can compute the equilibrium constant forthe reaction CO + H2O CO2 + H2.
Compared to the value of K = 1.44 we found
previously, the agreement is excellent.
Note, that to define an equilibrium constant itis necessary to specify a reaction, even though
78
-
7/29/2019 06 625 Course Notes
79/97
it is not necessary to even consider a reaction to
obtain the equilibrium distribution of species!
1 nuj = np.array([-1, -1, 1, 1]) # stoichiometric coefficients of the react
2 K = np.prod(yj**nuj)
3 print K
>>> 1.43446295961
3.3.5 Finding equilibrium composition bydirect minimization of Gibbs free en-ergy on mole numbers
Adapted from problem 4.5 in Cutlip and Shacham
Ethane and steam are fed to a steam cracker at a
total pressure of 1 atm and at 1000K at a ratio of 4mol H2O to 1 mol ethane. Estimate the equilibriumdistribution of products (CH4, C2H4, C2H2, CO2,CO, O2, H2, H2O, and C2H6).
Solution method: We will construct a Gibbs energyfunction for the mixture, and obtain the equilib-rium composition by minimization of the function
subject to elemental mass balance constraints.
1 import numpy as np
2
3 R = 0.00198588 # kcal/mol/K
79
-
7/29/2019 06 625 Course Notes
80/97
4 T = 1000 # K
5
6 species = [CH4, C2H4, C2H2, CO2, CO, O2, H2, H2O, C2H6]
7
8 # $G_^\circ for each species. These are the heats of formation for each
9 # species.
10 Gjo = np.array([4.61, 28.249, 40.604, -94.61, -47.942, 0, 0, -46.03, 26.13]) #
1. The Gibbs energy of a mixture
We start with G =jnjj.
Recalling that we define j = Gj + RT ln aj,and in the ideal gas limit, aj = yjP/P
, andthat yj =
njnj
.
Since in this problem, P = 1 atm, this leads tothe function GRT =
nj=1
njGjRT + ln
njnj
.
1 import numpy as np
2
3 def func(nj):
4 nj = np.array(nj)
5 Enj = np.sum(nj);
6 G = np.sum(nj * (Gjo / R / T + np.log(nj / Enj)))
7 return G
2. Linear equality constraints for atomic mass conser-vation
The total number of each type of atom must bethe same as what entered the reactor. Theseform equality constraints on the equilibriumcomposition.
80
-
7/29/2019 06 625 Course Notes
81/97
We express these constraints as: Aeqn = b
where n is a vector of the moles of each speciespresent in the mixture.
1 Aeq = np.array([[0, 0, 0, 2, 1, 2, 0, 1, 0], # oxygen b
2 [4, 4, 2, 0, 0, 0, 2, 2, 6], # hydrogen
3 [1, 2, 2, 1, 1, 0, 0, 0, 2]]) # carbon b
4
5 # the incoming feed was 4 mol H2O and 1 mol ethane
6 beq = np.array([4, # moles of oxygen atoms coming in
7 14, # moles of hydrogen atoms coming in
8 2]) # moles of carbon atoms coming in9
10 def ec1(n):
11 equality constraint
12 return np.dot(Aeq, n) - beq
Now we solve the problem.
1 # initial guess suggested in the example
2
n0 = [1e-3, 1e-3, 1e-3, 0.993, 1.0, 1e-4, 5.992, 1.0, 1e-3]3
4 from scipy.optimize import fmin_slsqp
5
6 X = fmin_slsqp(func, n0, f_eqcons=ec1, iter=300, acc=1e-12)
7
8 for s,x in zip(species, X):
9 print {0:10s} {1:1.4g}.format(s, x)
10
11 # check that constraints were met
12 print np.dot(Aeq, X) - beq
13 print np.all( np.abs( np.dot(Aeq, X) - beq) < 1e-12)
>>> >>> >>> >>> Optimization terminated successfull
Current function value: -104.403947663
81
-
7/29/2019 06 625 Course Notes
82/97
Iterations: 217
Function evaluations: 2937Gradient evaluations: 217
>>> ... ... CH4 0.06694
C2H4 8.108e-08
C2H2 5.174e-08
CO2 0.5441
CO 1.389
O2 1.222e-14
H2 5.343H2O 1.523
C2H6 8.44e-08
... [ -1.66977543e-13 1.77635684e-15 4.44089210
True
I found it necessary to tighten the accuracy pa-rameter to get pretty good matches to the so-
lutions found in Matlab. It was also necessaryto increase the number of iterations. Even still,not all of the numbers match well, especiallythe very small numbers. You can, however, seethat the constraints were satisfied pretty well.
Interestingly there is a distribution of products!
That is interesting because only steam and ethaneenter the reactor, but a small fraction of methaneis formed!
82
-
7/29/2019 06 625 Course Notes
83/97
The main product is hydrogen. The stoichiom-
etry of steam reforming is ideallyC2H6+4H2O 2CO2+7H2. Even though nearly all the ethaneis consumed, we do not get the full yield of hy-drogen.
It appears that another equilibrium, one be-tween CO, CO2, H2O and H2, may be limitingthat, since the rest of the hydrogen is largely inthe water.
It is also of great importance that we have notsaid anything about reactions, i.e. how theseproducts were formed.
The water gas shift reaction is: CO + H2O
CO2 +H2. We can compute the Gibbs free en-ergy of the reaction from the heats of formationof each species. Assuming these are the forma-tion energies at 1000K, this is the reaction freeenergy at 1000K.
1 G_wgs = Gjo[3] + Gjo[6] - Gjo[4] - Gjo[7]
2 print G_wgs
3
4 K = np.exp(-G_wgs / (R*T))
5 print K
-0.638
>>> >>> 1.37887528109
83
-
7/29/2019 06 625 Course Notes
84/97
3. Equilibrium constant based on mole numbers
One normally uses activities to define the equi-librium constant. Since there are the samenumber of moles on each side of the reaction allfactors that convert mole numbers to activity,concentration or pressure cancel, so we simplyconsider the ratio of mole numbers here.
1 print (X[3] * X[6]) / (X[4] * X[7])
1.37450039394
This is close, but not exactly the same as theequilibrium constant computed above. Theyshould be exactly the same, and the differenceis due to convergence errors in the solution to
the problem. Clearly, there is an equilibrium between these
species that prevents the complete reaction ofsteam reforming.
4. Summary
This is an appealing way to minimize the Gibbs
energy of a mixture. No assumptions aboutreactions are necessary, and the constraints areeasy to identify. The Gibbs energy function isespecially easy to code.
84
-
7/29/2019 06 625 Course Notes
85/97
3.3.6 Equilibria with multiple reactions
We use the same fundamental approaches to solv-ing equilibrium problems when there are multiplereactions
In fact, we do not need to consider reactions atall if we know the Gibbs free energies of eachspecies
Let us consider this set of reactions, where all speciesare in the gas phase. Assume we start with equimo-lar amounts of A and B, and a total pressure of 2.5atm at 400 K.
A + B Cwith K1 = 108
A + B D with K2 = 284
We want to know what the equilibrium compositionfor these reactions are.
We have two equations: K1 =aCaAaB
andK2 =
aDaAaB
We know the activity of a gas species is aj =Pj/1atm or equivalently in mole fraction: aj =
xjP/1atm.
We define reaction extents for each reaction: 1and 2
85
-
7/29/2019 06 625 Course Notes
86/97
Then:
nA = nA0 1 2 (17)nB = nB0 1 2 (18)
nC = 1 (19)
nD = 2 (20)
ntotal = nt0 1 2 (21)
We can define mole fractions from these equationswhich allow us to express the equilibrium equationsin two unknowns.
It is convenient to normalize all equations by nt0,which leads to these definitions for the mole frac-tions:
yA =yA0 1 2
1 1 2(22)
yB =yB0 1 2
1 1 2(23)
yC =1
1
1
2
(24)
yD = 2
1 1 2(25)
(26)
86
-
7/29/2019 06 625 Course Notes
87/97
These are plugged into the activity aj = yjP/1atm.
Here is the code we use to solve this problem.
1 ya0 = 0.5 # initial mole fraction of A
2 yb0 = 0.5 # initial mole fraction of B
3 P = 2.5 # initial pressure in atm
4
5 def xj(extent):
6 convenience function to calculate mole fractions
7 ext1, ext2 = extent
8 ya = (ya0 - ext1 - ext2) / (1.0 - ext1 - ext2)
9 yb = (yb0 - ext1 - ext2) / (1.0 - ext1 - ext2)
10 yc = (ext1) / (1.0 - ext1 - ext2)
11 yd = (ext2) / (1.0 - ext1 - ext2)
12 return [ya, yb, yc, yd]
13
14 def func(extent):
15 zeros function for fsolve
16 ya, yb, yc, yd = xj(extent)
17
18 eq1 = 108.0 - (yc * P)/(ya * P * yb * P)
19 eq2 = 284.0 - (yd * P)/(ya * P * yb * P)
20
21 return [eq1, eq2]
22
23 from scipy.optimize import fsolve
24
25 guess = [0.1, 0.39]
26 sol = fsolve(func, guess)
27
28 print The reaction extents are:\n,sol
29
30 print The mole fractions are: \n,xj(sol)
The reaction extents are:
[ 0.13335692 0.35067931]
The mole fractions are:
87
-
7/29/2019 06 625 Course Notes
88/97
[0.030939713802784111, 0.030939713802784111, 0.2584617
There are significant amounts of each product
Note that other initial guesses give unphysical so-lutions, i.e. negative mole fractions.
Also note that this solution applies to a constanttotal pressurewhich means in this case the volumemust be changing since there is a change in thenumber of moles
You would get a different result in a constantvolume reactor where the total pressure changes
There is a constraint on the two reaction extents.
since no mole fraction can be negative, 1+2
yA0 Other solutions violate this constraint
You may have to use constrained optimizationto find physical solutions
3.4 Rate laws for reversible reactions
We can think of reversible reactions as two reac-tions going in opposite directions.
A + B C+ D can be thought of as:
88
-
7/29/2019 06 625 Course Notes
89/97
A + B C+ D C+ D A + B Each reaction has a forward reaction rate, e.g.:
r1 = k1CACBr2 = k2CCCD
Now, to find the rate that species A is "generated"
we have:
r1A = r1 and r2A = r2, and the net rate is rA =r1A + r2A = k1CACB + k2CCCD. At equilibrium, the net rate must be zero, which
means:
k1CA,eqCB,eq = k2CC,eqCD,eq
or:k1k2
= CC,eqCD,eqCA,eqCB,eq = Keq
You can see that between k1, k2 and Keq, only twoof them are independent. i.e. k2 = k1/Keq.
Thus, we may also see net reaction rates for equi-librium reaction rates written as:
rA = k1(CACB CCCDKeq ) It is important that these constraints exist, so that
thermodynamics are not violated.
89
-
7/29/2019 06 625 Course Notes
90/97
3.4.1 A CSTR with a reversible reaction
Recall the water gas shift reaction we discussedbefore H2O + CO CO2 + H2.
We previously calculated the equilibrium coeffi-cient to be 1.44 at 1000K.
Assume the reaction is elementary, and the forwardrate constant is k1 = 0.02 L / (mol * s)
The reactor is initially fed pure A and B at con-centrations of 0.05 mol / L.
What is the exit concentration of A? the reactorvolume is 10 L, and the volumetric flow rate intothe reactor is 0.01 L / s.
1
from scipy.optimize import fsolve2
3 Keq = 1.44395809814
4 v0 = 0.01 # L / s
5 V = 10 # L
6
7 k1 = 0.02 # L / m o l / s
8
9 Ca0 = Cb0 = 0.05 # m o l / L
10 Cc0 = Cd0 = 0.0
11
12 Fa0 = v0 * Ca0
13
14 def cstr(Ca):
15 xi = (Ca - Ca0) / (-1) # compute reaction extent
16 Cb = Cb0 - xi
17 Cc = Cc0 + xi
90
-
7/29/2019 06 625 Course Notes
91/97
18 Cd = Cd0 + xi
19
20 ra = -k1 * (Ca * Cb - (Cc * Cd) / Keq)
21 return Fa0 - Ca * v0 + V * ra
22
23 guess = 0.2
24 ca_exit, = fsolve(cstr, guess)
25
26 print the exit concentration of C_A is {0:1.4f} mol / L.format(ca_exit)
27 print the exit concentration of C_C is {0:1.4f} mol / L.format(Ca0 - ca_exit)
the exit concentration of C_A is 0.0327 mol / L
the exit concentration of C_C is 0.0173 mol / L
There is less C produced than you would expectfrom the equilibrium composition
The reactants are not in the reactor long enoughto reach equilibrium
You can explore this solution. Try using a lowervolumetric flow rate, or a larger volume reactor.You will see that the concentrations converge tothe equilibrium limit we computed before.
3.5 Mole balances with multiple re-actions
There is nothing particularly new in mole balanceswith multiple reactions
We still write species based mole balances
91
-
7/29/2019 06 625 Course Notes
92/97
We use the net rate law for each species
This typically leads to coupled equations
For CSTRs these are often coupled nonlinearalgebra equations
For PFRs these are often coupled differentialequations
3.5.1 Multiple reactions in a CSTR
We consider a reactor design with multiple reac-tions
Mesitylene (trimethyl benzene) can be hydrogenatedto form m-xylene, which can be further hydro-genated to toluene
The reactions we consider are:
M+ H2 X+ CH4 (27)X+ H2 T+ CH4 (28)
The reaction is carried out isothermally at 1500
R at 35 atm.
The feed is 2/3 hydrogen and 1/3 mesitylene
92
-
7/29/2019 06 625 Course Notes
93/97
The volumetric feed rate is 476 cubic feet per hour
and the reactor volume is 238 cubic feet
The rate laws are
r1 = k1CMC0.5H (29)
r2 = k2CXC0.5H (30)
The rate constants are:
k1 = 55.20(ft3/lb mol)0.5/h (31)
k2 = 30.20(ft3/lb mol)0.5/h (32)
(33)
Here is the code we need to setup and solve thisproblem.
1 def funcC(C):
2 vo = 476.0 # ft^3 / hr
3 V = 238.0 # ft^3
4 Po = 35.0 # atm
5 T = 1500.0 # Rankine
6 R = 0.73 # in appropriate units
7 CTo = Po / R / T
8
9 Cmo = CTo / 3.0
10 Cho = CTo * 2.0 / 3.0
11 Cxo = 0.0
12 Cmeo = 0.0
13 Ctolo = 0.0
93
-
7/29/2019 06 625 Course Notes
94/97
14
15 tau = V / vo
16
17 CM, CH, CX, CMe, CT = C
18
19 # rate laws
20 k1 = 55.20 # (ft^3/lbmol)^0.5/h
21 k2 = 30.20 # (ft^3/lbmol)^0.5/h
22 r1m = -k1 * CM * CH**0.5
23 r2t = k2 * CX * CH**0.5
24
25 # net rates
26 rM = r1m
27 rH = r1m - r2t28 rX = -r1m - r2t
29 rMe = -r1m + r2t
30 rT = r2t
31
32 return [tau * (-rM) - Cmo + CM,
33 tau * (-rH) - Cho + CH,
34 tau * (-rX) - Cxo + CX,
35 tau * (-rMe) - Cmeo + CMe,
36 tau * (-rT) - Ctolo + CT]
37
38 initGuesses = [0.002, 0.002, 0.002, 0.002, 0.002]
39 from scipy.optimize import fsolve
40
41 exitC = fsolve(funcC, initGuesses)
42
43 species = [M, H, X, Me, T]
44 for s,C in zip(species, exitC):
45 print {0:^3s}{1:1.5f} lbmol/ft^3.format(s,C)
M 0.00294 lbmol/ft^3
H 0.00905 lbmol/ft^3X 0.00317 lbmol/ft^3
Me 0.01226 lbmol/ft^3
T 0.00455 lbmol/ft^3
94
-
7/29/2019 06 625 Course Notes
95/97
3.5.2 Multiple reactions in a PFR
Now we solve the same problem in a PFR.
1 import numpy as np
2 from scipy.integrate import odeint
3
4 vo = 476.0 # ft^3 / hr
5 Po = 35.0 # atm
6 T = 1500.0 # Rankine
7 R = 0.73 # in appropriate units
8 CTo = Po / R / T
9 Fto = CTo * vo10
11 # initial molar flows
12 Fmo = Fto / 3.0
13 Fho = Fto * 2.0 / 3.0
14 Fxo = 0.0
15 Fmeo = 0.0
16 Ftolo = 0.0
17
18 def dFdV(F, t):
19 PFR moe balances
20
Ft = F.sum()21
22 v = vo * Ft / Fto
23 C = F / v
24 CM, CH, CX, CMe, CT = C
25
26 # rate laws
27 k1 = 55.20
28 k2 = 30.20
29 r1m = -k1 * CM * CH**0.5
30 r2t = k2 * CX * CH**0.5
31
32 # net rates33 rM = r1m
34 rH = r1m - r2t
35 rX = -r1m - r2t
36 rMe = -r1m + r2t
95
-
7/29/2019 06 625 Course Notes
96/97
37 rT = r2t
38
39 dFMdV = rM
40 dFHdV = rH
41 dFXdV = rX
42 dFMedV = rMe
43 dFTdV = rT
44
45 return [ dFMdV, dFHdV, dFXdV, dFMedV, dFTdV ]
46
47 Finit = [Fmo, Fho, Fxo, Fmeo, Ftolo]
48 Vspan = np.linspace(0.0, 238.0)
49
50 sol = odeint(dFdV, Finit, Vspan)51
52 Ft = sol.sum(axis=1) # sum each row
53 v = vo * Ft / Fto
54
55 FM = sol[:,0]
56 FH = sol[:,1]
57 FX = sol[:,2]
58 FMe = sol[:,3]
59 FT = sol[:,4]
60
61 tau = Vspan / vo
62
63 import matplotlib.pyplot as plt
64 plt.plot(tau, FM / v, label=$C_M$)
65 plt.plot(tau, FH / v, label=$C_H$)
66 plt.plot(tau, FX / v, label=$C_X$)
67
68 plt.legend(loc=best)
69 plt.xlabel($\\tau$ (hr))
70 plt.ylabel(Concentration (lbmol/ft$^3$))
71 plt.savefig(images/multiple-reactions-pfr.png)
72 plt.show()
96
-
7/29/2019 06 625 Course Notes
97/97
You can see that the basic approach is the same asfor a single reaction
the code is just a lot longer
In this example it was not necessary to computethe total molar flow. Inspection shows that it is aconstant. Hence, the volumetric flow is also con-stant.