06 625 Course Notes

download 06 625 Course Notes

of 97

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.