├── CycleSteps ├── FuncAdsorption.m ├── FuncCnCDepressurization.m ├── FuncCoCDepressurization.m ├── FuncCoCPressurization.m ├── FuncHeavyReflux.m ├── FuncLightReflux.m ├── Isotherm.m └── WENO.m ├── EconomicOptimization.m ├── EconomicOptimization_v2.m ├── LICENSE ├── NSGA-II ├── Pop_Override.m ├── callOutputfuns.m ├── crossoverOp.m ├── evaluate.m ├── extractPop.m ├── initpop.m ├── loadpopfile.m ├── mutationOp.m ├── ndsort.m ├── nsga2.m ├── nsgaopt.m ├── output2file.m ├── selectOp.m ├── sortt.m ├── statpop.m ├── varlimit.m └── verifyOpt.m ├── PSACycle.m ├── PSACycleSimulation.m ├── Params.mat ├── ProcessInputParameters.m ├── ProcessOptimization.m └── README.md /CycleSteps/FuncAdsorption.m: -------------------------------------------------------------------------------- 1 | function derivatives = FuncAdsorption(~, state_vars, params, isotherm_params) 2 | %#codegen 3 | %Adsorption: Calculate the change in state variables for adsorption step 4 | % When coupled with MATLAB ode solvers, (ode15s), solve the system of PDEs 5 | % for the PSA step. The system of PDEs is solved by using the finite volume 6 | % method (FVM) where the spatial domain is discretized into N volumes. 7 | % Each of these volumes is assumed to have a uniform value for the state 8 | % variables. Weighted Essentially Non-oscillatory (WENO) is used to improve 9 | % calculations by calculating the value of the state variables at the walls 10 | % of the finite volumes. 11 | % 12 | % Input: 13 | % ~: This is the time variable. Since time is not used to calculate 14 | % the derivatives but a place holder is required for the ode solver, 15 | % this is used 16 | % 17 | % state_vars: This is the dimensionless state variables at time being 18 | % evaluated. The ordering is as followed [P_1, P_2,...,P_N+2, y_1, 19 | % ...,y_N+2, q_CO2_1,...,q_CO2_N+2, q_N2_1,...,q_N2_N+2, T_1,..., 20 | % T_N+2] 21 | % 22 | % params: All parameters needed for the simulation. Provided in the 23 | % ProcessInputParameters function file 24 | % 25 | % isotherm_params: All parameters for the isotherm. Provided in the 26 | % ProcessInputParameters function file 27 | % 28 | % Output: 29 | % derivatives: These are the temporal derivatives of the state 30 | % variables. The ordering is the same as the state variables 31 | % 32 | %% Retrieve process parameters 33 | N = params(1) ; 34 | deltaU_1 = params(2) ; 35 | deltaU_2 = params(3) ; 36 | ro_s = params(4) ; 37 | T_0 = params(5) ; 38 | epsilon = params(6) ; 39 | r_p = params(7) ; 40 | mu = params(8) ; 41 | R = params(9) ; 42 | v_0 = params(10) ; 43 | q_s0 = params(11) ; 44 | C_pg = params(12) ; 45 | C_pa = params(13) ; 46 | C_ps = params(14) ; 47 | D_m = params(15) ; 48 | K_z = params(16) ; 49 | P_0 = params(17) ; 50 | L = params(18) ; 51 | MW_CO2 = params(19) ; 52 | MW_N2 = params(20) ; 53 | k_1_LDF = params(21) ; 54 | k_2_LDF = params(22) ; 55 | y_0 = params(23) ; 56 | P_inlet = params(26) ; 57 | ndot_0 = P_0/R/T_0*v_0 ; 58 | % 59 | %% Initialize state variables 60 | P = zeros(N+2, 1) ; 61 | y = zeros(N+2, 1) ; 62 | x1 = zeros(N+2, 1) ; 63 | x2 = zeros(N+2, 1) ; 64 | T = zeros(N+2, 1) ; 65 | 66 | P(1:N+2) = state_vars(1:N+2) ; 67 | y(1:N+2) = max(state_vars(N+3:2*N+4), 0) ; 68 | x1(1:N+2) = max(state_vars(2*N+5:3*N+6), 0) ; 69 | x2(1:N+2) = state_vars(3*N+7:4*N+8) ; 70 | T(1:N+2) = state_vars(4*N+9:5*N+10) ; 71 | % 72 | %% Initialize all variables used in the function 73 | % Temporal derivatives 74 | derivatives = zeros(5*N+10, 1) ; 75 | dPdt = zeros(N+2, 1) ; 76 | dPdt1 = zeros(N+2, 1) ; 77 | dPdt2 = zeros(N+2, 1) ; 78 | dPdt3 = zeros(N+2, 1) ; 79 | dydt = zeros(N+2, 1) ; 80 | dydt1 = zeros(N+2, 1) ; 81 | dydt2 = zeros(N+2, 1) ; 82 | dydt3 = zeros(N+2, 1) ; 83 | dx1dt = zeros(N+2, 1) ; 84 | dx2dt = zeros(N+2, 1) ; 85 | dTdt = zeros(N+2, 1) ; 86 | dTdt1 = zeros(N+2, 1) ; 87 | dTdt2 = zeros(N+2, 1) ; 88 | dTdt3 = zeros(N+2, 1) ; 89 | % Spatial derivatives 90 | dpdz = zeros(N+2, 1) ; 91 | dpdzh = zeros(N+1, 1) ; 92 | dydz = zeros(N+2, 1) ; 93 | d2ydz2 = zeros(N+2, 1) ; 94 | dTdz = zeros(N+2, 1) ; 95 | d2Tdz2 = zeros(N+2, 1) ; 96 | % 97 | %% 98 | % The following estimations are parameters that are used. The axial 99 | % dispersion coefficient is calculated using the following equation 100 | % from Ruthvan "Pressure Swing Adsorption" 101 | % 102 | %% 103 | % $$ D_{L}= 0.7 D_{m} + r_{p} \cdot v_{0} $$ 104 | % 105 | %% 106 | % The Concentration of gas is calculated using the ideal gas law 107 | % 108 | %% 109 | % $$ C_{g}=\frac{\bar{P}}{R\bar{T}} \cdot \frac{P_{0}}{T_{0}} $$ 110 | % 111 | %% Calculate all parameters used 112 | dz = 1/N ; 113 | D_l = 0.7*D_m + v_0*r_p ; 114 | Pe = v_0*L/D_l ; 115 | phi = R*T_0*q_s0*(1-epsilon)/epsilon/P_0 ; 116 | ro_g = P(1:N+2).*P_0/R./T(1:N+2)/T_0 ; 117 | % 118 | %% Boundary Conditions 119 | % The boundary condtions for the adsorption, corresponding to both ends 120 | % (light and heavy product end) of the column being opened. A gas of known 121 | % temperature and composition being fed into the column through the heavy 122 | % product end. For the heavy product end, the following boundary condtions 123 | % are used: 124 | % 125 | %% 126 | % $$ \bar{P} = 1+\Delta \quad $$ or $$ \quad \bar{v} = 1 \quad | Z=0^- $$ 127 | % 128 | % $$ y = y_{0} \quad | Z=0^- $$ 129 | % 130 | % $$ \bar{T} = \frac{T_{0}}{T_{0}} = 1 \quad | Z=0^- $$ 131 | % 132 | %% 133 | % It is noted that for the inlet pressure, this is either set as a constant 134 | % or the inlet velocity is set as a constant and the pressure changes to 135 | % satisfy the velocity constraint at the inlet 136 | 137 | y(1) = y_0 ; 138 | T(1) = T_0/T_0 ; 139 | 140 | if params(end) == 1 141 | % Inlet pressure is specified as a constant 142 | P(1) = P_inlet ; 143 | elseif params(end) == 0 144 | % Inlet velocity is specified as a constant 145 | vh = zeros(N+1, 1) ; 146 | 147 | MW = MW_N2+(MW_CO2-MW_N2)*y(1) ; 148 | 149 | a_1 = 150*mu*(1-epsilon)^2*dz*L/2/4/r_p^2/epsilon^3/T(1)/T_0/R ; 150 | a_2_1 = 1.75*(1-epsilon)/2/r_p/epsilon/epsilon/epsilon*dz*L/2 ; 151 | a_2 = a_2_1/R/T(1)/T_0*ndot_0*MW ; 152 | 153 | a = a_1+a_2 ; 154 | b = P(2)/T(1)*P_0/R/T_0 ; 155 | c = -ndot_0 ; 156 | 157 | vh(1) = (-b+sqrt(b^2-4*a*c))/2/a/v_0 ; 158 | 159 | a_p = a_1*T(1)*T_0*R ; 160 | b_p = a_2_1*MW/R/T(1)/T_0 ; 161 | 162 | P(1) = ((a_p*vh(1)*v_0+P(2)*P_0)./(1-b_p*vh(1)*v_0*vh(1)*v_0))/P_0 ; 163 | 164 | % to reproduce results of chapter 5 of Karson's thesis, the code 165 | % below has to be added, this is the issue i found in the Karson codes 166 | viscous_term=150*mu*(1-epsilon)^2/4/r_p^2/epsilon^2; 167 | vh(1) = 1 ; 168 | P(1)=((viscous_term.*vh(1)*v_0*dz/2*L./P_0)+P(2))./(1-(dz/2*L/R/T(1)/T_0)*MW*(1.75*(1-epsilon)/2/r_p/epsilon)*vh(1).^2.*v_0.^2); 169 | 170 | else 171 | error('Please specify whether inlet velocity or pressure is constant for the feed step') 172 | end 173 | % 174 | %% 175 | % For the light product end, the following boundary condtions are used: 176 | % 177 | %% 178 | % $$ \bar{P} = 1 \quad | Z=1^+ $$ 179 | % 180 | % $$ \frac{\partial y}{\partial \tau} = 0 \quad | Z=1^+ $$ 181 | % 182 | % $$ \frac{\partial \bar{T}}{\partial \tau} = 0 \quad | Z=1^+ $$ 183 | % 184 | %% 185 | y(N+2) = y(N+1) ; 186 | T(N+2) = T(N+1) ; 187 | if P(N+1) >= 1 188 | P(N+2) = 1 ; 189 | else 190 | P(N+2) = P(N+1) ; 191 | end 192 | % 193 | %% Spatial Derivative Calculations 194 | % 195 | % *1st Derivatives* 196 | % 197 | % For the first derivative, the value at each volume is estimated based on 198 | % the values at the walls of the volumes. These values are estimated using 199 | % the Weighted Essentially NonOscillatory (WENO) scheme. 200 | % 201 | %% 202 | % $$ \frac{\partial f_j}{\partial Z}=\frac{f_{j+0.5}-f_{j-0.5}}{\Delta Z} $$ 203 | % 204 | %% 205 | % Pressure: at the center of the volumes and the walls 206 | 207 | Ph = WENO(P, 'upwind') ; 208 | 209 | dpdz(2:N+1) = (Ph(2:N+1)-Ph(1:N))/dz ; 210 | dpdzh(2:N) = (P(3:N+1)-P(2:N))/dz ; 211 | dpdzh(1) = 2*(P(2)-P(1))/dz ; 212 | dpdzh(N+1) = 2*(P(N+2)-P(N+1))/dz ; 213 | % 214 | %% 215 | % Mole Fraction: at the center of the volumes 216 | 217 | yh = WENO(y, 'upwind') ; 218 | 219 | dydz(2:N+1) = (yh(2:N+1)-yh(1:N))/dz ; 220 | % 221 | %% 222 | % Temperature: at the center of the volumes 223 | 224 | Th = WENO(T, 'upwind') ; 225 | 226 | dTdz(2:N+1) = (Th(2:N+1)-Th(1:N))/dz ; 227 | % 228 | %% 229 | % *2nd Derivatives* 230 | % 231 | % The second derivatives are calculated based off of the values of the 232 | % nodes. It is only necessary to calculate the 2nd derivative of the 233 | % temperature and mole fraction. It is noted that at the ends of the 234 | % column, no diffusion/conduction is occuring, so these are set to 0 in 235 | % the value of the derivative. 236 | % 237 | %% 238 | % $$ \frac{{\partial}^2 f_{j}}{\partial {Z}^2} = \frac{f_{j+1}+f_{j-1}- 239 | % 2f_{j}}{{\Delta Z}^2} $$ 240 | % 241 | %% 242 | % Mole Fraction 243 | d2ydz2(3:N) = (y(4:N+1)+y(2:N-1)-2*y(3:N))/dz/dz ; 244 | d2ydz2(2) = (y(3)-y(2))/dz/dz ; 245 | d2ydz2(N+1) = (y(N)-y(N+1))/dz/dz ; 246 | % 247 | %% 248 | % Temperature 249 | d2Tdz2(3:N) = (T(4:N+1)+T(2:N-1)-2*T(3:N))/dz/dz ; 250 | d2Tdz2(2) = 4*(Th(2)+T(1)-2*T(2))/dz/dz ; 251 | d2Tdz2(N+1) = 4*(Th(N)+T(N+2)-2*T(N+1))/dz/dz ; 252 | % 253 | %% Velocity Calculations 254 | % Calculates the interstitial velocity of the gas at the walls of the 255 | % volumes based off of the pressure gradients. NOTE: bear in mind that 256 | % the velocity in the Ergun's equation is the superficial velocity, and 257 | % not the interstitial, that's why in the denominator in the viscous term 258 | % it is found epsilon^2 and not epsilon^3, the same for kinetic terms, 259 | % where it is found epsilon and not epsilon^3. Superficial velocity is 260 | % equal to interstitial velocity times the void fraction 261 | % 262 | %% 263 | % $$ U = v \cdot \varepsilon $$ 264 | % 265 | % $$ - \frac{\partial \bar{P}}{\partial Z}\frac{P_0}{L} = 266 | % \frac{150\mu(1-\varepsilon)^2}{4r_{p}\varepsilon^2}\bar{v}v_0 + 267 | % \frac{1.75(1-\varepsilon)}{2r_{p}\varepsilon} 268 | % (\sum_{i}y_{i}MW_{i}C_{g})\bar{v}^2{v_{0}}^2 $$ 269 | % 270 | %% 271 | ro_gh = (P_0/R/T_0)*Ph(1:N+1)./Th(1:N+1) ; 272 | 273 | viscous_term = 150*mu*(1-epsilon)^2/4/r_p^2/epsilon^2 ; 274 | kinetic_term_h = (ro_gh.*(MW_N2+(MW_CO2-MW_N2).*yh)).*(1.75*(1-epsilon)... 275 | /2/r_p/epsilon) ; 276 | 277 | % Velocities at walls of volumes 278 | vh = -sign(dpdzh).*(-viscous_term+(abs(viscous_term^2+4*kinetic_term_h... 279 | .*abs(dpdzh)*P_0/L)).^(.5))/2./kinetic_term_h/v_0 ; 280 | % 281 | %% Temporal Derivatives 282 | % 283 | %% 284 | % *1) Adsorbed Mass Balance (molar loading for components 1 and 2)* 285 | % Linear Driving Force (LDF) is used to calculate the uptake rate of 286 | % the gas. The LDF mass transfer coefficients are assumed to be 287 | % constant over the pressure and temperature range of importance. 288 | % 289 | %% 290 | % $$ \frac{\partial x_{i}}{\partial \tau}q_{s0}= k_{i}({q_{i}}^*-q_{i}) $$ 291 | % 292 | %% 293 | % 1.1) Calculate equilibrium molar loading 294 | q = Isotherm(y, P*P_0, T*T_0, isotherm_params) ; 295 | q_1 = q(:, 1)*ro_s ; 296 | q_2 = q(:, 2)*ro_s ; 297 | % 298 | %% 299 | % 1.2) Calculate the LDF parameter 300 | k_1 = k_1_LDF*L/v_0 ; 301 | k_2 = k_2_LDF*L/v_0 ; 302 | % 303 | %% 304 | % 1.3) Calculate the temporal derivative 305 | dx1dt(2:N+1) = k_1*(q_1(2:N+1)/q_s0 - x1(2:N+1)) ; 306 | dx2dt(2:N+1) = k_2*(q_2(2:N+1)/q_s0 - x2(2:N+1)) ; 307 | % 308 | %% 309 | % *2) Column Energy Balance (Column Temperature)* 310 | % For the energy balance in the column, it is assumed that: 311 | % * There is thermal equilibrium between the solid and gas phase 312 | % * Conduction occurs through both the solid and gas phase 313 | % * Advection only occurs in the gas phase 314 | % 315 | %% 316 | % $$ \big[ \varepsilon C_{g}C_{p,g}+(1-\varepsilon)(C_{p,s}\rho_{s}+ 317 | % C_{p,a}q_{s0})\big]\frac{\partial \bar{T}}{\partial \tau}= 318 | % \frac{K_z}{v_0L} \frac{\partial^2 \bar{T}}{\partial Z^2} 319 | % - \varepsilon C_{g}C_{p,g} \bar{v}\frac{\partial \bar{T}}{\partial Z} 320 | % + \sum_{i} (1-\varepsilon)(-\Delta H_{i})\frac{q_{s0}}{T_{0}}\frac{ 321 | % \partial \bar{x_{i}}}{\partial \tau} $$ 322 | % 323 | %% 324 | % [J/m^3/K] 325 | sink_term = ((1-epsilon)*(ro_s*C_ps+q_s0*C_pa)+(epsilon.*ro_g(2:N+1).*C_pg)) ; 326 | % 327 | %% 328 | % 2.1) Calculate the temperature change due to conduction in the column 329 | % (both solid and gas phase) 330 | % 331 | %% 332 | % $$ \frac{K_z}{v_0L} \frac{\partial^2 \bar{T}}{\partial Z^2} $$ 333 | % 334 | %% 335 | transfer_term = K_z./v_0./L ; 336 | dTdt1(2:N+1) = transfer_term.*d2Tdz2(2:N+1)./sink_term ; 337 | % 338 | %% 339 | % 2.2) Calculate the temperature change due to advection 340 | % 341 | %% 342 | % $$ -\varepsilon C_{g}C_{p,g} \bar{v}\frac{\partial \bar{T}}{\partial Z} $$ 343 | % 344 | %% 345 | PvT = Ph(1:N+1).*vh(1:N+1)./Th(1:N+1) ; 346 | Pv = Ph(1:N+1).*vh(1:N+1) ; 347 | dTdt2(2:N+1) = -epsilon.*C_pg.*P_0./R./T_0.*((Pv(2:N+1)-Pv(1:N))- ... 348 | T(2:N+1).*(PvT(2:N+1)-PvT(1:N)))./dz./sink_term ; 349 | % 350 | %% 351 | % 2.3) Calculate the temperature change due to adsorption/desoprtion enthalpy 352 | % 353 | %% 354 | % $$ \sum_{i} (1-\varepsilon)(-\Delta 355 | % H_{i})\frac{q_{s0}}{T_{0}}\frac{\partial \bar{x_{i}}}{\partial \tau} $$ 356 | % 357 | % $$ \Delta H_{i} = \Delta U_i - R\bar{T}T_{0} $$ 358 | % 359 | %% 360 | generation_term_1 = (1-epsilon).*q_s0.*(-(deltaU_1-R*T(2:N+1)*T_0))./T_0 ; 361 | generation_term_2 = (1-epsilon).*q_s0.*(-(deltaU_2-R*T(2:N+1)*T_0))./T_0 ; 362 | 363 | dTdt3(2:N+1) = (generation_term_1.*dx1dt(2:N+1)+... 364 | generation_term_2.*dx2dt(2:N+1))./sink_term ; 365 | % 366 | %% 367 | % 2.4) Total sum of all temperature derivatives 368 | dTdt(2:N+1) = dTdt1(2:N+1) + dTdt2(2:N+1) + dTdt3(2:N+1) ; 369 | % 370 | %% 371 | % *3) Total mass balance* 372 | % 373 | %% 374 | % $$ \frac{\partial \bar{P}}{\partial \tau} = -\frac{\partial( \bar{v} 375 | % \bar{P}/\bar{T})}{\partial Z} - \Psi \bar{T} \sum_{i}\frac{\partial 376 | % \bar{x_{i}}}{\partial \tau} + \frac{\bar{P}}{\bar{T}}\frac{\partial 377 | % \bar{T}}{\partial \tau} $$ 378 | % 379 | %% 380 | % 3.1) Calculate the change in pressure due to advection 381 | dPdt1(2:N+1) = -T(2:N+1).*(PvT(2:N+1)-PvT(1:N))./dz ; 382 | % 383 | %% 384 | % 3.2) Calculate the change in pressure due to adsorption/desorption 385 | dPdt2(2:N+1) = -phi*T(2:N+1).*(dx1dt(2:N+1)+dx2dt(2:N+1)) ; 386 | % 387 | %% 388 | % 3.3) Calculate the change in pressure due to temperature changes 389 | dPdt3(2:N+1) = P(2:N+1).*dTdt(2:N+1)./T(2:N+1) ; 390 | % 391 | %% 392 | % 3.4) Total sum of all presure changes 393 | dPdt(2:N+1) = dPdt1(2:N+1) + dPdt2(2:N+1) + dPdt3(2:N+1) ; 394 | % 395 | %% 396 | % *4) Component Mass Balance (Based on Mole Fraction)* 397 | % 398 | %% 399 | % $$ \frac{\partial y}{\partial \tau} = \frac{1}{Pe} \big(\frac{{ 400 | % \partial}^2 y}{\partial {Z}^2}+\frac{1}{\bar{P}}\frac{\partial 401 | % \bar{P}}{\partial Z}\frac{\partial y}{\partial Z}-\frac{1}{\bar{T}} 402 | % \frac{\partial \bar{T}}{\partial Z}\frac{\partial y}{\partial Z} 403 | % \big)-\bar{v}\frac{\partial y}{\partial Z}+\frac{\Psi \bar{T}}{ 404 | % \bar{P}} \big((y-1)\frac{\partial \bar{x_{1}}}{\partial \tau}+y 405 | % \frac{\partial\bar{x_{2}}}{\partial \tau}\big) $$ 406 | % 407 | %% 408 | % 4.1) Calculate the change in mole fraction due to diffusion 409 | % 410 | %% 411 | % $$ \frac{1}{Pe} \big(\frac{{\partial}^2 y}{\partial {Z}^2}+\frac{1}{ 412 | % \bar{P}}\frac{\partial \bar{P}}{\partial Z}\frac{\partial y}{ 413 | % \partial Z}-\frac{1}{\bar{T}}\frac{\partial \bar{T}}{\partial Z} 414 | % \frac{\partial y}{\partial Z} \big) $$ 415 | % 416 | %% 417 | dydt1(2:N+1) = (1/Pe)*(d2ydz2(2:N+1)+(dydz(2:N+1).*dpdz(2:N+1)./P(2:N+1))... 418 | -(dydz(2:N+1).*dTdz(2:N+1)./T(2:N+1))) ; 419 | % 420 | %% 421 | % 4.2) Calculate the change in mole fraction due to advection 422 | % 423 | %% 424 | % $$ -\bar{v}\frac{\partial y}{\partial Z} $$ 425 | % 426 | %% 427 | ypvt = yh(1:N+1).*Ph(1:N+1).*vh(1:N+1)./Th(1:N+1) ; 428 | dydt2(2:N+1) = -(T(2:N+1)./P(2:N+1)).*((ypvt(2:N+1)-ypvt(1:N))... 429 | -y(2:N+1).*(PvT(2:N+1)-PvT(1:N)))./dz ; 430 | % 431 | %% 432 | % 4.3) Calculate the change in mole fraction due to adsorption/desorption 433 | % 434 | %% 435 | % $$ \frac{\Psi \bar{T}}{\bar{P}} \big((y-1)\frac{\partial \bar{x_{1}}}{ 436 | % \partial \tau}+y\frac{\partial \bar{x_{2}}}{\partial \tau}\big) $$ 437 | % 438 | %% 439 | dydt3(2:N+1) = (phi*T(2:N+1)./P(2:N+1)).*((y(2:N+1)-1).*dx1dt(2:N+1)... 440 | + y(2:N+1).*dx2dt(2:N+1)) ; 441 | % 442 | %% 443 | % 4.4) Total sum of all mole fraction changes 444 | dydt(2:N+1) = dydt1(2:N+1) + dydt2(2:N+1) + dydt3(2:N+1) ; 445 | % 446 | %% Boundary Derivatives 447 | dPdt(1) = 0 ; 448 | dPdt(N+2) = 0 ; 449 | dydt(1) = 0 ; 450 | dydt(N+2) = dydt(N+1) ; 451 | dx1dt(1) = 0 ; 452 | dx2dt(1) = 0 ; 453 | dx1dt(N+2) = 0 ; 454 | dx2dt(N+2) = 0 ; 455 | dTdt(1) = 0 ; 456 | dTdt(N+2) = dTdt(N+1) ; 457 | % 458 | %% Export derivatives to output 459 | derivatives(1:N+2) = dPdt(1:N+2) ; 460 | derivatives(N+3:2*N+4) = dydt(1:N+2) ; 461 | derivatives(2*N+5:3*N+6) = dx1dt(1:N+2) ; 462 | derivatives(3*N+7:4*N+8) = dx2dt(1:N+2) ; 463 | derivatives(4*N+9:5*N+10) = dTdt(1:N+2) ; 464 | % 465 | end -------------------------------------------------------------------------------- /CycleSteps/FuncCnCDepressurization.m: -------------------------------------------------------------------------------- 1 | function derivatives = FuncCnCDepressurization(~, state_vars, params, isotherm_params) 2 | %#codegen 3 | %CnC_Depressurization: Calculate the change in state variables for CnC depres step 4 | % When coupled with MATLAB ode solvers, (ode15s), solve the system of PDEs 5 | % for the PSA step. The system of PDEs is solved by using the finite volume 6 | % method (FVM) where the spatial domain is discretized into N volumes. 7 | % Each of these volumes is assumed to have a uniform value for the state 8 | % variables. Weighted Essentially Non-oscillatory (WENO) is used to improve 9 | % calculations by calculating the value of the state variables at the walls 10 | % of the finite volumes. 11 | % 12 | % Input: 13 | % ~: This is the time variable. Since time is not used to calculate 14 | % the derivatives but a place holder is required for the ode solver, 15 | % this is used 16 | % 17 | % state_vars: This is the dimensionless state variables at time being 18 | % evaluated. The ordering is as followed [P_1, P_2,...,P_N+2, y_1, 19 | % ...,y_N+2, q_CO2_1,...,q_CO2_N+2, q_N2_1,...,q_N2_N+2, T_1,..., 20 | % T_N+2] 21 | % 22 | % params: All parameters needed for the simulation. Provided in the 23 | % ProcessInputParameters function file 24 | % 25 | % isotherm_params: All parameters for the isotherm. Provided in the 26 | % ProcessInputParameters function file 27 | % 28 | % Output: 29 | % derivatives: These are the temporal derivatives of the state 30 | % variables. The ordering is the same as the state variables 31 | % 32 | %% Retrieve process parameters 33 | N = params(1) ; 34 | deltaU_1 = params(2) ; 35 | deltaU_2 = params(3) ; 36 | ro_s = params(4) ; 37 | T_0 = params(5) ; 38 | epsilon = params(6) ; 39 | r_p = params(7) ; 40 | mu = params(8) ; 41 | R = params(9) ; 42 | v_0 = params(10) ; 43 | q_s0 = params(11) ; 44 | C_pg = params(12) ; 45 | C_pa = params(13) ; 46 | C_ps = params(14) ; 47 | D_m = params(15) ; 48 | K_z = params(16) ; 49 | P_0 = params(17) ; 50 | L = params(18) ; 51 | MW_CO2 = params(19) ; 52 | MW_N2 = params(20) ; 53 | k_1_LDF = params(21) ; 54 | k_2_LDF = params(22) ; 55 | tau = params(24) ; 56 | P_l = params(25) ; 57 | % 58 | %% Initialize state variables 59 | P = zeros(N+2, 1) ; 60 | y = zeros(N+2, 1) ; 61 | x1 = zeros(N+2, 1) ; 62 | x2 = zeros(N+2, 1) ; 63 | T = zeros(N+2, 1) ; 64 | 65 | P(1:N+2) = state_vars(1:N+2) ; 66 | y(1:N+2) = max(state_vars(N+3:2*N+4), 0) ; 67 | x1(1:N+2) = max(state_vars(2*N+5:3*N+6), 0) ; 68 | x2(1:N+2) = state_vars(3*N+7:4*N+8) ; 69 | T(1:N+2) = state_vars(4*N+9:5*N+10) ; 70 | % 71 | %% Initialize all variables used in the function 72 | % Temporal derivatives 73 | derivatives = zeros(5*N+10, 1) ; 74 | dPdt = zeros(N+2, 1) ; 75 | dPdt1 = zeros(N+2, 1) ; 76 | dPdt2 = zeros(N+2, 1) ; 77 | dPdt3 = zeros(N+2, 1) ; 78 | dydt = zeros(N+2, 1) ; 79 | dydt1 = zeros(N+2, 1) ; 80 | dydt2 = zeros(N+2, 1) ; 81 | dydt3 = zeros(N+2, 1) ; 82 | dx1dt = zeros(N+2, 1) ; 83 | dx2dt = zeros(N+2, 1) ; 84 | dTdt = zeros(N+2, 1) ; 85 | dTdt1 = zeros(N+2, 1) ; 86 | dTdt2 = zeros(N+2, 1) ; 87 | dTdt3 = zeros(N+2, 1) ; 88 | % Spatial derivatives 89 | dpdz = zeros(N+2, 1) ; 90 | dpdzh = zeros(N+1, 1) ; 91 | dydz = zeros(N+2, 1) ; 92 | d2ydz2 = zeros(N+2, 1) ; 93 | dTdz = zeros(N+2, 1) ; 94 | d2Tdz2 = zeros(N+2, 1) ; 95 | % 96 | %% 97 | % The following estimations are parameters that are used. The axial 98 | % dispersion coefficient is calculated using the following equation 99 | % from Ruthvan "Pressure Swing Adsorption" 100 | % 101 | %% 102 | % $$ D_{L}= 0.7 D_{m} + r_{p} \cdot v_{0} $$ 103 | % 104 | %% 105 | % The Concentration of gas is calculated using the ideal gas law 106 | % 107 | %% 108 | % $$ C_{g}=\frac{\bar{P}}{R\bar{T}} \cdot \frac{P_{0}}{T_{0}} $$ 109 | % 110 | %% Calculate all parameters used 111 | dz = 1/N ; 112 | D_l = 0.7*D_m + v_0*r_p ; 113 | Pe = v_0*L/D_l ; 114 | phi = R*T_0*q_s0*(1-epsilon)/epsilon/P_0 ; 115 | ro_g = P(1:N+2).*P_0/R./T(1:N+2)/T_0 ; 116 | % 117 | %% Boundary Conditions 118 | % The boundary condtions for the CnC depressurization, corresponding to the 119 | % heavy product end of the column being opened and the the light product 120 | % end of the column being closed. For the heavy product end, the following 121 | % boundary condtions are used: 122 | % 123 | %% 124 | % $$ \frac{\partial \bar{P}}{\partial \tau} = \lambda(\bar{P_L}-\bar{P}) \quad | Z=0^- $$ 125 | % 126 | % $$ \frac{\partial y}{\partial \tau} = 0 \quad | Z=0^- $$ 127 | % 128 | % $$ \frac{\partial \bar{T}}{\partial \tau} = 0 \quad | Z=0^- $$ 129 | % 130 | %% 131 | y(1) = y(2) ; 132 | T(1) = T(2) ; 133 | if P(1) > P(2) 134 | P(1) = P(2) ; 135 | end 136 | % 137 | %% 138 | % For the light product end, the following boundary condtions are used: 139 | % 140 | %% 141 | % $$ \frac{\partial \bar{P}}{\partial \tau} = 0 \quad | Z=1^+ $$ 142 | % 143 | % $$ \frac{\partial y}{\partial \tau} = 0 \quad | Z=1^+ $$ 144 | % 145 | % $$ \frac{\partial \bar{T}}{\partial \tau} = 0 \quad | Z=1^+ $$ 146 | % 147 | %% 148 | y(N+2) = y(N+1) ; 149 | T(N+2) = T(N+1) ; 150 | P(N+2) = P(N+1) ; 151 | % 152 | %% Spatial Derivative Calculations 153 | % 154 | % *1st Derivatives* 155 | % 156 | % For the first derivative, the value at each volume is estimated based on 157 | % the values at the walls of the volumes. These values are estimated using 158 | % the Weighted Essentially NonOscillatory (WENO) scheme. 159 | % 160 | %% 161 | % $$ \frac{\partial f_j}{\partial Z}=\frac{f_{j+0.5}-f_{j-0.5}}{\Delta Z} $$ 162 | % 163 | %% 164 | % Pressure: at the center of the volumes and the walls 165 | 166 | Ph = WENO(P, 'downwind') ; 167 | 168 | dpdz(2:N+1) = (Ph(2:N+1)-Ph(1:N))/dz ; 169 | dpdzh(2:N) = (P(3:N+1)-P(2:N))/dz ; 170 | dpdzh(1) = 2*(P(2)-P(1))/dz ; 171 | dpdzh(N+1) = 2*(P(N+2)-P(N+1))/dz ; 172 | % 173 | %% 174 | % Mole Fraction: at the center of the volumes 175 | 176 | yh = WENO(y, 'downwind') ; 177 | 178 | dydz(2:N+1) = (yh(2:N+1)-yh(1:N))/dz ; 179 | % 180 | %% 181 | % Temperature: at the center of the volumes 182 | 183 | Th = WENO(T, 'downwind') ; 184 | 185 | dTdz(2:N+1) = (Th(2:N+1)-Th(1:N))/dz ; 186 | % 187 | %% 188 | % *2nd Derivatives* 189 | % 190 | % The second derivatives are calculated based off of the values of the 191 | % nodes. It is only necessary to calculate the 2nd derivative of the 192 | % temperature and mole fraction. It is noted that at the ends of the 193 | % column, no diffusion/conduction is occuring, so these are set to 0 in 194 | % the value of the derivative. 195 | % 196 | %% 197 | % $$ \frac{{\partial}^2 f_{j}}{\partial {Z}^2} = \frac{f_{j+1}+f_{j-1}- 198 | % 2f_{j}}{{\Delta Z}^2} $$ 199 | % 200 | %% 201 | % Mole Fraction 202 | d2ydz2(3:N) = (y(4:N+1)+y(2:N-1)-2*y(3:N))/dz/dz ; 203 | d2ydz2(2) = (y(3)-y(2))/dz/dz ; 204 | d2ydz2(N+1) = (y(N)-y(N+1))/dz/dz ; 205 | % 206 | %% 207 | % Temperature 208 | d2Tdz2(3:N) = (T(4:N+1)+T(2:N-1)-2*T(3:N))/dz/dz ; 209 | d2Tdz2(2) = 4*(Th(2)+T(1)-2*T(2))/dz/dz ; 210 | d2Tdz2(N+1) = 4*(Th(N)+T(N+2)-2*T(N+1))/dz/dz ; 211 | % 212 | %% Velocity Calculations 213 | % Calculates the interstitial velocity of the gas at the walls of the 214 | % volumes based off of the pressure gradients. NOTE: bear in mind that 215 | % the velocity in the Ergun's equation is the superficial velocity, and 216 | % not the interstitial, that's why in the denominator in the viscous term 217 | % it is found epsilon^2 and not epsilon^3, the same for kinetic terms, 218 | % where it is found epsilon and not epsilon^3. Superficial velocity is 219 | % equal to interstitial velocity times the void fraction 220 | % 221 | %% 222 | % $$ U = v \cdot \varepsilon $$ 223 | % 224 | % $$ - \frac{\partial \bar{P}}{\partial Z}\frac{P_0}{L} = 225 | % \frac{150\mu(1-\varepsilon)^2}{4r_{p}\varepsilon^2}\bar{v}v_0 + 226 | % \frac{1.75(1-\varepsilon)}{2r_{p}\varepsilon} 227 | % (\sum_{i}y_{i}MW_{i}C_{g})\bar{v}^2{v_{0}}^2 $$ 228 | % 229 | %% 230 | ro_gh = (P_0/R/T_0)*Ph(1:N+1)./Th(1:N+1) ; 231 | 232 | viscous_term = 150*mu*(1-epsilon)^2/4/r_p^2/epsilon^2 ; 233 | kinetic_term_h = (ro_gh.*(MW_N2+(MW_CO2-MW_N2).*yh)).*(1.75*(1-epsilon)... 234 | /2/r_p/epsilon) ; 235 | 236 | % Velocities at walls of volumes 237 | vh = -sign(dpdzh).*(-viscous_term+(abs(viscous_term^2+4*kinetic_term_h... 238 | .*abs(dpdzh)*P_0/L)).^(.5))/2./kinetic_term_h/v_0 ; 239 | % 240 | %% Temporal Derivatives 241 | % 242 | %% 243 | % *1) Adsorbed Mass Balance (molar loading for components 1 and 2)* 244 | % Linear Driving Force (LDF) is used to calculate the uptake rate of 245 | % the gas. The LDF mass transfer coefficients are assumed to be 246 | % constant over the pressure and temperature range of importance. 247 | % 248 | %% 249 | % $$ \frac{\partial x_{i}}{\partial \tau}q_{s0}= k_{i}({q_{i}}^*-q_{i}) $$ 250 | % 251 | %% 252 | % 1.1) Calculate equilibrium molar loading 253 | q = Isotherm(y, P*P_0, T*T_0, isotherm_params) ; 254 | q_1 = q(:, 1)*ro_s ; 255 | q_2 = q(:, 2)*ro_s ; 256 | % 257 | %% 258 | % 1.2) Calculate the LDF parameter 259 | k_1 = k_1_LDF*L/v_0 ; 260 | k_2 = k_2_LDF*L/v_0 ; 261 | % 262 | %% 263 | % 1.3) Calculate the temporal derivative 264 | dx1dt(2:N+1) = k_1*(q_1(2:N+1)/q_s0 - x1(2:N+1)) ; 265 | dx2dt(2:N+1) = k_2*(q_2(2:N+1)/q_s0 - x2(2:N+1)) ; 266 | % 267 | %% 268 | % *2) Column Energy Balance (Column Temperature)* 269 | % For the energy balance in the column, it is assumed that: 270 | % * There is thermal equilibrium between the solid and gas phase 271 | % * Conduction occurs through both the solid and gas phase 272 | % * Advection only occurs in the gas phase 273 | % 274 | %% 275 | % $$ \big[ \varepsilon C_{g}C_{p,g}+(1-\varepsilon)(C_{p,s}\rho_{s}+ 276 | % C_{p,a}q_{s0})\big]\frac{\partial \bar{T}}{\partial \tau}= 277 | % \frac{K_z}{v_0L} \frac{\partial^2 \bar{T}}{\partial Z^2} 278 | % - \varepsilon C_{g}C_{p,g} \bar{v}\frac{\partial \bar{T}}{\partial Z} 279 | % + \sum_{i} (1-\varepsilon)(-\Delta H_{i})\frac{q_{s0}}{T_{0}}\frac{ 280 | % \partial \bar{x_{i}}}{\partial \tau} $$ 281 | % 282 | %% 283 | % [J/m^3/K] 284 | sink_term = ((1-epsilon)*(ro_s*C_ps+q_s0*C_pa)+(epsilon.*ro_g(2:N+1).*C_pg)) ; 285 | % 286 | %% 287 | % 2.1) Calculate the temperature change due to conduction in the column 288 | % (both solid and gas phase) 289 | % 290 | %% 291 | % $$ \frac{K_z}{v_0L} \frac{\partial^2 \bar{T}}{\partial Z^2} $$ 292 | % 293 | %% 294 | transfer_term = K_z./v_0./L ; 295 | dTdt1(2:N+1) = transfer_term.*d2Tdz2(2:N+1)./sink_term ; 296 | % 297 | %% 298 | % 2.2) Calculate the temperature change due to advection 299 | % 300 | %% 301 | % $$ -\varepsilon C_{g}C_{p,g} \bar{v}\frac{\partial \bar{T}}{\partial Z} $$ 302 | % 303 | %% 304 | PvT = Ph(1:N+1).*vh(1:N+1)./Th(1:N+1) ; 305 | Pv = Ph(1:N+1).*vh(1:N+1) ; 306 | dTdt2(2:N+1) = -epsilon.*C_pg.*P_0./R./T_0.*((Pv(2:N+1)-Pv(1:N))- ... 307 | T(2:N+1).*(PvT(2:N+1)-PvT(1:N)))./dz./sink_term ; 308 | % 309 | %% 310 | % 2.3) Calculate the temperature change due to adsorption/desoprtion enthalpy 311 | % 312 | %% 313 | % $$ \sum_{i} (1-\varepsilon)(-\Delta 314 | % H_{i})\frac{q_{s0}}{T_{0}}\frac{\partial \bar{x_{i}}}{\partial \tau} $$ 315 | % 316 | % $$ \Delta H_{i} = \Delta U_i - R\bar{T}T_{0} $$ 317 | % 318 | %% 319 | generation_term_1 = (1-epsilon).*q_s0.*(-(deltaU_1-R*T(2:N+1)*T_0))./T_0 ; 320 | generation_term_2 = (1-epsilon).*q_s0.*(-(deltaU_2-R*T(2:N+1)*T_0))./T_0 ; 321 | 322 | dTdt3(2:N+1) = (generation_term_1.*dx1dt(2:N+1)+... 323 | generation_term_2.*dx2dt(2:N+1))./sink_term ; 324 | % 325 | %% 326 | % 2.4) Total sum of all temperature derivatives 327 | dTdt(2:N+1) = dTdt1(2:N+1) + dTdt2(2:N+1) + dTdt3(2:N+1) ; 328 | % 329 | %% 330 | % *3) Total mass balance* 331 | % 332 | %% 333 | % $$ \frac{\partial \bar{P}}{\partial \tau} = -\frac{\partial( \bar{v} 334 | % \bar{P}/\bar{T})}{\partial Z} - \Psi \bar{T} \sum_{i}\frac{\partial 335 | % \bar{x_{i}}}{\partial \tau} + \frac{\bar{P}}{\bar{T}}\frac{\partial 336 | % \bar{T}}{\partial \tau} $$ 337 | % 338 | %% 339 | % 3.1) Calculate the change in pressure due to advection 340 | dPdt1(2:N+1) = -T(2:N+1).*(PvT(2:N+1)-PvT(1:N))./dz ; 341 | % 342 | %% 343 | % 3.2) Calculate the change in pressure due to adsorption/desorption 344 | dPdt2(2:N+1) = -phi*T(2:N+1).*(dx1dt(2:N+1)+dx2dt(2:N+1)) ; 345 | % 346 | %% 347 | % 3.3) Calculate the change in pressure due to temperature changes 348 | dPdt3(2:N+1) = P(2:N+1).*dTdt(2:N+1)./T(2:N+1) ; 349 | % 350 | %% 351 | % 3.4) Total sum of all presure changes 352 | dPdt(2:N+1) = dPdt1(2:N+1) + dPdt2(2:N+1) + dPdt3(2:N+1) ; 353 | % 354 | %% 355 | % *4) Component Mass Balance (Based on Mole Fraction)* 356 | % 357 | %% 358 | % $$ \frac{\partial y}{\partial \tau} = \frac{1}{Pe} \big(\frac{{ 359 | % \partial}^2 y}{\partial {Z}^2}+\frac{1}{\bar{P}}\frac{\partial 360 | % \bar{P}}{\partial Z}\frac{\partial y}{\partial Z}-\frac{1}{\bar{T}} 361 | % \frac{\partial \bar{T}}{\partial Z}\frac{\partial y}{\partial Z} 362 | % \big)-\bar{v}\frac{\partial y}{\partial Z}+\frac{\Psi \bar{T}}{ 363 | % \bar{P}} \big((y-1)\frac{\partial \bar{x_{1}}}{\partial \tau}+y 364 | % \frac{\partial\bar{x_{2}}}{\partial \tau}\big) $$ 365 | % 366 | %% 367 | % 4.1) Calculate the change in mole fraction due to diffusion 368 | % 369 | %% 370 | % $$ \frac{1}{Pe} \big(\frac{{\partial}^2 y}{\partial {Z}^2}+\frac{1}{ 371 | % \bar{P}}\frac{\partial \bar{P}}{\partial Z}\frac{\partial y}{ 372 | % \partial Z}-\frac{1}{\bar{T}}\frac{\partial \bar{T}}{\partial Z} 373 | % \frac{\partial y}{\partial Z} \big) $$ 374 | % 375 | %% 376 | dydt1(2:N+1) = (1/Pe)*(d2ydz2(2:N+1)+(dydz(2:N+1).*dpdz(2:N+1)./P(2:N+1))... 377 | -(dydz(2:N+1).*dTdz(2:N+1)./T(2:N+1))) ; 378 | % 379 | %% 380 | % 4.2) Calculate the change in mole fraction due to advection 381 | % 382 | %% 383 | % $$ -\bar{v}\frac{\partial y}{\partial Z} $$ 384 | % 385 | %% 386 | ypvt = yh(1:N+1).*Ph(1:N+1).*vh(1:N+1)./Th(1:N+1) ; 387 | dydt2(2:N+1) = -(T(2:N+1)./P(2:N+1)).*((ypvt(2:N+1)-ypvt(1:N))... 388 | -y(2:N+1).*(PvT(2:N+1)-PvT(1:N)))./dz ; 389 | % 390 | %% 391 | % 4.3) Calculate the change in mole fraction due to adsorption/desorption 392 | % 393 | %% 394 | % $$ \frac{\Psi \bar{T}}{\bar{P}} \big((y-1)\frac{\partial \bar{x_{1}}}{ 395 | % \partial \tau}+y\frac{\partial \bar{x_{2}}}{\partial \tau}\big) $$ 396 | % 397 | %% 398 | dydt3(2:N+1) = (phi*T(2:N+1)./P(2:N+1)).*((y(2:N+1)-1).*dx1dt(2:N+1)... 399 | + y(2:N+1).*dx2dt(2:N+1)) ; 400 | % 401 | %% 402 | % 4.4) Total sum of all mole fraction changes 403 | dydt(2:N+1) = dydt1(2:N+1) + dydt2(2:N+1) + dydt3(2:N+1) ; 404 | % 405 | %% Boundary Derivatives 406 | %dPdt(1) = tau*(P_l/P_0-P(1)) ; 407 | dPdt(1) = tau*L/v_0*(P_l/P_0-P(1)) ; 408 | dPdt(N+2) = dPdt(N+1) ; 409 | dydt(1) = dydt(2) ; 410 | dydt(N+2) = dydt(N+1) ; 411 | dx1dt(1) = 0 ; 412 | dx2dt(1) = 0 ; 413 | dx1dt(N+2) = 0 ; 414 | dx2dt(N+2) = 0 ; 415 | dTdt(1) = dTdt(2) ; 416 | dTdt(N+2) = dTdt(N+1) ; 417 | % 418 | %% Export derivatives to output 419 | derivatives(1:N+2) = dPdt(1:N+2) ; 420 | derivatives(N+3:2*N+4) = dydt(1:N+2) ; 421 | derivatives(2*N+5:3*N+6) = dx1dt(1:N+2) ; 422 | derivatives(3*N+7:4*N+8) = dx2dt(1:N+2) ; 423 | derivatives(4*N+9:5*N+10) = dTdt(1:N+2) ; 424 | % 425 | end -------------------------------------------------------------------------------- /CycleSteps/FuncCoCDepressurization.m: -------------------------------------------------------------------------------- 1 | function derivatives = FuncCoCDepressurization(~, state_vars, params, isotherm_params) 2 | %#codegen 3 | %CoCDepressurization: Calculate the change in state variables for CoC depres step 4 | % When coupled with MATLAB ode solvers, (ode15s), solve the system of PDEs 5 | % for the PSA step. The system of PDEs is solved by using the finite volume 6 | % method (FVM) where the spatial domain is discretized into N volumes. 7 | % Each of these volumes is assumed to have a uniform value for the state 8 | % variables. Weighted Essentially Non-oscillatory (WENO) is used to improve 9 | % calculations by calculating the value of the state variables at the walls 10 | % of the finite volumes. 11 | % 12 | % Input: 13 | % ~: This is the time variable. Since time is not used to calculate 14 | % the derivatives but a place holder is required for the ode solver, 15 | % this is used 16 | % 17 | % state_vars: This is the dimensionless state variables at time being 18 | % evaluated. The ordering is as followed [P_1, P_2,...,P_N+2, y_1, 19 | % ...,y_N+2, q_CO2_1,...,q_CO2_N+2, q_N2_1,...,q_N2_N+2, T_1,..., 20 | % T_N+2] 21 | % 22 | % params: All parameters needed for the simulation. Provided in the 23 | % ProcessInputParameters function file 24 | % 25 | % isotherm_params: All parameters for the isotherm. Provided in the 26 | % ProcessInputParameters function file 27 | % 28 | % Output: 29 | % derivatives: These are the temporal derivatives of the state 30 | % variables. The ordering is the same as the state variables 31 | % 32 | %% Retrieve process parameters 33 | N = params(1) ; 34 | deltaU_1 = params(2) ; 35 | deltaU_2 = params(3) ; 36 | ro_s = params(4) ; 37 | T_0 = params(5) ; 38 | epsilon = params(6) ; 39 | r_p = params(7) ; 40 | mu = params(8) ; 41 | R = params(9) ; 42 | v_0 = params(10) ; 43 | q_s0 = params(11) ; 44 | C_pg = params(12) ; 45 | C_pa = params(13) ; 46 | C_ps = params(14) ; 47 | D_m = params(15) ; 48 | K_z = params(16) ; 49 | P_0 = params(17) ; 50 | L = params(18) ; 51 | MW_CO2 = params(19) ; 52 | MW_N2 = params(20) ; 53 | k_1_LDF = params(21) ; 54 | k_2_LDF = params(22) ; 55 | tau = params(24) ; 56 | P_I = params(32) ; 57 | % 58 | %% Initialize state variables 59 | P = zeros(N+2, 1) ; 60 | y = zeros(N+2, 1) ; 61 | x1 = zeros(N+2, 1) ; 62 | x2 = zeros(N+2, 1) ; 63 | T = zeros(N+2, 1) ; 64 | 65 | P(1:N+2) = state_vars(1:N+2) ; 66 | y(1:N+2) = max(state_vars(N+3:2*N+4), 0) ; 67 | x1(1:N+2) = max(state_vars(2*N+5:3*N+6), 0) ; 68 | x2(1:N+2) = state_vars(3*N+7:4*N+8) ; 69 | T(1:N+2) = state_vars(4*N+9:5*N+10) ; 70 | % 71 | %% Initialize all variables used in the function 72 | % Temporal derivatives 73 | derivatives = zeros(5*N+10, 1) ; 74 | dPdt = zeros(N+2, 1) ; 75 | dPdt1 = zeros(N+2, 1) ; 76 | dPdt2 = zeros(N+2, 1) ; 77 | dPdt3 = zeros(N+2, 1) ; 78 | dydt = zeros(N+2, 1) ; 79 | dydt1 = zeros(N+2, 1) ; 80 | dydt2 = zeros(N+2, 1) ; 81 | dydt3 = zeros(N+2, 1) ; 82 | dx1dt = zeros(N+2, 1) ; 83 | dx2dt = zeros(N+2, 1) ; 84 | dTdt = zeros(N+2, 1) ; 85 | dTdt1 = zeros(N+2, 1) ; 86 | dTdt2 = zeros(N+2, 1) ; 87 | dTdt3 = zeros(N+2, 1) ; 88 | % Spatial derivatives 89 | dpdz = zeros(N+2, 1) ; 90 | dpdzh = zeros(N+1, 1) ; 91 | dydz = zeros(N+2, 1) ; 92 | d2ydz2 = zeros(N+2, 1) ; 93 | dTdz = zeros(N+2, 1) ; 94 | d2Tdz2 = zeros(N+2, 1) ; 95 | % 96 | %% 97 | % The following estimations are parameters that are used. The axial 98 | % dispersion coefficient is calculated using the following equation 99 | % from Ruthvan "Pressure Swing Adsorption" 100 | % 101 | %% 102 | % $$ D_{L}= 0.7 D_{m} + r_{p} \cdot v_{0} $$ 103 | % 104 | %% 105 | % The Concentration of gas is calculated using the ideal gas law 106 | % 107 | %% 108 | % $$ C_{g}=\frac{\bar{P}}{R\bar{T}} \cdot \frac{P_{0}}{T_{0}} $$ 109 | % 110 | %% Calculate all parameters used 111 | dz = 1/N ; 112 | D_l = 0.7*D_m + v_0*r_p ; 113 | Pe = v_0*L/D_l ; 114 | phi = R*T_0*q_s0*(1-epsilon)/epsilon/P_0 ; 115 | ro_g = P(1:N+2).*P_0/R./T(1:N+2)/T_0 ; 116 | % 117 | %% Boundary Conditions 118 | % The boundary condtions for the CoC depressurization, corresponding to the 119 | % heavy product end of the column being closed and the the light product 120 | % end of the column being opened. For the heavy product end, the following 121 | % boundary condtions are used: 122 | % 123 | %% 124 | % $$ \frac{\partial \bar{P}}{\partial \tau} = 0 \quad | Z=0^- $$ 125 | % 126 | % $$ \frac{\partial y}{\partial \tau} = 0 \quad | Z=0^- $$ 127 | % 128 | % $$ \frac{\partial \bar{T}}{\partial \tau} = 0 \quad | Z=0^- $$ 129 | % 130 | %% 131 | P(1) = P(2) ; 132 | y(1) = y(2) ; 133 | T(1) = T(2) ; 134 | % 135 | %% 136 | % For the light product end, the following boundary condtions are used: 137 | % 138 | %% 139 | % $$ \frac{\partial \bar{P}}{\partial \tau} = \lambda(\bar{P_I}-\bar{P}) \quad | Z=0^- $$ 140 | % 141 | % $$ \frac{\partial y}{\partial \tau} = 0 \quad | Z=1^+ $$ 142 | % 143 | % $$ \frac{\partial \bar{T}}{\partial \tau} = 0 \quad | Z=1^+ $$ 144 | % 145 | %% 146 | y(N+2) = y(N+1) ; 147 | T(N+2) = T(N+1) ; 148 | % if P(N+2) > P(N+1) 149 | % P(N+2) = P(N+1) ; 150 | % end 151 | % 152 | %% Spatial Derivative Calculations 153 | % 154 | % *1st Derivatives* 155 | % 156 | % For the first derivative, the value at each volume is estimated based on 157 | % the values at the walls of the volumes. These values are estimated using 158 | % the Weighted Essentially NonOscillatory (WENO) scheme. 159 | % 160 | %% 161 | % $$ \frac{\partial f_j}{\partial Z}=\frac{f_{j+0.5}-f_{j-0.5}}{\Delta Z} $$ 162 | % 163 | %% 164 | % Pressure: at the center of the volumes and the walls 165 | 166 | Ph = WENO(P, 'upwind') ; 167 | 168 | dpdz(2:N+1) = (Ph(2:N+1)-Ph(1:N))/dz ; 169 | dpdzh(2:N) = (P(3:N+1)-P(2:N))/dz ; 170 | dpdzh(1) = 2*(P(2)-P(1))/dz ; 171 | dpdzh(N+1) = 2*(P(N+2)-P(N+1))/dz ; 172 | % 173 | %% 174 | % Mole Fraction: at the center of the volumes 175 | 176 | yh = WENO(y, 'upwind') ; 177 | 178 | dydz(2:N+1) = (yh(2:N+1)-yh(1:N))/dz ; 179 | % 180 | %% 181 | % Temperature: at the center of the volumes 182 | 183 | Th = WENO(T, 'upwind') ; 184 | 185 | dTdz(2:N+1) = (Th(2:N+1)-Th(1:N))/dz ; 186 | % 187 | %% 188 | % *2nd Derivatives* 189 | % 190 | % The second derivatives are calculated based off of the values of the 191 | % nodes. It is only necessary to calculate the 2nd derivative of the 192 | % temperature and mole fraction. It is noted that at the ends of the 193 | % column, no diffusion/conduction is occuring, so these are set to 0 in 194 | % the value of the derivative. 195 | % 196 | %% 197 | % $$ \frac{{\partial}^2 f_{j}}{\partial {Z}^2} = \frac{f_{j+1}+f_{j-1}- 198 | % 2f_{j}}{{\Delta Z}^2} $$ 199 | % 200 | %% 201 | % Mole Fraction 202 | d2ydz2(3:N) = (y(4:N+1)+y(2:N-1)-2*y(3:N))/dz/dz ; 203 | d2ydz2(2) = (y(3)-y(2))/dz/dz ; 204 | d2ydz2(N+1) = (y(N)-y(N+1))/dz/dz ; 205 | % 206 | %% 207 | % Temperature 208 | d2Tdz2(3:N) = (T(4:N+1)+T(2:N-1)-2*T(3:N))/dz/dz ; 209 | d2Tdz2(2) = 4*(Th(2)+T(1)-2*T(2))/dz/dz ; 210 | d2Tdz2(N+1) = 4*(Th(N)+T(N+2)-2*T(N+1))/dz/dz ; 211 | % 212 | %% Velocity Calculations 213 | % Calculates the interstitial velocity of the gas at the walls of the 214 | % volumes based off of the pressure gradients. NOTE: bear in mind that 215 | % the velocity in the Ergun's equation is the superficial velocity, and 216 | % not the interstitial, that's why in the denominator in the viscous term 217 | % it is found epsilon^2 and not epsilon^3, the same for kinetic terms, 218 | % where it is found epsilon and not epsilon^3. Superficial velocity is 219 | % equal to interstitial velocity times the void fraction 220 | % 221 | %% 222 | % $$ U = v \cdot \varepsilon $$ 223 | % 224 | % $$ - \frac{\partial \bar{P}}{\partial Z}\frac{P_0}{L} = 225 | % \frac{150\mu(1-\varepsilon)^2}{4r_{p}\varepsilon^2}\bar{v}v_0 + 226 | % \frac{1.75(1-\varepsilon)}{2r_{p}\varepsilon} 227 | % (\sum_{i}y_{i}MW_{i}C_{g})\bar{v}^2{v_{0}}^2 $$ 228 | % 229 | %% 230 | ro_gh = (P_0/R/T_0)*Ph(1:N+1)./Th(1:N+1) ; 231 | 232 | viscous_term = 150*mu*(1-epsilon)^2/4/r_p^2/epsilon^2 ; 233 | kinetic_term_h = (ro_gh.*(MW_N2+(MW_CO2-MW_N2).*yh)).*(1.75*(1-epsilon)... 234 | /2/r_p/epsilon) ; 235 | 236 | % Velocities at walls of volumes 237 | vh = -sign(dpdzh).*(-viscous_term+(abs(viscous_term^2+4*kinetic_term_h... 238 | .*abs(dpdzh)*P_0/L)).^(.5))/2./kinetic_term_h/v_0 ; 239 | % 240 | %% Temporal Derivatives 241 | % 242 | %% 243 | % *1) Adsorbed Mass Balance (molar loading for components 1 and 2)* 244 | % Linear Driving Force (LDF) is used to calculate the uptake rate of 245 | % the gas. The LDF mass transfer coefficients are assumed to be 246 | % constant over the pressure and temperature range of importance. 247 | % 248 | %% 249 | % $$ \frac{\partial x_{i}}{\partial \tau}q_{s0}= k_{i}({q_{i}}^*-q_{i}) $$ 250 | % 251 | %% 252 | % 1.1) Calculate equilibrium molar loading 253 | q = Isotherm(y, P*P_0, T*T_0, isotherm_params) ; 254 | q_1 = q(:, 1)*ro_s ; 255 | q_2 = q(:, 2)*ro_s ; 256 | % 257 | %% 258 | % 1.2) Calculate the LDF parameter 259 | k_1 = k_1_LDF*L/v_0 ; 260 | k_2 = k_2_LDF*L/v_0 ; 261 | % 262 | %% 263 | % 1.3) Calculate the temporal derivative 264 | dx1dt(2:N+1) = k_1*(q_1(2:N+1)/q_s0 - x1(2:N+1)) ; 265 | dx2dt(2:N+1) = k_2*(q_2(2:N+1)/q_s0 - x2(2:N+1)) ; 266 | % 267 | %% 268 | % *2) Column Energy Balance (Column Temperature)* 269 | % For the energy balance in the column, it is assumed that: 270 | % * There is thermal equilibrium between the solid and gas phase 271 | % * Conduction occurs through both the solid and gas phase 272 | % * Advection only occurs in the gas phase 273 | % 274 | %% 275 | % $$ \big[ \varepsilon C_{g}C_{p,g}+(1-\varepsilon)(C_{p,s}\rho_{s}+ 276 | % C_{p,a}q_{s0})\big]\frac{\partial \bar{T}}{\partial \tau}= 277 | % \frac{K_z}{v_0L} \frac{\partial^2 \bar{T}}{\partial Z^2} 278 | % - \varepsilon C_{g}C_{p,g} \bar{v}\frac{\partial \bar{T}}{\partial Z} 279 | % + \sum_{i} (1-\varepsilon)(-\Delta H_{i})\frac{q_{s0}}{T_{0}}\frac{ 280 | % \partial \bar{x_{i}}}{\partial \tau} $$ 281 | % 282 | %% 283 | % [J/m^3/K] 284 | sink_term = ((1-epsilon)*(ro_s*C_ps+q_s0*C_pa)+(epsilon.*ro_g(2:N+1).*C_pg)) ; 285 | % 286 | %% 287 | % 2.1) Calculate the temperature change due to conduction in the column 288 | % (both solid and gas phase) 289 | % 290 | %% 291 | % $$ \frac{K_z}{v_0L} \frac{\partial^2 \bar{T}}{\partial Z^2} $$ 292 | % 293 | %% 294 | transfer_term = K_z./v_0./L ; 295 | dTdt1(2:N+1) = transfer_term.*d2Tdz2(2:N+1)./sink_term ; 296 | % 297 | %% 298 | % 2.2) Calculate the temperature change due to advection 299 | % 300 | %% 301 | % $$ -\varepsilon C_{g}C_{p,g} \bar{v}\frac{\partial \bar{T}}{\partial Z} $$ 302 | % 303 | %% 304 | PvT = Ph(1:N+1).*vh(1:N+1)./Th(1:N+1) ; 305 | Pv = Ph(1:N+1).*vh(1:N+1) ; 306 | dTdt2(2:N+1) = -epsilon.*C_pg.*P_0./R./T_0.*((Pv(2:N+1)-Pv(1:N))- ... 307 | T(2:N+1).*(PvT(2:N+1)-PvT(1:N)))./dz./sink_term ; 308 | % 309 | %% 310 | % 2.3) Calculate the temperature change due to adsorption/desoprtion enthalpy 311 | % 312 | %% 313 | % $$ \sum_{i} (1-\varepsilon)(-\Delta 314 | % H_{i})\frac{q_{s0}}{T_{0}}\frac{\partial \bar{x_{i}}}{\partial \tau} $$ 315 | % 316 | % $$ \Delta H_{i} = \Delta U_i - R\bar{T}T_{0} $$ 317 | % 318 | %% 319 | generation_term_1 = (1-epsilon).*q_s0.*(-(deltaU_1-R*T(2:N+1)*T_0))./T_0 ; 320 | generation_term_2 = (1-epsilon).*q_s0.*(-(deltaU_2-R*T(2:N+1)*T_0))./T_0 ; 321 | 322 | dTdt3(2:N+1) = (generation_term_1.*dx1dt(2:N+1)+... 323 | generation_term_2.*dx2dt(2:N+1))./sink_term ; 324 | % 325 | %% 326 | % 2.4) Total sum of all temperature derivatives 327 | dTdt(2:N+1) = dTdt1(2:N+1) + dTdt2(2:N+1) + dTdt3(2:N+1) ; 328 | % 329 | %% 330 | % *3) Total mass balance* 331 | % 332 | %% 333 | % $$ \frac{\partial \bar{P}}{\partial \tau} = -\frac{\partial( \bar{v} 334 | % \bar{P}/\bar{T})}{\partial Z} - \Psi \bar{T} \sum_{i}\frac{\partial 335 | % \bar{x_{i}}}{\partial \tau} + \frac{\bar{P}}{\bar{T}}\frac{\partial 336 | % \bar{T}}{\partial \tau} $$ 337 | % 338 | %% 339 | % 3.1) Calculate the change in pressure due to advection 340 | dPdt1(2:N+1) = -T(2:N+1).*(PvT(2:N+1)-PvT(1:N))./dz ; 341 | % 342 | %% 343 | % 3.2) Calculate the change in pressure due to adsorption/desorption 344 | dPdt2(2:N+1) = -phi*T(2:N+1).*(dx1dt(2:N+1)+dx2dt(2:N+1)) ; 345 | % 346 | %% 347 | % 3.3) Calculate the change in pressure due to temperature changes 348 | dPdt3(2:N+1) = P(2:N+1).*dTdt(2:N+1)./T(2:N+1) ; 349 | % 350 | %% 351 | % 3.4) Total sum of all presure changes 352 | dPdt(2:N+1) = dPdt1(2:N+1) + dPdt2(2:N+1) + dPdt3(2:N+1) ; 353 | % 354 | %% 355 | % *4) Component Mass Balance (Based on Mole Fraction)* 356 | % 357 | %% 358 | % $$ \frac{\partial y}{\partial \tau} = \frac{1}{Pe} \big(\frac{{ 359 | % \partial}^2 y}{\partial {Z}^2}+\frac{1}{\bar{P}}\frac{\partial 360 | % \bar{P}}{\partial Z}\frac{\partial y}{\partial Z}-\frac{1}{\bar{T}} 361 | % \frac{\partial \bar{T}}{\partial Z}\frac{\partial y}{\partial Z} 362 | % \big)-\bar{v}\frac{\partial y}{\partial Z}+\frac{\Psi \bar{T}}{ 363 | % \bar{P}} \big((y-1)\frac{\partial \bar{x_{1}}}{\partial \tau}+y 364 | % \frac{\partial\bar{x_{2}}}{\partial \tau}\big) $$ 365 | % 366 | %% 367 | % 4.1) Calculate the change in mole fraction due to diffusion 368 | % 369 | %% 370 | % $$ \frac{1}{Pe} \big(\frac{{\partial}^2 y}{\partial {Z}^2}+\frac{1}{ 371 | % \bar{P}}\frac{\partial \bar{P}}{\partial Z}\frac{\partial y}{ 372 | % \partial Z}-\frac{1}{\bar{T}}\frac{\partial \bar{T}}{\partial Z} 373 | % \frac{\partial y}{\partial Z} \big) $$ 374 | % 375 | %% 376 | dydt1(2:N+1) = (1/Pe)*(d2ydz2(2:N+1)+(dydz(2:N+1).*dpdz(2:N+1)./P(2:N+1))... 377 | -(dydz(2:N+1).*dTdz(2:N+1)./T(2:N+1))) ; 378 | % 379 | %% 380 | % 4.2) Calculate the change in mole fraction due to advection 381 | % 382 | %% 383 | % $$ -\bar{v}\frac{\partial y}{\partial Z} $$ 384 | % 385 | %% 386 | ypvt = yh(1:N+1).*Ph(1:N+1).*vh(1:N+1)./Th(1:N+1) ; 387 | dydt2(2:N+1) = -(T(2:N+1)./P(2:N+1)).*((ypvt(2:N+1)-ypvt(1:N))... 388 | -y(2:N+1).*(PvT(2:N+1)-PvT(1:N)))./dz ; 389 | % 390 | %% 391 | % 4.3) Calculate the change in mole fraction due to adsorption/desorption 392 | % 393 | %% 394 | % $$ \frac{\Psi \bar{T}}{\bar{P}} \big((y-1)\frac{\partial \bar{x_{1}}}{ 395 | % \partial \tau}+y\frac{\partial \bar{x_{2}}}{\partial \tau}\big) $$ 396 | % 397 | %% 398 | dydt3(2:N+1) = (phi*T(2:N+1)./P(2:N+1)).*((y(2:N+1)-1).*dx1dt(2:N+1)... 399 | + y(2:N+1).*dx2dt(2:N+1)) ; 400 | % 401 | %% 402 | % 4.4) Total sum of all mole fraction changes 403 | dydt(2:N+1) = dydt1(2:N+1) + dydt2(2:N+1) + dydt3(2:N+1) ; 404 | % 405 | %% Boundary Derivatives 406 | dPdt(1) = dPdt(2) ; 407 | dPdt(N+2) = tau*L/v_0.*(P_I/P_0-P(N+2)) ; 408 | dydt(1) = dydt(2) ; 409 | dydt(N+2) = dydt(N+1) ; 410 | dx1dt(1) = 0 ; 411 | dx2dt(1) = 0 ; 412 | dx1dt(N+2) = 0 ; 413 | dx2dt(N+2) = 0 ; 414 | dTdt(1) = dTdt(2) ; 415 | dTdt(N+2) = dTdt(N+1) ; 416 | % 417 | %% Export derivatives to output 418 | derivatives(1:N+2) = dPdt(1:N+2) ; 419 | derivatives(N+3:2*N+4) = dydt(1:N+2) ; 420 | derivatives(2*N+5:3*N+6) = dx1dt(1:N+2) ; 421 | derivatives(3*N+7:4*N+8) = dx2dt(1:N+2) ; 422 | derivatives(4*N+9:5*N+10) = dTdt(1:N+2) ; 423 | % 424 | end -------------------------------------------------------------------------------- /CycleSteps/FuncCoCPressurization.m: -------------------------------------------------------------------------------- 1 | function derivatives = FuncCoCPressurization(~, state_vars, params, isotherm_params) 2 | %#codegen 3 | %CoCPressurization: Calculate the change in state variables for CoC pres step 4 | % When coupled with MATLAB ode solvers, (ode15s), solve the system of PDEs 5 | % for the PSA step. The system of PDEs is solved by using the finite volume 6 | % method (FVM) where the spatial domain is discretized into N volumes. 7 | % Each of these volumes is assumed to have a uniform value for the state 8 | % variables. Weighted Essentially Non-oscillatory (WENO) is used to improve 9 | % calculations by calculating the value of the state variables at the walls 10 | % of the finite volumes. 11 | % 12 | % Input: 13 | % ~: This is the time variable. Since time is not used to calculate 14 | % the derivatives but a place holder is required for the ode solver, 15 | % this is used 16 | % 17 | % state_vars: This is the dimensionless state variables at time being 18 | % evaluated. The ordering is as followed [P_1, P_2,...,P_N+2, y_1, 19 | % ...,y_N+2, q_CO2_1,...,q_CO2_N+2, q_N2_1,...,q_N2_N+2, T_1,..., 20 | % T_N+2] 21 | % 22 | % params: All parameters needed for the simulation. Provided in the 23 | % ProcessInputParameters function file 24 | % 25 | % isotherm_params: All parameters for the isotherm. Provided in the 26 | % ProcessInputParameters function file 27 | % 28 | % Output: 29 | % derivatives: These are the temporal derivatives of the state 30 | % variables. The ordering is the same as the state variables 31 | % 32 | %% Retrieve process parameters 33 | N = params(1) ; 34 | deltaU_1 = params(2) ; 35 | deltaU_2 = params(3) ; 36 | ro_s = params(4) ; 37 | T_0 = params(5) ; 38 | epsilon = params(6) ; 39 | r_p = params(7) ; 40 | mu = params(8) ; 41 | R = params(9) ; 42 | v_0 = params(10) ; 43 | q_s0 = params(11) ; 44 | C_pg = params(12) ; 45 | C_pa = params(13) ; 46 | C_ps = params(14) ; 47 | D_m = params(15) ; 48 | K_z = params(16) ; 49 | P_0 = params(17) ; 50 | L = params(18) ; 51 | MW_CO2 = params(19) ; 52 | MW_N2 = params(20) ; 53 | k_1_LDF = params(21) ; 54 | k_2_LDF = params(22) ; 55 | y_0 = params(23) ; 56 | tau = params(24) ; 57 | % 58 | %% Initialize state variables 59 | P = zeros(N+2, 1) ; 60 | y = zeros(N+2, 1) ; 61 | x1 = zeros(N+2, 1) ; 62 | x2 = zeros(N+2, 1) ; 63 | T = zeros(N+2, 1) ; 64 | 65 | P(1:N+2) = state_vars(1:N+2) ; 66 | y(1:N+2) = max(state_vars(N+3:2*N+4), 0) ; 67 | x1(1:N+2) = max(state_vars(2*N+5:3*N+6), 0) ; 68 | x2(1:N+2) = state_vars(3*N+7:4*N+8) ; 69 | T(1:N+2) = state_vars(4*N+9:5*N+10) ; 70 | % 71 | %% Initialize all variables used in the function 72 | % Temporal derivatives 73 | derivatives = zeros(5*N+10, 1) ; 74 | dPdt = zeros(N+2, 1) ; 75 | dPdt1 = zeros(N+2, 1) ; 76 | dPdt2 = zeros(N+2, 1) ; 77 | dPdt3 = zeros(N+2, 1) ; 78 | dydt = zeros(N+2, 1) ; 79 | dydt1 = zeros(N+2, 1) ; 80 | dydt2 = zeros(N+2, 1) ; 81 | dydt3 = zeros(N+2, 1) ; 82 | dx1dt = zeros(N+2, 1) ; 83 | dx2dt = zeros(N+2, 1) ; 84 | dTdt = zeros(N+2, 1) ; 85 | dTdt1 = zeros(N+2, 1) ; 86 | dTdt2 = zeros(N+2, 1) ; 87 | dTdt3 = zeros(N+2, 1) ; 88 | % Spatial derivatives 89 | dpdz = zeros(N+2, 1) ; 90 | dpdzh = zeros(N+1, 1) ; 91 | dydz = zeros(N+2, 1) ; 92 | d2ydz2 = zeros(N+2, 1) ; 93 | dTdz = zeros(N+2, 1) ; 94 | d2Tdz2 = zeros(N+2, 1) ; 95 | % 96 | %% 97 | % The following estimations are parameters that are used. The axial 98 | % dispersion coefficient is calculated using the following equation 99 | % from Ruthvan "Pressure Swing Adsorption" 100 | % 101 | %% 102 | % $$ D_{L}= 0.7 D_{m} + r_{p} \cdot v_{0} $$ 103 | % 104 | %% 105 | % The Concentration of gas is calculated using the ideal gas law 106 | % 107 | %% 108 | % $$ C_{g}=\frac{\bar{P}}{R\bar{T}} \cdot \frac{P_{0}}{T_{0}} $$ 109 | % 110 | %% Calculate all parameters used 111 | dz = 1/N ; 112 | D_l = 0.7*D_m + v_0*r_p ; 113 | Pe = v_0*L/D_l ; 114 | phi = R*T_0*q_s0*(1-epsilon)/epsilon/P_0 ; 115 | ro_g = P(1:N+2).*P_0/R./T(1:N+2)/T_0 ; 116 | % 117 | %% Boundary Conditions 118 | % The boundary condtions for the CoC pressurization, corresponding to the 119 | % heavy product end of the column being opened and the the light product 120 | % end of the column being closed. A gas of known temperature and 121 | % composition being fed into the column through the heavy product end. 122 | % For the heavy product end, the following boundary condtions are used: 123 | % 124 | %% 125 | % $$ \bar{P} = \bar{P}_{L} \quad | Z=0^- $$ 126 | % 127 | % $$ y = y_{0} \quad | Z=0^- $$ 128 | % 129 | % $$ \frac{\partial \bar{P}}{\partial \tau} = \lambda(1.0-\bar{P}) \quad | Z=0^- $$ 130 | % 131 | %% 132 | y(1) = y_0 ; 133 | T(1) = T_0/T_0 ; 134 | if P(2) > P(1) 135 | P(1) = P(2) ; 136 | end 137 | % 138 | %% 139 | % For the light product end, the following boundary condtions are used: 140 | % 141 | %% 142 | % $$ \frac{\partial \bar{P}}{\partial \tau} = 0 \quad | Z=1^+ $$ 143 | % 144 | % $$ \frac{\partial y}{\partial \tau} = 0 \quad | Z=1^+ $$ 145 | % 146 | % $$ \frac{\partial \bar{T}}{\partial \tau} = 0 \quad | Z=1^+ $$ 147 | % 148 | %% 149 | y(N+2) = y(N+1) ; 150 | T(N+2) = T(N+1) ; 151 | P(N+2) = P(N+1) ; 152 | % 153 | %% Spatial Derivative Calculations 154 | % 155 | % *1st Derivatives* 156 | % 157 | % For the first derivative, the value at each volume is estimated based on 158 | % the values at the walls of the volumes. These values are estimated using 159 | % the Weighted Essentially NonOscillatory (WENO) scheme. 160 | % 161 | %% 162 | % $$ \frac{\partial f_j}{\partial Z}=\frac{f_{j+0.5}-f_{j-0.5}}{\Delta Z} $$ 163 | % 164 | %% 165 | % Weno mid point scheme is used to calculated the value at the boundary 166 | % between two volumes. Since the velocity shifts from negative to 167 | % positive, the WENO schemes under both velocities are calculated, and 168 | % then the point corresponding to the correct velocity is selected for 169 | % each node 170 | % 171 | %% 172 | % Determine pressure changes between two adjacent finite volume 173 | dP = P(2:N+2)-P(1:N+1) ; 174 | 175 | % Determine in which position the pressure change is positive, and in 176 | % which is negative 177 | idx_f = find(dP <= 0) ; 178 | idx_b = find(dP > 0) ; 179 | % 180 | %% 181 | % Pressure: at the center of the volumes and the walls 182 | Ph = zeros(N+1, 1); 183 | Ph_f = WENO(P, 'upwind') ; 184 | Ph_b = WENO(P, 'downwind') ; 185 | 186 | Ph(idx_f) = Ph_f(idx_f) ; 187 | Ph(idx_b) = Ph_b(idx_b) ; 188 | Ph(1) = P(1) ; 189 | Ph(N+1) = P(N+2) ; 190 | 191 | dpdz(2:N+1) = (Ph(2:N+1)-Ph(1:N))/dz ; 192 | dpdzh(2:N) = (P(3:N+1)-P(2:N))/dz ; 193 | dpdzh(1) = 2*(P(2)-P(1))/dz ; 194 | dpdzh(N+1) = 2*(P(N+2)-P(N+1))/dz ; 195 | % 196 | %% 197 | % Mole Fraction: at the center of the volumes 198 | yh = zeros(N+1, 1); 199 | yh_f = WENO(y, 'upwind') ; 200 | yh_b = WENO(y, 'downwind') ; 201 | 202 | yh(idx_f) = yh_f(idx_f) ; 203 | yh(idx_b) = yh_b(idx_b) ; 204 | 205 | if P(1) > P(2) 206 | yh(1) = y(1) ; 207 | else 208 | yh(1) = y(2) ; 209 | end 210 | yh(N+1) = y(N+2) ; 211 | 212 | dydz(2:N+1) = (yh(2:N+1)-yh(1:N))/dz ; 213 | % 214 | %% 215 | % Column Temperature: at the center of the volumes 216 | Th = zeros(N+1, 1); 217 | Th_f = WENO(T, 'upwind') ; 218 | Th_b = WENO(T, 'downwind') ; 219 | 220 | Th(idx_f) = Th_f(idx_f) ; 221 | Th(idx_b) = Th_b(idx_b) ; 222 | 223 | if P(1) > P(2) 224 | Th(1) = T(1) ; 225 | else 226 | Th(1) = T(2) ; 227 | end 228 | Th(N+1) = T(N+2) ; 229 | 230 | dTdz(2:N+1) = (Th(2:N+1)-Th(1:N))/dz ; 231 | % 232 | %% 233 | % *2nd Derivatives* 234 | % 235 | % The second derivatives are calculated based off of the values of the 236 | % nodes. It is only necessary to calculate the 2nd derivative of the 237 | % temperature and mole fraction. It is noted that at the ends of the 238 | % column, no diffusion/conduction is occuring, so these are set to 0 in 239 | % the value of the derivative. 240 | % 241 | %% 242 | % $$ \frac{{\partial}^2 f_{j}}{\partial {Z}^2} = \frac{f_{j+1}+f_{j-1}- 243 | % 2f_{j}}{{\Delta Z}^2} $$ 244 | % 245 | %% 246 | % Mole Fraction 247 | d2ydz2(3:N) = (y(4:N+1)+y(2:N-1)-2*y(3:N))/dz/dz ; 248 | d2ydz2(2) = (y(3)-y(2))/dz/dz ; 249 | d2ydz2(N+1) = (y(N)-y(N+1))/dz/dz ; 250 | % 251 | %% 252 | % Temperature 253 | d2Tdz2(3:N) = (T(4:N+1)+T(2:N-1)-2*T(3:N))/dz/dz ; 254 | d2Tdz2(2) = 4*(Th(2)+T(1)-2*T(2))/dz/dz ; 255 | d2Tdz2(N+1) = 4*(Th(N)+T(N+2)-2*T(N+1))/dz/dz ; 256 | % 257 | %% Velocity Calculations 258 | % Calculates the interstitial velocity of the gas at the walls of the 259 | % volumes based off of the pressure gradients. NOTE: bear in mind that 260 | % the velocity in the Ergun's equation is the superficial velocity, and 261 | % not the interstitial, that's why in the denominator in the viscous term 262 | % it is found epsilon^2 and not epsilon^3, the same for kinetic terms, 263 | % where it is found epsilon and not epsilon^3. Superficial velocity is 264 | % equal to interstitial velocity times the void fraction 265 | % 266 | %% 267 | % $$ U = v \cdot \varepsilon $$ 268 | % 269 | % $$ - \frac{\partial \bar{P}}{\partial Z}\frac{P_0}{L} = 270 | % \frac{150\mu(1-\varepsilon)^2}{4r_{p}\varepsilon^2}\bar{v}v_0 + 271 | % \frac{1.75(1-\varepsilon)}{2r_{p}\varepsilon} 272 | % (\sum_{i}y_{i}MW_{i}C_{g})\bar{v}^2{v_{0}}^2 $$ 273 | % 274 | %% 275 | ro_gh = (P_0/R/T_0)*Ph(1:N+1)./Th(1:N+1) ; 276 | 277 | viscous_term = 150*mu*(1-epsilon)^2/4/r_p^2/epsilon^2 ; 278 | kinetic_term_h = (ro_gh.*(MW_N2+(MW_CO2-MW_N2).*yh)).*(1.75*(1-epsilon)... 279 | /2/r_p/epsilon) ; 280 | 281 | % Velocities at walls of volumes 282 | vh = -sign(dpdzh).*(-viscous_term+(abs(viscous_term^2+4*kinetic_term_h... 283 | .*abs(dpdzh)*P_0/L)).^(.5))/2./kinetic_term_h/v_0 ; 284 | % 285 | %% Temporal Derivatives 286 | % 287 | %% 288 | % *1) Adsorbed Mass Balance (molar loading for components 1 and 2)* 289 | % Linear Driving Force (LDF) is used to calculate the uptake rate of 290 | % the gas. The LDF mass transfer coefficients are assumed to be 291 | % constant over the pressure and temperature range of importance. 292 | % 293 | %% 294 | % $$ \frac{\partial x_{i}}{\partial \tau}q_{s0}= k_{i}({q_{i}}^*-q_{i}) $$ 295 | % 296 | %% 297 | % 1.1) Calculate equilibrium molar loading 298 | q = Isotherm(y, P*P_0, T*T_0, isotherm_params) ; 299 | q_1 = q(:, 1)*ro_s ; 300 | q_2 = q(:, 2)*ro_s ; 301 | % 302 | %% 303 | % 1.2) Calculate the LDF parameter 304 | k_1 = k_1_LDF*L/v_0 ; 305 | k_2 = k_2_LDF*L/v_0 ; 306 | % 307 | %% 308 | % 1.3) Calculate the temporal derivative 309 | dx1dt(2:N+1) = k_1*(q_1(2:N+1)/q_s0 - x1(2:N+1)) ; 310 | dx2dt(2:N+1) = k_2*(q_2(2:N+1)/q_s0 - x2(2:N+1)) ; 311 | % 312 | %% 313 | % *2) Column Energy Balance (Column Temperature)* 314 | % For the energy balance in the column, it is assumed that: 315 | % * There is thermal equilibrium between the solid and gas phase 316 | % * Conduction occurs through both the solid and gas phase 317 | % * Advection only occurs in the gas phase 318 | % 319 | %% 320 | % $$ \big[ \varepsilon C_{g}C_{p,g}+(1-\varepsilon)(C_{p,s}\rho_{s}+ 321 | % C_{p,a}q_{s0})\big]\frac{\partial \bar{T}}{\partial \tau}= 322 | % \frac{K_z}{v_0L} \frac{\partial^2 \bar{T}}{\partial Z^2} 323 | % - \varepsilon C_{g}C_{p,g} \bar{v}\frac{\partial \bar{T}}{\partial Z} 324 | % + \sum_{i} (1-\varepsilon)(-\Delta H_{i})\frac{q_{s0}}{T_{0}}\frac{ 325 | % \partial \bar{x_{i}}}{\partial \tau} $$ 326 | % 327 | %% 328 | % [J/m^3/K] 329 | sink_term = ((1-epsilon)*(ro_s*C_ps+q_s0*C_pa)+(epsilon.*ro_g(2:N+1).*C_pg)) ; 330 | % 331 | %% 332 | % 2.1) Calculate the temperature change due to conduction in the column 333 | % (both solid and gas phase) 334 | % 335 | %% 336 | % $$ \frac{K_z}{v_0L} \frac{\partial^2 \bar{T}}{\partial Z^2} $$ 337 | % 338 | %% 339 | transfer_term = K_z./v_0./L ; 340 | dTdt1(2:N+1) = transfer_term.*d2Tdz2(2:N+1)./sink_term ; 341 | % 342 | %% 343 | % 2.2) Calculate the temperature change due to advection 344 | % 345 | %% 346 | % $$ -\varepsilon C_{g}C_{p,g} \bar{v}\frac{\partial \bar{T}}{\partial Z} $$ 347 | % 348 | %% 349 | PvT = Ph(1:N+1).*vh(1:N+1)./Th(1:N+1) ; 350 | Pv = Ph(1:N+1).*vh(1:N+1) ; 351 | dTdt2(2:N+1) = -epsilon.*C_pg.*P_0./R./T_0.*((Pv(2:N+1)-Pv(1:N))- ... 352 | T(2:N+1).*(PvT(2:N+1)-PvT(1:N)))./dz./sink_term ; 353 | % 354 | %% 355 | % 2.3) Calculate the temperature change due to adsorption/desoprtion enthalpy 356 | % 357 | %% 358 | % $$ \sum_{i} (1-\varepsilon)(-\Delta 359 | % H_{i})\frac{q_{s0}}{T_{0}}\frac{\partial \bar{x_{i}}}{\partial \tau} $$ 360 | % 361 | % $$ \Delta H_{i} = \Delta U_i - R\bar{T}T_{0} $$ 362 | % 363 | %% 364 | generation_term_1 = (1-epsilon).*q_s0.*(-(deltaU_1-R*T(2:N+1)*T_0))./T_0 ; 365 | generation_term_2 = (1-epsilon).*q_s0.*(-(deltaU_2-R*T(2:N+1)*T_0))./T_0 ; 366 | 367 | dTdt3(2:N+1) = (generation_term_1.*dx1dt(2:N+1)+... 368 | generation_term_2.*dx2dt(2:N+1))./sink_term ; 369 | % 370 | %% 371 | % 2.4) Total sum of all temperature derivatives 372 | dTdt(2:N+1) = dTdt1(2:N+1) + dTdt2(2:N+1) + dTdt3(2:N+1) ; 373 | % 374 | %% 375 | % *3) Total mass balance* 376 | % 377 | %% 378 | % $$ \frac{\partial \bar{P}}{\partial \tau} = -\frac{\partial( \bar{v} 379 | % \bar{P}/\bar{T})}{\partial Z} - \Psi \bar{T} \sum_{i}\frac{\partial 380 | % \bar{x_{i}}}{\partial \tau} + \frac{\bar{P}}{\bar{T}}\frac{\partial 381 | % \bar{T}}{\partial \tau} $$ 382 | % 383 | %% 384 | % 3.1) Calculate the change in pressure due to advection 385 | dPdt1(2:N+1) = -T(2:N+1).*(PvT(2:N+1)-PvT(1:N))./dz ; 386 | % 387 | %% 388 | % 3.2) Calculate the change in pressure due to adsorption/desorption 389 | dPdt2(2:N+1) = -phi*T(2:N+1).*(dx1dt(2:N+1)+dx2dt(2:N+1)) ; 390 | % 391 | %% 392 | % 3.3) Calculate the change in pressure due to temperature changes 393 | dPdt3(2:N+1) = P(2:N+1).*dTdt(2:N+1)./T(2:N+1) ; 394 | % 395 | %% 396 | % 3.4) Total sum of all presure changes 397 | dPdt(2:N+1) = dPdt1(2:N+1) + dPdt2(2:N+1) + dPdt3(2:N+1) ; 398 | % 399 | %% 400 | % *4) Component Mass Balance (Based on Mole Fraction)* 401 | % 402 | %% 403 | % $$ \frac{\partial y}{\partial \tau} = \frac{1}{Pe} \big(\frac{{ 404 | % \partial}^2 y}{\partial {Z}^2}+\frac{1}{\bar{P}}\frac{\partial 405 | % \bar{P}}{\partial Z}\frac{\partial y}{\partial Z}-\frac{1}{\bar{T}} 406 | % \frac{\partial \bar{T}}{\partial Z}\frac{\partial y}{\partial Z} 407 | % \big)-\bar{v}\frac{\partial y}{\partial Z}+\frac{\Psi \bar{T}}{ 408 | % \bar{P}} \big((y-1)\frac{\partial \bar{x_{1}}}{\partial \tau}+y 409 | % \frac{\partial\bar{x_{2}}}{\partial \tau}\big) $$ 410 | % 411 | %% 412 | % 4.1) Calculate the change in mole fraction due to diffusion 413 | % 414 | %% 415 | % $$ \frac{1}{Pe} \big(\frac{{\partial}^2 y}{\partial {Z}^2}+\frac{1}{ 416 | % \bar{P}}\frac{\partial \bar{P}}{\partial Z}\frac{\partial y}{ 417 | % \partial Z}-\frac{1}{\bar{T}}\frac{\partial \bar{T}}{\partial Z} 418 | % \frac{\partial y}{\partial Z} \big) $$ 419 | % 420 | %% 421 | dydt1(2:N+1) = (1/Pe)*(d2ydz2(2:N+1)+(dydz(2:N+1).*dpdz(2:N+1)./P(2:N+1))... 422 | -(dydz(2:N+1).*dTdz(2:N+1)./T(2:N+1))) ; 423 | % 424 | %% 425 | % 4.2) Calculate the change in mole fraction due to advection 426 | % 427 | %% 428 | % $$ -\bar{v}\frac{\partial y}{\partial Z} $$ 429 | % 430 | %% 431 | ypvt = yh(1:N+1).*Ph(1:N+1).*vh(1:N+1)./Th(1:N+1) ; 432 | dydt2(2:N+1) = -(T(2:N+1)./P(2:N+1)).*((ypvt(2:N+1)-ypvt(1:N))... 433 | -y(2:N+1).*(PvT(2:N+1)-PvT(1:N)))./dz ; 434 | % 435 | %% 436 | % 4.3) Calculate the change in mole fraction due to adsorption/desorption 437 | % 438 | %% 439 | % $$ \frac{\Psi \bar{T}}{\bar{P}} \big((y-1)\frac{\partial \bar{x_{1}}}{ 440 | % \partial \tau}+y\frac{\partial \bar{x_{2}}}{\partial \tau}\big) $$ 441 | % 442 | %% 443 | dydt3(2:N+1) = (phi*T(2:N+1)./P(2:N+1)).*((y(2:N+1)-1).*dx1dt(2:N+1)... 444 | + y(2:N+1).*dx2dt(2:N+1)) ; 445 | % 446 | %% 447 | % 4.4) Total sum of all mole fraction changes 448 | dydt(2:N+1) = dydt1(2:N+1) + dydt2(2:N+1) + dydt3(2:N+1) ; 449 | % 450 | %% Boundary Derivatives 451 | %dPdt(1) = tau*(1-P(1)) ; 452 | dPdt(1) = tau*L/v_0*(1-P(1)) ; 453 | dPdt(N+2) = dPdt(N+1) ; 454 | dydt(1) = 0 ; 455 | dydt(N+2) = dydt(N+1) ; 456 | dx1dt(1) = 0 ; 457 | dx2dt(1) = 0 ; 458 | dx1dt(N+2) = 0 ; 459 | dx2dt(N+2) = 0 ; 460 | dTdt(1) = 0 ; 461 | dTdt(N+2) = dTdt(N+1) ; 462 | % 463 | %% Export derivatives to output 464 | derivatives(1:N+2) = dPdt(1:N+2) ; 465 | derivatives(N+3:2*N+4) = dydt(1:N+2) ; 466 | derivatives(2*N+5:3*N+6) = dx1dt(1:N+2) ; 467 | derivatives(3*N+7:4*N+8) = dx2dt(1:N+2) ; 468 | derivatives(4*N+9:5*N+10) = dTdt(1:N+2) ; 469 | % 470 | end -------------------------------------------------------------------------------- /CycleSteps/FuncHeavyReflux.m: -------------------------------------------------------------------------------- 1 | function derivatives = FuncHeavyReflux(~, state_vars, params, isotherm_params) 2 | %#codegen 3 | %HeavyReflux: Calculate the change in state variables for heavy reflux step 4 | % When coupled with MATLAB ode solvers, (ode15s), solve the system of PDEs 5 | % for the PSA step. The system of PDEs is solved by using the finite volume 6 | % method (FVM) where the spatial domain is discretized into N volumes. 7 | % Each of these volumes is assumed to have a uniform value for the state 8 | % variables. Weighted Essentially Non-oscillatory (WENO) is used to improve 9 | % calculations by calculating the value of the state variables at the walls 10 | % of the finite volumes. 11 | % 12 | % Input: 13 | % ~: This is the time variable. Since time is not used to calculate 14 | % the derivatives but a place holder is required for the ode solver, 15 | % this is used 16 | % 17 | % state_vars: This is the dimensionless state variables at time being 18 | % evaluated. The ordering is as followed [P_1, P_2,...,P_N+2, y_1, 19 | % ...,y_N+2, q_CO2_1,...,q_CO2_N+2, q_N2_1,...,q_N2_N+2, T_1,..., 20 | % T_N+2] 21 | % 22 | % params: All parameters needed for the simulation. Provided in the 23 | % ProcessInputParameters function file 24 | % 25 | % isotherm_params: All parameters for the isotherm. Provided in the 26 | % ProcessInputParameters function file 27 | % 28 | % Output: 29 | % derivatives: These are the temporal derivatives of the state 30 | % variables. The ordering is the same as the state variables 31 | % 32 | %% Retrieve process parameters 33 | N = params(1) ; 34 | deltaU_1 = params(2) ; 35 | deltaU_2 = params(3) ; 36 | ro_s = params(4) ; 37 | T_0 = params(5) ; 38 | epsilon = params(6) ; 39 | r_p = params(7) ; 40 | mu = params(8) ; 41 | R = params(9) ; 42 | v_0 = params(10) ; 43 | q_s0 = params(11) ; 44 | C_pg = params(12) ; 45 | C_pa = params(13) ; 46 | C_ps = params(14) ; 47 | D_m = params(15) ; 48 | K_z = params(16) ; 49 | P_0 = params(17) ; 50 | L = params(18) ; 51 | MW_CO2 = params(19) ; 52 | MW_N2 = params(20) ; 53 | k_1_LDF = params(21) ; 54 | k_2_LDF = params(22) ; 55 | P_inlet = params(26) ; 56 | y_HR = params(33) ; 57 | T_HR = params(34) ; 58 | ndot_HR = params(35) ; 59 | % 60 | %% Initialize state variables 61 | P = zeros(N+2, 1) ; 62 | y = zeros(N+2, 1) ; 63 | x1 = zeros(N+2, 1) ; 64 | x2 = zeros(N+2, 1) ; 65 | T = zeros(N+2, 1) ; 66 | 67 | P(1:N+2) = state_vars(1:N+2) ; 68 | y(1:N+2) = max(state_vars(N+3:2*N+4), 0) ; 69 | x1(1:N+2) = max(state_vars(2*N+5:3*N+6), 0) ; 70 | x2(1:N+2) = state_vars(3*N+7:4*N+8) ; 71 | T(1:N+2) = state_vars(4*N+9:5*N+10) ; 72 | % 73 | %% Initialize all variables used in the function 74 | % Temporal derivatives 75 | derivatives = zeros(5*N+10, 1) ; 76 | dPdt = zeros(N+2, 1) ; 77 | dPdt1 = zeros(N+2, 1) ; 78 | dPdt2 = zeros(N+2, 1) ; 79 | dPdt3 = zeros(N+2, 1) ; 80 | dydt = zeros(N+2, 1) ; 81 | dydt1 = zeros(N+2, 1) ; 82 | dydt2 = zeros(N+2, 1) ; 83 | dydt3 = zeros(N+2, 1) ; 84 | dx1dt = zeros(N+2, 1) ; 85 | dx2dt = zeros(N+2, 1) ; 86 | dTdt = zeros(N+2, 1) ; 87 | dTdt1 = zeros(N+2, 1) ; 88 | dTdt2 = zeros(N+2, 1) ; 89 | dTdt3 = zeros(N+2, 1) ; 90 | % Spatial derivatives 91 | dpdz = zeros(N+2, 1) ; 92 | dpdzh = zeros(N+1, 1) ; 93 | dydz = zeros(N+2, 1) ; 94 | d2ydz2 = zeros(N+2, 1) ; 95 | dTdz = zeros(N+2, 1) ; 96 | d2Tdz2 = zeros(N+2, 1) ; 97 | % 98 | %% 99 | % The following estimations are parameters that are used. The axial 100 | % dispersion coefficient is calculated using the following equation 101 | % from Ruthvan "Pressure Swing Adsorption" 102 | % 103 | %% 104 | % $$ D_{L}= 0.7 D_{m} + r_{p} \cdot v_{0} $$ 105 | % 106 | %% 107 | % The Concentration of gas is calculated using the ideal gas law 108 | % 109 | %% 110 | % $$ C_{g}=\frac{\bar{P}}{R\bar{T}} \cdot \frac{P_{0}}{T_{0}} $$ 111 | % 112 | %% Calculate all parameters used 113 | dz = 1/N ; 114 | D_l = 0.7*D_m + v_0*r_p ; 115 | Pe = v_0*L/D_l ; 116 | phi = R*T_0*q_s0*(1-epsilon)/epsilon/P_0 ; 117 | ro_g = P(1:N+2).*P_0/R./T(1:N+2)/T_0 ; 118 | % 119 | %% Boundary Conditions 120 | % The boundary condtions for the heavy reflux, corresponding to both ends 121 | % (light and heavy product end) of the column being opened. A gas of known 122 | % temperature and composition being fed into the column through the heavy 123 | % product end. For the heavy product end, the following boundary condtions 124 | % are used: 125 | % 126 | %% 127 | % $$ \bar{P} = 1+\Delta \quad $$ or $$ \quad \bar{v} = 1 \quad | Z=0^- $$ 128 | % 129 | % $$ y = y_{HR} \quad | Z=0^- $$ 130 | % 131 | % $$ \bar{T} = \frac{T_{HR}}{T_{0}} \quad | Z=0^- $$ 132 | % 133 | %% 134 | % It is noted that for the inlet pressure, this is either set as a constant 135 | % or the inlet velocity is set as a constant and the pressure changes to 136 | % satisfy the velocity constraint at the inlet 137 | 138 | y(1) = y_HR ; 139 | T(1) = T_HR/T_0 ; 140 | 141 | if params(end) == 1 142 | % Inlet pressure is specified as a constant 143 | P(1) = P_inlet ; 144 | elseif params(end) == 0 145 | % Inlet velocity is specified as a constant 146 | vh = zeros(N+1, 1) ; 147 | 148 | MW = MW_N2+(MW_CO2-MW_N2)*y(1) ; 149 | 150 | a_1 = 150*mu*(1-epsilon)^2*dz*L/2/4/r_p^2/epsilon^3/T(1)/T_0/R ; 151 | a_2_1 = 1.75*(1-epsilon)/2/r_p/epsilon/epsilon/epsilon*dz*L/2 ; 152 | a_2 = a_2_1/R/T(1)/T_0*ndot_HR*MW ; 153 | 154 | a = a_1+a_2 ; 155 | b = P(2)/T(1)*P_0/R/T_0 ; 156 | c = -ndot_HR ; 157 | 158 | vh(1) = (-b+sqrt(b^2-4*a*c))/2/a/v_0 ; 159 | 160 | a_p = a_1*T(1)*T_0*R ; 161 | b_p = a_2_1*MW/R/T(1)/T_0 ; 162 | 163 | P(1) = ((a_p*vh(1)*v_0+P(2)*P_0)./(1-b_p*vh(1)*v_0*vh(1)*v_0))/P_0 ; 164 | else 165 | error('Please specify whether inlet velocity or pressure is constant for the feed step') 166 | end 167 | % 168 | %% 169 | % For the light product end, the following boundary condtions are used: 170 | % 171 | %% 172 | % $$ \bar{P} = 1 \quad | Z=1^+ $$ 173 | % 174 | % $$ \frac{\partial y}{\partial \tau} = 0 \quad | Z=1^+ $$ 175 | % 176 | % $$ \frac{\partial \bar{T}}{\partial \tau} = 0 \quad | Z=1^+ $$ 177 | % 178 | %% 179 | y(N+2) = y(N+1) ; 180 | T(N+2) = T(N+1) ; 181 | if P(N+1) >= 1 182 | P(N+2) = 1 ; 183 | else 184 | P(N+2) = P(N+1) ; 185 | end 186 | % 187 | %% Spatial Derivative Calculations 188 | % 189 | % *1st Derivatives* 190 | % 191 | % For the first derivative, the value at each volume is estimated based on 192 | % the values at the walls of the volumes. These values are estimated using 193 | % the Weighted Essentially NonOscillatory (WENO) scheme. 194 | % 195 | %% 196 | % $$ \frac{\partial f_j}{\partial Z}=\frac{f_{j+0.5}-f_{j-0.5}}{\Delta Z} $$ 197 | % 198 | %% 199 | % Pressure: at the center of the volumes and the walls 200 | 201 | Ph = WENO(P, 'upwind') ; 202 | 203 | dpdz(2:N+1) = (Ph(2:N+1)-Ph(1:N))/dz ; 204 | dpdzh(2:N) = (P(3:N+1)-P(2:N))/dz ; 205 | dpdzh(1) = 2*(P(2)-P(1))/dz ; 206 | dpdzh(N+1) = 2*(P(N+2)-P(N+1))/dz ; 207 | % 208 | %% 209 | % Mole Fraction: at the center of the volumes 210 | 211 | yh = WENO(y, 'upwind') ; 212 | 213 | dydz(2:N+1) = (yh(2:N+1)-yh(1:N))/dz ; 214 | % 215 | %% 216 | % Temperature: at the center of the volumes 217 | 218 | Th = WENO(T, 'upwind') ; 219 | 220 | dTdz(2:N+1) = (Th(2:N+1)-Th(1:N))/dz ; 221 | % 222 | %% 223 | % *2nd Derivatives* 224 | % 225 | % The second derivatives are calculated based off of the values of the 226 | % nodes. It is only necessary to calculate the 2nd derivative of the 227 | % temperature and mole fraction. It is noted that at the ends of the 228 | % column, no diffusion/conduction is occuring, so these are set to 0 in 229 | % the value of the derivative. 230 | % 231 | %% 232 | % $$ \frac{{\partial}^2 f_{j}}{\partial {Z}^2} = \frac{f_{j+1}+f_{j-1}- 233 | % 2f_{j}}{{\Delta Z}^2} $$ 234 | % 235 | %% 236 | % Mole Fraction 237 | d2ydz2(3:N) = (y(4:N+1)+y(2:N-1)-2*y(3:N))/dz/dz ; 238 | d2ydz2(2) = (y(3)-y(2))/dz/dz ; 239 | d2ydz2(N+1) = (y(N)-y(N+1))/dz/dz ; 240 | % 241 | %% 242 | % Temperature 243 | d2Tdz2(3:N) = (T(4:N+1)+T(2:N-1)-2*T(3:N))/dz/dz ; 244 | d2Tdz2(2) = 4*(Th(2)+T(1)-2*T(2))/dz/dz ; 245 | d2Tdz2(N+1) = 4*(Th(N)+T(N+2)-2*T(N+1))/dz/dz ; 246 | % 247 | %% Velocity Calculations 248 | % Calculates the interstitial velocity of the gas at the walls of the 249 | % volumes based off of the pressure gradients. NOTE: bear in mind that 250 | % the velocity in the Ergun's equation is the superficial velocity, and 251 | % not the interstitial, that's why in the denominator in the viscous term 252 | % it is found epsilon^2 and not epsilon^3, the same for kinetic terms, 253 | % where it is found epsilon and not epsilon^3. Superficial velocity is 254 | % equal to interstitial velocity times the void fraction 255 | % 256 | %% 257 | % $$ U = v \cdot \varepsilon $$ 258 | % 259 | % $$ - \frac{\partial \bar{P}}{\partial Z}\frac{P_0}{L} = 260 | % \frac{150\mu(1-\varepsilon)^2}{4r_{p}\varepsilon^2}\bar{v}v_0 + 261 | % \frac{1.75(1-\varepsilon)}{2r_{p}\varepsilon} 262 | % (\sum_{i}y_{i}MW_{i}C_{g})\bar{v}^2{v_{0}}^2 $$ 263 | % 264 | %% 265 | ro_gh = (P_0/R/T_0)*Ph(1:N+1)./Th(1:N+1) ; 266 | 267 | viscous_term = 150*mu*(1-epsilon)^2/4/r_p^2/epsilon^2 ; 268 | kinetic_term_h = (ro_gh.*(MW_N2+(MW_CO2-MW_N2).*yh)).*(1.75*(1-epsilon)... 269 | /2/r_p/epsilon) ; 270 | 271 | % Velocities at walls of volumes 272 | vh = -sign(dpdzh).*(-viscous_term+(abs(viscous_term^2+4*kinetic_term_h... 273 | .*abs(dpdzh)*P_0/L)).^(.5))/2./kinetic_term_h/v_0 ; 274 | % 275 | %% Temporal Derivatives 276 | % 277 | %% 278 | % *1) Adsorbed Mass Balance (molar loading for components 1 and 2)* 279 | % Linear Driving Force (LDF) is used to calculate the uptake rate of 280 | % the gas. The LDF mass transfer coefficients are assumed to be 281 | % constant over the pressure and temperature range of importance. 282 | % 283 | %% 284 | % $$ \frac{\partial x_{i}}{\partial \tau}q_{s0}= k_{i}({q_{i}}^*-q_{i}) $$ 285 | % 286 | %% 287 | % 1.1) Calculate equilibrium molar loading 288 | q = Isotherm(y, P*P_0, T*T_0, isotherm_params) ; 289 | q_1 = q(:, 1)*ro_s ; 290 | q_2 = q(:, 2)*ro_s ; 291 | % 292 | %% 293 | % 1.2) Calculate the LDF parameter 294 | k_1 = k_1_LDF*L/v_0 ; 295 | k_2 = k_2_LDF*L/v_0 ; 296 | % 297 | %% 298 | % 1.3) Calculate the temporal derivative 299 | dx1dt(2:N+1) = k_1*(q_1(2:N+1)/q_s0 - x1(2:N+1)) ; 300 | dx2dt(2:N+1) = k_2*(q_2(2:N+1)/q_s0 - x2(2:N+1)) ; 301 | % 302 | %% 303 | % *2) Column Energy Balance (Column Temperature)* 304 | % For the energy balance in the column, it is assumed that: 305 | % * There is thermal equilibrium between the solid and gas phase 306 | % * Conduction occurs through both the solid and gas phase 307 | % * Advection only occurs in the gas phase 308 | % 309 | %% 310 | % $$ \big[ \varepsilon C_{g}C_{p,g}+(1-\varepsilon)(C_{p,s}\rho_{s}+ 311 | % C_{p,a}q_{s0})\big]\frac{\partial \bar{T}}{\partial \tau}= 312 | % \frac{K_z}{v_0L} \frac{\partial^2 \bar{T}}{\partial Z^2} 313 | % - \varepsilon C_{g}C_{p,g} \bar{v}\frac{\partial \bar{T}}{\partial Z} 314 | % + \sum_{i} (1-\varepsilon)(-\Delta H_{i})\frac{q_{s0}}{T_{0}}\frac{ 315 | % \partial \bar{x_{i}}}{\partial \tau} $$ 316 | % 317 | %% 318 | % [J/m^3/K] 319 | sink_term = ((1-epsilon)*(ro_s*C_ps+q_s0*C_pa)+(epsilon.*ro_g(2:N+1).*C_pg)) ; 320 | % 321 | %% 322 | % 2.1) Calculate the temperature change due to conduction in the column 323 | % (both solid and gas phase) 324 | % 325 | %% 326 | % $$ \frac{K_z}{v_0L} \frac{\partial^2 \bar{T}}{\partial Z^2} $$ 327 | % 328 | %% 329 | transfer_term = K_z./v_0./L ; 330 | dTdt1(2:N+1) = transfer_term.*d2Tdz2(2:N+1)./sink_term ; 331 | % 332 | %% 333 | % 2.2) Calculate the temperature change due to advection 334 | % 335 | %% 336 | % $$ -\varepsilon C_{g}C_{p,g} \bar{v}\frac{\partial \bar{T}}{\partial Z} $$ 337 | % 338 | %% 339 | PvT = Ph(1:N+1).*vh(1:N+1)./Th(1:N+1) ; 340 | Pv = Ph(1:N+1).*vh(1:N+1) ; 341 | dTdt2(2:N+1) = -epsilon.*C_pg.*P_0./R./T_0.*((Pv(2:N+1)-Pv(1:N))- ... 342 | T(2:N+1).*(PvT(2:N+1)-PvT(1:N)))./dz./sink_term ; 343 | % 344 | %% 345 | % 2.3) Calculate the temperature change due to adsorption/desoprtion enthalpy 346 | % 347 | %% 348 | % $$ \sum_{i} (1-\varepsilon)(-\Delta 349 | % H_{i})\frac{q_{s0}}{T_{0}}\frac{\partial \bar{x_{i}}}{\partial \tau} $$ 350 | % 351 | % $$ \Delta H_{i} = \Delta U_i - R\bar{T}T_{0} $$ 352 | % 353 | %% 354 | generation_term_1 = (1-epsilon).*q_s0.*(-(deltaU_1-R*T(2:N+1)*T_0))./T_0 ; 355 | generation_term_2 = (1-epsilon).*q_s0.*(-(deltaU_2-R*T(2:N+1)*T_0))./T_0 ; 356 | 357 | dTdt3(2:N+1) = (generation_term_1.*dx1dt(2:N+1)+... 358 | generation_term_2.*dx2dt(2:N+1))./sink_term ; 359 | % 360 | %% 361 | % 2.4) Total sum of all temperature derivatives 362 | dTdt(2:N+1) = dTdt1(2:N+1) + dTdt2(2:N+1) + dTdt3(2:N+1) ; 363 | % 364 | %% 365 | % *3) Total mass balance* 366 | % 367 | %% 368 | % $$ \frac{\partial \bar{P}}{\partial \tau} = -\frac{\partial( \bar{v} 369 | % \bar{P}/\bar{T})}{\partial Z} - \Psi \bar{T} \sum_{i}\frac{\partial 370 | % \bar{x_{i}}}{\partial \tau} + \frac{\bar{P}}{\bar{T}}\frac{\partial 371 | % \bar{T}}{\partial \tau} $$ 372 | % 373 | %% 374 | % 3.1) Calculate the change in pressure due to advection 375 | dPdt1(2:N+1) = -T(2:N+1).*(PvT(2:N+1)-PvT(1:N))./dz ; 376 | % 377 | %% 378 | % 3.2) Calculate the change in pressure due to adsorption/desorption 379 | dPdt2(2:N+1) = -phi*T(2:N+1).*(dx1dt(2:N+1)+dx2dt(2:N+1)) ; 380 | % 381 | %% 382 | % 3.3) Calculate the change in pressure due to temperature changes 383 | dPdt3(2:N+1) = P(2:N+1).*dTdt(2:N+1)./T(2:N+1) ; 384 | % 385 | %% 386 | % 3.4) Total sum of all presure changes 387 | dPdt(2:N+1) = dPdt1(2:N+1) + dPdt2(2:N+1) + dPdt3(2:N+1) ; 388 | % 389 | %% 390 | % *4) Component Mass Balance (Based on Mole Fraction)* 391 | % 392 | %% 393 | % $$ \frac{\partial y}{\partial \tau} = \frac{1}{Pe} \big(\frac{{ 394 | % \partial}^2 y}{\partial {Z}^2}+\frac{1}{\bar{P}}\frac{\partial 395 | % \bar{P}}{\partial Z}\frac{\partial y}{\partial Z}-\frac{1}{\bar{T}} 396 | % \frac{\partial \bar{T}}{\partial Z}\frac{\partial y}{\partial Z} 397 | % \big)-\bar{v}\frac{\partial y}{\partial Z}+\frac{\Psi \bar{T}}{ 398 | % \bar{P}} \big((y-1)\frac{\partial \bar{x_{1}}}{\partial \tau}+y 399 | % \frac{\partial\bar{x_{2}}}{\partial \tau}\big) $$ 400 | % 401 | %% 402 | % 4.1) Calculate the change in mole fraction due to diffusion 403 | % 404 | %% 405 | % $$ \frac{1}{Pe} \big(\frac{{\partial}^2 y}{\partial {Z}^2}+\frac{1}{ 406 | % \bar{P}}\frac{\partial \bar{P}}{\partial Z}\frac{\partial y}{ 407 | % \partial Z}-\frac{1}{\bar{T}}\frac{\partial \bar{T}}{\partial Z} 408 | % \frac{\partial y}{\partial Z} \big) $$ 409 | % 410 | %% 411 | dydt1(2:N+1) = (1/Pe)*(d2ydz2(2:N+1)+(dydz(2:N+1).*dpdz(2:N+1)./P(2:N+1))... 412 | -(dydz(2:N+1).*dTdz(2:N+1)./T(2:N+1))) ; 413 | % 414 | %% 415 | % 4.2) Calculate the change in mole fraction due to advection 416 | % 417 | %% 418 | % $$ -\bar{v}\frac{\partial y}{\partial Z} $$ 419 | % 420 | %% 421 | ypvt = yh(1:N+1).*Ph(1:N+1).*vh(1:N+1)./Th(1:N+1) ; 422 | dydt2(2:N+1) = -(T(2:N+1)./P(2:N+1)).*((ypvt(2:N+1)-ypvt(1:N))... 423 | -y(2:N+1).*(PvT(2:N+1)-PvT(1:N)))./dz ; 424 | % 425 | %% 426 | % 4.3) Calculate the change in mole fraction due to adsorption/desorption 427 | % 428 | %% 429 | % $$ \frac{\Psi \bar{T}}{\bar{P}} \big((y-1)\frac{\partial \bar{x_{1}}}{ 430 | % \partial \tau}+y\frac{\partial \bar{x_{2}}}{\partial \tau}\big) $$ 431 | % 432 | %% 433 | dydt3(2:N+1) = (phi*T(2:N+1)./P(2:N+1)).*((y(2:N+1)-1).*dx1dt(2:N+1)... 434 | + y(2:N+1).*dx2dt(2:N+1)) ; 435 | % 436 | %% 437 | % 4.4) Total sum of all mole fraction changes 438 | dydt(2:N+1) = dydt1(2:N+1) + dydt2(2:N+1) + dydt3(2:N+1) ; 439 | % 440 | %% Boundary Derivatives 441 | dPdt(1) = 0 ; 442 | dPdt(N+2) = 0 ; 443 | dydt(1) = 0 ; 444 | dydt(N+2) = dydt(N+1) ; 445 | dx1dt(1) = 0 ; 446 | dx2dt(1) = 0 ; 447 | dx1dt(N+2) = 0 ; 448 | dx2dt(N+2) = 0 ; 449 | dTdt(1) = 0 ; 450 | dTdt(N+2) = dTdt(N+1) ; 451 | % 452 | %% Export derivatives to output 453 | derivatives(1:N+2) = dPdt(1:N+2) ; 454 | derivatives(N+3:2*N+4) = dydt(1:N+2) ; 455 | derivatives(2*N+5:3*N+6) = dx1dt(1:N+2) ; 456 | derivatives(3*N+7:4*N+8) = dx2dt(1:N+2) ; 457 | derivatives(4*N+9:5*N+10) = dTdt(1:N+2) ; 458 | % 459 | end -------------------------------------------------------------------------------- /CycleSteps/FuncLightReflux.m: -------------------------------------------------------------------------------- 1 | function derivatives = FuncLightReflux(~, state_vars, params, isotherm_params) 2 | %#codegen 3 | %LightReflux: Calculate the change in state variables for Light Reflux step 4 | % When coupled with MATLAB ode solvers, (ode15s), solve the system of PDEs 5 | % for the PSA step. The system of PDEs is solved by using the finite volume 6 | % method (FVM) where the spatial domain is discretized into N volumes. 7 | % Each of these volumes is assumed to have a uniform value for the state 8 | % variables. Weighted Essentially Non-oscillatory (WENO) is used to improve 9 | % calculations by calculating the value of the state variables at the walls 10 | % of the finite volumes. 11 | % 12 | % Input: 13 | % ~: This is the time variable. Since time is not used to calculate 14 | % the derivatives but a place holder is required for the ode solver, 15 | % this is used 16 | % 17 | % state_vars: This is the dimensionless state variables at time being 18 | % evaluated. The ordering is as followed [P_1, P_2,...,P_N+2, y_1, 19 | % ...,y_N+2, q_CO2_1,...,q_CO2_N+2, q_N2_1,...,q_N2_N+2, T_1,..., 20 | % T_N+2] 21 | % 22 | % params: All parameters needed for the simulation. Provided in the 23 | % ProcessInputParameters function file 24 | % 25 | % isotherm_params: All parameters for the isotherm. Provided in the 26 | % ProcessInputParameters function file 27 | % 28 | % Output: 29 | % derivatives: These are the temporal derivatives of the state 30 | % variables. The ordering is the same as the state variables 31 | % 32 | %% Retrieve process parameters 33 | N = params(1) ; 34 | deltaU_1 = params(2) ; 35 | deltaU_2 = params(3) ; 36 | ro_s = params(4) ; 37 | T_0 = params(5) ; 38 | epsilon = params(6) ; 39 | r_p = params(7) ; 40 | mu = params(8) ; 41 | R = params(9) ; 42 | v_0 = params(10) ; 43 | q_s0 = params(11) ; 44 | C_pg = params(12) ; 45 | C_pa = params(13) ; 46 | C_ps = params(14) ; 47 | D_m = params(15) ; 48 | K_z = params(16) ; 49 | P_0 = params(17) ; 50 | L = params(18) ; 51 | MW_CO2 = params(19) ; 52 | MW_N2 = params(20) ; 53 | k_1_LDF = params(21) ; 54 | k_2_LDF = params(22) ; 55 | P_l = params(25) ; 56 | alpha = params(30) ; 57 | y_LR = params(27) ; 58 | T_LR = params(28) ; 59 | ndot_LP = params(29)*alpha ; 60 | % 61 | %% Initialize state variables 62 | P = zeros(N+2, 1) ; 63 | y = zeros(N+2, 1) ; 64 | x1 = zeros(N+2, 1) ; 65 | x2 = zeros(N+2, 1) ; 66 | T = zeros(N+2, 1) ; 67 | 68 | P(1:N+2) = state_vars(1:N+2) ; 69 | y(1:N+2) = max(state_vars(N+3:2*N+4), 0) ; 70 | x1(1:N+2) = max(state_vars(2*N+5:3*N+6), 0) ; 71 | x2(1:N+2) = state_vars(3*N+7:4*N+8) ; 72 | T(1:N+2) = state_vars(4*N+9:5*N+10) ; 73 | % 74 | %% Initialize all variables used in the function 75 | % Temporal derivatives 76 | derivatives = zeros(5*N+10, 1) ; 77 | dPdt = zeros(N+2, 1) ; 78 | dPdt1 = zeros(N+2, 1) ; 79 | dPdt2 = zeros(N+2, 1) ; 80 | dPdt3 = zeros(N+2, 1) ; 81 | dydt = zeros(N+2, 1) ; 82 | dydt1 = zeros(N+2, 1) ; 83 | dydt2 = zeros(N+2, 1) ; 84 | dydt3 = zeros(N+2, 1) ; 85 | dx1dt = zeros(N+2, 1) ; 86 | dx2dt = zeros(N+2, 1) ; 87 | dTdt = zeros(N+2, 1) ; 88 | dTdt1 = zeros(N+2, 1) ; 89 | dTdt2 = zeros(N+2, 1) ; 90 | dTdt3 = zeros(N+2, 1) ; 91 | % Spatial derivatives 92 | dpdz = zeros(N+2, 1) ; 93 | dpdzh = zeros(N+1, 1) ; 94 | dydz = zeros(N+2, 1) ; 95 | d2ydz2 = zeros(N+2, 1) ; 96 | dTdz = zeros(N+2, 1) ; 97 | d2Tdz2 = zeros(N+2, 1) ; 98 | % 99 | %% 100 | % The following estimations are parameters that are used. The axial 101 | % dispersion coefficient is calculated using the following equation 102 | % from Ruthvan "Pressure Swing Adsorption" 103 | % 104 | %% 105 | % $$ D_{L}= 0.7 D_{m} + r_{p} \cdot v_{0} $$ 106 | % 107 | %% 108 | % The Concentration of gas is calculated using the ideal gas law 109 | % 110 | %% 111 | % $$ C_{g}=\frac{\bar{P}}{R\bar{T}} \cdot \frac{P_{0}}{T_{0}} $$ 112 | % 113 | %% Calculate all parameters used 114 | dz = 1/N ; 115 | D_l = 0.7*D_m + v_0*r_p ; 116 | Pe = v_0*L/D_l ; 117 | phi = R*T_0*q_s0*(1-epsilon)/epsilon/P_0 ; 118 | ro_g = P(1:N+2).*P_0/R./T(1:N+2)/T_0 ; 119 | % 120 | %% Boundary Conditions 121 | % The boundary condtions for the light reflux, corresponding to both ends 122 | % (light and heavy product end) of the column being opened with gas being 123 | % fed at a certain molar flow rate through the light product end. For the 124 | % heavy product end, the following boundary condtions are used: 125 | % 126 | %% 127 | % $$ \bar{P} = \frac{P_L}{P_{0}} \quad | Z=0^- $$ 128 | % 129 | % $$ \frac{\partial y}{\partial \tau} = 0 \quad | Z=0^- $$ 130 | % 131 | % $$ \frac{\partial \bar{T}}{\partial \tau} = 0 \quad | Z=0^- $$ 132 | % 133 | %% 134 | P(1) = P_l/P_0 ; 135 | y(1) = y(2) ; 136 | T(1) = T(2) ; 137 | % 138 | %% 139 | % For the light product end, the following boundary condtions are used: 140 | % These boundary conditions are based off of the light product produced 141 | % during the adsorption step and the reflux ratio. 142 | % 143 | %% 144 | % $$ \bar{P}\cdot\bar{v} = constant \quad | Z=1^+ $$ 145 | % 146 | % $$ y = y_{LP} \quad | Z=1^+ $$ 147 | % 148 | % $$ \bar{T} = \bar{T}_{LP} \quad | Z=1^+ $$ 149 | % 150 | %% 151 | y(N+2) = y_LR ; 152 | T(N+2) = T_LR/T_0 ; 153 | 154 | % Calculate the velocity and the inlet pressure of the light product gas 155 | 156 | vh = zeros(N+1, 1) ; 157 | 158 | MW=MW_N2+(MW_CO2-MW_N2)*y(N+2); 159 | 160 | a_1 = 150*mu*(1-epsilon)^2*dz*L/2/4/r_p^2/epsilon^3/T(N+2)/T_0/R ; 161 | a_2_1 = 1.75*(1-epsilon)/2/r_p/epsilon/epsilon/epsilon*dz*L/2 ; 162 | a_2 = a_2_1/R/T(N+2)/T_0*ndot_LP*MW ; 163 | 164 | a = a_1+a_2 ; 165 | b = P(N+1)/T(N+2)*P_0/R/T_0 ; 166 | c = -ndot_LP ; 167 | 168 | vh(N+1) = (-b+sqrt(b^2-4*a*c))/2/a/v_0 ; 169 | 170 | a_p = a_1*T(N+2)*T_0*R ; 171 | b_p = a_2_1*MW/R/T(N+2)/T_0 ; 172 | 173 | P(N+2) = ((a_p*vh(N+1)*v_0+P(N+1)*P_0)./(1-b_p*vh(N+1)*v_0*vh(N+1)*v_0))/P_0 ; 174 | % 175 | %% Spatial Derivative Calculations 176 | % 177 | % *1st Derivatives* 178 | % 179 | % For the first derivative, the value at each volume is estimated based on 180 | % the values at the walls of the volumes. These values are estimated using 181 | % the Weighted Essentially NonOscillatory (WENO) scheme. 182 | % 183 | %% 184 | % $$ \frac{\partial f_j}{\partial Z}=\frac{f_{j+0.5}-f_{j-0.5}}{\Delta Z} $$ 185 | % 186 | %% 187 | % Pressure: at the center of the volumes and the walls 188 | 189 | Ph = WENO(P, 'downwind') ; 190 | 191 | dpdz(2:N+1) = (Ph(2:N+1)-Ph(1:N))/dz ; 192 | dpdzh(2:N) = (P(3:N+1)-P(2:N))/dz ; 193 | dpdzh(1) = 2*(P(2)-P(1))/dz ; 194 | dpdzh(N+1) = 2*(P(N+2)-P(N+1))/dz ; 195 | % 196 | %% 197 | % Mole Fraction: at the center of the volumes 198 | 199 | yh = WENO(y, 'downwind') ; 200 | 201 | dydz(2:N+1) = (yh(2:N+1)-yh(1:N))/dz ; 202 | % 203 | %% 204 | % Temperature: at the center of the volumes 205 | 206 | Th = WENO(T, 'downwind') ; 207 | 208 | dTdz(2:N+1) = (Th(2:N+1)-Th(1:N))/dz ; 209 | % 210 | %% 211 | % *2nd Derivatives* 212 | % 213 | % The second derivatives are calculated based off of the values of the 214 | % nodes. It is only necessary to calculate the 2nd derivative of the 215 | % temperature and mole fraction. It is noted that at the ends of the 216 | % column, no diffusion/conduction is occuring, so these are set to 0 in 217 | % the value of the derivative. 218 | % 219 | %% 220 | % $$ \frac{{\partial}^2 f_{j}}{\partial {Z}^2} = \frac{f_{j+1}+f_{j-1}- 221 | % 2f_{j}}{{\Delta Z}^2} $$ 222 | % 223 | %% 224 | % Mole Fraction 225 | d2ydz2(3:N) = (y(4:N+1)+y(2:N-1)-2*y(3:N))/dz/dz ; 226 | d2ydz2(2) = (y(3)-y(2))/dz/dz ; 227 | d2ydz2(N+1) = (y(N)-y(N+1))/dz/dz ; 228 | % 229 | %% 230 | % Temperature 231 | d2Tdz2(3:N) = (T(4:N+1)+T(2:N-1)-2*T(3:N))/dz/dz ; 232 | d2Tdz2(2) = 4*(Th(2)+T(1)-2*T(2))/dz/dz ; 233 | d2Tdz2(N+1) = 4*(Th(N)+T(N+2)-2*T(N+1))/dz/dz ; 234 | % 235 | %% Velocity Calculations 236 | % Calculates the interstitial velocity of the gas at the walls of the 237 | % volumes based off of the pressure gradients. NOTE: bear in mind that 238 | % the velocity in the Ergun's equation is the superficial velocity, and 239 | % not the interstitial, that's why in the denominator in the viscous term 240 | % it is found epsilon^2 and not epsilon^3, the same for kinetic terms, 241 | % where it is found epsilon and not epsilon^3. Superficial velocity is 242 | % equal to interstitial velocity times the void fraction 243 | % 244 | %% 245 | % $$ U = v \cdot \varepsilon $$ 246 | % 247 | % $$ - \frac{\partial \bar{P}}{\partial Z}\frac{P_0}{L} = 248 | % \frac{150\mu(1-\varepsilon)^2}{4r_{p}\varepsilon^2}\bar{v}v_0 + 249 | % \frac{1.75(1-\varepsilon)}{2r_{p}\varepsilon} 250 | % (\sum_{i}y_{i}MW_{i}C_{g})\bar{v}^2{v_{0}}^2 $$ 251 | % 252 | %% 253 | ro_gh = (P_0/R/T_0)*Ph(1:N+1)./Th(1:N+1) ; 254 | 255 | viscous_term = 150*mu*(1-epsilon)^2/4/r_p^2/epsilon^2 ; 256 | kinetic_term_h = (ro_gh.*(MW_N2+(MW_CO2-MW_N2).*yh)).*(1.75*(1-epsilon)... 257 | /2/r_p/epsilon) ; 258 | 259 | % Velocities at walls of volumes 260 | vh = -sign(dpdzh).*(-viscous_term+(abs(viscous_term^2+4*kinetic_term_h... 261 | .*abs(dpdzh)*P_0/L)).^(.5))/2./kinetic_term_h/v_0 ; 262 | % 263 | %% Temporal Derivatives 264 | % 265 | %% 266 | % *1) Adsorbed Mass Balance (molar loading for components 1 and 2)* 267 | % Linear Driving Force (LDF) is used to calculate the uptake rate of 268 | % the gas. The LDF mass transfer coefficients are assumed to be 269 | % constant over the pressure and temperature range of importance. 270 | % 271 | %% 272 | % $$ \frac{\partial x_{i}}{\partial \tau}q_{s0}= k_{i}({q_{i}}^*-q_{i}) $$ 273 | % 274 | %% 275 | % 1.1) Calculate equilibrium molar loading 276 | q = Isotherm(y, P*P_0, T*T_0, isotherm_params) ; 277 | q_1 = q(:, 1)*ro_s ; 278 | q_2 = q(:, 2)*ro_s ; 279 | % 280 | %% 281 | % 1.2) Calculate the LDF parameter 282 | k_1 = k_1_LDF*L/v_0 ; 283 | k_2 = k_2_LDF*L/v_0 ; 284 | % 285 | %% 286 | % 1.3) Calculate the temporal derivative 287 | dx1dt(2:N+1) = k_1*(q_1(2:N+1)/q_s0 - x1(2:N+1)) ; 288 | dx2dt(2:N+1) = k_2*(q_2(2:N+1)/q_s0 - x2(2:N+1)) ; 289 | % 290 | %% 291 | % *2) Column Energy Balance (Column Temperature)* 292 | % For the energy balance in the column, it is assumed that: 293 | % * There is thermal equilibrium between the solid and gas phase 294 | % * Conduction occurs through both the solid and gas phase 295 | % * Advection only occurs in the gas phase 296 | % 297 | %% 298 | % $$ \big[ \varepsilon C_{g}C_{p,g}+(1-\varepsilon)(C_{p,s}\rho_{s}+ 299 | % C_{p,a}q_{s0})\big]\frac{\partial \bar{T}}{\partial \tau}= 300 | % \frac{K_z}{v_0L} \frac{\partial^2 \bar{T}}{\partial Z^2} 301 | % - \varepsilon C_{g}C_{p,g} \bar{v}\frac{\partial \bar{T}}{\partial Z} 302 | % + \sum_{i} (1-\varepsilon)(-\Delta H_{i})\frac{q_{s0}}{T_{0}}\frac{ 303 | % \partial \bar{x_{i}}}{\partial \tau} $$ 304 | % 305 | %% 306 | % [J/m^3/K] 307 | sink_term = ((1-epsilon)*(ro_s*C_ps+q_s0*C_pa)+(epsilon.*ro_g(2:N+1).*C_pg)) ; 308 | % 309 | %% 310 | % 2.1) Calculate the temperature change due to conduction in the column 311 | % (both solid and gas phase) 312 | % 313 | %% 314 | % $$ \frac{K_z}{v_0L} \frac{\partial^2 \bar{T}}{\partial Z^2} $$ 315 | % 316 | %% 317 | transfer_term = K_z./v_0./L ; 318 | dTdt1(2:N+1) = transfer_term.*d2Tdz2(2:N+1)./sink_term ; 319 | % 320 | %% 321 | % 2.2) Calculate the temperature change due to advection 322 | % 323 | %% 324 | % $$ -\varepsilon C_{g}C_{p,g} \bar{v}\frac{\partial \bar{T}}{\partial Z} $$ 325 | % 326 | %% 327 | PvT = Ph(1:N+1).*vh(1:N+1)./Th(1:N+1) ; 328 | Pv = Ph(1:N+1).*vh(1:N+1) ; 329 | dTdt2(2:N+1) = -epsilon.*C_pg.*P_0./R./T_0.*((Pv(2:N+1)-Pv(1:N))- ... 330 | T(2:N+1).*(PvT(2:N+1)-PvT(1:N)))./dz./sink_term ; 331 | % 332 | %% 333 | % 2.3) Calculate the temperature change due to adsorption/desoprtion enthalpy 334 | % 335 | %% 336 | % $$ \sum_{i} (1-\varepsilon)(-\Delta 337 | % H_{i})\frac{q_{s0}}{T_{0}}\frac{\partial \bar{x_{i}}}{\partial \tau} $$ 338 | % 339 | % $$ \Delta H_{i} = \Delta U_i - R\bar{T}T_{0} $$ 340 | % 341 | %% 342 | generation_term_1 = (1-epsilon).*q_s0.*(-(deltaU_1-R*T(2:N+1)*T_0))./T_0 ; 343 | generation_term_2 = (1-epsilon).*q_s0.*(-(deltaU_2-R*T(2:N+1)*T_0))./T_0 ; 344 | 345 | dTdt3(2:N+1) = (generation_term_1.*dx1dt(2:N+1)+... 346 | generation_term_2.*dx2dt(2:N+1))./sink_term ; 347 | % 348 | %% 349 | % 2.4) Total sum of all temperature derivatives 350 | dTdt(2:N+1) = dTdt1(2:N+1) + dTdt2(2:N+1) + dTdt3(2:N+1) ; 351 | % 352 | %% 353 | % *3) Total mass balance* 354 | % 355 | %% 356 | % $$ \frac{\partial \bar{P}}{\partial \tau} = -\frac{\partial( \bar{v} 357 | % \bar{P}/\bar{T})}{\partial Z} - \Psi \bar{T} \sum_{i}\frac{\partial 358 | % \bar{x_{i}}}{\partial \tau} + \frac{\bar{P}}{\bar{T}}\frac{\partial 359 | % \bar{T}}{\partial \tau} $$ 360 | % 361 | %% 362 | % 3.1) Calculate the change in pressure due to advection 363 | dPdt1(2:N+1) = -T(2:N+1).*(PvT(2:N+1)-PvT(1:N))./dz ; 364 | % 365 | %% 366 | % 3.2) Calculate the change in pressure due to adsorption/desorption 367 | dPdt2(2:N+1) = -phi*T(2:N+1).*(dx1dt(2:N+1)+dx2dt(2:N+1)) ; 368 | % 369 | %% 370 | % 3.3) Calculate the change in pressure due to temperature changes 371 | dPdt3(2:N+1) = P(2:N+1).*dTdt(2:N+1)./T(2:N+1) ; 372 | % 373 | %% 374 | % 3.4) Total sum of all presure changes 375 | dPdt(2:N+1) = dPdt1(2:N+1) + dPdt2(2:N+1) + dPdt3(2:N+1) ; 376 | % 377 | %% 378 | % *4) Component Mass Balance (Based on Mole Fraction)* 379 | % 380 | %% 381 | % $$ \frac{\partial y}{\partial \tau} = \frac{1}{Pe} \big(\frac{{ 382 | % \partial}^2 y}{\partial {Z}^2}+\frac{1}{\bar{P}}\frac{\partial 383 | % \bar{P}}{\partial Z}\frac{\partial y}{\partial Z}-\frac{1}{\bar{T}} 384 | % \frac{\partial \bar{T}}{\partial Z}\frac{\partial y}{\partial Z} 385 | % \big)-\bar{v}\frac{\partial y}{\partial Z}+\frac{\Psi \bar{T}}{ 386 | % \bar{P}} \big((y-1)\frac{\partial \bar{x_{1}}}{\partial \tau}+y 387 | % \frac{\partial\bar{x_{2}}}{\partial \tau}\big) $$ 388 | % 389 | %% 390 | % 4.1) Calculate the change in mole fraction due to diffusion 391 | % 392 | %% 393 | % $$ \frac{1}{Pe} \big(\frac{{\partial}^2 y}{\partial {Z}^2}+\frac{1}{ 394 | % \bar{P}}\frac{\partial \bar{P}}{\partial Z}\frac{\partial y}{ 395 | % \partial Z}-\frac{1}{\bar{T}}\frac{\partial \bar{T}}{\partial Z} 396 | % \frac{\partial y}{\partial Z} \big) $$ 397 | % 398 | %% 399 | dydt1(2:N+1) = (1/Pe)*(d2ydz2(2:N+1)+(dydz(2:N+1).*dpdz(2:N+1)./P(2:N+1))... 400 | -(dydz(2:N+1).*dTdz(2:N+1)./T(2:N+1))) ; 401 | % 402 | %% 403 | % 4.2) Calculate the change in mole fraction due to advection 404 | % 405 | %% 406 | % $$ -\bar{v}\frac{\partial y}{\partial Z} $$ 407 | % 408 | %% 409 | ypvt = yh(1:N+1).*Ph(1:N+1).*vh(1:N+1)./Th(1:N+1) ; 410 | dydt2(2:N+1) = -(T(2:N+1)./P(2:N+1)).*((ypvt(2:N+1)-ypvt(1:N))... 411 | -y(2:N+1).*(PvT(2:N+1)-PvT(1:N)))./dz ; 412 | % 413 | %% 414 | % 4.3) Calculate the change in mole fraction due to adsorption/desorption 415 | % 416 | %% 417 | % $$ \frac{\Psi \bar{T}}{\bar{P}} \big((y-1)\frac{\partial \bar{x_{1}}}{ 418 | % \partial \tau}+y\frac{\partial \bar{x_{2}}}{\partial \tau}\big) $$ 419 | % 420 | %% 421 | dydt3(2:N+1) = (phi*T(2:N+1)./P(2:N+1)).*((y(2:N+1)-1).*dx1dt(2:N+1)... 422 | + y(2:N+1).*dx2dt(2:N+1)) ; 423 | % 424 | %% 425 | % 4.4) Total sum of all mole fraction changes 426 | dydt(2:N+1) = dydt1(2:N+1) + dydt2(2:N+1) + dydt3(2:N+1) ; 427 | % 428 | %% Boundary Derivatives 429 | dPdt(1) = 0 ; 430 | dPdt(N+2) = 0 ; 431 | dydt(1) = dydt(2) ; 432 | dydt(N+2) = 0 ; 433 | dx1dt(1) = 0 ; 434 | dx2dt(1) = 0 ; 435 | dx1dt(N+2) = 0 ; 436 | dx2dt(N+2) = 0 ; 437 | dTdt(1) = dTdt(2) ; 438 | dTdt(N+2) = 0 ; 439 | % 440 | %% Export derivatives to output 441 | derivatives(1:N+2) = dPdt(1:N+2) ; 442 | derivatives(N+3:2*N+4) = dydt(1:N+2) ; 443 | derivatives(2*N+5:3*N+6) = dx1dt(1:N+2) ; 444 | derivatives(3*N+7:4*N+8) = dx2dt(1:N+2) ; 445 | derivatives(4*N+9:5*N+10) = dTdt(1:N+2) ; 446 | % 447 | end -------------------------------------------------------------------------------- /CycleSteps/Isotherm.m: -------------------------------------------------------------------------------- 1 | function q=Isotherm(y, P, T, isotherm_parameters) 2 | %hypotheticalisotherm- Calculate the molar loadings of a two component mixture 3 | % Calculate the total loading of a two component mixture loading. It is 4 | % assumed that the isotherm is able to be represented by a dual site 5 | % langmuir competitive isotherm with temperature dependent parameters. 6 | % The isotherm can be in terms of concentration [mol/m^3] or Partial 7 | % pressure [Pa]. 8 | % Inputs: 9 | % y: mole fraction of component one [-]. It is assumed the mole fraction 10 | % of component 2 is 1-y 11 | % P: Total pressure of the gas [Pa] 12 | % T: Temperature of the gas [K] 13 | % isothermparams: Parameters for the isotherm. View script Input_PSA for 14 | % structure 15 | % input_units: specify whether the isotherm is in terms of concentration 16 | % of partial pressure 17 | 18 | R=8.314; 19 | 20 | q_s_b_1=isotherm_parameters(1); 21 | q_s_d_1=isotherm_parameters(3); 22 | q_s_b_2=isotherm_parameters(2); 23 | q_s_d_2=isotherm_parameters(4); 24 | b_1=isotherm_parameters(5); 25 | d_1=isotherm_parameters(7); 26 | b_2=isotherm_parameters(6); 27 | d_2=isotherm_parameters(8); 28 | deltaU_b_1=isotherm_parameters(9); 29 | deltaU_d_1=isotherm_parameters(11); 30 | deltaU_b_2=isotherm_parameters(10); 31 | deltaU_d_2=isotherm_parameters(12); 32 | 33 | 34 | B_1=b_1*exp(-deltaU_b_1/R./T); 35 | D_1=d_1*exp(-deltaU_d_1/R./T); 36 | B_2=b_2*exp(-deltaU_b_2/R./T); 37 | D_2=d_2*exp(-deltaU_d_2/R./T); 38 | 39 | if isotherm_parameters(13) == 0 40 | 41 | P_1=y.*P; 42 | P_2=(1-y).*P; 43 | input_1=P_1; 44 | input_2=P_2; 45 | 46 | elseif isotherm_parameters(13) ==1 47 | C_1=y.*P./R./T; 48 | C_2=(1-y).*P./R./T; 49 | input_1=C_1; 50 | input_2=C_2; 51 | else 52 | error('Please specify whether the isotherms are in terms of Concentration or Partial Pressure') 53 | 54 | end 55 | 56 | 57 | q1_b=q_s_b_1.*B_1.*input_1./(1+B_1.*input_1+B_2.*input_2); 58 | q1_d=q_s_d_1.*D_1.*input_1./(1+D_1.*input_1+D_2.*input_2); 59 | 60 | q1=q1_b+q1_d; 61 | 62 | q2_b=q_s_b_2.*B_2.*input_2./(1+B_1.*input_1+B_2.*input_2); 63 | q2_d=q_s_d_2.*D_2.*input_2./(1+D_1.*input_1+D_2.*input_2); 64 | 65 | q2=q2_b+q2_d; 66 | 67 | q = [q1, q2] ; 68 | 69 | end -------------------------------------------------------------------------------- /CycleSteps/WENO.m: -------------------------------------------------------------------------------- 1 | function flux_w = WENO(flux_c, FlowDir) 2 | %WENO: Apply Weighted Essentially NonOscillatory scheme 3 | % Receive fluxes at finite volume centers and direction of flow (upwind - 4 | % downwind), the flow direction is given by the velocity or pressure drop 5 | % If v>=0 or dP<0, then an upwind scheme is applied. If v<0 or dP>0, then 6 | % an downwind scheme is applied. The function return the fluxes at finite 7 | % volume walls. upwind means that the computation is done from left to 8 | % right on the domain, or in terms of the PSA column "co-current flow 9 | % regarding the feed inlet". downwind means that the computation is done 10 | % from right to left on the domain, or in terms of the PSA column "counter 11 | % -current flow regarding the feed inlet" 12 | % 13 | % Input: 14 | % flux_c : flux at the finite volume centers 15 | % FlowDir: direction of flow. OPTIONS: upwind and downwind 16 | % 17 | % Output: 18 | % flux_w : flux at the edges or walls of the finite volumes 19 | % 20 | %% 21 | % For co-current flow (upwind) the fluxes at the walls of finite volumes 22 | % are calculated as follows: 23 | % 24 | %% 25 | % $$ f_{j+0.5}=\frac{\alpha_{0,j}}{\alpha_{0,j}+\alpha_{1,j}} 26 | % \Big[\frac{1}{2}(f_{j}+f_{j+1})\Big] + \frac{\alpha_{1,j}}{\alpha_{0,j}+ 27 | % \alpha_{1,j}}\Big[\frac{3}{2}f_{j}-\frac{1}{2}f_{j-1}\Big] $$ 28 | % 29 | % $$ \alpha_{0,j}= \frac{2/3}{(f_{j+1}-f_{j}+\delta)^4} $$ 30 | % 31 | % $$ \alpha_{1,j}= \frac{1/3}{(f_{j}-f_{j-1}+\delta)^4} $$ 32 | % 33 | %% 34 | % For counter-current flow (downwind) the fluxes at the walls of finite 35 | % volumes are calculated as follows: 36 | % 37 | %% 38 | % $$ f_{j+0.5}=\frac{\alpha_{0,j}}{\alpha_{0,j}+\alpha_{1,j}} 39 | % \Big[\frac{1}{2}(f_{j}+f_{j+1})\Big] + \frac{\alpha_{1,j}}{\alpha_{0,j}+ 40 | % \alpha_{1,j}}\Big[\frac{3}{2}f_{j+1}-\frac{1}{2}f_{j+2}\Big] $$ 41 | % 42 | % $$ \alpha_{0,j}= \frac{2/3}{(f_{j}-f_{j+1}+\delta)^4} $$ 43 | % 44 | % $$ \alpha_{1,j}= \frac{1/3}{(f_{j+1}-f_{j+2}+\delta)^4} $$ 45 | % 46 | %% 47 | oo = 10^-10 ; 48 | [N, m] = size(flux_c) ; 49 | N = N-2 ; 50 | flux_w = zeros(N+1, m) ; 51 | alpha0 = zeros(size(flux_c)) ; 52 | alpha1 = zeros(size(flux_c)) ; 53 | 54 | % Fluxes at boundaries of the domain 55 | flux_w(1, :) = flux_c(1, :) ; 56 | flux_w(N+1, :) = flux_c(N+2, :) ; 57 | 58 | if strcmpi(FlowDir, 'upwind') == 1 59 | 60 | alpha0(2:N, :) =(2/3)./((flux_c(3:N+1, :)-flux_c(2:N, :)+oo).^4) ; 61 | alpha1(3:N, :) =(1/3)./((flux_c(3:N, :)-flux_c(2:N-1, :)+oo).^4) ; 62 | alpha1(2, :) =(1/3)./((2*(flux_c(2, :)-flux_c(1, :))+oo).^4) ; 63 | 64 | flux_w(3:N, :) = (alpha0(3:N, :)./(alpha0(3:N, :)+alpha1(3:N, :)))... 65 | .*((flux_c(3:N, :)+flux_c(4:N+1, :))./2)+(alpha1(3:N, :)... 66 | ./(alpha0(3:N, :)+alpha1(3:N, :))).*(1.5*flux_c(3:N, :)... 67 | -.5*flux_c(2:N-1, :)) ; 68 | 69 | flux_w(2, :) = (alpha0(2, :)./(alpha0(2, :)+alpha1(2, :)))... 70 | .*((flux_c(2, :)+flux_c(3, :))./2)+(alpha1(2, :)... 71 | ./(alpha0(2, :)+alpha1(2, :))).*(2*flux_c(2, :)... 72 | -flux_c(1, :)) ; 73 | 74 | elseif strcmpi(FlowDir, 'downwind') == 1 75 | 76 | alpha0(2:N, :) = (2/3)./((flux_c(2:N, :)-flux_c(3:N+1, :)+oo).^4) ; 77 | alpha1(2:N-1, :) = (1/3)./((flux_c(3:N, :)-flux_c(4:N+1, :)+oo).^4) ; 78 | alpha1(N, :) = (1/3)./((2*(flux_c(N+1, :)-flux_c(N+2, :))+oo).^4) ; 79 | 80 | flux_w(2:N-1, :) = (alpha0(2:N-1, :)./(alpha0(2:N-1, :)+alpha1(2:N-1, :)))... 81 | .*((flux_c(2:N-1, :)+flux_c(3:N, :))./2)+(alpha1(2:N-1, :)... 82 | ./(alpha0(2:N-1, :)+alpha1(2:N-1, :))).*(1.5*flux_c(3:N, :)... 83 | -.5*flux_c(4:N+1, :)) ; 84 | 85 | flux_w(N, :) = (alpha0(N, :)./(alpha0(N, :)+alpha1(N, :)))... 86 | .*((flux_c(N, :)+flux_c(N+1, :))./2)+(alpha1(N, :)... 87 | ./(alpha0(N, :)+alpha1(N, :))).*(2*flux_c(N+1, :)... 88 | -flux_c(N+2, :)) ; 89 | else 90 | error('Please specify the direction of flow. OPTIONS: upwind and downwind') 91 | end 92 | % 93 | end -------------------------------------------------------------------------------- /EconomicOptimization.m: -------------------------------------------------------------------------------- 1 | clc 2 | format long 3 | parpool('local', 12); 4 | 5 | addpath('CycleSteps') 6 | addpath('GA_files') 7 | 8 | load('Params') 9 | 10 | N = 10 ; 11 | type = 'EconomicEvaluation' ; 12 | 13 | for i = 12:12 14 | 15 | % load parameters 16 | IsothermParams = IsothermPar(i, :) ; 17 | material_propertry = SimParam(i, :) ; 18 | 19 | material = {} ; 20 | material{1} = material_propertry ; 21 | material{2} = IsothermParams ; 22 | 23 | Function = @(x) PSACycleSimulation( x, material, type, N ) ; % Function to simulate the PSA cycle 24 | 25 | % initial variables 26 | [~, vars] = sortt(loadpopfile('UTSA-16_Process.txt')); 27 | vars = [vars, ones(length(vars), 1), 1e4*ones(length(vars), 1)]; 28 | 29 | options = nsgaopt(); % create default options structure 30 | options.popsize = 60; % populaion size 31 | options.outputfile = 'UTSA-16_Economic.txt'; 32 | options.maxGen = 80; % max generation 33 | 34 | options.vartype = [1, 1, 1, 1, 1, 1] ; 35 | 36 | options.initfun={@Pop_Override, vars} ; % Supply variables from previous results 37 | 38 | options.numObj = 2 ; % number of objectives 39 | options.numVar = 6 ; % number of design variables 40 | options.numCons = 3 ; % number of constraints 41 | options.lb = [1e5, 10, 0.01, 0.1, 0, 1e4] ; % lower bound of x 42 | options.ub = [10e5, 1000, 0.99, 2, 1, 5e4] ; % upper bound of x 43 | options.nameObj = {'-productivity','energy'} ; % the objective names are showed in GUI window. 44 | options.objfun = Function ; % objective function handle 45 | 46 | options.useParallel = 'yes' ; % parallel computation is non-essential here 47 | options.poolsize = 12 ; % number of worker processes 48 | 49 | result = nsga2(options) ; % begin the optimization! 50 | 51 | 52 | % re-optimize using 30 finite volumes 53 | N = 30 ; 54 | Function = @(x) FiveStepModSkarstromProcessSim( x, material, type, N ) ; % Function to simulate the PSA cycle 55 | 56 | options.objfun = Function ; % objective function handle 57 | options.initfun = {@initpop, result} ; % Supply variables from previous results 58 | options.maxGen = 120 ; % populaion size 59 | options.outputfile = 'UTSA-16_Economic_2.txt' ; 60 | result2 = nsga2(options) ; % begin the optimization! 61 | 62 | end 63 | 64 | -------------------------------------------------------------------------------- /EconomicOptimization_v2.m: -------------------------------------------------------------------------------- 1 | clc 2 | format long 3 | parpool('local', 12); 4 | 5 | addpath('CycleSteps') 6 | addpath('GA_files') 7 | 8 | load('Params') 9 | 10 | N = 30 ; 11 | type = 'EconomicEvaluation' ; 12 | 13 | for i = 12:12 14 | 15 | % load parameters 16 | IsothermParams = IsothermPar(i, :) ; 17 | material_propertry = SimParam(i, :) ; 18 | 19 | material = {} ; 20 | material{1} = material_propertry ; 21 | material{2} = IsothermParams ; 22 | 23 | Function = @(x) PSACycleSimulation( x, material, type, N ) ; % Function to simulate the PSA cycle 24 | 25 | % initial variables 26 | [~, vars] = sortt(loadpopfile('Ec.txt')); 27 | 28 | options = nsgaopt(); % create default options structure 29 | options.popsize = 60; % populaion size 30 | options.outputfile = 'Ec_2.txt'; 31 | options.maxGen = 120; % max generation 32 | 33 | options.vartype = [1, 1, 1, 1, 1, 1] ; 34 | 35 | options.initfun={@Pop_Override, vars} ; % Supply variables from previous results 36 | 37 | options.numObj = 2 ; % number of objectives 38 | options.numVar = 6 ; % number of design variables 39 | options.numCons = 3 ; % number of constraints 40 | options.lb = [1e5, 10, 0.01, 0.1, 0, 1e4] ; % lower bound of x 41 | options.ub = [10e5, 1000, 0.99, 2, 1, 5e4] ; % upper bound of x 42 | options.nameObj = {'-productivity','energy'} ; % the objective names are showed in GUI window. 43 | options.objfun = Function ; % objective function handle 44 | 45 | options.useParallel = 'yes' ; % parallel computation is non-essential here 46 | options.poolsize = 12 ; % number of worker processes 47 | 48 | result2 = nsga2(options) ; % begin the optimization! 49 | 50 | end 51 | 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 PEESE 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 | -------------------------------------------------------------------------------- /NSGA-II/Pop_Override.m: -------------------------------------------------------------------------------- 1 | function pop= Pop_Override(opt, pop, varargin) 2 | % Function: pop = Pop_Override(opt, pop, varargin) 3 | % Description: Load population from exist variables. Does not reject based 4 | % on number of constraints from previous results 5 | 6 | popsize_new=opt.popsize; 7 | 8 | variable=opt.initfun{2}; 9 | 10 | popsize_old=size(variable, 1); 11 | 12 | numb_var_sim=opt.numVar; 13 | 14 | numb_var_supp=size(variable, 2); 15 | 16 | if numb_var_sim ~= numb_var_supp 17 | error('NSGA2:OptModelError', 'Number of variables supplied does not equal what is requested'); 18 | end 19 | 20 | pop_size=opt.popsize; 21 | 22 | variable=opt.initfun{2}; 23 | 24 | if popsize_old >= popsize_new 25 | for j = 1:pop_size 26 | pop(j).var = variable(j, :); 27 | end 28 | else 29 | for j = 1:popsize_old 30 | pop(j).var = variable(j, :); 31 | end 32 | pop(popsize_old+1:end) = initpopUniform(opt, pop(popsize_old+1:end)); 33 | end 34 | 35 | 36 | 37 | function pop = initpopUniform(opt, pop) 38 | % Function: pop = initpopUniform(opt, pop) 39 | % Description: Initialize population using random number 40 | % 41 | % Copyright 2011 by LSSSSWC 42 | % Revision: 1.0 Data: 2011-07-01 43 | %************************************************************************* 44 | 45 | nVar = opt.numVar; 46 | type = opt.vartype; 47 | 48 | lb = opt.lb; 49 | ub = opt.ub; 50 | 51 | popsize = length(pop); 52 | for i = 1:popsize 53 | var = lb + rand(1, nVar) .* (ub-lb); 54 | 55 | % if desing variable is integer, round to the nearest integer 56 | for v = 1:nVar 57 | if( type(v) == 2) 58 | var(v) = round(var(v)); 59 | end 60 | end 61 | 62 | % limit in the lower and upper bound 63 | var = varlimit(var, lb, ub); 64 | 65 | pop(i).var = var; 66 | 67 | end 68 | end 69 | 70 | end -------------------------------------------------------------------------------- /NSGA-II/callOutputfuns.m: -------------------------------------------------------------------------------- 1 | function opt = callOutputfuns(opt, state, pop, type) 2 | % Function: opt = callOutputfuns(opt, state, pop, type) 3 | % Description: Call output function(if exist). 4 | % Parameters: 5 | % type : output type. 6 | % -1 = the last call (close file for instance) 7 | % other values(or no exist) = normal output 8 | % 9 | % LSSSSWC, NWPU 10 | % Revision: 1.1 Data: 2011-07-13 11 | %************************************************************************* 12 | 13 | 14 | if(nargin <= 3) 15 | type = 0; % normal output 16 | end 17 | 18 | 19 | if( ~isempty(opt.outputfuns) ) 20 | fun = opt.outputfuns{1}; 21 | opt = fun(opt, state, pop, type, opt.outputfuns{2:end}); 22 | end 23 | 24 | 25 | -------------------------------------------------------------------------------- /NSGA-II/crossoverOp.m: -------------------------------------------------------------------------------- 1 | function pop = crossoverOp(opt, pop, state) 2 | % Function: pop = crossoverOp(opt, pop, state) 3 | % Description: Crossover operator. All of the individuals would be do crossover, but 4 | % only "crossoverFraction" of design variables of an individual would changed. 5 | % 6 | % LSSSSWC, NWPU 7 | % Revision: 1.1 Data: 2011-07-13 8 | %************************************************************************* 9 | 10 | %************************************************************************* 11 | % 1. Check for the parameters 12 | %************************************************************************* 13 | % determine the crossover method 14 | strfun = lower(opt.crossover{1}); 15 | numOptions = length(opt.crossover) - 1; 16 | [crossoverOpt{1:numOptions}] = opt.crossover{2:end}; 17 | 18 | switch( strfun ) 19 | case 'intermediate' 20 | fun = @crsIntermediate; 21 | otherwise 22 | error('NSGA2:CrossoverOpError', 'No support crossover operator!'); 23 | end 24 | 25 | nVar = opt.numVar; 26 | 27 | % "auto" crossover fraction 28 | if( ischar(opt.crossoverFraction) ) 29 | if( strcmpi(opt.crossoverFraction, 'auto') ) 30 | fraction = 2.0 / nVar; 31 | else 32 | error('NSGA2:CrossoverOpError', 'The "crossoverFraction" parameter should be scalar or "auto" string.'); 33 | end 34 | else 35 | fraction = opt.crossoverFraction; 36 | end 37 | 38 | 39 | for ind = 1:2:length(pop) % Popsize should be even number 40 | % Create children 41 | [child1, child2] = fun( pop(ind), pop(ind+1), fraction, crossoverOpt ); 42 | 43 | % Round 44 | for v = 1:nVar 45 | if( opt.vartype(v) == 2) 46 | child1.var(v) = round( child1.var(v) ); 47 | child2.var(v) = round( child2.var(v) ); 48 | end 49 | end 50 | 51 | % Bounding limit 52 | child1.var = varlimit(child1.var, opt.lb, opt.ub); 53 | child2.var = varlimit(child2.var, opt.lb, opt.ub); 54 | 55 | pop(ind) = child1; 56 | pop(ind+1) = child2; 57 | 58 | end 59 | 60 | 61 | 62 | function [child1, child2] = crsIntermediate(parent1, parent2, fraction, options) 63 | % Function: [child1, child2] = crsIntermediate(parent1, parent2, fraction, options) 64 | % Description: (For real coding) Intermediate crossover. (Same as Matlab's crossover 65 | % operator) 66 | % child = parent1 + rand * Ratio * ( parent2 - parent1) 67 | % Parameters: 68 | % fraction : crossover fraction of variables of an individual 69 | % options = ratio 70 | % 71 | % LSSSSWC, NWPU 72 | % Revision: 1.1 Data: 2011-07-13 73 | %************************************************************************* 74 | 75 | 76 | if( length(options)~=1 || ~isnumeric(options{1})) 77 | error('NSGA2:CrossoverOpError', 'Crossover operator parameter error!'); 78 | end 79 | 80 | ratio = options{1}; 81 | 82 | child1 = parent1; 83 | child2 = parent2; 84 | 85 | nVar = length(parent1.var); 86 | crsFlag = rand(1, nVar) < fraction; 87 | 88 | randNum = rand(1,nVar); % uniformly distribution 89 | 90 | child1.var = parent1.var + crsFlag .* randNum .* ratio .* (parent2.var - parent1.var); 91 | child2.var = parent2.var - crsFlag .* randNum .* ratio .* (parent2.var - parent1.var); 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /NSGA-II/evaluate.m: -------------------------------------------------------------------------------- 1 | function [pop, state] = evaluate(opt, pop, state, varargin) 2 | % Function: [pop, state] = evaluate(opt, pop, state, varargin) 3 | % Description: Evaluate the objective functions of each individual in the 4 | % population. 5 | % 6 | % LSSSSWC, NWPU 7 | % Revision: 1.0 Data: 2011-04-20 8 | %************************************************************************* 9 | 10 | N = length(pop); 11 | allTime = zeros(N, 1); % allTime : use to calculate average evaluation times 12 | 13 | %************************************************************************* 14 | % Evaluate objective function in parallel 15 | %************************************************************************* 16 | if( strcmpi(opt.useParallel, 'yes') == 1 ) 17 | % curPoolInfo = gcp('nocreate'); 18 | % 19 | % % Check if there is a parpool open or not 20 | % [Pooldatasize, ~]=size(curPoolInfo); 21 | % if Pooldatasize == 0 22 | % curPoolsize=0; 23 | % else 24 | % curPoolsize = curPoolInfo.NumWorkers; 25 | % end 26 | % 27 | % % There isn't opened worker process 28 | % if(curPoolsize == 0) 29 | % if(opt.poolsize == 0) 30 | % parpool open local; 31 | % else 32 | % parpool(opt.poolsize); 33 | % end 34 | % % Close and recreate worker process 35 | % else 36 | % if(opt.poolsize ~= curPoolsize) 37 | % delete(gcp); 38 | % parpool(opt.poolsize); 39 | % end 40 | % end 41 | 42 | parfor i = 1:N 43 | fprintf('\nEvaluating the objective function... Generation: %d / %d, Individual: %d / %d \n',state.currentGen, opt.maxGen, i, N); 44 | [pop(i), allTime(i)] = evalIndividual(pop(i), opt.objfun, varargin{:}); 45 | end 46 | 47 | %************************************************************************* 48 | % Evaluate objective function in serial 49 | %************************************************************************* 50 | else 51 | for i = 1:N 52 | fprintf('\nEvaluating the objective function... Generation: %d / %d, Individual: %d / %d \n',state.currentGen, opt.maxGen, i, N); 53 | [pop(i), allTime(i)] = evalIndividual(pop(i), opt.objfun, varargin{:}); 54 | end 55 | end 56 | 57 | %************************************************************************* 58 | % Statistics 59 | %************************************************************************* 60 | state.avgEvalTime = sum(allTime) / length(allTime); 61 | state.evaluateCount = state.evaluateCount + length(pop); 62 | 63 | 64 | 65 | 66 | function [indi, evalTime] = evalIndividual(indi, objfun, varargin) 67 | % Function: [indi, evalTime] = evalIndividual(indi, objfun, varargin) 68 | % Description: Evaluate one objective function. 69 | % 70 | % LSSSSWC, NWPU 71 | % Revision: 1.1 Data: 2011-07-25 72 | %************************************************************************* 73 | 74 | tStart = tic; 75 | [y, cons] = objfun( indi.var, varargin{:} ); 76 | evalTime = toc(tStart); 77 | 78 | % Save the objective values and constraint violations 79 | indi.obj = y; 80 | 81 | if( ~isempty(indi.cons) ) 82 | idx = find( cons ); 83 | indi.cons=cons; 84 | if( ~isempty(idx) ) 85 | indi.nViol = length(idx); 86 | indi.violSum = sum( abs(cons) ); 87 | else 88 | indi.nViol = 0; 89 | indi.violSum = 0; 90 | end 91 | 92 | end 93 | 94 | 95 | -------------------------------------------------------------------------------- /NSGA-II/extractPop.m: -------------------------------------------------------------------------------- 1 | function nextpop = extractPop(opt, combinepop) 2 | % Function: nextpop = extractPop(opt, combinepop) 3 | % Description: Extract the best n individuals in 'combinepop'(population 4 | % size is 2n). 5 | % 6 | % LSSSSWC, NWPU 7 | % Revision: 1.1 Data: 2011-07-12 8 | %************************************************************************* 9 | 10 | popsize = length(combinepop) / 2; 11 | nextpop = combinepop(1:popsize); %just for initializing 12 | 13 | rankVector = vertcat(combinepop.rank); 14 | 15 | n = 0; % individuals number of next population 16 | rank = 1; % current rank number 17 | idx = find(rankVector == rank); 18 | numInd = length(idx); % number of individuals in current front 19 | while( n + numInd <= popsize ) 20 | nextpop( n+1 : n+numInd ) = combinepop( idx ); 21 | 22 | n = n + numInd; 23 | rank = rank + 1; 24 | 25 | idx = find(rankVector == rank); 26 | numInd = length(idx); 27 | end 28 | 29 | % If the number of individuals in the next front plus the number of individuals 30 | % in the current front is greater than the population size, then select the 31 | % best individuals by corwding distance(NSGA-II) or preference distance(R-NSGA-II). 32 | if( n < popsize ) 33 | if(~isempty(opt.refPoints)) 34 | prefDistance = vertcat(combinepop(idx).prefDistance); 35 | prefDistance = [prefDistance, idx]; 36 | prefDistance = sortrows( prefDistance, 1); 37 | idxSelect = prefDistance( 1:popsize-n, 2); % Select the individuals with smallest preference distance 38 | nextpop(n+1 : popsize) = combinepop(idxSelect); 39 | else 40 | distance = vertcat(combinepop(idx).distance); 41 | distance = [distance, idx]; 42 | distance = flipud( sortrows( distance, 1) ); % Sort the individuals in descending order of crowding distance in the front. 43 | idxSelect = distance( 1:popsize-n, 2); % Select the (popsize-n) individuals with largest crowding distance. 44 | nextpop(n+1 : popsize) = combinepop(idxSelect); 45 | end 46 | end 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /NSGA-II/initpop.m: -------------------------------------------------------------------------------- 1 | function pop = initpop(opt, pop, varargin) 2 | % Function: pop = initpop(opt, pop, varargin) 3 | % Description: Initialize population. 4 | % Syntax: 5 | % pop = initpop(opt, pop) 6 | % (default) Create a random initial population with a uniform distribution. 7 | % 8 | % pop = initpop(opt, pop, 'pop.txt') 9 | % Load population from exist file and use the last population. If the popsize 10 | % less than the current popsize, then random numbers will used to fill the population. 11 | % 12 | % pop = initpop(opt, pop, 'pop.txt', ngen) 13 | % Load population from file with specified generation. 14 | % 15 | % pop = initpop(opt, pop, oldresult) 16 | % Specify exist result structure. 17 | % 18 | % pop = initpop(opt, pop, oldresult, ngen) 19 | % Specify exist result structure and the population which will be used. 20 | % 21 | % Parameters: 22 | % pop : an empty population 23 | % 24 | % LSSSSWC, NWPU 25 | % Revision: 1.1 Data: 2011-07-01 26 | %************************************************************************* 27 | 28 | 29 | %************************************************************************* 30 | % 1. Identify parameters 31 | %************************************************************************* 32 | method = 'uniform'; 33 | 34 | if(nargin >= 3) 35 | if( ischar(varargin{1}) ) 36 | method = 'file'; 37 | elseif( isstruct(varargin{1}) ) 38 | method = 'existpop'; 39 | end 40 | end 41 | 42 | %************************************************************************* 43 | % 2. Initialize population with different methods 44 | %************************************************************************* 45 | if( strcmpi(method, 'uniform')) 46 | pop = initpopUniform(opt, pop); 47 | elseif(strcmpi(method, 'file')) 48 | fprintf('...Initialize population from file "%s"\n', varargin{1}); 49 | pop = initpopFromFile(opt, pop, varargin{:}); 50 | elseif(strcmpi(method, 'existpop')) 51 | fprintf('...Initialize population from specified result.\n'); 52 | pop = initpopFromExistResult(opt, pop, varargin{:}); 53 | end 54 | 55 | 56 | 57 | 58 | function pop = initpopFromFile(opt, pop, varargin) 59 | % Function: pop = initpopFromFile(opt, pop, varargin) 60 | % Description: Load population from specified population file. 61 | % Syntax: 62 | % pop = initpop(opt, pop, 'pop.txt') 63 | % pop = initpop(opt, pop, 'pop.txt', ngen) 64 | % 65 | % Copyright 2011 by LSSSSWC 66 | % Revision: 1.0 Data: 2011-07-01 67 | %************************************************************************* 68 | fileName = varargin{1}; 69 | 70 | oldResult = loadpopfile(fileName); 71 | pop = initpopFromExistResult(opt, pop, oldResult, varargin{2:end}); 72 | 73 | 74 | 75 | 76 | 77 | function pop = initpopFromExistResult(opt, pop, varargin) 78 | % Function: pop = initpopFromExistResult(opt, pop, varargin) 79 | % Description: Load population from exist result structure. 80 | % Syntax: 81 | % pop = initpop(opt, pop, oldresult) 82 | % pop = initpop(opt, pop, oldresult, ngen) 83 | % 84 | % Copyright 2011 by LSSSSWC 85 | % Revision: 1.0 Data: 2011-07-01 86 | %************************************************************************* 87 | 88 | % 1. Verify param 89 | oldresult = varargin{1}; 90 | if( ~isstruct(oldresult) || ~isfield(oldresult, 'pops') ) 91 | error('NSGA2:InitPopError', 'The result structure specified is not correct!'); 92 | end 93 | 94 | 95 | oldpops = oldresult.pops; 96 | ind = oldpops(1,1); % individual used to verify optimization param 97 | if( opt.numVar ~= length(ind.var) || ... 98 | opt.numObj ~= length(ind.obj) || ... 99 | opt.numCons ~= length(ind.cons) ) 100 | error('NSGA2:InitPopError', ... 101 | 'The specified optimization result is not for current optimization model!'); 102 | end 103 | clear ind 104 | 105 | 106 | % 2. Determine which population would be used 107 | ngen = 0; 108 | if( nargin >= 4) 109 | ngen = varargin{2}; 110 | end 111 | 112 | maxGen = size(oldpops, 1); 113 | if(ngen == 0) 114 | ngen = maxGen; 115 | elseif(ngen > maxGen) 116 | warning('NSGA2:InitPopWarning', ... 117 | 'The specified generation "%d" does not exist, use "%d" instead.',... 118 | ngen, maxGen); 119 | ngen = maxGen; 120 | end 121 | 122 | 123 | % 3. Create initial population 124 | popsizeOld = size(oldpops, 2); 125 | popsizeNew = opt.popsize; 126 | 127 | if( popsizeNew <= popsizeOld ) % a) All from old pop 128 | for i = 1:popsizeNew 129 | pop(i).var = oldpops(ngen, i).var; 130 | end 131 | else % b) Use random individuals to fill the population 132 | for i = 1:popsizeOld 133 | pop(i).var = oldpops(ngen, i).var; 134 | end 135 | pop(popsizeOld+1:end) = initpopUniform(opt, pop(popsizeOld+1:end)); 136 | end 137 | 138 | 139 | 140 | 141 | function pop = initpopUniform(opt, pop) 142 | % Function: pop = initpopUniform(opt, pop) 143 | % Description: Initialize population using random number 144 | % 145 | % Copyright 2011 by LSSSSWC 146 | % Revision: 1.0 Data: 2011-07-01 147 | %************************************************************************* 148 | 149 | nVar = opt.numVar; 150 | type = opt.vartype; 151 | 152 | lb = opt.lb; 153 | ub = opt.ub; 154 | 155 | popsize = length(pop); 156 | for i = 1:popsize 157 | var = lb + rand(1, nVar) .* (ub-lb); 158 | 159 | % if desing variable is integer, round to the nearest integer 160 | for v = 1:nVar 161 | if( type(v) == 2) 162 | var(v) = round(var(v)); 163 | end 164 | end 165 | 166 | % limit in the lower and upper bound 167 | var = varlimit(var, lb, ub); 168 | 169 | pop(i).var = var; 170 | 171 | end 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /NSGA-II/loadpopfile.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PEESEgroup/PSA/3de0832320971656dd79d9c7473d4029a10fee97/NSGA-II/loadpopfile.m -------------------------------------------------------------------------------- /NSGA-II/mutationOp.m: -------------------------------------------------------------------------------- 1 | function pop = mutationOp(opt, pop, state) 2 | % Function: pop = mutationOp(opt, pop, state) 3 | % Description: Mutation Operator. All of the individuals would do mutation, but 4 | % only "mutationFraction" of design variables of an individual would changed. 5 | % 6 | % LSSSSWC, NWPU 7 | % Revision: 1.1 Data: 2011-07-13 8 | %************************************************************************* 9 | 10 | %************************************************************************* 11 | % 1. Check for the parameters 12 | %************************************************************************* 13 | % mutation method 14 | strfun = lower(opt.mutation{1}); 15 | numOptions = length(opt.mutation) - 1; 16 | [mutationopt{1:numOptions}] = opt.mutation{2:end}; 17 | 18 | switch (strfun) 19 | case 'gaussian' 20 | fun = @mutationGaussian; 21 | otherwise 22 | error('NSGA2:MutationOpError', 'No support mutation operator!'); 23 | end 24 | 25 | nVar = opt.numVar; 26 | 27 | % "auto" mutation fraction 28 | if( ischar(opt.mutationFraction) ) 29 | if( strcmpi(opt.mutationFraction, 'auto') ) 30 | fraction = 2.0 / nVar; 31 | else 32 | error('NSGA2:MutationOpError', 'The "mutationsFraction" parameter should be scalar or "auto" string.'); 33 | end 34 | else 35 | fraction = opt.mutationFraction; 36 | end 37 | 38 | 39 | % All of the individual would be modified, but only 'mutationFraction' of design 40 | % variables for an individual would be changed. 41 | for ind = 1:length(pop) 42 | child = fun( pop(ind), opt, state, fraction, mutationopt); 43 | 44 | % Rounding for integer variables 45 | for v = 1:nVar 46 | if( opt.vartype(v) == 2) 47 | child.var(v) = round( child.var(v) ); 48 | end 49 | end 50 | 51 | child.var = varlimit(child.var, opt.lb, opt.ub); 52 | 53 | pop(ind) = child; 54 | end 55 | 56 | 57 | 58 | function child = mutationGaussian( parent, opt, state, fraction, options) 59 | % Function: child = mutationGaussian( parent, opt, state, fraction, options) 60 | % Description: Gaussian mutation operator. Reference Matlab's help : 61 | % Genetic Algorithm Options :: Options Reference (Global Optimization Toolbox) 62 | % Parameters: 63 | % fraction : mutation fraction of variables of an individual 64 | % options{1} : scale. This paramter should be large enough for interger variables 65 | % to change from one to another. 66 | % options{2} : shrink 67 | % Return: 68 | % 69 | % LSSSSWC, NWPU 70 | % Revision: 1.1 Data: 2011-07-13 71 | %************************************************************************* 72 | 73 | 74 | %************************************************************************* 75 | % 1. Verify the parameters. 76 | %************************************************************************* 77 | if( length(options)~=2) 78 | error('NSGA2:MutationOpError', 'Mutation operator parameter error!'); 79 | end 80 | 81 | 82 | %************************************************************************* 83 | % 2. Calc the "scale" and "shrink" parameter. 84 | %************************************************************************* 85 | scale = options{1}; 86 | shrink = options{2}; 87 | scale = scale - shrink * scale * state.currentGen / opt.maxGen; 88 | 89 | lb = opt.lb; 90 | ub = opt.ub; 91 | scale = scale * (ub - lb); 92 | 93 | 94 | %************************************************************************* 95 | % 3. Do the mutation. 96 | %************************************************************************* 97 | child = parent; 98 | numVar = length(child.var); 99 | for i = 1:numVar 100 | if(rand() < fraction) 101 | child.var(i) = parent.var(i) + scale(i) * randn(); 102 | end 103 | end 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /NSGA-II/ndsort.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PEESEgroup/PSA/3de0832320971656dd79d9c7473d4029a10fee97/NSGA-II/ndsort.m -------------------------------------------------------------------------------- /NSGA-II/nsga2.m: -------------------------------------------------------------------------------- 1 | function result = nsga2(opt, varargin) 2 | % Function: result = nsga2(opt, varargin) 3 | % Description: The main flowchart of of NSGA-II. Note: 4 | % All objectives must be minimization. If a objective is maximization, the 5 | % objective should be multipled by -1. 6 | % 7 | % Syntax: 8 | % result = nsga2(opt): 'opt' is generated by function nsgaopt(). 9 | % result = nsga2(opt, param): 'param' can be any data type, it will be 10 | % pass to the objective function objfun(). 11 | % 12 | % Then ,the result structure can be pass to plotnsga to display the 13 | % population: plotnsga(result); 14 | % 15 | % Parameters: 16 | % opt : A structure generated by funciton nsgaopt(). 17 | % varargin : Additional parameter will be pass to the objective functions. 18 | % It can be any data type. For example, if you call: nsga2(opt, param), 19 | % then objfun would be called as objfun(x,param), in which, x is the 20 | % design variables vector. 21 | % Return: 22 | % result : A structure contains optimization result. 23 | % 24 | % LSSSSWC, NWPU 25 | % Revision: 1.2 Data: 2011-07-26 26 | %************************************************************************* 27 | 28 | 29 | tStart = tic(); 30 | %************************************************************************* 31 | % Verify the optimization model 32 | %************************************************************************* 33 | opt = verifyOpt(opt); 34 | 35 | %************************************************************************* 36 | % variables initialization 37 | %************************************************************************* 38 | nVar = opt.numVar; 39 | nObj = opt.numObj; 40 | nCons = opt.numCons; 41 | popsize = opt.popsize; 42 | 43 | % pop : current population 44 | % newpop : new population created by genetic algorithm operators 45 | % combinepop = pop + newpop; 46 | pop = repmat( struct(... 47 | 'var', zeros(1,nVar), ... 48 | 'obj', zeros(1,nObj), ... 49 | 'cons', zeros(1,nCons),... 50 | 'rank', 0,... 51 | 'distance', 0,... 52 | 'prefDistance', 0,... % preference distance used in R-NSGA-II 53 | 'nViol', 0,... 54 | 'violSum', 0),... 55 | [1,popsize]); 56 | 57 | % state: optimization state of one generation 58 | state = struct(... 59 | 'currentGen', 1,... % current generation number 60 | 'evaluateCount', 0,... % number of objective function evaluation 61 | 'totalTime', 0,... % total time from the beginning 62 | 'firstFrontCount', 0,... % individual number of first front 63 | 'frontCount', 0,... % number of front 64 | 'avgEvalTime', 0 ... % average evaluation time of objective function (current generation) 65 | ); 66 | 67 | result.pops = repmat(pop, [opt.maxGen, 1]); % each row is the population of one generation 68 | result.states = repmat(state, [opt.maxGen, 1]); % each row is the optimizaiton state of one generation 69 | result.opt = opt; % use for output 70 | 71 | % global variables 72 | global STOP_NSGA; %STOP_NSGA : used in GUI , if STOP_NSGA~=0, then stop the optimizaiton 73 | STOP_NSGA = 0; 74 | 75 | 76 | %************************************************************************* 77 | % initialize the P0 population 78 | %************************************************************************* 79 | ngen = 1; 80 | pop = opt.initfun{1}(opt, pop, opt.initfun{2:end}); 81 | [pop, state] = evaluate(opt, pop, state, varargin{:}); 82 | [opt, pop] = ndsort(opt, pop); 83 | 84 | % state 85 | state.currentGen = ngen; 86 | state.totalTime = toc(tStart); 87 | state = statpop(pop, state); 88 | 89 | result.pops(1, :) = pop; 90 | result.states(1) = state; 91 | 92 | % output 93 | opt = callOutputfuns(opt, state, pop); 94 | 95 | 96 | %************************************************************************* 97 | % NSGA2 iteration 98 | %************************************************************************* 99 | while( ngen < opt.maxGen && STOP_NSGA==0) 100 | % 0. Display some information 101 | ngen = ngen+1; 102 | state.currentGen = ngen; 103 | 104 | % 1. Create new population 105 | newpop = selectOp(opt, pop); 106 | newpop = crossoverOp(opt, newpop, state); 107 | newpop = mutationOp(opt, newpop, state); 108 | [newpop, state] = evaluate(opt, newpop, state, varargin{:}); 109 | 110 | % 2. Combine the new population and old population : combinepop = pop + newpop 111 | combinepop = [pop, newpop]; 112 | 113 | % 3. Fast non dominated sort 114 | [opt, combinepop] = ndsort(opt, combinepop); 115 | 116 | % 4. Extract the next population 117 | pop = extractPop(opt, combinepop); 118 | 119 | % 5. Save current generation results 120 | state.totalTime = toc(tStart); 121 | state = statpop(pop, state); 122 | 123 | result.pops(ngen, :) = pop; 124 | result.states(ngen) = state; 125 | 126 | % 6. output 127 | if( mod(ngen, opt.outputInterval)==0 ) 128 | opt = callOutputfuns(opt, state, pop); 129 | end 130 | 131 | end 132 | 133 | % call output function for closing file 134 | opt = callOutputfuns(opt, state, pop, -1); 135 | 136 | 137 | 138 | %toc(tStart); 139 | 140 | 141 | -------------------------------------------------------------------------------- /NSGA-II/nsgaopt.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PEESEgroup/PSA/3de0832320971656dd79d9c7473d4029a10fee97/NSGA-II/nsgaopt.m -------------------------------------------------------------------------------- /NSGA-II/output2file.m: -------------------------------------------------------------------------------- 1 | function opt = output2file(opt, state, pop, type, varargin) 2 | % Function: opt = output2file(opt, state, pop, type, varargin) 3 | % Description: Output the population 'pop' to file. The file name is 4 | % specified by 'opt.outputfile' field. 5 | % Parameters: 6 | % type : output type. -1 = the last call, close the opened file. 7 | % others(or no exist) = normal output 8 | % varargin : any parameter define in the options.outputfuns cell array. 9 | % 10 | % LSSSSWC, NWPU 11 | % Revision: 1.2 Data: 2011-07-13 12 | %************************************************************************* 13 | 14 | 15 | if(isempty(opt.outputfile)) 16 | return; % the output file name is not specified, return directly 17 | end 18 | 19 | if( isfield(opt, 'outputfileFID') ) 20 | fid = opt.outputfileFID; 21 | else 22 | fid = []; 23 | end 24 | 25 | %************************************************************************* 26 | % 1.Open the output file and output some population info 27 | %************************************************************************* 28 | if( isempty(fid) ) 29 | fid = fopen(opt.outputfile, 'w'); 30 | if( fid == 0) 31 | error('NSGA2:OutputFileError', 'Can not open output file!! file name:%s', opt.outputfile); 32 | end 33 | opt.outputfileFID = fid; 34 | 35 | % Output some infomation 36 | fprintf(fid, '#NSGA2\r\n'); 37 | 38 | fprintf(fid, 'popsize %d\r\n', opt.popsize); 39 | fprintf(fid, 'maxGen %d\r\n', opt.maxGen); 40 | fprintf(fid, 'numVar %d\r\n', opt.numVar); 41 | fprintf(fid, 'numObj %d\r\n', opt.numObj); 42 | fprintf(fid, 'numCons %d\r\n', opt.numCons); 43 | 44 | % Output state field names 45 | fprintf(fid, 'stateFieldNames\t'); 46 | names = fieldnames(state); 47 | for i = 1:length(names) 48 | fprintf(fid, '%s\t', names{i}); 49 | end 50 | fprintf(fid, '\r\n'); 51 | 52 | fprintf(fid, '#end\r\n\r\n\r\n'); 53 | end 54 | 55 | %************************************************************************* 56 | % 2. If this is the last call, close the output file 57 | %************************************************************************* 58 | if(type == -1) 59 | fclose(fid); 60 | rmfield(opt, 'outputfileFID'); 61 | return 62 | end 63 | 64 | %************************************************************************* 65 | % 3. Output population to file 66 | %************************************************************************* 67 | fprintf(fid, '#Generation %d / %d\r\n', state.currentGen, opt.maxGen); 68 | 69 | % output each state field 70 | names = fieldnames(state); 71 | for i = 1:length(names) 72 | fprintf(fid, '%s\t%g\r\n', names{i}, getfield(state, names{i})); 73 | end 74 | fprintf(fid, '#end\r\n'); 75 | 76 | for i = 1:opt.numVar 77 | fprintf(fid, 'Var%d\t', i); 78 | end 79 | for i = 1:opt.numObj 80 | fprintf(fid, 'Obj%d\t', i); 81 | end 82 | for i = 1:opt.numCons 83 | fprintf(fid, 'Cons%d\t', i); 84 | end 85 | fprintf(fid, '\r\n'); 86 | 87 | for p = 1 : opt.popsize 88 | for i = 1:opt.numVar 89 | fprintf(fid, '%g\t', pop(p).var(i) ); 90 | end 91 | for i = 1:opt.numObj 92 | fprintf(fid, '%g\t', pop(p).obj(i) ); 93 | end 94 | for i = 1:opt.numCons 95 | fprintf(fid, '%g\t', pop(p).cons(i)); 96 | end 97 | fprintf(fid, '\r\n'); 98 | end 99 | 100 | fprintf(fid, '\r\n\r\n\r\n'); 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /NSGA-II/selectOp.m: -------------------------------------------------------------------------------- 1 | function newpop = selectOp(opt, pop) 2 | % Function: newpop = selectOp(opt, pop) 3 | % Description: Selection operator, use binary tournament selection. 4 | % 5 | % LSSSSWC, NWPU 6 | % Revision: 1.1 Data: 2011-07-12 7 | %************************************************************************* 8 | 9 | popsize = length(pop); 10 | pool = zeros(1, popsize); % pool : the individual index selected 11 | 12 | randnum = randi(popsize, [1, 2 * popsize]); 13 | 14 | j = 1; 15 | for i = 1:2:(2*popsize) 16 | p1 = randnum(i); 17 | p2 = randnum(i+1); 18 | 19 | if(~isempty(opt.refPoints)) 20 | % Preference operator (R-NSGA-II) 21 | result = preferenceComp( pop(p1), pop(p2) ); 22 | else 23 | % Crowded-comparison operator (NSGA-II) 24 | result = crowdingComp( pop(p1), pop(p2) ); 25 | end 26 | 27 | if(result == 1) 28 | pool(j) = p1; 29 | else 30 | pool(j) = p2; 31 | end 32 | 33 | j = j + 1; 34 | end 35 | newpop = pop( pool ); 36 | 37 | 38 | 39 | function result = crowdingComp( guy1, guy2) 40 | % Function: result = crowdingComp( guy1, guy2) 41 | % Description: Crowding comparison operator. 42 | % Return: 43 | % 1 = guy1 is better than guy2 44 | % 0 = other cases 45 | % 46 | % LSSSSWC, NWPU 47 | % Revision: 1.0 Data: 2011-04-20 48 | %************************************************************************* 49 | 50 | if((guy1.rank < guy2.rank) || ((guy1.rank == guy2.rank) && (guy1.distance > guy2.distance) )) 51 | result = 1; 52 | else 53 | result = 0; 54 | end 55 | 56 | 57 | 58 | function result = preferenceComp(guy1, guy2) 59 | % Function: result = preferenceComp(guy1, guy2) 60 | % Description: Preference operator used in R-NSGA-II 61 | % Return: 62 | % 1 = guy1 is better than guy2 63 | % 0 = other cases 64 | % 65 | % Copyright 2011 by LSSSSWC 66 | % Revision: 1.0 Data: 2011-07-11 67 | %************************************************************************* 68 | 69 | if( (guy1.rank < guy2.rank) || ... 70 | ((guy1.rank == guy2.rank) && (guy1.prefDistance < guy2.prefDistance)) ) 71 | result = 1; 72 | else 73 | result = 0; 74 | end 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /NSGA-II/sortt.m: -------------------------------------------------------------------------------- 1 | function [results, parameters]=sortt(A) 2 | %% 3 | 4 | a=A.pops(end, :); 5 | 6 | [~, n]=size(a); 7 | 8 | q=length(a(1, 1).obj); 9 | results=zeros(n, q); 10 | qq=a(1, 1).var; 11 | [~, nn]=size(qq); 12 | 13 | parameters=zeros(n, nn); 14 | 15 | 16 | for i= 1:n 17 | 18 | results(i, 1:q)=a(1, i).obj; 19 | parameters(i, 1:nn)=a(1, i).var; 20 | 21 | end 22 | %% 23 | 24 | results=abs(results); 25 | 26 | end -------------------------------------------------------------------------------- /NSGA-II/statpop.m: -------------------------------------------------------------------------------- 1 | function state = statpop(pop, state) 2 | % Function: state = statpop(pop, state) 3 | % Description: Statistic Population. 4 | % 5 | % LSSSSWC, NWPU 6 | % Revision: 1.0 Data: 2011-04-20 7 | %************************************************************************* 8 | 9 | 10 | N = length(pop); 11 | rankVec = vertcat(pop.rank); 12 | rankVec = sort(rankVec); 13 | 14 | state.frontCount = rankVec(N); 15 | state.firstFrontCount = length( find(rankVec==1) ); 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /NSGA-II/varlimit.m: -------------------------------------------------------------------------------- 1 | function var = varlimit(var, lb, ub) 2 | % Function: var = varlimit(var, lb, ub) 3 | % Description: Limit the variables in [lb, ub]. 4 | % 5 | % LSSSSWC, NWPU 6 | % Revision: 1.0 Data: 2011-04-20 7 | %************************************************************************* 8 | 9 | numVar = length(var); 10 | for i = 1:numVar 11 | if( var(i) < lb(i) ) 12 | var(i) = lb(i); 13 | elseif( var(i) > ub(i) ) 14 | var(i) = ub(i); 15 | end 16 | end 17 | 18 | -------------------------------------------------------------------------------- /NSGA-II/verifyOpt.m: -------------------------------------------------------------------------------- 1 | function opt = verifyOpt(opt) 2 | % Function: opt = verifyOpt(opt) 3 | % Description: Verify the optimization model. 4 | % LSSSSWC, NWPU 5 | % Revision: 1.1 Data: 2011-07-15 6 | %************************************************************************* 7 | 8 | 9 | %************************************************************************* 10 | % popsize 11 | %************************************************************************* 12 | if ( mod(opt.popsize, 2) ~= 0 ) 13 | %warning('NSGA2:PopSizeError', 'The population size shoud be even number!%d => %d', opt.popsize, opt.popsize+1); 14 | opt.popsize = opt.popsize + 1; 15 | end 16 | 17 | %************************************************************************* 18 | % lb, ub 19 | %************************************************************************* 20 | if( length(opt.lb)~=opt.numVar || length(opt.lb)~=opt.numVar ) 21 | error('NSGA2:OptModelError', 'The numbers of lower and upper bounds(%d,%d) should be equal to the design variable number(%d)!', ... 22 | length(opt.ub), length(opt.lb), opt.numVar); 23 | end 24 | 25 | %************************************************************************* 26 | % vartype 27 | %************************************************************************* 28 | if( length(opt.vartype) ~= opt.numVar ) 29 | %warning('NSGA2:OptModelWarning', 'Design variables'' data type error! All the type is set to REAL coding (vartype=1)!'); 30 | opt.vartype = ones(1, opt.numVar); 31 | end 32 | 33 | %************************************************************************* 34 | % nameObj, nameVar, nameCons 35 | %************************************************************************* 36 | if( ~iscell(opt.nameObj) || ~iscell(opt.nameVar) || ~iscell(opt.nameCons)) 37 | error('NSGA2:OptModelError', 'The names of objectives, design variables or constraints should be specified in cell array, for example, {''obj1'',''obj2''}'); 38 | end 39 | 40 | if( (~isempty(opt.nameObj) && length(opt.nameObj)~=opt.numObj) || ... 41 | (~isempty(opt.nameVar) && length(opt.nameVar)~=opt.numVar) || ... 42 | (~isempty(opt.nameCons) && length(opt.nameCons)~=opt.numCons)) 43 | error('NSGA2:OptModelError', 'All names of objectives, design variables or constraints should be specified, if one is specified!'); 44 | end 45 | 46 | %************************************************************************* 47 | % useparallel 48 | %************************************************************************* 49 | if( ~ischar(opt.useParallel) || ... 50 | isempty( find(strcmpi(opt.useParallel, {'yes', 'no'}))) ) 51 | error('NSGA2:OptParamError', 'useParallel can be only "yes" or "no"!'); 52 | end 53 | 54 | %************************************************************************* 55 | % R-NSGA-II parameters 56 | %************************************************************************* 57 | % refPoints 58 | if( ~isempty(opt.refPoints) && size(opt.refPoints,2)~=opt.numObj) 59 | error('NSGA2:OptParamError', 'The reference points has the format refPoints(nPoint, numObj)!'); 60 | end 61 | % refWeight 62 | if( ~isempty(opt.refPoints) && ~isempty(opt.refWeight) && length(opt.refWeight)~=opt.numObj) 63 | error('NSGA2:OptParamError', 'The weight factor vector used in R-NSGA-II must has the length of numObj!'); 64 | end 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /PSACycle.m: -------------------------------------------------------------------------------- 1 | function [objectives, constraints, a, b, c, d, e, t1, t2, t3, t4, t5] = PSACycle(vars, material, x0, type, N, it_disp) 2 | %Skarstrom: Simulate a 5-Step Modified Skarstrom PSA cycle 3 | % This function is able to simulate a 5-Step Modified Skarstrom PSA cycle 4 | % and provide the state variables and process objectives. Finite Volume 5 | % method is used for calculating the derivatives due to the inherent mass 6 | % and energy conservation due to calculating the flux. An example of the 7 | % FVM set up is shown below with the ordering of the volumes 8 | % 9 | % Diagram of Column divided into sections shown below with corresponding 10 | % designation for inlet and outlet. N is a user defined value below 11 | % 12 | % Inlet Outlet 13 | % -------------------------------\ \---------------------- 14 | % 1 | 2 | 3 | 4 | N| N+1 | N+2 15 | % --------------------------------\ \--------------------- 16 | % 17 | % Input: 18 | % vars: Process variables which are in order: Length of column [m] 19 | % adsoprtion pressure [Pa], inlet molar flux [mol/s/m^2], time of 20 | % adsorption step [s], light product reflux ratio [-], heavy product 21 | % reflux ratio [-], and intermediate pressure. Not all of these 22 | % variables are used for all cycles, so when the simulated cycle do 23 | % not have the step relating the "vars", assign these to zero. These 24 | % variables are inputed as they are desing variables and can be 25 | % changed to be optimized. 26 | % 27 | % x0 : Initial profile of state variables in the column, it's not mandatory 28 | % N : Number of Finite Volumes, it's not mandatory 29 | % 30 | % it_disp: This tells the program whether to show the cycle number along 31 | % with the CCS values or not. This is automatically set to no to 32 | % allow for the speed up of calculations. 33 | % 34 | % Output: 35 | % The output variables can be customized depend upon of the purpose. Here 36 | % are two cases: 37 | % 1) Optimization: it is necessary to get the "objectives" variable: this 38 | % provides the purity and recovery of CO2. And also the constraints: 39 | % Provides whether a constraint has been violated or not. There are 40 | % some constraints that need to be satisfied. First, the recovery of 41 | % the process must be over 90%. Second, the purity of the final product 42 | % must be greater than the entering stream. 43 | % 44 | % a-e: These are the state variables for the five steps: 45 | % CoCPressurization, Adsorption, Heavy Reflux, CnCDepressurization and 46 | % Light Reflux. 47 | % The matrix is set up where each row contains all the values of the 48 | % state variables at a single moment in time, and each column contains 49 | % the values of a single state variable at a single location throughout 50 | % the step. As shown above in the graph, for each state variable there 51 | % are N volumes for which the value is known at the center of volume. 52 | % In addition, for calculating purity and recovery, the state variable 53 | % values at the two ends of the column are also provided. NOTE: since 54 | % there are no spatial derivatives in the solid loading equations, and 55 | % purity and recovery do not need these values, they are assumed to be 56 | % equal to the volume next to it. The order of the state variables are 57 | % as follows 58 | % 59 | % a-e(:, 1:N+2) are the dimensionless pressure. In order to retrieve the 60 | % true pressure, multiply the dimensionless pressure by the adsorption 61 | % pressure, P_0 or P_H. 62 | % a-e(:, N+3:2*N+4) are the CO2 gas mole fraction 63 | % a-e(:, 2*N+5:3*N+6) are the dimensionless CO2 molar loadings. In order 64 | % to retrieve the true molar loading, multiply the dimensionless molar 65 | % loading by the molar loading scaling factor, q_s 66 | % a-e(:, 3*N+7:4*N+8) are the dimensionless N2 molar loadings. In order 67 | % to retrieve the true molar loading, multiply the dimensionless molar 68 | % loading by the molar loading scaling factor, q_s 69 | % a-e(:, 4*N+9:5*N+10) are the dimensionless column temperature. In order 70 | % to retrieve the true column temperature, multiply the column temperature 71 | % by the feed temperature, T_0 72 | % 73 | % t1-t5 are the dimensionless times for the five steps. In order to 74 | % retrieve the true time, multiply the dimensionless time by the ratio 75 | % of the length to the velocity scaling factor (L/v_0) 76 | % 77 | % 2) Data collection intended to ANNs training: To get an output suitable 78 | % to train Artificial Neural Network of each step of the PSA cycle, 79 | % it is necessay to collect the initial and final states of variables 80 | % ans store it in adequate variables to be used by the ANN toolbox. 81 | % These variables are: a_fin, b_fin, c_fin, d_fin, e_fin, a_in, b_in, 82 | % c_in, d_in, e_in. 83 | % Where fin means final state, and in means initial state. 84 | % 85 | % The following assumptions were made: 86 | % 1) Ideal Gas Law is used to describe the gas phase 87 | % 2) No concentration, pressure, or temperature gradient in the the radial 88 | % or azmuth directions 89 | % 3) Linear Driving Force is used to describe the gas diffusion into the 90 | % adsorbent 91 | % 4) Adsorbent properties, and void fraction are constant throughout the 92 | % column 93 | % 5) Viscosity of the gas is independent of pressure 94 | % 6) There is thermal equilibrium between the adsorbent and the gas phase 95 | % 7) Ergun Equation is used to describe the pressure drop across the bed 96 | % 8) Column operates adiabatically, so any wall energy balance have to be 97 | % used 98 | % 9) An axially dispersed plug flow model is used to represent bulk fluid 99 | % flow 100 | % 101 | %% Check number of inputs 102 | % If no value is given for the iteration variable, it is no. If no value 103 | % is given for N, it is 10. If no value is given for the x, it is empty. 104 | if nargin < 6 105 | it_disp = 'no'; 106 | if nargin < 5 107 | N = 10 ; 108 | if nargin < 4 109 | type = 'ProcessEvaluation' ; 110 | end 111 | end 112 | end 113 | % 114 | %% Initialize parameter for the simulation 115 | % Specify the parameters for the simulation 116 | 117 | % Initialize objectives and constraints output 118 | switch type 119 | case 'ProcessEvaluation' 120 | constraints = [0, 0, 0]; 121 | case 'EconomicEvaluation' 122 | constraints = [0, 0, 0]; 123 | otherwise 124 | error('Error. %s is not a recognizable type of operation.' , type) ; 125 | end 126 | objectives = [0, 0] ; 127 | 128 | % Input parameters 129 | InputParams = ProcessInputParameters(vars, material, N) ; 130 | Params = InputParams{1} ; 131 | IsothermParams = InputParams{2} ; 132 | Times = InputParams{3} ; 133 | EconomicParams = InputParams{4} ; 134 | % economic_class = InputParams{5} ; 135 | 136 | % Retrieve process parameters 137 | N = Params(1) ; 138 | ro_s = Params(4) ; 139 | T_0 = Params(5) ; 140 | epsilon = Params(6) ; 141 | r_p = Params(7) ; 142 | mu = Params(8) ; 143 | R = Params(9) ; 144 | v_0 = Params(10) ; 145 | q_s0 = Params(11)/ro_s ; 146 | P_0 = Params(17) ; 147 | L = Params(18) ; 148 | MW_CO2 = Params(19) ; 149 | MW_N2 = Params(20) ; 150 | y_0 = Params(23) ; 151 | ndot_0 = vars(3) ; 152 | P_l = Params(25) ; 153 | P_inlet = Params(26) ; 154 | alpha = Params(30) ; 155 | beta = Params(31) ; 156 | y_HR = Params(33) ; 157 | T_HR = Params(34) ; 158 | ndot_HR = Params(35) ; 159 | 160 | % Call PSA cycle functions 161 | CoCPressurization_fxn = @(t, x) FuncCoCPressurization(t, x, Params, IsothermParams) ; 162 | Adsorption_fxn = @(t, x) FuncAdsorption(t, x, Params, IsothermParams) ; 163 | HeavyReflux_fxn = @(t, x) FuncHeavyReflux(t, x, Params, IsothermParams) ; 164 | CnCDepressurization_fxn = @(t, x) FuncCnCDepressurization(t, x, Params, IsothermParams) ; 165 | 166 | % Retrieve times of PSA steps 167 | t_CoCPres = Times(1) ; 168 | t_ads = Times(2) ; 169 | t_HR = Times(6) ; 170 | t_CnCDepres = Times(3) ; 171 | t_LR = Times(4) ; 172 | 173 | % Dimensionless times 174 | tau_CoCPres = t_CoCPres*v_0/L ; 175 | tau_ads = t_ads*v_0/L ; 176 | tau_HR = t_HR*v_0/L ; 177 | tau_CnCDepres = t_CnCDepres*v_0/L ; 178 | tau_LR = t_LR*v_0/L ; 179 | 180 | % Initialize the column (initial conditions) 181 | if nargin < 2 || isempty(x0) 182 | q = Isotherm(y_0, P_l, 298.15, IsothermParams) ; 183 | x0 = zeros(5*N+10,1) ; 184 | x0(1:N+2) = P_l/P_0 ; 185 | x0(N+3) = y_0 ; 186 | x0(N+4:2*N+4) = y_0 ; 187 | x0(2*N+5:3*N+6) = q(1)/q_s0 ; 188 | x0(3*N+7:4*N+8) = q(2)/q_s0 ; 189 | x0(4*N+9) = 1 ; 190 | x0(4*N+10:5*N+10)= 298.15/T_0 ; 191 | end 192 | 193 | % opts1 = odeset( 'RelTol', 1e-6) ; 194 | % opts2 = odeset( 'RelTol', 1e-6) ; 195 | % opts3 = odeset( 'RelTol', 1e-6) ; 196 | % opts4 = odeset( 'RelTol', 1e-6) ; 197 | % opts5 = odeset( 'RelTol', 1e-6) ; 198 | 199 | opts1 = odeset( 'JPattern', JacPressurization(N), 'RelTol', 1e-6) ; 200 | opts2 = odeset( 'JPattern', JacAdsorption(N), 'RelTol', 1e-6) ; 201 | opts3 = odeset( 'JPattern', JacAdsorption(N), 'RelTol', 1e-6) ; 202 | opts4 = odeset( 'JPattern', Jac_CnCDepressurization(N), 'RelTol', 1e-6) ; 203 | opts5 = odeset( 'JPattern', Jac_LightReflux(N), 'RelTol', 1e-6) ; 204 | % 205 | %% Begin simulating PSA cycle. This is skipped if the first constraint is violated. 206 | % Run the simulation until the change in the temperature, gas mole fraction 207 | % and CO2 molar loading is less than 0.5%. For the molar loading and the 208 | % mole fraction, the simulation also stops if the absolute change in the 209 | % state variable is less than 5e-5 and 2.5e-4 respectively. 210 | 211 | % initialize variables to store the desired data to be collected 212 | a_in = [] ; 213 | b_in = [] ; 214 | c_in = [] ; 215 | d_in = [] ; 216 | e_in = [] ; 217 | a_fin = [] ; 218 | b_fin = [] ; 219 | c_fin = [] ; 220 | d_fin = [] ; 221 | e_fin = [] ; 222 | 223 | if constraints(1) == 0 224 | 225 | for i=1:700 226 | %disp(['Iteration for CSS condition number: ', num2str(i)]) 227 | 228 | % Store initial conditions for CoCPressurization step - all iterations 229 | a_in = [a_in; x0'] ; 230 | 231 | %% 1. Simulate CoCPressurization step 232 | [t1, a] = ode15s(CoCPressurization_fxn, [0 tau_CoCPres], x0, opts1) ; 233 | 234 | % Correct the output (clean up results from simulation) 235 | idx = find(a(:, 1) < a(:, 2)) ; % P_1 < P_2 236 | a(idx ,1) = a(idx, 2) ; % P_1 = P_2 237 | a(idx, N+3) = a(idx, N+4) ; % y_1 = y_2 238 | a(idx, 4*N+9) = a(idx, 4*N+10) ; % T_1 = T_2 239 | a(:, 2*N+5) = a(:, 2*N+6) ; % x1_1 = x1_2 240 | a(:, 3*N+7) = a(:, 3*N+8) ; % x2_1 = x2_2 241 | a(:, 3*N+6) = a(:, 3*N+5) ; % x1_N+2 = x1_N+1 242 | a(:, 4*N+8) = a(:, 4*N+7) ; % x2_N+2 = x2_N+1 243 | a(:, N+3:2*N+4) = max(min(a(:, N+3:2*N+4), 1), 0) ; % 0 <= y => 1 244 | 245 | % Store final conditions for CoCPressurization step - all iterations 246 | % and the CO2 and total moles at the Front and End of the column 247 | [totalFront, CO2Front, ~] = StreamCompositionCalculator(t1*L/v_0, a, 'HPEnd') ; 248 | [totalEnd, CO2End, ~] = StreamCompositionCalculator(t1*L/v_0, a, 'LPEnd') ; 249 | a_fin = [a_fin; a(end, :), CO2Front, totalFront, CO2End, totalEnd] ; 250 | 251 | % Prepare initial conditions for Adsorption step 252 | x10 = a(end, :)' ; % Final state of previous step is the 253 | % initial state for current step 254 | x10(1) = P_inlet ; % BC z=0 P: P_1 = P_inlet 255 | x10(N+2) = 1 ; % BC z=1 P: P_N+2 = 1 256 | x10(N+3) = y_0 ; % BC z=0 y: y_1 = y_0 257 | x10(2*N+4) = x10(2*N+3) ; % BC z=1 y: y_N+2 = y_N+1 258 | x10(4*N+9) = 1 ; % BC z=0 T: T_1 = 1 259 | x10(5*N+10) = x10(5*N+9) ; % BC z=1 T: T_N+2 = T_N+1 260 | 261 | % Store initial conditions for Adsorption step - all iterations 262 | b_in = [b_in; x10'] ; 263 | 264 | % Initial conditions of states at first step of the PSA cycle 265 | statesIC = a(1, [2:N+1, N+4:2*N+3, 2*N+6:3*N+5, 3*N+8:4*N+7, 4*N+10:5*N+9]) ; 266 | % 267 | %% 2. Simulate Adsorption step 268 | [t2, b] = ode15s(Adsorption_fxn, [0 tau_ads], x10, opts2) ; 269 | 270 | % Correct the output (clean up results from simulation) 271 | idx = find(b(:, N+1) < 1) ; % P_N+1 < 1 = P_N+2 272 | b(idx, N+2) = b(idx, N+1) ; % P_N+2 = P_N+1 273 | b(:, 2*N+5) = b(:, 2*N+6) ; % x1_1 = x1_2 274 | b(:, 3*N+7) = b(:, 3*N+8) ; % x2_1 = x2_2 275 | b(:, 3*N+6) = b(:, 3*N+5) ; % x1_N+2 = x1_N+1 276 | b(:, 4*N+8) = b(:, 4*N+7) ; % x2_N+2 = x2_N+1 277 | b(:, N+3:2*N+4) = max(min(b(:, N+3:2*N+4), 1), 0) ; % 0 <= y => 1 278 | 279 | if Params(end) == 0 280 | %b = VelocityCorrection(b, ndot_0, 'HPEnd') ; 281 | b = velocitycleanup(b) ; 282 | end 283 | 284 | % Store final conditions for Adsorption step - all iterations 285 | % and the CO2 and total moles at the Front and End of the column 286 | [totalFront, CO2Front, ~] = StreamCompositionCalculator(t2*L/v_0, b, 'HPEnd') ; 287 | [totalEnd, CO2End, TEnd] = StreamCompositionCalculator(t2*L/v_0, b, 'LPEnd') ; 288 | b_fin = [b_fin; b(end, :), CO2Front, totalFront, CO2End, totalEnd] ; 289 | 290 | % Add and update necessary parameters for Light Reflux step. These 291 | % are the composition and temperature going out of the adsorption 292 | % (light product end), which are those for the inlet of the LR step 293 | y_LR = CO2End/totalEnd ; 294 | T_LR = TEnd ; 295 | ndot_LR = totalEnd/t_ads ; 296 | Params(27) = y_LR ; 297 | Params(28) = T_LR ; 298 | Params(29) = ndot_LR ; 299 | 300 | % Call the function for Light Reflux step with updated parameters 301 | LightReflux_fxn = @(t, x) FuncLightReflux(t, x, Params, IsothermParams) ; 302 | 303 | % Prepare initial conditions for Heavy Reflux step 304 | x20 = b(end, :)' ; % Final state of previous step is the 305 | % initial state for current step 306 | x20(1) = P_inlet ; % BC z=0 P: P_1 = P_inlet 307 | x20(1) = x20(2) ; 308 | x20(N+2) = 1 ; % BC z=1 P: P_N+2 = 1 309 | x20(N+3) = y_HR ; % BC z=0 y: y_1 = y_HR 310 | x20(2*N+4) = x20(2*N+3) ; % BC z=1 y: y_N+2 = y_N+1 311 | x20(4*N+9) = T_HR/T_0 ; % BC z=0 T: T_1 = T_HR/T_0 312 | x20(5*N+10) = x20(5*N+9) ; % BC z=1 T: T_N+2 = T_N+1 313 | 314 | % Store initial conditions for Heavy Reflux step - all iterations 315 | c_in = [c_in; x20'] ; 316 | % 317 | %% 3. Simulate Heavy Reflux step 318 | [t3, c] = ode15s(HeavyReflux_fxn, [0 tau_HR], x20, opts3) ; 319 | 320 | % Correct the output (clean up results from simulation) 321 | idx = find(c(:, N+1) < 1) ; % P_N+1 < 1 = P_N+2 322 | c(idx, N+2) = c(idx, N+1) ; % P_N+2 = P_N+1 323 | c(:, 2*N+5) = c(:, 2*N+6) ; % x1_1 = x1_2 324 | c(:, 3*N+7) = c(:, 3*N+8) ; % x2_1 = x2_2 325 | c(:, 3*N+6) = c(:, 3*N+5) ; % x1_N+2 = x1_N+1 326 | c(:, 4*N+8) = c(:, 4*N+7) ; % x2_N+2 = x2_N+1 327 | c(:, N+3:2*N+4) = max(min(c(:, N+3:2*N+4), 1), 0) ; % 0 <= y => 1 328 | 329 | if Params(end) == 0 330 | c = VelocityCorrection(c, ndot_HR, 'HPEnd') ; 331 | %c = velocitycleanup(c) ; 332 | end 333 | 334 | % Store final conditions for Heavy Reflux step - all iterations 335 | % and the CO2 and total moles at the Front and End of the column 336 | [totalFront, CO2Front, ~] = StreamCompositionCalculator(t3*L/v_0, c, 'HPEnd') ; 337 | [totalEnd, CO2End, ~] = StreamCompositionCalculator(t3*L/v_0, c, 'LPEnd') ; 338 | c_fin = [c_fin; c(end, :), CO2Front, totalFront, CO2End, totalEnd] ; 339 | 340 | % Prepare initial conditions for CoCDepressurization step 341 | x30 = c(end,:)' ; % Final state of previous step is the 342 | % initial state for current step 343 | x30(1) = x30(2) ; % BC z=0 P: P_1 = P_2 344 | x30(N+2) = x30(N+1) ; % BC z=1 P: P_N+2 = P_N+1 345 | x30(N+3) = x30(N+4) ; % BC z=0 y: y_1 = y_2 346 | x30(2*N+4) = x30(2*N+3) ; % BC z=1 y: y_N+2 = y_N+1 347 | x30(4*N+9) = x30(4*N+10) ; % BC z=0 T: T_1 = T_2 348 | x30(5*N+10) = x30(5*N+9) ; % BC z=1 T: T_N+2 = T_N+1 349 | 350 | % Store initial conditions for CoCDepressurization step - all iterations 351 | d_in = [d_in; x30'] ; 352 | % 353 | %% 4. Simulate CnCDepressurization step 354 | [t4, d] = ode15s(CnCDepressurization_fxn, [0 tau_CnCDepres], x30, opts4) ; 355 | 356 | % correct the output (clean up results from simulation) 357 | idx = find(d(:, 2) < d(:, 1)) ; % P_2 < P_1 358 | d(idx ,1) = d(idx, 2) ; % P_1 = P_2 359 | d(:, 2*N+5) = d(:, 2*N+6) ; % x1_1 = x1_2 360 | d(:, 3*N+7) = d(:, 3*N+8) ; % x2_1 = x2_2 361 | d(:, 3*N+6) = d(:, 3*N+5) ; % x1_N+2 = x1_N+1 362 | d(:, 4*N+8) = d(:, 4*N+7) ; % x2_N+2 = x2_N+1 363 | d(:, N+3:2*N+4) = max(min(d(:, N+3:2*N+4), 1), 0) ; % 0 <= y => 1 364 | 365 | % Store final donditions for CnCDepressurization step - all iterations 366 | % and the CO2 and total moles at the Front and End of the column 367 | [totalFront, CO2Front, ~] = StreamCompositionCalculator(t4*L/v_0, d, 'HPEnd') ; 368 | [totalEnd, CO2End, ~] = StreamCompositionCalculator(t4*L/v_0, d, 'LPEnd') ; 369 | d_fin = [d_fin; d(end, :), CO2Front, totalFront, CO2End, totalEnd] ; 370 | 371 | % Prepare initial donditions for Light Reflux step 372 | x40 = d(end,:)' ; % Final state of previous step is the 373 | % initial state for durrent step 374 | x40(1) = P_l/P_0 ; % Bd z=0 P: P_1 = P_l/P_0 375 | %x40(N+2) = 2*P_l/P_0 ; % Bd z=1 P: P_N+2 = 2*P_l/P_0 % NOTE: be aware of this alpha here, on the other dode is just 2 376 | x40(N+3) = x40(N+4) ; % Bd z=0 y: y_1 = y_2 377 | x40(2*N+4) = y_LR ; % Bd z=1 y: y_N+2 = y_LR 378 | x40(4*N+9) = x40(4*N+10) ; % Bd z=0 T: T_1 = T_2 379 | x40(5*N+10) = T_LR/T_0 ; % Bd z=1 T: T_N+2 = T_LR/T_0 380 | 381 | % Store initial conditions for Light Reflux step - all iterations 382 | e_in = [e_in; x40'] ; 383 | % 384 | %% 5. Simulate Light Reflux step 385 | [t5, e] = ode15s(LightReflux_fxn, [0 tau_LR], x40, opts5) ; 386 | 387 | % Correct the output (clean up results from simulation) 388 | idx = find(e(:, 2) < e(:, 1)) ; % P_2 < P_1 389 | e(idx ,1) = e(idx, 2) ; % P_1 = P_2 390 | e(:, 2*N+5) = e(:, 2*N+6) ; % x1_1 = x1_2 391 | e(:, 3*N+7) = e(:, 3*N+8) ; % x2_1 = x2_2 392 | e(:, 3*N+6) = e(:, 3*N+5) ; % x1_N+2 = x1_N+1 393 | e(:, 4*N+8) = e(:, 4*N+7) ; % x2_N+2 = x2_N+1 394 | e(:, N+3:2*N+4) = max(min(e(:, N+3:2*N+4), 1), 0) ; % 0 <= y => 1 395 | 396 | e = VelocityCorrection(e, ndot_LR*alpha, 'LPEnd') ; 397 | %e = velocitycleanup(e) ; 398 | 399 | % Store final conditions for Light Reflux step - all iterations 400 | % and the CO2 and total moles at the Front and End of the column 401 | [totalFront, CO2Front, TFront] = StreamCompositionCalculator(t5*L/v_0, e, 'HPEnd') ; 402 | [totalEnd, CO2End, ~] = StreamCompositionCalculator(t5*L/v_0, e, 'LPEnd') ; 403 | e_fin = [e_fin; e(end, :), CO2Front, totalFront, CO2End, totalEnd] ; 404 | 405 | % Calculate necessary parameters for Heavy Reflux step 406 | y_HR = CO2Front/totalFront ; 407 | T_HR = TFront ; 408 | ndot_HR = totalFront.*beta/t_HR ; 409 | Params(33) = y_HR ; 410 | Params(34) = T_HR ; 411 | Params(35) = ndot_HR ; 412 | 413 | HeavyReflux_fxn = @(t, x) FuncHeavyReflux(t, x, Params, IsothermParams) ; 414 | 415 | % Prepare initial conditions for CoCPressurization step 416 | x0 = e(end, :)' ; % Final state of previous step is the 417 | % initial state for current step 418 | x0(1) = x0(2) ; % BC z=0 P: P_1 = P_2 419 | x0(N+2) = x0(N+1) ; % BC z=1 P: P_N+2 = P_N+1 420 | x0(N+3) = y_0 ; % BC z=0 y: y_1 = y_0 421 | x0(2*N+4) = x0(2*N+3) ; % BC z=1 y: y_N+2 = y_N+1 422 | x0(4*N+9) = 1 ; % BC z=0 T: T_1 = 1 423 | x0(5*N+10) = x0(5*N+9) ; % BC z=1 T: T_N+2 = T_N+1 424 | 425 | % Final conditions of states at lat step of the PSA cycle 426 | statesFC = e(end, [2:N+1, N+4:2*N+3, 2*N+6:3*N+5, 3*N+8:4*N+7, 4*N+10:5*N+9]) ; 427 | % 428 | %% Check CCS condition 429 | 430 | % [cyclic_check, cyclic_display] = CCS_Check(a, b, c, d, e, t1, t2, t3, t4, t5) ; 431 | % 432 | % if strcmpi(it_disp, 'yes') == 1 433 | % display([i, cyclic_display]) ; 434 | % end 435 | % 436 | % if cyclic_check == 1 437 | % break 438 | % end 439 | % CCS condition of states 440 | CSS_states = norm(statesIC-statesFC) ; 441 | % Mass balance condition 442 | [~, ~, massBalance] = ProcessEvaluation(a, b, c, d, e, t1, t2, t3, t4, t5) ; 443 | 444 | % Check if CCS has been acheived or not 445 | if CSS_states <= 1e-3 && abs(massBalance-1) <= 0.005 446 | break 447 | end 448 | % % Condition to stop if the mass balance is not satisfied but also is 449 | % % not changing between ten consecutive iterations 450 | % mb(i) = massBalance ; 451 | % if i > 15 452 | % if CSS_states <= 1e-3 && abs(massBalance-1) > 0.005 453 | % stateMB = mb(end-15+1:end) ; 454 | % %diffStatesMB = stateMB(end:-1:2)-stateMB(end-1:-1:1) ; 455 | % diffStatesMB = stateMB(end-1:-1:1)-stateMB(end) ; 456 | % normStatesMB = norm(diffStatesMB) ; 457 | % if normStatesMB < 1e-5 458 | % break 459 | % end 460 | % end 461 | % end 462 | % 463 | end 464 | 465 | %% Process and Economic evaluation 466 | 467 | [purity, recovery, MB] = ProcessEvaluation(a, b, c, d, e, t1, t2, t3, t4, t5) ; 468 | 469 | desired_flow = EconomicParams(1) ; 470 | % cycle_time = EconomicParams(3) ; 471 | cycle_time = t_CoCPres + t_ads + t_HR + t_CnCDepres + t_LR ; 472 | 473 | % Calculate the amount of flue gas that is fed during the cycle 474 | [n_tot_pres, ~, ~] = StreamCompositionCalculator(t1*L/v_0, a, 'HPEnd') ; 475 | [n_tot_ads, ~, ~] = StreamCompositionCalculator(t2*L/v_0, b, 'HPEnd') ; 476 | gas_fed = n_tot_pres + n_tot_ads ; % mols/m^2 477 | 478 | % Calculate the required radius of the column to satisfy the molar flow rate 479 | radius_inner = sqrt((desired_flow.*cycle_time/gas_fed)/pi()) ; % m 480 | r_in = radius_inner ; 481 | 482 | % Calculate the energy required during the Pressurization step 483 | E_pres = CompressionEnergy(t1*L/v_0, a, 1e5) ; % kWh 484 | 485 | % Calculate the energy required during the Feed step 486 | E_feed = CompressionEnergy(t2*L/v_0, b, 1e5) ; % kWh 487 | 488 | % Calculate the energy required during the Heavy Reflux Step 489 | E_HR = CompressionEnergy(t3*L/v_0, c, 1e5) ; % kWh 490 | 491 | % Calculate the energy required during the Counter Current Depressurization step 492 | E_bldwn = VacuumEnergy(t4*L/v_0, d, 1e5) ; % kWh 493 | 494 | % Calculate the energy required during the Light Reflux Step 495 | E_evac = VacuumEnergy(t5*L/v_0, e, 1e5) ; % kWh 496 | 497 | % Calculate the total energy required 498 | energy_per_cycle = E_pres + E_feed + E_HR + E_bldwn + E_evac ; % [kWh per cycle] 499 | 500 | % Calculate the CO2 recovered during the cycle [ton CO_2 per cycle and mol/cycle] 501 | [~, n_CO2_CnCD, ~] = StreamCompositionCalculator(t4*L/v_0, d, 'HPEnd') ; 502 | [~, n_CO2_LR, ~] = StreamCompositionCalculator(t5*L/v_0, e, 'HPEnd') ; 503 | CO2_recovered_cycle = (n_CO2_CnCD+(1-beta)*n_CO2_LR)*r_in^2*pi()*MW_CO2/1e3 ; 504 | CO2_recovered_cycle2 = (n_CO2_CnCD+(1-beta)*n_CO2_LR)*r_in^2*pi() ; 505 | 506 | %Calculate the productivity of the column and the energy requirments 507 | mass_adsorbent = L*pi()*r_in^2*(1-epsilon)*ro_s ; 508 | productivity = CO2_recovered_cycle2./cycle_time./mass_adsorbent ; 509 | energy_requirments = energy_per_cycle./CO2_recovered_cycle ; 510 | 511 | % Compile objectives and constraint violations 512 | con = recovery/MB - 0.9 ; 513 | if con < 0 514 | constraints(2) = abs(con) ; 515 | end 516 | 517 | switch type 518 | case 'ProcessEvaluation' 519 | objectives(1) = -purity ; 520 | objectives(2) = -recovery/MB ; 521 | 522 | con = purity - y_0 ; 523 | if con < 0 524 | constraints(3) = abs(con) ; 525 | constraints(3) = 0 ; 526 | end 527 | 528 | case 'EconomicEvaluation' 529 | objectives(1) = -productivity ; 530 | objectives(2) = energy_requirments ; 531 | 532 | con = purity - 0.9 ; 533 | if con < 0 534 | constraints(3) = abs(con) ; 535 | end 536 | end 537 | % 538 | end 539 | % 540 | %% Filter stored data 541 | % Prepare the collected data to store it in the output variables only 542 | % every 5 cycle but guaranteeing that the last cycle (CSS condition) 543 | % is stored 544 | 545 | if mod(i, 5) ==0 546 | idx_out = [1, 5:5:i] ; 547 | else 548 | idx_out = [1, 5:5:i, i] ; 549 | end 550 | 551 | % a_fin = a_fin(idx_out, :) ; 552 | % b_fin = b_fin(idx_out, :) ; 553 | % c_fin = c_fin(idx_out, :) ; 554 | % d_fin = d_fin(idx_out, :) ; 555 | % e_fin = e_fin(idx_out, :) ; 556 | % a_in = a_in(idx_out, :) ; 557 | % b_in = b_in(idx_out, :) ; 558 | % c_in = c_in(idx_out, :) ; 559 | % d_in = d_in(idx_out, :) ; 560 | % e_in = e_in(idx_out, :) ; 561 | % 562 | %% Complementary Functions 563 | 564 | function [n_tot, n_CO2, Temp] = StreamCompositionCalculator(time, state_vars, ProductEnd) 565 | %MoleStreamCalculator: Calculate the composition of streams 566 | % Calculate the composition (moles of CO2 and total moles), and 567 | % temperature of a stream at any end of the column end. 568 | % 569 | % Input: 570 | % time : Dimensional time vector supplied by the ODE solver 571 | % state_vars: Dimensionless state variable matrix supplied for the 572 | % step from the ODE solver 573 | % ProductEnd: End of the column where the composition will be 574 | % calculated. OPTIONS: HPEnd and LPEnd. HPEnd stands for 575 | % heavy product end, which is at the bottom of the column, 576 | % and LPEnd stands for light product end, which is at the 577 | % top of the column. 578 | % 579 | % Output: 580 | % n_tot : Average total number of moles at the desired end of the 581 | % requested step 582 | % n_CO2 : Average number of moles of CO2 at the desired end of the 583 | % requested step 584 | % Temp : Average temperature at the desired end of the requested 585 | % step 586 | % 587 | %% Check number of inputs 588 | % If no value is given for the ProductEnd, this is set up by default 589 | % as HPEnd, which is the heavy product end for any step. 590 | if nargin < 3 591 | ProductEnd = 'HPEnd'; 592 | end 593 | % 594 | %% 595 | % Differential section length of the column 596 | dz = L/N ; 597 | 598 | % Dimensionalize all variables at the two ends of the columns 599 | % Collect pressure, temperature and mole fraction at the column end 600 | % of interest. 601 | if strcmpi(ProductEnd, 'HPEnd') == 1 602 | 603 | P = state_vars(:, 1:2)*P_0 ; 604 | y = state_vars(:, N+3) ; 605 | T = state_vars(:, 4*N+9)*T_0 ; 606 | 607 | % Calculate the density of the gas [kg/m^3] 608 | ro_g = (y*MW_CO2 + (1-y)*MW_N2).*P(:, 1)/R./T ; 609 | 610 | % calculate concentrations [mol/m^3] 611 | C_tot = P(:, 1)/R./T ; 612 | C_CO2 = C_tot.*y ; 613 | 614 | elseif strcmpi(ProductEnd, 'LPEnd') == 1 615 | 616 | P = state_vars(:, N+1:N+2)*P_0 ; 617 | y = state_vars(:, 2*N+4) ; 618 | T = state_vars(:, 5*N+10)*T_0 ; 619 | 620 | % calculate the density of the gas [kg/m^3] 621 | ro_g = (y*MW_CO2 + (1-y)*MW_N2).*P(:, 2)/R./T ; 622 | 623 | % calculate concentrations [mol/m^3] 624 | C_tot = P(:, 2)/R./T ; 625 | C_CO2 = C_tot.*y ; 626 | 627 | else 628 | error('Please specify in which end of the column the composition will be calculated. OPTIONS: HPEnd and LPEnd') 629 | end 630 | 631 | % Calculate the pressure gradient at the edges [Pa/m] 632 | dPdz = 2*(P(:, 2)-P(:, 1))/dz ; 633 | 634 | % calculate superficial velocity using ergun equation [m/s] 635 | viscous_term = 150*mu*(1-epsilon)^2/4/r_p^2/epsilon^3 ; 636 | kinetic_term = (1.75*(1-epsilon)/2/r_p/epsilon^3) * ro_g ; 637 | v = -sign(dPdz).*(-viscous_term+(abs(viscous_term^2+... 638 | 4*kinetic_term.*abs(dPdz))).^(.5))/2./kinetic_term ; 639 | 640 | % calculate molar fluxes [mol/m^2/s] 641 | ndot_tot = abs(v.*C_tot) ; 642 | ndot_CO2 = abs(v.*C_CO2) ; 643 | 644 | % calculate total moles per column area [mol/m^2] 645 | n_tot = trapz(time, ndot_tot) ; 646 | n_CO2 = trapz(time, ndot_CO2) ; 647 | 648 | % calculate the average temperature of the gas [K]. Only important 649 | % if the emissions are being used in another step in the cycle (e.g. 650 | % light reflux, light product pressurization) 651 | energy_flux_tot = ndot_tot.*T ; 652 | energy_tot = trapz(time, energy_flux_tot) ; 653 | Temp = energy_tot/n_tot ; 654 | % 655 | end 656 | 657 | function [purity, recovery, mass_balance] = ProcessEvaluation(varargin) 658 | %ProcessEvaluation: Calculate the process objectives 659 | % Calculate the purity and the recovery of the heavy product and the 660 | % overall mass balance of the heavy product for the cycle. 661 | % 662 | % Input: 663 | % a-e : The dimensionless state variables of each step for the 664 | % cycle 665 | % t1-t5 : The time steps for each step of the cycle 666 | % 667 | % Output: 668 | % purity : Purity of the heavy product 669 | % recovery : Recovery of the heavy product 670 | % mass_balance: Mass balance of the heavy product (ensure all heavy 671 | % product that enters, leaves. No accumulation) 672 | % 673 | %% 674 | step = cell(nargin/2,1) ; 675 | tau = cell(nargin/2,1) ; 676 | for st = 1:nargin/2 677 | step{st} = varargin{st} ; 678 | tau{st} = varargin{nargin/2+st} ; 679 | end 680 | % 681 | %% Calculate total moles going in and out of column [mols/m^2] 682 | 683 | [~, n_CO2_CoCPres_HPEnd, ~] = StreamCompositionCalculator(tau{1}, step{1}, 'HPEnd') ; 684 | [~, n_CO2_ads_HPEnd, ~] = StreamCompositionCalculator(tau{2}, step{2}, 'HPEnd') ; 685 | [~, n_CO2_ads_LPEnd, ~] = StreamCompositionCalculator(tau{2}, step{2}, 'LPEnd') ; 686 | [~, n_CO2_HR_LPEnd, ~] = StreamCompositionCalculator(tau{3}, step{3}, 'LPEnd') ; 687 | [~, n_CO2_HR_HPEnd, ~] = StreamCompositionCalculator(tau{3}, step{3}, 'HPEnd') ; 688 | [n_tot_CnCDepres_HPEnd, n_CO2_CnCDepres_HPEnd, ~] = StreamCompositionCalculator(tau{4}, step{4}, 'HPEnd') ; 689 | [~, n_CO2_LR_LPEnd, ~] = StreamCompositionCalculator(tau{5}, step{5}, 'LPEnd') ; 690 | [n_tot_LR_HPEnd, n_CO2_LR_HPEnd, ~] = StreamCompositionCalculator(tau{5}, step{5}, 'HPEnd') ; 691 | % 692 | %% Calculate Purity, recovery and mass balance of the column 693 | 694 | purity = (n_CO2_CnCDepres_HPEnd+(1-beta)*n_CO2_LR_HPEnd)/(n_tot_CnCDepres_HPEnd+(1-beta)*n_tot_LR_HPEnd) ; 695 | recovery = (n_CO2_CnCDepres_HPEnd+(1-beta)*n_CO2_LR_HPEnd)/(n_CO2_CoCPres_HPEnd+n_CO2_ads_HPEnd) ; 696 | 697 | mass_balance = (n_CO2_CnCDepres_HPEnd+n_CO2_ads_LPEnd+n_CO2_HR_LPEnd+n_CO2_LR_HPEnd)/... 698 | (n_CO2_CoCPres_HPEnd+n_CO2_ads_HPEnd+n_CO2_HR_HPEnd+n_CO2_LR_LPEnd) ; 699 | % 700 | end 701 | 702 | function energy = CompressionEnergy(time, state_vars, Patm) 703 | %CompressionEnergy: Calculate the compression energy from entrance 704 | % Calculate the total energy required by the compressor to increase 705 | % the pressure to the desired value. Intended to be used with PSA 706 | % dimensionless simulations. 707 | % 708 | % Input: 709 | % time : Dimensional time vector supplied by the ODE solver 710 | % state_vars: Dimensionless state variable matrix supplied for the 711 | % step from the ODE solver 712 | % Patm : Atmospheric pressure [Pa]. This value can also be changed 713 | % if the flue gas is at a higher pressure. This value is 714 | % the cutoff for the compressor. Above this value, energy 715 | % is required. Below his value, no energy is required 716 | % 717 | % Output: 718 | % energy : Energy Requirments [kWh] 719 | % 720 | %% Check number of inputs 721 | 722 | % Differential section length of the column 723 | dz = L/N ; 724 | 725 | % Compresor parameters 726 | adiabatic_index = 1.4 ; 727 | compressor_efficiency = 0.72 ; 728 | 729 | % Calculate the pressure gradient at the edges [Pa/m] 730 | P = state_vars(:, 1:2)*P_0 ; 731 | y = state_vars(:, N+3) ; 732 | T = state_vars(:, 4*N+9)*T_0 ; 733 | 734 | % Calculate the density of the gas [kg/m^3] 735 | ro_g = (y*MW_CO2 + (1-y)*MW_N2).*P(:, 1)/R./T ; 736 | 737 | dPdz = 2*(P(:, 2)-P(:, 1))/dz ; 738 | 739 | % calculate superficial velocity using ergun equation [m/s] 740 | viscous_term = 150*mu*(1-epsilon)^2/4/r_p^2/epsilon^3 ; 741 | kinetic_term = (1.75*(1-epsilon)/2/r_p/epsilon^3) * ro_g ; 742 | v = -sign(dPdz).*(-viscous_term+(abs(viscous_term^2+... 743 | 4*kinetic_term.*abs(dPdz))).^(.5))/2./kinetic_term ; 744 | 745 | % Calculate the compression ratio along with the impact it has on the 746 | % energy requirments 747 | ratio_term = ((P(:, 1)/Patm).^((adiabatic_index-1)/adiabatic_index)-1) ; 748 | ratio_term = max(ratio_term, 0) ; 749 | 750 | %Calculate the total energy required by the compressor 751 | integral_term = abs(v.*P(:, 1).*ratio_term) ; 752 | 753 | energy = trapz(time, integral_term).*((adiabatic_index)./(adiabatic_index-1))./compressor_efficiency*pi()*r_in.^2 ; 754 | 755 | energy = energy/3.6e6 ; 756 | end 757 | 758 | function energy = VacuumEnergy(time, state_vars, Patm, ProductEnd) 759 | %VacuumEnergy: Calculate the vacuum energy at both ends of the column 760 | % Calculate the total energy required by the vacuum pump to operate 761 | % at the desired pressure. Intended to be used with PSA dimensionless 762 | % simulations. 763 | % 764 | % Input: 765 | % time : Dimensional time vector supplied by the ODE solver 766 | % state_vars: Dimensionless state variable matrix supplied for the 767 | % step from the ODE solver 768 | % ProductEnd: End of the column where the composition will be 769 | % calculated. OPTIONS: HPEnd and LPEnd. HPEnd stands for 770 | % heavy product end, which is at the bottom of the column, 771 | % and LPEnd stands for light product end, which is at the 772 | % top of the column. 773 | % Patm : Atmospheric pressure [Pa].This value is the cutoff for 774 | % the vacuum. Above this value, energy is not required. 775 | % Below his value, energy is required 776 | % 777 | % Output: 778 | % energy : Energy Requirments [kWh] 779 | % 780 | %% Check number of inputs 781 | % If no value is given for the ProductEnd, this is set up by default 782 | % as HPEnd, which is the heavy product end for any step. 783 | if nargin < 4 784 | ProductEnd = 'HPEnd'; 785 | end 786 | % 787 | %% 788 | % Differential section length of the column 789 | dz = L/N ; 790 | 791 | % Vacuum parameters 792 | adiabatic_index = 1.4 ; 793 | vacuum_efficiency = 0.72 ; 794 | 795 | % Dimensionalize all variables at the two ends of the columns 796 | % Collect pressure, temperature and mole fraction at the column end 797 | % of interest. 798 | if strcmpi(ProductEnd, 'HPEnd') == 1 799 | 800 | P = state_vars(:, 1:2)*P_0 ; 801 | y = state_vars(:, N+3) ; 802 | T = state_vars(:, 4*N+9)*T_0 ; 803 | 804 | % Calculate the density of the gas [kg/m^3] 805 | ro_g = (y*MW_CO2 + (1-y)*MW_N2).*P(:, 1)/R./T ; 806 | 807 | P_out = P(:, 1) ; 808 | 809 | elseif strcmpi(ProductEnd, 'LPEnd') == 1 810 | 811 | P = state_vars(:, N+1:N+2)*P_0 ; 812 | y = state_vars(:, 2*N+4) ; 813 | T = state_vars(:, 5*N+10)*T_0 ; 814 | 815 | % calculate the density of the gas [kg/m^3] 816 | ro_g = (y*MW_CO2 + (1-y)*MW_N2).*P(:, 2)/R./T ; 817 | 818 | P_out = P(:, 2) ; 819 | 820 | else 821 | error('Please specify in which end of the column the composition will be calculated. OPTIONS: HPEnd and LPEnd') 822 | end 823 | 824 | % Calculate the pressure gradient at the edges [Pa/m] 825 | dPdz = 2*(P(:, 2)-P(:, 1))/dz ; 826 | 827 | % calculate superficial velocity using ergun equation [m/s] 828 | viscous_term = 150*mu*(1-epsilon)^2/4/r_p^2/epsilon^3 ; 829 | kinetic_term = (1.75*(1-epsilon)/2/r_p/epsilon^3) * ro_g ; 830 | v = -sign(dPdz).*(-viscous_term+(abs(viscous_term^2+... 831 | 4*kinetic_term.*abs(dPdz))).^(.5))/2./kinetic_term ; 832 | 833 | % Calculate the compression ratio along with the impact it has on the 834 | % energy requirments 835 | ratio_term = ((Patm./P_out).^((adiabatic_index-1)/adiabatic_index)-1) ; 836 | ratio_term = max(ratio_term, 0) ; 837 | 838 | integral_term = abs(v.*P_out.*ratio_term) ; 839 | 840 | %Calculate the total energy required by the compressor 841 | energy=trapz(time, integral_term).*((adiabatic_index)./(adiabatic_index-1))./vacuum_efficiency*pi()*r_in.^2; 842 | 843 | energy = energy/3.6e6 ; 844 | % 845 | end 846 | 847 | function x_new = VelocityCorrection(x, n_hr, CorrectionEnd) 848 | %% Check number of inputs 849 | % If no value is given for the CorrectionEnd, this is set up by 850 | % default as HPEnd, which is the heavy product end for any step. 851 | if nargin < 3 852 | CorrectionEnd = 'HPEnd'; 853 | end 854 | % 855 | %% 856 | x_new = x ; 857 | 858 | % Differential section length of the column 859 | dz = L/N ; 860 | 861 | % Dimensionalize all variables at the two ends of the columns 862 | if strcmpi(CorrectionEnd, 'HPEnd') == 1 863 | 864 | T = x(:, 4*N+9)*T_0 ; 865 | y = x(:, N+3) ; 866 | P = x(:, 2)*P_0 ; 867 | 868 | elseif strcmpi(CorrectionEnd, 'LPEnd') == 1 869 | 870 | T = x(:, 5*N+10)*T_0 ; 871 | y = x(:, 2*N+4) ; 872 | P = x(:, N+1)*P_0 ; 873 | 874 | else 875 | error('Please specify in which end of the column the velocity correction will be calculated. OPTIONS: HPEnd and LPEnd') 876 | end 877 | 878 | MW = MW_N2+(MW_CO2-MW_N2)*y ; 879 | 880 | a_1 = 150*mu*(1-epsilon)^2*dz/2/4/r_p^2/epsilon^3/R./T ; 881 | a_2_1 = 1.75*(1-epsilon)/2/r_p/epsilon/epsilon/epsilon*dz/2 ; 882 | a_2 = a_2_1/R./T*n_hr.*MW ; 883 | 884 | a_a = a_1+a_2 ; 885 | b_b = P./T/R ; 886 | c_c = -n_hr ; 887 | 888 | vh = (-b_b+sqrt(b_b.^2-4.*a_a.*c_c))/2./a_a ; 889 | 890 | a_p = a_1.*T*R ; 891 | b_p = a_2_1.*MW/R./T ; 892 | 893 | % Correction 894 | if strcmpi(CorrectionEnd, 'HPEnd') == 1 895 | 896 | x_new(:, 1) = ((a_p.*vh+P)./(1-b_p.*vh.*vh))./P_0 ; 897 | 898 | elseif strcmpi(CorrectionEnd, 'LPEnd') == 1 899 | 900 | x_new(:, N+2) = ((a_p.*vh+P)./(1-b_p.*vh.*vh))./P_0 ; 901 | 902 | end 903 | % 904 | end 905 | 906 | function x_new = velocitycleanup(x) 907 | 908 | x_new=x; 909 | numb1=150*mu*(1-epsilon)^2/4/r_p^2/epsilon^2; 910 | ro_gent=x(:,2).*P_0/R/T_0; 911 | numb2_ent=(ro_gent.*(MW_N2+(MW_CO2-MW_N2)*x(:,N+3)).*(1.75*(1-epsilon)/2/r_p/epsilon)); 912 | 913 | 914 | x_new(:,1)=(numb1*v_0+numb2_ent*v_0*v_0)*L/P_0/2/N + x(:,2); 915 | end 916 | % 917 | %% Jacobian patterns functions 918 | 919 | function J_pres = JacPressurization(N) 920 | %JacPressurization: Calculates a Jacobian pattern for the step 921 | % This function calculates a jacobian pattern for the pressurization 922 | % step. This is to be used with the ode solver to decrease the 923 | % computational time 924 | % 925 | % Input: 926 | % N : Number of finite volumes used in the column 927 | % Output: 928 | % J_pres: The sparse Jacobian pattern scheme 929 | % 930 | %% Create individual segments 931 | % Four band Jacobian scheme for advection terms 932 | B4 = ones(N+2, 4) ; 933 | A4 = full(spdiags(B4, -2:1, N+2, N+2)) ; 934 | 935 | % One band Jacobian scheme for adsorption/desoprtion term 936 | B1 = ones(N+2, 1) ; 937 | A1 = full(spdiags(B1, 0, N+2, N+2)) ; 938 | A1(1, 1) = 0 ; 939 | A1(N+2, N+2) = 0 ; 940 | 941 | % Zero band Jacobian Term 942 | A0 = zeros(N+2) ; 943 | % 944 | %% Create Overall Jacobian based on individual segments 945 | J_pres = [ A4, A4, A1, A1, A4;... 946 | A4, A4, A1, A1, A4;... 947 | A1, A1, A1, A0, A1;... 948 | A1, A1, A0, A1, A1;... 949 | A4, A1, A1, A1, A4 ] ; 950 | % 951 | %% Modify based on boundary conditions 952 | % Pressure Inlet 953 | J_pres(1, :) = 0 ; 954 | J_pres(1, 1) = 1 ; 955 | 956 | % Pressure Outlet 957 | J_pres(N+2, :) = J_pres(N+1, :) ; 958 | J_pres(:, N+2) = 0 ; 959 | 960 | % Mole Fraction Inlet 961 | J_pres(N+3, :) = 0 ; 962 | J_pres(:, N+3) = 0 ; 963 | 964 | % Mole Fraction Outlet 965 | J_pres(2*N+4, :) = J_pres(2*N+3, :) ; 966 | J_pres(:, 2*N+4) = 0 ; 967 | 968 | % Temperature Inlet 969 | J_pres(4*N+9, :) = 0 ; 970 | J_pres(:, 4*N+9) = 0 ; 971 | 972 | % Temperature Outlet 973 | J_pres(5*N+10, :) = J_pres(5*N+9, :) ; 974 | J_pres(:, 5*N+10) = 0 ; 975 | 976 | J_pres=sparse(J_pres); 977 | % 978 | end 979 | 980 | function J_ads = JacAdsorption(N) 981 | %JacAdsorption: Calculates a Jacobian pattern for the step 982 | % This function calculates a jacobian pattern for the pressurization 983 | % step. This is to be used with the ode solver to decrease the 984 | % computational time 985 | % 986 | % Input: 987 | % N : Number of finite volumes used in the column 988 | % Output: 989 | % J_ads: The sparse Jacobian pattern scheme 990 | % 991 | %% Create individual segments 992 | % Four band Jacobian scheme for advection terms 993 | B4 = ones(N+2, 4) ; 994 | A4 = full(spdiags(B4, -2:1, N+2, N+2)) ; 995 | 996 | % One band Jacobian scheme for adsorption/ desoprtion term 997 | B1 = ones(N+2, 1) ; 998 | A1 = full(spdiags(B1, 0, N+2, N+2)) ; 999 | A1(1, 1) = 0 ; 1000 | A1(N+2, N+2) = 0 ; 1001 | 1002 | % Zero band Jacobian Term 1003 | A0 = zeros(N+2) ; 1004 | % 1005 | %% Create Overall Jacobian based on individual segments 1006 | J_ads = [ A4, A4, A1, A1, A4;... 1007 | A4, A4, A1, A1, A4;... 1008 | A1, A1, A1, A0, A1;... 1009 | A1, A1, A0, A1, A1;... 1010 | A4, A1, A1, A1, A4 ] ; 1011 | % 1012 | %% Modify based on boundary conditions 1013 | % Pressure Inlet 1014 | J_ads(1, :) = 0 ; 1015 | J_ads(:, 1) = 0 ; 1016 | 1017 | % Pressure Outlet 1018 | J_ads(N+2, :) = 0 ; 1019 | J_ads(:, N+2) = 0 ; 1020 | 1021 | % Mole Fraction Inlet 1022 | J_ads(N+3, :) = 0 ; 1023 | J_ads(:, N+3) = 0 ; 1024 | 1025 | % Mole Fraction Outlet 1026 | J_ads(2*N+4, :) = J_ads(2*N+3, :) ; 1027 | J_ads(:, 2*N+4) = 0 ; 1028 | 1029 | % Temperature Inlet 1030 | J_ads(4*N+9, :) = 0 ; 1031 | J_ads(:, 4*N+9) = 0 ; 1032 | 1033 | % Temperature Outlet 1034 | J_ads(5*N+10, :) = J_ads(5*N+9, :) ; 1035 | J_ads(:, 5*N+10) = 0 ; 1036 | 1037 | J_ads = sparse(J_ads) ; 1038 | % 1039 | end 1040 | 1041 | function J_CnCdepres = Jac_CnCDepressurization(N) 1042 | %Jac_CnCDepressurization: Calculates a Jacobian pattern for the step 1043 | % This function calculates a jacobian pattern for the pressurization 1044 | % step. This is to be used with the ode solver to decrease the 1045 | % computational time 1046 | % 1047 | % Input: 1048 | % N : Number of finite volumes used in the column 1049 | % Output: 1050 | % J_CnCdepres: The sparse Jacobian pattern scheme 1051 | % 1052 | %% Create individual segments 1053 | % Four band Jacobian scheme for advection terms 1054 | B4 = ones(N+2, 4) ; 1055 | A4 = full(spdiags(B4, -1:2, N+2, N+2)) ; 1056 | A4(1, :) = A4(2, :) ; 1057 | A4(N+2, :) = A4(N+1, :) ; 1058 | 1059 | % One band Jacobian scheme for adsorption/ desoprtion term 1060 | B1 = ones(N+2, 1) ; 1061 | A1 = full(spdiags(B1, 0, N+2, N+2)) ; 1062 | A1(1, 1) = 0 ; 1063 | A1(N+2, N+2) = 0 ; 1064 | A1(1, 2) = 1 ; 1065 | A1(N+2, N+1) = 1 ; 1066 | 1067 | % Zero band Jacobian Term 1068 | A0 = zeros(N+2) ; 1069 | % 1070 | %% Create Overall Jacobian based on individual segments 1071 | J_CnCdepres = [ A4, A4, A1, A1, A4;... 1072 | A4, A4, A1, A1, A4;... 1073 | A1, A1, A1, A0, A1;... 1074 | A1, A1, A0, A1, A1;... 1075 | A4, A1, A1, A1, A4 ] ; 1076 | % 1077 | %% Modify based on boundary conditions 1078 | % Pressure Inlet 1079 | J_CnCdepres(1, :) = 0 ; 1080 | J_CnCdepres(1, 1) = 1 ; 1081 | 1082 | % Pressure Outlet 1083 | J_CnCdepres(N+2, :) = J_CnCdepres(N+1, :) ; 1084 | 1085 | % Mole Fraction Inlet 1086 | J_CnCdepres(N+3, :) = J_CnCdepres(N+4, :) ; 1087 | 1088 | % Mole Fraction Outlet 1089 | J_CnCdepres(2*N+4) = J_CnCdepres(2*N+3) ; 1090 | 1091 | % Molar Loading 1092 | J_CnCdepres(2*N+5, :) = 0 ; 1093 | J_CnCdepres(3*N+6:3*N+7, :) = 0 ; 1094 | J_CnCdepres(4*N+8, :) = 0 ; 1095 | 1096 | % Temperature Inlet 1097 | J_CnCdepres(4*N+9, :) = J_CnCdepres(4*N+10, :) ; 1098 | 1099 | %Temperature Outlet 1100 | J_CnCdepres(5*N+10, :) = J_CnCdepres(5*N+9) ; 1101 | 1102 | J_CnCdepres = sparse(J_CnCdepres) ; 1103 | % 1104 | end 1105 | 1106 | function J_LR = Jac_LightReflux(N) 1107 | %Jac_LightReflux: Calculates a Jacobian pattern for the step 1108 | % This function calculates a jacobian pattern for the pressurization 1109 | % step. This is to be used with the ode solver to decrease the 1110 | % computational time 1111 | % 1112 | % Input: 1113 | % N : Number of finite volumes used in the column 1114 | % Output: 1115 | % J_LR: The sparse Jacobian pattern scheme 1116 | % 1117 | %% Create individual segments 1118 | % Four band Jacobian scheme for advection terms 1119 | B4 = ones(N+2, 4) ; 1120 | A4 = full(spdiags(B4, -1:2, N+2, N+2)) ; 1121 | A4(1, :) = A4(2, :) ; 1122 | A4(N+2, :) = A4(N+1, :) ; 1123 | 1124 | % One band Jacobian scheme for adsorption/ desoprtion term 1125 | B1 = ones(N+2, 1) ; 1126 | A1 = full(spdiags(B1, 0, N+2, N+2)) ; 1127 | A1(1, 1) = 0 ; 1128 | A1(N+2, N+2) = 0 ; 1129 | A1(1, 2) = 1 ; 1130 | A1(N+2, N+1) = 1 ; 1131 | 1132 | % Zero band Jacobian Term 1133 | A0 = zeros(N+2) ; 1134 | % 1135 | %% Create Overall Jacobian based on individual segments 1136 | J_LR = [ A4, A1, A1, A1, A4;... 1137 | A4, A4, A1, A1, A4;... 1138 | A1, A1, A1, A0, A1;... 1139 | A1, A1, A0, A1, A1;... 1140 | A4, A1, A1, A1, A4 ] ; 1141 | % 1142 | %% Modify based on boundary conditions 1143 | % Pressure Inlet 1144 | J_LR(1, :) = 0 ; 1145 | J_LR(1, 1) = 1 ; 1146 | 1147 | % Pressure Outlet 1148 | J_LR(N+2, :) = J_LR(N+1, :) ; 1149 | 1150 | % Mole Fraction Inlet 1151 | J_LR(N+3, :) = J_LR(N+4, :) ; 1152 | 1153 | % Mole Fraction Outlet 1154 | J_LR(2*N+4) = J_LR(2*N+3) ; 1155 | 1156 | % Molar Loading 1157 | J_LR(2*N+5, :) = 0 ; 1158 | J_LR(3*N+6:3*N+7, :) = 0 ; 1159 | J_LR(4*N+8, :) = 0 ; 1160 | 1161 | % Temperature Inlet 1162 | J_LR(4*N+9, :) = J_LR(4*N+10, :) ; 1163 | 1164 | % Temperature Outlet 1165 | J_LR(5*N+10, :) = J_LR(5*N+9) ; 1166 | 1167 | J_LR = sparse(J_LR) ; 1168 | % 1169 | end 1170 | % 1171 | %% Press and Depress Termination functions 1172 | 1173 | function [value, isterminal, direction] = PressurizationStop(~, y) 1174 | %PressurizationStop: Function used with ode15s to stop the step 1175 | % Termination function for the depressurization step to stop the 1176 | % solution once the desired pressure is reached 1177 | % 1178 | %% 1179 | PressureOutlet = y(N+2) ; 1180 | value = 0.99-PressureOutlet ; 1181 | isterminal = 1 ; 1182 | direction = 0 ; 1183 | % 1184 | end 1185 | 1186 | function [value, isterminal, direction] = CnCDepressurizationStop(~, y) 1187 | %CnCDepressurizationStop: Function used with ode15s to stop the step 1188 | % Termination function for the CnC depressurization step to stop the 1189 | % solution once the desired pressure is reached 1190 | % 1191 | %% 1192 | PressureOutlet = real(y(N+2)) ; 1193 | value = PressureOutlet-1.9*P_l/P_0 ; 1194 | isterminal = 1 ; 1195 | direction = -1 ; 1196 | % 1197 | end 1198 | % 1199 | end -------------------------------------------------------------------------------- /PSACycleSimulation.m: -------------------------------------------------------------------------------- 1 | function [ objectives, constraints ] = PSACycleSimulation( x, material, type, N) 2 | 3 | % % Retrieve required process variables provided as inputs of the function 4 | % L = process_vars(1) ; % Length of the column [m] 5 | % P_0 = process_vars(2) ; % Adsorption pressure [Pa] 6 | % ndot_0 = process_vars(3) ; % Inlet molar flux [mol/s/m^2] 7 | % t_ads = process_vars(4) ; % Time of adsorption step [s] 8 | % alpha = process_vars(5) ; % Light product reflux ratio [-] 9 | % beta = process_vars(6) ; % Heavy product reflux ratio [-] 10 | % P_I = process_vars(7) ; % Intermediate pressure [Pa] 11 | % P_l = process_vars(8) ; % Purge Pressure [Pa] 12 | 13 | process_variables = [1.0, x(1), x(1)*x(4)/8.314/313.15, x(2), x(3), x(5), 1e4, x(6)] ; 14 | 15 | try 16 | [objectives, constraints] = PSACycle(process_variables, material, [], type, N) ; 17 | catch 18 | %warning('Problem using function. Assigning a value of 0 for objectives and constraints vialations'); 19 | objectives(1) = 1e5 ; 20 | objectives(2) = 1e5 ; 21 | constraints(1) = 1 ; 22 | constraints(2) = 1 ; 23 | constraints(3) = 1 ; 24 | end 25 | 26 | end 27 | 28 | -------------------------------------------------------------------------------- /Params.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PEESEgroup/PSA/3de0832320971656dd79d9c7473d4029a10fee97/Params.mat -------------------------------------------------------------------------------- /ProcessInputParameters.m: -------------------------------------------------------------------------------- 1 | function InputParams = ProcessInputParameters(process_vars, material, N) 2 | %InputParameters: Specify input parameters for PSA simulation 3 | % Number of finite volumes is always an input from the calling script/ 4 | % function. In addition, x is used to allow varying specific parameters 5 | % (times, pressures, heats of adsorptions, etc.) from the calling script/ 6 | % function. It is not necessary for simple simulations. 7 | 8 | %% State all input parameters for the simulation 9 | 10 | % Retrieve required process variables provided as inputs of the function 11 | L = process_vars(1) ; % Length of the column [m] 12 | P_0 = process_vars(2) ; % Adsorption pressure [Pa] 13 | ndot_0 = process_vars(3) ; % Inlet molar flux [mol/s/m^2] 14 | t_ads = process_vars(4) ; % Time of adsorption step [s] 15 | alpha = process_vars(5) ; % Light product reflux ratio [-] 16 | beta = process_vars(6) ; % Heavy product reflux ratio [-] 17 | P_I = process_vars(7) ; % Intermediate pressure [Pa] 18 | P_l = process_vars(8) ; % Purge Pressure [Pa] 19 | 20 | % Retrieving Params that depend on the adsorbent material 21 | material_propertry = material{1} ; 22 | IsothermPar = material{2} ; 23 | 24 | % Operating bed parameters 25 | t_pres = 20 ; % Maximum/time of pressurization step [s] 26 | t_CnCdepres = 30 ; % Maximum/time of depressurization step [s] 27 | t_CoCdepres = 70 ; % Maximum/time of depressurization step [s] 28 | t_LR = t_ads ; % Time of light reflux step [s] 29 | t_HR = t_LR ; % Time of heavy reflux step [s] 30 | tau = 0.5 ; % Parameter used for determining speed of pressure change 31 | P_inlet = 1.02 ; % Pressure of feed gas at the inlet of the adsorption step 32 | 33 | % Flue gas parameters and constants 34 | R = 8.314 ; % Universal gas constant [J/mol/K : Pa*m^3/mol/K] 35 | T_0 = 313.15 ; % Feed temperature of flue gas [K] 36 | y_0 = 0.15 ; % Inlet gas CO2 mole fraction[-] 37 | Ctot_0 = P_0/R/T_0 ; % Inlet total concentration [mol/m^3] 38 | v_0 = ndot_0/Ctot_0 ; % Inlet velocity and scaling parameter [m/s] 39 | mu = 1.72e-5 ; % Viscosity of gas [Pa*s] 40 | epsilon = 0.37 ; % Void fraction 41 | D_m = 1.2995e-5 ; % Molecular diffusivity [m^2/s] 42 | K_z = 0.09 ; % Thermal conduction in gas phase [W/m/k] 43 | C_pg = 30.7 ; % Specific heat of gas [J/mol/k] 44 | C_pa = 30.7 ; % Specific heat of adsorbed phase [J/mol/k] 45 | MW_CO2 = 0.04402 ; % Molecular weight of CO2 [kg/mol] 46 | MW_N2 = 0.02802 ; % Molecular weight of N2 [kg/mol] 47 | %feed_gas = 'Constant Pressure' ; % Whether flue gas during the feed step has a constant pressure or velocity 48 | feed_gas = 'Constant Velocity' ; % Whether flue gas during the feed step has a constant pressure or velocity 49 | 50 | % Adsorbent parameters 51 | % ro_s = 1130 ; % Density of the adsorbent [kg/m^3] 52 | ro_s = material_propertry(1) ; 53 | r_p = 1e-3 ; % Radius of the pellets [m] 54 | C_ps = 1070 ; % Specific heat capacity of the adsorbent [J/kg/K] 55 | q_s = 5.84 ; % Molar loading scaling factor [mol/kg] 56 | q_s0 = q_s*ro_s ; % Molar loading scaling factor [mol/m^3] 57 | k_CO2_LDF = 0.1631 ; % Mass transfer coefficient for CO2 [1/s] 58 | k_N2_LDF = 0.2044 ; % Mass transfer coefficient for N2 [1/s] 59 | 60 | % Isotherm parameters 61 | q_s_b = [IsothermPar(1), IsothermPar(7)] ; % Saturation loading on site b [mol/kg] 62 | q_s_d = [IsothermPar(2), IsothermPar(8)] ; % Saturation loading on site d [mol/kg] 63 | b = [IsothermPar(3), IsothermPar(9)] ; % Pre-exponential factor for site b [Pa-1] 64 | d = [IsothermPar(4), IsothermPar(10)] ; % Pre-exponential factor for site d [Pa-1] 65 | deltaU_b = [IsothermPar(5), IsothermPar(11)] ; % Heat of adsorption for site b [J/mol] 66 | deltaU_d = [IsothermPar(6), IsothermPar(12)] ; % Heat of adsorption for site d [J/mol] 67 | 68 | % deltaU = [-36000, -15800] ; 69 | deltaU = [material_propertry(2), material_propertry(3)] ; 70 | 71 | 72 | 73 | %% Distribute the values to the necessary variables 74 | Params = zeros(39, 1) ; 75 | Params(1) = N ; 76 | Params(2) = deltaU(1) ; 77 | Params(3) = deltaU(2) ; 78 | Params(4) = ro_s ; 79 | Params(5) = T_0 ; 80 | Params(6) = epsilon ; 81 | Params(7) = r_p ; 82 | Params(8) = mu ; 83 | Params(9) = R ; 84 | Params(10) = v_0 ; 85 | Params(11) = q_s0 ; 86 | Params(12) = C_pg ; 87 | Params(13) = C_pa ; 88 | Params(14) = C_ps ; 89 | Params(15) = D_m ; 90 | Params(16) = K_z ; 91 | Params(17) = P_0 ; 92 | Params(18) = L ; 93 | Params(19) = MW_CO2 ; 94 | Params(20) = MW_N2 ; 95 | Params(21) = k_CO2_LDF ; 96 | Params(22) = k_N2_LDF ; 97 | Params(23) = y_0 ; 98 | Params(24) = tau ; 99 | Params(25) = P_l ; 100 | Params(26) = P_inlet ; 101 | Params(27) = 1 ; % Place for y at outlet of Adsorption = y at inlet of Light Reflux: y_LP 102 | % y_LR = 1 - No initial guess for inlet CO2 mole fraction in Light Reflux step 103 | Params(28) = 1 ; % Place for T at outlet of Adsorption = T at inlet of Light Reflux: T_LP 104 | % T_LR = 1 - No initial guess for inlet temperature in Light Reflux step 105 | Params(29) = 1 ; % Place for ndot at outlet of Adsorption = ndot at inlet of Light Reflux 106 | % ndot_LR = 1 - No initial guess for inlet ndotin Light Reflux step 107 | Params(30) = alpha ; 108 | Params(31) = beta ; 109 | Params(32) = P_I ; 110 | Params(33) = y_0 ; % Place for y at outlet of CnC depressurization = y at inlet of of Heavy Reflux: y_HP 111 | % y_HR = y_0 - Initial guess for inlet CO2 mole fraction in Heavy Reflux step 112 | Params(34) = T_0 ; % Place for T at outlet of CnC depressurization = T at inlet of of Heavy Reflux: T_HP 113 | % T_HR = T_0 - Initial guess for inlet temperature in Heavy Reflux step 114 | Params(35) = ndot_0*beta ; % 300/30 % Place for ndot at outlet of CnC depressurization = ndot at inlet of Heavy Reflux 115 | % ndot_HR = ndot_0*beta - Initial guess for inlet ndotin Heavy Reflux step 116 | Params(36) = 0.01 ; % Place for y at outlet of Adsorption = y at inlet of CnC pressurization: y_LP 117 | % y_LR = 0.01 - Initial guess for inlet CO2 mole fraction in CnC pressurization step 118 | Params(37) = T_0 ; % Place for T at outlet of Adsorption = T at inlet of CnC pressurization: T_LP 119 | % T_LR = T_0 - Initial guess for inlet temperature in CnC pressurization step 120 | Params(38) = ndot_0 ; % Place for ndot at outlet of Adsorption = ndot at inlet of CnC pressurization 121 | % NOTE: not used, seems not necessary. ndot_LR = ndot_0 - Initial guess for inlet ndot 122 | % in CnC pressurization step 123 | 124 | if strcmpi(feed_gas, 'Constant Pressure') == 1 125 | Params(end) = 1 ; 126 | elseif strcmpi(feed_gas, 'Constant Velocity') == 1 127 | Params(end) = 0 ; 128 | else 129 | error('Please specify whether inlet velocity or pressure is constant for the feed step') 130 | end 131 | 132 | Times = [ t_pres; t_ads; t_CnCdepres; t_LR; t_CoCdepres; t_HR ] ; 133 | 134 | IsothermParams = [q_s_b, q_s_d, b, d, deltaU_b, deltaU_d, IsothermPar(13)] ; 135 | % 136 | 137 | %% Economic Parameters 138 | 139 | desired_flow = 100 ; % Desired flow rate of flue gas per column [mol/s] 140 | electricity_cost = 0.07 ; % Cost of electricity [$/kWh] 141 | hour_to_year_conversion = 8000 ; % total hours in a year, the remaining time is assumed to be down for maitenance [hr/year] 142 | life_span_equipment = 20 ; % Life Span of all equuipment besides adsorbent [years] 143 | life_span_adsorbent = 5 ; % Life Span of adsorbent [years] 144 | CEPCI = 536.4 ; % CEPCI of present month year (Jan 2016). 145 | 146 | % change this according to the cycle to be simulated 147 | cycle_time = t_pres + t_ads + t_HR + t_CnCdepres + t_LR ; % total time required for 1 cycle [s] 148 | 149 | EconomicParams = zeros(6, 1) ; 150 | EconomicParams(1) = desired_flow ; 151 | EconomicParams(2) = electricity_cost ; 152 | EconomicParams(3) = cycle_time ; 153 | EconomicParams(4) = hour_to_year_conversion ; 154 | EconomicParams(5) = life_span_equipment ; 155 | EconomicParams(6) = life_span_adsorbent ; 156 | EconomicParams(7) = CEPCI ; 157 | % 158 | %% Combine all lists into one variable of cells that can easily be passed 159 | InputParams{1} = Params ; 160 | InputParams{2} = IsothermParams ; 161 | InputParams{3} = Times ; 162 | InputParams{4} = EconomicParams ; 163 | % InputParams{5} = economic_class ; 164 | % 165 | end -------------------------------------------------------------------------------- /ProcessOptimization.m: -------------------------------------------------------------------------------- 1 | clc 2 | format long 3 | parpool('local', 12); 4 | 5 | addpath('CycleSteps') 6 | addpath('GA_files') 7 | 8 | load('Params') 9 | 10 | N = 10 ; 11 | type = 'ProcessEvaluation' ; 12 | 13 | for i = 12:12 14 | 15 | % load parameters 16 | IsothermParams = IsothermPar(i, :) ; 17 | material_propertry = SimParam(i, :) ; 18 | 19 | material = {} ; 20 | material{1} = material_propertry ; 21 | material{2} = IsothermParams ; 22 | 23 | Function = @(x) PSACycleSimulation( x, material, type, N ) ; % Function to simulate the PSA cycle 24 | 25 | options = nsgaopt() ; % create default options structure 26 | options.popsize = 40 ; % populaion size 27 | options.maxGen = 60 ; % max generation 28 | 29 | options.vartype = [1, 1, 1, 1] ; 30 | options.outputfile = 'UTSA-16_Process.txt' ; 31 | 32 | options.numObj = 2 ; % number of objectives 33 | options.numVar = 4 ; % number of design variables 34 | options.numCons = 3 ; % number of constraints 35 | options.lb = [1e5, 10, 0.01, 0.1] ; % lower bound of x 36 | options.ub = [10e5, 1000, 0.99, 2] ; % upper bound of x 37 | options.nameObj = {'-purity','recovery'} ; % the objective names are showed in GUI window. 38 | options.objfun = Function ; % objective function handle 39 | 40 | options.useParallel = 'yes' ; % parallel computation is non-essential here 41 | options.poolsize = 12 ; % number of worker processes 42 | 43 | result = nsga2(options) ; % begin the optimization! 44 | 45 | end 46 | 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PSA 2 | PSA simulation code 3 | --------------------------------------------------------------------------------