├── LICENSE
├── README.md
└── tutorial
├── julia
├── Tutorial 0 - Introduction to Julia
│ ├── J0_IntroToJulia.ipynb
│ ├── doc_page_pic.png
│ └── issues_pic.png
├── Tutorial 1 - Linear Algebra
│ ├── J1_LAsystems.ipynb
│ ├── J1_LAsystems_solved.ipynb
│ └── Monochlorobenzene.png
├── Tutorial 2 - Nonlinear Algebraic Systems
│ ├── .ipynb_checkpoints
│ │ └── J2_NLAsystems_solved-checkpoint.ipynb
│ ├── CSTR_ChloroBenzene.png
│ ├── J2_NLAsystems.ipynb
│ └── J2_NLAsystems_solved.ipynb
├── Tutorial 3 - ODEs, Initial Value Problems
│ ├── CSTR_pic.png
│ └── J3_ODEIVPsystems.ipynb
├── Tutorial 4 - ODEs, Boundary Value Problems
│ ├── .ipynb_checkpoints
│ │ └── J4_ODEBVPsystems_solved-checkpoint.ipynb
│ ├── J4_ODEBVPsystems.ipynb
│ ├── J4_ODEBVPsystems_solved.ipynb
│ ├── pipe1.png
│ └── pipe2.png
├── Tutorial 5 - Partial Differential Equations
│ ├── .ipynb_checkpoints
│ │ ├── Initial Value PDEs-checkpoint.ipynb
│ │ └── J5_PDEsystems-checkpoint.ipynb
│ ├── J5_PDEsystems.ipynb
│ └── pyrometer_pde_pic.png
└── Tutorial 6 - Optimization
│ ├── J6_Optimization.ipynb
│ ├── J6_Optimization_solved.ipynb
│ ├── SpecTable1.png
│ ├── Treatment_Train.png
│ └── optimization_full.ipynb
└── matlab
├── Tutorial 0 - Intro to Matlab
└── M0_IntroToMatlab.mlx
├── Tutorial 1 - Linear Algebra
├── M1_LAsystems.mlx
└── M1_LAsystems_solved.mlx
├── Tutorial 2 - Nonlinear Algebraic Systems
├── M2_NLAsystems.mlx
└── M2_NLAsystems_solved.mlx
├── Tutorial 3 - ODEs, Initial Value Problems
└── M3_ODEIVPsystems.mlx
├── Tutorial 4 - ODEs, Boundary Value Problems
├── M4_ODEBVPsystems.mlx
└── M4_ODEBVPsystems_solved.mlx
├── Tutorial 5 - Partial Differential Equations
├── M5_PDEsystems.mlx
└── M5_PDEsystems_solved.mlx
└── Tutorial 6 - Optimization
├── M6_Optimization.mlx
└── M6_Optimization_solved.mlx
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Matthew Wilhelm, Chenyu Wang, Matthew Stuber
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Chemical Engineering - Analysis Notebooks
2 | Literate Programming Examples for Chemical Engineering Analysis
3 |
4 | ## Authors
5 | Matthew Wilhelm, Chenyu Wang, Matthew Stuber
6 |
7 | ## Overview of Content
8 |
9 | This repository contains a series of supplemental notebooks intended for use
10 | in courses centered around application of numerical methods with a chemical engineering
11 | context. This content was originally developed for the junior level, Chemical
12 | Engineering Analysis course at the University of Connecticut through funding provided by a Mini Grant from the Center for Education, Teaching, and Learning (CETL) and from the CACHE Organization.
13 |
14 | This original course made use of Dorfman and Daoutidis's "Numerical Methods with Chemical Engineering Applications" as a primary textbook. A list of the chapters of Dorfman and Daoutidis that correspond to each tutorial is provided below. We should, however, note that the enclosed material was designed to be used in a stand-alone fashion and may be readily integrated into other courses.
15 |
16 | - Tutorial \#0 (Introduction to Matlab/Julia) - N/A
17 | - Tutorial \#1 (Linear Algebraic Systems) - Chapter 2
18 | - Tutorial \#2 (Nonlinear Algebraic Systems) - Chapter 3
19 | - Tutorial \#3 (ODEs, Initial Value Problems) - Chapter 4
20 | - Tutorial \#4 (ODEs, Boundary Value Problems) - Chapter 6
21 | - Tutorial \#5 (Partial Differential Equations) - Chapter 7
22 | - Tutorial \#6 (Optimization) - N/A
23 |
24 | ## References
25 | - Dorfman, Kevin D., and Prodromos Daoutidis. Numerical Methods with Chemical Engineering Applications. Cambridge University Press, 2017.
26 |
--------------------------------------------------------------------------------
/tutorial/julia/Tutorial 0 - Introduction to Julia/doc_page_pic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/julia/Tutorial 0 - Introduction to Julia/doc_page_pic.png
--------------------------------------------------------------------------------
/tutorial/julia/Tutorial 0 - Introduction to Julia/issues_pic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/julia/Tutorial 0 - Introduction to Julia/issues_pic.png
--------------------------------------------------------------------------------
/tutorial/julia/Tutorial 1 - Linear Algebra/J1_LAsystems.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Linear Algebraic Systems \n",
8 | "\n",
9 | "
\n",
10 | "\n",
11 | "# Learning Objectives \n",
12 | "\n",
13 | "### *Setting up a linear system*\n",
14 | "- Be able to distinguish a linear system from a nonlinear system.\n",
15 | "- Learn to write a linear system in a matrix-vector format.\n",
16 | "- Input a linear system into a program\n",
17 | "- Write applicable engineering models in a linear format.\n",
18 | "\n",
19 | "### *Characterizing linear systems*\n",
20 | "- Determine the bandwidth(s) of the linear system.*\n",
21 | "- Determine if a linear system has a unique solution.\n",
22 | "- Will solving a linear system provide a meaningful result (is it well-posed).\n",
23 | "\n",
24 | "### *Solving a linear system*\n",
25 | "- Solve a linear system by Gauss-Elimination\n",
26 | "- Solve a linear system by LU factorizations.\n",
27 | "- State when numerical errors occur may occur in Gauss-Elimination and how to resolve them.\\*\n",
28 | "- Be able to state some conditions for the convergence of Jacobi and Gauss-Siedel methods.\\*\n",
29 | "\n",
30 | "\\*Not covered in tutorial.\n",
31 | "\n",
32 | "
"
33 | ]
34 | },
35 | {
36 | "cell_type": "markdown",
37 | "metadata": {},
38 | "source": [
39 | "# Relevance \n",
40 | "\n",
41 | "It would be only a slight overstatement to say that linear algebra underlies all modern numerical algorithms to one degree or another. Even software which specifically addresses nonlinear or complicated forms will often make use of linear algebra in a myriad of subroutines. In essence, many complicated problems can be reduced to repeated formulating and solving linear systems. \n",
42 | "\n",
43 | "
"
44 | ]
45 | },
46 | {
47 | "cell_type": "markdown",
48 | "metadata": {},
49 | "source": [
50 | "# Chlorination of Benzene \n",
51 | "\n",
52 | "Monochlorobenzene is a high boiling point solvent used in many industrial applications ranging from the manufacture of [dyes](https://patentimages.storage.googleapis.com/76/55/12/31756682f755c5/US6013811.pdf), [plastic and rubber processing](https://patentimages.storage.googleapis.com/66/c8/e1/69918f557fbd35/US8563079.pdf), [paints](https://patentimages.storage.googleapis.com/28/17/a7/a40e46f75806cf/US9062206.pdf), and [waxes](https://patents.google.com/patent/US3999960A/en). Approximately, [$1 billion](https://www.prnewswire.com/news-releases/global-chlorobenzene-industry-300939916.html) worth of this solvent are used per year. It is [produced by combining chlorine with benzene](https://onlinelibrary.wiley.com/doi/abs/10.1002/14356007.o06_o03) in the presence of a Lewis acid catalyst such as Ferric Chloride FeCl3 or Anhydrous Aluminum Chloride AlCl3. When benzene and chlorine are mixed two key reactions occur: (1) the chlorination of benzene and (2) the secondary undesirable chlorination of the resulting monochlorobenzene. Product purity is ensured by performing a series of separation steps after the initial reaction and reagant usage is reduced by including a recycle loop. \n",
53 | "\n",
54 | "\n",
55 | "Objective: A large customer order has been placed for monochlorobenzene that needs to be fulfilled on a tight timeline. The small chemical company you work for has occasionally produced this solvent in the past and has an existing 6m3 continuously stirred tank reactor (CSTR) that will need to be repurposed to produce 25kmol/h monochlorobenzene. Moreover, there isn't sufficient development time available to attempt to improve the processing conditions for the reactor or separators. As a result, you'll need to use the documented process conditions for the reactor and separators which means supplying to the first separator an effluent consisting of 90% benzene, 7% monochlorobenzene, and 3% dichlorobenzene by mole. We need to determine the recycle flow rates and reaction rates required needed to achieve the desired rate at which monochlorobenzene need to be produced. \n",
56 | "
\n",
57 | "\n",
58 | "Denote benzene as (A), monochlorobenzene as (B), and dichlorobenzene as (C). The mole fraction of species A in stream S1 is denote by y1,A and the molar flowrate F [kmol/h] for stream S1 is denoted F1.\n",
59 | "\n",
60 | "- The feed stream is composed entirely of A and Cl2.\n",
61 | "- An excess amount of Cl2 is provided and HCl removal can be assumed to be trivial. So it's reasonable exclude it from our design considerations. Moreover, this means the reactions which take place in the reactor vessel can be simplified to A → B (at rate r1 [kmol/h]) and B → C (at rate r2 [kmol/h]).\n",
62 | "\n",
63 | "
\n",
64 | "\n",
65 | "# Variables and Mass Balances \n",
66 | "\n",
67 | "For streams containing only a pure substance, we need only keep track of the total flow rate. Enumerating each quantity in the model is given by $F_1$, $F_2$, $F_3$, $F_4$, $F_5$, $F_6$, $F_7$, $y_{3,A}$, $y_{3,B}$, $y_{3,C}$, $y_{4,B}$, $y_{4,C}$, $r_1$, $r_2$, and $\\nu$. Five of these are specified (e.g. these are **parameters** with fixed values). This leaves ten degrees of freedom associated with a variable. We'll now need to write out the equations which govern the system (physical laws, equipment specifications, and performance specifications). We'll start with writing the mass balances over each unit operation. \n",
68 | "\n",
69 | "Mixer (Overall Mass Balance): \n",
70 | "\n",
71 | "\\begin{align}\n",
72 | " F_1 + F_7 - F_2 &= 0\n",
73 | "\\end{align}\n",
74 | "\n",
75 | "Reactor (Individual Component Mass Balances):\n",
76 | "\n",
77 | "\\begin{align}\n",
78 | " F_2 - r_1\\nu - y_{3,A}F_3 &= 0 \\\\\n",
79 | " (r_1 - r_2)\\nu + y_{3,B}F_3 &= 0 \\\\\n",
80 | " r_2\\nu - y_{3,C}F_3 &= 0\n",
81 | "\\end{align}\n",
82 | "\n",
83 | "Separator #1 (Individual Component Mass Balances):\n",
84 | "\n",
85 | "\\begin{align}\n",
86 | " F_3 - F_4 - F_7 &= 0 \\\\\n",
87 | " y_{3,B}F_3 - y_{4,B}F_4 &= 0 \\\\\n",
88 | " y_{3,C}F_3 - y_{4,C}F_4 &= 0\n",
89 | "\\end{align}\n",
90 | "\n",
91 | "Separator #2 (Individual Component Mass Balances):\n",
92 | "\n",
93 | "\\begin{align}\n",
94 | " F_4 - F_5 - F_6 &= 0 \\\\\n",
95 | " y_{4,B}F_4 - F_5 &= 0\n",
96 | "\\end{align}\n",
97 | "\n",
98 | "In order to write these equations in a matrix-vector form: we'll first need to associate each variable (excluding **parameters**) in the model with a component in a vector of variables, $\\mathbf{x}$. \n",
99 | "\n",
100 | "\\begin{align}\n",
101 | " \\mathbf{x} = (F_1, F_2, F_3, F_4, F_6, F_7, y_{4,B}, y_{4,C}, r_1, r_2)\n",
102 | "\\end{align}\n",
103 | "\n",
104 | "So, $x_2 = F_2$, $x_3 = F_3$ and so on until we have $x_{10} = r_2$. \n",
105 | "\n",
106 | "\n",
107 | "Now, take a minute to inspect the model and categorize the system of equations. \n",
108 | "
\n",
109 | "\n",
110 | "The 2nd and 3rd equations for Separator #1 as well as the 2nd equation in Separator #2 contain the expressions $y_{4,B}F_4$ and $y_{4,C}F_4$. These terms consist of two variables being multiplied by one another (a bilinear term) which is one of simplest commonly encountered nonlinear term. All other expressions consist of addition, subtraction, and the multiplication of a parameter by a variable. So this system of equations is nonlinear but would be linear if we ommitted the expressions: $y_{4,B}F_4$ and $y_{4,C}F_4$.\n",
111 | "\n",
112 | "\n",
113 | "It's often useful to reformulate a model (algebraicly rearrange and potentially introduce new variables) to arrive at a more easily solvable form. \n",
114 | "
\n",
115 | "\n",
116 | "We'll start by introducing two new variables: the molar flowrates $F_{4,B}$ and $F_{4,C}$ defined as by\n",
117 | "\n",
118 | "\\begin{align}\n",
119 | " F_{4,B} = y_{4,B}F_4 \\\\\n",
120 | " F_{4,C} = y_{4,C}F_4\n",
121 | "\\end{align}\n",
122 | "\n",
123 | "We can then write an additional equation for the molar flowrates of S4:\n",
124 | "\n",
125 | "Molar flowrate expressions for S4:\n",
126 | "\n",
127 | "\\begin{align}\n",
128 | " F_{4,B} + F_{4,C} &= F_4\n",
129 | "\\end{align}\n",
130 | "\n",
131 | "Next, we'll introduce the variables: $F_{3,A}$, $F_{3,B}$, and $F_{3,C}$ defined as:\n",
132 | "\n",
133 | "\\begin{align}\n",
134 | " F_{3,A} = y_{3,A}F_3 \\\\\n",
135 | " F_{3,B} = y_{3,B}F_3 \\\\\n",
136 | " F_{3,C} = y_{3,C}F_3\n",
137 | "\\end{align}\n",
138 | "\n",
139 | "We can then write an additional equation for the molar flowrates of S3:\n",
140 | "\n",
141 | "Molar flowrate expressions for S3:\n",
142 | "\n",
143 | "\\begin{align}\n",
144 | "F_{3,A} + F_{3,B} + F_{3,C} = F_3\n",
145 | "\\end{align}\n",
146 | "\n",
147 | "We can then rearrange the mass balances for the Reactor, Separator #1, and Separator #2.\n",
148 | "\n",
149 | "Reactor (Individual Component Mass Balances):\n",
150 | "\n",
151 | "\\begin{align}\n",
152 | " F_2 - r_1\\nu - F_{3,A} &= 0 \\\\\n",
153 | " (r_1 - r_2)\\nu + F_{3,B} &= 0 \\\\\n",
154 | " r_2\\nu - F_{3,C} &= 0\n",
155 | "\\end{align}\n",
156 | "\n",
157 | "Reformulated Separator #1 Mass Balances:\n",
158 | "\n",
159 | "\\begin{align}\n",
160 | " F_3 - F_4 - F_7 &= 0 \\\\\n",
161 | " F_{3,B} - F_{4,B} &= 0 \\\\\n",
162 | " F_{3,C} - F_{4,C} &= 0\n",
163 | "\\end{align}\n",
164 | "\n",
165 | "Reformulated Separator #2 Mass Balances:\n",
166 | "\n",
167 | "\\begin{align}\n",
168 | " F_4 - F_5 - F_6 &= 0 \\\\\n",
169 | " F_{4,B} - F_5 &= 0\n",
170 | "\\end{align}\n",
171 | "\n",
172 | "\\begin{align}\n",
173 | " \\mathbf{x'} = (F_1, F_2, F_{3,A}, F_{3,B}, F_{3,C}, F_3, F_{4,B}, F_{4,C}, F_4, F_6, F_7, r_1, r_2)\n",
174 | "\\end{align}\n"
175 | ]
176 | },
177 | {
178 | "cell_type": "markdown",
179 | "metadata": {},
180 | "source": [
181 | "\n",
182 | "INTERACTIVE! Now we enter the above linear equation, Mx' = b in matrix-vector form.\n",
183 | "
"
184 | ]
185 | },
186 | {
187 | "cell_type": "code",
188 | "execution_count": null,
189 | "metadata": {},
190 | "outputs": [],
191 | "source": [
192 | "# Define the Mx' = b linear system\n",
193 | "\n",
194 | "\n",
195 | "# Define storage for linear system\n",
196 | "M = zeros(13,13) # Makes an 2d array of size 13-by-13. This array has 13 rows and 13 columns.\n",
197 | "b = zeros(13) # Makes a column vector of size 10. A vector with 13 rows and 1 column.\n",
198 | "\n",
199 | "# Input parameters\n",
200 | "y3A = 0.90 # effluent benzene concentration of reactor\n",
201 | "y3B = 0.07 # effluent monochlorobenzene concentration of reactor\n",
202 | "y3C = 0.03 # effluent dichlorobenzene concentration of reactor\n",
203 | "V = 6 # reactor volume\n",
204 | "F_5 = 25.0 # flow rate of monochlorobenzene required\n",
205 | "\n",
206 | "### MASS BALANCE (MIXER) ###\n",
207 | "\n",
208 | "# fills in the first row\n",
209 | "M[1,1] = 1.0 # Set the matrix M's entry in the first row and first column to 1\n",
210 | "M[1,2] = -1.0 # Set the matrix M's entry in the first row and second column to -1\n",
211 | "M[1,11] = 1.0 # Set the matrix M's entry in the first row and eighth column to 1\n",
212 | "\n",
213 | "# b[1] and all other entries of M in row #1, so no need to change these values\n",
214 | "\n",
215 | "M[2,2] = 1.0 # reactor - equation #1 \n",
216 | "M[2,3] = -1.0\n",
217 | "M[2,12] = -V\n",
218 | "\n",
219 | "M[3,4] = 1.0 # reactor - equation #2 \n",
220 | "M[3,12] = -V\n",
221 | "M[3,13] = V\n",
222 | "\n",
223 | "M[4,5] = -1.0 # reactor - equation #3 \n",
224 | "M[4,13] = V\n",
225 | "\n",
226 | "M[10,8] = 1.0 # stream 4 balance \n",
227 | "M[10,9] = -1.0\n",
228 | "M[10,7] = 1.0\n",
229 | "\n",
230 | "M[11,3] = 1.0 # stream 3 balance #1 \n",
231 | "M[11,4] = 1.0\n",
232 | "M[11,5] = 1.0\n",
233 | "M[11,6] = -1.0\n",
234 | "\n",
235 | "M[12,3] = 1.0 # stream 3 balance #2\n",
236 | "M[12,6] = -y3A\n",
237 | "\n",
238 | "M[13,4] = 1.0 # stream 4 balance #3\n",
239 | "M[13,6] = -y3B\n",
240 | "\n",
241 | "### SEPARATOR #1 & 2 - MASS BALANCES ###\n",
242 | "\n",
243 | "# FILL IN THE REST BELOW HERE IN ROWS 5 to 9 of M!!!!!!!!"
244 | ]
245 | },
246 | {
247 | "cell_type": "code",
248 | "execution_count": null,
249 | "metadata": {},
250 | "outputs": [],
251 | "source": [
252 | "# Run this to display the input matrix and solution vector\n",
253 | "display(\"This Matrix (M) is \") \n",
254 | "display(M)\n",
255 | "display(\"The r.h.s vector (b) is \") \n",
256 | "display(b)\n",
257 | "x = M\\b # solve the system\n",
258 | "display(x)"
259 | ]
260 | },
261 | {
262 | "cell_type": "markdown",
263 | "metadata": {},
264 | "source": [
265 | "# Existence and Uniqueness of the Solution \n",
266 | "\n",
267 | "Now that the linear system has been formulated, let's check to see whether the system has a solution. For a square matrix M, M system is invertible\n",
268 | "\n",
269 | "\n",
270 | " INTERACTIVE! Make use of the function det to check see if matrix M is invertible. Does the linear equation Mx = b have a unique solution? \n",
271 | "
"
272 | ]
273 | },
274 | {
275 | "cell_type": "code",
276 | "execution_count": null,
277 | "metadata": {},
278 | "outputs": [],
279 | "source": [
280 | "using LinearAlgebra: det\n",
281 | "\n",
282 | "# FILL IN THE REST BELOW HERE\n"
283 | ]
284 | },
285 | {
286 | "cell_type": "markdown",
287 | "metadata": {},
288 | "source": [
289 | "# Well-posed problems and the Condition Number \n",
290 | "\n",
291 | "While a linear system, `Mx = b`, may have a unique solution, we also seek to understand whether using a particular algorithm to solve `Mx = b` will yeild an accurate solution. We often don't know `M` or `b` exactly. As a consequence, solutions which vary greatly when `M` or `b` are slightly perturbed may be suspect. This is generally assessed by evaluating the **condition number** of a linear system and is defined as: $\\text{cond}(M) \\equiv ||M|| \\times ||M^{-1}||$. A derivation of this is given in section 2.3.4 of Dorfman and Daoutidis, Numerical Methods with Chemical Engineering Applications which arrives at the following inequality:\n",
292 | "\n",
293 | "$||\\Delta x || \\leq \\text{cond}(M)\\bigg(\\frac{|| \\Delta b || || x ||}{|| b ||}\\bigg)$\n",
294 | "\n",
295 | "For a fixed condition number, $\\text{cond}(M)$, solution $x$, and $b$ if we slightly perturb $b$ by $\\Delta b$ then $x$ may change by at most an amount proportional to the condition number. Another way of interpreting this is by applying the following rule of thumb: the condition number $\\kappa$ means that the method looses $\\log_{10}{\\kappa}$ of accuracy relative to rounding error.\n",
296 | "\n",
297 | "If an application leads to an ill-posed problem (values of **M** and **b** may be known to very high precision), there are a multitude of ways this may be dealt with. This most common and often most robust approach is to apply a [**preconditioner**](http://www.mathcs.emory.edu/~benzi/Web_papers/survey.pdf). That is we find an appropriate matrix **Y** and multiply both sides of the original linear system to create an equivalent new linear system, `(YM)x = (Yb)`, which has a lower condition number. We can then solve the equivalent modified system. "
298 | ]
299 | },
300 | {
301 | "cell_type": "markdown",
302 | "metadata": {},
303 | "source": [
304 | "\n",
305 | "INTERACTIVE! Make use of the function cond to compute the condition number using the $L^2$-norm. Is the matrix A well-conditioned? \n",
306 | "
"
307 | ]
308 | },
309 | {
310 | "cell_type": "code",
311 | "execution_count": null,
312 | "metadata": {},
313 | "outputs": [],
314 | "source": [
315 | "using LinearAlgebra: cond\n",
316 | "\n",
317 | "# FILL IN THE REST BELOW HERE"
318 | ]
319 | },
320 | {
321 | "cell_type": "markdown",
322 | "metadata": {},
323 | "source": [
324 | "# Solving the linear equation with Gauss Elimination "
325 | ]
326 | },
327 | {
328 | "cell_type": "markdown",
329 | "metadata": {},
330 | "source": [
331 | "Now let's write a quick script to solve Mx = b. We'll do this in three steps. Defining a function to perform forward elimination, a function to back substitution, and lastly a main function to perform each of the prior functions in sequence."
332 | ]
333 | },
334 | {
335 | "cell_type": "code",
336 | "execution_count": null,
337 | "metadata": {},
338 | "outputs": [],
339 | "source": [
340 | "function forward_elimination!(M,b,n)\n",
341 | " \n",
342 | " for k = 1:(n - 1)\n",
343 | " Mmax = M[k,k]\n",
344 | " swap_row = k\n",
345 | " for i = k+1:n\n",
346 | " if abs(M[i,k])>abs(Mmax)\n",
347 | " Mmax = M[i,k]\n",
348 | " swap_row = i\n",
349 | " end\n",
350 | " end\n",
351 | " if swap_row != k\n",
352 | " old_pivot = copy(M[k,:])\n",
353 | " old_b = b[k]\n",
354 | " M[k,:] = copy(M[swap_row,:])\n",
355 | " M[swap_row,:] = old_pivot\n",
356 | " b[k] = b[swap_row]\n",
357 | " b[swap_row] = old_b\n",
358 | " end\n",
359 | " for i = (k + 1):n\n",
360 | " m = M[i,k]/M[k,k]\n",
361 | " for j = (k + 1):n\n",
362 | " M[i,j] = M[i,j] - m*M[k,j]\n",
363 | " end\n",
364 | " b[i] = b[i] - m*b[k]\n",
365 | " end\n",
366 | " end\n",
367 | " \n",
368 | " return nothing\n",
369 | "end"
370 | ]
371 | },
372 | {
373 | "cell_type": "markdown",
374 | "metadata": {},
375 | "source": [
376 | "\n",
377 | "Comment: Note that while M, b are used in prior cells these variables don't interfere in the definition of forward_elimination!. The variables listed in the argument of forward_elimination!, that is (M,x,b,n), are evaluated in local scope when forward_elimination! is called.\n",
378 | "
"
379 | ]
380 | },
381 | {
382 | "cell_type": "markdown",
383 | "metadata": {},
384 | "source": [
385 | "\n",
386 | "INTERACTIVE! The overall gauss elimination function is defined below. You just need to finish coding the back_substitution! function.\n",
387 | "
"
388 | ]
389 | },
390 | {
391 | "cell_type": "code",
392 | "execution_count": null,
393 | "metadata": {},
394 | "outputs": [],
395 | "source": [
396 | "function back_substitution!(M,x,b,n)\n",
397 | " \n",
398 | " # FILL IN THE REST BELOW HERE\n",
399 | " \n",
400 | " return nothing\n",
401 | "end"
402 | ]
403 | },
404 | {
405 | "cell_type": "code",
406 | "execution_count": null,
407 | "metadata": {},
408 | "outputs": [],
409 | "source": [
410 | "function Gauss_Elimination!(M, b)\n",
411 | " n = length(b)\n",
412 | " x = zeros(n)\n",
413 | " \n",
414 | " forward_elimination!(M,b,n)\n",
415 | " back_substitution!(M,x,b,n) \n",
416 | " \n",
417 | " return x\n",
418 | "end\n",
419 | "\n",
420 | "# Runs a Gauss elimination routine using Mx = b has inputs\n",
421 | "# We create copies to avoid overwriting the original arrays\n",
422 | "Mc = copy(M)\n",
423 | "bc = copy(b)\n",
424 | "x = Gauss_Elimination!(Mc, bc)\n",
425 | "\n",
426 | "display(\"The solution via Gauss elimination is \"); \n",
427 | "display(x)"
428 | ]
429 | },
430 | {
431 | "cell_type": "markdown",
432 | "metadata": {},
433 | "source": [
434 | "# Solving with an LU factorization \n",
435 | "\n",
436 | "\n",
437 | "INTERACTIVE! Run the following snippet of code to solve the linear system using LU factorization to solve the linear system.\n",
438 | "
"
439 | ]
440 | },
441 | {
442 | "cell_type": "code",
443 | "execution_count": null,
444 | "metadata": {},
445 | "outputs": [],
446 | "source": [
447 | "using LinearAlgebra: lu\n",
448 | "\n",
449 | "lu_fact = lu(M) # performs an LU factorization of M. Note that pivot = Val(false)\n",
450 | " # means that the LU factorization is performed without pivoting\n",
451 | " # (otherwise a permutation matrix describing the pivots is computed as well)\n",
452 | "\n",
453 | "y = lu_fact.L\\b # solve Ly = b for y\n",
454 | "x = lu_fact.U\\y # solve Ux = y for x\n",
455 | "\n",
456 | "display(\"The solution via LU factorization is \"); \n",
457 | "display(x)"
458 | ]
459 | },
460 | {
461 | "cell_type": "markdown",
462 | "metadata": {},
463 | "source": [
464 | "
\n",
465 | "\n",
466 | "# Questions for reflection \n",
467 | "\n",
468 | "- What varieties of problems may result in a banded matrix?\n",
469 | "- What does it mean that an iterative method converged? \n",
470 | "- When is an absolute or relative convergence criteria preferable?\n",
471 | "- When is solving by one method versus another preferable (e.g. Banded Gauss-Elimination versus Gauss-Siedel)?"
472 | ]
473 | }
474 | ],
475 | "metadata": {
476 | "kernelspec": {
477 | "display_name": "Julia 1.5.1",
478 | "language": "julia",
479 | "name": "julia-1.5"
480 | },
481 | "language_info": {
482 | "file_extension": ".jl",
483 | "mimetype": "application/julia",
484 | "name": "julia",
485 | "version": "1.5.1"
486 | }
487 | },
488 | "nbformat": 4,
489 | "nbformat_minor": 4
490 | }
491 |
--------------------------------------------------------------------------------
/tutorial/julia/Tutorial 1 - Linear Algebra/J1_LAsystems_solved.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Linear Algebraic Systems \n",
8 | "\n",
9 | "
\n",
10 | "\n",
11 | "# Learning Objectives \n",
12 | "\n",
13 | "### *Setting up a linear system*\n",
14 | "- Be able to distinguish a linear system from a nonlinear system.\n",
15 | "- Learn to write a linear system in a matrix-vector format.\n",
16 | "- Input a linear system into a program\n",
17 | "- Write applicable engineering models in a linear format.\n",
18 | "\n",
19 | "### *Characterizing linear systems*\n",
20 | "- Determine the bandwidth(s) of the linear system.*\n",
21 | "- Determine if a linear system has a unique solution.\n",
22 | "- Will solving a linear system provide a meaningful result (is it well-posed).\n",
23 | "\n",
24 | "### *Solving a linear system*\n",
25 | "- Solve a linear system by Gauss-Elimination\n",
26 | "- Solve a linear system by LU factorizations.\n",
27 | "- State when numerical errors occur may occur in Gauss-Elimination and how to resolve them.\\*\n",
28 | "- Be able to state some conditions for the convergence of Jacobi and Gauss-Siedel methods.\\*\n",
29 | "\n",
30 | "\\*Not covered in tutorial.\n",
31 | "\n",
32 | "
"
33 | ]
34 | },
35 | {
36 | "cell_type": "markdown",
37 | "metadata": {},
38 | "source": [
39 | "# Relevance \n",
40 | "\n",
41 | "It would be only a slight overstatement to say that linear algebra underlies all modern numerical algorithms to one degree or another. Even software which specifically addresses nonlinear or complicated forms will often make use of linear algebra in a myriad of subroutines. In essence, many complicated problems can be reduced to repeated formulating and solving linear systems. \n",
42 | "\n",
43 | "
"
44 | ]
45 | },
46 | {
47 | "cell_type": "markdown",
48 | "metadata": {},
49 | "source": [
50 | "# Chlorination of Benzene \n",
51 | "\n",
52 | "Monochlorobenzene is a high boiling point solvent used in many industrial applications ranging from the manufacture of [dyes](https://patentimages.storage.googleapis.com/76/55/12/31756682f755c5/US6013811.pdf), [plastic and rubber processing](https://patentimages.storage.googleapis.com/66/c8/e1/69918f557fbd35/US8563079.pdf), [paints](https://patentimages.storage.googleapis.com/28/17/a7/a40e46f75806cf/US9062206.pdf), and [waxes](https://patents.google.com/patent/US3999960A/en). Approximately, [$1 billion](https://www.prnewswire.com/news-releases/global-chlorobenzene-industry-300939916.html) worth of this solvent are used per year. It is [produced by combining chlorine with benzene](https://onlinelibrary.wiley.com/doi/abs/10.1002/14356007.o06_o03) in the presence of a Lewis acid catalyst such as Ferric Chloride FeCl3 or Anhydrous Aluminum Chloride AlCl3. When benzene and chlorine are mixed two key reactions occur: (1) the chlorination of benzene and (2) the secondary undesirable chlorination of the resulting monochlorobenzene. Product purity is ensured by performing a series of separation steps after the initial reaction and reagant usage is reduced by including a recycle loop. \n",
53 | "\n",
54 | "\n",
55 | "Objective: A large customer order has been placed for monochlorobenzene that needs to be fulfilled on a tight timeline. The small chemical company you work for has occasionally produced this solvent in the past and has an existing 6m3 continuously stirred tank reactor (CSTR) that will need to be repurposed to produce 25kmol/h monochlorobenzene. Moreover, there isn't sufficient development time available to attempt to improve the processing conditions for the reactor or separators. As a result, you'll need to use the documented process conditions for the reactor and separators which means supplying to the first separator an effluent consisting of 90% benzene, 7% monochlorobenzene, and 3% dichlorobenzene by mole. We need to determine the recycle flow rates and reaction rates required needed to achieve the desired rate at which monochlorobenzene need to be produced. \n",
56 | "
\n",
57 | "\n",
58 | "Denote benzene as (A), monochlorobenzene as (B), and dichlorobenzene as (C). The mole fraction of species A in stream S1 is denote by y1,A and the molar flowrate F [kmol/h] for stream S1 is denoted F1.\n",
59 | "\n",
60 | "- The feed stream is composed entirely of A and Cl2.\n",
61 | "- An excess amount of Cl2 is provided and HCl removal can be assumed to be trivial. So it's reasonable exclude it from our design considerations. Moreover, this means the reactions which take place in the reactor vessel can be simplified to A → B (at rate r1 [kmol/h]) and B → C (at rate r2 [kmol/h]).\n",
62 | "\n",
63 | "
\n",
64 | "\n",
65 | "# Variables and Mass Balances \n",
66 | "\n",
67 | "For streams containing only a pure substance, we need only keep track of the total flow rate. Enumerating each quantity in the model is given by $F_1$, $F_2$, $F_3$, $F_4$, $F_5$, $F_6$, $F_7$, $y_{3,A}$, $y_{3,B}$, $y_{3,C}$, $y_{4,B}$, $y_{4,C}$, $r_1$, $r_2$, and $\\nu$. Five of these are specified (e.g. these are **parameters** with fixed values). This leaves ten degrees of freedom associated with a variable. We'll now need to write out the equations which govern the system (physical laws, equipment specifications, and performance specifications). We'll start with writing the mass balances over each unit operation. \n",
68 | "\n",
69 | "Mixer (Overall Mass Balance): \n",
70 | "\n",
71 | "\\begin{align}\n",
72 | " F_1 + F_7 - F_2 &= 0\n",
73 | "\\end{align}\n",
74 | "\n",
75 | "Reactor (Individual Component Mass Balances):\n",
76 | "\n",
77 | "\\begin{align}\n",
78 | " F_2 - r_1\\nu - y_{3,A}F_3 &= 0 \\\\\n",
79 | " (r_1 - r_2)\\nu + y_{3,B}F_3 &= 0 \\\\\n",
80 | " r_2\\nu - y_{3,C}F_3 &= 0\n",
81 | "\\end{align}\n",
82 | "\n",
83 | "Separator #1 (Individual Component Mass Balances):\n",
84 | "\n",
85 | "\\begin{align}\n",
86 | " F_3 - F_4 - F_7 &= 0 \\\\\n",
87 | " y_{3,B}F_3 - y_{4,B}F_4 &= 0 \\\\\n",
88 | " y_{3,C}F_3 - y_{4,C}F_4 &= 0\n",
89 | "\\end{align}\n",
90 | "\n",
91 | "Separator #2 (Individual Component Mass Balances):\n",
92 | "\n",
93 | "\\begin{align}\n",
94 | " F_4 - F_5 - F_6 &= 0 \\\\\n",
95 | " y_{4,B}F_4 - F_5 &= 0\n",
96 | "\\end{align}\n",
97 | "\n",
98 | "In order to write these equations in a matrix-vector form: we'll first need to associate each variable (excluding **parameters**) in the model with a component in a vector of variables, $\\mathbf{x}$. \n",
99 | "\n",
100 | "\\begin{align}\n",
101 | " \\mathbf{x} = (F_1, F_2, F_3, F_4, F_6, F_7, y_{4,B}, y_{4,C}, r_1, r_2)\n",
102 | "\\end{align}\n",
103 | "\n",
104 | "So, $x_2 = F_2$, $x_3 = F_3$ and so on until we have $x_{10} = r_2$. \n",
105 | "\n",
106 | "\n",
107 | "Now, take a minute to inspect the model and categorize the system of equations. \n",
108 | "
\n",
109 | "\n",
110 | "The 2nd and 3rd equations for Separator #1 as well as the 2nd equation in Separator #2 contain the expressions $y_{4,B}F_4$ and $y_{4,C}F_4$. These terms consist of two variables being multiplied by one another (a bilinear term) which is one of simplest commonly encountered nonlinear term. All other expressions consist of addition, subtraction, and the multiplication of a parameter by a variable. So this system of equations is nonlinear but would be linear if we ommitted the expressions: $y_{4,B}F_4$ and $y_{4,C}F_4$.\n",
111 | "\n",
112 | "\n",
113 | "It's often useful to reformulate a model (algebraicly rearrange and potentially introduce new variables) to arrive at a more easily solvable form. \n",
114 | "
\n",
115 | "\n",
116 | "We'll start by introducing two new variables: the molar flowrates $F_{4,B}$ and $F_{4,C}$ defined as by\n",
117 | "\n",
118 | "\\begin{align}\n",
119 | " F_{4,B} = y_{4,B}F_4 \\\\\n",
120 | " F_{4,C} = y_{4,C}F_4\n",
121 | "\\end{align}\n",
122 | "\n",
123 | "We can then write an additional equation for the molar flowrates of S4:\n",
124 | "\n",
125 | "Molar flowrate expressions for S4:\n",
126 | "\n",
127 | "\\begin{align}\n",
128 | " F_{4,B} + F_{4,C} &= F_4\n",
129 | "\\end{align}\n",
130 | "\n",
131 | "Next, we'll introduce the variables: $F_{3,A}$, $F_{3,B}$, and $F_{3,C}$ defined as:\n",
132 | "\n",
133 | "\\begin{align}\n",
134 | " F_{3,A} = y_{3,A}F_3 \\\\\n",
135 | " F_{3,B} = y_{3,B}F_3 \\\\\n",
136 | " F_{3,C} = y_{3,C}F_3\n",
137 | "\\end{align}\n",
138 | "\n",
139 | "We can then write an additional equation for the molar flowrates of S3:\n",
140 | "\n",
141 | "Molar flowrate expressions for S3:\n",
142 | "\n",
143 | "\\begin{align}\n",
144 | "F_{3,A} + F_{3,B} + F_{3,C} = F_3\n",
145 | "\\end{align}\n",
146 | "\n",
147 | "We can then rearrange the mass balances for the Reactor, Separator #1, and Separator #2.\n",
148 | "\n",
149 | "Reactor (Individual Component Mass Balances):\n",
150 | "\n",
151 | "\\begin{align}\n",
152 | " F_2 - r_1\\nu - F_{3,A} &= 0 \\\\\n",
153 | " (r_1 - r_2)\\nu + F_{3,B} &= 0 \\\\\n",
154 | " r_2\\nu - F_{3,C} &= 0\n",
155 | "\\end{align}\n",
156 | "\n",
157 | "Reformulated Separator #1 Mass Balances:\n",
158 | "\n",
159 | "\\begin{align}\n",
160 | " F_3 - F_4 - F_7 &= 0 \\\\\n",
161 | " F_{3,B} - F_{4,B} &= 0 \\\\\n",
162 | " F_{3,C} - F_{4,C} &= 0\n",
163 | "\\end{align}\n",
164 | "\n",
165 | "Reformulated Separator #2 Mass Balances:\n",
166 | "\n",
167 | "\\begin{align}\n",
168 | " F_4 - F_5 - F_6 &= 0 \\\\\n",
169 | " F_{4,B} - F_5 &= 0\n",
170 | "\\end{align}\n",
171 | "\n",
172 | "\\begin{align}\n",
173 | " \\mathbf{x'} = (F_1, F_2, F_{3,A}, F_{3,B}, F_{3,C}, F_3, F_{4,B}, F_{4,C}, F_4, F_6, F_7, r_1, r_2)\n",
174 | "\\end{align}\n"
175 | ]
176 | },
177 | {
178 | "cell_type": "markdown",
179 | "metadata": {},
180 | "source": [
181 | "\n",
182 | "INTERACTIVE! Now we enter the above linear equation, Mx' = b in matrix-vector form.\n",
183 | "
"
184 | ]
185 | },
186 | {
187 | "cell_type": "code",
188 | "execution_count": 1,
189 | "metadata": {},
190 | "outputs": [
191 | {
192 | "data": {
193 | "text/plain": [
194 | "-0.07"
195 | ]
196 | },
197 | "metadata": {},
198 | "output_type": "display_data"
199 | }
200 | ],
201 | "source": [
202 | "# Define the Mx' = b linear system\n",
203 | "\n",
204 | "\n",
205 | "# Define storage for linear system\n",
206 | "M = zeros(13,13) # Makes an 2d array of size 13-by-13. This array has 13 rows and 13 columns.\n",
207 | "b = zeros(13) # Makes a column vector of size 10. A vector with 13 rows and 1 column.\n",
208 | "\n",
209 | "# Input parameters\n",
210 | "y3A = 0.90 # effluent benzene concentration of reactor\n",
211 | "y3B = 0.07 # effluent monochlorobenzene concentration of reactor\n",
212 | "y3C = 0.03 # effluent dichlorobenzene concentration of reactor\n",
213 | "V = 6 # reactor volume\n",
214 | "F_5 = 25.0 # flow rate of monochlorobenzene required\n",
215 | "\n",
216 | "### MASS BALANCE (MIXER) ###\n",
217 | "\n",
218 | "# fills in the first row\n",
219 | "M[1,1] = 1.0 # Set the matrix M's entry in the first row and second column to 1\n",
220 | "M[1,2] = -1.0 # Set the matrix M's entry in the first row and second column to -1\n",
221 | "M[1,11] = 1.0 # Set the matrix M's entry in the first row and eighth column to 1\n",
222 | "\n",
223 | "# b[1] and all other entries of M in row #1, so no need to change these values\n",
224 | "\n",
225 | "### REACTOR MASS BALANCES ###\n",
226 | "\n",
227 | "# reactor - equation #1\n",
228 | "M[2,2] = 1.0 \n",
229 | "M[2,3] = -1.0\n",
230 | "M[2,12] = -V\n",
231 | "\n",
232 | "# reactor - equation #2\n",
233 | "M[3,4] = 1.0 \n",
234 | "M[3,12] = -V\n",
235 | "M[3,13] = V\n",
236 | "\n",
237 | "# reactor - equation #3\n",
238 | "M[4,5] = -1.0 \n",
239 | "M[4,13] = V\n",
240 | "\n",
241 | "### SEPARATOR #1 - MASS BALANCES ###\n",
242 | "\n",
243 | "# separator #1 - equation #1\n",
244 | "M[5,6] = 1.0\n",
245 | "M[5,9] = -1.0\n",
246 | "M[5,11] = -1.0\n",
247 | "\n",
248 | "# separator #1 - equation #2\n",
249 | "M[6,4] = 1.0\n",
250 | "M[6,7] = -1.0\n",
251 | "\n",
252 | "# separator #1 - equation #3\n",
253 | "M[7,5] = 1.0\n",
254 | "M[7,8] = -1.0\n",
255 | "\n",
256 | "### SEPARATOR #2 - MASS BALANCES ###\n",
257 | "\n",
258 | "# equation #1\n",
259 | "M[8,9] = 1.0 \n",
260 | "M[8,10] = -1.0 \n",
261 | "b[8] = F_5\n",
262 | "\n",
263 | "# equation #2\n",
264 | "M[9,8] = 1.0 \n",
265 | "b[9] = F_5\n",
266 | "\n",
267 | "### MOLAR FLOWRATE EXPRESSIONs ###\n",
268 | "M[10,8] = 1.0 \n",
269 | "M[10,9] = -1.0\n",
270 | "M[10,7] = 1.0\n",
271 | "\n",
272 | "M[11,3] = 1.0 \n",
273 | "M[11,4] = 1.0\n",
274 | "M[11,5] = 1.0\n",
275 | "M[11,6] = -1.0\n",
276 | "\n",
277 | "M[12,3] = 1.0\n",
278 | "M[12,6] = -y3A\n",
279 | "\n",
280 | "M[13,4] = 1.0\n",
281 | "M[13,6] = -y3B\n"
282 | ]
283 | },
284 | {
285 | "cell_type": "code",
286 | "execution_count": 2,
287 | "metadata": {},
288 | "outputs": [
289 | {
290 | "data": {
291 | "text/plain": [
292 | "\"This Matrix (M) is \""
293 | ]
294 | },
295 | "metadata": {},
296 | "output_type": "display_data"
297 | },
298 | {
299 | "data": {
300 | "text/plain": [
301 | "13×13 Matrix{Float64}:\n",
302 | " 1.0 -1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0\n",
303 | " 0.0 1.0 -1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -6.0 0.0\n",
304 | " 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -6.0 6.0\n",
305 | " 0.0 0.0 0.0 0.0 -1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 6.0\n",
306 | " 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 -1.0 0.0 -1.0 0.0 0.0\n",
307 | " 0.0 0.0 0.0 1.0 0.0 0.0 -1.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
308 | " 0.0 0.0 0.0 0.0 1.0 0.0 0.0 -1.0 0.0 0.0 0.0 0.0 0.0\n",
309 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 -1.0 0.0 0.0 0.0\n",
310 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0\n",
311 | " 0.0 0.0 0.0 0.0 0.0 0.0 1.0 1.0 -1.0 0.0 0.0 0.0 0.0\n",
312 | " 0.0 0.0 1.0 1.0 1.0 -1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
313 | " 0.0 0.0 1.0 0.0 0.0 -0.9 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
314 | " 0.0 0.0 0.0 1.0 0.0 -0.07 0.0 0.0 0.0 0.0 0.0 0.0 0.0"
315 | ]
316 | },
317 | "metadata": {},
318 | "output_type": "display_data"
319 | },
320 | {
321 | "data": {
322 | "text/plain": [
323 | "\"The r.h.s vector (b) is \""
324 | ]
325 | },
326 | "metadata": {},
327 | "output_type": "display_data"
328 | },
329 | {
330 | "data": {
331 | "text/plain": [
332 | "13-element Vector{Float64}:\n",
333 | " 0.0\n",
334 | " 0.0\n",
335 | " 0.0\n",
336 | " 0.0\n",
337 | " 0.0\n",
338 | " 0.0\n",
339 | " 0.0\n",
340 | " 25.0\n",
341 | " 25.0\n",
342 | " 0.0\n",
343 | " 0.0\n",
344 | " 0.0\n",
345 | " 0.0"
346 | ]
347 | },
348 | "metadata": {},
349 | "output_type": "display_data"
350 | },
351 | {
352 | "data": {
353 | "text/plain": [
354 | "13-element Vector{Float64}:\n",
355 | " 83.33333333333337\n",
356 | " 833.3333333333339\n",
357 | " 750.0000000000006\n",
358 | " 58.33333333333337\n",
359 | " 25.0\n",
360 | " 833.3333333333339\n",
361 | " 58.33333333333337\n",
362 | " 25.0\n",
363 | " 83.33333333333337\n",
364 | " 58.33333333333337\n",
365 | " 750.0000000000006\n",
366 | " 13.888888888888896\n",
367 | " 4.166666666666667"
368 | ]
369 | },
370 | "metadata": {},
371 | "output_type": "display_data"
372 | }
373 | ],
374 | "source": [
375 | "# Run this to display the input matrix and the solution vector\n",
376 | "display(\"This Matrix (M) is \") \n",
377 | "display(M)\n",
378 | "display(\"The r.h.s vector (b) is \") \n",
379 | "display(b)\n",
380 | "x = M\\b\n",
381 | "display(x)"
382 | ]
383 | },
384 | {
385 | "cell_type": "markdown",
386 | "metadata": {},
387 | "source": [
388 | "# Existence and Uniqueness of the Solution \n",
389 | "\n",
390 | "Now that the linear system has been formulated, let's check to see whether the system has a solution. For a square matrix M, M system is invertible\n",
391 | "\n",
392 | "\n",
393 | " INTERACTIVE! Make use of the function det to check see if matrix M is invertible. Does the linear equation Mx = b have a unique solution? \n",
394 | "
"
395 | ]
396 | },
397 | {
398 | "cell_type": "code",
399 | "execution_count": 3,
400 | "metadata": {},
401 | "outputs": [
402 | {
403 | "data": {
404 | "text/plain": [
405 | "-1.0799999999999992"
406 | ]
407 | },
408 | "metadata": {},
409 | "output_type": "display_data"
410 | }
411 | ],
412 | "source": [
413 | "using LinearAlgebra: det\n",
414 | "\n",
415 | "# FILL IN THE REST BELOW HERE\n",
416 | "det(M)"
417 | ]
418 | },
419 | {
420 | "cell_type": "markdown",
421 | "metadata": {},
422 | "source": [
423 | "# Well-posed problems and the Condition Number \n",
424 | "\n",
425 | "While a linear system, `Mx = b`, may have a unique solution, we also seek to understand whether using a particular algorithm to solve `Mx = b` will yeild an accurate solution. We often don't know `M` or `b` exactly. As a consequence, solutions which vary greatly when `M` or `b` are slightly perturbed may be suspect. This is generally assessed by evaluating the **condition number** of a linear system and is defined as: $\\text{cond}(M) \\equiv ||M|| \\times ||M^{-1}||$. A derivation of this is given in section 2.3.4 of Dorfman and Daoutidis, Numerical Methods with Chemical Engineering Applications which arrives at the following inequality:\n",
426 | "\n",
427 | "$||\\Delta x || \\leq \\text{cond}(M)\\bigg(\\frac{|| \\Delta b || || x ||}{|| b ||}\\bigg)$\n",
428 | "\n",
429 | "For a fixed condition number, $\\text{cond}(M)$, solution $x$, and $b$ if we slightly perturb $b$ by $\\Delta b$ then $x$ may change by at most an amount proportional to the condition number. Another way of interpreting this is by applying the following rule of thumb: the condition number $\\kappa$ means that the method looses $\\log_{10}{\\kappa}$ of accuracy relative to rounding error.\n",
430 | "\n",
431 | "If an application leads to an ill-posed problem (values of **M** and **b** may be known to very high precision), there are a multitude of ways this may be dealt with. This most common and often most robust approach is to apply a [**preconditioner**](http://www.mathcs.emory.edu/~benzi/Web_papers/survey.pdf). That is we find an appropriate matrix **Y** and multiply both sides of the original linear system to create an equivalent new linear system, `(YM)x = (Yb)`, which has a lower condition number. We can then solve the equivalent modified system. "
432 | ]
433 | },
434 | {
435 | "cell_type": "markdown",
436 | "metadata": {},
437 | "source": [
438 | "\n",
439 | "INTERACTIVE! Make use of the function cond to compute the condition number using the $L^2$-norm. Is the matrix A well-conditioned? \n",
440 | "
"
441 | ]
442 | },
443 | {
444 | "cell_type": "code",
445 | "execution_count": 4,
446 | "metadata": {},
447 | "outputs": [
448 | {
449 | "data": {
450 | "text/plain": [
451 | "1494.7023893087862"
452 | ]
453 | },
454 | "metadata": {},
455 | "output_type": "display_data"
456 | }
457 | ],
458 | "source": [
459 | "using LinearAlgebra: cond\n",
460 | "\n",
461 | "# FILL IN THE REST BELOW HERE\n",
462 | "cond(M)"
463 | ]
464 | },
465 | {
466 | "cell_type": "markdown",
467 | "metadata": {},
468 | "source": [
469 | "# Solving the linear equation with Gauss Elimination "
470 | ]
471 | },
472 | {
473 | "cell_type": "markdown",
474 | "metadata": {},
475 | "source": [
476 | "Now let's write a quick script to solve Mx = b. We'll do this in three steps. Defining a function to perform forward elimination, a function to back substitution, and lastly a main function to perform each of the prior functions in sequence."
477 | ]
478 | },
479 | {
480 | "cell_type": "code",
481 | "execution_count": 5,
482 | "metadata": {},
483 | "outputs": [
484 | {
485 | "data": {
486 | "text/plain": [
487 | "forward_elimination! (generic function with 1 method)"
488 | ]
489 | },
490 | "metadata": {},
491 | "output_type": "display_data"
492 | }
493 | ],
494 | "source": [
495 | "function forward_elimination!(M,b,n)\n",
496 | " \n",
497 | " for k = 1:(n - 1)\n",
498 | " Mmax = M[k,k]\n",
499 | " swap_row = k\n",
500 | " for i = k+1:n\n",
501 | " if abs(M[i,k])>abs(Mmax)\n",
502 | " Mmax = M[i,k]\n",
503 | " swap_row = i\n",
504 | " end\n",
505 | " end\n",
506 | " if swap_row != k\n",
507 | " old_pivot = copy(M[k,:])\n",
508 | " old_b = b[k]\n",
509 | " M[k,:] = copy(M[swap_row,:])\n",
510 | " M[swap_row,:] = old_pivot\n",
511 | " b[k] = b[swap_row]\n",
512 | " b[swap_row] = old_b\n",
513 | " end\n",
514 | " for i = (k + 1):n\n",
515 | " m = M[i,k]/M[k,k]\n",
516 | " for j = (k + 1):n\n",
517 | " M[i,j] = M[i,j] - m*M[k,j]\n",
518 | " end\n",
519 | " b[i] = b[i] - m*b[k]\n",
520 | " end\n",
521 | " end\n",
522 | " \n",
523 | " return nothing\n",
524 | "end"
525 | ]
526 | },
527 | {
528 | "cell_type": "markdown",
529 | "metadata": {},
530 | "source": [
531 | "\n",
532 | "Comment: Note that while M, b are used in prior cells these variables don't interfere in the definition of forward_elimination!. The variables listed in the argument of forward_elimination!, that is (M,x,b,n), are evaluated in local scope when forward_elimination! is called.\n",
533 | "
"
534 | ]
535 | },
536 | {
537 | "cell_type": "markdown",
538 | "metadata": {},
539 | "source": [
540 | "\n",
541 | "INTERACTIVE! The overall gauss elimination function is defined below. You just need to finish coding the back_substitution! function.\n",
542 | "
"
543 | ]
544 | },
545 | {
546 | "cell_type": "code",
547 | "execution_count": 6,
548 | "metadata": {},
549 | "outputs": [
550 | {
551 | "data": {
552 | "text/plain": [
553 | "back_substitution! (generic function with 1 method)"
554 | ]
555 | },
556 | "metadata": {},
557 | "output_type": "display_data"
558 | }
559 | ],
560 | "source": [
561 | "function back_substitution!(M,x,b,n)\n",
562 | " \n",
563 | " # FILL IN THE REST BELOW HERE\n",
564 | " x[n] = b[n]/M[n,n]\n",
565 | " for i = n-1:-1:1\n",
566 | " S = b[i]\n",
567 | " for j = i+1:n\n",
568 | " S = S-M[i,j]*x[j]\n",
569 | " end\n",
570 | " x[i] = S/M[i,i]\n",
571 | " end\n",
572 | " return nothing\n",
573 | "end"
574 | ]
575 | },
576 | {
577 | "cell_type": "code",
578 | "execution_count": 7,
579 | "metadata": {},
580 | "outputs": [
581 | {
582 | "data": {
583 | "text/plain": [
584 | "\"The solution via Gauss elimination is \""
585 | ]
586 | },
587 | "metadata": {},
588 | "output_type": "display_data"
589 | },
590 | {
591 | "data": {
592 | "text/plain": [
593 | "13-element Vector{Float64}:\n",
594 | " 83.33333333333337\n",
595 | " 833.3333333333339\n",
596 | " 750.0000000000006\n",
597 | " 58.33333333333337\n",
598 | " 25.0\n",
599 | " 833.3333333333339\n",
600 | " 58.33333333333337\n",
601 | " 25.0\n",
602 | " 83.33333333333337\n",
603 | " 58.33333333333337\n",
604 | " 750.0000000000006\n",
605 | " 13.888888888888896\n",
606 | " 4.166666666666667"
607 | ]
608 | },
609 | "metadata": {},
610 | "output_type": "display_data"
611 | }
612 | ],
613 | "source": [
614 | "function Gauss_Elimination!(M, b)\n",
615 | " n = length(b)\n",
616 | " x = zeros(n)\n",
617 | " \n",
618 | " forward_elimination!(M,b,n)\n",
619 | " back_substitution!(M,x,b,n) \n",
620 | " \n",
621 | " return x\n",
622 | "end\n",
623 | "\n",
624 | "# Runs a Gauss elimination routine using Mx = b has inputs\n",
625 | "# We create copies to avoid overwriting the original arrays\n",
626 | "Mc = copy(M)\n",
627 | "bc = copy(b)\n",
628 | "x = Gauss_Elimination!(Mc, bc)\n",
629 | "\n",
630 | "display(\"The solution via Gauss elimination is \"); \n",
631 | "display(x)"
632 | ]
633 | },
634 | {
635 | "cell_type": "markdown",
636 | "metadata": {},
637 | "source": [
638 | "# Solving with an LU factorization \n",
639 | "\n",
640 | "\n",
641 | "INTERACTIVE! Run the following snippet of code to solve the linear system using LU factorization to solve the linear system.\n",
642 | "
"
643 | ]
644 | },
645 | {
646 | "cell_type": "code",
647 | "execution_count": 8,
648 | "metadata": {},
649 | "outputs": [
650 | {
651 | "data": {
652 | "text/plain": [
653 | "\"The solution via LU factorization is \""
654 | ]
655 | },
656 | "metadata": {},
657 | "output_type": "display_data"
658 | },
659 | {
660 | "data": {
661 | "text/plain": [
662 | "13-element Vector{Float64}:\n",
663 | " 83.33333333333337\n",
664 | " 833.3333333333339\n",
665 | " 750.0000000000006\n",
666 | " 58.33333333333337\n",
667 | " 25.0\n",
668 | " 833.3333333333339\n",
669 | " 58.33333333333337\n",
670 | " 25.0\n",
671 | " 83.33333333333337\n",
672 | " 58.33333333333337\n",
673 | " 750.0000000000006\n",
674 | " 13.888888888888896\n",
675 | " 4.166666666666667"
676 | ]
677 | },
678 | "metadata": {},
679 | "output_type": "display_data"
680 | }
681 | ],
682 | "source": [
683 | "using LinearAlgebra: lu\n",
684 | "\n",
685 | "lu_fact = lu(M) # performs an LU factorization of M. Note that pivot = Val(false)\n",
686 | " # means that the LU factorization is performed without pivoting\n",
687 | " # (otherwise a permutation matrix describing the pivots is computed as well)\n",
688 | "\n",
689 | "y = lu_fact.L\\b # solve Ly = b for y\n",
690 | "x = lu_fact.U\\y # solve Ux = y for x\n",
691 | "\n",
692 | "display(\"The solution via LU factorization is \"); \n",
693 | "display(x)"
694 | ]
695 | },
696 | {
697 | "cell_type": "markdown",
698 | "metadata": {},
699 | "source": [
700 | "
\n",
701 | "\n",
702 | "# Questions for reflection \n",
703 | "\n",
704 | "- What varieties of problems may result in a banded matrix?\n",
705 | "- What does it mean that an iterative method converged? \n",
706 | "- When is an absolute or relative convergence criteria preferable?\n",
707 | "- When is solving by one method versus another preferable (e.g. Banded Gauss-Elimination versus Gauss-Siedel)?"
708 | ]
709 | }
710 | ],
711 | "metadata": {
712 | "kernelspec": {
713 | "display_name": "Julia 1.7.2",
714 | "language": "julia",
715 | "name": "julia-1.7"
716 | },
717 | "language_info": {
718 | "file_extension": ".jl",
719 | "mimetype": "application/julia",
720 | "name": "julia",
721 | "version": "1.7.2"
722 | }
723 | },
724 | "nbformat": 4,
725 | "nbformat_minor": 4
726 | }
727 |
--------------------------------------------------------------------------------
/tutorial/julia/Tutorial 1 - Linear Algebra/Monochlorobenzene.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/julia/Tutorial 1 - Linear Algebra/Monochlorobenzene.png
--------------------------------------------------------------------------------
/tutorial/julia/Tutorial 2 - Nonlinear Algebraic Systems/.ipynb_checkpoints/J2_NLAsystems_solved-checkpoint.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Linear Algebraic Systems \n",
8 | "\n",
9 | "
\n",
10 | "\n",
11 | "# Learning Objectives \n",
12 | "\n",
13 | "By the end of this module, students will be able to\n",
14 | "\n",
15 | "### *Solve single nonlinear equations*\n",
16 | "- Solve a single nonlinear equation by Picard’s method.\n",
17 | "- Solve a single nonlinear equation by Newton’s method.\n",
18 | "- Determine whether solving a nonlinear equation by fixed-point method will converge.\\*\n",
19 | "- State the rate of convergence.\\*\n",
20 | "\n",
21 | "### *Solve systems of nonlinear equations*\n",
22 | "- Formulate residual equations.\n",
23 | "- Input a residual of a system of nonlinear equations in a program.\n",
24 | "- Solve a system of nonlinear equations by Newton-Raphson.\n",
25 | "\n",
26 | "### *Solve parametric nonlinear systems by continuation methods*\n",
27 | "- Solve a parametric nonlinear system using zeroth-order continuation for initial guesses.\\*\n",
28 | "\n",
29 | "\\*Not covered in tutorial.\n",
30 | "\n",
31 | "
"
32 | ]
33 | },
34 | {
35 | "cell_type": "markdown",
36 | "metadata": {},
37 | "source": [
38 | "# Solving Single Nonlinear Algebra Equations \n",
39 | "\n",
40 | "In this section, we will explore Picard’s method and Newton’s method, which are two traditional methods to systematically solve a single nonlinear algebra equation $f(x)=0$.\n",
41 | "\n",
42 | "### Picard's Method\n",
43 | "Picard’s method, which is also known as the method of successive substitution, involves simply adding $x$ to both sides of $f(x)=0$. The iterative scheme is\n",
44 | "\n",
45 | "\\\\[x^{\\left(k+1\\right)} =f\\left(x^{\\left(k\\right)} \\right)+x^{\\left(k\\right)}.\\\\]\n",
46 | "\n",
47 | "It is very easy to implement, but it may be very slow to converge.\n",
48 | "\n",
49 | "**Example 1**: Use Picard's method to find the root of function $f(x)=e^{-x}-x$ using initial guess $x^{(0)}=1$. \n",
50 | "\n",
51 | "We begin by defining a callback functions (f_ex1) to evaluate $f(x)$."
52 | ]
53 | },
54 | {
55 | "cell_type": "code",
56 | "execution_count": 2,
57 | "metadata": {},
58 | "outputs": [
59 | {
60 | "data": {
61 | "text/plain": [
62 | "f_ex1 (generic function with 1 method)"
63 | ]
64 | },
65 | "execution_count": 2,
66 | "metadata": {},
67 | "output_type": "execute_result"
68 | }
69 | ],
70 | "source": [
71 | "f_ex1(x) = exp(-x) - x"
72 | ]
73 | },
74 | {
75 | "cell_type": "markdown",
76 | "metadata": {},
77 | "source": [
78 | "We now define a function to perform Picard's method and run it for this problem."
79 | ]
80 | },
81 | {
82 | "cell_type": "code",
83 | "execution_count": 3,
84 | "metadata": {},
85 | "outputs": [
86 | {
87 | "name": "stdout",
88 | "output_type": "stream",
89 | "text": [
90 | "The solution is x = 0.5671477142601192\n"
91 | ]
92 | }
93 | ],
94 | "source": [
95 | "\"\"\"\n",
96 | "nonlinear_picard\n",
97 | "\n",
98 | "Solves for `f(x) = 0` via Picard iteration where `f` is the residual, `x` is an initial guess, \n",
99 | "`kmax` is the maximum number of iterations, `tol` is the absolute convergence tolerance. This returns\n",
100 | "a tuple which holds solution `x` and a boolean value `flag` which is true if the algorithm successfully\n",
101 | "converged and false otherwise.\n",
102 | "\"\"\"\n",
103 | "function nonlinear_picard(f,x,kmax,tol)\n",
104 | " f_val = f(x)\n",
105 | " k = 0 \n",
106 | " while abs(f_val) > tol\n",
107 | " x += f_val # Update x\n",
108 | " k += 1 # Update iteration counter\n",
109 | " f_val = f(x) # Compute function at next value\n",
110 | " if (k > kmax)\n",
111 | " println(\"Maximum iterations exceeded.\")\n",
112 | " break\n",
113 | " end\n",
114 | " end\n",
115 | " flag = (k > kmax || x == Inf || x == -Inf) ? false : true\n",
116 | " return x, flag\n",
117 | "end\n",
118 | "\n",
119 | "x0 = 1.0 # initial guess\n",
120 | "kmax = 20 # max number of iterations allowed\n",
121 | "tol = 1e-5 # convergence tolerance\n",
122 | "x,flag = nonlinear_picard(f_ex1,x0,kmax,tol)\n",
123 | "println(\"The solution is x = $(x)\")"
124 | ]
125 | },
126 | {
127 | "cell_type": "markdown",
128 | "metadata": {},
129 | "source": [
130 | "### Newton's Method\n",
131 | "\n",
132 | "Newton’s method is the most common method for solving a single nonlinear equation. The iterative scheme is\n",
133 | "\n",
134 | "\\\\[x^{(k+1)}=x^{(k)}- \\frac{f(x^{(k)})}{f'(x^{(k)})}.\\\\]\n",
135 | "\n",
136 | "As indicated by the scheme, the derivative $f'(x)$ is required for iterations.\n",
137 | "\n",
138 | "In order to solve **Example 1** using Newton's method, we should derive the derivative term $f'(x)=-e^{-x}-1$."
139 | ]
140 | },
141 | {
142 | "cell_type": "code",
143 | "execution_count": 4,
144 | "metadata": {},
145 | "outputs": [
146 | {
147 | "data": {
148 | "text/plain": [
149 | "df_ex1 (generic function with 1 method)"
150 | ]
151 | },
152 | "execution_count": 4,
153 | "metadata": {},
154 | "output_type": "execute_result"
155 | }
156 | ],
157 | "source": [
158 | "df_ex1(x) = -exp(-x) - 1"
159 | ]
160 | },
161 | {
162 | "cell_type": "markdown",
163 | "metadata": {},
164 | "source": [
165 | "We now define a function to perform Newton's method and run it for this problem."
166 | ]
167 | },
168 | {
169 | "cell_type": "code",
170 | "execution_count": 18,
171 | "metadata": {},
172 | "outputs": [
173 | {
174 | "name": "stdout",
175 | "output_type": "stream",
176 | "text": [
177 | "The solution is x = 0.567143285989123\n"
178 | ]
179 | }
180 | ],
181 | "source": [
182 | "\"\"\"\n",
183 | "nonlinear_newton\n",
184 | "\n",
185 | "Solves for `f(x) = 0` via Newton's method where `f` is the residual, `df` is the derivative of the residual,\n",
186 | "`x` is an initial guess, `kmax` is the maximum number of iterations, and `tol` is the absolute convergence tolerance.\n",
187 | "This returns a tuple which holds solution `x` and a boolean value `flag` which is true if the algorithm successfully\n",
188 | "converged and false otherwise.\n",
189 | "\"\"\"\n",
190 | "function nonlinear_newton(f,df,x,kmax,tol)\n",
191 | " fval = f(x)\n",
192 | " k = 0\n",
193 | " while abs(fval) > tol\n",
194 | " dfval = df(x)\n",
195 | " x -= fval/dfval\n",
196 | " k += 1\n",
197 | " fval = f(x)\n",
198 | " if k > kmax\n",
199 | " println(\"Maximum iterations exceeded.\")\n",
200 | " break\n",
201 | " end\n",
202 | " end\n",
203 | " flag = (k > kmax || x == Inf || x == -Inf) ? false : true\n",
204 | " return x, flag\n",
205 | "end\n",
206 | "\n",
207 | "x0 = 1.0 # initial guess\n",
208 | "kmax = 20 # max number of iterations allowed\n",
209 | "tol = 1e-5 # convergence tolerance\n",
210 | "x,flag = nonlinear_newton(f_ex1,df_ex1,x0,kmax,tol)\n",
211 | "println(\"The solution is x = $(x)\")"
212 | ]
213 | },
214 | {
215 | "cell_type": "markdown",
216 | "metadata": {},
217 | "source": [
218 | "Netwon's method exhibits quadratic convergence, that is a huge advantage, as the convergence accelerates rapidly near the solution."
219 | ]
220 | },
221 | {
222 | "cell_type": "markdown",
223 | "metadata": {},
224 | "source": [
225 | "\n",
226 | "INTERACTIVE! Run the above snippet of code while varying the initial guess to see how the solution responds. Does this always yield the same value? If so, why? For extreme guesses are any errors encountered? Take a moment and try to reflect on condition(s) under which these may occur.\n",
227 | "
"
228 | ]
229 | },
230 | {
231 | "cell_type": "markdown",
232 | "metadata": {},
233 | "source": [
234 | "# Solving Systems of Nonlinear Algebra Equations \n",
235 | "\n",
236 | "In this section, we introduce the numerical methods to solve systems of nonlinear algebra equations. The general form is given by:\n",
237 | "\n",
238 | "\\\\[R_1(x_1,x_2,\\ldots,x_n)=0\\\\\n",
239 | "R_2(x_1,x_2,\\ldots,x_n)=0\\\\\n",
240 | "\\ \\ \\ \\ \\ \\ \\ \\vdots\\\\\n",
241 | "R_n(x_1,x_2,\\ldots,x_n)=0\\\\]\n",
242 | "\n",
243 | "We need to be careful to write the equations in a form such that they are equal to zero in order to apply the fixed-point methods. The compact notation of the system of nonlinear equations is $\\bf R(\\bf x)=\\bf0$, with $\\bf x$ being the vector of unknowns as $\\mathbf x = (x_1,x_2,\\ldots,x_n)$, and \n",
244 | "\n",
245 | "\\\\[\\mathbf{R}=\\left\\lbrack \\begin{array}{c}\n",
246 | "R_1 \\left(x_1 ,x_2 ,\\dots ,x_n \\right)\\\\\n",
247 | "R_2 \\left(x_1 ,x_2 ,\\dots ,x_n \\right)\\\\\n",
248 | "\\vdots \\\\\n",
249 | "R_n \\left(x_1 ,x_2 ,\\dots ,x_n \\right)\n",
250 | "\\end{array}\\right\\rbrack\\\\]\n",
251 | "\n",
252 | "a vector-valued function which we will refer to as the **residual**.\n",
253 | "\n",
254 | "### Newton-Raphson\n",
255 | "A common method for solving nonlinear systems of equations is the Newton–Raphson method. It relies on the same idea as Newton’s method but is now generalized to n dimensions. The iterative scheme is given by\n",
256 | "\n",
257 | "\\\\[\\mathbf{J}(\\mathbf{x}^{(k)})(\\mathbf{x}^{(k+1)}-\\mathbf{x}^{(k)})=-\\mathbf{R}(\\mathbf{x}^{(k)}),\\\\]\n",
258 | "\n",
259 | "where the $\\mathbf J$ is the Jacobian matrix defined as\n",
260 | "\n",
261 | "\\\\[\\mathbf{J}=\\left\\lbrack \\begin{array}{ccc}\n",
262 | "\\frac{\\partial R_1 }{\\partial x_1 } & \\dots & \\frac{\\partial R_1 }{\\partial x_n }\\\\\n",
263 | "\\vdots & \\ddots & \\vdots \\\\\n",
264 | "\\frac{\\partial R_n }{\\partial x_1 } & \\dots & \\frac{\\partial R_n }{\\partial x_n }\n",
265 | "\\end{array}\\right\\rbrack,\\\\]\n",
266 | "\n",
267 | "that is to be evaluated at the value $\\mathbf{x}^{(k)}$. In component form, the elements of the Jacobian are the partial derivatives $J_{ij}=\\frac{\\partial R_i}{\\partial x_j}$.\n",
268 | "It is common to use a shorthand notation $\\mathbf{\\delta}^{(k+1)}=\\mathbf{x}^{(k+1)}-\\mathbf{x}^{(k)}$ for the difference between the previous values and the new values of $\\mathbf x$. The iterative scheme then becomes the linear system\n",
269 | "\n",
270 | "\\\\[\\mathbf{J}(\\mathbf{x}^{(k)})(\\mathbf{\\delta}^{(k+1)})=-\\mathbf{R}(\\mathbf{x}^{(k)}),\\\\]\n",
271 | "\n",
272 | "\\\\[\\mathbf x^{(k+1)}:=\\mathbf x^{(k)}+\\mathbf{\\delta}^{(k+1)}.\\\\]\n",
273 | "\n",
274 | "We have reduced solving the system of nonlinear equations into an iterative method where, at each step, we need to solve a system of linear equations. Since $\\mathbf{x}^{(k)}$ changes at each time step, we need to solve the linear system for different right-hand-side constant vectors and Jacobian matrices.\n",
275 | "\n",
276 | "The implementation of this method goes through the following steps.\n",
277 | "\n",
278 | "1. Create a vector with the residuals.\n",
279 | "2. Compute the elements in the Jacobian. \n",
280 | "3. Pick an initial guess $\\mathbf{x}^{(0)}$.\n",
281 | "4.Iterate until either $\\| \\mathbf R \\|$ and/or $\\|\\mathbf \\delta\\|$ are small.\n",
282 | "\n",
283 | "**Example 2:** Use Netwon-Raphson to find the root of the system of equations\n",
284 | "\n",
285 | "\\\\[f_1(x_1,x_2)=e^{-x_1}-x_2, \\\\\n",
286 | "f_2(x_1,x_2)=x_1+x_2^2-3x_2, \\\\]\n",
287 | "\n",
288 | "using the initial guess $x_1^{(0)}=0$ and $x_2^{(0)}=0$.\n",
289 | "\n",
290 | "**Solution:**\n",
291 | "The residual vector for this problem is\n",
292 | "\n",
293 | "\\\\[\\mathbf{R}=\\left\\lbrack \\begin{array}{c}\n",
294 | "e^{-x_1 } -x_2 \\\\\n",
295 | "x_1 +x_2^2 -3x_2 \n",
296 | "\\end{array}\\right\\rbrack\\\\]\n",
297 | "\n",
298 | "If we take the logical choice for the vector of unknowns to be $\\mathbf x =(x_1,x_2)$, then the Jacobian is\n",
299 | "\n",
300 | "\\\\[\\mathbf{J}=\\left\\lbrack \\begin{array}{cc}\n",
301 | "-e^{-x_1 } & -1\\\\\n",
302 | "1 & {2x}_2 -3\n",
303 | "\\end{array}\\right\\rbrack\\\\]\n",
304 | "\n",
305 | "We then define corresponding callback functions R_ex2! and J_ex2! which compute the residual and it's jacobian, respectively: "
306 | ]
307 | },
308 | {
309 | "cell_type": "code",
310 | "execution_count": 9,
311 | "metadata": {},
312 | "outputs": [
313 | {
314 | "data": {
315 | "text/plain": [
316 | "J_ex2! (generic function with 1 method)"
317 | ]
318 | },
319 | "execution_count": 9,
320 | "metadata": {},
321 | "output_type": "execute_result"
322 | }
323 | ],
324 | "source": [
325 | "function R_ex2!(out, x)\n",
326 | " out[1] = exp(-x[1]) - x[2]\n",
327 | " out[2] = x[1] + x[2]^2 -3*x[2]\n",
328 | " return nothing\n",
329 | "end\n",
330 | "\n",
331 | "function J_ex2!(out, x)\n",
332 | " out[1,1] = -exp(-x[1])\n",
333 | " out[1,2] = -1\n",
334 | " out[2,1] = 1\n",
335 | " out[2,2] = 2*x[2] - 3\n",
336 | " return nothing\n",
337 | "end"
338 | ]
339 | },
340 | {
341 | "cell_type": "markdown",
342 | "metadata": {},
343 | "source": [
344 | "Then, we implement the iterative algorithm by:"
345 | ]
346 | },
347 | {
348 | "cell_type": "code",
349 | "execution_count": 11,
350 | "metadata": {},
351 | "outputs": [
352 | {
353 | "name": "stdout",
354 | "output_type": "stream",
355 | "text": [
356 | "The solution is (x1,x2) = (0.982752934497163,0.37427931266177783)\n"
357 | ]
358 | }
359 | ],
360 | "source": [
361 | "using LinearAlgebra: norm\n",
362 | "\n",
363 | "\"\"\"\n",
364 | "nonlinear_newton_raphson\n",
365 | "\n",
366 | "Solves for `R(x) = 0` via Newton's method where `R!` is the residual, `J!` is the Jacobian of the residual,\n",
367 | "`x` is an initial guess, `kmax` is the maximum number of iterations, and `tol` is the absolute convergence tolerance.\n",
368 | "This returns a tuple which holds solution `x` and a boolean value `flag` which is true if the algorithm successfully\n",
369 | "converged and false otherwise.\n",
370 | "\"\"\"\n",
371 | "function nonlinear_newton_raphson(R!,J!,x,kmax,tol)\n",
372 | " \n",
373 | " # create intermediate storage\n",
374 | " nx = length(x) \n",
375 | " Rout = zeros(nx)\n",
376 | " Jout = zeros(nx,nx)\n",
377 | " \n",
378 | " R!(Rout, x)\n",
379 | " k = 0\n",
380 | " while norm(Rout) > tol\n",
381 | " J!(Jout, x) # compute Jacobian\n",
382 | " del = -Jout\\Rout # calculate step\n",
383 | " x += del # perform step\n",
384 | " k += 1\n",
385 | " R!(Rout, x) # evaluate residual\n",
386 | " if k > kmax\n",
387 | " println(\"Maximum iterations exceeded.\")\n",
388 | " break\n",
389 | " end\n",
390 | " end\n",
391 | " \n",
392 | " flag = (k > kmax || x == Inf || x == -Inf) ? false : true\n",
393 | " return x, flag\n",
394 | "end\n",
395 | "\n",
396 | "x0 = [0.0; 0.0] # initial guess\n",
397 | "kmax = 100 # max number of iterations allowed\n",
398 | "tol = 1e-5 # convergence tolerance\n",
399 | "x, flag = nonlinear_newton_raphson(R_ex2!,J_ex2!,x0,kmax,tol)\n",
400 | "println(\"The solution is (x1,x2) = ($(x[1]),$(x[2]))\")"
401 | ]
402 | },
403 | {
404 | "cell_type": "markdown",
405 | "metadata": {},
406 | "source": [
407 | "# Case Study: Reactor-Separator-Recycle Process for Chlorination of Benzene \n",
408 | "\n",
409 | "In this section, we consider a reactor-separator-recycle process for chlorination of benzene as illustrated in the following figure:\n",
410 | "\n",
411 | "
\n",
412 | "\n",
413 | "In the continuous stirred-tank reactor (CSTR), benzene $\\text C_6 \\text H_6$ (A) reacts with chlorine $\\text{Cl}_2$ to yield monochlorobenzene $\\text C_6 \\text H_5 \\text{Cl}$ (B) and dichlorobenzene $\\text C_6 \\text H_4 \\text{Cl}_2$ (C). The chlorine $\\text{Cl}_2$ is in excess, thus the reactions can be simplified as:\n",
414 | "\n",
415 | "\\\\[A\\longrightarrow B \\\\]\n",
416 | "\\\\[B\\longrightarrow C \\\\]\n",
417 | "\n",
418 | "The reaction rates are given by:\n",
419 | "\n",
420 | "\\\\[r_1=k_1 \\frac{y_{3,A}}{y_{3,A} \\hat v_A + y_{3,B} \\hat v_B + y_{3,C} \\hat v_C},\\\\\n",
421 | "r_2 =k_2 \\frac{y_{3,B}}{y_{3,A} \\hat v_A + y_{3,B} \\hat v_B + y_{3,C} \\hat v_C}.\\\\]\n",
422 | "\n",
423 | "The reactor volume is $v=6 \\;\\text m^3$, the reaction rate constants are $k_1=0.4\\;\\text h^{-1}$ and $k_2 = 0.055 \\;\\text h^{-1}$, and the specific volumes of each species are $\\hat v_A=8.937\\times10^{-2} \\;\\text m^3/\\text{kmol}$, $\\hat v_B=1.018\\times10^{-1} \\;\\text m^3/\\text{kmol}$, and $\\hat v_C=1.130\\times10^{-1}\\;\\text m^3/\\text{kmol}$. \n",
424 | "\n",
425 | "The purporse is to produce $50\\; \\text{kmol}$ per hour in the outlet stream of the final separator ($F_5=50\\; \\text{kmol/h}$) and we now want to solve for **the mole fractions of each species** ($y_{3,A}$, $y_{3,B}$, and $y_{3,C}$) in the stream $F_3$.\n",
426 | "\n",
427 | "To solve this problem, we analyze the degrees of freedom for this system:\n",
428 | "- Unknowns: $F_1,F_2,F_3,F_4,F_5,F_6,F_7,y_{3,A},y_{3,B},y_{3,C},y_{4,B},y_{4,C},k_1,k_2,\\hat v_A, \\hat v_B, \\hat v_C, v$\n",
429 | "- Process specifications: $F_5,k_1,k_2,\\hat v_A, \\hat v_B, \\hat v_C, v$\n",
430 | "- Degrees of freedom: 18 unkowns - 7 specifications = 11 degrees of freedom\n",
431 | "\n",
432 | "Thus, 11 independent equations are required for solving the system. Based on the conservation of mass, we can set up the system of equations:\n",
433 | "\n",
434 | "- Mixer:\n",
435 | "\n",
436 | "\\\\[ F_1+F_7=F_2 \\\\]\n",
437 | "\n",
438 | "- Reactor:\n",
439 | "\n",
440 | "\\\\[F_2-r_1v=y_{3,A} F_3\\\\\n",
441 | "0=(r_1-r_2)v+y_{3,B} F_3\\\\\n",
442 | "0=r_2v - y_{3,C}F_3\\\\]\n",
443 | "\n",
444 | "- Separator 1: \n",
445 | "\n",
446 | "\\\\[F_3=F_4+F_7\\\\\n",
447 | "y_{3,B}F_3=y_{4,B}F_4\\\\\n",
448 | "y_{3,C}F_3=y_{4,C}F_4\\\\]\n",
449 | "\n",
450 | "- Separator 2:\n",
451 | "\n",
452 | "\\\\[F_4=F_5+F_6\\\\\n",
453 | "y_{4,B}F_4=F_5\\\\]\n",
454 | "\n",
455 | "- Other Relationships\n",
456 | "\n",
457 | "\\\\[y_{3,A}+y_{3,B}+y_{3,C}=1\\\\\n",
458 | "y_{4,B}+y_{4,C}=1\\\\]\n",
459 | "\n",
460 | "Since the compositions are unknown and the reaction rate laws are complex, the system is nonlinear.\n",
461 | "\n",
462 | "### Solutions:\n",
463 | "To solve a nonlinear system of equations, we can use the Newton-Raphson method. The 11 independent equations should be written in residual form by moving every term to one side:\n",
464 | "\n",
465 | "\\\\[\n",
466 | "\\begin{array}{l}\n",
467 | "F_1 -F_2 +F_7 =0 \\\\\n",
468 | "F_2 - k_1 y_{3,A}v / (y_{3,A} {\\hat{v} }_A +y_{3,B} {\\hat{v} }_B +y_{3,C} {\\hat{v} }_C) - y_{3,A} F_3 =0\\\\\n",
469 | "v(k_2 y_{3,B} -k_1 y_{3,A}) / (y_{3,A} {\\hat{v} }_A +y_{3,B} {\\hat{v} }_B +y_{3,C} {\\hat{v} }_C) + y_{3,B} F_3 =0\\\\\n",
470 | "k_2 y_{3,B}v / (y_{3,A} {\\hat{v} }_A +y_{3,B} {\\hat{v} }_B +y_{3,C} {\\hat{v} }_C)-y_{3,C} F_3 =0 \\\\\n",
471 | "F_3 -F_4 -F_7 =0\\\\\n",
472 | "y_{3,B} F_3 -y_{4,B} F_4 =0\\\\\n",
473 | "y_{3,C} F_3 -y_{4,C} F_4 =0\\\\\n",
474 | "F_4 -F_5 -F_6 =0\\\\\n",
475 | "y_{4,B} F_4 -F_5 =0\\\\\n",
476 | "y_{3,A} +y_{3,B} +y_{3,C} -1=0\\\\\n",
477 | "y_{4,B} +y_{4,C} -1=0\n",
478 | "\\end{array}\n",
479 | "\\\\]\n",
480 | "\n",
481 | "We define the variable vector for this problem as $\\mathbf x=(F_1,F_2,y_{3,A},y_{3,B},y_{3,C},F_3,y_{4,B},y_{4,C},F_4,F_6,F_7)$, then the residual vector is constructed as:\n",
482 | "\n",
483 | "\\\\[\\mathbf{R}\\left(\\mathbf{x}\\right)=\\left\\lbrack \\begin{array}{l}\n",
484 | "x_1 -x_2 +x_{11} \\\\\n",
485 | "x_2 -\\frac{k_1 x_3 v}{x_3 \\hat{v}_A +x_4 \\hat{v}_B +x_5 \\hat{v}_C }-x_3 x_6 \\\\\n",
486 | "\\frac{\\left({k_2 x_4 -k}_1 x_3 \\right)v}{x_3 \\hat{v}_A +x_4 \\hat{v}_B +x_5 \\hat{v}_C }+x_4 x_6 \\\\\n",
487 | "\\frac{k_2 x_4 v}{x_3 \\hat{v}_A +x_4 \\hat{v}_B +x_5 \\hat{v}_C }+x_5 x_6 \\\\\n",
488 | "x_6 -x_9 -x_{11} \\\\\n",
489 | "x_4 x_6 -x_7 x_9 \\\\\n",
490 | "x_5 x_6 -x_8 x_9 \\\\\n",
491 | "x_9 -F_5 -x_{10} \\\\\n",
492 | "x_7 x_9 -F_5 \\\\\n",
493 | "x_3 +x_4 +x_5 -1\\\\\n",
494 | "x_7 +x_8 -1\n",
495 | "\\end{array}\\right\\rbrack =\\mathbf{0}\\\\]\n",
496 | "\n",
497 | "Then, we form the Jacobian as:\n",
498 | "\n",
499 | "\\\\[\\mathbf{J}\\left(\\mathbf{x}\\right)=\\left\\lbrack \\begin{array}{ccc}\n",
500 | "\\frac{\\partial R_1 }{\\partial x_1 } & \\dots & \\frac{\\partial R_1 }{\\partial x_{11} }\\\\\n",
501 | "\\vdots & \\ddots & \\vdots \\\\\n",
502 | "\\frac{\\partial R_{11} }{\\partial x_1 } & \\dots & \\frac{\\partial R_{11} }{\\partial x_{11} }\n",
503 | "\\end{array}\\right\\rbrack\\\\]\n",
504 | "\n",
505 | "Most entries in the Jacobian are zero, the non-zero entries are indicated in the code below. \n",
506 | "\n",
507 | "\n",
508 | "INTERACTIVE! Fill in the remaining entries to complete the residual and Jacobian below.\n",
509 | "
"
510 | ]
511 | },
512 | {
513 | "cell_type": "code",
514 | "execution_count": 20,
515 | "metadata": {},
516 | "outputs": [
517 | {
518 | "data": {
519 | "text/plain": [
520 | "J_case! (generic function with 1 method)"
521 | ]
522 | },
523 | "execution_count": 20,
524 | "metadata": {},
525 | "output_type": "execute_result"
526 | }
527 | ],
528 | "source": [
529 | "function R_case!(out,x)\n",
530 | " \n",
531 | " # process specifications and constants\n",
532 | " V = 6 # m^3\n",
533 | " F5 = 25 # kmol/h\n",
534 | " Va = 8.937e-2 # m^3/kmol\n",
535 | " Vb = 1.018e-1 # m^3/kmol\n",
536 | " Vc = 1.13e-1 # m^3/kmol\n",
537 | " k1 = 0.4 # h^-1\n",
538 | " k2 = 0.055 # h^-1\n",
539 | "\n",
540 | " out[1] = x[1] - x[2] + x[11] # (eqn 1) x1 - x2 + x11 = 0\n",
541 | "\n",
542 | " # (eqn 2) x2 - k1*x3*v/(x3*Va + x4*Vb + x5*Vc) - x3*x6 = 0\n",
543 | " out[2] = x[2] - k1*x[3]*V/(x[3]*Va + x[4]*Vb + x[5]*Vc) - x[3]*x[6]\n",
544 | "\n",
545 | " # (eqn 3) (k2*x4 - k1*x3)*v/(x3*Va + x4*Vb + x5*Vc) + x4*x6 = 0\n",
546 | " out[3] = (k2*x[4] - k1*x[3])*V/(x[3]*Va + x[4]*Vb + x[5]*Vc) + x[4]*x[6]\n",
547 | "\n",
548 | " # (eqn 4) k2*x4*v/(x3*Va + x4*Vb + x5*Vc) - x5*x6 = 0\n",
549 | " out[4] = k2*x[4]*V/(x[3]*Va + x[4]*Vb + x[5]*Vc) - x[5]*x[6]\n",
550 | "\n",
551 | " out[5] = x[6] - x[9] - x[11] # (eqn 5) x6 - x9 - x11 = 0\n",
552 | " out[6] = x[4]*x[6] - x[7]*x[9] # (eqn 6) x4*x6 - x7*x9 = 0\n",
553 | " out[7] = x[5]*x[6] - x[8]*x[9] # (eqn 7) x5*x6 - x8*x9 = 0\n",
554 | " out[8] = x[9] - F5 - x[10] # (eqn 8) x9 - F5 - x10 = 0\n",
555 | " out[9] = x[7]*x[9] - F5 # (eqn 9) x7*x9 - F5 = 0\n",
556 | " out[10] = x[3] + x[4] + x[5] - 1 # (eqn 10) x3 + x4 + x5 - 1 = 0\n",
557 | " out[11] = x[7] + x[8] - 1 # (eqn 11) x7 + x8 - 1 = 0\n",
558 | " \n",
559 | " return nothing\n",
560 | "end\n",
561 | "\n",
562 | "function J_case!(out,x)\n",
563 | " \n",
564 | " # process specifications and constants\n",
565 | " V = 6 # m^3\n",
566 | " F5 = 25 # kmol/h\n",
567 | " Va = 8.937e-2 # m^3/kmol\n",
568 | " Vb = 1.018e-1 # m^3/kmol\n",
569 | " Vc = 1.13e-1 # m^3/kmol\n",
570 | " k1 = 0.4 # h^-1\n",
571 | " k2 = 0.055 # h^-1\n",
572 | "\n",
573 | " # Set all values in out to zero\n",
574 | " fill!(out, 0.0)\n",
575 | " \n",
576 | " out[1,1] = 1; out[1,2] = -1; out[1,11] = 1 # dR1/dx1, dR1/dx2, and dR1/dx11\n",
577 | "\n",
578 | " # dR2/dx2, dR2/dx3, dR2/dx4, dR2/dx5, and dR2/dx\n",
579 | " out[2,2] = 1\n",
580 | " out[2,3] = -(k1*V*(x[3]*Va + x[4]*Vb + x[5]*Vc) - k1*x[3]*V*Va)/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2 - x[6]\n",
581 | " out[2,4] = k1*x[3]*V*Vb/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
582 | " out[2,5] = k1*x[3]*V*Vc/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
583 | " out[2,6] = -x[3]\n",
584 | "\n",
585 | " \n",
586 | " # dR3/dx3, dR3/dx4, dR3/dx5, and dR3/dx6\n",
587 | " out[3,3] = (-k1*V*(x[3]*Va + x[4]*Vb + x[5]*Vc) - (k2*x[4] - k1*x[3])*V*Va)/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
588 | " out[3,4] = (k2*V*(x[3]*Va + x[4]*Vb + x[5]*Vc) - (k2*x[4] - k1*x[3])*V*Vb)/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2 + x[6]\n",
589 | " out[3,5] = -(k2*x[4] - k1*x[3])*V*Vc/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
590 | " out[3,6] = x[4]\n",
591 | " \n",
592 | " # dR4/dx3, dR4/dx4, dR4/dx5, and dR4/dx6\n",
593 | " out[4,3] = -k2*x[4]*V*Va/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
594 | " out[4,4] = (k2*V*(x[3]*Va + x[4]*Vb + x[5]*Vc) - k2*x[4]*V*Vb)/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
595 | " out[4,5] = -k2*x[4]*V*Vc/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2 - x[6]\n",
596 | " out[4,6] = -x[5]\n",
597 | "\n",
598 | " # dR5/dx6, dR5/dx9, and dR5/dx11\n",
599 | " out[5,6] = 1; out[5,9] = -1; out[5,11] = -1\n",
600 | "\n",
601 | " # dR6/dx4, dR6/dx6, dR6/dx7, and dR6/dx9\n",
602 | " out[6,4] = x[6]; out[6,6] = x[4]; out[6,7] = -x[9]; out[6,9] = -x[7]\n",
603 | "\n",
604 | " # dR7/dx5, dR7/dx6, dR7/dx8, and dR7/dx9\n",
605 | " out[7,5] = x[6]; out[7,6] = x[5]; out[7,8] = -x[9]; out[7,9] = -x[8]\n",
606 | "\n",
607 | " out[8,9] = 1; out[8,10] = -1 # dR8/dx9, dR8/dx10\n",
608 | " out[9,7] = x[9]; out[9,9] = x[7] # dR9/dx7, dR9/dx9\n",
609 | " out[10,3] = 1; out[10,4] = 1; out[10,5] = 1 # dR10/dx3, dR10/dx4, dR10/dx5\n",
610 | " out[11,7] = 1; out[11,8] = 1 # dR11/dx7 and dR11/dx8\n",
611 | " \n",
612 | " return nothing\n",
613 | "end"
614 | ]
615 | },
616 | {
617 | "cell_type": "markdown",
618 | "metadata": {},
619 | "source": [
620 | "Then, we implement the Newton-Raphson algorithm by:"
621 | ]
622 | },
623 | {
624 | "cell_type": "code",
625 | "execution_count": 21,
626 | "metadata": {},
627 | "outputs": [
628 | {
629 | "name": "stdout",
630 | "output_type": "stream",
631 | "text": [
632 | "The mole fraction of each species are\n",
633 | "y_{3,A} = 0.9454833705152079\n",
634 | "y_{3,B} = 0.05408780736715945\n",
635 | "y_{3,C} = 0.00042882211763266596\n"
636 | ]
637 | }
638 | ],
639 | "source": [
640 | "x0 = [35.7; 357; 0.9; 0.07; 0.03; 357; 0.7; 0.3; 35.7; 10.7; 321.3]\n",
641 | "tol = 1e-4\n",
642 | "kmax = 100\n",
643 | "x,flag = nonlinear_newton_raphson(R_case!,J_case!,x0,kmax,tol)\n",
644 | "println(\"The mole fraction of each species are\")\n",
645 | "println(\"y_{3,A} = $(x[3])\") \n",
646 | "println(\"y_{3,B} = $(x[4])\")\n",
647 | "println(\"y_{3,C} = $(x[5])\")"
648 | ]
649 | },
650 | {
651 | "cell_type": "markdown",
652 | "metadata": {},
653 | "source": [
654 | "
\n",
655 | "\n",
656 | "# Question(s) for reflection \n",
657 | "\n",
658 | "- The Newton-Raphson algorithm makes use of derivative information to compute the direction of steepest descent by solving a linear system of equations. One common issue encountered when applying this approach is potentially overshooting the soluton $\\mathbf{x} = \\mathbf{x}^*$. How might one solve this issue?"
659 | ]
660 | }
661 | ],
662 | "metadata": {
663 | "kernelspec": {
664 | "display_name": "Julia 1.7.2",
665 | "language": "julia",
666 | "name": "julia-1.7"
667 | },
668 | "language_info": {
669 | "file_extension": ".jl",
670 | "mimetype": "application/julia",
671 | "name": "julia",
672 | "version": "1.7.2"
673 | }
674 | },
675 | "nbformat": 4,
676 | "nbformat_minor": 4
677 | }
678 |
--------------------------------------------------------------------------------
/tutorial/julia/Tutorial 2 - Nonlinear Algebraic Systems/CSTR_ChloroBenzene.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/julia/Tutorial 2 - Nonlinear Algebraic Systems/CSTR_ChloroBenzene.png
--------------------------------------------------------------------------------
/tutorial/julia/Tutorial 2 - Nonlinear Algebraic Systems/J2_NLAsystems.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Nonlinear Algebraic Systems \n",
8 | "\n",
9 | "
\n",
10 | "\n",
11 | "# Learning Objectives \n",
12 | "\n",
13 | "By the end of this module, students will be able to\n",
14 | "\n",
15 | "### *Solve single nonlinear equations*\n",
16 | "- Solve a single nonlinear equation by Picard’s method.\n",
17 | "- Solve a single nonlinear equation by Newton’s method.\n",
18 | "- Determine whether solving a nonlinear equation by fixed-point method will converge.\\*\n",
19 | "- State the rate of convergence.\\*\n",
20 | "\n",
21 | "### *Solve systems of nonlinear equations*\n",
22 | "- Formulate residual equations.\n",
23 | "- Input a residual of a system of nonlinear equations in a program.\n",
24 | "- Solve a system of nonlinear equations by Newton-Raphson.\n",
25 | "\n",
26 | "### *Solve parametric nonlinear systems by continuation methods*\n",
27 | "- Solve a parametric nonlinear system using zeroth-order continuation for initial guesses.\\*\n",
28 | "\n",
29 | "\\*Not covered in tutorial.\n",
30 | "\n",
31 | "
"
32 | ]
33 | },
34 | {
35 | "cell_type": "markdown",
36 | "metadata": {},
37 | "source": [
38 | "# Solving Single Nonlinear Algebra Equations \n",
39 | "\n",
40 | "In this section, we will explore Picard’s method and Newton’s method, which are two traditional methods to systematically solve a single nonlinear algebra equation $f(x)=0$.\n",
41 | "\n",
42 | "### Picard's Method\n",
43 | "Picard’s method, which is also known as the method of successive substitution, involves simply adding $x$ to both sides of $f(x)=0$. The iterative scheme is\n",
44 | "\n",
45 | "\\\\[x^{\\left(k+1\\right)} =f\\left(x^{\\left(k\\right)} \\right)+x^{\\left(k\\right)}.\\\\]\n",
46 | "\n",
47 | "It is very easy to implement, but it may be very slow to converge.\n",
48 | "\n",
49 | "**Example 1**: Use Picard's method to find the root of function $f(x)=e^{-x}-x$ using initial guess $x^{(0)}=1$. \n",
50 | "\n",
51 | "We begin by defining a callback functions (f_ex1) to evaluate $f(x)$."
52 | ]
53 | },
54 | {
55 | "cell_type": "code",
56 | "execution_count": 2,
57 | "metadata": {},
58 | "outputs": [
59 | {
60 | "data": {
61 | "text/plain": [
62 | "f_ex1 (generic function with 1 method)"
63 | ]
64 | },
65 | "execution_count": 2,
66 | "metadata": {},
67 | "output_type": "execute_result"
68 | }
69 | ],
70 | "source": [
71 | "f_ex1(x) = exp(-x) - x"
72 | ]
73 | },
74 | {
75 | "cell_type": "markdown",
76 | "metadata": {},
77 | "source": [
78 | "We now define a function to perform Picard's method and run it for this problem."
79 | ]
80 | },
81 | {
82 | "cell_type": "code",
83 | "execution_count": 3,
84 | "metadata": {},
85 | "outputs": [
86 | {
87 | "name": "stdout",
88 | "output_type": "stream",
89 | "text": [
90 | "The solution is x = 0.5671477142601192\n"
91 | ]
92 | }
93 | ],
94 | "source": [
95 | "\"\"\"\n",
96 | "nonlinear_picard\n",
97 | "\n",
98 | "Solves for `f(x) = 0` via Picard iteration where `f` is the residual, `x` is an initial guess, \n",
99 | "`kmax` is the maximum number of iterations, `tol` is the absolute convergence tolerance. This returns\n",
100 | "a tuple which holds solution `x` and a boolean value `flag` which is true if the algorithm successfully\n",
101 | "converged and false otherwise.\n",
102 | "\"\"\"\n",
103 | "function nonlinear_picard(f,x,kmax,tol)\n",
104 | " f_val = f(x)\n",
105 | " k = 0 \n",
106 | " while abs(f_val) > tol\n",
107 | " x += f_val # Update x\n",
108 | " k += 1 # Update iteration counter\n",
109 | " f_val = f(x) # Compute function at next value\n",
110 | " if (k > kmax)\n",
111 | " println(\"Maximum iterations exceeded.\")\n",
112 | " break\n",
113 | " end\n",
114 | " end\n",
115 | " flag = (k > kmax || x == Inf || x == -Inf) ? false : true\n",
116 | " return x, flag\n",
117 | "end\n",
118 | "\n",
119 | "x0 = 1.0 # initial guess\n",
120 | "kmax = 20 # max number of iterations allowed\n",
121 | "tol = 1e-5 # convergence tolerance\n",
122 | "x,flag = nonlinear_picard(f_ex1,x0,kmax,tol)\n",
123 | "println(\"The solution is x = $(x)\")"
124 | ]
125 | },
126 | {
127 | "cell_type": "markdown",
128 | "metadata": {},
129 | "source": [
130 | "### Newton's Method\n",
131 | "\n",
132 | "Newton’s method is the most common method for solving a single nonlinear equation. The iterative scheme is\n",
133 | "\n",
134 | "\\\\[x^{(k+1)}=x^{(k)}- \\frac{f(x^{(k)})}{f'(x^{(k)})}.\\\\]\n",
135 | "\n",
136 | "As indicated by the scheme, the derivative $f'(x)$ is required for iterations.\n",
137 | "\n",
138 | "In order to solve **Example 1** using Newton's method, we should derive the derivative term $f'(x)=-e^{-x}-1$."
139 | ]
140 | },
141 | {
142 | "cell_type": "code",
143 | "execution_count": 4,
144 | "metadata": {},
145 | "outputs": [
146 | {
147 | "data": {
148 | "text/plain": [
149 | "df_ex1 (generic function with 1 method)"
150 | ]
151 | },
152 | "execution_count": 4,
153 | "metadata": {},
154 | "output_type": "execute_result"
155 | }
156 | ],
157 | "source": [
158 | "df_ex1(x) = -exp(-x) - 1"
159 | ]
160 | },
161 | {
162 | "cell_type": "markdown",
163 | "metadata": {},
164 | "source": [
165 | "We now define a function to perform Newton's method and run it for this problem."
166 | ]
167 | },
168 | {
169 | "cell_type": "code",
170 | "execution_count": 18,
171 | "metadata": {},
172 | "outputs": [
173 | {
174 | "name": "stdout",
175 | "output_type": "stream",
176 | "text": [
177 | "The solution is x = 0.567143285989123\n"
178 | ]
179 | }
180 | ],
181 | "source": [
182 | "\"\"\"\n",
183 | "nonlinear_newton\n",
184 | "\n",
185 | "Solves for `f(x) = 0` via Newton's method where `f` is the residual, `df` is the derivative of the residual,\n",
186 | "`x` is an initial guess, `kmax` is the maximum number of iterations, and `tol` is the absolute convergence tolerance.\n",
187 | "This returns a tuple which holds solution `x` and a boolean value `flag` which is true if the algorithm successfully\n",
188 | "converged and false otherwise.\n",
189 | "\"\"\"\n",
190 | "function nonlinear_newton(f,df,x,kmax,tol)\n",
191 | " fval = f(x)\n",
192 | " k = 0\n",
193 | " while abs(fval) > tol\n",
194 | " dfval = df(x)\n",
195 | " x -= fval/dfval\n",
196 | " k += 1\n",
197 | " fval = f(x)\n",
198 | " if k > kmax\n",
199 | " println(\"Maximum iterations exceeded.\")\n",
200 | " break\n",
201 | " end\n",
202 | " end\n",
203 | " flag = (k > kmax || x == Inf || x == -Inf) ? false : true\n",
204 | " return x, flag\n",
205 | "end\n",
206 | "\n",
207 | "x0 = 1.0 # initial guess\n",
208 | "kmax = 20 # max number of iterations allowed\n",
209 | "tol = 1e-5 # convergence tolerance\n",
210 | "x,flag = nonlinear_newton(f_ex1,df_ex1,x0,kmax,tol)\n",
211 | "println(\"The solution is x = $(x)\")"
212 | ]
213 | },
214 | {
215 | "cell_type": "markdown",
216 | "metadata": {},
217 | "source": [
218 | "Netwon's method exhibits quadratic convergence, that is a huge advantage, as the convergence accelerates rapidly near the solution."
219 | ]
220 | },
221 | {
222 | "cell_type": "markdown",
223 | "metadata": {},
224 | "source": [
225 | "\n",
226 | "INTERACTIVE! Run the above snippet of code while varying the initial guess to see how the solution responds. Does this always yield the same value? If so, why? For extreme guesses are any errors encountered? Take a moment and try to reflect on condition(s) under which these may occur.\n",
227 | "
"
228 | ]
229 | },
230 | {
231 | "cell_type": "markdown",
232 | "metadata": {},
233 | "source": [
234 | "# Solving Systems of Nonlinear Algebra Equations \n",
235 | "\n",
236 | "In this section, we introduce the numerical methods to solve systems of nonlinear algebra equations. The general form is given by:\n",
237 | "\n",
238 | "\\\\[R_1(x_1,x_2,\\ldots,x_n)=0\\\\\n",
239 | "R_2(x_1,x_2,\\ldots,x_n)=0\\\\\n",
240 | "\\ \\ \\ \\ \\ \\ \\ \\vdots\\\\\n",
241 | "R_n(x_1,x_2,\\ldots,x_n)=0\\\\]\n",
242 | "\n",
243 | "We need to be careful to write the equations in a form such that they are equal to zero in order to apply the fixed-point methods. The compact notation of the system of nonlinear equations is $\\bf R(\\bf x)=\\bf0$, with $\\bf x$ being the vector of unknowns as $\\mathbf x = (x_1,x_2,\\ldots,x_n)$, and \n",
244 | "\n",
245 | "\\\\[\\mathbf{R}=\\left\\lbrack \\begin{array}{c}\n",
246 | "R_1 \\left(x_1 ,x_2 ,\\dots ,x_n \\right)\\\\\n",
247 | "R_2 \\left(x_1 ,x_2 ,\\dots ,x_n \\right)\\\\\n",
248 | "\\vdots \\\\\n",
249 | "R_n \\left(x_1 ,x_2 ,\\dots ,x_n \\right)\n",
250 | "\\end{array}\\right\\rbrack\\\\]\n",
251 | "\n",
252 | "a vector-valued function which we will refer to as the **residual**.\n",
253 | "\n",
254 | "### Newton-Raphson\n",
255 | "A common method for solving nonlinear systems of equations is the Newton–Raphson method. It relies on the same idea as Newton’s method but is now generalized to n dimensions. The iterative scheme is given by\n",
256 | "\n",
257 | "\\\\[\\mathbf{J}(\\mathbf{x}^{(k)})(\\mathbf{x}^{(k+1)}-\\mathbf{x}^{(k)})=-\\mathbf{R}(\\mathbf{x}^{(k)}),\\\\]\n",
258 | "\n",
259 | "where the $\\mathbf J$ is the Jacobian matrix defined as\n",
260 | "\n",
261 | "\\\\[\\mathbf{J}=\\left\\lbrack \\begin{array}{ccc}\n",
262 | "\\frac{\\partial R_1 }{\\partial x_1 } & \\dots & \\frac{\\partial R_1 }{\\partial x_n }\\\\\n",
263 | "\\vdots & \\ddots & \\vdots \\\\\n",
264 | "\\frac{\\partial R_n }{\\partial x_1 } & \\dots & \\frac{\\partial R_n }{\\partial x_n }\n",
265 | "\\end{array}\\right\\rbrack,\\\\]\n",
266 | "\n",
267 | "that is to be evaluated at the value $\\mathbf{x}^{(k)}$. In component form, the elements of the Jacobian are the partial derivatives $J_{ij}=\\frac{\\partial R_i}{\\partial x_j}$.\n",
268 | "It is common to use a shorthand notation $\\mathbf{\\delta}^{(k+1)}=\\mathbf{x}^{(k+1)}-\\mathbf{x}^{(k)}$ for the difference between the previous values and the new values of $\\mathbf x$. The iterative scheme then becomes the linear system\n",
269 | "\n",
270 | "\\\\[\\mathbf{J}(\\mathbf{x}^{(k)})(\\mathbf{\\delta}^{(k+1)})=-\\mathbf{R}(\\mathbf{x}^{(k)}),\\\\]\n",
271 | "\n",
272 | "\\\\[\\mathbf x^{(k+1)}:=\\mathbf x^{(k)}+\\mathbf{\\delta}^{(k+1)}.\\\\]\n",
273 | "\n",
274 | "We have reduced solving the system of nonlinear equations into an iterative method where, at each step, we need to solve a system of linear equations. Since $\\mathbf{x}^{(k)}$ changes at each time step, we need to solve the linear system for different right-hand-side constant vectors and Jacobian matrices.\n",
275 | "\n",
276 | "The implementation of this method goes through the following steps.\n",
277 | "\n",
278 | "1. Create a vector with the residuals.\n",
279 | "2. Compute the elements in the Jacobian. \n",
280 | "3. Pick an initial guess $\\mathbf{x}^{(0)}$.\n",
281 | "4.Iterate until either $\\| \\mathbf R \\|$ and/or $\\|\\mathbf \\delta\\|$ are small.\n",
282 | "\n",
283 | "**Example 2:** Use Netwon-Raphson to find the root of the system of equations\n",
284 | "\n",
285 | "\\\\[f_1(x_1,x_2)=e^{-x_1}-x_2, \\\\\n",
286 | "f_2(x_1,x_2)=x_1+x_2^2-3x_2, \\\\]\n",
287 | "\n",
288 | "using the initial guess $x_1^{(0)}=0$ and $x_2^{(0)}=0$.\n",
289 | "\n",
290 | "**Solution:**\n",
291 | "The residual vector for this problem is\n",
292 | "\n",
293 | "\\\\[\\mathbf{R}=\\left\\lbrack \\begin{array}{c}\n",
294 | "e^{-x_1 } -x_2 \\\\\n",
295 | "x_1 +x_2^2 -3x_2 \n",
296 | "\\end{array}\\right\\rbrack\\\\]\n",
297 | "\n",
298 | "If we take the logical choice for the vector of unknowns to be $\\mathbf x =(x_1,x_2)$, then the Jacobian is\n",
299 | "\n",
300 | "\\\\[\\mathbf{J}=\\left\\lbrack \\begin{array}{cc}\n",
301 | "-e^{-x_1 } & -1\\\\\n",
302 | "1 & {2x}_2 -3\n",
303 | "\\end{array}\\right\\rbrack\\\\]\n",
304 | "\n",
305 | "We then define corresponding callback functions R_ex2! and J_ex2! which compute the residual and it's jacobian, respectively: "
306 | ]
307 | },
308 | {
309 | "cell_type": "code",
310 | "execution_count": 9,
311 | "metadata": {},
312 | "outputs": [
313 | {
314 | "data": {
315 | "text/plain": [
316 | "J_ex2! (generic function with 1 method)"
317 | ]
318 | },
319 | "execution_count": 9,
320 | "metadata": {},
321 | "output_type": "execute_result"
322 | }
323 | ],
324 | "source": [
325 | "function R_ex2!(out, x)\n",
326 | " out[1] = exp(-x[1]) - x[2]\n",
327 | " out[2] = x[1] + x[2]^2 -3*x[2]\n",
328 | " return nothing\n",
329 | "end\n",
330 | "\n",
331 | "function J_ex2!(out, x)\n",
332 | " out[1,1] = -exp(-x[1])\n",
333 | " out[1,2] = -1\n",
334 | " out[2,1] = 1\n",
335 | " out[2,2] = 2*x[2] - 3\n",
336 | " return nothing\n",
337 | "end"
338 | ]
339 | },
340 | {
341 | "cell_type": "markdown",
342 | "metadata": {},
343 | "source": [
344 | "Then, we implement the iterative algorithm by:"
345 | ]
346 | },
347 | {
348 | "cell_type": "code",
349 | "execution_count": 11,
350 | "metadata": {},
351 | "outputs": [
352 | {
353 | "name": "stdout",
354 | "output_type": "stream",
355 | "text": [
356 | "The solution is (x1,x2) = (0.982752934497163,0.37427931266177783)\n"
357 | ]
358 | }
359 | ],
360 | "source": [
361 | "using LinearAlgebra: norm\n",
362 | "\n",
363 | "\"\"\"\n",
364 | "nonlinear_newton_raphson\n",
365 | "\n",
366 | "Solves for `R(x) = 0` via Newton's method where `R!` is the residual, `J!` is the Jacobian of the residual,\n",
367 | "`x` is an initial guess, `kmax` is the maximum number of iterations, and `tol` is the absolute convergence tolerance.\n",
368 | "This returns a tuple which holds solution `x` and a boolean value `flag` which is true if the algorithm successfully\n",
369 | "converged and false otherwise.\n",
370 | "\"\"\"\n",
371 | "function nonlinear_newton_raphson(R!,J!,x,kmax,tol)\n",
372 | " \n",
373 | " # create intermediate storage\n",
374 | " nx = length(x) \n",
375 | " Rout = zeros(nx)\n",
376 | " Jout = zeros(nx,nx)\n",
377 | " \n",
378 | " R!(Rout, x)\n",
379 | " k = 0\n",
380 | " while norm(Rout) > tol\n",
381 | " J!(Jout, x) # compute Jacobian\n",
382 | " del = -Jout\\Rout # calculate step\n",
383 | " x += del # perform step\n",
384 | " k += 1\n",
385 | " R!(Rout, x) # evaluate residual\n",
386 | " if k > kmax\n",
387 | " println(\"Maximum iterations exceeded.\")\n",
388 | " break\n",
389 | " end\n",
390 | " end\n",
391 | " \n",
392 | " flag = (k > kmax || x == Inf || x == -Inf) ? false : true\n",
393 | " return x, flag\n",
394 | "end\n",
395 | "\n",
396 | "x0 = [0.0; 0.0] # initial guess\n",
397 | "kmax = 100 # max number of iterations allowed\n",
398 | "tol = 1e-5 # convergence tolerance\n",
399 | "x, flag = nonlinear_newton_raphson(R_ex2!,J_ex2!,x0,kmax,tol)\n",
400 | "println(\"The solution is (x1,x2) = ($(x[1]),$(x[2]))\")"
401 | ]
402 | },
403 | {
404 | "attachments": {},
405 | "cell_type": "markdown",
406 | "metadata": {},
407 | "source": [
408 | "# Case Study: Reactor-Separator-Recycle Process for Chlorination of Benzene \n",
409 | "\n",
410 | "In this section, we consider a reactor-separator-recycle process for chlorination of benzene as illustrated in the following figure:\n",
411 | "\n",
412 | "
\n",
413 | "\n",
414 | "In the continuous stirred-tank reactor (CSTR), benzene $\\text C_6 \\text H_6$ (A) reacts with chlorine $\\text{Cl}_2$ to yield monochlorobenzene $\\text C_6 \\text H_5 \\text{Cl}$ (B) and dichlorobenzene $\\text C_6 \\text H_4 \\text{Cl}_2$ (C). The chlorine $\\text{Cl}_2$ is in excess, thus the reactions can be simplified as:\n",
415 | "\n",
416 | "\\\\[A\\longrightarrow B \\\\]\n",
417 | "\\\\[B\\longrightarrow C \\\\]\n",
418 | "\n",
419 | "The reaction rates are given by:\n",
420 | "\n",
421 | "\\\\[r_1=k_1 \\frac{y_{3,A}}{y_{3,A} \\hat v_A + y_{3,B} \\hat v_B + y_{3,C} \\hat v_C},\\\\\n",
422 | "r_2 =k_2 \\frac{y_{3,B}}{y_{3,A} \\hat v_A + y_{3,B} \\hat v_B + y_{3,C} \\hat v_C}.\\\\]\n",
423 | "\n",
424 | "The reactor volume is $v=6 \\;\\text m^3$, the reaction rate constants are $k_1=0.4\\;\\text h^{-1}$ and $k_2 = 0.055 \\;\\text h^{-1}$, and the specific volumes of each species are $\\hat v_A=8.937\\times10^{-2} \\;\\text m^3/\\text{kmol}$, $\\hat v_B=1.018\\times10^{-1} \\;\\text m^3/\\text{kmol}$, and $\\hat v_C=1.130\\times10^{-1}\\;\\text m^3/\\text{kmol}$. \n",
425 | "\n",
426 | "The purporse is to produce $50\\; \\text{kmol}$ per hour in the outlet stream of the final separator ($F_5=50\\; \\text{kmol/h}$) and we now want to solve for **the mole fractions of each species** ($y_{3,A}$, $y_{3,B}$, and $y_{3,C}$) in the stream $F_3$.\n",
427 | "\n",
428 | "To solve this problem, we analyze the degrees of freedom for this system:\n",
429 | "- Unknowns: $F_1,F_2,F_3,F_4,F_5,F_6,F_7,y_{3,A},y_{3,B},y_{3,C},y_{4,B},y_{4,C},k_1,k_2,\\hat v_A, \\hat v_B, \\hat v_C, v$\n",
430 | "- Process specifications: $F_5,k_1,k_2,\\hat v_A, \\hat v_B, \\hat v_C, v$\n",
431 | "- Degrees of freedom: 18 unkowns - 7 specifications = 11 degrees of freedom\n",
432 | "\n",
433 | "Thus, 11 independent equations are required for solving the system. Based on the conservation of mass, we can set up the system of equations:\n",
434 | "\n",
435 | "- Mixer:\n",
436 | "\n",
437 | "\\\\[ F_1+F_7=F_2 \\\\]\n",
438 | "\n",
439 | "- Reactor:\n",
440 | "\n",
441 | "\\\\[F_2-r_1v=y_{3,A} F_3\\\\\n",
442 | "0=(r_1-r_2)v+y_{3,B} F_3\\\\\n",
443 | "0=r_2v - y_{3,C}F_3\\\\]\n",
444 | "\n",
445 | "- Separator 1: \n",
446 | "\n",
447 | "\\\\[F_3=F_4+F_7\\\\\n",
448 | "y_{3,B}F_3=y_{4,B}F_4\\\\\n",
449 | "y_{3,C}F_3=y_{4,C}F_4\\\\]\n",
450 | "\n",
451 | "- Separator 2:\n",
452 | "\n",
453 | "\\\\[F_4=F_5+F_6\\\\\n",
454 | "y_{4,B}F_4=F_5\\\\]\n",
455 | "\n",
456 | "- Other Relationships\n",
457 | "\n",
458 | "\\\\[y_{3,A}+y_{3,B}+y_{3,C}=1\\\\\n",
459 | "y_{4,B}+y_{4,C}=1\\\\]\n",
460 | "\n",
461 | "Since the compositions are unknown and the reaction rate laws are complex, the system is nonlinear.\n",
462 | "\n",
463 | "### Solutions:\n",
464 | "To solve a nonlinear system of equations, we can use the Newton-Raphson method. The 11 independent equations should be written in residual form by moving every term to one side:\n",
465 | "\n",
466 | "\\\\[\n",
467 | "\\begin{array}{l}\n",
468 | "F_1 -F_2 +F_7 =0 \\\\\n",
469 | "F_2 - k_1 y_{3,A}v / (y_{3,A} {\\hat{v} }_A +y_{3,B} {\\hat{v} }_B +y_{3,C} {\\hat{v} }_C) - y_{3,A} F_3 =0\\\\\n",
470 | "v(k_2 y_{3,B} -k_1 y_{3,A}) / (y_{3,A} {\\hat{v} }_A +y_{3,B} {\\hat{v} }_B +y_{3,C} {\\hat{v} }_C) + y_{3,B} F_3 =0\\\\\n",
471 | "k_2 y_{3,B}v / (y_{3,A} {\\hat{v} }_A +y_{3,B} {\\hat{v} }_B +y_{3,C} {\\hat{v} }_C)-y_{3,C} F_3 =0 \\\\\n",
472 | "F_3 -F_4 -F_7 =0\\\\\n",
473 | "y_{3,B} F_3 -y_{4,B} F_4 =0\\\\\n",
474 | "y_{3,C} F_3 -y_{4,C} F_4 =0\\\\\n",
475 | "F_4 -F_5 -F_6 =0\\\\\n",
476 | "y_{4,B} F_4 -F_5 =0\\\\\n",
477 | "y_{3,A} +y_{3,B} +y_{3,C} -1=0\\\\\n",
478 | "y_{4,B} +y_{4,C} -1=0\n",
479 | "\\end{array}\n",
480 | "\\\\]\n",
481 | "\n",
482 | "We define the variable vector for this problem as $\\mathbf x=(F_1,F_2,y_{3,A},y_{3,B},y_{3,C},F_3,y_{4,B},y_{4,C},F_4,F_6,F_7)$, then the residual vector is constructed as:\n",
483 | "\n",
484 | "\\\\[\\mathbf{R}\\left(\\mathbf{x}\\right)=\\left\\lbrack \\begin{array}{l}\n",
485 | "x_1 -x_2 +x_{11} \\\\\n",
486 | "x_2 -\\frac{k_1 x_3 v}{x_3 \\hat{v}_A +x_4 \\hat{v}_B +x_5 \\hat{v}_C }-x_3 x_6 \\\\\n",
487 | "\\frac{\\left({k_2 x_4 -k}_1 x_3 \\right)v}{x_3 \\hat{v}_A +x_4 \\hat{v}_B +x_5 \\hat{v}_C }+x_4 x_6 \\\\\n",
488 | "\\frac{k_2 x_4 v}{x_3 \\hat{v}_A +x_4 \\hat{v}_B +x_5 \\hat{v}_C }+x_5 x_6 \\\\\n",
489 | "x_6 -x_9 -x_{11} \\\\\n",
490 | "x_4 x_6 -x_7 x_9 \\\\\n",
491 | "x_5 x_6 -x_8 x_9 \\\\\n",
492 | "x_9 -F_5 -x_{10} \\\\\n",
493 | "x_7 x_9 -F_5 \\\\\n",
494 | "x_3 +x_4 +x_5 -1\\\\\n",
495 | "x_7 +x_8 -1\n",
496 | "\\end{array}\\right\\rbrack =\\mathbf{0}\\\\]\n",
497 | "\n",
498 | "Then, we form the Jacobian as:\n",
499 | "\n",
500 | "\\\\[\\mathbf{J}\\left(\\mathbf{x}\\right)=\\left\\lbrack \\begin{array}{ccc}\n",
501 | "\\frac{\\partial R_1 }{\\partial x_1 } & \\dots & \\frac{\\partial R_1 }{\\partial x_{11} }\\\\\n",
502 | "\\vdots & \\ddots & \\vdots \\\\\n",
503 | "\\frac{\\partial R_{11} }{\\partial x_1 } & \\dots & \\frac{\\partial R_{11} }{\\partial x_{11} }\n",
504 | "\\end{array}\\right\\rbrack\\\\]\n",
505 | "\n",
506 | "Most entries in the Jacobian are zero, the non-zero entries are indicated in the code below. \n",
507 | "\n",
508 | "\n",
509 | "INTERACTIVE! Fill in the remaining entries to complete the residual and Jacobian below.\n",
510 | "
"
511 | ]
512 | },
513 | {
514 | "cell_type": "code",
515 | "execution_count": 20,
516 | "metadata": {},
517 | "outputs": [
518 | {
519 | "data": {
520 | "text/plain": [
521 | "J_case! (generic function with 1 method)"
522 | ]
523 | },
524 | "execution_count": 20,
525 | "metadata": {},
526 | "output_type": "execute_result"
527 | }
528 | ],
529 | "source": [
530 | "function R_case!(out,x)\n",
531 | " \n",
532 | " # process specifications and constants\n",
533 | " V = 6 # m^3\n",
534 | " F5 = 25 # kmol/h\n",
535 | " Va = 8.937e-2 # m^3/kmol\n",
536 | " Vb = 1.018e-1 # m^3/kmol\n",
537 | " Vc = 1.13e-1 # m^3/kmol\n",
538 | " k1 = 0.4 # h^-1\n",
539 | " k2 = 0.055 # h^-1\n",
540 | "\n",
541 | " out[1] = x[1] - x[2] + x[11] # (eqn 1) x1 - x2 + x11 = 0\n",
542 | "\n",
543 | " # (eqn 2) x2 - k1*x3*v/(x3*Va + x4*Vb + x5*Vc) - x3*x6 = 0\n",
544 | " out[2] = x[2] - k1*x[3]*V/(x[3]*Va + x[4]*Vb + x[5]*Vc) - x[3]*x[6]\n",
545 | "\n",
546 | " # (eqn 3) (k2*x4 - k1*x3)*v/(x3*Va + x4*Vb + x5*Vc) + x4*x6 = 0\n",
547 | " out[3] = (k2*x[4] - k1*x[3])*V/(x[3]*Va + x[4]*Vb + x[5]*Vc) + x[4]*x[6]\n",
548 | "\n",
549 | " # (eqn 4) k2*x4*v/(x3*Va + x4*Vb + x5*Vc) - x5*x6 = 0\n",
550 | " out[4] = k2*x[4]*V/(x[3]*Va + x[4]*Vb + x[5]*Vc) - x[5]*x[6]\n",
551 | "\n",
552 | " ### SPECIFY THE COMPONENTS 5 TO 7 OF THE RESIDUAL HERE ###\n",
553 | " ### SPECIFY THE COMPONENTS 5 TO 7 OF THE RESIDUAL HERE ###\n",
554 | " ### SPECIFY THE COMPONENTS 5 TO 7 OF THE RESIDUAL HERE ###\n",
555 | " \n",
556 | " out[8] = x[9] - F5 - x[10] # (eqn 8) x9 - F5 - x10 = 0\n",
557 | " out[9] = x[7]*x[9] - F5 # (eqn 9) x7*x9 - F5 = 0\n",
558 | " out[10] = x[3] + x[4] + x[5] - 1 # (eqn 10) x3 + x4 + x5 - 1 = 0\n",
559 | " out[11] = x[7] + x[8] - 1 # (eqn 11) x7 + x8 - 1 = 0\n",
560 | " \n",
561 | " return nothing\n",
562 | "end\n",
563 | "\n",
564 | "function J_case!(out,x)\n",
565 | " \n",
566 | " # process specifications and constants\n",
567 | " V = 6 # m^3\n",
568 | " F5 = 25 # kmol/h\n",
569 | " Va = 8.937e-2 # m^3/kmol\n",
570 | " Vb = 1.018e-1 # m^3/kmol\n",
571 | " Vc = 1.13e-1 # m^3/kmol\n",
572 | " k1 = 0.4 # h^-1\n",
573 | " k2 = 0.055 # h^-1\n",
574 | "\n",
575 | " # Set all values in out to zero\n",
576 | " fill!(out, 0.0)\n",
577 | " \n",
578 | " out[1,1] = 1; out[1,2] = -1; out[1,11] = 1 # dR1/dx1, dR1/dx2, and dR1/dx11\n",
579 | "\n",
580 | " # dR2/dx2, dR2/dx3, dR2/dx4, dR2/dx5, and dR2/dx\n",
581 | " out[2,2] = 1\n",
582 | " out[2,3] = -(k1*V*(x[3]*Va + x[4]*Vb + x[5]*Vc) - k1*x[3]*V*Va)/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2 - x[6]\n",
583 | " out[2,4] = k1*x[3]*V*Vb/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
584 | " out[2,5] = k1*x[3]*V*Vc/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
585 | " out[2,6] = -x[3]\n",
586 | "\n",
587 | " \n",
588 | " # dR3/dx3, dR3/dx4, dR3/dx5, and dR3/dx6\n",
589 | " out[3,3] = (-k1*V*(x[3]*Va + x[4]*Vb + x[5]*Vc) - (k2*x[4] - k1*x[3])*V*Va)/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
590 | " out[3,4] = (k2*V*(x[3]*Va + x[4]*Vb + x[5]*Vc) - (k2*x[4] - k1*x[3])*V*Vb)/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2 + x[6]\n",
591 | " out[3,5] = -(k2*x[4] - k1*x[3])*V*Vc/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
592 | " out[3,6] = x[4]\n",
593 | " \n",
594 | " # dR4/dx3, dR4/dx4, dR4/dx5, and dR4/dx6\n",
595 | " out[4,3] = -k2*x[4]*V*Va/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
596 | " out[4,4] = (k2*V*(x[3]*Va + x[4]*Vb + x[5]*Vc) - k2*x[4]*V*Vb)/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
597 | " out[4,5] = -k2*x[4]*V*Vc/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2 - x[6]\n",
598 | " out[4,6] = -x[5]\n",
599 | "\n",
600 | " ### SPECIFY THE JACOBIAN OF COMPONENTS 5 TO 7 OF THE RESIDUAL HERE ###\n",
601 | " ### SPECIFY THE JACOBIAN OF COMPONENTS 5 TO 7 OF THE RESIDUAL HERE ###\n",
602 | " ### SPECIFY THE JACOBIAN OF COMPONENTS 5 TO 7 OF THE RESIDUAL HERE ###\n",
603 | "\n",
604 | " out[8,9] = 1; out[8,10] = -1 # dR8/dx9, dR8/dx10\n",
605 | " out[9,7] = x[9]; out[9,9] = x[7] # dR9/dx7, dR9/dx9\n",
606 | " out[10,3] = 1; out[10,4] = 1; out[10,5] = 1 # dR10/dx3, dR10/dx4, dR10/dx5\n",
607 | " out[11,7] = 1; out[11,8] = 1 # dR11/dx7 and dR11/dx8\n",
608 | " \n",
609 | " return nothing\n",
610 | "end"
611 | ]
612 | },
613 | {
614 | "cell_type": "markdown",
615 | "metadata": {},
616 | "source": [
617 | "Then, we implement the Newton-Raphson algorithm by:"
618 | ]
619 | },
620 | {
621 | "cell_type": "code",
622 | "execution_count": 21,
623 | "metadata": {},
624 | "outputs": [
625 | {
626 | "name": "stdout",
627 | "output_type": "stream",
628 | "text": [
629 | "The mole fraction of each species are\n",
630 | "y_{3,A} = 0.9454833705152079\n",
631 | "y_{3,B} = 0.05408780736715945\n",
632 | "y_{3,C} = 0.00042882211763266596\n"
633 | ]
634 | }
635 | ],
636 | "source": [
637 | "x0 = [35.7; 357; 0.9; 0.07; 0.03; 357; 0.7; 0.3; 35.7; 10.7; 321.3]\n",
638 | "tol = 1e-4\n",
639 | "kmax = 100\n",
640 | "x,flag = nonlinear_newton_raphson(R_case!,J_case!,x0,kmax,tol)\n",
641 | "println(\"The mole fraction of each species are\")\n",
642 | "println(\"y_{3,A} = $(x[3])\") \n",
643 | "println(\"y_{3,B} = $(x[4])\")\n",
644 | "println(\"y_{3,C} = $(x[5])\")"
645 | ]
646 | },
647 | {
648 | "cell_type": "markdown",
649 | "metadata": {},
650 | "source": [
651 | "
\n",
652 | "\n",
653 | "# Question(s) for reflection \n",
654 | "\n",
655 | "- The Newton-Raphson algorithm makes use of derivative information to compute the direction of steepest descent by solving a linear system of equations. One common issue encountered when applying this approach is potentially overshooting the soluton $\\mathbf{x} = \\mathbf{x}^*$. How might one solve this issue?"
656 | ]
657 | }
658 | ],
659 | "metadata": {
660 | "kernelspec": {
661 | "display_name": "Julia 1.5.1",
662 | "language": "julia",
663 | "name": "julia-1.5"
664 | },
665 | "language_info": {
666 | "file_extension": ".jl",
667 | "mimetype": "application/julia",
668 | "name": "julia",
669 | "version": "1.5.1"
670 | }
671 | },
672 | "nbformat": 4,
673 | "nbformat_minor": 4
674 | }
675 |
--------------------------------------------------------------------------------
/tutorial/julia/Tutorial 2 - Nonlinear Algebraic Systems/J2_NLAsystems_solved.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Linear Algebraic Systems \n",
8 | "\n",
9 | "
\n",
10 | "\n",
11 | "# Learning Objectives \n",
12 | "\n",
13 | "By the end of this module, students will be able to\n",
14 | "\n",
15 | "### *Solve single nonlinear equations*\n",
16 | "- Solve a single nonlinear equation by Picard’s method.\n",
17 | "- Solve a single nonlinear equation by Newton’s method.\n",
18 | "- Determine whether solving a nonlinear equation by fixed-point method will converge.\\*\n",
19 | "- State the rate of convergence.\\*\n",
20 | "\n",
21 | "### *Solve systems of nonlinear equations*\n",
22 | "- Formulate residual equations.\n",
23 | "- Input a residual of a system of nonlinear equations in a program.\n",
24 | "- Solve a system of nonlinear equations by Newton-Raphson.\n",
25 | "\n",
26 | "### *Solve parametric nonlinear systems by continuation methods*\n",
27 | "- Solve a parametric nonlinear system using zeroth-order continuation for initial guesses.\\*\n",
28 | "\n",
29 | "\\*Not covered in tutorial.\n",
30 | "\n",
31 | "
"
32 | ]
33 | },
34 | {
35 | "cell_type": "markdown",
36 | "metadata": {},
37 | "source": [
38 | "# Solving Single Nonlinear Algebra Equations \n",
39 | "\n",
40 | "In this section, we will explore Picard’s method and Newton’s method, which are two traditional methods to systematically solve a single nonlinear algebra equation $f(x)=0$.\n",
41 | "\n",
42 | "### Picard's Method\n",
43 | "Picard’s method, which is also known as the method of successive substitution, involves simply adding $x$ to both sides of $f(x)=0$. The iterative scheme is\n",
44 | "\n",
45 | "\\\\[x^{\\left(k+1\\right)} =f\\left(x^{\\left(k\\right)} \\right)+x^{\\left(k\\right)}.\\\\]\n",
46 | "\n",
47 | "It is very easy to implement, but it may be very slow to converge.\n",
48 | "\n",
49 | "**Example 1**: Use Picard's method to find the root of function $f(x)=e^{-x}-x$ using initial guess $x^{(0)}=1$. \n",
50 | "\n",
51 | "We begin by defining a callback functions (f_ex1) to evaluate $f(x)$."
52 | ]
53 | },
54 | {
55 | "cell_type": "code",
56 | "execution_count": 2,
57 | "metadata": {},
58 | "outputs": [
59 | {
60 | "data": {
61 | "text/plain": [
62 | "f_ex1 (generic function with 1 method)"
63 | ]
64 | },
65 | "execution_count": 2,
66 | "metadata": {},
67 | "output_type": "execute_result"
68 | }
69 | ],
70 | "source": [
71 | "f_ex1(x) = exp(-x) - x"
72 | ]
73 | },
74 | {
75 | "cell_type": "markdown",
76 | "metadata": {},
77 | "source": [
78 | "We now define a function to perform Picard's method and run it for this problem."
79 | ]
80 | },
81 | {
82 | "cell_type": "code",
83 | "execution_count": 3,
84 | "metadata": {},
85 | "outputs": [
86 | {
87 | "name": "stdout",
88 | "output_type": "stream",
89 | "text": [
90 | "The solution is x = 0.5671477142601192\n"
91 | ]
92 | }
93 | ],
94 | "source": [
95 | "\"\"\"\n",
96 | "nonlinear_picard\n",
97 | "\n",
98 | "Solves for `f(x) = 0` via Picard iteration where `f` is the residual, `x` is an initial guess, \n",
99 | "`kmax` is the maximum number of iterations, `tol` is the absolute convergence tolerance. This returns\n",
100 | "a tuple which holds solution `x` and a boolean value `flag` which is true if the algorithm successfully\n",
101 | "converged and false otherwise.\n",
102 | "\"\"\"\n",
103 | "function nonlinear_picard(f,x,kmax,tol)\n",
104 | " f_val = f(x)\n",
105 | " k = 0 \n",
106 | " while abs(f_val) > tol\n",
107 | " x += f_val # Update x\n",
108 | " k += 1 # Update iteration counter\n",
109 | " f_val = f(x) # Compute function at next value\n",
110 | " if (k > kmax)\n",
111 | " println(\"Maximum iterations exceeded.\")\n",
112 | " break\n",
113 | " end\n",
114 | " end\n",
115 | " flag = (k > kmax || x == Inf || x == -Inf) ? false : true\n",
116 | " return x, flag\n",
117 | "end\n",
118 | "\n",
119 | "x0 = 1.0 # initial guess\n",
120 | "kmax = 20 # max number of iterations allowed\n",
121 | "tol = 1e-5 # convergence tolerance\n",
122 | "x,flag = nonlinear_picard(f_ex1,x0,kmax,tol)\n",
123 | "println(\"The solution is x = $(x)\")"
124 | ]
125 | },
126 | {
127 | "cell_type": "markdown",
128 | "metadata": {},
129 | "source": [
130 | "### Newton's Method\n",
131 | "\n",
132 | "Newton’s method is the most common method for solving a single nonlinear equation. The iterative scheme is\n",
133 | "\n",
134 | "\\\\[x^{(k+1)}=x^{(k)}- \\frac{f(x^{(k)})}{f'(x^{(k)})}.\\\\]\n",
135 | "\n",
136 | "As indicated by the scheme, the derivative $f'(x)$ is required for iterations.\n",
137 | "\n",
138 | "In order to solve **Example 1** using Newton's method, we should derive the derivative term $f'(x)=-e^{-x}-1$."
139 | ]
140 | },
141 | {
142 | "cell_type": "code",
143 | "execution_count": 4,
144 | "metadata": {},
145 | "outputs": [
146 | {
147 | "data": {
148 | "text/plain": [
149 | "df_ex1 (generic function with 1 method)"
150 | ]
151 | },
152 | "execution_count": 4,
153 | "metadata": {},
154 | "output_type": "execute_result"
155 | }
156 | ],
157 | "source": [
158 | "df_ex1(x) = -exp(-x) - 1"
159 | ]
160 | },
161 | {
162 | "cell_type": "markdown",
163 | "metadata": {},
164 | "source": [
165 | "We now define a function to perform Newton's method and run it for this problem."
166 | ]
167 | },
168 | {
169 | "cell_type": "code",
170 | "execution_count": 18,
171 | "metadata": {},
172 | "outputs": [
173 | {
174 | "name": "stdout",
175 | "output_type": "stream",
176 | "text": [
177 | "The solution is x = 0.567143285989123\n"
178 | ]
179 | }
180 | ],
181 | "source": [
182 | "\"\"\"\n",
183 | "nonlinear_newton\n",
184 | "\n",
185 | "Solves for `f(x) = 0` via Newton's method where `f` is the residual, `df` is the derivative of the residual,\n",
186 | "`x` is an initial guess, `kmax` is the maximum number of iterations, and `tol` is the absolute convergence tolerance.\n",
187 | "This returns a tuple which holds solution `x` and a boolean value `flag` which is true if the algorithm successfully\n",
188 | "converged and false otherwise.\n",
189 | "\"\"\"\n",
190 | "function nonlinear_newton(f,df,x,kmax,tol)\n",
191 | " fval = f(x)\n",
192 | " k = 0\n",
193 | " while abs(fval) > tol\n",
194 | " dfval = df(x)\n",
195 | " x -= fval/dfval\n",
196 | " k += 1\n",
197 | " fval = f(x)\n",
198 | " if k > kmax\n",
199 | " println(\"Maximum iterations exceeded.\")\n",
200 | " break\n",
201 | " end\n",
202 | " end\n",
203 | " flag = (k > kmax || x == Inf || x == -Inf) ? false : true\n",
204 | " return x, flag\n",
205 | "end\n",
206 | "\n",
207 | "x0 = 1.0 # initial guess\n",
208 | "kmax = 20 # max number of iterations allowed\n",
209 | "tol = 1e-5 # convergence tolerance\n",
210 | "x,flag = nonlinear_newton(f_ex1,df_ex1,x0,kmax,tol)\n",
211 | "println(\"The solution is x = $(x)\")"
212 | ]
213 | },
214 | {
215 | "cell_type": "markdown",
216 | "metadata": {},
217 | "source": [
218 | "Netwon's method exhibits quadratic convergence, that is a huge advantage, as the convergence accelerates rapidly near the solution."
219 | ]
220 | },
221 | {
222 | "cell_type": "markdown",
223 | "metadata": {},
224 | "source": [
225 | "\n",
226 | "INTERACTIVE! Run the above snippet of code while varying the initial guess to see how the solution responds. Does this always yield the same value? If so, why? For extreme guesses are any errors encountered? Take a moment and try to reflect on condition(s) under which these may occur.\n",
227 | "
"
228 | ]
229 | },
230 | {
231 | "cell_type": "markdown",
232 | "metadata": {},
233 | "source": [
234 | "# Solving Systems of Nonlinear Algebra Equations \n",
235 | "\n",
236 | "In this section, we introduce the numerical methods to solve systems of nonlinear algebra equations. The general form is given by:\n",
237 | "\n",
238 | "\\\\[R_1(x_1,x_2,\\ldots,x_n)=0\\\\\n",
239 | "R_2(x_1,x_2,\\ldots,x_n)=0\\\\\n",
240 | "\\ \\ \\ \\ \\ \\ \\ \\vdots\\\\\n",
241 | "R_n(x_1,x_2,\\ldots,x_n)=0\\\\]\n",
242 | "\n",
243 | "We need to be careful to write the equations in a form such that they are equal to zero in order to apply the fixed-point methods. The compact notation of the system of nonlinear equations is $\\bf R(\\bf x)=\\bf0$, with $\\bf x$ being the vector of unknowns as $\\mathbf x = (x_1,x_2,\\ldots,x_n)$, and \n",
244 | "\n",
245 | "\\\\[\\mathbf{R}=\\left\\lbrack \\begin{array}{c}\n",
246 | "R_1 \\left(x_1 ,x_2 ,\\dots ,x_n \\right)\\\\\n",
247 | "R_2 \\left(x_1 ,x_2 ,\\dots ,x_n \\right)\\\\\n",
248 | "\\vdots \\\\\n",
249 | "R_n \\left(x_1 ,x_2 ,\\dots ,x_n \\right)\n",
250 | "\\end{array}\\right\\rbrack\\\\]\n",
251 | "\n",
252 | "a vector-valued function which we will refer to as the **residual**.\n",
253 | "\n",
254 | "### Newton-Raphson\n",
255 | "A common method for solving nonlinear systems of equations is the Newton–Raphson method. It relies on the same idea as Newton’s method but is now generalized to n dimensions. The iterative scheme is given by\n",
256 | "\n",
257 | "\\\\[\\mathbf{J}(\\mathbf{x}^{(k)})(\\mathbf{x}^{(k+1)}-\\mathbf{x}^{(k)})=-\\mathbf{R}(\\mathbf{x}^{(k)}),\\\\]\n",
258 | "\n",
259 | "where the $\\mathbf J$ is the Jacobian matrix defined as\n",
260 | "\n",
261 | "\\\\[\\mathbf{J}=\\left\\lbrack \\begin{array}{ccc}\n",
262 | "\\frac{\\partial R_1 }{\\partial x_1 } & \\dots & \\frac{\\partial R_1 }{\\partial x_n }\\\\\n",
263 | "\\vdots & \\ddots & \\vdots \\\\\n",
264 | "\\frac{\\partial R_n }{\\partial x_1 } & \\dots & \\frac{\\partial R_n }{\\partial x_n }\n",
265 | "\\end{array}\\right\\rbrack,\\\\]\n",
266 | "\n",
267 | "that is to be evaluated at the value $\\mathbf{x}^{(k)}$. In component form, the elements of the Jacobian are the partial derivatives $J_{ij}=\\frac{\\partial R_i}{\\partial x_j}$.\n",
268 | "It is common to use a shorthand notation $\\mathbf{\\delta}^{(k+1)}=\\mathbf{x}^{(k+1)}-\\mathbf{x}^{(k)}$ for the difference between the previous values and the new values of $\\mathbf x$. The iterative scheme then becomes the linear system\n",
269 | "\n",
270 | "\\\\[\\mathbf{J}(\\mathbf{x}^{(k)})(\\mathbf{\\delta}^{(k+1)})=-\\mathbf{R}(\\mathbf{x}^{(k)}),\\\\]\n",
271 | "\n",
272 | "\\\\[\\mathbf x^{(k+1)}:=\\mathbf x^{(k)}+\\mathbf{\\delta}^{(k+1)}.\\\\]\n",
273 | "\n",
274 | "We have reduced solving the system of nonlinear equations into an iterative method where, at each step, we need to solve a system of linear equations. Since $\\mathbf{x}^{(k)}$ changes at each time step, we need to solve the linear system for different right-hand-side constant vectors and Jacobian matrices.\n",
275 | "\n",
276 | "The implementation of this method goes through the following steps.\n",
277 | "\n",
278 | "1. Create a vector with the residuals.\n",
279 | "2. Compute the elements in the Jacobian. \n",
280 | "3. Pick an initial guess $\\mathbf{x}^{(0)}$.\n",
281 | "4.Iterate until either $\\| \\mathbf R \\|$ and/or $\\|\\mathbf \\delta\\|$ are small.\n",
282 | "\n",
283 | "**Example 2:** Use Netwon-Raphson to find the root of the system of equations\n",
284 | "\n",
285 | "\\\\[f_1(x_1,x_2)=e^{-x_1}-x_2, \\\\\n",
286 | "f_2(x_1,x_2)=x_1+x_2^2-3x_2, \\\\]\n",
287 | "\n",
288 | "using the initial guess $x_1^{(0)}=0$ and $x_2^{(0)}=0$.\n",
289 | "\n",
290 | "**Solution:**\n",
291 | "The residual vector for this problem is\n",
292 | "\n",
293 | "\\\\[\\mathbf{R}=\\left\\lbrack \\begin{array}{c}\n",
294 | "e^{-x_1 } -x_2 \\\\\n",
295 | "x_1 +x_2^2 -3x_2 \n",
296 | "\\end{array}\\right\\rbrack\\\\]\n",
297 | "\n",
298 | "If we take the logical choice for the vector of unknowns to be $\\mathbf x =(x_1,x_2)$, then the Jacobian is\n",
299 | "\n",
300 | "\\\\[\\mathbf{J}=\\left\\lbrack \\begin{array}{cc}\n",
301 | "-e^{-x_1 } & -1\\\\\n",
302 | "1 & {2x}_2 -3\n",
303 | "\\end{array}\\right\\rbrack\\\\]\n",
304 | "\n",
305 | "We then define corresponding callback functions R_ex2! and J_ex2! which compute the residual and it's jacobian, respectively: "
306 | ]
307 | },
308 | {
309 | "cell_type": "code",
310 | "execution_count": 9,
311 | "metadata": {},
312 | "outputs": [
313 | {
314 | "data": {
315 | "text/plain": [
316 | "J_ex2! (generic function with 1 method)"
317 | ]
318 | },
319 | "execution_count": 9,
320 | "metadata": {},
321 | "output_type": "execute_result"
322 | }
323 | ],
324 | "source": [
325 | "function R_ex2!(out, x)\n",
326 | " out[1] = exp(-x[1]) - x[2]\n",
327 | " out[2] = x[1] + x[2]^2 -3*x[2]\n",
328 | " return nothing\n",
329 | "end\n",
330 | "\n",
331 | "function J_ex2!(out, x)\n",
332 | " out[1,1] = -exp(-x[1])\n",
333 | " out[1,2] = -1\n",
334 | " out[2,1] = 1\n",
335 | " out[2,2] = 2*x[2] - 3\n",
336 | " return nothing\n",
337 | "end"
338 | ]
339 | },
340 | {
341 | "cell_type": "markdown",
342 | "metadata": {},
343 | "source": [
344 | "Then, we implement the iterative algorithm by:"
345 | ]
346 | },
347 | {
348 | "cell_type": "code",
349 | "execution_count": 11,
350 | "metadata": {},
351 | "outputs": [
352 | {
353 | "name": "stdout",
354 | "output_type": "stream",
355 | "text": [
356 | "The solution is (x1,x2) = (0.982752934497163,0.37427931266177783)\n"
357 | ]
358 | }
359 | ],
360 | "source": [
361 | "using LinearAlgebra: norm\n",
362 | "\n",
363 | "\"\"\"\n",
364 | "nonlinear_newton_raphson\n",
365 | "\n",
366 | "Solves for `R(x) = 0` via Newton's method where `R!` is the residual, `J!` is the Jacobian of the residual,\n",
367 | "`x` is an initial guess, `kmax` is the maximum number of iterations, and `tol` is the absolute convergence tolerance.\n",
368 | "This returns a tuple which holds solution `x` and a boolean value `flag` which is true if the algorithm successfully\n",
369 | "converged and false otherwise.\n",
370 | "\"\"\"\n",
371 | "function nonlinear_newton_raphson(R!,J!,x,kmax,tol)\n",
372 | " \n",
373 | " # create intermediate storage\n",
374 | " nx = length(x) \n",
375 | " Rout = zeros(nx)\n",
376 | " Jout = zeros(nx,nx)\n",
377 | " \n",
378 | " R!(Rout, x)\n",
379 | " k = 0\n",
380 | " while norm(Rout) > tol\n",
381 | " J!(Jout, x) # compute Jacobian\n",
382 | " del = -Jout\\Rout # calculate step\n",
383 | " x += del # perform step\n",
384 | " k += 1\n",
385 | " R!(Rout, x) # evaluate residual\n",
386 | " if k > kmax\n",
387 | " println(\"Maximum iterations exceeded.\")\n",
388 | " break\n",
389 | " end\n",
390 | " end\n",
391 | " \n",
392 | " flag = (k > kmax || x == Inf || x == -Inf) ? false : true\n",
393 | " return x, flag\n",
394 | "end\n",
395 | "\n",
396 | "x0 = [0.0; 0.0] # initial guess\n",
397 | "kmax = 100 # max number of iterations allowed\n",
398 | "tol = 1e-5 # convergence tolerance\n",
399 | "x, flag = nonlinear_newton_raphson(R_ex2!,J_ex2!,x0,kmax,tol)\n",
400 | "println(\"The solution is (x1,x2) = ($(x[1]),$(x[2]))\")"
401 | ]
402 | },
403 | {
404 | "cell_type": "markdown",
405 | "metadata": {},
406 | "source": [
407 | "# Case Study: Reactor-Separator-Recycle Process for Chlorination of Benzene \n",
408 | "\n",
409 | "In this section, we consider a reactor-separator-recycle process for chlorination of benzene as illustrated in the following figure:\n",
410 | "\n",
411 | "
\n",
412 | "\n",
413 | "In the continuous stirred-tank reactor (CSTR), benzene $\\text C_6 \\text H_6$ (A) reacts with chlorine $\\text{Cl}_2$ to yield monochlorobenzene $\\text C_6 \\text H_5 \\text{Cl}$ (B) and dichlorobenzene $\\text C_6 \\text H_4 \\text{Cl}_2$ (C). The chlorine $\\text{Cl}_2$ is in excess, thus the reactions can be simplified as:\n",
414 | "\n",
415 | "\\\\[A\\longrightarrow B \\\\]\n",
416 | "\\\\[B\\longrightarrow C \\\\]\n",
417 | "\n",
418 | "The reaction rates are given by:\n",
419 | "\n",
420 | "\\\\[r_1=k_1 \\frac{y_{3,A}}{y_{3,A} \\hat v_A + y_{3,B} \\hat v_B + y_{3,C} \\hat v_C},\\\\\n",
421 | "r_2 =k_2 \\frac{y_{3,B}}{y_{3,A} \\hat v_A + y_{3,B} \\hat v_B + y_{3,C} \\hat v_C}.\\\\]\n",
422 | "\n",
423 | "The reactor volume is $v=6 \\;\\text m^3$, the reaction rate constants are $k_1=0.4\\;\\text h^{-1}$ and $k_2 = 0.055 \\;\\text h^{-1}$, and the specific volumes of each species are $\\hat v_A=8.937\\times10^{-2} \\;\\text m^3/\\text{kmol}$, $\\hat v_B=1.018\\times10^{-1} \\;\\text m^3/\\text{kmol}$, and $\\hat v_C=1.130\\times10^{-1}\\;\\text m^3/\\text{kmol}$. \n",
424 | "\n",
425 | "The purporse is to produce $50\\; \\text{kmol}$ per hour in the outlet stream of the final separator ($F_5=50\\; \\text{kmol/h}$) and we now want to solve for **the mole fractions of each species** ($y_{3,A}$, $y_{3,B}$, and $y_{3,C}$) in the stream $F_3$.\n",
426 | "\n",
427 | "To solve this problem, we analyze the degrees of freedom for this system:\n",
428 | "- Unknowns: $F_1,F_2,F_3,F_4,F_5,F_6,F_7,y_{3,A},y_{3,B},y_{3,C},y_{4,B},y_{4,C},k_1,k_2,\\hat v_A, \\hat v_B, \\hat v_C, v$\n",
429 | "- Process specifications: $F_5,k_1,k_2,\\hat v_A, \\hat v_B, \\hat v_C, v$\n",
430 | "- Degrees of freedom: 18 unkowns - 7 specifications = 11 degrees of freedom\n",
431 | "\n",
432 | "Thus, 11 independent equations are required for solving the system. Based on the conservation of mass, we can set up the system of equations:\n",
433 | "\n",
434 | "- Mixer:\n",
435 | "\n",
436 | "\\\\[ F_1+F_7=F_2 \\\\]\n",
437 | "\n",
438 | "- Reactor:\n",
439 | "\n",
440 | "\\\\[F_2-r_1v=y_{3,A} F_3\\\\\n",
441 | "0=(r_1-r_2)v+y_{3,B} F_3\\\\\n",
442 | "0=r_2v - y_{3,C}F_3\\\\]\n",
443 | "\n",
444 | "- Separator 1: \n",
445 | "\n",
446 | "\\\\[F_3=F_4+F_7\\\\\n",
447 | "y_{3,B}F_3=y_{4,B}F_4\\\\\n",
448 | "y_{3,C}F_3=y_{4,C}F_4\\\\]\n",
449 | "\n",
450 | "- Separator 2:\n",
451 | "\n",
452 | "\\\\[F_4=F_5+F_6\\\\\n",
453 | "y_{4,B}F_4=F_5\\\\]\n",
454 | "\n",
455 | "- Other Relationships\n",
456 | "\n",
457 | "\\\\[y_{3,A}+y_{3,B}+y_{3,C}=1\\\\\n",
458 | "y_{4,B}+y_{4,C}=1\\\\]\n",
459 | "\n",
460 | "Since the compositions are unknown and the reaction rate laws are complex, the system is nonlinear.\n",
461 | "\n",
462 | "### Solutions:\n",
463 | "To solve a nonlinear system of equations, we can use the Newton-Raphson method. The 11 independent equations should be written in residual form by moving every term to one side:\n",
464 | "\n",
465 | "\\\\[\n",
466 | "\\begin{array}{l}\n",
467 | "F_1 -F_2 +F_7 =0 \\\\\n",
468 | "F_2 - k_1 y_{3,A}v / (y_{3,A} {\\hat{v} }_A +y_{3,B} {\\hat{v} }_B +y_{3,C} {\\hat{v} }_C) - y_{3,A} F_3 =0\\\\\n",
469 | "v(k_2 y_{3,B} -k_1 y_{3,A}) / (y_{3,A} {\\hat{v} }_A +y_{3,B} {\\hat{v} }_B +y_{3,C} {\\hat{v} }_C) + y_{3,B} F_3 =0\\\\\n",
470 | "k_2 y_{3,B}v / (y_{3,A} {\\hat{v} }_A +y_{3,B} {\\hat{v} }_B +y_{3,C} {\\hat{v} }_C)-y_{3,C} F_3 =0 \\\\\n",
471 | "F_3 -F_4 -F_7 =0\\\\\n",
472 | "y_{3,B} F_3 -y_{4,B} F_4 =0\\\\\n",
473 | "y_{3,C} F_3 -y_{4,C} F_4 =0\\\\\n",
474 | "F_4 -F_5 -F_6 =0\\\\\n",
475 | "y_{4,B} F_4 -F_5 =0\\\\\n",
476 | "y_{3,A} +y_{3,B} +y_{3,C} -1=0\\\\\n",
477 | "y_{4,B} +y_{4,C} -1=0\n",
478 | "\\end{array}\n",
479 | "\\\\]\n",
480 | "\n",
481 | "We define the variable vector for this problem as $\\mathbf x=(F_1,F_2,y_{3,A},y_{3,B},y_{3,C},F_3,y_{4,B},y_{4,C},F_4,F_6,F_7)$, then the residual vector is constructed as:\n",
482 | "\n",
483 | "\\\\[\\mathbf{R}\\left(\\mathbf{x}\\right)=\\left\\lbrack \\begin{array}{l}\n",
484 | "x_1 -x_2 +x_{11} \\\\\n",
485 | "x_2 -\\frac{k_1 x_3 v}{x_3 \\hat{v}_A +x_4 \\hat{v}_B +x_5 \\hat{v}_C }-x_3 x_6 \\\\\n",
486 | "\\frac{\\left({k_2 x_4 -k}_1 x_3 \\right)v}{x_3 \\hat{v}_A +x_4 \\hat{v}_B +x_5 \\hat{v}_C }+x_4 x_6 \\\\\n",
487 | "\\frac{k_2 x_4 v}{x_3 \\hat{v}_A +x_4 \\hat{v}_B +x_5 \\hat{v}_C }+x_5 x_6 \\\\\n",
488 | "x_6 -x_9 -x_{11} \\\\\n",
489 | "x_4 x_6 -x_7 x_9 \\\\\n",
490 | "x_5 x_6 -x_8 x_9 \\\\\n",
491 | "x_9 -F_5 -x_{10} \\\\\n",
492 | "x_7 x_9 -F_5 \\\\\n",
493 | "x_3 +x_4 +x_5 -1\\\\\n",
494 | "x_7 +x_8 -1\n",
495 | "\\end{array}\\right\\rbrack =\\mathbf{0}\\\\]\n",
496 | "\n",
497 | "Then, we form the Jacobian as:\n",
498 | "\n",
499 | "\\\\[\\mathbf{J}\\left(\\mathbf{x}\\right)=\\left\\lbrack \\begin{array}{ccc}\n",
500 | "\\frac{\\partial R_1 }{\\partial x_1 } & \\dots & \\frac{\\partial R_1 }{\\partial x_{11} }\\\\\n",
501 | "\\vdots & \\ddots & \\vdots \\\\\n",
502 | "\\frac{\\partial R_{11} }{\\partial x_1 } & \\dots & \\frac{\\partial R_{11} }{\\partial x_{11} }\n",
503 | "\\end{array}\\right\\rbrack\\\\]\n",
504 | "\n",
505 | "Most entries in the Jacobian are zero, the non-zero entries are indicated in the code below. \n",
506 | "\n",
507 | "\n",
508 | "INTERACTIVE! Fill in the remaining entries to complete the residual and Jacobian below.\n",
509 | "
"
510 | ]
511 | },
512 | {
513 | "cell_type": "code",
514 | "execution_count": 20,
515 | "metadata": {},
516 | "outputs": [
517 | {
518 | "data": {
519 | "text/plain": [
520 | "J_case! (generic function with 1 method)"
521 | ]
522 | },
523 | "execution_count": 20,
524 | "metadata": {},
525 | "output_type": "execute_result"
526 | }
527 | ],
528 | "source": [
529 | "function R_case!(out,x)\n",
530 | " \n",
531 | " # process specifications and constants\n",
532 | " V = 6 # m^3\n",
533 | " F5 = 25 # kmol/h\n",
534 | " Va = 8.937e-2 # m^3/kmol\n",
535 | " Vb = 1.018e-1 # m^3/kmol\n",
536 | " Vc = 1.13e-1 # m^3/kmol\n",
537 | " k1 = 0.4 # h^-1\n",
538 | " k2 = 0.055 # h^-1\n",
539 | "\n",
540 | " out[1] = x[1] - x[2] + x[11] # (eqn 1) x1 - x2 + x11 = 0\n",
541 | "\n",
542 | " # (eqn 2) x2 - k1*x3*v/(x3*Va + x4*Vb + x5*Vc) - x3*x6 = 0\n",
543 | " out[2] = x[2] - k1*x[3]*V/(x[3]*Va + x[4]*Vb + x[5]*Vc) - x[3]*x[6]\n",
544 | "\n",
545 | " # (eqn 3) (k2*x4 - k1*x3)*v/(x3*Va + x4*Vb + x5*Vc) + x4*x6 = 0\n",
546 | " out[3] = (k2*x[4] - k1*x[3])*V/(x[3]*Va + x[4]*Vb + x[5]*Vc) + x[4]*x[6]\n",
547 | "\n",
548 | " # (eqn 4) k2*x4*v/(x3*Va + x4*Vb + x5*Vc) - x5*x6 = 0\n",
549 | " out[4] = k2*x[4]*V/(x[3]*Va + x[4]*Vb + x[5]*Vc) - x[5]*x[6]\n",
550 | "\n",
551 | " out[5] = x[6] - x[9] - x[11] # (eqn 5) x6 - x9 - x11 = 0\n",
552 | " out[6] = x[4]*x[6] - x[7]*x[9] # (eqn 6) x4*x6 - x7*x9 = 0\n",
553 | " out[7] = x[5]*x[6] - x[8]*x[9] # (eqn 7) x5*x6 - x8*x9 = 0\n",
554 | " out[8] = x[9] - F5 - x[10] # (eqn 8) x9 - F5 - x10 = 0\n",
555 | " out[9] = x[7]*x[9] - F5 # (eqn 9) x7*x9 - F5 = 0\n",
556 | " out[10] = x[3] + x[4] + x[5] - 1 # (eqn 10) x3 + x4 + x5 - 1 = 0\n",
557 | " out[11] = x[7] + x[8] - 1 # (eqn 11) x7 + x8 - 1 = 0\n",
558 | " \n",
559 | " return nothing\n",
560 | "end\n",
561 | "\n",
562 | "function J_case!(out,x)\n",
563 | " \n",
564 | " # process specifications and constants\n",
565 | " V = 6 # m^3\n",
566 | " F5 = 25 # kmol/h\n",
567 | " Va = 8.937e-2 # m^3/kmol\n",
568 | " Vb = 1.018e-1 # m^3/kmol\n",
569 | " Vc = 1.13e-1 # m^3/kmol\n",
570 | " k1 = 0.4 # h^-1\n",
571 | " k2 = 0.055 # h^-1\n",
572 | "\n",
573 | " # Set all values in out to zero\n",
574 | " fill!(out, 0.0)\n",
575 | " \n",
576 | " out[1,1] = 1; out[1,2] = -1; out[1,11] = 1 # dR1/dx1, dR1/dx2, and dR1/dx11\n",
577 | "\n",
578 | " # dR2/dx2, dR2/dx3, dR2/dx4, dR2/dx5, and dR2/dx\n",
579 | " out[2,2] = 1\n",
580 | " out[2,3] = -(k1*V*(x[3]*Va + x[4]*Vb + x[5]*Vc) - k1*x[3]*V*Va)/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2 - x[6]\n",
581 | " out[2,4] = k1*x[3]*V*Vb/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
582 | " out[2,5] = k1*x[3]*V*Vc/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
583 | " out[2,6] = -x[3]\n",
584 | "\n",
585 | " \n",
586 | " # dR3/dx3, dR3/dx4, dR3/dx5, and dR3/dx6\n",
587 | " out[3,3] = (-k1*V*(x[3]*Va + x[4]*Vb + x[5]*Vc) - (k2*x[4] - k1*x[3])*V*Va)/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
588 | " out[3,4] = (k2*V*(x[3]*Va + x[4]*Vb + x[5]*Vc) - (k2*x[4] - k1*x[3])*V*Vb)/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2 + x[6]\n",
589 | " out[3,5] = -(k2*x[4] - k1*x[3])*V*Vc/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
590 | " out[3,6] = x[4]\n",
591 | " \n",
592 | " # dR4/dx3, dR4/dx4, dR4/dx5, and dR4/dx6\n",
593 | " out[4,3] = -k2*x[4]*V*Va/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
594 | " out[4,4] = (k2*V*(x[3]*Va + x[4]*Vb + x[5]*Vc) - k2*x[4]*V*Vb)/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2\n",
595 | " out[4,5] = -k2*x[4]*V*Vc/(x[3]*Va + x[4]*Vb + x[5]*Vc)^2 - x[6]\n",
596 | " out[4,6] = -x[5]\n",
597 | "\n",
598 | " # dR5/dx6, dR5/dx9, and dR5/dx11\n",
599 | " out[5,6] = 1; out[5,9] = -1; out[5,11] = -1\n",
600 | "\n",
601 | " # dR6/dx4, dR6/dx6, dR6/dx7, and dR6/dx9\n",
602 | " out[6,4] = x[6]; out[6,6] = x[4]; out[6,7] = -x[9]; out[6,9] = -x[7]\n",
603 | "\n",
604 | " # dR7/dx5, dR7/dx6, dR7/dx8, and dR7/dx9\n",
605 | " out[7,5] = x[6]; out[7,6] = x[5]; out[7,8] = -x[9]; out[7,9] = -x[8]\n",
606 | "\n",
607 | " out[8,9] = 1; out[8,10] = -1 # dR8/dx9, dR8/dx10\n",
608 | " out[9,7] = x[9]; out[9,9] = x[7] # dR9/dx7, dR9/dx9\n",
609 | " out[10,3] = 1; out[10,4] = 1; out[10,5] = 1 # dR10/dx3, dR10/dx4, dR10/dx5\n",
610 | " out[11,7] = 1; out[11,8] = 1 # dR11/dx7 and dR11/dx8\n",
611 | " \n",
612 | " return nothing\n",
613 | "end"
614 | ]
615 | },
616 | {
617 | "cell_type": "markdown",
618 | "metadata": {},
619 | "source": [
620 | "Then, we implement the Newton-Raphson algorithm by:"
621 | ]
622 | },
623 | {
624 | "cell_type": "code",
625 | "execution_count": 21,
626 | "metadata": {},
627 | "outputs": [
628 | {
629 | "name": "stdout",
630 | "output_type": "stream",
631 | "text": [
632 | "The mole fraction of each species are\n",
633 | "y_{3,A} = 0.9454833705152079\n",
634 | "y_{3,B} = 0.05408780736715945\n",
635 | "y_{3,C} = 0.00042882211763266596\n"
636 | ]
637 | }
638 | ],
639 | "source": [
640 | "x0 = [35.7; 357; 0.9; 0.07; 0.03; 357; 0.7; 0.3; 35.7; 10.7; 321.3]\n",
641 | "tol = 1e-4\n",
642 | "kmax = 100\n",
643 | "x,flag = nonlinear_newton_raphson(R_case!,J_case!,x0,kmax,tol)\n",
644 | "println(\"The mole fraction of each species are\")\n",
645 | "println(\"y_{3,A} = $(x[3])\") \n",
646 | "println(\"y_{3,B} = $(x[4])\")\n",
647 | "println(\"y_{3,C} = $(x[5])\")"
648 | ]
649 | },
650 | {
651 | "cell_type": "markdown",
652 | "metadata": {},
653 | "source": [
654 | "
\n",
655 | "\n",
656 | "# Question(s) for reflection \n",
657 | "\n",
658 | "- The Newton-Raphson algorithm makes use of derivative information to compute the direction of steepest descent by solving a linear system of equations. One common issue encountered when applying this approach is potentially overshooting the soluton $\\mathbf{x} = \\mathbf{x}^*$. How might one solve this issue?"
659 | ]
660 | }
661 | ],
662 | "metadata": {
663 | "kernelspec": {
664 | "display_name": "Julia 1.7.2",
665 | "language": "julia",
666 | "name": "julia-1.7"
667 | },
668 | "language_info": {
669 | "file_extension": ".jl",
670 | "mimetype": "application/julia",
671 | "name": "julia",
672 | "version": "1.7.2"
673 | }
674 | },
675 | "nbformat": 4,
676 | "nbformat_minor": 4
677 | }
678 |
--------------------------------------------------------------------------------
/tutorial/julia/Tutorial 3 - ODEs, Initial Value Problems/CSTR_pic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/julia/Tutorial 3 - ODEs, Initial Value Problems/CSTR_pic.png
--------------------------------------------------------------------------------
/tutorial/julia/Tutorial 4 - ODEs, Boundary Value Problems/J4_ODEBVPsystems.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Ordinary Differential Equations, Boundary Value Problems \n",
8 | "\n",
9 | "
\n",
10 | "\n",
11 | "# Learning Objectives \n",
12 | "\n",
13 | "### ODE boundary value problems (BVPs)\n",
14 | "- Be able to distinguish bewteen ODE IVPs and BVPs\n",
15 | "- Be able to formulate three common types of boundary conditions: Dirichlet, Neumann, Robin\n",
16 | "\n",
17 | "### Finite difference method (FDM)\n",
18 | "- Be able to derive forward, backward, and centered finite difference approximations for the first derivatives\n",
19 | "- Be able to derive centered finite difference approximations for the second derivatives\n",
20 | "- Be able to state the truncation errors for different finite difference approximations\n",
21 | "\n",
22 | "### Solving ODE BVPs\n",
23 | "- Demonstrate the ability to discretize ODE BVPs into a set of algebraic equations\n",
24 | "- Be able to deal with Dirichlet boundary conditions\n",
25 | "- Be able to deal with Neumann and Robin boundary conditions by making fictious nodes\n",
26 | "- Solve algebraic equations using appropriate numerical methods (Gauss-Siedel for linear systems or Newton-Raphson for nonlinear systems)\n",
27 | "\n",
28 | "### Solving coupled BVPs*\n",
29 | "- Be able to solve coupled BVPs by interlacing the unknown dependent variables\n",
30 | "- Be able to identify the bandwidth of the Jacobin matrix of the reformulated equations\n",
31 | "\n",
32 | "* Not covered in tutorial\n",
33 | "\n",
34 | "
"
35 | ]
36 | },
37 | {
38 | "cell_type": "markdown",
39 | "metadata": {},
40 | "source": [
41 | "\n",
42 | "INTERACTIVE! Before starting this example, import the Plots.jl library by running the cell below.\n",
43 | "
"
44 | ]
45 | },
46 | {
47 | "cell_type": "code",
48 | "execution_count": null,
49 | "metadata": {},
50 | "outputs": [],
51 | "source": [
52 | "import Pkg; Pkg.add(\"Plots\"); using Plots"
53 | ]
54 | },
55 | {
56 | "cell_type": "markdown",
57 | "metadata": {},
58 | "source": [
59 | "In the last tutorial, we introduced the general ODE form,\n",
60 | "\n",
61 | "\\\\[F\\left(x,y,\\frac{dy}{dx},\\frac{d^2y}{dx^2},\\ldots,\\frac{d^ny}{dx^n}\\right)=0,\\\\]\n",
62 | "\n",
63 | "which when combined with initial conditions, formed the ODE-IVP problem, that we then proceeded to solve.\n",
64 | "\n",
65 | "In this unit, we consider ODE problems in which $n$ auxiliary conditions are specified at different values of the independent variable $x$ rather than just the specification of the initial condition. Such conditions are termed boundary conditions and the resulting problems are called boundary value problems (BVPs). In most of the problems of interest in chemical engineering the ODE will be of the form,\n",
66 | "\n",
67 | "\\\\[\\frac{d^2y}{dx^2}=f\\left(x,y,\\frac{dy}{dx}\\right),\\\\]\n",
68 | "\n",
69 | "where $x$ belongs to an interval $[x_l,x_r]$. The positions $x=x_l$ and $x=x_r$ define the boundaries of the spatial domain of interest. The three common types of boundary conditions are as follows:\n",
70 | "- **Dirichlet** (boundary condition of the first kind): For this type, we specify the value of $y$ on the boundary, e.g. $y(x_l)=\\alpha$.\n",
71 | "- **Neumann** (boundary condition of the second kind): For this type, we specify the derivative of $y$ on the boundary, e.g. $\\left. \\frac{dy}{dx} \\right |_{x_l} = \\alpha$.\n",
72 | "- **Robin** (boundary condition of the third kind): This type involves a combination of $y$ and the derivative of $y$ on the boundary, e.g. $\\alpha_1 \\frac{dy}{dx} + \\alpha_2 y(x_r) = \\alpha_3$.\n",
73 | "\n",
74 | "## Finite Difference Method\n",
75 | "The finite difference method is a common approach for solving ODE-BVPs. The first step to apply the finite difference method, is to discretize the spatial domain. This begins by defining $n$ (often equidistant) nodes $x_1,\\ldots,x_n$. The discretization step is then given by\n",
76 | "\n",
77 | "\\\\[\\Delta x = \\frac{x_r-x_l}{n-1}.\\\\]\n",
78 | "\n",
79 | "First-order derivatives and second-order derivatives appearing in $F$ will be replaced with finite difference approximations. Common finite difference approximations for the first-order derivatives are list below.\n",
80 | "- Forward difference approximation: $\\left.\\frac{dy}{dx}\\right |_{x_i}=\\frac{y_{i+1}-y_i}{\\Delta x}$ \n",
81 | "- Backward difference approximation: $\\left.\\frac{dy}{dx}\\right |_{x_i}=\\frac{y_{i}-y_{i-1}}{\\Delta x}$ \n",
82 | "- Centered finite difference approximation: $\\left.\\frac{dy}{dx}\\right |_{x_i}=\\frac{y_{i+1}-y_{i-1}}{2\\Delta x}$\n",
83 | "The centered finite difference approximation is the most common finite difference approximation used for second-order derivatives and is defined as\n",
84 | "\n",
85 | "\\\\[\\left.\\frac{d^2y}{dx^2}\\right |_{x_i}=\\frac{y_{i+1} +2y_i -y_{i-1}}{\\Delta x^2}.\\\\]\n",
86 | "\n",
87 | "## Solving ODE-BVPs\n",
88 | "After the spatial discretization and the derivation of the finite difference approximations, we can proceed with the transformation of the ODE-BVP into a set of algebraic equations. At each interior node, we use the centered finite difference approximations to convert the ODE $\\frac{d^2y}{dx^2}=f(x,y,\\frac{dy}{dx})$ into the discretized form\n",
89 | "\n",
90 | "\\\\[\\frac{y_{i+1} - 2y_i +y_{i-1}}{\\Delta x^2}=f(x_i,y_i,\\frac{y_{i+1} - y_{i-1}}{2 \\Delta x}).\\\\]\n",
91 | "\n",
92 | "The result is a set of algebraic equations in $n-2$ unknowns. The remaining two equations will come from the boundary conditions. The resulting set of (in general, nonlinear) algebraic equations will have to be solved simultaneously to obtain the values of the unknowns. An outline for general approach to solving BVPs is:\n",
93 | "1. Discretize the domain into n nodes.\n",
94 | "2. For the interior nodes, discretize the ODE using finite difference approximations of the derivatives.\n",
95 | "3. For the boundary nodes, if there is a Dirichlet boundary, set the boundary node to the specified value. Otherwise, make a fictitious node and discretize both the boundary condition and the ODE.\n",
96 | "4. If the problem is linear, solve using any method the resulting system $\\mathbf A \\mathbf x = \\mathbf b$. If the problem is nonlinear, form the Jacobian and solve by Newton–Raphson.\n",
97 | "5. Check for mesh refinement.\n",
98 | "\n",
99 | "When solving ODE-BVPs, it is very important to apply the appropriate procedure for dealing with each different of type of **boundary condition**.\n",
100 | "When a **Dirichlet** boundary condition is present, the value of the boundary node is a specified, e.g. if we have $y(x_l)=\\alpha$, then we set $y_1=\\alpha$.\n",
101 | "When a **Neumann** and **Robin** boundary conditions with a derivative term is present, extra consideration is merited. When using the centered finite difference method for approximations, a fictious node should be introduced in order to to keep the same level of accuracy at the boundary. \n",
102 | "Consider a Neumann boundary condition at the left boundary $\\left. \\frac{dy}{dx} \\right |_{x_l} = \\alpha$, we need to introduce a fictious node $x_0$ on the left of $x_l$, with a corresponding value of $y(x=x_0)=y_0$. Subsequently, we implement the centered finite difference approximation of the boundary condition:\n",
103 | "\n",
104 | "\\\\[\\frac{y_2-y_1}{2\\Delta x} = \\alpha,\\\\]\n",
105 | "\n",
106 | "which we can solve for the value of the fictitious node,\n",
107 | "\n",
108 | "\\\\[y_0 = y_2 - 2 \\alpha \\Delta x.\\\\]\n",
109 | "\n",
110 | "We now substitute this result into the differential equation at $x_1$.\n",
111 | "\n",
112 | "## Case Study: Laminar Flow\n",
113 | "Suppose we have laminar flow of water, at 20 °C, through a horizontal cylindrical tube.\n",
114 | "\n",
115 | "\n",
116 | "
\n",
117 | "\n",
118 | "\n",
119 | "Assume that we only consider the kinematics in the z-direction, and the flow is fully developed and at steady state. Then we can have the equation:\n",
120 | "\n",
121 | "\\\\[0=-\\frac{\\partial P}{\\partial z} + \\mu \\left [ \\frac{1}{r} \\frac{\\partial}{\\partial r} \\left(r \\frac{\\partial u_z}{\\partial r} \\right) \\right ]. \\\\]\n",
122 | "\n",
123 | "We can rearrange and simplify the above equation as\n",
124 | "\n",
125 | "\\\\[\\frac{1}{r} \\frac{\\partial u_z}{\\partial r} + \\frac{\\partial^2 u_z}{\\partial r^2} = \\frac{1}{\\mu} \\frac{\\partial P}{\\partial z}.\\\\]\n",
126 | "\n",
127 | "The viscosity of water at $20 ^\\circ \\text C$ is $\\mu = 8.9\\times 10^{-4} \\; \\text{Pa} \\cdot \\text s$, and the pressure drop through the pipe is $\\frac{\\partial P}{\\partial z} = -1000 \\; \\text{Pa}/\\text{m}$. We plug in this information and the equation simplifed to\n",
128 | "\n",
129 | "\\\\[\\frac{1}{r} \\frac{\\partial u_z}{\\partial r} + \\frac{\\partial^2 u_z}{\\partial r^2} = \\frac{-1000}{8.9\\times 10^{-4}}.\\\\]\n",
130 | "\n",
131 | "At the inner surface of the pipe, a no-slip boundary condition holds, which is a Dirichlet condition:\n",
132 | "\n",
133 | "\\\\[u_z|_{r=R} = 0.\\\\]\n",
134 | "\n",
135 | "At the center of the pipe, a no-flux boundary condition holds due to symmetry, which is a Neumann condition:\n",
136 | "\n",
137 | "\\\\[\\left. \\frac{du_z}{dr} \\right |_{r=0} = 0.\\\\]\n",
138 | "\n",
139 | "We may now solve for the $z$-direction velocity $u_z$ profile.\n",
140 | "\n",
141 | "\n",
142 | "## Solution:\n",
143 | "We first discretize the spatial domain into $n$ nodes, $r_1,\\ldots,r_n$, with step size $\\Delta r =R/(n-1)$.\n",
144 | "\n",
145 | "\n",
146 | "
\n",
147 | "\n",
148 | "\n",
149 | "Then, we use centered finite difference approximations for the derivative terms at each interior node:\n",
150 | "\n",
151 | "\\\\[\\left. \\frac{d u_z}{dr} \\right|_{r_i} \\approx \\frac{u_{z,i+1} - u_{z,i-1}}{\\Delta r},\\\\]\n",
152 | "\n",
153 | "\\\\[\\left. \\frac{d^2 u_z}{dr^2} \\right|_{r_i} \\approx \\frac{u_{z,i+1} - 2 u_{z,i} + u_{z,i-1}}{\\Delta r^2}.\\\\]\n",
154 | "\n",
155 | "Thus, the equations for interior nodes become\n",
156 | "\n",
157 | "\\\\[\\frac{1}{r_i} \\frac{u_{z,i+1} - u_{z,i-1}}{\\Delta r}+\\frac{u_{z,i+1} - 2 u_{z,i} + u_{z,i-1}}{\\Delta r^2}=\\frac{-1000}{8.9\\times 10^{-4}}.\\\\]\n",
158 | "\n",
159 | "For the boundary nodes, at $r=R$ where the Dirchlet boundary condition is applied, we have\n",
160 | "\n",
161 | "\\\\[u_{z,n}=0.\\\\]\n",
162 | "\n",
163 | "At $r=0$ where the Neumann boundary condition is applied, a fictious node $u_{z,0}$ is introduced, thus we have\n",
164 | "\n",
165 | "\\\\[\\frac{u_{z,2}-{u_{z,0}}}{\\Delta r}=0.\\\\]\n",
166 | "\n",
167 | "Solving for the fictious node, we get $u_{z,0} = u_{z,2}$. The equation for the first node has to be modified because the radius is equal to zero at that node. We apply L’Hôpital’s rule:\n",
168 | "\n",
169 | "\\\\[\\lim_{r \\to 0} \\frac{1}{r} \\frac{d u_z}{dr} = \\lim_{r \\to 0} \\frac{\\frac{d}{dr}\\left(\\frac{du_z}{dr}\\right)}{\\frac{d}{dr}(r)} = \\lim_{r\\to 0}\\frac{d^2 u_z}{dr^2}.\\\\]\n",
170 | "\n",
171 | "So the equation at $r=0$ becomes $2\\frac{d^2 u_z}{dr^2} = \\frac{-1000}{8.9\\times 10^{-4}}$.\n",
172 | "We apply centered finite differencing and plug the fictious node into the first equation to obtain\n",
173 | "\n",
174 | "\\\\[2\\cdot\\frac{2u_{z,2}-2u_{z,1}}{\\Delta r^2}=\\frac{-1000}{8.9\\times 10^{-4}}. \\\\]\n",
175 | "\n",
176 | "Now, we have a system of linear equations $\\mathbf A \\mathbf u_z =\\mathbf b$ and we can solve for the velocity profile."
177 | ]
178 | },
179 | {
180 | "cell_type": "code",
181 | "execution_count": 6,
182 | "metadata": {},
183 | "outputs": [
184 | {
185 | "name": "stdout",
186 | "output_type": "stream",
187 | "text": [
188 | "done\n"
189 | ]
190 | }
191 | ],
192 | "source": [
193 | "# process specifications and constants\n",
194 | "mu = 8.9e-4 # Pa*s\n",
195 | "dPdz = -1000 # Pa/m\n",
196 | "delta_r = 0.001 # m\n",
197 | "r = range(0.0, 0.05, step = delta_r) # m\n",
198 | "println(\"done\")"
199 | ]
200 | },
201 | {
202 | "cell_type": "markdown",
203 | "metadata": {},
204 | "source": [
205 | "\n",
206 | "INTERACTIVE! Now, we enter the discretized system of linear algebraic equations in matrix-vector form.\n",
207 | "You should write the equation for the first node (center of the pipe) by yourself..\n",
208 | "
"
209 | ]
210 | },
211 | {
212 | "cell_type": "code",
213 | "execution_count": null,
214 | "metadata": {},
215 | "outputs": [],
216 | "source": [
217 | "# creating a matrix and vector for the equations\n",
218 | "n = length(r)\n",
219 | "A = zeros(n,n)\n",
220 | "b = zeros(n)\n",
221 | "\n",
222 | "### FILL IN THE ENTRIES OF A[1,i] and b[1] for i = 1,...,n by yourself here ###\n",
223 | "# interior nodes\n",
224 | "for i = 2:n-1\n",
225 | " A[i,i-1] = -1/(r[i]*2*delta_r) + 1/delta_r^2\n",
226 | " A[i,i] = -2/delta_r^2\n",
227 | " A[i,i+1] = 1/(r[i]*2*delta_r) + 1/delta_r^2\n",
228 | " b[i] = dPdz/mu\n",
229 | "end\n",
230 | "\n",
231 | "# last node\n",
232 | "A[n,n] = 1\n",
233 | "b[n] = 0\n",
234 | "\n",
235 | "# solving the matrix for the velocity profile\n",
236 | "u = A\\b\n",
237 | "\n",
238 | "# plot of velocity profile\n",
239 | "plt = plot(u,r,label=\"\")\n",
240 | "plot!(plt,u,-r,label=\"\", title= \"Velocity Profile\")\n",
241 | "xlabel!(\"Velocity (m/s)\")\n",
242 | "ylabel!(\"Radius (m)\")"
243 | ]
244 | },
245 | {
246 | "cell_type": "markdown",
247 | "metadata": {},
248 | "source": [
249 | "
\n",
250 | "\n",
251 | "# Questions for reflection \n",
252 | "\n",
253 | "- How can you tell if the spatial discretization chosen for this example was small enough?\n",
254 | "- In general, a number of higher-order spatial discretizations may be used when discretizing a problem (see [slide 9 here](https://www.mathematik.uni-dortmund.de/~kuzmin/cfdintro/lecture4.pdf)). What potential trade-offs may occur when using a higher-order accuracy scheme to discretize the problem solved in the=is tutorial? "
255 | ]
256 | },
257 | {
258 | "cell_type": "code",
259 | "execution_count": null,
260 | "metadata": {},
261 | "outputs": [],
262 | "source": []
263 | }
264 | ],
265 | "metadata": {
266 | "kernelspec": {
267 | "display_name": "Julia 1.5.1",
268 | "language": "julia",
269 | "name": "julia-1.5"
270 | },
271 | "language_info": {
272 | "file_extension": ".jl",
273 | "mimetype": "application/julia",
274 | "name": "julia",
275 | "version": "1.5.1"
276 | }
277 | },
278 | "nbformat": 4,
279 | "nbformat_minor": 4
280 | }
281 |
--------------------------------------------------------------------------------
/tutorial/julia/Tutorial 4 - ODEs, Boundary Value Problems/pipe1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/julia/Tutorial 4 - ODEs, Boundary Value Problems/pipe1.png
--------------------------------------------------------------------------------
/tutorial/julia/Tutorial 4 - ODEs, Boundary Value Problems/pipe2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/julia/Tutorial 4 - ODEs, Boundary Value Problems/pipe2.png
--------------------------------------------------------------------------------
/tutorial/julia/Tutorial 5 - Partial Differential Equations/pyrometer_pde_pic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/julia/Tutorial 5 - Partial Differential Equations/pyrometer_pde_pic.png
--------------------------------------------------------------------------------
/tutorial/julia/Tutorial 6 - Optimization/J6_Optimization.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Optimization \n",
8 | "\n",
9 | "
\n",
10 | "\n",
11 | "## Learning Objectives \n",
12 | "\n",
13 | "- Classify optimization problems as linear, quadratic, nonlinear, and mixed-integer problems.\n",
14 | "- Learn how to interpret the solutions of optimization problems.\n",
15 | "- Learn how to input optimization problems in Julia/JuMP).\n",
16 | "- Assess the sensitivity of a solution with respect to constraints.\n",
17 | "- Learn how to formulate and solve optimization problems for multiple examples.\n",
18 | "\n",
19 | "
\n",
20 | "\n",
21 | "## Organization \n",
22 | "\n",
23 | "- First, we'll review some of the types of optimization problems.\n",
24 | "- Next, we'll solve a Sudoku puzzle.\n",
25 | "- You'll formulate and solve a simple blending and pooling problem.\n",
26 | "- Lastly, we'll analyze the blending and pooling problem from a sensitivity perspective.\n",
27 | " \n",
28 | "
\n",
29 | "\n",
30 | "## Problem Forms \n",
31 | "\n",
32 | "Optimizers generally exploit a specific mathematical structure in the problem formulation as a means to solve a problem. In fact, some underlying assumptions are always required to ensure a problem is optimized. One common example arrise from combining KKT conditions with constraint qualifications or alternatively regularity conditions for nonlinear programs.\n",
33 | "\n",
34 | "These problem types range from linear programs for which problems with 100k variables and constraints may routinely be solved [in less than a minute](http://plato.asu.edu/ftp/lpsimp.html) to continuous convex problems to nonconvex nonlinear mixed-integer problems (were some problems with fewer than 5 variables have yet to be solved). As such, using an appropriate optimizer for a particular problem type can transform an unsolveable problem into a trival one. The most heavily studied and widely used descriptors for each program are linear, quadratic, nonlinear, and mixed-integer.\n",
35 | "\n",
36 | "**Types of based on objectives/constraints:**\n",
37 | "\n",
38 | "- **Linear:** All the constraints and the objective are linear.\n",
39 | "- **Quadratic** all the constraints and the objective are quadratic or linear.\n",
40 | "- **Nonlinear** at least one constraint or the objective is not linear.\n",
41 | "\n",
42 | "**Types based on manner of variable used:**\n",
43 | "\n",
44 | "- **Continuous:** All variables can vary between values in some possibly open interval.\n",
45 | "- **Integer:** All variables are integer valued.\n",
46 | "- **Mixed-integer:** The problem contains both integer and continuous variables.\n",
47 | "\n",
48 | "One of the central focuses of optimization research has focused on developing specialized routines for solving programs with other special forms. A number of other important forms include [specially-ordered sets](https://link.springer.com/article/10.1007/BF01589393), [second-order cones](https://ieeexplore.ieee.org/abstract/document/6983691), [equilibrium constraints](https://faculty.wcas.northwestern.edu/~lchrist/Ferris_mathematical_programs2.pdf), as well as a myriad of special nonlinear forms. When attempting to solve a difficult model it's worth checking a few references (such as [Mosek's Modeling Cookbook](https://docs.mosek.com/MOSEKModelingCookbook-letter.pdf)) to see if the problem can be re-written as simplier form or a simplier problem type.\n",
49 | "\n",
50 | "This trend has lead to the development of a number of specialized languages and software packages used to describe optimization problems interface with a myriad of different optimizers. In Python, the main tools for this are [Pyomo]() and [CVXOPT](https://cvxopt.org/). In Julia, we have [JuMP](https://jump.dev/JuMP.jl/stable/), and [Convex.jl](https://jump.dev/Convex.jl/stable/). A number of other commercial offerings available include [GAMS](https://www.gams.com/), [AIMMS](https://en.wikipedia.org/wiki/AIMMS), and [AMPL](https://ampl.com/) along with a number of offerings associated with commercially available solvers such as [CPLEX Optimization Studio by IBM](https://www.ibm.com/analytics/cplex-optimizer) or [Xpress Mosel for FICO Express](https://www.fico.com/en/products/fico-xpress-optimization). We'll work through formulating and solving a couple of optimization problems using JuMP in this workbook.\n",
51 | "\n",
52 | "
"
53 | ]
54 | },
55 | {
56 | "cell_type": "code",
57 | "execution_count": 1,
58 | "metadata": {},
59 | "outputs": [
60 | {
61 | "name": "stderr",
62 | "output_type": "stream",
63 | "text": [
64 | "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m registry at `C:\\Users\\wilhe\\.julia\\registries\\General`\n",
65 | "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n",
66 | "\u001b[32m\u001b[1mNo Changes\u001b[22m\u001b[39m to `C:\\Users\\wilhe\\Project.toml`\n",
67 | "\u001b[32m\u001b[1mNo Changes\u001b[22m\u001b[39m to `C:\\Users\\wilhe\\Manifest.toml`\n",
68 | "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n",
69 | "\u001b[32m\u001b[1mNo Changes\u001b[22m\u001b[39m to `C:\\Users\\wilhe\\Project.toml`\n",
70 | "\u001b[32m\u001b[1mNo Changes\u001b[22m\u001b[39m to `C:\\Users\\wilhe\\Manifest.toml`\n",
71 | "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n",
72 | "\u001b[32m\u001b[1mNo Changes\u001b[22m\u001b[39m to `C:\\Users\\wilhe\\Project.toml`\n",
73 | "\u001b[32m\u001b[1mNo Changes\u001b[22m\u001b[39m to `C:\\Users\\wilhe\\Manifest.toml`\n"
74 | ]
75 | }
76 | ],
77 | "source": [
78 | "# Run this block once. Adds Julia packages to your Julia local installation from the package manager\n",
79 | "\n",
80 | "using Pkg # Import functions from the package manager into this session.\n",
81 | "Pkg.add(\"JuMP\") # A modeling language for Mathematical optimization in Julia.\n",
82 | "Pkg.add(\"Ipopt\") # A highly performant nonlinear optimizer.\n",
83 | "Pkg.add(\"GLPK\") # An open-source linear and mixed-integer linear optimizer\n",
84 | "\n",
85 | "nothing # suppresses long output on last line"
86 | ]
87 | },
88 | {
89 | "cell_type": "markdown",
90 | "metadata": {},
91 | "source": [
92 | "
\n",
93 | "\n",
94 | "## Solving Sudoku with Mixed-Integer Programming* \n",
95 | "\n",
96 | "* Adapted from a now defunct example by Ian Dunning at https://www.juliaopt.org/notebooks/JuMP-Sudoku.html.\n",
97 | "\n",
98 | "Sudoku is a popular number puzzle. The goal is to place the digits 1,...,9 on a nine-by-nine grid, with some of the digits already filled in. Your solution must satisfy the following rules:\n",
99 | "\n",
100 | " The numbers 1 to 9 must appear in each 3x3 square\n",
101 | " The numbers 1 to 9 must appear in each row\n",
102 | " The numbers 1 to 9 must appear in each column\n",
103 | "\n",
104 | "Any combination satisfying the above rules is a valid solution. Thus, we're simply seeking any feasible solution. While this isn't strictly an optimization problem (it's a constraint satisfaction problem), but we can make it into equivalent optimization problem. Namely, we can just specify that the objective is some constant value of our choosing. \n",
105 | "\n",
106 | "We can model this problem using 0-1 integer programming: a problem where all the decision variables are binary. We'll use JuMP to create the model, and then we can solve it with any integer optimizer."
107 | ]
108 | },
109 | {
110 | "cell_type": "code",
111 | "execution_count": 2,
112 | "metadata": {},
113 | "outputs": [],
114 | "source": [
115 | "using JuMP, GLPK # import functions from the JuMP & GLPK package into your current environment\n",
116 | "\n",
117 | "# create a model called \"sudoku\" which holds all variables, constraints, \n",
118 | "# functions, and options needed to solve the sudoku problem\n",
119 | "sudoku = Model(GLPK.Optimizer)\n",
120 | "\n",
121 | "# add binary variables\n",
122 | "@variable(sudoku, x[i=1:9, j=1:9, k=1:9], Bin) # Bin indicates the variable is binary (has a value of 0 or 1)\n",
123 | "\n",
124 | "nothing"
125 | ]
126 | },
127 | {
128 | "cell_type": "markdown",
129 | "metadata": {},
130 | "source": [
131 | "Only one digit can be in each square. So for each square represented by a pair of i,j variables the sum over k should be equal to one."
132 | ]
133 | },
134 | {
135 | "cell_type": "code",
136 | "execution_count": 3,
137 | "metadata": {},
138 | "outputs": [],
139 | "source": [
140 | "for i = 1:9, j = 1:9 \n",
141 | " @constraint(sudoku, sum(x[i,j,k] for k=1:9) == 1)\n",
142 | "end"
143 | ]
144 | },
145 | {
146 | "cell_type": "markdown",
147 | "metadata": {},
148 | "source": [
149 | "Each variable can only appear once in each column."
150 | ]
151 | },
152 | {
153 | "cell_type": "code",
154 | "execution_count": 4,
155 | "metadata": {},
156 | "outputs": [],
157 | "source": [
158 | "for j = 1:9, k = 1:9\n",
159 | " @constraint(sudoku, sum(x[i,j,k] for i=1:9) == 1)\n",
160 | "end"
161 | ]
162 | },
163 | {
164 | "cell_type": "markdown",
165 | "metadata": {},
166 | "source": [
167 | "\n",
168 | "INTERACTIVE! Each variable can only appear once in each row. Fill in the appropriate expression.\n",
169 | "
"
170 | ]
171 | },
172 | {
173 | "cell_type": "code",
174 | "execution_count": 5,
175 | "metadata": {},
176 | "outputs": [],
177 | "source": [
178 | "for ind = 1:9, k = 1:9\n",
179 | " # ADD CODE HERE\n",
180 | "end"
181 | ]
182 | },
183 | {
184 | "cell_type": "markdown",
185 | "metadata": {},
186 | "source": [
187 | "Each variable can only appear once in each 3-by-3 subgrid."
188 | ]
189 | },
190 | {
191 | "cell_type": "code",
192 | "execution_count": 6,
193 | "metadata": {},
194 | "outputs": [],
195 | "source": [
196 | "# i is the top left row, j is the top left column\n",
197 | "# We'll sum from i to i+2, e.g. i=4, r=4, 5, 6\n",
198 | "for i = 1:3:7, j = 1:3:7, k = 1:9\n",
199 | " @constraint(sudoku, sum(x[r,c,k] for r=i:i+2, c=j:j+2) == 1)\n",
200 | "end"
201 | ]
202 | },
203 | {
204 | "cell_type": "markdown",
205 | "metadata": {},
206 | "source": [
207 | "Fills in the provided values using zero to represent an empty cell"
208 | ]
209 | },
210 | {
211 | "cell_type": "code",
212 | "execution_count": 7,
213 | "metadata": {},
214 | "outputs": [],
215 | "source": [
216 | "# enter the initial values\n",
217 | "init_val = [ 5 3 0 0 7 0 0 0 0;\n",
218 | " 6 0 0 1 9 5 0 0 0;\n",
219 | " 0 9 8 0 0 0 0 6 0;\n",
220 | " \n",
221 | " 8 0 0 0 6 0 0 0 3;\n",
222 | " 4 0 0 8 0 3 0 0 1;\n",
223 | " 7 0 0 0 2 0 0 0 6;\n",
224 | " \n",
225 | " 0 6 0 0 0 0 2 8 0;\n",
226 | " 0 0 0 4 1 9 0 0 5;\n",
227 | " 0 0 0 0 8 0 0 7 9]\n",
228 | "\n",
229 | "# if the value is specified initially add a constraint fixing the variables to that value.\n",
230 | "for i = 1:9, j = 1:9\n",
231 | " if init_val[i,j] != 0\n",
232 | " @constraint(sudoku, x[i,j,init_val[i,j]] == 1)\n",
233 | " end\n",
234 | "end"
235 | ]
236 | },
237 | {
238 | "cell_type": "markdown",
239 | "metadata": {},
240 | "source": [
241 | "\n",
242 | "What type of optimization problem was the sudoku? Is GLPK an appropriate optimizer for this type of problem? You'll want to search for GLPK's documentation and check there.\n",
243 | "
\n",
244 | "\n",
245 | "
"
246 | ]
247 | },
248 | {
249 | "cell_type": "code",
250 | "execution_count": 8,
251 | "metadata": {},
252 | "outputs": [
253 | {
254 | "data": {
255 | "text/plain": [
256 | "9×9×9 Array{Float64,3}:\n",
257 | "[:, :, 1] =\n",
258 | " 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0\n",
259 | " 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0\n",
260 | " 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
261 | " 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0\n",
262 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0\n",
263 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
264 | " 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
265 | " 0.0 0.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0\n",
266 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
267 | "\n",
268 | "[:, :, 2] =\n",
269 | " 0.0 0.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0\n",
270 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
271 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0\n",
272 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
273 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
274 | " 0.0 1.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0\n",
275 | " 1.0 0.0 0.0 0.0 0.0 1.0 1.0 0.0 0.0\n",
276 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
277 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
278 | "\n",
279 | "[:, :, 3] =\n",
280 | " 0.0 1.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0\n",
281 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
282 | " 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0\n",
283 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0\n",
284 | " 0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0 0.0\n",
285 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
286 | " 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0\n",
287 | " 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
288 | " 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0\n",
289 | "\n",
290 | "[:, :, 4] =\n",
291 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
292 | " 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
293 | " 0.0 0.0 0.0 0.0 1.0 0.0 1.0 0.0 0.0\n",
294 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0\n",
295 | " 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
296 | " 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0\n",
297 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0\n",
298 | " 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0\n",
299 | " 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
300 | "\n",
301 | "[:, :, 5] =\n",
302 | " 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
303 | " 0.0 0.0 0.0 0.0 0.0 1.0 0.0 1.0 0.0\n",
304 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
305 | " 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0\n",
306 | " 0.0 1.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0\n",
307 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
308 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
309 | " 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 1.0\n",
310 | " 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0\n",
311 | "\n",
312 | "[:, :, 6] =\n",
313 | " 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0\n",
314 | " 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
315 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0\n",
316 | " 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0\n",
317 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
318 | " 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 1.0\n",
319 | " 0.0 1.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0\n",
320 | " 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0\n",
321 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
322 | "\n",
323 | "[:, :, 7] =\n",
324 | " 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0\n",
325 | " 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
326 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
327 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
328 | " 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0\n",
329 | " 1.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0\n",
330 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
331 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
332 | " 0.0 1.0 0.0 0.0 0.0 1.0 0.0 1.0 0.0\n",
333 | "\n",
334 | "[:, :, 8] =\n",
335 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
336 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0\n",
337 | " 0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0 0.0\n",
338 | " 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
339 | " 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0\n",
340 | " 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0\n",
341 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0\n",
342 | " 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
343 | " 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0\n",
344 | "\n",
345 | "[:, :, 9] =\n",
346 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
347 | " 0.0 0.0 0.0 0.0 1.0 0.0 1.0 0.0 0.0\n",
348 | " 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
349 | " 0.0 0.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0\n",
350 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0\n",
351 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
352 | " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n",
353 | " 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0\n",
354 | " 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0"
355 | ]
356 | },
357 | "execution_count": 8,
358 | "metadata": {},
359 | "output_type": "execute_result"
360 | }
361 | ],
362 | "source": [
363 | "optimize!(sudoku)\n",
364 | "\n",
365 | "x_val = value.(x) # Extract the values of x"
366 | ]
367 | },
368 | {
369 | "cell_type": "markdown",
370 | "metadata": {},
371 | "source": [
372 | "To display the solution, we need to look for the values of **x**[i, j, k] that are equal to 1.\n",
373 | "\n",
374 | "\n",
375 | "INTERACTIVE! Unpack the solution to a 9-by-9 matrix and confirm this is a valid Sudoku solution. Note that the \n",
376 | "solution returned may not be an integer. So you'll need to round this to the nearest integer using iround(y). \n",
377 | "
"
378 | ]
379 | },
380 | {
381 | "cell_type": "code",
382 | "execution_count": 9,
383 | "metadata": {},
384 | "outputs": [
385 | {
386 | "data": {
387 | "text/plain": [
388 | "9×9 Array{Float64,2}:\n",
389 | " 5.0 3.0 2.0 2.0 7.0 6.0 1.0 3.0 7.0\n",
390 | " 6.0 4.0 7.0 1.0 9.0 5.0 9.0 5.0 8.0\n",
391 | " 1.0 9.0 8.0 3.0 4.0 8.0 4.0 6.0 2.0\n",
392 | " 8.0 1.0 9.0 9.0 6.0 1.0 5.0 4.0 3.0\n",
393 | " 4.0 5.0 3.0 8.0 5.0 3.0 7.0 9.0 1.0\n",
394 | " 7.0 2.0 6.0 7.0 2.0 4.0 8.0 2.0 6.0\n",
395 | " 2.0 6.0 1.0 6.0 3.0 2.0 2.0 8.0 4.0\n",
396 | " 3.0 8.0 5.0 4.0 1.0 9.0 6.0 1.0 5.0\n",
397 | " 9.0 7.0 4.0 5.0 8.0 7.0 3.0 7.0 9.0"
398 | ]
399 | },
400 | "execution_count": 9,
401 | "metadata": {},
402 | "output_type": "execute_result"
403 | }
404 | ],
405 | "source": [
406 | "final_val = zeros(9,9)\n",
407 | "\n",
408 | "# ADD HERE"
409 | ]
410 | },
411 | {
412 | "cell_type": "markdown",
413 | "metadata": {},
414 | "source": [
415 | "
\n",
416 | "\n",
417 | "## Integrating water system treatment in the process industry \n",
418 | "\n",
419 | "* Adapted from [R. Karuppiah & I. Grossmann. Global optimization for the synthesis of integrated water systems in chemical processes. Computers and Chemical Engineering 30 (2006) 650–673](https://www.sciencedirect.com/science/article/abs/pii/S0098135405002991)\n",
420 | "\n",
421 | "The efficient utilitization of water will be an integral part to solving the numerous water management issues of today and the near future. Increasing usage of water, anticipated water scarities, and changing regulations are expected to result in numerous related challenges in the next decades. These challenges may extend to the adaptation of numerous industrial processes. as fresh water is a key resource in such processes. These include washing operations in the food processing, thermal processing (cooling, quenching, and scrubbing) in iron and steel manufacturing, and the desalination of crude oil. In each of this processes, some level of contamination is introduced into the freshwater by the process. This process water is then treated in a central facility to remove contaminants and satisfy local regulatory specifications for the disposal of wastewater. In design or improvement of these central facilities, three key questions must be considered. 1) What unit operations need to be included? 2) How should these process units be connected? 3) How should one operate these processes units?\n",
422 | "\n",
423 | "A typical design objective for a wastewater treatment plant may be to minimize it's overall cost in a given year of operation. That is we minimize the sum of the cost fo freshwater intake, the investment cost associated with each treatment unit, and the operating cost of each unit. This can be achieved by minimizing the follow objective:\n",
424 | "\n",
425 | "$\\Phi = A_R\\sum_{j} C_j^{Inv} F_j^{\\alpha} + H\\sum_{j} F_j C_j^{Op} + H F_{fw} C_{fw}$ \n",
426 | "\n",
427 | "where H is the number of hours of operation per annum ($h$); the cost of freshwater is given by $C_{fw}$ (`$/ton`); the fresh water intake mass if given by $F_{fw}$ (`ton/h`); The annualized factor for investment on treatment units is given by $A_R$; the $C_j^{Inv} F_j^{\\alpha}$ is the investment cost (`$`) of the $j$-th treatment unit which treats a flowrate stream of contaminated water $F_j$ (`ton/h`); $C_j^{Op} F_j^{\\alpha}$ is the operating cost (`$/h`) of the $j$-th treatment unit which treats a flowrate of contaminated water $F_j$ (`ton/h`); the parameter $\\alpha$ is a constant cost function exponent $0 \\leq \\alpha \\leq 1$.\n",
428 | "\n",
429 | "The **mixers** units can be described as follows:\n",
430 | "- The outlet flowrate is the sum of the inlet stream flow rate: $F^{outlet} = \\sum_{i = 1}^{n} F^{inlet}_i $ where $F^{outlet}$, $F^{inlet}_1$, $\\ldots$, $F^{inlet}_n$ are given in (tons/hr) and $n$ is the number of inlets streams.\n",
431 | "- The mass of each contaminant in the outlet equals the mass of each contaminant in the inlet streams: $F^{outlet}C_j^{outlet} = \\sum_{i = 1}^{n} F^{inlet}_i C_j^{inlet}$ \n",
432 | "\n",
433 | "The **splitter** units:\n",
434 | "- The outlet flowrates are the sum of the inlet flowrate: $F^{inlet} = \\sum_{i = 1}^{m} F^{outlet}_i $ where $F^{inlet}$, $F^{outlet}_1$, $\\ldots$, $F^{outlet}_m$ are given in (tons/hr) and $m$ is the number of outlet streams.\n",
435 | "- The each outlet streams contaminant concentration is equal to the inlet stream contaminant concentration: $F^{outlet}_m$. \n",
436 | "\n",
437 | "A fixed mass of contaminant is added to a stream by each **process unit**:\n",
438 | "- A process unit has a single inlet stream and a single outlet stream: $F^{inlet} = F^{outlet}$\n",
439 | "- The process unit reduces the l $F^{inlet}C_j^{inlet} + L^{p}_j = F^{outlet}C_j^{outlet}$\n",
440 | "\n",
441 | "The **treatment units**:\n",
442 | "- A process unit has a single inlet stream and a single outlet stream with the same flowrate: $F^{inlet} = F^{outlet}$\n",
443 | "- The concentration of contaminant in the outlet $C_j^{outlet} = \\beta^{t}_j C_j^{inlet}$ where $\\beta^{t}_j$ is the removal ratio of $j$.\n",
444 | "\n",
445 | "Two contaminants A & B are introduced into the system and the concentration of each contaminant must be reduced to 10ppm prior to discharging it into the enviroment. The cost of freshwater is assumed to be $1/ton, the annualized factor for investment on the treatment unit is taken to be 0.1, and the total time of operation of the plant is 8000h/year. While numerous different treatment technologies and separation train configurations are possible, we've settled on using two distinct units to perform a primary and secondary treatment. We'd like to determine whether the inclusion of a recycle stream(s) may reduce the process cost and whether is preferable to have the recyle stream bridge one unit or two.\n",
446 | "\n",
447 | "
\n",
448 | "\n",
449 | "The flowrate of water required by each process unit, along with containment inlet concentration limits, and amount of contaminant discharged are given in Table 1. We'll assume an 𝛼 value of 0.7, an investment. The process unit uses 40 ton/h of freshwater and discharges 1 kg/h of A and 1kg/h of B.\n",
450 | "\n",
451 | "The efficiency of the treatment units are given by the removal ratio in Table 2 along with cost model constants: \n",
452 | "\n",
453 | "
\n",
454 | "\n",
455 | "\n",
456 | "INTERACTIVE! Is this model convex or nonconvex? Continuous or mixed-integer? Why? \n",
457 | "
\n",
458 | "\n",
459 | "We've now started to define objective and constraint functions which compute the concentration and flow rates associated with each stream and the overall system cost. Generally, there are two approaches to doing this. We can introduce a new variable for each concentration and flowrate and allow the optimizer to enforce mass balances. Alternative, we can algebraiclly re-arrange these mass balances and avoid introducing a large number of variables. In some cases, the former approach exposes special forms that an optimizer may use effectively. In other cases, the decreased number of variables may be more beneficial. We primarily use the later approach but we'll introduce variables to handle the recycle-loops which generally cannot be re-arranged into an explicit algebraic form. The optimization variables are $x=\\left(F_{\\mathrm{feed}} ,F_2 ,F_9 ,C_{A,4} ,C_{B,4} ,C_{A,9} ,C_{B,9} ,F_6 ,C_{A,8} ,C_{B,8} \\right)$. Take some time to inspect the constraint function and reflect on the choice of these variables.\n",
460 | "\n",
461 | "\n",
462 | "Generally optimizers require that all variables have both lower (lb) and upper (ub) bounds defined. If there aren't any obvious choices in a problem, you may have to exercise your best judgement and choose reasonable bounds. Equipment based bounds on flowrate are provided below. Further, we assume 0.1 is a sufficiently high concetration such that it will be an inactive constraint. Furthermore, an initial guess (x0) often must be specified. For convex problems, the solution obtained will generally not depend on this guess. \n",
463 | "
"
464 | ]
465 | },
466 | {
467 | "cell_type": "code",
468 | "execution_count": 10,
469 | "metadata": {},
470 | "outputs": [],
471 | "source": [
472 | "# Bounds\n",
473 | "lb = [0.1; 0.1; 0.1; 0.0; 0.0; 0.0; 0.0; 0.1; 0.0; 0.0]\n",
474 | "ub = [100.0; 100.0; 100.0; 0.1; 0.1; 0.1; 0.1; 100.0; 0.1; 0.1]\n",
475 | "\n",
476 | "# Specify initial guess\n",
477 | "x0 = 0.5*(lb + ub)\n",
478 | "\n",
479 | "nothing"
480 | ]
481 | },
482 | {
483 | "cell_type": "markdown",
484 | "metadata": {},
485 | "source": [
486 | "We start by creating an optimization problem, assign it the Ipopt optimizer, and add variables to the model."
487 | ]
488 | },
489 | {
490 | "cell_type": "code",
491 | "execution_count": 11,
492 | "metadata": {},
493 | "outputs": [
494 | {
495 | "data": {
496 | "text/plain": [
497 | "10-element Array{VariableRef,1}:\n",
498 | " x[1]\n",
499 | " x[2]\n",
500 | " x[3]\n",
501 | " x[4]\n",
502 | " x[5]\n",
503 | " x[6]\n",
504 | " x[7]\n",
505 | " x[8]\n",
506 | " x[9]\n",
507 | " x[10]"
508 | ]
509 | },
510 | "execution_count": 11,
511 | "metadata": {},
512 | "output_type": "execute_result"
513 | }
514 | ],
515 | "source": [
516 | "using JuMP, Ipopt\n",
517 | "\n",
518 | "model = Model(Ipopt.Optimizer)\n",
519 | "\n",
520 | "@variable(model, lb[i] <= x[i=1:10] <= ub[i], start = x0[i])"
521 | ]
522 | },
523 | {
524 | "cell_type": "markdown",
525 | "metadata": {},
526 | "source": [
527 | "\n",
528 | "INTERACTIVE! Finish the intermediate calculations and constraints used to define the problem.\n",
529 | "
"
530 | ]
531 | },
532 | {
533 | "cell_type": "code",
534 | "execution_count": 12,
535 | "metadata": {},
536 | "outputs": [],
537 | "source": [
538 | "# process unit #1\n",
539 | "Lp11 = 1.5/1000\n",
540 | "Lp21 = 1/1000\n",
541 | "\n",
542 | "@NLexpression(model, f1e, 40 + x[1])\n",
543 | "@NLexpression(model, c1_1e, Lp11/f1e)\n",
544 | "@NLexpression(model, c2_1e, Lp21/f1e)\n",
545 | "\n",
546 | "# mixer #1\n",
547 | "@NLexpression(model, f4e, x[2] - f1e - x[3]) \n",
548 | "@NLexpression(model, c1_2e, (c1_1e*f1e + x[4]*f4e + x[6]*x[3])/x[2])\n",
549 | "@NLexpression(model, c2_2e, (c2_1e*f1e + x[5]*f4e + x[7]*x[3])/x[2])\n",
550 | "\n",
551 | "# treatment unit #1\n",
552 | "@NLexpression(model, f3e, x[2])\n",
553 | "@NLexpression(model, c1_3e, (1.0 - 0.95)*c1_2e)\n",
554 | "@NLexpression(model, c2_3e, (1.0 - 0.2)*c2_2e)\n",
555 | "\n",
556 | "# splitter #1\n",
557 | "@NLexpression(model, f5e, f3e - f4e)\n",
558 | "@NLexpression(model, c1_5e, c1_3e)\n",
559 | "@NLexpression(model, c2_5e, c2_3e)\n",
560 | "\n",
561 | "@NLconstraint(model, ceq1, x[4] - c1_3e == 0.0)\n",
562 | "@NLconstraint(model, ceq2, x[5] - c2_3e == 0.0)\n",
563 | "\n",
564 | "# mixer #2\n",
565 | "@NLexpression(model, f6e, x[8])\n",
566 | "@NLexpression(model, f8e, f6e - f5e)\n",
567 | "@NLexpression(model, c1_6, (c1_5e*f5e + x[9]*f8e)/f6e)\n",
568 | "@NLexpression(model, c2_6, (c2_5e*f5e + x[10]*f8e)/f6e)\n",
569 | "\n",
570 | "# treatment unit #2\n",
571 | "# ADD CODE HERE!\n",
572 | "\n",
573 | "# splitter #2\n",
574 | "@NLexpression(model, f10e, f7e - f8e - x[3])\n",
575 | "@NLexpression(model, c1_10, c1_7e)\n",
576 | "@NLexpression(model, c2_10, c2_7e)\n",
577 | "\n",
578 | "@NLconstraint(model, ceq3, x[9] - c1_7e == 0)\n",
579 | "@NLconstraint(model, ceq4, x[10] - c2_7e == 0)\n",
580 | "@NLconstraint(model, ceq5, x[6] - c1_7e == 0)\n",
581 | "@NLconstraint(model, ceq6, x[7] - c2_7e == 0)\n",
582 | "\n",
583 | "# inequality effluent constraints\n",
584 | "c1_spec = 0.00001;\n",
585 | "c2_spec = 0.00001;\n",
586 | "@NLconstraint(model, cons_ef1, c1_10 <= c1_spec)\n",
587 | "@NLconstraint(model, cons_ef2, c2_10 <= c2_spec)\n",
588 | "\n",
589 | "nothing"
590 | ]
591 | },
592 | {
593 | "cell_type": "code",
594 | "execution_count": 13,
595 | "metadata": {},
596 | "outputs": [],
597 | "source": [
598 | "# financial assumptions\n",
599 | "AR = 0.1 # annualized factor\n",
600 | "H = 8000 # total hours plant operates\n",
601 | "alpha = 0.7 # scaling term for capital cost\n",
602 | "Cwater = 1.0 # cost of freshwater ($/ton)\n",
603 | "Cinv1, Cinv2 = 16800.0, 12600.0 # investment costs\n",
604 | "Cop1, Cop2 = 0.0067, 1.0 # marginal operating costs\n",
605 | " \n",
606 | "@NLobjective(model, Min, AR*(Cinv1*x[2]^alpha + Cinv2*x[8]^alpha) + Cop1*x[2] + Cop2*x[8] + H*x[1]*Cwater)"
607 | ]
608 | },
609 | {
610 | "cell_type": "markdown",
611 | "metadata": {},
612 | "source": [
613 | "Now, we solve the optimization problem."
614 | ]
615 | },
616 | {
617 | "cell_type": "code",
618 | "execution_count": null,
619 | "metadata": {},
620 | "outputs": [],
621 | "source": [
622 | "# Solve optimization problem\n",
623 | "JuMP.optimize!(model)\n",
624 | "\n",
625 | "# Retrieve solution information\n",
626 | "println(\"Result Status: \", primal_status(model))\n",
627 | "println(\"Termination Status: \", termination_status(model))\n",
628 | "println(\"Objective values: \", objective_value(model))"
629 | ]
630 | },
631 | {
632 | "cell_type": "markdown",
633 | "metadata": {},
634 | "source": [
635 | "\n",
636 | "INTERACTIVE! Next we extract information about the optimization problem. We'll want to check the status of the solve and the objective value, initially. Generally, optimizers will return codes to indicate if the solve was successful and to indicate any potential numerical issues that occur. Now, check the optimal solution and describe the system configuration.\n",
637 | "
"
638 | ]
639 | },
640 | {
641 | "cell_type": "markdown",
642 | "metadata": {},
643 | "source": [
644 | "In some cases, manufacturing plants are permitted a certain allowable amount of emissions at a particular site. Multiple emission sources may exist on a single site and it can be valuable to understand how expensive it is to reduce emissions in each process unit on that site. Let's determine the profitability associated with potentially relaxing our emissions target for this process. In order to do this, we'll perform a sensitivity analysis. That is to say, how much will changing a constraint and/or the variable bounds result in changing the solution? If we can perturb a constraint without affect this solution then the constraint is inactive. Otherwise, the dual values indicate the degree to which the solution may change by altering a constraint. We can obtain this information from the lambda structure. Nonlinear inequality constraint dual values are accessed via as shown below:"
645 | ]
646 | },
647 | {
648 | "cell_type": "code",
649 | "execution_count": null,
650 | "metadata": {},
651 | "outputs": [],
652 | "source": [
653 | "println(\"Effluent constraint #1 & #2 duals = $(dual(cons_ef1)) & $(dual(cons_ef2))\")"
654 | ]
655 | },
656 | {
657 | "cell_type": "markdown",
658 | "metadata": {},
659 | "source": [
660 | "\n",
661 | "INTERACTIVE! How much effect would changing the 10ppm effluent specification slightly have on the process cost? \n",
662 | "
"
663 | ]
664 | },
665 | {
666 | "cell_type": "code",
667 | "execution_count": null,
668 | "metadata": {},
669 | "outputs": [],
670 | "source": []
671 | },
672 | {
673 | "cell_type": "markdown",
674 | "metadata": {},
675 | "source": [
676 | "\n",
677 | "INTERACTIVE! Based on the form of the optimization problem, would you expect a local (convex) optimizer to return the best possible objective value? Why or why (not)? What sort of numerical experiments would support a determination in either case?\n",
678 | "
"
679 | ]
680 | },
681 | {
682 | "cell_type": "markdown",
683 | "metadata": {},
684 | "source": [
685 | "
\n",
686 | "\n",
687 | "# A question to reflect on\n",
688 | "We've solved two optimization problems: an integer program and a nonlinear program. The full design of a wastewater treatment plant may be handled by combining these two approaches. This can include which process units, how many process units, and treatment units are included as well as a full exploration of recycle loop configurations. This variety of problem is termed superstructure optimization and developing efficient means of solving these problems remains an active area of research. How might we modify the above problem using 0-1 integer variables to explore other potential recycle configurations?\n",
689 | "\n",
690 | "
"
691 | ]
692 | },
693 | {
694 | "cell_type": "code",
695 | "execution_count": null,
696 | "metadata": {},
697 | "outputs": [],
698 | "source": []
699 | }
700 | ],
701 | "metadata": {
702 | "kernelspec": {
703 | "display_name": "Julia 1.5.1",
704 | "language": "julia",
705 | "name": "julia-1.5"
706 | },
707 | "language_info": {
708 | "file_extension": ".jl",
709 | "mimetype": "application/julia",
710 | "name": "julia",
711 | "version": "1.5.1"
712 | }
713 | },
714 | "nbformat": 4,
715 | "nbformat_minor": 4
716 | }
717 |
--------------------------------------------------------------------------------
/tutorial/julia/Tutorial 6 - Optimization/SpecTable1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/julia/Tutorial 6 - Optimization/SpecTable1.png
--------------------------------------------------------------------------------
/tutorial/julia/Tutorial 6 - Optimization/Treatment_Train.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/julia/Tutorial 6 - Optimization/Treatment_Train.png
--------------------------------------------------------------------------------
/tutorial/matlab/Tutorial 0 - Intro to Matlab/M0_IntroToMatlab.mlx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/matlab/Tutorial 0 - Intro to Matlab/M0_IntroToMatlab.mlx
--------------------------------------------------------------------------------
/tutorial/matlab/Tutorial 1 - Linear Algebra/M1_LAsystems.mlx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/matlab/Tutorial 1 - Linear Algebra/M1_LAsystems.mlx
--------------------------------------------------------------------------------
/tutorial/matlab/Tutorial 1 - Linear Algebra/M1_LAsystems_solved.mlx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/matlab/Tutorial 1 - Linear Algebra/M1_LAsystems_solved.mlx
--------------------------------------------------------------------------------
/tutorial/matlab/Tutorial 2 - Nonlinear Algebraic Systems/M2_NLAsystems.mlx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/matlab/Tutorial 2 - Nonlinear Algebraic Systems/M2_NLAsystems.mlx
--------------------------------------------------------------------------------
/tutorial/matlab/Tutorial 2 - Nonlinear Algebraic Systems/M2_NLAsystems_solved.mlx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/matlab/Tutorial 2 - Nonlinear Algebraic Systems/M2_NLAsystems_solved.mlx
--------------------------------------------------------------------------------
/tutorial/matlab/Tutorial 3 - ODEs, Initial Value Problems/M3_ODEIVPsystems.mlx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/matlab/Tutorial 3 - ODEs, Initial Value Problems/M3_ODEIVPsystems.mlx
--------------------------------------------------------------------------------
/tutorial/matlab/Tutorial 4 - ODEs, Boundary Value Problems/M4_ODEBVPsystems.mlx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/matlab/Tutorial 4 - ODEs, Boundary Value Problems/M4_ODEBVPsystems.mlx
--------------------------------------------------------------------------------
/tutorial/matlab/Tutorial 4 - ODEs, Boundary Value Problems/M4_ODEBVPsystems_solved.mlx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/matlab/Tutorial 4 - ODEs, Boundary Value Problems/M4_ODEBVPsystems_solved.mlx
--------------------------------------------------------------------------------
/tutorial/matlab/Tutorial 5 - Partial Differential Equations/M5_PDEsystems.mlx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/matlab/Tutorial 5 - Partial Differential Equations/M5_PDEsystems.mlx
--------------------------------------------------------------------------------
/tutorial/matlab/Tutorial 5 - Partial Differential Equations/M5_PDEsystems_solved.mlx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/matlab/Tutorial 5 - Partial Differential Equations/M5_PDEsystems_solved.mlx
--------------------------------------------------------------------------------
/tutorial/matlab/Tutorial 6 - Optimization/M6_Optimization.mlx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/matlab/Tutorial 6 - Optimization/M6_Optimization.mlx
--------------------------------------------------------------------------------
/tutorial/matlab/Tutorial 6 - Optimization/M6_Optimization_solved.mlx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSORLab/Chemical_Engineering_Analysis_Notebooks/5ff53153e50aa048e4fb21da8818f4bb762a61d8/tutorial/matlab/Tutorial 6 - Optimization/M6_Optimization_solved.mlx
--------------------------------------------------------------------------------