Chemical Reactor
We consider a single first-order, irreversible chemical reaction in an isothermal CSTR
The material balances and the system data are provided in [DAR11] and is given by the discrete time nonlinear model
in which \(c_A\geq 0\) and \(c_B\geq 0\) are the molar concentrations of \(A\) and \(B\) respectively, \(Q\leq 20\) (L/min) is the flow through the reactor and \(h\) is the sampling rate in minutes. The constants and their meanings are given in table below.
Reactor constants |
|||
---|---|---|---|
value |
unit |
||
feed concentration of \(A\) |
\(c_f^{A}\) |
1 |
mol/L |
feed concentration of \(B\) |
\(c_f^{B}\) |
0 |
mol/L |
volume of the reactor |
\(V_R\) |
10 |
L |
rate constant |
\(k_r\) |
1.2 |
L/(mol min) |
sampling rate |
\(h\) |
0.5 |
min |
initial value |
\((c_0^{A},c_0^B)\) |
\((0.4, 0.2)\) |
From this set of parameters we can compute the equilibrium \((c_e^{A},c_e^B,Q_e) = (\frac 1 2, \frac 1 2, 12)\) of the system.
To initialize the system dynamics a function that implements \(f(x,u)\), where \(x = (c_{A},c_{B})^T\) and \(u=Q\) has to be defined.
V = 10.
cf_A = 1.
cf_B = 0.
k_r = 1.2
h = 0.5
def f(x,u):
y = nmpyc.array(2)
y[0] = x[0] + h*((u[0]/V) *(cf_A - x[0]) - k_r*x[0])
y[1] = x[1] + h*((u[0]/V) *(cf_B - x[1]) + k_r*x[1])
return y
After that, the nMPyC system object can be set by calling
system = nmpyc.system(f, 2, 1, system_type='discrete')
In the next step, the objective is defined by using the stage cost given by
Since we do not need terminal cost, we can initialize the objective directly using the following implementation.
def l(x,u):
return 0.5 * (x[0]-0.5)**2 + 0.5 * (x[1]-0.5)**2 + 0.5 * (u[0]-12)**2
objective = nmpyc.objective(l)
In terms of the constraints we assume that
This can be realized in the code as follows:
constraints = nmpyc.constraints()
lbx = nmpyc.zeros(2)
ubu = nmpyc.ones(1)*20
lbu = nmpyc.zeros(1)
constraints.add_bound('lower','state', lbx)
constraints.add_bound('lower','control', lbu)
constraints.add_bound('upper','control', ubu)
Moreover, we consider the equilibrium \((c_e^{A},c_e^B,Q_e)\) as th terminal condition for our optimal control problem, which is implemented as
xeq = nmpyc.array([0.5,0.5])
def he(x):
return x - xeq
constraints.add_constr('terminal_eq', he)
After all components of the optimal control problem have been implemented, we can now combine them into a model and start the MPC loop. For this Purpose, we define
and set \(N=15\), \(K=100\).
model = nmpyc.model(objective,system,constraints)
x0 = nmpyc.array([0.4,0.2])
res = model.mpc(x0,15,100)
Following the simulation we can visualize the results by calling
res.plot()
which generates the plot bellow.